1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 11.6(1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2017 Cisco Systems, Inc. or its affiliated entities. All Rights Reserved. 9 */ 10 /** 11 * This JavaScript library is made available to Cisco partners and customers as 12 * a convenience to help minimize the cost of Cisco Finesse customizations. 13 * This library can be used in Cisco Finesse deployments. Cisco does not 14 * permit the use of this library in customer deployments that do not include 15 * Cisco Finesse. Support for the JavaScript library is provided on a 16 * "best effort" basis via CDN. Like any custom deployment, it is the 17 * responsibility of the partner and/or customer to ensure that the 18 * customization works correctly and this includes ensuring that the Cisco 19 * Finesse JavaScript is properly integrated into 3rd party applications. 20 * Cisco reserves the right to make changes to the JavaScript code and 21 * corresponding API as part of the normal Cisco Finesse release cycle. The 22 * implication of this is that new versions of the JavaScript might be 23 * incompatible with applications built on older Finesse integrations. That 24 * said, it is Cisco's intention to ensure JavaScript compatibility across 25 * versions as much as possible and Cisco will make every effort to clearly 26 * document any differences in the JavaScript across versions in the event 27 * that a backwards compatibility impacting change is made. 28 */ 29 (function (root, factory) { 30 if (typeof define === 'function' && define.amd) { 31 define('finesse', [], factory); 32 } else { 33 root.finesse = factory(); 34 } 35 }(this, function () { 36 /** 37 * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 38 * Available via the MIT or new BSD license. 39 * see: http://github.com/jrburke/almond for details 40 */ 41 //Going sloppy to avoid 'use strict' string cost, but strict practices should 42 //be followed. 43 /*jslint sloppy: true */ 44 /*global setTimeout: false */ 45 46 var requirejs, require, define; 47 (function (undef) { 48 var main, req, makeMap, handlers, 49 defined = {}, 50 waiting = {}, 51 config = {}, 52 defining = {}, 53 hasOwn = Object.prototype.hasOwnProperty, 54 aps = [].slice, 55 jsSuffixRegExp = /\.js$/; 56 57 function hasProp(obj, prop) { 58 return hasOwn.call(obj, prop); 59 } 60 61 /** 62 * Given a relative module name, like ./something, normalize it to 63 * a real name that can be mapped to a path. 64 * @param {String} name the relative name 65 * @param {String} baseName a real name that the name arg is relative 66 * to. 67 * @returns {String} normalized name 68 */ 69 function normalize(name, baseName) { 70 var nameParts, nameSegment, mapValue, foundMap, lastIndex, 71 foundI, foundStarMap, starI, i, j, part, 72 baseParts = baseName && baseName.split("/"), 73 map = config.map, 74 starMap = (map && map['*']) || {}; 75 76 //Adjust any relative paths. 77 if (name && name.charAt(0) === ".") { 78 //If have a base name, try to normalize against it, 79 //otherwise, assume it is a top-level require that will 80 //be relative to baseUrl in the end. 81 if (baseName) { 82 //Convert baseName to array, and lop off the last part, 83 //so that . matches that "directory" and not name of the baseName's 84 //module. For instance, baseName of "one/two/three", maps to 85 //"one/two/three.js", but we want the directory, "one/two" for 86 //this normalization. 87 baseParts = baseParts.slice(0, baseParts.length - 1); 88 name = name.split('/'); 89 lastIndex = name.length - 1; 90 91 // Node .js allowance: 92 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 93 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 94 } 95 96 name = baseParts.concat(name); 97 98 //start trimDots 99 for (i = 0; i < name.length; i += 1) { 100 part = name[i]; 101 if (part === ".") { 102 name.splice(i, 1); 103 i -= 1; 104 } else if (part === "..") { 105 if (i === 1 && (name[2] === '..' || name[0] === '..')) { 106 //End of the line. Keep at least one non-dot 107 //path segment at the front so it can be mapped 108 //correctly to disk. Otherwise, there is likely 109 //no path mapping for a path starting with '..'. 110 //This can still fail, but catches the most reasonable 111 //uses of .. 112 break; 113 } else if (i > 0) { 114 name.splice(i - 1, 2); 115 i -= 2; 116 } 117 } 118 } 119 //end trimDots 120 121 name = name.join("/"); 122 } else if (name.indexOf('./') === 0) { 123 // No baseName, so this is ID is resolved relative 124 // to baseUrl, pull off the leading dot. 125 name = name.substring(2); 126 } 127 } 128 129 //Apply map config if available. 130 if ((baseParts || starMap) && map) { 131 nameParts = name.split('/'); 132 133 for (i = nameParts.length; i > 0; i -= 1) { 134 nameSegment = nameParts.slice(0, i).join("/"); 135 136 if (baseParts) { 137 //Find the longest baseName segment match in the config. 138 //So, do joins on the biggest to smallest lengths of baseParts. 139 for (j = baseParts.length; j > 0; j -= 1) { 140 mapValue = map[baseParts.slice(0, j).join('/')]; 141 142 //baseName segment has config, find if it has one for 143 //this name. 144 if (mapValue) { 145 mapValue = mapValue[nameSegment]; 146 if (mapValue) { 147 //Match, update name to the new value. 148 foundMap = mapValue; 149 foundI = i; 150 break; 151 } 152 } 153 } 154 } 155 156 if (foundMap) { 157 break; 158 } 159 160 //Check for a star map match, but just hold on to it, 161 //if there is a shorter segment match later in a matching 162 //config, then favor over this star map. 163 if (!foundStarMap && starMap && starMap[nameSegment]) { 164 foundStarMap = starMap[nameSegment]; 165 starI = i; 166 } 167 } 168 169 if (!foundMap && foundStarMap) { 170 foundMap = foundStarMap; 171 foundI = starI; 172 } 173 174 if (foundMap) { 175 nameParts.splice(0, foundI, foundMap); 176 name = nameParts.join('/'); 177 } 178 } 179 180 return name; 181 } 182 183 function makeRequire(relName, forceSync) { 184 return function () { 185 //A version of a require function that passes a moduleName 186 //value for items that may need to 187 //look up paths relative to the moduleName 188 return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 189 }; 190 } 191 192 function makeNormalize(relName) { 193 return function (name) { 194 return normalize(name, relName); 195 }; 196 } 197 198 function makeLoad(depName) { 199 return function (value) { 200 defined[depName] = value; 201 }; 202 } 203 204 function callDep(name) { 205 if (hasProp(waiting, name)) { 206 var args = waiting[name]; 207 delete waiting[name]; 208 defining[name] = true; 209 main.apply(undef, args); 210 } 211 212 if (!hasProp(defined, name) && !hasProp(defining, name)) { 213 throw new Error('No ' + name); 214 } 215 return defined[name]; 216 } 217 218 //Turns a plugin!resource to [plugin, resource] 219 //with the plugin being undefined if the name 220 //did not have a plugin prefix. 221 function splitPrefix(name) { 222 var prefix, 223 index = name ? name.indexOf('!') : -1; 224 if (index > -1) { 225 prefix = name.substring(0, index); 226 name = name.substring(index + 1, name.length); 227 } 228 return [prefix, name]; 229 } 230 231 /** 232 * Makes a name map, normalizing the name, and using a plugin 233 * for normalization if necessary. Grabs a ref to plugin 234 * too, as an optimization. 235 */ 236 makeMap = function (name, relName) { 237 var plugin, 238 parts = splitPrefix(name), 239 prefix = parts[0]; 240 241 name = parts[1]; 242 243 if (prefix) { 244 prefix = normalize(prefix, relName); 245 plugin = callDep(prefix); 246 } 247 248 //Normalize according 249 if (prefix) { 250 if (plugin && plugin.normalize) { 251 name = plugin.normalize(name, makeNormalize(relName)); 252 } else { 253 name = normalize(name, relName); 254 } 255 } else { 256 name = normalize(name, relName); 257 parts = splitPrefix(name); 258 prefix = parts[0]; 259 name = parts[1]; 260 if (prefix) { 261 plugin = callDep(prefix); 262 } 263 } 264 265 //Using ridiculous property names for space reasons 266 return { 267 f: prefix ? prefix + '!' + name : name, //fullName 268 n: name, 269 pr: prefix, 270 p: plugin 271 }; 272 }; 273 274 function makeConfig(name) { 275 return function () { 276 return (config && config.config && config.config[name]) || {}; 277 }; 278 } 279 280 handlers = { 281 require: function (name) { 282 return makeRequire(name); 283 }, 284 exports: function (name) { 285 var e = defined[name]; 286 if (typeof e !== 'undefined') { 287 return e; 288 } else { 289 return (defined[name] = {}); 290 } 291 }, 292 module: function (name) { 293 return { 294 id: name, 295 uri: '', 296 exports: defined[name], 297 config: makeConfig(name) 298 }; 299 } 300 }; 301 302 main = function (name, deps, callback, relName) { 303 var cjsModule, depName, ret, map, i, 304 args = [], 305 callbackType = typeof callback, 306 usingExports; 307 308 //Use name if no relName 309 relName = relName || name; 310 311 //Call the callback to define the module, if necessary. 312 if (callbackType === 'undefined' || callbackType === 'function') { 313 //Pull out the defined dependencies and pass the ordered 314 //values to the callback. 315 //Default to [require, exports, module] if no deps 316 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 317 for (i = 0; i < deps.length; i += 1) { 318 map = makeMap(deps[i], relName); 319 depName = map.f; 320 321 //Fast path CommonJS standard dependencies. 322 if (depName === "require") { 323 args[i] = handlers.require(name); 324 } else if (depName === "exports") { 325 //CommonJS module spec 1.1 326 args[i] = handlers.exports(name); 327 usingExports = true; 328 } else if (depName === "module") { 329 //CommonJS module spec 1.1 330 cjsModule = args[i] = handlers.module(name); 331 } else if (hasProp(defined, depName) || 332 hasProp(waiting, depName) || 333 hasProp(defining, depName)) { 334 args[i] = callDep(depName); 335 } else if (map.p) { 336 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 337 args[i] = defined[depName]; 338 } else { 339 throw new Error(name + ' missing ' + depName); 340 } 341 } 342 343 ret = callback ? callback.apply(defined[name], args) : undefined; 344 345 if (name) { 346 //If setting exports via "module" is in play, 347 //favor that over return value and exports. After that, 348 //favor a non-undefined return value over exports use. 349 if (cjsModule && cjsModule.exports !== undef && 350 cjsModule.exports !== defined[name]) { 351 defined[name] = cjsModule.exports; 352 } else if (ret !== undef || !usingExports) { 353 //Use the return value from the function. 354 defined[name] = ret; 355 } 356 } 357 } else if (name) { 358 //May just be an object definition for the module. Only 359 //worry about defining if have a module name. 360 defined[name] = callback; 361 } 362 }; 363 364 requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 365 if (typeof deps === "string") { 366 if (handlers[deps]) { 367 //callback in this case is really relName 368 return handlers[deps](callback); 369 } 370 //Just return the module wanted. In this scenario, the 371 //deps arg is the module name, and second arg (if passed) 372 //is just the relName. 373 //Normalize module name, if it contains . or .. 374 return callDep(makeMap(deps, callback).f); 375 } else if (!deps.splice) { 376 //deps is a config object, not an array. 377 config = deps; 378 if (config.deps) { 379 req(config.deps, config.callback); 380 } 381 if (!callback) { 382 return; 383 } 384 385 if (callback.splice) { 386 //callback is an array, which means it is a dependency list. 387 //Adjust args if there are dependencies 388 deps = callback; 389 callback = relName; 390 relName = null; 391 } else { 392 deps = undef; 393 } 394 } 395 396 //Support require(['a']) 397 callback = callback || function () {}; 398 399 //If relName is a function, it is an errback handler, 400 //so remove it. 401 if (typeof relName === 'function') { 402 relName = forceSync; 403 forceSync = alt; 404 } 405 406 //Simulate async callback; 407 if (forceSync) { 408 main(undef, deps, callback, relName); 409 } else { 410 //Using a non-zero value because of concern for what old browsers 411 //do, and latest browsers "upgrade" to 4 if lower value is used: 412 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 413 //If want a value immediately, use require('id') instead -- something 414 //that works in almond on the global level, but not guaranteed and 415 //unlikely to work in other AMD implementations. 416 setTimeout(function () { 417 main(undef, deps, callback, relName); 418 }, 4); 419 } 420 421 return req; 422 }; 423 424 /** 425 * Just drops the config on the floor, but returns req in case 426 * the config return value is used. 427 */ 428 req.config = function (cfg) { 429 return req(cfg); 430 }; 431 432 /** 433 * Expose module registry for debugging and tooling 434 */ 435 requirejs._defined = defined; 436 437 define = function (name, deps, callback) { 438 439 //This module may not have dependencies 440 if (!deps.splice) { 441 //deps is not an array, so probably means 442 //an object literal or factory function for 443 //the value. Adjust args. 444 callback = deps; 445 deps = []; 446 } 447 448 if (!hasProp(defined, name) && !hasProp(waiting, name)) { 449 waiting[name] = [name, deps, callback]; 450 } 451 }; 452 453 define.amd = { 454 jQuery: true 455 }; 456 }()); 457 define("../thirdparty/almond", function(){}); 458 459 /* Simple JavaScript Inheritance 460 * By John Resig http://ejohn.org/ 461 * MIT Licensed. 462 */ 463 // Inspired by base2 and Prototype 464 define('../thirdparty/Class',[], function () { 465 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 466 // The base Class implementation (does nothing) 467 /** @private */ 468 Class = function(){}; 469 470 // Create a new Class that inherits from this class 471 /** @private */ 472 Class.extend = function(prop) { 473 var _super = this.prototype; 474 475 // Instantiate a base class (but only create the instance, 476 // don't run the init constructor) 477 initializing = true; 478 var prototype = new this(); 479 initializing = false; 480 481 // Copy the properties over onto the new prototype 482 for (var name in prop) { 483 // Check if we're overwriting an existing function 484 prototype[name] = typeof prop[name] == "function" && 485 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 486 (function(name, fn){ 487 return function() { 488 var tmp = this._super; 489 490 // Add a new ._super() method that is the same method 491 // but on the super-class 492 this._super = _super[name]; 493 494 // The method only need to be bound temporarily, so we 495 // remove it when we're done executing 496 var ret = fn.apply(this, arguments); 497 this._super = tmp; 498 499 return ret; 500 }; 501 })(name, prop[name]) : 502 prop[name]; 503 } 504 505 // The dummy class constructor 506 /** @private */ 507 function Class() { 508 // All construction is actually done in the init method 509 if ( !initializing && this.init ) 510 this.init.apply(this, arguments); 511 } 512 513 // Populate our constructed prototype object 514 Class.prototype = prototype; 515 516 // Enforce the constructor to be what we expect 517 Class.prototype.constructor = Class; 518 519 // And make this class extendable 520 Class.extend = arguments.callee; 521 522 return Class; 523 }; 524 return Class; 525 }); 526 527 /** 528 * JavaScript base object that all finesse objects should inherit 529 * from because it encapsulates and provides the common functionality. 530 * 531 * Note: This javascript class requires the "inhert.js" to be included 532 * (which simplifies the class inheritance). 533 * 534 * 535 * @requires finesse.utilities.Logger 536 */ 537 538 /** The following comment is to prevent jslint errors about 539 * using variables before they are defined. 540 */ 541 /*global Class */ 542 define('FinesseBase', ["../thirdparty/Class"], function (Class) { 543 var FinesseBase = Class.extend({ 544 init: function () { 545 } 546 }); 547 548 window.finesse = window.finesse || {}; 549 window.finesse.FinesseBase = FinesseBase; 550 551 return FinesseBase; 552 }); 553 554 /** 555 * A collection of conversion utilities. 556 * Last modified 07-06-2011, Cisco Systems 557 * 558 */ 559 /** @private */ 560 define('utilities/../../thirdparty/util/converter',[], function () { 561 /** 562 * @class 563 * Contains a collection of utility functions. 564 * @private 565 */ 566 Converter = (function () { 567 return { 568 /* This work is licensed under Creative Commons GNU LGPL License. 569 570 License: http://creativecommons.org/licenses/LGPL/2.1/ 571 Version: 0.9 572 Author: Stefan Goessner/2006 573 Web: http://goessner.net/ 574 575 2013-09-16 Modified to remove use of XmlNode.innerHTML in the innerXml function by Cisco Systems, Inc. 576 */ 577 xml2json: function (xml, tab) { 578 var X = { 579 toObj: function (xml) { 580 var o = {}; 581 if (xml.nodeType === 1) { 582 // element node .. 583 if (xml.attributes.length) 584 // element with attributes .. 585 for (var i = 0; i < xml.attributes.length; i++) 586 o["@" + xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue || "").toString(); 587 if (xml.firstChild) { 588 // element has child nodes .. 589 var textChild = 0, 590 cdataChild = 0, 591 hasElementChild = false; 592 for (var n = xml.firstChild; n; n = n.nextSibling) { 593 if (n.nodeType == 1) hasElementChild = true; 594 else if (n.nodeType == 3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; 595 // non-whitespace text 596 else if (n.nodeType == 4) cdataChild++; 597 // cdata section node 598 } 599 if (hasElementChild) { 600 if (textChild < 2 && cdataChild < 2) { 601 // structured element with evtl. a single text or/and cdata node .. 602 X.removeWhite(xml); 603 for (var n = xml.firstChild; n; n = n.nextSibling) { 604 if (n.nodeType == 3) 605 // text node 606 o["#text"] = X.escape(n.nodeValue); 607 else if (n.nodeType == 4) 608 // cdata node 609 o["#cdata"] = X.escape(n.nodeValue); 610 else if (o[n.nodeName]) { 611 // multiple occurence of element .. 612 if (o[n.nodeName] instanceof Array) 613 o[n.nodeName][o[n.nodeName].length] = X.toObj(n); 614 else 615 o[n.nodeName] = [o[n.nodeName], X.toObj(n)]; 616 } 617 else 618 // first occurence of element.. 619 o[n.nodeName] = X.toObj(n); 620 } 621 } 622 else { 623 // mixed content 624 if (!xml.attributes.length) 625 o = X.escape(X.innerXml(xml)); 626 else 627 o["#text"] = X.escape(X.innerXml(xml)); 628 } 629 } 630 else if (textChild) { 631 // pure text 632 if (!xml.attributes.length) 633 o = X.escape(X.innerXml(xml)); 634 else 635 o["#text"] = X.escape(X.innerXml(xml)); 636 } 637 else if (cdataChild) { 638 // cdata 639 if (cdataChild > 1) 640 o = X.escape(X.innerXml(xml)); 641 else 642 for (var n = xml.firstChild; n; n = n.nextSibling) 643 o["#cdata"] = X.escape(n.nodeValue); 644 } 645 } 646 if (!xml.attributes.length && !xml.firstChild) o = null; 647 } 648 else if (xml.nodeType == 9) { 649 // document.node 650 o = X.toObj(xml.documentElement); 651 } 652 else 653 throw ("unhandled node type: " + xml.nodeType); 654 return o; 655 }, 656 toJson: function(o, name, ind) { 657 var json = name ? ("\"" + name + "\"") : ""; 658 if (o instanceof Array) { 659 for (var i = 0, n = o.length; i < n; i++) 660 o[i] = X.toJson(o[i], "", ind + "\t"); 661 json += (name ? ":[": "[") + (o.length > 1 ? ("\n" + ind + "\t" + o.join(",\n" + ind + "\t") + "\n" + ind) : o.join("")) + "]"; 662 } 663 else if (o == null) 664 json += (name && ":") + "null"; 665 else if (typeof(o) == "object") { 666 var arr = []; 667 for (var m in o) 668 arr[arr.length] = X.toJson(o[m], m, ind + "\t"); 669 json += (name ? ":{": "{") + (arr.length > 1 ? ("\n" + ind + "\t" + arr.join(",\n" + ind + "\t") + "\n" + ind) : arr.join("")) + "}"; 670 } 671 else if (typeof(o) == "string") 672 json += (name && ":") + "\"" + o.toString() + "\""; 673 else 674 json += (name && ":") + o.toString(); 675 return json; 676 }, 677 innerXml: function(node) { 678 var s = ""; 679 var asXml = function(n) { 680 var s = ""; 681 if (n.nodeType == 1) { 682 s += "<" + n.nodeName; 683 for (var i = 0; i < n.attributes.length; i++) 684 s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue || "").toString() + "\""; 685 if (n.firstChild) { 686 s += ">"; 687 for (var c = n.firstChild; c; c = c.nextSibling) 688 s += asXml(c); 689 s += "</" + n.nodeName + ">"; 690 } 691 else 692 s += "/>"; 693 } 694 else if (n.nodeType == 3) 695 s += n.nodeValue; 696 else if (n.nodeType == 4) 697 s += "<![CDATA[" + n.nodeValue + "]]>"; 698 return s; 699 }; 700 for (var c = node.firstChild; c; c = c.nextSibling) 701 s += asXml(c); 702 return s; 703 }, 704 escape: function(txt) { 705 return txt.replace(/[\\]/g, "\\\\") 706 .replace(/[\"]/g, '\\"') 707 .replace(/[\n]/g, '\\n') 708 .replace(/[\r]/g, '\\r'); 709 }, 710 removeWhite: function(e) { 711 e.normalize(); 712 for (var n = e.firstChild; n;) { 713 if (n.nodeType == 3) { 714 // text node 715 if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { 716 // pure whitespace text node 717 var nxt = n.nextSibling; 718 e.removeChild(n); 719 n = nxt; 720 } 721 else 722 n = n.nextSibling; 723 } 724 else if (n.nodeType == 1) { 725 // element node 726 X.removeWhite(n); 727 n = n.nextSibling; 728 } 729 else 730 // any other node 731 n = n.nextSibling; 732 } 733 return e; 734 } 735 }; 736 if (xml.nodeType == 9) 737 // document node 738 xml = xml.documentElement; 739 var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 740 return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 741 }, 742 743 /* This work is licensed under Creative Commons GNU LGPL License. 744 745 License: http://creativecommons.org/licenses/LGPL/2.1/ 746 Version: 0.9 747 Author: Stefan Goessner/2006 748 Web: http://goessner.net/ 749 */ 750 json2xml: function(o, tab) { 751 var toXml = function(v, name, ind) { 752 var xml = ""; 753 if (v instanceof Array) { 754 for (var i = 0, n = v.length; i < n; i++) 755 xml += ind + toXml(v[i], name, ind + "\t") + "\n"; 756 } 757 else if (typeof(v) == "object") { 758 var hasChild = false; 759 xml += ind + "<" + name; 760 for (var m in v) { 761 if (m.charAt(0) == "@") 762 xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\""; 763 else 764 hasChild = true; 765 } 766 xml += hasChild ? ">": "/>"; 767 if (hasChild) { 768 for (var m in v) { 769 if (m == "#text") 770 xml += v[m]; 771 else if (m == "#cdata") 772 xml += "<![CDATA[" + v[m] + "]]>"; 773 else if (m.charAt(0) != "@") 774 xml += toXml(v[m], m, ind + "\t"); 775 } 776 xml += (xml.charAt(xml.length - 1) == "\n" ? ind: "") + "</" + name + ">"; 777 } 778 } 779 else { 780 xml += ind + "<" + name + ">" + v.toString() + "</" + name + ">"; 781 } 782 return xml; 783 }, 784 xml = ""; 785 for (var m in o) 786 xml += toXml(o[m], m, ""); 787 return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 788 } 789 }; 790 })(); 791 792 window.finesse = window.finesse || {}; 793 window.finesse.Converter = Converter; 794 795 return Converter; 796 }); 797 798 /** 799 * SaxParser.js: provides a simple SAX parser 800 * 801 * NONVALIDATING - this will not validate whether you have valid XML or not. It will simply report what it finds. 802 * Only supports elements, attributes, and text. No comments, cdata, processing instructions, etc. 803 */ 804 805 /** 806 * @requires 807 * @ignore 808 */ 809 // Add SaxParser to the finesse.utilities namespace 810 define('utilities/SaxParser',[], function () { 811 var SaxParser = { 812 parse: function(xml, callback) { 813 // Event callbacks 814 /** @private */ 815 var triggerEvent = function (type, data) { 816 callback.call(null, type, data); 817 }, 818 /** @private */ 819 triggerStartElement = function (name) { 820 triggerEvent("StartElement", name); 821 }, 822 /** @private */ 823 triggerEndElement = function (name) { 824 triggerEvent("EndElement", name); 825 }, 826 /** @private */ 827 triggerAttribute = function (name, value) { 828 triggerEvent("Attribute", { "name": name, "value": value }); 829 }, 830 /** @private */ 831 triggerText = function (text) { 832 triggerEvent("Text", text); 833 }, 834 835 // Parsing 836 cursor = 0, 837 xmlLength = xml.length, 838 whitespaceRegex = /^[ \t\r\n]*$/, 839 /** @private */ 840 isWhitespace = function (text) { 841 return whitespaceRegex.test(text); 842 }, 843 /** @private */ 844 moveToNonWhitespace = function () { 845 while (isWhitespace(xml.charAt(cursor))) { 846 cursor += 1; 847 } 848 }, 849 /** @private */ 850 parseAttribute = function () { 851 var nameBuffer = [], 852 valueBuffer = [], 853 valueIsQuoted = false, 854 cursorChar = ""; 855 856 nameBuffer.push(xml.charAt(cursor)); 857 858 // Get the name 859 cursor += 1; 860 while (cursor < xmlLength) { 861 cursorChar = xml.charAt(cursor); 862 if (isWhitespace(cursorChar) || cursorChar === "=") { 863 // Move on to gathering value 864 break; 865 } 866 else { 867 nameBuffer.push(cursorChar); 868 } 869 cursor += 1; 870 } 871 872 // Skip the equals sign and any whitespace 873 moveToNonWhitespace(); 874 if (cursorChar === "=") { 875 cursor += 1; 876 } else { 877 throw new Error("Did not find = following attribute name at " + cursor); 878 } 879 moveToNonWhitespace(); 880 881 // Get the value 882 valueIsQuoted = cursor !== xmlLength - 1 ? xml.charAt(cursor) === "\"": false; 883 if (valueIsQuoted) { 884 cursor += 1; 885 while (cursor < xmlLength) { 886 cursorChar = xml.charAt(cursor); 887 if (cursorChar === "\"") { 888 // Found the closing quote, so end value 889 triggerAttribute(nameBuffer.join(""), valueBuffer.join("")); 890 break; 891 } 892 else { 893 valueBuffer.push(cursorChar); 894 } 895 cursor += 1; 896 } 897 } 898 else { 899 throw new Error("Found unquoted attribute value at " + cursor); 900 } 901 }, 902 /** @private */ 903 parseEndElement = function () { 904 var elementNameBuffer = [], 905 cursorChar = ""; 906 cursor += 2; 907 while (cursor < xmlLength) { 908 cursorChar = xml.charAt(cursor); 909 if (cursorChar === ">") { 910 triggerEndElement(elementNameBuffer.join("")); 911 break; 912 } 913 else { 914 elementNameBuffer.push(cursorChar); 915 } 916 cursor += 1; 917 } 918 }, 919 /** @private */ 920 parseReference = function() { 921 var type, 922 TYPE_DEC_CHAR_REF = 1, 923 TYPE_HEX_CHAR_REF = 2, 924 TYPE_ENTITY_REF = 3, 925 buffer = ""; 926 cursor += 1; 927 // Determine the type of reference. 928 if (xml.charAt(cursor) === "#") { 929 cursor += 1; 930 if (xml.charAt(cursor) === "x") { 931 type = TYPE_HEX_CHAR_REF; 932 cursor += 1; 933 } else { 934 type = TYPE_DEC_CHAR_REF; 935 } 936 } else { 937 type = TYPE_ENTITY_REF; 938 } 939 // Read the reference into a buffer. 940 while (xml.charAt(cursor) !== ";") { 941 buffer += xml.charAt(cursor); 942 cursor += 1; 943 if (cursor >= xmlLength) { 944 throw new Error("Unterminated XML reference: " + buffer); 945 } 946 } 947 // Convert the reference to the appropriate character. 948 switch (type) { 949 case TYPE_DEC_CHAR_REF: 950 return String.fromCharCode(parseInt(buffer, 10)); 951 case TYPE_HEX_CHAR_REF: 952 return String.fromCharCode(parseInt(buffer, 16)); 953 case TYPE_ENTITY_REF: 954 switch (buffer) { 955 case "amp": 956 return "&"; 957 case "lt": 958 return "<"; 959 case "gt": 960 return ">"; 961 case "apos": 962 return "'"; 963 case "quot": 964 return "\""; 965 default: 966 throw new Error("Invalid XML entity reference: " + buffer); 967 } 968 // break; (currently unreachable) 969 } 970 }, 971 /** @private */ 972 parseElement = function () { 973 var elementNameBuffer = [], 974 textBuffer = [], 975 cursorChar = "", 976 whitespace = false; 977 978 // Get element name 979 cursor += 1; 980 while (cursor < xmlLength) { 981 cursorChar = xml.charAt(cursor); 982 whitespace = isWhitespace(cursorChar); 983 if (!whitespace && cursorChar !== "/" && cursorChar !== ">") { 984 elementNameBuffer.push(cursorChar); 985 } 986 else { 987 elementNameBuffer = elementNameBuffer.join(""); 988 triggerStartElement(elementNameBuffer); 989 break; 990 } 991 cursor += 1; 992 } 993 994 // Get attributes 995 if (whitespace) { 996 while (cursor < xmlLength) { 997 moveToNonWhitespace(); 998 cursorChar = xml.charAt(cursor); 999 if (cursorChar !== "/" && cursorChar !== ">") { 1000 // Start of attribute 1001 parseAttribute(); 1002 } 1003 cursorChar = xml.charAt(cursor); 1004 if (cursorChar === "/" || cursorChar === ">") { 1005 break; 1006 } 1007 else { 1008 cursor += 1; 1009 } 1010 } 1011 } 1012 1013 // End tag if "/>" was found, 1014 // otherwise we're at the end of the start tag and have to parse into it 1015 if (cursorChar === "/") { 1016 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === ">") { 1017 cursor += 1; 1018 triggerEndElement(elementNameBuffer); 1019 } 1020 } 1021 else { 1022 // cursor is on ">", so parse into element content. Assume text until we find a "<", 1023 // which could be a child element or the current element's end tag. We do not support 1024 // mixed content of text and elements as siblings unless the text is only whitespace. 1025 // Text cannot contain <, >, ", or &. They should be <, >, ", & respectively. 1026 cursor += 1; 1027 while (cursor < xmlLength) { 1028 cursorChar = xml.charAt(cursor); 1029 if (cursorChar === "<") { 1030 // Determine if end tag or element 1031 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === "/") { 1032 // At end tag 1033 textBuffer = textBuffer.join(""); 1034 if (!isWhitespace(textBuffer)) { 1035 triggerText(textBuffer); 1036 } 1037 parseEndElement(); 1038 break; 1039 } 1040 else { 1041 // At start tag 1042 textBuffer = textBuffer.join(""); 1043 if (!isWhitespace(textBuffer)) { 1044 triggerText(textBuffer); 1045 } 1046 parseElement(); 1047 textBuffer = []; 1048 } 1049 } else if (cursorChar === "&") { 1050 textBuffer.push(parseReference()); 1051 } 1052 else { 1053 textBuffer.push(cursorChar); 1054 } 1055 cursor += 1; 1056 } 1057 } 1058 }, 1059 /** @private */ 1060 skipXmlDeclaration = function() { 1061 if (xml.substr(0, 5) === "<?xml" && isWhitespace(xml.charAt(5))) { 1062 cursor = xml.indexOf(">") + 1; 1063 } 1064 moveToNonWhitespace(); 1065 }; 1066 1067 // Launch. 1068 skipXmlDeclaration(); 1069 parseElement(); 1070 } 1071 }; 1072 1073 window.finesse = window.finesse || {}; 1074 window.finesse.utilities = window.finesse.utilities || {}; 1075 window.finesse.utilities.SaxParser = SaxParser; 1076 1077 return SaxParser; 1078 }); 1079 1080 /** 1081 * Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> 1082 * © 2011 Colin Snover <http://zetafleet.com> 1083 * Released under MIT license. 1084 */ 1085 define('iso8601',[], function () { 1086 (function (Date, undefined) { 1087 var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 1088 /** @private **/ 1089 Date.parse = function (date) { 1090 var timestamp, struct, minutesOffset = 0; 1091 1092 // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 1093 // before falling back to any implementation-specific date parsing, so that’s what we do, even if native 1094 // implementations could be faster 1095 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm 1096 if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { 1097 // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC 1098 for (var i = 0, k; (k = numericKeys[i]); ++i) { 1099 struct[k] = +struct[k] || 0; 1100 } 1101 1102 // allow undefined days and months 1103 struct[2] = (+struct[2] || 1) - 1; 1104 struct[3] = +struct[3] || 1; 1105 1106 if (struct[8] !== 'Z' && struct[9] !== undefined) { 1107 minutesOffset = struct[10] * 60 + struct[11]; 1108 1109 if (struct[9] === '+') { 1110 minutesOffset = 0 - minutesOffset; 1111 } 1112 } 1113 1114 timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); 1115 } 1116 else { 1117 timestamp = origParse ? origParse(date) : NaN; 1118 } 1119 1120 return timestamp; 1121 }; 1122 }(Date)); 1123 }); 1124 1125 /*! 1126 Math.uuid.js (v1.4) 1127 http://www.broofa.com 1128 mailto:robert@broofa.com 1129 1130 Copyright (c) 2010 Robert Kieffer 1131 Dual licensed under the MIT and GPL licenses. 1132 */ 1133 1134 /* 1135 * Generate a random uuid. 1136 * 1137 * USAGE: Math.uuid(length, radix) 1138 * length - the desired number of characters 1139 * radix - the number of allowable values for each character. 1140 * 1141 * EXAMPLES: 1142 * // No arguments - returns RFC4122, version 4 ID 1143 * >>> Math.uuid() 1144 * "92329D39-6F5C-4520-ABFC-AAB64544E172" 1145 * 1146 * // One argument - returns ID of the specified length 1147 * >>> Math.uuid(15) // 15 character ID (default base=62) 1148 * "VcydxgltxrVZSTV" 1149 * 1150 * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) 1151 * >>> Math.uuid(8, 2) // 8 character ID (base=2) 1152 * "01001010" 1153 * >>> Math.uuid(8, 10) // 8 character ID (base=10) 1154 * "47473046" 1155 * >>> Math.uuid(8, 16) // 8 character ID (base=16) 1156 * "098F4D35" 1157 */ 1158 define('Math.uuid',[], function () { 1159 (function() { 1160 // Private array of chars to use 1161 var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 1162 1163 /** @private **/ 1164 Math.uuid = function (len, radix) { 1165 var chars = CHARS, uuid = [], i; 1166 radix = radix || chars.length; 1167 1168 if (len) { 1169 // Compact form 1170 for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 1171 } else { 1172 // rfc4122, version 4 form 1173 var r; 1174 1175 // rfc4122 requires these characters 1176 uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 1177 uuid[14] = '4'; 1178 1179 // Fill in random data. At i==19 set the high bits of clock sequence as 1180 // per rfc4122, sec. 4.1.5 1181 for (i = 0; i < 36; i++) { 1182 if (!uuid[i]) { 1183 r = 0 | Math.random()*16; 1184 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1185 } 1186 } 1187 } 1188 1189 return uuid.join(''); 1190 }; 1191 1192 // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 1193 // by minimizing calls to random() 1194 /** @private **/ 1195 Math.uuidFast = function() { 1196 var chars = CHARS, uuid = new Array(36), rnd=0, r; 1197 for (var i = 0; i < 36; i++) { 1198 if (i==8 || i==13 || i==18 || i==23) { 1199 uuid[i] = '-'; 1200 } else if (i==14) { 1201 uuid[i] = '4'; 1202 } else { 1203 if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 1204 r = rnd & 0xf; 1205 rnd = rnd >> 4; 1206 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1207 } 1208 } 1209 return uuid.join(''); 1210 }; 1211 1212 // A more compact, but less performant, RFC4122v4 solution: 1213 /** @private **/ 1214 Math.uuidCompact = function() { 1215 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 1216 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 1217 return v.toString(16); 1218 }); 1219 }; 1220 })(); 1221 }); 1222 1223 /** 1224 * The following comment prevents JSLint errors concerning undefined global variables. 1225 * It tells JSLint that these identifiers are defined elsewhere. 1226 */ 1227 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true, plusplus: true, unparam: true, forin: true */ 1228 1229 /** The following comment is to prevent jslint errors about 1230 * using variables before they are defined. 1231 */ 1232 /*global $, _prefs,_uiMsg,ciscowidgets,dojo,finesse,gadgets,hostUrl, Handlebars */ 1233 1234 /** 1235 * A collection of utility functions. 1236 * 1237 * @requires finesse.Converter 1238 */ 1239 define('utilities/Utilities',[ 1240 "../../thirdparty/util/converter", 1241 "utilities/SaxParser", 1242 "iso8601", 1243 "Math.uuid" 1244 ], 1245 function (Converter, SaxParser) { 1246 var Utilities = /** @lends finesse.utilities.Utilities */ { 1247 1248 /** 1249 * @class 1250 * Utilities is collection of utility methods. 1251 * 1252 * @augments finesse.restservices.RestBase 1253 * @see finesse.restservices.Contacts 1254 * @constructs 1255 */ 1256 _fakeConstuctor: function () { 1257 /* This is here for jsdocs. */ 1258 }, 1259 1260 /** 1261 * @private 1262 * Retrieves the specified item from window.localStorage 1263 * @param {String} key 1264 * The key of the item to retrieve 1265 * @returns {String} 1266 * The string with the value of the retrieved item; returns 1267 * what the browser would return if not found (typically null or undefined) 1268 * Returns false if window.localStorage feature is not even found. 1269 */ 1270 getDOMStoreItem: function (key) { 1271 var store = window.localStorage; 1272 if (store) { 1273 return store.getItem(key); 1274 } 1275 }, 1276 1277 /** 1278 * @private 1279 * Sets an item into window.localStorage 1280 * @param {String} key 1281 * The key for the item to set 1282 * @param {String} value 1283 * The value to set 1284 * @returns {Boolean} 1285 * True if successful, false if window.localStorage is 1286 * not even found. 1287 */ 1288 setDOMStoreItem: function (key, value) { 1289 var store = window.localStorage; 1290 if (store) { 1291 store.setItem(key, value); 1292 return true; 1293 } 1294 return false; 1295 }, 1296 1297 /** 1298 * @private 1299 * Removes a particular item from window.localStorage 1300 * @param {String} key 1301 * The key of the item to remove 1302 * @returns {Boolean} 1303 * True if successful, false if not 1304 * Returns false if window.localStorage feature is not even found. 1305 */ 1306 removeDOMStoreItem: function (key) { 1307 var store = window.localStorage; 1308 if (store) { 1309 store.removeItem(key); 1310 return true; 1311 } 1312 return false; 1313 }, 1314 1315 /** 1316 * @private 1317 * Dumps all the contents of window.localStorage 1318 * @returns {Boolean} 1319 * True if successful, false if not. 1320 * Returns false if window.localStorage feature is not even found. 1321 */ 1322 clearDOMStore: function () { 1323 var store = window.localStorage; 1324 if (store) { 1325 store.clear(); 1326 return true; 1327 } 1328 return false; 1329 }, 1330 1331 /** 1332 * @private 1333 * Creates a message listener for window.postMessage messages. 1334 * @param {Function} callback 1335 * The callback that will be invoked with the message. The callback 1336 * is responsible for any security checks. 1337 * @param {String} [origin] 1338 * The origin to check against for security. Allows all messages 1339 * if no origin is provided. 1340 * @returns {Function} 1341 * The callback function used to register with the message listener. 1342 * This is different than the one provided as a parameter because 1343 * the function is overloaded with origin checks. 1344 * @throws {Error} If the callback provided is not a function. 1345 */ 1346 receiveMessage: function (callback, origin) { 1347 if (typeof callback !== "function") { 1348 throw new Error("Callback is not a function."); 1349 } 1350 1351 //Create a function closure to perform origin check. 1352 /** @private */ 1353 var cb = function (e) { 1354 // If an origin check is requested (provided), we'll only invoke the callback if it passes 1355 if (typeof origin !== "string" || (typeof origin === "string" && typeof e.origin === "string" && e.origin.toLowerCase() === origin.toLowerCase())) { 1356 callback(e); 1357 } 1358 }; 1359 1360 if (window.addEventListener) { //Firefox, Opera, Chrome, Safari 1361 window.addEventListener("message", cb, false); 1362 } else { //Internet Explorer 1363 window.attachEvent("onmessage", cb); 1364 } 1365 1366 //Return callback used to register with listener so that invoker 1367 //could use it to remove. 1368 return cb; 1369 }, 1370 1371 /** 1372 * @private 1373 * Sends a message to a target frame using window.postMessage. 1374 * @param {Function} message 1375 * Message to be sent to target frame. 1376 * @param {Object} [target="parent"] 1377 * An object reference to the target frame. Default us the parent. 1378 * @param {String} [targetOrigin="*"] 1379 * The URL of the frame this frame is sending the message to. 1380 */ 1381 sendMessage: function (message, target, targetOrigin) { 1382 //Default to any target URL if none is specified. 1383 targetOrigin = targetOrigin || "*"; 1384 1385 //Default to parent target if none is specified. 1386 target = target || parent; 1387 1388 //Ensure postMessage is supported by browser before invoking. 1389 if (window.postMessage) { 1390 target.postMessage(message, targetOrigin); 1391 } 1392 }, 1393 1394 /** 1395 * Returns the passed in handler, if it is a function. 1396 * @param {Function} handler 1397 * The handler to validate 1398 * @returns {Function} 1399 * The provided handler if it is valid 1400 * @throws Error 1401 * If the handler provided is invalid 1402 */ 1403 validateHandler: function (handler) { 1404 if (handler === undefined || typeof handler === "function") { 1405 return handler; 1406 } else { 1407 throw new Error("handler must be a function"); 1408 } 1409 }, 1410 1411 /** 1412 * @private 1413 * Tries to get extract the AWS error code from a 1414 * finesse.clientservices.ClientServices parsed error response object. 1415 * @param {Object} rsp 1416 * The handler to validate 1417 * @returns {String} 1418 * The error code, HTTP status code, or undefined 1419 */ 1420 getErrCode: function (rsp) { 1421 try { // Best effort to get the error code 1422 return rsp.object.ApiErrors.ApiError.ErrorType; 1423 } catch (e) { // Second best effort to get the HTTP Status code 1424 if (rsp && rsp.status) { 1425 return "HTTP " + rsp.status; 1426 } 1427 } // Otherwise, don't return anything (undefined) 1428 }, 1429 1430 /** 1431 * @private 1432 * Tries to get extract the AWS error data from a 1433 * finesse.clientservices.ClientServices parsed error response object. 1434 * @param {Object} rsp 1435 * The handler to validate 1436 * @returns {String} 1437 * The error data, HTTP status code, or undefined 1438 */ 1439 getErrData: function (rsp) { 1440 try { // Best effort to get the error data 1441 return rsp.object.ApiErrors.ApiError.ErrorData; 1442 } catch (e) { // Second best effort to get the HTTP Status code 1443 if (rsp && rsp.status) { 1444 return "HTTP " + rsp.status; 1445 } 1446 } // Otherwise, don't return anything (undefined) 1447 }, 1448 1449 /** 1450 * @private 1451 * Tries to get extract the AWS overrideable boolean from a 1452 * finesse.clientservices.ClientServices parsed error response object. 1453 * @param {Object} rsp 1454 * The handler to validate 1455 * @returns {String} 1456 * The overrideable boolean, HTTP status code, or undefined 1457 */ 1458 getErrOverrideable: function (rsp) { 1459 try { // Best effort to get the override boolean 1460 return rsp.object.ApiErrors.ApiError.Overrideable; 1461 } catch (e) { // Second best effort to get the HTTP Status code 1462 if (rsp && rsp.status) { 1463 return "HTTP " + rsp.status; 1464 } 1465 } // Otherwise, don't return anything (undefined) 1466 }, 1467 1468 /** 1469 * Trims leading and trailing whitespace from a string. 1470 * @param {String} str 1471 * The string to trim 1472 * @returns {String} 1473 * The trimmed string 1474 */ 1475 trim: function (str) { 1476 return str.replace(/^\s*/, "").replace(/\s*$/, ""); 1477 }, 1478 1479 /** 1480 * Utility method for getting the current time in milliseconds. 1481 * @returns {String} 1482 * The current time in milliseconds 1483 */ 1484 currentTimeMillis : function () { 1485 return (new Date()).getTime(); 1486 }, 1487 1488 /** 1489 * Gets the current drift (between client and server) 1490 * 1491 * @returns {integer} which is the current drift (last calculated; 0 if we have not calculated yet) 1492 */ 1493 getCurrentDrift : function () { 1494 var drift; 1495 1496 //Get the current client drift from localStorage 1497 drift = window.sessionStorage.getItem("clientTimestampDrift"); 1498 if (drift) { 1499 drift = parseInt(drift, 10); 1500 if (isNaN(drift)) { 1501 drift = 0; 1502 } 1503 } 1504 return drift; 1505 }, 1506 1507 /** 1508 * Converts the specified clientTime to server time by adjusting by the current drift. 1509 * 1510 * @param clientTime is the time in milliseconds 1511 * @returns {int} serverTime in milliseconds 1512 */ 1513 convertToServerTimeMillis : function(clientTime) { 1514 var drift = this.getCurrentDrift(); 1515 return (clientTime + drift); 1516 }, 1517 1518 /** 1519 * Utility method for getting the current time, 1520 * adjusted by the calculated "drift" to closely 1521 * approximate the server time. This is used 1522 * when calculating durations based on a server 1523 * timestamp, which otherwise can produce unexpected 1524 * results if the times on client and server are 1525 * off. 1526 * 1527 * @returns {String} 1528 * The current server time in milliseconds 1529 */ 1530 currentServerTimeMillis : function () { 1531 var drift = this.getCurrentDrift(); 1532 return (new Date()).getTime() + drift; 1533 }, 1534 1535 /** 1536 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds. 1537 * 1538 * @param timeInMs is the time in milliseconds 1539 * @returns {String} which corresponds to minutes and seconds (e.g. 11:23) 1540 */ 1541 buildTimeString : function (timeInMs) { 1542 var min, sec, timeStr = "00:00"; 1543 1544 if (timeInMs && timeInMs !== "-1") { 1545 // calculate minutes, and seconds 1546 min = this.pad(Math.floor(timeInMs / 60000)); 1547 sec = this.pad(Math.floor((timeInMs % 60000) / 1000)); 1548 1549 // construct MM:SS time string 1550 timeStr = min + ":" + sec; 1551 } 1552 return timeStr; 1553 }, 1554 1555 /** 1556 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds (and optionally hours) 1557 * 1558 * @param timeInMs is the time in milliseconds 1559 * @returns {String} which corresponds to hours, minutes and seconds (e.g. 01:11:23 or 11:23) 1560 */ 1561 buildTimeStringWithOptionalHours: function (timeInMs) { 1562 var hour, min, sec, timeStr = "00:00", optionalHour = "", timeInSecs; 1563 1564 if (timeInMs && timeInMs !== "-1") { 1565 timeInSecs = timeInMs / 1000; 1566 1567 // calculate {hours}, minutes, and seconds 1568 hour = this.pad(Math.floor(timeInSecs / 3600)); 1569 min = this.pad(Math.floor((timeInSecs % 3600) / 60)); 1570 sec = this.pad(Math.floor((timeInSecs % 3600) % 60)); 1571 1572 //Optionally add the hour if we have hours 1573 if (hour > 0) { 1574 optionalHour = hour + ":"; 1575 } 1576 1577 // construct MM:SS time string (or optionally HH:MM:SS) 1578 timeStr = optionalHour + min + ":" + sec; 1579 } 1580 return timeStr; 1581 }, 1582 1583 1584 /** 1585 * Builds a string which specifies the amount of time user has been in this state (e.g. 11:23). 1586 * 1587 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1588 * @param stateStartTimeInMs is integer argument which specifies time call entered current state 1589 * @returns {String} which is the elapsed time (MINUTES:SECONDS) 1590 * 1591 */ 1592 buildElapsedTimeString : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1593 var result, delta; 1594 1595 result = "--:--"; 1596 if (stateStartTimeInMs !== 0) { 1597 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1598 1599 if (delta > 0) { 1600 result = this.buildTimeString(delta); 1601 } 1602 } 1603 return result; 1604 }, 1605 1606 /** 1607 * Builds a string which specifies the amount of time user has been in this state with optional hours (e.g. 01:11:23 or 11:23). 1608 * 1609 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1610 * @param startTimeInMs is integer argument which specifies the start time 1611 * @returns {String} which is the elapsed time (MINUTES:SECONDS) or (HOURS:MINUTES:SECONDS) 1612 * 1613 */ 1614 buildElapsedTimeStringWithOptionalHours : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1615 var result, delta; 1616 1617 result = "--:--"; 1618 if (stateStartTimeInMs !== 0) { 1619 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1620 1621 if (delta > 0) { 1622 result = this.buildTimeStringWithOptionalHours(delta); 1623 } 1624 } 1625 return result; 1626 }, 1627 1628 1629 /** 1630 * Builds a string which displays the total call time in minutes and seconds. 1631 * 1632 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1633 * @param callStartTimeInMs is integer argument which specifies time the call started 1634 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1635 */ 1636 buildTotalTimeString : function (adjustedServerTimeInMs, callStartTimeInMs) { 1637 return this.buildElapsedTimeString(adjustedServerTimeInMs, callStartTimeInMs); 1638 }, 1639 1640 /** 1641 * Builds a string which displays the hold time in minutes and seconds. 1642 * 1643 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1644 * @param holdStartTimeInMs is integer argument which specifies time the hold started 1645 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1646 */ 1647 buildHoldTimeString : function (adjustedServerTimeInMs, holdStartTimeInMs) { 1648 return this.buildElapsedTimeString(adjustedServerTimeInMs, holdStartTimeInMs); 1649 }, 1650 1651 /** 1652 * Builds a string which displays the elapsed time the call has been in wrap up. 1653 * 1654 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1655 * @param wrapupStartTimeInMs is integer argument which specifies time call entered wrapup state 1656 * @returns {String} which is the elapsed wrapup time 1657 * 1658 */ 1659 buildWrapupTimeString : function (adjustedServerTimeInMs, wrapupStartTimeInMs) { 1660 return this.buildElapsedTimeString(adjustedServerTimeInMs, wrapupStartTimeInMs); 1661 }, 1662 1663 /** 1664 * Extracts a time from the timeStr. Note: The timeStr could be empty. In this case, the time returned will be 0. 1665 * @param timeStr is a time string in ISO8601 format (note: could be empty) 1666 * @returns {long} is the time 1667 */ 1668 extractTime : function (timeStr) { 1669 var result = 0, theDate; 1670 if (timeStr === "") { 1671 result = 0; 1672 } else if (timeStr === null) { 1673 result = 0; 1674 } else { 1675 theDate = this.parseDateStringISO8601(timeStr); 1676 result = theDate.getTime(); 1677 } 1678 return result; 1679 }, 1680 1681 /** 1682 * @private 1683 * Generates an RFC1422v4-compliant UUID using pesudorandom numbers. 1684 * @returns {String} 1685 * An RFC1422v4-compliant UUID using pesudorandom numbers. 1686 **/ 1687 generateUUID: function () { 1688 return Math.uuidCompact(); 1689 }, 1690 1691 /** @private */ 1692 xml2json: finesse.Converter.xml2json, 1693 1694 1695 /** 1696 * @private 1697 * Utility method to get the JSON parser either from gadgets.json 1698 * or from window.JSON (which will be initialized by CUIC if 1699 * browser doesn't support 1700 */ 1701 getJSONParser: function() { 1702 var _container = window.gadgets || {}, 1703 parser = _container.json || window.JSON; 1704 return parser; 1705 }, 1706 1707 /** 1708 * @private 1709 * Utility method to convert a javascript object to XML. 1710 * @param {Object} object 1711 * The object to convert to XML. 1712 * @param {Boolean} escapeFlag 1713 * If escapeFlag evaluates to true: 1714 * - XML escaping is done on the element values. 1715 * - Attributes, #cdata, and #text is not supported. 1716 * - The XML is unformatted (no whitespace between elements). 1717 * If escapeFlag evaluates to false: 1718 * - Element values are written 'as is' (no escaping). 1719 * - Attributes, #cdata, and #text is supported. 1720 * - The XML is formatted. 1721 * @returns The XML string. 1722 */ 1723 json2xml: function (object, escapeFlag) { 1724 var xml; 1725 if (escapeFlag) { 1726 xml = this._json2xmlWithEscape(object); 1727 } 1728 else { 1729 xml = finesse.Converter.json2xml(object, '\t'); 1730 } 1731 return xml; 1732 }, 1733 1734 /** 1735 * @private 1736 * Utility method to convert XML string into javascript object. 1737 */ 1738 xml2JsObj : function (event) { 1739 var parser = this.getJSONParser(); 1740 return parser.parse(finesse.Converter.xml2json(jQuery.parseXML(event), "")); 1741 }, 1742 1743 /** 1744 * @private 1745 * Utility method to convert an XML string to a javascript object. 1746 * @desc This function calls to the SAX parser and responds to callbacks 1747 * received from the parser. Entity translation is not handled here. 1748 * @param {String} xml 1749 * The XML to parse. 1750 * @returns The javascript object. 1751 */ 1752 xml2js: function (xml) { 1753 var STATES = { 1754 INVALID: 0, 1755 NEW_NODE: 1, 1756 ATTRIBUTE_NODE: 2, 1757 TEXT_NODE: 3, 1758 END_NODE: 4 1759 }, 1760 state = STATES.INVALID, 1761 rootObj = {}, 1762 newObj, 1763 objStack = [rootObj], 1764 nodeName = "", 1765 1766 /** 1767 * @private 1768 * Adds a property to the current top JSO. 1769 * @desc This is also where we make considerations for arrays. 1770 * @param {String} name 1771 * The name of the property to add. 1772 * @param (String) value 1773 * The value of the property to add. 1774 */ 1775 addProperty = function (name, value) { 1776 var current = objStack[objStack.length - 1]; 1777 if(current.hasOwnProperty(name) && current[name] instanceof Array){ 1778 current[name].push(value); 1779 }else if(current.hasOwnProperty(name)){ 1780 current[name] = [current[name], value]; 1781 }else{ 1782 current[name] = value; 1783 } 1784 }, 1785 1786 /** 1787 * @private 1788 * The callback passed to the SAX parser which processes events from 1789 * the SAX parser in order to construct the resulting JSO. 1790 * @param (String) type 1791 * The type of event received. 1792 * @param (String) data 1793 * The data received from the SAX parser. The contents of this 1794 * parameter vary based on the type of event. 1795 */ 1796 xmlFound = function (type, data) { 1797 switch (type) { 1798 case "StartElement": 1799 // Because different node types have different expectations 1800 // of parenting, we don't push another JSO until we know 1801 // what content we're getting 1802 1803 // If we're already in the new node state, we're running 1804 // into a child node. There won't be any text here, so 1805 // create another JSO 1806 if(state === STATES.NEW_NODE){ 1807 newObj = {}; 1808 addProperty(nodeName, newObj); 1809 objStack.push(newObj); 1810 } 1811 state = STATES.NEW_NODE; 1812 nodeName = data; 1813 break; 1814 case "EndElement": 1815 // If we're in the new node state, we've found no content 1816 // set the tag property to null 1817 if(state === STATES.NEW_NODE){ 1818 addProperty(nodeName, null); 1819 }else if(state === STATES.END_NODE){ 1820 objStack.pop(); 1821 } 1822 state = STATES.END_NODE; 1823 break; 1824 case "Attribute": 1825 // If were in the new node state, no JSO has yet been created 1826 // for this node, create one 1827 if(state === STATES.NEW_NODE){ 1828 newObj = {}; 1829 addProperty(nodeName, newObj); 1830 objStack.push(newObj); 1831 } 1832 // Attributes are differentiated from child elements by a 1833 // preceding "@" in the property name 1834 addProperty("@" + data.name, data.value); 1835 state = STATES.ATTRIBUTE_NODE; 1836 break; 1837 case "Text": 1838 // In order to maintain backwards compatibility, when no 1839 // attributes are assigned to a tag, its text contents are 1840 // assigned directly to the tag property instead of a JSO. 1841 1842 // If we're in the attribute node state, then the JSO for 1843 // this tag was already created when the attribute was 1844 // assigned, differentiate this property from a child 1845 // element by naming it "#text" 1846 if(state === STATES.ATTRIBUTE_NODE){ 1847 addProperty("#text", data); 1848 }else{ 1849 addProperty(nodeName, data); 1850 } 1851 state = STATES.TEXT_NODE; 1852 break; 1853 } 1854 }; 1855 SaxParser.parse(xml, xmlFound); 1856 return rootObj; 1857 }, 1858 1859 /** 1860 * @private 1861 * Traverses a plain-old-javascript-object recursively and outputs its XML representation. 1862 * @param {Object} obj 1863 * The javascript object to be converted into XML. 1864 * @returns {String} The XML representation of the object. 1865 */ 1866 js2xml: function (obj) { 1867 var xml = "", i, elem; 1868 1869 if (obj !== null) { 1870 if (obj.constructor === Object) { 1871 for (elem in obj) { 1872 if (obj[elem] === null || typeof(obj[elem]) === 'undefined') { 1873 xml += '<' + elem + '/>'; 1874 } else if (obj[elem].constructor === Array) { 1875 for (i = 0; i < obj[elem].length; i++) { 1876 xml += '<' + elem + '>' + this.js2xml(obj[elem][i]) + '</' + elem + '>'; 1877 } 1878 } else if (elem[0] !== '@') { 1879 if (this.js2xmlObjIsEmpty(obj[elem])) { 1880 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '/>'; 1881 } else if (elem === "#text") { 1882 xml += obj[elem]; 1883 } else { 1884 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '>' + this.js2xml(obj[elem]) + '</' + elem + '>'; 1885 } 1886 } 1887 } 1888 } else { 1889 xml = obj; 1890 } 1891 } 1892 1893 return xml; 1894 }, 1895 1896 /** 1897 * @private 1898 * Utility method called exclusively by js2xml() to find xml attributes. 1899 * @desc Traverses children one layer deep of a javascript object to "look ahead" 1900 * for properties flagged as such (with '@'). 1901 * @param {Object} obj 1902 * The obj to traverse. 1903 * @returns {String} Any attributes formatted for xml, if any. 1904 */ 1905 js2xmlAtt: function (obj) { 1906 var elem; 1907 1908 if (obj !== null) { 1909 if (obj.constructor === Object) { 1910 for (elem in obj) { 1911 if (obj[elem] !== null && typeof(obj[elem]) !== "undefined" && obj[elem].constructor !== Array) { 1912 if (elem[0] === '@'){ 1913 return ' ' + elem.substring(1) + '="' + obj[elem] + '"'; 1914 } 1915 } 1916 } 1917 } 1918 } 1919 1920 return ''; 1921 }, 1922 1923 /** 1924 * @private 1925 * Utility method called exclusively by js2xml() to determine if 1926 * a node has any children, with special logic for ignoring attributes. 1927 * @desc Attempts to traverse the elements in the object while ignoring attributes. 1928 * @param {Object} obj 1929 * The obj to traverse. 1930 * @returns {Boolean} whether or not the JS object is "empty" 1931 */ 1932 js2xmlObjIsEmpty: function (obj) { 1933 var elem; 1934 1935 if (obj !== null) { 1936 if (obj.constructor === Object) { 1937 for (elem in obj) { 1938 if (obj[elem] !== null) { 1939 if (obj[elem].constructor === Array){ 1940 return false; 1941 } 1942 1943 if (elem[0] !== '@'){ 1944 return false; 1945 } 1946 } else { 1947 return false; 1948 } 1949 } 1950 } else { 1951 return false; 1952 } 1953 } 1954 1955 return true; 1956 }, 1957 1958 /** 1959 * Encodes the given string into base64. 1960 *<br> 1961 * <b>NOTE:</b> {input} is assumed to be UTF-8; only the first 1962 * 8 bits of each input element are significant. 1963 * 1964 * @param {String} input 1965 * The string to convert to base64. 1966 * @returns {String} 1967 * The converted string. 1968 */ 1969 b64Encode: function (input) { 1970 var output = "", idx, data, 1971 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 1972 1973 for (idx = 0; idx < input.length; idx += 3) { 1974 data = input.charCodeAt(idx) << 16 | 1975 input.charCodeAt(idx + 1) << 8 | 1976 input.charCodeAt(idx + 2); 1977 1978 //assume the first 12 bits are valid 1979 output += table.charAt((data >>> 18) & 0x003f) + 1980 table.charAt((data >>> 12) & 0x003f); 1981 output += ((idx + 1) < input.length) ? 1982 table.charAt((data >>> 6) & 0x003f) : 1983 "="; 1984 output += ((idx + 2) < input.length) ? 1985 table.charAt(data & 0x003f) : 1986 "="; 1987 } 1988 1989 return output; 1990 }, 1991 1992 /** 1993 * Decodes the given base64 string. 1994 * <br> 1995 * <b>NOTE:</b> output is assumed to be UTF-8; only the first 1996 * 8 bits of each output element are significant. 1997 * 1998 * @param {String} input 1999 * The base64 encoded string 2000 * @returns {String} 2001 * Decoded string 2002 */ 2003 b64Decode: function (input) { 2004 var output = "", idx, h, data, 2005 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 2006 2007 for (idx = 0; idx < input.length; idx += 4) { 2008 h = [ 2009 table.indexOf(input.charAt(idx)), 2010 table.indexOf(input.charAt(idx + 1)), 2011 table.indexOf(input.charAt(idx + 2)), 2012 table.indexOf(input.charAt(idx + 3)) 2013 ]; 2014 2015 data = (h[0] << 18) | (h[1] << 12) | (h[2] << 6) | h[3]; 2016 if (input.charAt(idx + 2) === '=') { 2017 data = String.fromCharCode( 2018 (data >>> 16) & 0x00ff 2019 ); 2020 } else if (input.charAt(idx + 3) === '=') { 2021 data = String.fromCharCode( 2022 (data >>> 16) & 0x00ff, 2023 (data >>> 8) & 0x00ff 2024 ); 2025 } else { 2026 data = String.fromCharCode( 2027 (data >>> 16) & 0x00ff, 2028 (data >>> 8) & 0x00ff, 2029 data & 0x00ff 2030 ); 2031 } 2032 output += data; 2033 } 2034 2035 return output; 2036 }, 2037 2038 /** 2039 * @private 2040 * Extracts the username and the password from the Base64 encoded string. 2041 * @params {String} 2042 * A base64 encoded string containing credentials that (when decoded) 2043 * are colon delimited. 2044 * @returns {Object} 2045 * An object with the following structure: 2046 * {id:string, password:string} 2047 */ 2048 getCredentials: function (authorization) { 2049 var credObj = {}, 2050 credStr = this.b64Decode(authorization), 2051 colonIndx = credStr.indexOf(":"); 2052 2053 //Check to ensure that string is colon delimited. 2054 if (colonIndx === -1) { 2055 throw new Error("String is not colon delimited."); 2056 } 2057 2058 //Extract ID and password. 2059 credObj.id = credStr.substring(0, colonIndx); 2060 credObj.password = credStr.substring(colonIndx + 1); 2061 return credObj; 2062 }, 2063 2064 /** 2065 * Takes a string and removes any spaces within the string. 2066 * @param {String} string 2067 * The string to remove spaces from 2068 * @returns {String} 2069 * The string without spaces 2070 */ 2071 removeSpaces: function (string) { 2072 return string.split(' ').join(''); 2073 }, 2074 2075 /** 2076 * Escapes spaces as encoded " " characters so they can 2077 * be safely rendered by jQuery.text(string) in all browsers. 2078 * 2079 * (Although IE behaves as expected, Firefox collapses spaces if this function is not used.) 2080 * 2081 * @param text 2082 * The string whose spaces should be escaped 2083 * 2084 * @returns 2085 * The string with spaces escaped 2086 */ 2087 escapeSpaces: function (string) { 2088 return string.replace(/\s/g, '\u00a0'); 2089 }, 2090 2091 /** 2092 * Adds a span styled to line break at word edges around the string passed in. 2093 * @param str String to be wrapped in word-breaking style. 2094 * @private 2095 */ 2096 addWordWrapping : function (str) { 2097 return '<span style="word-wrap: break-word;">' + str + '</span>'; 2098 }, 2099 2100 /** 2101 * Takes an Object and determines whether it is an Array or not. 2102 * @param {Object} obj 2103 * The Object in question 2104 * @returns {Boolean} 2105 * true if the object is an Array, else false. 2106 */ 2107 isArray: function (obj) { 2108 return obj.constructor.toString().indexOf("Array") !== -1; 2109 }, 2110 2111 /** 2112 * @private 2113 * Takes a data object and returns an array extracted 2114 * @param {Object} data 2115 * JSON payload 2116 * 2117 * @returns {array} 2118 * extracted array 2119 */ 2120 getArray: function (data) { 2121 if (this.isArray(data)) { 2122 //Return if already an array. 2123 return data; 2124 } else { 2125 //Create an array, iterate through object, and push to array. This 2126 //should only occur with one object, and therefore one obj in array. 2127 var arr = []; 2128 arr.push(data); 2129 return arr; 2130 } 2131 }, 2132 2133 /** 2134 * @private 2135 * Extracts the ID for an entity given the Finesse REST URI. The ID is 2136 * assumed to be the last element in the URI (after the last "/"). 2137 * @param {String} uri 2138 * The Finesse REST URI to extract the ID from. 2139 * @returns {String} 2140 * The ID extracted from the REST URI. 2141 */ 2142 getId: function (uri) { 2143 if (!uri) { 2144 return ""; 2145 } 2146 var strLoc = uri.lastIndexOf("/"); 2147 return uri.slice(strLoc + 1); 2148 }, 2149 2150 /** 2151 * Compares two objects for equality. 2152 * @param {Object} obj1 2153 * First of two objects to compare. 2154 * @param {Object} obj2 2155 * Second of two objects to compare. 2156 */ 2157 getEquals: function (objA, objB) { 2158 var key; 2159 2160 for (key in objA) { 2161 if (objA.hasOwnProperty(key)) { 2162 if (!objA[key]) { 2163 objA[key] = ""; 2164 } 2165 2166 if (typeof objB[key] === 'undefined') { 2167 return false; 2168 } 2169 if (typeof objB[key] === 'object') { 2170 if (!objB[key].equals(objA[key])) { 2171 return false; 2172 } 2173 } 2174 if (objB[key] !== objA[key]) { 2175 return false; 2176 } 2177 } 2178 } 2179 return true; 2180 }, 2181 2182 /** 2183 * Regular expressions used in translating HTML and XML entities 2184 */ 2185 ampRegEx : new RegExp('&', 'gi'), 2186 ampEntityRefRegEx : new RegExp('&', 'gi'), 2187 ltRegEx : new RegExp('<', 'gi'), 2188 ltEntityRefRegEx : new RegExp('<', 'gi'), 2189 gtRegEx : new RegExp('>', 'gi'), 2190 gtEntityRefRegEx : new RegExp('>', 'gi'), 2191 xmlSpecialCharRegEx: new RegExp('[&<>"\']', 'g'), 2192 entityRefRegEx: new RegExp('&[^;]+(?:;|$)', 'g'), 2193 2194 /** 2195 * Translates between special characters and HTML entities 2196 * 2197 * @param text 2198 * The text to translate 2199 * 2200 * @param makeEntityRefs 2201 * If true, encode special characters as HTML entities; if 2202 * false, decode HTML entities back to special characters 2203 * 2204 * @private 2205 */ 2206 translateHTMLEntities: function (text, makeEntityRefs) { 2207 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2208 if (makeEntityRefs) { 2209 text = text.replace(this.ampRegEx, '&'); 2210 text = text.replace(this.ltRegEx, '<'); 2211 text = text.replace(this.gtRegEx, '>'); 2212 } else { 2213 text = text.replace(this.gtEntityRefRegEx, '>'); 2214 text = text.replace(this.ltEntityRefRegEx, '<'); 2215 text = text.replace(this.ampEntityRefRegEx, '&'); 2216 } 2217 } 2218 2219 return text; 2220 }, 2221 2222 /** 2223 * Translates between special characters and XML entities 2224 * 2225 * @param text 2226 * The text to translate 2227 * 2228 * @param makeEntityRefs 2229 * If true, encode special characters as XML entities; if 2230 * false, decode XML entities back to special characters 2231 * 2232 * @private 2233 */ 2234 translateXMLEntities: function (text, makeEntityRefs) { 2235 /** @private */ 2236 var escape = function (character) { 2237 switch (character) { 2238 case "&": 2239 return "&"; 2240 case "<": 2241 return "<"; 2242 case ">": 2243 return ">"; 2244 case "'": 2245 return "'"; 2246 case "\"": 2247 return """; 2248 default: 2249 return character; 2250 } 2251 }, 2252 /** @private */ 2253 unescape = function (entity) { 2254 switch (entity) { 2255 case "&": 2256 return "&"; 2257 case "<": 2258 return "<"; 2259 case ">": 2260 return ">"; 2261 case "'": 2262 return "'"; 2263 case """: 2264 return "\""; 2265 default: 2266 if (entity.charAt(1) === "#" && entity.charAt(entity.length - 1) === ";") { 2267 if (entity.charAt(2) === "x") { 2268 return String.fromCharCode(parseInt(entity.slice(3, -1), 16)); 2269 } else { 2270 return String.fromCharCode(parseInt(entity.slice(2, -1), 10)); 2271 } 2272 } else { 2273 throw new Error("Invalid XML entity: " + entity); 2274 } 2275 } 2276 }; 2277 2278 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2279 if (makeEntityRefs) { 2280 text = text.replace(this.xmlSpecialCharRegEx, escape); 2281 } else { 2282 text = text.replace(this.entityRefRegEx, unescape); 2283 } 2284 } 2285 2286 return text; 2287 }, 2288 2289 /** 2290 * @private 2291 * Utility method to pad the number with a leading 0 for single digits 2292 * @param (Number) num 2293 * the number to pad 2294 */ 2295 pad : function (num) { 2296 if (num < 10) { 2297 return "0" + num; 2298 } 2299 2300 return String(num); 2301 }, 2302 2303 /** 2304 * Pad with zeros based on a padWidth. 2305 * 2306 * @param num 2307 * @param padWidth 2308 * @returns {String} with padded zeros (based on padWidth) 2309 */ 2310 padWithWidth : function (num, padWidth) { 2311 var value, index, result; 2312 2313 result = ""; 2314 for(index=padWidth;index>1;index--) 2315 { 2316 value = Math.pow(10, index-1); 2317 2318 if (num < value) { 2319 result = result + "0"; 2320 } 2321 } 2322 result = result + num; 2323 2324 return String(result); 2325 }, 2326 2327 /** 2328 * Converts a date to an ISO date string. 2329 * 2330 * @param aDate 2331 * @returns {String} in ISO date format 2332 * 2333 * Note: Some browsers don't support this method (e.g. IE8). 2334 */ 2335 convertDateToISODateString : function (aDate) { 2336 var result; 2337 2338 result = aDate.getUTCFullYear() + "-" + this.padWithWidth(aDate.getUTCMonth()+1, 2) + "-" + this.padWithWidth(aDate.getUTCDate(), 2) + "T" + this.padWithWidth(aDate.getUTCHours(), 2) + ":" + this.padWithWidth(aDate.getUTCMinutes(), 2) + ":" + this.padWithWidth(aDate.getUTCSeconds(), 2)+ "." + this.padWithWidth(aDate.getUTCMilliseconds(), 3) + "Z"; 2339 return result; 2340 }, 2341 2342 /** 2343 * Get the date in ISO date format. 2344 * 2345 * @param aDate is the date 2346 * @returns {String} date in ISO format 2347 * 2348 * Note: see convertDateToISODateString() above. 2349 */ 2350 dateToISOString : function (aDate) { 2351 var result; 2352 2353 try { 2354 result = aDate.toISOString(); 2355 } catch (e) { 2356 result = this.convertDateToISODateString(aDate); 2357 } 2358 return result; 2359 }, 2360 2361 /** 2362 * Parse string (which is formated as ISO8601 date) into Javascript Date object. 2363 * 2364 * @param s ISO8601 string 2365 * @return {Date} 2366 * Note: Some browsers don't support Date constructor which take ISO8601 date (e.g. IE 8). 2367 */ 2368 parseDateStringISO8601 : function (s) { 2369 var i, re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:.(\d+))?(Z|[+\-]\d{2})(?::(\d{2}))?/, 2370 d = s.match(re); 2371 if( !d ) { 2372 return null; 2373 } 2374 for( i in d ) { 2375 d[i] = ~~d[i]; 2376 } 2377 return new Date(Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6], d[7]) + (d[8] * 60 + d[9]) * 60000); 2378 }, 2379 2380 /** 2381 * Utility method to render a timestamp value (in seconds) into HH:MM:SS format. 2382 * @param {Number} time 2383 * The timestamp in ms to render 2384 * @returns {String} 2385 * Time string in HH:MM:SS format. 2386 */ 2387 getDisplayTime : function (time) { 2388 var hour, min, sec, timeStr = "00:00:00"; 2389 2390 if (time && time !== "-1") { 2391 // calculate hours, minutes, and seconds 2392 hour = this.pad(Math.floor(time / 3600)); 2393 min = this.pad(Math.floor((time % 3600) / 60)); 2394 sec = this.pad(Math.floor((time % 3600) % 60)); 2395 // construct HH:MM:SS time string 2396 timeStr = hour + ":" + min + ":" + sec; 2397 } 2398 2399 return timeStr; 2400 }, 2401 2402 /** 2403 * Checks if the string is null. If it is, return empty string; else return 2404 * the string itself. 2405 * 2406 * @param {String} str 2407 * The string to check 2408 * @return {String} 2409 * Empty string or string itself 2410 */ 2411 convertNullToEmptyString : function (str) { 2412 return str || ""; 2413 }, 2414 2415 /** 2416 * Utility method to render a timestamp string (of format 2417 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format. 2418 * 2419 * @param {String} timestamp 2420 * The timestamp to render 2421 * @param {Date} [now] 2422 * Optional argument to provide the time from which to 2423 * calculate the duration instead of using the current time 2424 * @returns {String} 2425 * Duration string in HH:MM:SS format. 2426 */ 2427 convertTsToDuration : function (timestamp, now) { 2428 return this.convertTsToDurationWithFormat(timestamp, false, now); 2429 }, 2430 2431 /** 2432 * Utility method to render a timestamp string (of format 2433 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format, 2434 * with optional -1 for null or negative times. 2435 * 2436 * @param {String} timestamp 2437 * The timestamp to render 2438 * @param {Boolean} forFormat 2439 * If True, if duration is null or negative, return -1 so that the duration can be formated 2440 * as needed in the Gadget. 2441 * @param {Date} [now] 2442 * Optional argument to provide the time from which to 2443 * calculate the duration instead of using the current time 2444 * @returns {String} 2445 * Duration string in HH:MM:SS format. 2446 */ 2447 convertTsToDurationWithFormat : function (timestamp, forFormat, now) { 2448 var startTimeInMs, nowInMs, durationInSec = "-1"; 2449 2450 // Calculate duration 2451 if (timestamp && typeof timestamp === "string") { 2452 // first check it '--' for a msg in grid 2453 if (timestamp === '--' || timestamp ==="" || timestamp === "-1") { 2454 return "-1"; 2455 } 2456 // else try convert string into a time 2457 startTimeInMs = Date.parse(timestamp); 2458 if (!isNaN(startTimeInMs)) { 2459 if (!now || !(now instanceof Date)) { 2460 nowInMs = this.currentServerTimeMillis(); 2461 } else { 2462 nowInMs = this.convertToServerTimeMillis(now.getTime()); 2463 } 2464 durationInSec = Math.floor((nowInMs - startTimeInMs) / 1000); 2465 // Since currentServerTime is not exact (lag between sending and receiving 2466 // messages will differ slightly), treat a slightly negative (less than 1 sec) 2467 // value as 0, to avoid "--" showing up when a state first changes. 2468 if (durationInSec === -1) { 2469 durationInSec = 0; 2470 } 2471 2472 if (durationInSec < 0) { 2473 if (forFormat) { 2474 return "-1"; 2475 } else { 2476 return this.getDisplayTime("-1"); 2477 } 2478 } 2479 } 2480 }else { 2481 if(forFormat){ 2482 return "-1"; 2483 } 2484 } 2485 return this.getDisplayTime(durationInSec); 2486 }, 2487 2488 /** 2489 * @private 2490 * Takes the time in seconds and duration in % and return the duration in milliseconds. 2491 * 2492 * @param time in seconds 2493 * @param duration in % 2494 */ 2495 2496 getRefreshTime :function(expiryTime , duration){ 2497 var durationInMs = Math.floor((expiryTime * duration * 1000) / 100); 2498 return durationInMs; 2499 }, 2500 2501 /** 2502 * Takes a string (typically from window.location) and finds the value which corresponds to a name. For 2503 * example: http://www.company.com/?param1=value1¶m2=value2 2504 * 2505 * @param str is the string to search 2506 * @param name is the name to search for 2507 */ 2508 getParameterByName : function(str, name) { 2509 var regex, results; 2510 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 2511 regex = new RegExp("[\\?&]" + name + "=([^]*)"); 2512 results = regex.exec(str); 2513 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 2514 }, 2515 2516 /** 2517 * 2518 * Returns the base64 encoded user authorization String. 2519 * @returns {String} the Authorization String 2520 * 2521 */ 2522 getUserAuthString: function () { 2523 var authString = window.sessionStorage.getItem('userFinesseAuth'); 2524 return authString; 2525 }, 2526 2527 /** 2528 * Return the user access token as JSON Object. 2529 * @returns {Object} the access token JSON object 2530 * 2531 */ 2532 getAuthTokenObj: function(){ 2533 var authTokenString = window.sessionStorage.getItem('ssoTokenObject'); 2534 return this.getJSONParser().parse(authTokenString); 2535 }, 2536 2537 /** 2538 * Returns the user access token as String. 2539 * @returns {String} the access token 2540 * 2541 */ 2542 2543 getToken: function () { 2544 var tokenString = window.sessionStorage.getItem('ssoTokenObject'), tokenObj; 2545 if (tokenString && typeof tokenString === "string") { 2546 tokenObj = this.getJSONParser().parse(tokenString); 2547 if (tokenObj.token) { 2548 return tokenObj.token; 2549 } else { 2550 throw new Error( 2551 "Unable to retrieve token : Invalid token Object in browser session"); 2552 } 2553 } 2554 }, 2555 2556 /** 2557 * The authorization header based on SSO or non SSO deployment. 2558 * Can be "Bearer " or "Basic " 2559 * @returns {String} The authorization header string. 2560 */ 2561 getAuthHeaderString : function(configObj) { 2562 var authHeader; 2563 if (configObj.systemAuthMode === this.getAuthModes().SSO) { 2564 authHeader = "Bearer " + configObj.authToken; 2565 } else if (configObj.systemAuthMode === this.getAuthModes().NONSSO) { 2566 authHeader = "Basic " + configObj.authorization; 2567 } else { 2568 throw new Error("Unknown auth mode "+configObj.systemAuthMode); 2569 } 2570 return authHeader; 2571 }, 2572 2573 /** 2574 * Can be used as a constant for auth modes 2575 * Can be "SSO" , "NON_SSO" or "HYBRID" 2576 * @returns {String} The authorization header string. 2577 */ 2578 getAuthModes : function(){ 2579 return { 2580 SSO: "SSO", 2581 NONSSO: "NON_SSO", 2582 HYBRID: "HYBRID" 2583 }; 2584 }, 2585 2586 /** 2587 * Encodes the node name 2588 */ 2589 encodeNodeName : function(node){ 2590 if (node === null){ 2591 return null; 2592 } 2593 var originalChars, encodedChars,encodedNode, i; 2594 originalChars = ["?", "@", "&","'"]; 2595 encodedChars = ["?3F", "?40", "?26","?27"]; 2596 encodedNode = node; 2597 2598 if(encodedNode.indexOf(originalChars[0]) !== -1){ 2599 encodedNode = encodedNode.replace(/\?/g, encodedChars[0]); 2600 } 2601 for (i = 1; i < originalChars.length; i++){ 2602 if(encodedNode.indexOf(originalChars[i]) !== -1){ 2603 encodedNode = encodedNode.replace(new RegExp(originalChars[i], "g"), encodedChars[i]); 2604 } 2605 } 2606 return encodedNode; 2607 }, 2608 2609 /** 2610 * @private Utility method to convert milliseconds into minutes. 2611 * @param {String} Time in milliseconds 2612 * @returns {String} Time in minutes 2613 */ 2614 convertMilliSecondsToMinutes : function(millisec){ 2615 if(!millisec || isNaN(millisec)){ 2616 throw new Error("passed argument is not a number"); 2617 }else{ 2618 var minutes = Math.floor(millisec / (1000 * 60)); 2619 return minutes; 2620 } 2621 }, 2622 2623 2624 /** 2625 * @private Adds a new cookie to the page with a default domain. 2626 * @param {String} 2627 * key the key to assign a value to 2628 * @param {String} 2629 * value the value to assign to the key 2630 * @param {Number} 2631 * days number of days (from current) until the cookie should 2632 * expire 2633 */ 2634 addCookie : function (key, value, days) { 2635 var date, expires = "", 2636 cookie = key + "=" + escape(value); 2637 if (typeof days === "number") { 2638 date = new Date(); 2639 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 2640 cookie += "; expires=" + date.toGMTString(); 2641 } 2642 document.cookie = cookie + "; path=/"; 2643 }, 2644 2645 /** 2646 * @private 2647 * Get the value of a cookie given a key. 2648 * @param {String} key 2649 * a key to lookup 2650 * @returns {String} 2651 * the value mapped to a key, null if key doesn't exist 2652 */ 2653 getCookie : function (key) { 2654 var i, pairs, pair; 2655 if (document.cookie) { 2656 pairs = document.cookie.split(";"); 2657 for (i = 0; i < pairs.length; i += 1) { 2658 pair = this.trim(pairs[i]).split("="); 2659 if (pair[0] === key) { 2660 return unescape(pair[1]); 2661 } 2662 } 2663 } 2664 return null; 2665 }, 2666 2667 /** 2668 * @private 2669 * Deletes the cookie mapped to specified key. 2670 * @param {String} key 2671 * the key to delete 2672 */ 2673 deleteCookie : function (key) { 2674 this.addCookie(key, "", -1); 2675 }, 2676 2677 /** 2678 * @private 2679 * Case insensitive sort for use with arrays or Dojox stores 2680 * @param {String} a 2681 * first value 2682 * @param {String} b 2683 * second value 2684 */ 2685 caseInsensitiveSort: function (a, b) { 2686 var ret = 0, emptyString = ""; 2687 a = a + emptyString; 2688 b = b + emptyString; 2689 a = a.toLowerCase(); 2690 b = b.toLowerCase(); 2691 if (a > b) { 2692 ret = 1; 2693 } 2694 if (a < b) { 2695 ret = -1; 2696 } 2697 return ret; 2698 }, 2699 2700 /** 2701 * @private 2702 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 2703 * 2704 * The displayWjitFunc function will be called once and only once when the div for our wijit 2705 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 2706 * throw exceptions and do not render properly if they are created in a display:none div. 2707 * If our gadget is visisble the function will be called immediately. 2708 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 2709 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 2710 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 2711 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 2712 * end up waiting for the node to become visisble anyway. 2713 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 2714 */ 2715 onGadgetFirstVisible: function (displayWjitFunc) { 2716 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2717 try { 2718 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2719 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2720 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2721 2722 // Loop through all of the tab panels to find one that has our gadget id 2723 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2724 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2725 if (q.length > 0) { // You found it 2726 panelNode = node; 2727 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2728 active = dojo.hasClass(panelNode, "active"); 2729 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2730 return; 2731 } 2732 }); 2733 // If panel is already active - execute the function - we're done 2734 if (active) { 2735 //?console.log(frameId + " is visible display it"); 2736 setTimeout(displayWjitFunc); 2737 } 2738 // If its not visible - wait for the active class to show up. 2739 else { 2740 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2741 iterval = setInterval(dojo.hitch(this, function () { 2742 if (dojo.hasClass(panelNode, "active")) { 2743 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2744 clearInterval(iterval); 2745 setTimeout(displayWjitFunc); 2746 } 2747 }), 250); 2748 } 2749 } catch (err) { 2750 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2751 } 2752 }, 2753 2754 /** 2755 * @private 2756 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2757 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2758 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2759 */ 2760 downloadFile : function (url) { 2761 var iframe = document.getElementById("download_iframe"); 2762 2763 if (!iframe) 2764 { 2765 iframe = document.createElement("iframe"); 2766 $(document.body).append(iframe); 2767 $(iframe).css("display", "none"); 2768 } 2769 2770 iframe.src = url; 2771 }, 2772 2773 /** 2774 * @private 2775 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2776 */ 2777 bitMask: { 2778 /** @private */ 2779 isSet: function (value, mask) { 2780 return (value & mask) === mask; 2781 }, 2782 /** 2783 * Returns true if all flags in the intArray are set on the specified value 2784 * @private 2785 */ 2786 all: function (value, intArray) { 2787 var i = intArray.length; 2788 if (typeof(i) === "undefined") 2789 { 2790 intArray = [intArray]; 2791 i = 1; 2792 } 2793 while ((i = i - 1) !== -1) 2794 { 2795 if (!this.isSet(value, intArray[i])) 2796 { 2797 return false; 2798 } 2799 } 2800 return true; 2801 }, 2802 /** 2803 * @private 2804 * Returns true if any flags in the intArray are set on the specified value 2805 */ 2806 any: function (value, intArray) { 2807 var i = intArray.length; 2808 if (typeof(i) === "undefined") 2809 { 2810 intArray = [intArray]; 2811 i = 1; 2812 } 2813 while ((i = i - 1) !== -1) 2814 { 2815 if (this.isSet(value, intArray[i])) 2816 { 2817 return true; 2818 } 2819 } 2820 return false; 2821 } 2822 }, 2823 2824 /** @private */ 2825 renderDojoGridOffScreen: function (grid) { 2826 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2827 $(document.body).append(offscreenDiv); 2828 grid.placeAt(offscreenDiv); 2829 grid.startup(); 2830 document.body.removeChild(offscreenDiv); 2831 return grid; 2832 }, 2833 2834 /** @private */ 2835 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2836 var timerId = null, 2837 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2838 theInputControl = theControl.find("input"), 2839 theClearButton = theControl.find("a"), 2840 inputControlWidthWithClear = 204, 2841 inputControlWidthNoClear = 230, 2842 sPreviousInput = theInputControl.val(), 2843 /** @private **/ 2844 toggleClearButton = function(){ 2845 if (theInputControl.val() === "") { 2846 theClearButton.hide(); 2847 theControl.removeClass("input-append"); 2848 theInputControl.width(inputControlWidthNoClear); 2849 } else { 2850 theInputControl.width(inputControlWidthWithClear); 2851 theClearButton.show(); 2852 theControl.addClass("input-append"); 2853 } 2854 }; 2855 2856 // set placeholder text 2857 theInputControl.attr('placeholder', placeholderText); 2858 2859 theInputControl.unbind('keyup').bind('keyup', function() { 2860 if (sPreviousInput !== theInputControl.val()) { 2861 window.clearTimeout(timerId); 2862 sPreviousInput = theInputControl.val(); 2863 timerId = window.setTimeout(function() { 2864 changeCallback.call((callbackScope || window), theInputControl.val()); 2865 theInputControl[0].focus(); 2866 }, callbackDelay); 2867 } 2868 2869 toggleClearButton(); 2870 }); 2871 2872 theClearButton.bind('click', function() { 2873 theInputControl.val(''); 2874 changeCallback.call((callbackScope || window), ''); 2875 2876 toggleClearButton(); 2877 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2878 }); 2879 2880 theInputControl.val(""); 2881 toggleClearButton(); 2882 }, 2883 2884 DataTables: { 2885 /** @private */ 2886 createDataTable: function (options, dataTableOptions) { 2887 var grid, 2888 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 2889 headerRow = table.find("tr"), 2890 defaultOptions = { 2891 "aaData": [], 2892 "bPaginate": false, 2893 "bLengthChange": false, 2894 "bFilter": false, 2895 "bInfo": false, 2896 "sScrollY": "176", 2897 "oLanguage": { 2898 "sEmptyTable": "", 2899 "sZeroRecords": "" 2900 } 2901 }, 2902 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 2903 columnDefs = [], 2904 columnFormatter; 2905 2906 // Create a header cell for each column, and set up the datatable definition for the column 2907 $(options.columns).each(function (index, column) { 2908 headerRow.append($("<th></th>")); 2909 columnDefs[index] = { 2910 "mData": column.propertyName, 2911 "sTitle": column.columnHeader, 2912 "sWidth": column.width, 2913 "aTargets": [index], 2914 "bSortable": column.sortable, 2915 "bVisible": column.visible, 2916 "mRender": column.render 2917 }; 2918 if (typeof(column.renderFunction) === "function") 2919 { 2920 /** @ignore **/ 2921 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 2922 var returnValue; 2923 2924 //Apply column render logic to value before applying extra render function 2925 if (typeof(column.render) === "function") 2926 { 2927 value = column.render.call(value, value, value); 2928 } 2929 2930 if (typeof(type) === "string") 2931 { 2932 switch (type) 2933 { 2934 case "undefined": 2935 case "sort": 2936 returnValue = value; 2937 break; 2938 case "set": 2939 throw new Error("Unsupported set data in Finesse Grid"); 2940 case "filter": 2941 case "display": 2942 case "type": 2943 returnValue = column.renderFunction.call(dataObject, value, dataObject); 2944 break; 2945 default: 2946 break; 2947 } 2948 } 2949 else 2950 { 2951 throw new Error("type param not specified in Finesse DataTable mData"); 2952 } 2953 2954 return returnValue; 2955 }; 2956 } 2957 }); 2958 gridOptions.aoColumnDefs = columnDefs; 2959 2960 // Set the height 2961 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 2962 { 2963 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 2964 } 2965 2966 // Place it into the DOM 2967 if (typeof(options.container) !== "undefined" && options.container !== null) 2968 { 2969 $(options.container).append(table); 2970 } 2971 2972 // Create the DataTable 2973 table.dataTable(gridOptions); 2974 2975 return table; 2976 } 2977 }, 2978 2979 /** 2980 * @private 2981 * Sets a dojo button to the specified disable state, removing it from 2982 * the tab order if disabling, and restoring it to the tab order if enabling. 2983 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 2984 * @param {bool} disabled 2985 */ 2986 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 2987 var labelNode, 2988 tabIndex; 2989 2990 dojoButton.set("disabled", disabled); 2991 2992 // Remove the tabindex attribute on disabled buttons, store it, 2993 // and replace it when it becomes enabled again 2994 labelNode = $("#" + dojoButton.id + "_label"); 2995 if (disabled) 2996 { 2997 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 2998 labelNode.removeAttr("tabindex"); 2999 } 3000 else 3001 { 3002 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 3003 if (typeof(tabIndex) === "string") 3004 { 3005 labelNode.attr("tabindex", Number(tabIndex)); 3006 } 3007 } 3008 }, 3009 3010 /** 3011 * @private 3012 * Use this utility to disable the tab stop for a Dojo Firebug iframe within a gadget. 3013 * 3014 * Dojo sometimes adds a hidden iframe for enabling a firebug lite console in older 3015 * browsers. Unfortunately, this adds an additional tab stop that impacts accessibility. 3016 */ 3017 disableTabStopForDojoFirebugIframe: function () { 3018 var iframe = $("iframe[src*='loadFirebugConsole']"); 3019 3020 if ((iframe.length) && (iframe.attr("tabIndex") !== "-1")) { 3021 iframe.attr('tabIndex', '-1'); 3022 } 3023 }, 3024 3025 /** 3026 * @private 3027 * Measures the given text using the supplied fontFamily and fontSize 3028 * @param {string} text text to measure 3029 * @param {string} fontFamily 3030 * @param {string} fontSize 3031 * @return {number} pixel width 3032 */ 3033 measureText: function (text, fontFamily, fontSize) { 3034 var width, 3035 element = $("<div></div>").text(text).css({ 3036 "fontSize": fontSize, 3037 "fontFamily": fontFamily 3038 }).addClass("offscreen").appendTo(document.body); 3039 3040 width = element.width(); 3041 element.remove(); 3042 3043 return width; 3044 }, 3045 3046 /** 3047 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 3048 * needing to resize down in IE. This gets around that by calculating the height 3049 * manually and passing it in. 3050 * @return {undefined} 3051 */ 3052 "adjustGadgetHeight": function () { 3053 var bScrollHeight = $("body").height() + 20; 3054 gadgets.window.adjustHeight(bScrollHeight); 3055 }, 3056 3057 /** 3058 * Private helper method for converting a javascript object to xml, where the values of the elements are 3059 * appropriately escaped for XML. 3060 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 3061 * there is no whitespace between elements. 3062 * @param object The javascript object to convert to XML. 3063 * @returns The XML string. 3064 * @private 3065 */ 3066 _json2xmlWithEscape: function(object) { 3067 var that = this, 3068 xml = "", 3069 m, 3070 /** @private **/ 3071 toXmlHelper = function(value, name) { 3072 var xml = "", 3073 i, 3074 m; 3075 if (value instanceof Array) { 3076 for (i = 0; i < value.length; ++i) { 3077 xml += toXmlHelper(value[i], name); 3078 } 3079 } 3080 else if (typeof value === "object") { 3081 xml += "<" + name + ">"; 3082 for (m in value) { 3083 if (value.hasOwnProperty(m)) { 3084 xml += toXmlHelper(value[m], m); 3085 } 3086 } 3087 xml += "</" + name + ">"; 3088 } 3089 else { 3090 // is a leaf node 3091 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 3092 "</" + name + ">"; 3093 } 3094 return xml; 3095 }; 3096 for (m in object) { 3097 if (object.hasOwnProperty(m)) { 3098 xml += toXmlHelper(object[m], m); 3099 } 3100 } 3101 return xml; 3102 }, 3103 3104 /** 3105 * Private method for returning a sanitized version of the user agent string. 3106 * @returns the user agent string, but sanitized! 3107 * @private 3108 */ 3109 getSanitizedUserAgentString: function () { 3110 return this.translateXMLEntities(navigator.userAgent, true); 3111 }, 3112 3113 /** 3114 * Use JQuery's implementation of Promises (Deferred) to execute code when 3115 * multiple async processes have finished. An example use: 3116 * 3117 * var asyncProcess1 = $.Deferred(), 3118 * asyncProcess2 = $.Deferred(); 3119 * 3120 * finesse.utilities.Utilities.whenAllDone(asyncProcess1, asyncProcess2) // WHEN both asyncProcess1 and asyncProcess2 are resolved or rejected ... 3121 * .then( 3122 * // First function passed to then() is called when all async processes are complete, regardless of errors 3123 * function () { 3124 * console.log("all processes completed"); 3125 * }, 3126 * // Second function passed to then() is called if any async processed threw an exception 3127 * function (failures) { // Array of failure messages 3128 * console.log("Number of failed async processes: " + failures.length); 3129 * }); 3130 * 3131 * Found at: 3132 * http://stackoverflow.com/a/15094263/1244030 3133 * 3134 * Pass in any number of $.Deferred instances. 3135 * @returns {Object} 3136 */ 3137 whenAllDone: function () { 3138 var deferreds = [], 3139 result = $.Deferred(); 3140 3141 $.each(arguments, function(i, current) { 3142 var currentDeferred = $.Deferred(); 3143 current.then(function() { 3144 currentDeferred.resolve(false, arguments); 3145 }, function() { 3146 currentDeferred.resolve(true, arguments); 3147 }); 3148 deferreds.push(currentDeferred); 3149 }); 3150 3151 $.when.apply($, deferreds).then(function() { 3152 var failures = [], 3153 successes = []; 3154 3155 $.each(arguments, function(i, args) { 3156 // If we resolved with `true` as the first parameter 3157 // we have a failure, a success otherwise 3158 var target = args[0] ? failures : successes, 3159 data = args[1]; 3160 // Push either all arguments or the only one 3161 target.push(data.length === 1 ? data[0] : args); 3162 }); 3163 3164 if(failures.length) { 3165 return result.reject.apply(result, failures); 3166 } 3167 3168 return result.resolve.apply(result, successes); 3169 }); 3170 3171 return result; 3172 }, 3173 3174 /** 3175 * Private method to format a given string by replacing the place holders (like {0}) with the 3176 * corresponding supplied arguments. For example, calling this method as follows: 3177 * formatString("Hello {0}, {1} rocks!", "there", "Finesse"); 3178 * results in the following output string: 3179 * "Hello there, Finesse rocks!" 3180 * 3181 * @param {String} format - a string that holds the place holder to be replaced 3182 * 3183 * @returns {String} - string where the place holders are replaced with respective values 3184 * @private 3185 */ 3186 formatString : function(format) { 3187 if (!format || arguments.length <= 1) { 3188 return format; 3189 } 3190 3191 var i, retStr = format; 3192 for (i = 1; i < arguments.length; i += 1) { 3193 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 3194 } 3195 3196 // in order to fix French text with single quotes in it, we need to replace \' with ' 3197 return retStr.replace(/\\'/g, "'"); 3198 } 3199 }; 3200 3201 window.finesse = window.finesse || {}; 3202 window.finesse.utilities = window.finesse.utilities || {}; 3203 window.finesse.utilities.Utilities = Utilities; 3204 3205 return Utilities; 3206 }); 3207 3208 /** The following comment is to prevent jslint errors about 3209 * using variables before they are defined. 3210 */ 3211 /*global finesse*/ 3212 3213 /** 3214 * Initiated by the Master to create a shared BOSH connection. 3215 * 3216 * @requires Utilities 3217 */ 3218 3219 /** 3220 * @class 3221 * Establishes a shared event connection by creating a communication tunnel 3222 * with the notification server and consume events which could be published. 3223 * Public functions are exposed to register to the connection status information 3224 * and events. 3225 * @constructor 3226 * @param {String} host 3227 * The host name/ip of the Finesse server. 3228 * @throws {Error} If required constructor parameter is missing. 3229 */ 3230 /** @private */ 3231 define('clientservices/MasterTunnel',["utilities/Utilities"], function (Utilities) { 3232 var MasterTunnel = function (host, scheme) { 3233 if (typeof host !== "string" || host.length === 0) { 3234 throw new Error("Required host parameter missing."); 3235 } 3236 3237 var 3238 3239 /** 3240 * Flag to indicate whether the tunnel frame is loaded. 3241 * @private 3242 */ 3243 _isTunnelLoaded = false, 3244 3245 /** 3246 * Short reference to the Finesse utility. 3247 * @private 3248 */ 3249 _util = Utilities, 3250 3251 /** 3252 * The URL with host and port to the Finesse server. 3253 * @private 3254 */ 3255 _tunnelOrigin, 3256 3257 /** 3258 * Location of the tunnel HTML URL. 3259 * @private 3260 */ 3261 _tunnelURL, 3262 3263 /** 3264 * The port on which to connect to the Finesse server to load the eventing resources. 3265 * @private 3266 */ 3267 _tunnelOriginPort, 3268 3269 /** 3270 * Flag to indicate whether we have processed the tunnel config yet. 3271 * @private 3272 */ 3273 _isTunnelConfigInit = false, 3274 3275 /** 3276 * The tunnel frame window object. 3277 * @private 3278 */ 3279 _tunnelFrame, 3280 3281 /** 3282 * The handler registered with the object to be invoked when an event is 3283 * delivered by the notification server. 3284 * @private 3285 */ 3286 _eventHandler, 3287 3288 /** 3289 * The handler registered with the object to be invoked when presence is 3290 * delivered by the notification server. 3291 * @private 3292 */ 3293 _presenceHandler, 3294 3295 /** 3296 * The handler registered with the object to be invoked when the BOSH 3297 * connection has changed states. The object will contain the "status" 3298 * property and a "resourceID" property only if "status" is "connected". 3299 * @private 3300 */ 3301 _connInfoHandler, 3302 3303 /** 3304 * The last connection status published by the JabberWerx library. 3305 * @private 3306 */ 3307 _statusCache, 3308 3309 /** 3310 * The last event sent by notification server. 3311 * @private 3312 */ 3313 _eventCache, 3314 3315 /** 3316 * The ID of the user logged into notification server. 3317 * @private 3318 */ 3319 _id, 3320 3321 /** 3322 * The domain of the XMPP server, representing the portion of the JID 3323 * following '@': userid@domain.com 3324 * @private 3325 */ 3326 _xmppDomain, 3327 3328 /** 3329 * The password of the user logged into notification server. 3330 * @private 3331 */ 3332 _password, 3333 3334 /** 3335 * The jid of the pubsub service on the XMPP server 3336 * @private 3337 */ 3338 _pubsubDomain, 3339 3340 /** 3341 * The resource to use for the BOSH connection. 3342 * @private 3343 */ 3344 _resource, 3345 3346 /** 3347 * The resource ID identifying the client device (that we receive from the server). 3348 * @private 3349 */ 3350 _resourceID, 3351 3352 /** 3353 * The different types of messages that could be sent to the parent frame. 3354 * The types here should be understood by the parent frame and used to 3355 * identify how the message is formatted. 3356 * @private 3357 */ 3358 _TYPES = { 3359 EVENT: 0, 3360 ID: 1, 3361 PASSWORD: 2, 3362 RESOURCEID: 3, 3363 STATUS: 4, 3364 XMPPDOMAIN: 5, 3365 PUBSUBDOMAIN: 6, 3366 SUBSCRIBE: 7, 3367 UNSUBSCRIBE: 8, 3368 PRESENCE: 9, 3369 CONNECT_REQ: 10 3370 }, 3371 3372 _handlers = { 3373 subscribe: {}, 3374 unsubscribe: {} 3375 }, 3376 3377 3378 /** 3379 * Create a connection info object. 3380 * @returns {Object} 3381 * A connection info object containing a "status" and "resourceID". 3382 * @private 3383 */ 3384 _createConnInfoObj = function () { 3385 return { 3386 status: _statusCache, 3387 resourceID: _resourceID 3388 }; 3389 }, 3390 3391 /** 3392 * Utility function which sends a message to the dynamic tunnel frame 3393 * event frame formatted as follows: "type|message". 3394 * @param {Number} type 3395 * The category type of the message. 3396 * @param {String} message 3397 * The message to be sent to the tunnel frame. 3398 * @private 3399 */ 3400 _sendMessage = function (type, message) { 3401 message = type + "|" + message; 3402 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 3403 }, 3404 3405 /** 3406 * Utility to process the response of a subscribe request from 3407 * the tunnel frame, then invoking the stored callback handler 3408 * with the respective data (error, when applicable) 3409 * @param {String} data 3410 * The response in the format of "node[|error]" 3411 * @private 3412 */ 3413 _processSubscribeResponse = function (data) { 3414 var dataArray = data.split("|"), 3415 node = dataArray[0], 3416 err; 3417 3418 //Error is optionally the second item in the array 3419 if (dataArray.length) { 3420 err = dataArray[1]; 3421 } 3422 3423 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3424 if (_handlers.subscribe[node]) { 3425 _handlers.subscribe[node](err); 3426 delete _handlers.subscribe[node]; 3427 } 3428 }, 3429 3430 /** 3431 * Utility to process the response of an unsubscribe request from 3432 * the tunnel frame, then invoking the stored callback handler 3433 * with the respective data (error, when applicable) 3434 * @param {String} data 3435 * The response in the format of "node[|error]" 3436 * @private 3437 */ 3438 _processUnsubscribeResponse = function (data) { 3439 var dataArray = data.split("|"), 3440 node = dataArray[0], 3441 err; 3442 3443 //Error is optionally the second item in the array 3444 if (dataArray.length) { 3445 err = dataArray[1]; 3446 } 3447 3448 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3449 if (_handlers.unsubscribe[node]) { 3450 _handlers.unsubscribe[node](err); 3451 delete _handlers.unsubscribe[node]; 3452 } 3453 }, 3454 3455 /** 3456 * Handler for messages delivered by window.postMessage. Listens for events 3457 * published by the notification server, connection status published by 3458 * the JabberWerx library, and the resource ID created when the BOSH 3459 * connection has been established. 3460 * @param {Object} e 3461 * The message object as provided by the window.postMessage feature. 3462 * @private 3463 */ 3464 _messageHandler = function (e) { 3465 var 3466 3467 //Extract the message type and message data. The expected format is 3468 //"type|data" where type is a number represented by the TYPES object. 3469 delimPos = e.data.indexOf("|"), 3470 type = Number(e.data.substr(0, delimPos)), 3471 data = e.data.substr(delimPos + 1); 3472 3473 //Accepts messages and invoke the correct registered handlers. 3474 switch (type) { 3475 case _TYPES.EVENT: 3476 _eventCache = data; 3477 if (typeof _eventHandler === "function") { 3478 _eventHandler(data); 3479 } 3480 break; 3481 case _TYPES.STATUS: 3482 _statusCache = data; 3483 3484 //A "loaded" status means that the frame is ready to accept 3485 //credentials for establishing a BOSH connection. 3486 if (data === "loaded") { 3487 _isTunnelLoaded = true; 3488 if(_resource) { 3489 _sendMessage(_TYPES.RESOURCEID, _resource); 3490 } 3491 _sendMessage(_TYPES.ID, _id); 3492 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 3493 _sendMessage(_TYPES.PASSWORD, _password); 3494 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 3495 } else if (typeof _connInfoHandler === "function") { 3496 _connInfoHandler(_createConnInfoObj()); 3497 } 3498 break; 3499 case _TYPES.RESOURCEID: 3500 _resourceID = data; 3501 break; 3502 case _TYPES.SUBSCRIBE: 3503 _processSubscribeResponse(data); 3504 break; 3505 case _TYPES.UNSUBSCRIBE: 3506 _processUnsubscribeResponse(data); 3507 break; 3508 case _TYPES.PRESENCE: 3509 if (typeof _presenceHandler === "function") { 3510 _presenceHandler(data); 3511 } 3512 break; 3513 default: 3514 break; 3515 } 3516 }, 3517 3518 /** 3519 * Initialize the tunnel config so that the url can be http or https with the appropriate port 3520 * @private 3521 */ 3522 _initTunnelConfig = function () { 3523 if (_isTunnelConfigInit === true) { 3524 return; 3525 } 3526 3527 //Initialize tunnel origin 3528 //Determine tunnel origin based on host and scheme 3529 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 3530 if (scheme) { 3531 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 3532 } else { 3533 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 3534 } 3535 _tunnelURL = _tunnelOrigin + "/tunnel/"; 3536 3537 _isTunnelConfigInit = true; 3538 }, 3539 3540 /** 3541 * Create the tunnel iframe which establishes the shared BOSH connection. 3542 * Messages are sent across frames using window.postMessage. 3543 * @private 3544 */ 3545 _createTunnel = function () { 3546 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 3547 iframe = document.createElement("iframe"); 3548 iframe.style.display = "none"; 3549 iframe.setAttribute("id", tunnelID); 3550 iframe.setAttribute("name", tunnelID); 3551 iframe.setAttribute("src", _tunnelURL); 3552 document.body.appendChild(iframe); 3553 _tunnelFrame = window.frames[tunnelID]; 3554 }; 3555 3556 /** 3557 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 3558 * @private 3559 */ 3560 this.makeConnectReq = function () { 3561 _sendMessage(_TYPES.PASSWORD, _password); 3562 }; 3563 3564 /** 3565 * @private 3566 * Returns the host of the Finesse server. 3567 * @returns {String} 3568 * The host specified during the creation of the object. 3569 */ 3570 this.getHost = function () { 3571 return host; 3572 }; 3573 3574 /** 3575 * @private 3576 * The resource ID of the user who is logged into the notification server. 3577 * @returns {String} 3578 * The resource ID generated by the notification server. 3579 */ 3580 this.getResourceID = function () { 3581 return _resourceID; 3582 }; 3583 3584 /** 3585 * @private 3586 * Indicates whether the tunnel frame is loaded. 3587 * @returns {Boolean} 3588 * True if the tunnel frame is loaded, false otherwise. 3589 */ 3590 this.isTunnelLoaded = function () { 3591 return _isTunnelLoaded; 3592 }; 3593 3594 /** 3595 * @private 3596 * The location of the tunnel HTML URL. 3597 * @returns {String} 3598 * The location of the tunnel HTML URL. 3599 */ 3600 this.getTunnelURL = function () { 3601 return _tunnelURL; 3602 }; 3603 3604 /** 3605 * @private 3606 * Tunnels a subscribe request to the eventing iframe. 3607 * @param {String} node 3608 * The node to subscribe to 3609 * @param {Function} handler 3610 * Handler to invoke upon success or failure 3611 */ 3612 this.subscribe = function (node, handler) { 3613 if (handler && typeof handler !== "function") { 3614 throw new Error("Parameter is not a function."); 3615 } 3616 _handlers.subscribe[node] = handler; 3617 _sendMessage(_TYPES.SUBSCRIBE, node); 3618 }; 3619 3620 /** 3621 * @private 3622 * Tunnels an unsubscribe request to the eventing iframe. 3623 * @param {String} node 3624 * The node to unsubscribe from 3625 * @param {Function} handler 3626 * Handler to invoke upon success or failure 3627 */ 3628 this.unsubscribe = function (node, handler) { 3629 if (handler && typeof handler !== "function") { 3630 throw new Error("Parameter is not a function."); 3631 } 3632 _handlers.unsubscribe[node] = handler; 3633 _sendMessage(_TYPES.UNSUBSCRIBE, node); 3634 }; 3635 3636 /** 3637 * @private 3638 * Registers a handler to be invoked when an event is delivered. Only one 3639 * is registered at a time. If there has already been an event that was 3640 * delivered, the handler will be invoked immediately. 3641 * @param {Function} handler 3642 * Invoked when an event is delivered through the event connection. 3643 */ 3644 this.registerEventHandler = function (handler) { 3645 if (typeof handler !== "function") { 3646 throw new Error("Parameter is not a function."); 3647 } 3648 _eventHandler = handler; 3649 if (_eventCache) { 3650 handler(_eventCache); 3651 } 3652 }; 3653 3654 /** 3655 * @private 3656 * Unregisters the event handler completely. 3657 */ 3658 this.unregisterEventHandler = function () { 3659 _eventHandler = undefined; 3660 }; 3661 3662 /** 3663 * @private 3664 * Registers a handler to be invoked when a presence event is delivered. Only one 3665 * is registered at a time. 3666 * @param {Function} handler 3667 * Invoked when a presence event is delivered through the event connection. 3668 */ 3669 this.registerPresenceHandler = function (handler) { 3670 if (typeof handler !== "function") { 3671 throw new Error("Parameter is not a function."); 3672 } 3673 _presenceHandler = handler; 3674 }; 3675 3676 /** 3677 * @private 3678 * Unregisters the presence event handler completely. 3679 */ 3680 this.unregisterPresenceHandler = function () { 3681 _presenceHandler = undefined; 3682 }; 3683 3684 /** 3685 * @private 3686 * Registers a handler to be invoked when a connection status changes. The 3687 * object passed will contain a "status" property, and a "resourceID" 3688 * property, which will contain the most current resource ID assigned to 3689 * the client. If there has already been an event that was delivered, the 3690 * handler will be invoked immediately. 3691 * @param {Function} handler 3692 * Invoked when a connection status changes. 3693 */ 3694 this.registerConnectionInfoHandler = function (handler) { 3695 if (typeof handler !== "function") { 3696 throw new Error("Parameter is not a function."); 3697 } 3698 _connInfoHandler = handler; 3699 if (_statusCache) { 3700 handler(_createConnInfoObj()); 3701 } 3702 }; 3703 3704 /** 3705 * @private 3706 * Unregisters the connection information handler. 3707 */ 3708 this.unregisterConnectionInfoHandler = function () { 3709 _connInfoHandler = undefined; 3710 }; 3711 3712 /** 3713 * @private 3714 * Start listening for events and create a event tunnel for the shared BOSH 3715 * connection. 3716 * @param {String} id 3717 * The ID of the user for the notification server. 3718 * @param {String} password 3719 * The password of the user for the notification server. 3720 * @param {String} xmppDomain 3721 * The XMPP domain of the notification server 3722 * @param {String} pubsubDomain 3723 * The location (JID) of the XEP-0060 PubSub service 3724 * @param {String} resource 3725 * The resource to connect to the notification servier with. 3726 */ 3727 this.init = function (id, password, xmppDomain, pubsubDomain, resource) { 3728 3729 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string") { 3730 throw new Error("Invalid or missing required parameters."); 3731 } 3732 3733 _initTunnelConfig(); 3734 3735 _id = id; 3736 _password = password; 3737 _xmppDomain = xmppDomain; 3738 _pubsubDomain = pubsubDomain; 3739 _resource = resource; 3740 3741 //Attach a listener for messages sent from tunnel frame. 3742 _util.receiveMessage(_messageHandler, _tunnelOrigin); 3743 3744 //Create the tunnel iframe which will establish the shared connection. 3745 _createTunnel(); 3746 }; 3747 3748 //BEGIN TEST CODE// 3749 // /** 3750 // * Test code added to expose private functions that are used by unit test 3751 // * framework. This section of code is removed during the build process 3752 // * before packaging production code. The [begin|end]TestSection are used 3753 // * by the build to identify the section to strip. 3754 // * @ignore 3755 // */ 3756 // this.beginTestSection = 0; 3757 // 3758 // /** 3759 // * @ignore 3760 // */ 3761 // this.getTestObject = function () { 3762 // //Load mock dependencies. 3763 // var _mock = new MockControl(); 3764 // _util = _mock.createMock(finesse.utilities.Utilities); 3765 // 3766 // return { 3767 // //Expose mock dependencies 3768 // mock: _mock, 3769 // util: _util, 3770 // 3771 // //Expose internal private functions 3772 // types: _TYPES, 3773 // createConnInfoObj: _createConnInfoObj, 3774 // sendMessage: _sendMessage, 3775 // messageHandler: _messageHandler, 3776 // createTunnel: _createTunnel, 3777 // handlers: _handlers, 3778 // initTunnelConfig : _initTunnelConfig 3779 // }; 3780 // }; 3781 // 3782 // /** 3783 // * @ignore 3784 // */ 3785 // this.endTestSection = 0; 3786 // //END TEST CODE// 3787 }; 3788 3789 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 3790 finesse.clientservices = finesse.clientservices || {}; 3791 3792 window.finesse = window.finesse || {}; 3793 window.finesse.clientservices = window.finesse.clientservices || {}; 3794 window.finesse.clientservices.MasterTunnel = MasterTunnel; 3795 3796 return MasterTunnel; 3797 3798 }); 3799 3800 /** 3801 * Contains a list of topics used for client side pubsub. 3802 * 3803 */ 3804 3805 /** @private */ 3806 define('clientservices/Topics',[], function () { 3807 3808 var Topics = (function () { 3809 3810 /** 3811 * @private 3812 * The namespace prepended to all Finesse topics. 3813 */ 3814 this.namespace = "finesse.info"; 3815 3816 /** 3817 * @private 3818 * Gets the full topic name with the Finesse namespace prepended. 3819 * @param {String} topic 3820 * The topic category. 3821 * @returns {String} 3822 * The full topic name with prepended namespace. 3823 */ 3824 var _getNSTopic = function (topic) { 3825 return this.namespace + "." + topic; 3826 }; 3827 3828 /** @scope finesse.clientservices.Topics */ 3829 return { 3830 /** 3831 * @private 3832 * Client side request channel. 3833 */ 3834 REQUESTS: _getNSTopic("requests"), 3835 3836 /** 3837 * @private 3838 * Client side response channel. 3839 */ 3840 RESPONSES: _getNSTopic("responses"), 3841 3842 /** 3843 * @private 3844 * Connection status. 3845 */ 3846 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 3847 3848 /** 3849 * @private 3850 * Presence channel 3851 */ 3852 PRESENCE: _getNSTopic("presence"), 3853 3854 /** 3855 * Topic for listening to token refresh events. 3856 * The provided callback will be invoked when the access token is refreshed. 3857 * This event is only meant for updating the access token in gadget Config object 3858 */ 3859 ACCESS_TOKEN_REFRESHED_EVENT: _getNSTopic("accessTokenRefresh"), 3860 3861 /** 3862 * @private 3863 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 3864 */ 3865 getTopic: function (restUri) { 3866 //The topic should not start with '/' else it will get replaced with 3867 //'.' which is invalid. 3868 //Thus, remove '/' if it is at the beginning of the string 3869 if (restUri.indexOf('/') === 0) { 3870 restUri = restUri.substr(1); 3871 } 3872 3873 //Replace every instance of "/" with ".". This is done to follow the 3874 //OpenAjaxHub topic name convention. 3875 return restUri.replace(/\//g, "."); 3876 } 3877 }; 3878 }()); 3879 window.finesse = window.finesse || {}; 3880 window.finesse.clientservices = window.finesse.clientservices || {}; 3881 /** @private */ 3882 window.finesse.clientservices.Topics = Topics; 3883 3884 return Topics; 3885 }); 3886 /** The following comment is to prevent jslint errors about 3887 * using variables before they are defined. 3888 */ 3889 /*global finesse*/ 3890 3891 /** 3892 * Registers with the MasterTunnel to receive events, which it 3893 * could publish to the OpenAjax gadget pubsub infrastructure. 3894 * 3895 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 3896 */ 3897 3898 /** @private */ 3899 define('clientservices/MasterPublisher',[ 3900 "clientservices/MasterTunnel", 3901 "clientservices/Topics", 3902 "utilities/Utilities" 3903 ], 3904 function (MasterTunnel, Topics, Utilities) { 3905 3906 var MasterPublisher = function (tunnel, hub) { 3907 if (!(tunnel instanceof MasterTunnel)) { 3908 throw new Error("Required tunnel object missing or invalid."); 3909 } 3910 3911 var 3912 3913 ClientServices = finesse.clientservices.ClientServices, 3914 3915 /** 3916 * Reference to the gadget pubsub Hub instance. 3917 * @private 3918 */ 3919 _hub = hub, 3920 3921 /** 3922 * Reference to the Topics class. 3923 * @private 3924 */ 3925 _topics = Topics, 3926 3927 /** 3928 * Reference to conversion utilities class. 3929 * @private 3930 */ 3931 _utils = Utilities, 3932 3933 /** 3934 * References to ClientServices logger methods 3935 * @private 3936 */ 3937 _logger = { 3938 log: ClientServices.log 3939 }, 3940 3941 /** 3942 * Store the passed in tunnel. 3943 * @private 3944 */ 3945 _tunnel = tunnel, 3946 3947 /** 3948 * Caches the connection info event so that it could be published if there 3949 * is a request for it. 3950 * @private 3951 */ 3952 _connInfoCache = {}, 3953 3954 /** 3955 * The types of possible request types supported when listening to the 3956 * requests channel. Each request type could result in different operations. 3957 * @private 3958 */ 3959 _REQTYPES = { 3960 CONNECTIONINFO: "ConnectionInfoReq", 3961 SUBSCRIBE: "SubscribeNodeReq", 3962 UNSUBSCRIBE: "UnsubscribeNodeReq", 3963 CONNECT: "ConnectionReq" 3964 }, 3965 3966 /** 3967 * Will store list of nodes that have OF subscriptions created 3968 * _nodesList[node][subscribing].reqIds[subid] 3969 * _nodesList[node][active].reqIds[subid] 3970 * _nodesList[node][unsubscribing].reqIds[subid] 3971 * _nodesList[node][holding].reqIds[subid] 3972 * @private 3973 */ 3974 _nodesList = {}, 3975 3976 /** 3977 * The states that a subscription can be in 3978 * @private 3979 */ 3980 _CHANNELSTATES = { 3981 UNINITIALIZED: "Uninitialized", 3982 PENDING: "Pending", 3983 OPERATIONAL: "Operational" 3984 }, 3985 3986 /** 3987 * Checks if the payload is JSON 3988 * @returns {Boolean} 3989 * @private 3990 */ 3991 _isJsonPayload = function(event) { 3992 var delimStart, delimEnd, retval = false; 3993 3994 try { 3995 delimStart = event.indexOf('{'); 3996 delimEnd = event.lastIndexOf('}'); 3997 3998 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 3999 retval = true; //event contains JSON payload 4000 } 4001 } catch (err) { 4002 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 4003 } 4004 return retval; 4005 }, 4006 4007 /** 4008 * Parses a JSON event and then publishes. 4009 * 4010 * @param {String} event 4011 * The full event payload. 4012 * @throws {Error} If the payload object is malformed. 4013 * @private 4014 */ 4015 _parseAndPublishJSONEvent = function(event) { 4016 var topic, eventObj, publishEvent, 4017 delimPos = event.indexOf("{"), 4018 node, parser, 4019 eventJson = event, 4020 returnObj = {node: null, data: null}; 4021 4022 try { 4023 //Extract and strip the node path from the message 4024 if (delimPos > 0) 4025 { 4026 //We need to decode the URI encoded node path 4027 //TODO: make sure this is kosher with OpenAjax topic naming 4028 node = decodeURI(event.substr(0, delimPos)); 4029 eventJson = event.substr(delimPos); 4030 4031 //Converting the node path to openAjaxhub topic 4032 topic = _topics.getTopic(node); 4033 4034 returnObj.node = node; 4035 returnObj.payload = eventJson; 4036 } 4037 else 4038 { 4039 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 4040 throw new Error("node is not given in postMessage: " + eventJson); 4041 } 4042 4043 parser = _utils.getJSONParser(); 4044 4045 eventObj = parser.parse(eventJson); 4046 returnObj.data = eventObj; 4047 4048 } catch (err) { 4049 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 4050 throw new Error("Malformed event payload : " + err); 4051 } 4052 4053 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 4054 4055 publishEvent = {content : event, object : eventObj }; 4056 4057 //Publish event to proper event topic. 4058 if (topic && eventObj) { 4059 _hub.publish(topic, publishEvent); 4060 } 4061 }, 4062 4063 /** 4064 * Parses an XML event and then publishes. 4065 * 4066 * @param {String} event 4067 * The full event payload. 4068 * @throws {Error} If the payload object is malformed. 4069 * @private 4070 */ 4071 _parseAndPublishXMLEvent = function(event) { 4072 var topic, eventObj, publishEvent, restTopic, 4073 delimPos = event.indexOf("<"), 4074 node, 4075 eventXml = event; 4076 4077 try { 4078 //Extract and strip the node path from the message 4079 if (delimPos > 0) { 4080 //We need to decode the URI encoded node path 4081 //TODO: make sure this is kosher with OpenAjax topic naming 4082 node = decodeURI(event.substr(0, delimPos)); 4083 eventXml = event.substr(delimPos); 4084 //Converting the node path to openAjaxhub topic 4085 topic = _topics.getTopic(node); 4086 } else { 4087 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 4088 throw new Error("node is not given in postMessage: " + eventXml); 4089 } 4090 4091 eventObj = _utils.xml2JsObj(eventXml); 4092 4093 } catch (err) { 4094 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 4095 throw new Error("Malformed event payload : " + err); 4096 } 4097 4098 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 4099 4100 publishEvent = {content : event, object : eventObj }; 4101 4102 //Publish event to proper event topic. 4103 if (topic && eventObj) { 4104 _hub.publish(topic, publishEvent); 4105 } 4106 }, 4107 4108 /** 4109 * Publishes events to the appropriate topic. The topic name is determined 4110 * by fetching the source value from the event. 4111 * @param {String} event 4112 * The full event payload. 4113 * @throws {Error} If the payload object is malformed. 4114 * @private 4115 */ 4116 _eventHandler = function (event) { 4117 4118 //Handle JSON or XML events 4119 if (!_isJsonPayload(event)) 4120 { 4121 //XML 4122 _parseAndPublishXMLEvent(event); 4123 } 4124 else 4125 { 4126 //JSON 4127 _parseAndPublishJSONEvent(event); 4128 } 4129 }, 4130 4131 4132 /** 4133 * Handler for when presence events are sent through the MasterTunnel. 4134 * @returns {Object} 4135 * A presence xml event. 4136 * @private 4137 */ 4138 _presenceHandler = function (event) { 4139 var eventObj = _utils.xml2JsObj(event), publishEvent; 4140 4141 publishEvent = {content : event, object : eventObj}; 4142 4143 if (eventObj) { 4144 _hub.publish(_topics.PRESENCE, publishEvent); 4145 } 4146 }, 4147 4148 /** 4149 * Clone the connection info object from cache. 4150 * @returns {Object} 4151 * A connection info object containing a "status" and "resourceID". 4152 * @private 4153 */ 4154 _cloneConnInfoObj = function () { 4155 if (_connInfoCache) { 4156 return { 4157 status: _connInfoCache.status, 4158 resourceID: _connInfoCache.resourceID 4159 }; 4160 } else { 4161 return null; 4162 } 4163 }, 4164 4165 /** 4166 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4167 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4168 * @private 4169 */ 4170 _cleanupPendingRequests = function () { 4171 var node, curSubid, errObj = { 4172 error: { 4173 errorType: "Disconnected", 4174 errorMessage: "Outstanding request will never complete." 4175 } 4176 }; 4177 4178 // Iterate through all outstanding subscribe requests to notify them that it will never return 4179 for (node in _nodesList) { 4180 if (_nodesList.hasOwnProperty(node)) { 4181 for (curSubid in _nodesList[node].subscribing.reqIds) { 4182 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4183 // Notify this outstanding subscribe request to give up and error out 4184 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4185 } 4186 } 4187 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4188 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4189 // Notify this outstanding unsubscribe request to give up and error out 4190 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4191 } 4192 } 4193 } 4194 } 4195 }, 4196 4197 /** 4198 * Publishes the connection info to the connection info topic. 4199 * @param {Object} connInfo 4200 * The connection info object containing the status and resource ID. 4201 * @private 4202 */ 4203 _connInfoHandler = function (connInfo) { 4204 var before = _connInfoCache.status; 4205 _connInfoCache = connInfo; 4206 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4207 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4208 if (before === "connected" && connInfo.status !== "connected") { 4209 // Fail all pending requests when we transition to disconnected 4210 _cleanupPendingRequests(); 4211 } 4212 }, 4213 4214 4215 /** 4216 * Utility method to bookkeep node subscription requests and determine 4217 * whehter it is necessary to tunnel the request to JabberWerx. 4218 * @param {String} node 4219 * The node of interest 4220 * @param {String} reqId 4221 * A unique string identifying the request/subscription 4222 * @private 4223 */ 4224 _subscribeNode = function (node, subid) { 4225 if (_connInfoCache.status !== "connected") { 4226 _hub.publish(_topics.RESPONSES + "." + subid, { 4227 error: { 4228 errorType: "Not connected", 4229 errorMessage: "Cannot subscribe without connection." 4230 } 4231 }); 4232 return; 4233 } 4234 // NODE DOES NOT YET EXIST 4235 if (!_nodesList[node]) { 4236 _nodesList[node] = { 4237 "subscribing": { 4238 "reqIds": {}, 4239 "length": 0 4240 }, 4241 "active": { 4242 "reqIds": {}, 4243 "length": 0 4244 }, 4245 "unsubscribing": { 4246 "reqIds": {}, 4247 "length": 0 4248 }, 4249 "holding": { 4250 "reqIds": {}, 4251 "length": 0 4252 } 4253 }; 4254 } 4255 if (_nodesList[node].active.length === 0) { 4256 if (_nodesList[node].unsubscribing.length === 0) { 4257 if (_nodesList[node].subscribing.length === 0) { 4258 _nodesList[node].subscribing.reqIds[subid] = true; 4259 _nodesList[node].subscribing.length += 1; 4260 4261 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4262 _tunnel.subscribe(node, function (err) { 4263 var errObj, curSubid; 4264 if (err) { 4265 errObj = { 4266 subscribe: { 4267 content: err 4268 } 4269 }; 4270 4271 try { 4272 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4273 } catch (e) { 4274 errObj.error = { 4275 errorType: "parseError", 4276 errorMessage: "Could not serialize XML: " + e 4277 }; 4278 } 4279 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4280 } else { 4281 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4282 } 4283 4284 for (curSubid in _nodesList[node].subscribing.reqIds) { 4285 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4286 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4287 if (!err) { 4288 _nodesList[node].active.reqIds[curSubid] = true; 4289 _nodesList[node].active.length += 1; 4290 } 4291 delete _nodesList[node].subscribing.reqIds[curSubid]; 4292 _nodesList[node].subscribing.length -= 1; 4293 } 4294 } 4295 }); 4296 4297 } else { //other ids are subscribing 4298 _nodesList[node].subscribing.reqIds[subid] = true; 4299 _nodesList[node].subscribing.length += 1; 4300 } 4301 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 4302 _nodesList[node].holding.reqIds[subid] = true; 4303 _nodesList[node].holding.length += 1; 4304 } 4305 } else { // The node has active subscriptions; add this subid and return successful response 4306 _nodesList[node].active.reqIds[subid] = true; 4307 _nodesList[node].active.length += 1; 4308 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4309 } 4310 }, 4311 4312 /** 4313 * Utility method to bookkeep node unsubscribe requests and determine 4314 * whehter it is necessary to tunnel the request to JabberWerx. 4315 * @param {String} node 4316 * The node to unsubscribe from 4317 * @param {String} reqId 4318 * A unique string identifying the subscription to remove 4319 * @private 4320 */ 4321 _unsubscribeNode = function (node, subid) { 4322 if (!_nodesList[node]) { //node DNE, publish success response 4323 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4324 } else { 4325 if (_connInfoCache.status !== "connected") { 4326 _hub.publish(_topics.RESPONSES + "." + subid, { 4327 error: { 4328 errorType: "Not connected", 4329 errorMessage: "Cannot unsubscribe without connection." 4330 } 4331 }); 4332 return; 4333 } 4334 if (_nodesList[node].active.length > 1) { 4335 delete _nodesList[node].active.reqIds[subid]; 4336 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4337 _nodesList[node].active.length -= 1; 4338 } else if (_nodesList[node].active.length === 1) { // transition subid from active category to unsubscribing category 4339 _nodesList[node].unsubscribing.reqIds[subid] = true; 4340 _nodesList[node].unsubscribing.length += 1; 4341 delete _nodesList[node].active.reqIds[subid]; 4342 _nodesList[node].active.length -= 1; 4343 4344 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 4345 _tunnel.unsubscribe(node, function (err) { 4346 var errObj, curSubid; 4347 if (err) { 4348 errObj = { 4349 subscribe: { 4350 content: err 4351 } 4352 }; 4353 4354 try { 4355 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4356 } catch (e) { 4357 errObj.error = { 4358 errorType: "parseError", 4359 errorMessage: "Could not serialize XML: " + e 4360 }; 4361 } 4362 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 4363 } else { 4364 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 4365 } 4366 4367 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4368 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4369 // publish to all subids whether unsubscribe failed or succeeded 4370 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4371 if (!err) { 4372 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4373 _nodesList[node].unsubscribing.length -= 1; 4374 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 4375 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4376 _nodesList[node].unsubscribing.length -= 1; 4377 } 4378 } 4379 } 4380 4381 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 4382 for (curSubid in _nodesList[node].holding.reqIds) { 4383 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 4384 delete _nodesList[node].holding.reqIds[curSubid]; 4385 _nodesList[node].holding.length -= 1; 4386 _subscribeNode(node, curSubid); 4387 } 4388 } 4389 } 4390 }); 4391 } else { // length <= 0? 4392 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4393 } 4394 } 4395 }, 4396 4397 /** 4398 * Handles client requests to establish a BOSH connection. 4399 * @param {String} id 4400 * id of the xmpp user 4401 * @param {String} password 4402 * password of the xmpp user 4403 * @param {String} xmppDomain 4404 * xmppDomain of the xmpp user account 4405 * @private 4406 */ 4407 _connect = function (id, password, xmppDomain) { 4408 _tunnel.makeConnectReq(id, password, xmppDomain); 4409 }, 4410 4411 /** 4412 * Handles client requests made to the request topic. The type of the 4413 * request is described in the "type" property within the data payload. Each 4414 * type can result in a different operation. 4415 * @param {String} topic 4416 * The topic which data was published to. 4417 * @param {Object} data 4418 * The data containing requests information published by clients. 4419 * @param {String} data.type 4420 * The type of the request. Supported: "ConnectionInfoReq" 4421 * @param {Object} data.data 4422 * May contain data relevant for the particular requests. 4423 * @param {String} [data.invokeID] 4424 * The ID used to identify the request with the response. The invoke ID 4425 * will be included in the data in the publish to the topic. It is the 4426 * responsibility of the client to correlate the published data to the 4427 * request made by using the invoke ID. 4428 * @private 4429 */ 4430 _clientRequestHandler = function (topic, data) { 4431 var dataCopy; 4432 4433 //Ensure a valid data object with "type" and "data" properties. 4434 if (typeof data === "object" && 4435 typeof data.type === "string" && 4436 typeof data.data === "object") { 4437 switch (data.type) { 4438 case _REQTYPES.CONNECTIONINFO: 4439 //It is possible that Slave clients come up before the Master 4440 //client. If that is the case, the Slaves will need to make a 4441 //request for the Master to send the latest connection info to the 4442 //connectionInfo topic. 4443 dataCopy = _cloneConnInfoObj(); 4444 if (dataCopy) { 4445 if (data.invokeID !== undefined) { 4446 dataCopy.invokeID = data.invokeID; 4447 } 4448 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 4449 } 4450 break; 4451 case _REQTYPES.SUBSCRIBE: 4452 if (typeof data.data.node === "string") { 4453 _subscribeNode(data.data.node, data.invokeID); 4454 } 4455 break; 4456 case _REQTYPES.UNSUBSCRIBE: 4457 if (typeof data.data.node === "string") { 4458 _unsubscribeNode(data.data.node, data.invokeID); 4459 } 4460 break; 4461 case _REQTYPES.CONNECT: 4462 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 4463 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 4464 break; 4465 default: 4466 break; 4467 } 4468 } 4469 }; 4470 4471 (function () { 4472 //Register to receive events and connection status from tunnel. 4473 _tunnel.registerEventHandler(_eventHandler); 4474 _tunnel.registerPresenceHandler(_presenceHandler); 4475 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 4476 4477 //Listen to a request channel to respond to any requests made by other 4478 //clients because the Master may have access to useful information. 4479 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 4480 }()); 4481 4482 /** 4483 * @private 4484 * Handles client requests to establish a BOSH connection. 4485 * @param {String} id 4486 * id of the xmpp user 4487 * @param {String} password 4488 * password of the xmpp user 4489 * @param {String} xmppDomain 4490 * xmppDomain of the xmpp user account 4491 */ 4492 this.connect = function (id, password, xmppDomain) { 4493 _connect(id, password, xmppDomain); 4494 }; 4495 4496 /** 4497 * @private 4498 * Resets the list of explicit subscriptions 4499 */ 4500 this.wipeout = function () { 4501 _cleanupPendingRequests(); 4502 _nodesList = {}; 4503 }; 4504 4505 //BEGIN TEST CODE// 4506 /** 4507 * Test code added to expose private functions that are used by unit test 4508 * framework. This section of code is removed during the build process 4509 * before packaging production code. The [begin|end]TestSection are used 4510 * by the build to identify the section to strip. 4511 * @ignore 4512 */ 4513 this.beginTestSection = 0; 4514 4515 /** 4516 * @ignore 4517 */ 4518 this.getTestObject = function () { 4519 //Load mock dependencies. 4520 var _mock = new MockControl(); 4521 _hub = _mock.createMock(gadgets.Hub); 4522 _tunnel = _mock.createMock(); 4523 4524 return { 4525 //Expose mock dependencies 4526 mock: _mock, 4527 hub: _hub, 4528 tunnel: _tunnel, 4529 setTunnel: function (tunnel) { 4530 _tunnel = tunnel; 4531 }, 4532 getTunnel: function () { 4533 return _tunnel; 4534 }, 4535 4536 //Expose internal private functions 4537 reqtypes: _REQTYPES, 4538 eventHandler: _eventHandler, 4539 presenceHandler: _presenceHandler, 4540 4541 subscribeNode: _subscribeNode, 4542 unsubscribeNode: _unsubscribeNode, 4543 4544 getNodeList: function () { 4545 return _nodesList; 4546 }, 4547 setNodeList: function (nodelist) { 4548 _nodesList = nodelist; 4549 }, 4550 4551 cloneConnInfoObj: _cloneConnInfoObj, 4552 connInfoHandler: _connInfoHandler, 4553 clientRequestHandler: _clientRequestHandler 4554 4555 }; 4556 }; 4557 4558 4559 /** 4560 * @ignore 4561 */ 4562 this.endTestSection = 0; 4563 //END TEST CODE// 4564 4565 }; 4566 4567 window.finesse = window.finesse || {}; 4568 window.finesse.clientservices = window.finesse.clientservices || {}; 4569 window.finesse.clientservices.MasterPublisher = MasterPublisher; 4570 4571 return MasterPublisher; 4572 }); 4573 4574 /** The following comment is to prevent jslint errors about 4575 * using variables before they are defined. 4576 */ 4577 /*global publisher:true */ 4578 4579 /** 4580 * Exposes a set of API wrappers that will hide the dirty work of 4581 * constructing Finesse API requests and consuming Finesse events. 4582 * 4583 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 4584 */ 4585 4586 4587 /** 4588 * Allow clients to make Finesse API requests and consume Finesse events by 4589 * calling a set of exposed functions. The Services layer will do the dirty 4590 * work of establishing a shared BOSH connection (for designated Master 4591 * modules), consuming events for client subscriptions, and constructing API 4592 * requests. 4593 */ 4594 /** @private */ 4595 define('clientservices/ClientServices',[ 4596 "clientservices/MasterTunnel", 4597 "clientservices/MasterPublisher", 4598 "clientservices/Topics", 4599 "utilities/Utilities" 4600 ], 4601 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 4602 4603 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 4604 var 4605 4606 /** 4607 * Shortcut reference to the master tunnel 4608 * @private 4609 */ 4610 _tunnel, 4611 4612 _publisher, 4613 4614 /** 4615 * Shortcut reference to the finesse.utilities.Utilities singleton 4616 * This will be set by init() 4617 * @private 4618 */ 4619 _util, 4620 4621 /** 4622 * Shortcut reference to the gadgets.io object. 4623 * This will be set by init() 4624 * @private 4625 */ 4626 _io, 4627 4628 /** 4629 * Shortcut reference to the gadget pubsub Hub instance. 4630 * This will be set by init() 4631 * @private 4632 */ 4633 _hub, 4634 4635 /** 4636 * Logger object set externally by setLogger, defaults to nothing. 4637 * @private 4638 */ 4639 _logger = {}, 4640 4641 /** 4642 * Shortcut reference to the Topics class. 4643 * This will be set by init() 4644 * @private 4645 */ 4646 _topics, 4647 4648 /** 4649 * Config object needed to initialize this library 4650 * This must be set by init() 4651 * @private 4652 */ 4653 _config, 4654 4655 /** 4656 * @private 4657 * Whether or not this ClientService instance is a Master. 4658 */ 4659 _isMaster = false, 4660 4661 /** 4662 * @private 4663 * Whether the Client Services have been initiated yet. 4664 */ 4665 _inited = false, 4666 4667 /** 4668 * Stores the list of subscription IDs for all subscriptions so that it 4669 * could be retrieve for unsubscriptions. 4670 * @private 4671 */ 4672 _subscriptionID = {}, 4673 4674 /** 4675 * The possible states of the JabberWerx BOSH connection. 4676 * @private 4677 */ 4678 _STATUS = { 4679 CONNECTING: "connecting", 4680 CONNECTED: "connected", 4681 DISCONNECTED: "disconnected", 4682 DISCONNECTED_CONFLICT: "conflict", 4683 DISCONNECTED_UNAUTHORIZED: "unauthorized", 4684 DISCONNECTING: "disconnecting", 4685 RECONNECTING: "reconnecting", 4686 UNLOADING: "unloading", 4687 FAILING: "failing", 4688 RECOVERED: "recovered" 4689 }, 4690 4691 /** 4692 * Local reference for authMode enum object. 4693 * @private 4694 */ 4695 _authModes, 4696 4697 _failoverMode = false, 4698 4699 /** 4700 * Handler function to be invoked when BOSH connection is connecting. 4701 * @private 4702 */ 4703 _onConnectingHandler, 4704 4705 /** 4706 * Handler function to be invoked when BOSH connection is connected 4707 * @private 4708 */ 4709 _onConnectHandler, 4710 4711 /** 4712 * Handler function to be invoked when BOSH connection is disconnecting. 4713 * @private 4714 */ 4715 _onDisconnectingHandler, 4716 4717 /** 4718 * Handler function to be invoked when the BOSH is disconnected. 4719 * @private 4720 */ 4721 _onDisconnectHandler, 4722 4723 /** 4724 * Handler function to be invoked when the BOSH is reconnecting. 4725 * @private 4726 */ 4727 _onReconnectingHandler, 4728 4729 /** 4730 * Handler function to be invoked when the BOSH is unloading. 4731 * @private 4732 */ 4733 _onUnloadingHandler, 4734 4735 /** 4736 * Contains a cache of the latest connection info containing the current 4737 * state of the BOSH connection and the resource ID. 4738 * @private 4739 */ 4740 _connInfo, 4741 4742 /** 4743 * Keeps track of all the objects that need to be refreshed when we recover 4744 * due to our resilient connection. Only objects that we subscribe to will 4745 * be added to this list. 4746 * @private 4747 */ 4748 _refreshList = [], 4749 4750 /** 4751 * Needs to be passed as authorization header inside makeRequest wrapper function 4752 */ 4753 _authHeaderString, 4754 4755 /** 4756 * @private 4757 * Centralized logger.log method for external logger 4758 * @param {String} msg 4759 */ 4760 _log = function (msg) { 4761 // If the external logger throws up, it stops here. 4762 try { 4763 if (_logger.log) { 4764 _logger.log("[ClientServices] " + msg); 4765 } 4766 } catch (e) { } 4767 }, 4768 4769 /** 4770 * Go through each object in the _refreshList and call its refresh() function 4771 * @private 4772 */ 4773 _refreshObjects = function () { 4774 var i; 4775 4776 // wipe out the explicit subscription list before we refresh objects 4777 if (_publisher) { 4778 _publisher.wipeout(); 4779 } 4780 4781 // refresh each item in the refresh list 4782 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4783 _log("Refreshing " + _refreshList[i].getRestUrl()); 4784 _refreshList[i].refresh(10); 4785 } 4786 }, 4787 4788 /** 4789 * Handler to process connection info publishes. 4790 * @param {Object} data 4791 * The connection info data object. 4792 * @param {String} data.status 4793 * The BOSH connection status. 4794 * @param {String} data.resourceID 4795 * The resource ID for the connection. 4796 * @private 4797 */ 4798 _connInfoHandler = function (data) { 4799 4800 //Invoke registered handler depending on status received. Due to the 4801 //request topic where clients can make request for the Master to publish 4802 //the connection info, there is a chance that duplicate connection info 4803 //events may be sent, so ensure that there has been a state change 4804 //before invoking the handlers. 4805 if (_connInfo === undefined || _connInfo.status !== data.status) { 4806 _connInfo = data; 4807 switch (data.status) { 4808 case _STATUS.CONNECTING: 4809 if (_isMaster && _onConnectingHandler) { 4810 _onConnectingHandler(); 4811 } 4812 break; 4813 case _STATUS.CONNECTED: 4814 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 4815 _onConnectHandler(); 4816 } 4817 break; 4818 case _STATUS.DISCONNECTED: 4819 if (_isMaster && _onDisconnectHandler) { 4820 _onDisconnectHandler(); 4821 } 4822 break; 4823 case _STATUS.DISCONNECTED_CONFLICT: 4824 if (_isMaster && _onDisconnectHandler) { 4825 _onDisconnectHandler("conflict"); 4826 } 4827 break; 4828 case _STATUS.DISCONNECTED_UNAUTHORIZED: 4829 if (_isMaster && _onDisconnectHandler) { 4830 _onDisconnectHandler("unauthorized"); 4831 } 4832 break; 4833 case _STATUS.DISCONNECTING: 4834 if (_isMaster && _onDisconnectingHandler) { 4835 _onDisconnectingHandler(); 4836 } 4837 break; 4838 case _STATUS.RECONNECTING: 4839 if (_isMaster && _onReconnectingHandler) { 4840 _onReconnectingHandler(); 4841 } 4842 break; 4843 case _STATUS.UNLOADING: 4844 if (_isMaster && _onUnloadingHandler) { 4845 _onUnloadingHandler(); 4846 } 4847 break; 4848 case _STATUS.FAILING: 4849 if (!_isMaster) { 4850 // Stop 4851 _failoverMode = true; 4852 if (_onDisconnectHandler) { 4853 _onDisconnectHandler(); 4854 } 4855 } 4856 break; 4857 case _STATUS.RECOVERED: 4858 if (!_isMaster) { 4859 _failoverMode = false; 4860 if (_onConnectHandler) { 4861 _onConnectHandler(); 4862 } 4863 } 4864 // Whenever we are recovered, we need to refresh any objects 4865 // that are stored. 4866 _refreshObjects(); 4867 break; 4868 } 4869 } 4870 }, 4871 4872 /** 4873 * Ensure that ClientServices have been inited. 4874 * @private 4875 */ 4876 _isInited = function () { 4877 if (!_inited) { 4878 throw new Error("ClientServices needs to be inited."); 4879 } 4880 }, 4881 4882 /** 4883 * Have the client become the Master by initiating a tunnel to a shared 4884 * event BOSH connection. The Master is responsible for publishing all 4885 * events to the pubsub infrastructure. 4886 * 4887 * TODO: Currently we only check the global auth mode. This code has to 4888 * handle mixed mode - in this case the user specfic SSO mode has to be 4889 * exposed via an API. 4890 * 4891 * @private 4892 */ 4893 _becomeMaster = function () { 4894 var creds , id; 4895 _tunnel = new MasterTunnel(_config.host, _config.scheme); 4896 _publisher = new MasterPublisher(_tunnel, _hub); 4897 if(_authModes.SSO === _config.systemAuthMode ) { 4898 creds = _config.authToken; 4899 } else { 4900 creds = _config.password; 4901 } 4902 _util = Utilities; 4903 id = _util.encodeNodeName(_config.id); 4904 _tunnel.init(id, creds, _config.xmppDomain, _config.pubsubDomain, _config.resource); 4905 _isMaster = true; 4906 }, 4907 4908 /** 4909 * Make a request to the request channel to have the Master publish the 4910 * connection info object. 4911 * @private 4912 */ 4913 _makeConnectionInfoReq = function () { 4914 var data = { 4915 type: "ConnectionInfoReq", 4916 data: {}, 4917 invokeID: (new Date()).getTime() 4918 }; 4919 _hub.publish(_topics.REQUESTS, data); 4920 }, 4921 4922 /** 4923 * Utility method to register a handler which is associated with a 4924 * particular connection status. 4925 * @param {String} status 4926 * The connection status string. 4927 * @param {Function} handler 4928 * The handler to associate with a particular connection status. 4929 * @throws {Error} 4930 * If the handler provided is not a function. 4931 * @private 4932 */ 4933 _registerHandler = function (status, handler) { 4934 if (typeof handler === "function") { 4935 if (_connInfo && _connInfo.status === status) { 4936 handler(); 4937 } 4938 switch (status) { 4939 case _STATUS.CONNECTING: 4940 _onConnectingHandler = handler; 4941 break; 4942 case _STATUS.CONNECTED: 4943 _onConnectHandler = handler; 4944 break; 4945 case _STATUS.DISCONNECTED: 4946 _onDisconnectHandler = handler; 4947 break; 4948 case _STATUS.DISCONNECTING: 4949 _onDisconnectingHandler = handler; 4950 break; 4951 case _STATUS.RECONNECTING: 4952 _onReconnectingHandler = handler; 4953 break; 4954 case _STATUS.UNLOADING: 4955 _onUnloadingHandler = handler; 4956 break; 4957 } 4958 4959 } else { 4960 throw new Error("Callback is not a function"); 4961 } 4962 }, 4963 4964 /** 4965 * Callback function that is called when a refresh access token event message is posted to the Hub. 4966 * 4967 * Get access token from the data and update the finesse.gadget.Config object! 4968 * 4969 * @param {String} topic 4970 * which topic the event came on (unused) 4971 * @param {Object} data 4972 * the data published with the event 4973 * @private 4974 */ 4975 _accessTokenRefreshHandler = function(topic , data){ 4976 _log("Access token refreshed - topic :" + topic + ", authToken :" + data.authToken); 4977 4978 if(data.authToken){ 4979 _config.authToken = data.authToken; 4980 if(finesse.gadget && finesse.gadget.Config){ 4981 finesse.gadget.Config.authToken = data.authToken; 4982 } 4983 } 4984 }, 4985 4986 /** 4987 * @private 4988 * Retrieves systemAuthMode from parent Finesse Container. If parent is not available, mode will be retrieved from the systemInfo rest object 4989 * @throws {Error} 4990 * If unable to retrieve systemAuthMode 4991 */ 4992 _getSystemAuthMode = function(){ 4993 var parentFinesse , sysInfo; 4994 // For gadgets hosted outside of finesse container , finesse parent object will not be available 4995 try{ 4996 parentFinesse = window.parent.finesse; 4997 } catch (e){ 4998 parentFinesse = undefined; 4999 } 5000 5001 if( parentFinesse ){ 5002 _config.systemAuthMode = parentFinesse.container.Config.systemAuthMode; 5003 } else { 5004 sysInfo = new finesse.restservices.SystemInfo({ 5005 id: _config.id, 5006 onLoad: function (systemInfo) { 5007 _config.systemAuthMode = systemInfo.getSystemAuthMode(); 5008 }, 5009 onError: function (errRsp) { 5010 throw new Error("Unable to retrieve systemAuthMode from config. Initialization failed......"); 5011 } 5012 }); 5013 5014 } 5015 }; 5016 5017 return { 5018 5019 /** 5020 * @private 5021 * Adds an item to the list to be refreshed upon reconnect 5022 * @param {RestBase} object - rest object to be refreshed 5023 */ 5024 addToRefreshList: function (object) { 5025 _refreshList.push(object); 5026 }, 5027 5028 /** 5029 * @private 5030 * Removes the given item from the refresh list 5031 * @param {RestBase} object - rest object to be removed 5032 */ 5033 removeFromRefreshList: function (object) { 5034 var i; 5035 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5036 if (_refreshList[i] === object) { 5037 _refreshList.splice(i, 1); 5038 break; 5039 } 5040 } 5041 }, 5042 5043 /** 5044 * @private 5045 * The location of the tunnel HTML URL. 5046 * @returns {String} 5047 * The location of the tunnel HTML URL. 5048 */ 5049 getTunnelURL: function () { 5050 return _tunnel.getTunnelURL(); 5051 }, 5052 5053 /** 5054 * @private 5055 * Indicates whether the tunnel frame is loaded. 5056 * @returns {Boolean} 5057 * True if the tunnel frame is loaded, false otherwise. 5058 */ 5059 isTunnelLoaded: function () { 5060 return _tunnel.isTunnelLoaded(); 5061 }, 5062 5063 /** 5064 * @private 5065 * Indicates whether the ClientServices instance is a Master. 5066 * @returns {Boolean} 5067 * True if this instance of ClientServices is a Master, false otherwise. 5068 */ 5069 isMaster: function () { 5070 return _isMaster; 5071 }, 5072 5073 /** 5074 * @private 5075 * Get the resource ID. An ID is only available if the BOSH connection has 5076 * been able to connect successfully. 5077 * @returns {String} 5078 * The resource ID string. Null if the BOSH connection was never 5079 * successfully created and/or the resource ID has not been associated. 5080 */ 5081 getResourceID: function () { 5082 if (_connInfo !== undefined) { 5083 return _connInfo.resourceID; 5084 } 5085 return null; 5086 }, 5087 5088 /* 5089 getHub: function () { 5090 return _hub; 5091 }, 5092 */ 5093 /** 5094 * @private 5095 * Add a callback to be invoked when the BOSH connection is attempting 5096 * to connect. If the connection is already trying to connect, the 5097 * callback will be invoked immediately. 5098 * @param {Function} handler 5099 * An empty param function to be invoked on connecting. Only one 5100 * handler can be registered at a time. Handlers already registered 5101 * will be overwritten. 5102 */ 5103 registerOnConnectingHandler: function (handler) { 5104 _registerHandler(_STATUS.CONNECTING, handler); 5105 }, 5106 5107 /** 5108 * @private 5109 * Removes the on connecting callback that was registered. 5110 */ 5111 unregisterOnConnectingHandler: function () { 5112 _onConnectingHandler = undefined; 5113 }, 5114 5115 /** 5116 * Add a callback to be invoked when all of the following conditions are met: 5117 * <ul> 5118 * <li>When Finesse goes IN_SERVICE</li> 5119 * <li>The BOSH connection is established</li> 5120 * <li>The Finesse user presence becomes available</li> 5121 * </ul> 5122 * If all these conditions are met at the time this function is called, then 5123 * the handler will be invoked immediately. 5124 * @param {Function} handler 5125 * An empty param function to be invoked on connect. Only one handler 5126 * can be registered at a time. Handlers already registered will be 5127 * overwritten. 5128 * @example 5129 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 5130 */ 5131 registerOnConnectHandler: function (handler) { 5132 _registerHandler(_STATUS.CONNECTED, handler); 5133 }, 5134 5135 /** 5136 * @private 5137 * Removes the on connect callback that was registered. 5138 */ 5139 unregisterOnConnectHandler: function () { 5140 _onConnectHandler = undefined; 5141 }, 5142 5143 /** 5144 * Add a callback to be invoked when any of the following occurs: 5145 * <ul> 5146 * <li>Finesse is no longer IN_SERVICE</li> 5147 * <li>The BOSH connection is lost</li> 5148 * <li>The presence of the Finesse user is no longer available</li> 5149 * </ul> 5150 * If any of these conditions are met at the time this function is 5151 * called, the callback will be invoked immediately. 5152 * @param {Function} handler 5153 * An empty param function to be invoked on disconnected. Only one 5154 * handler can be registered at a time. Handlers already registered 5155 * will be overwritten. 5156 * @example 5157 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 5158 */ 5159 registerOnDisconnectHandler: function (handler) { 5160 _registerHandler(_STATUS.DISCONNECTED, handler); 5161 }, 5162 5163 /** 5164 * @private 5165 * Removes the on disconnect callback that was registered. 5166 */ 5167 unregisterOnDisconnectHandler: function () { 5168 _onDisconnectHandler = undefined; 5169 }, 5170 5171 /** 5172 * @private 5173 * Add a callback to be invoked when the BOSH is currently disconnecting. If 5174 * the connection is already disconnecting, invoke the callback immediately. 5175 * @param {Function} handler 5176 * An empty param function to be invoked on disconnected. Only one 5177 * handler can be registered at a time. Handlers already registered 5178 * will be overwritten. 5179 */ 5180 registerOnDisconnectingHandler: function (handler) { 5181 _registerHandler(_STATUS.DISCONNECTING, handler); 5182 }, 5183 5184 /** 5185 * @private 5186 * Removes the on disconnecting callback that was registered. 5187 */ 5188 unregisterOnDisconnectingHandler: function () { 5189 _onDisconnectingHandler = undefined; 5190 }, 5191 5192 /** 5193 * @private 5194 * Add a callback to be invoked when the BOSH connection is attempting 5195 * to connect. If the connection is already trying to connect, the 5196 * callback will be invoked immediately. 5197 * @param {Function} handler 5198 * An empty param function to be invoked on connecting. Only one 5199 * handler can be registered at a time. Handlers already registered 5200 * will be overwritten. 5201 */ 5202 registerOnReconnectingHandler: function (handler) { 5203 _registerHandler(_STATUS.RECONNECTING, handler); 5204 }, 5205 5206 /** 5207 * @private 5208 * Removes the on reconnecting callback that was registered. 5209 */ 5210 unregisterOnReconnectingHandler: function () { 5211 _onReconnectingHandler = undefined; 5212 }, 5213 5214 /** 5215 * @private 5216 * Add a callback to be invoked when the BOSH connection is unloading 5217 * 5218 * @param {Function} handler 5219 * An empty param function to be invoked on connecting. Only one 5220 * handler can be registered at a time. Handlers already registered 5221 * will be overwritten. 5222 */ 5223 registerOnUnloadingHandler: function (handler) { 5224 _registerHandler(_STATUS.UNLOADING, handler); 5225 }, 5226 5227 /** 5228 * @private 5229 * Removes the on unloading callback that was registered. 5230 */ 5231 unregisterOnUnloadingHandler: function () { 5232 _onUnloadingHandler = undefined; 5233 }, 5234 5235 /** 5236 * @private 5237 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5238 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5239 * configuration is encapsulated in here anyways. 5240 * This removes the dependency 5241 * @param {String} url 5242 * The relative url to make the request to (the host from the passed in config will be 5243 * appended). It is expected that any encoding to the URL is already done. 5244 * @param {Function} handler 5245 * Callback handler for makeRequest to invoke when the response returns. 5246 * Completely passed through to gadgets.io.makeRequest 5247 * @param {Object} params 5248 * The params object that gadgets.io.makeRequest expects. Authorization and locale 5249 * headers are mixed in. 5250 */ 5251 makeRequest: function (url, handler, params) { 5252 var requestedScheme, scheme = "http"; 5253 5254 // ClientServices needs to be initialized with a config for restHost, auth, and locale 5255 _isInited(); 5256 5257 // Allow mixin of auth and locale headers 5258 params = params || {}; 5259 5260 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 5261 // using GET http method because then the params are added to the url as query params, which 5262 // exposes the authorization string in the url. This is a placeholder until oauth comes in 5263 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 5264 5265 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 5266 5267 // Add Basic auth to request header 5268 params[gadgets.io.RequestParameters.HEADERS].Authorization = _util.getAuthHeaderString(_config); 5269 5270 //Locale 5271 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 5272 5273 //Allow clients to override the scheme: 5274 // - If not specified => we use HTTP 5275 // - If null specified => we use _config.scheme 5276 // - Otherwise => we use whatever they provide 5277 requestedScheme = params.SCHEME; 5278 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 5279 if (requestedScheme === null) { 5280 scheme = _config.scheme; 5281 } else { 5282 scheme = requestedScheme; 5283 } 5284 } 5285 scheme = _config.restScheme || scheme; 5286 5287 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 5288 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 5289 }, 5290 5291 /** 5292 * @private 5293 * Utility function to make a subscription to a particular topic. Only one 5294 * callback function is registered to a particular topic at any time. 5295 * @param {String} topic 5296 * The full topic name. The topic name should follow the OpenAjax 5297 * convention using dot notation (ex: finesse.api.User.1000). 5298 * @param {Function} callback 5299 * The function that should be invoked with the data when an event 5300 * is delivered to the specific topic. 5301 * @returns {Boolean} 5302 * True if the subscription was made successfully and the callback was 5303 * been registered. False if the subscription already exist, the 5304 * callback was not overwritten. 5305 */ 5306 subscribe: function (topic, callback, disableDuringFailover) { 5307 _isInited(); 5308 5309 //Ensure that the same subscription isn't made twice. 5310 if (!_subscriptionID[topic]) { 5311 //Store the subscription ID using the topic name as the key. 5312 _subscriptionID[topic] = _hub.subscribe(topic, 5313 //Invoke the callback just with the data object. 5314 function (topic, data) { 5315 if (!disableDuringFailover || _isMaster || !_failoverMode) { 5316 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 5317 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 5318 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 5319 // - Master instance will get all events regardless, because it is responsible for managing failover 5320 // - If we are not in a failover mode, everything goes 5321 // _refreshObjects will reconcile anything that was missed once we are back in action 5322 callback(data); 5323 } 5324 }); 5325 return true; 5326 } 5327 return false; 5328 }, 5329 5330 /** 5331 * @private 5332 * Unsubscribe from a particular topic. 5333 * @param {String} topic 5334 * The full topic name. 5335 */ 5336 unsubscribe: function (topic) { 5337 _isInited(); 5338 5339 //Unsubscribe from the topic using the subscription ID recorded when 5340 //the subscription was made, then delete the ID from data structure. 5341 if (_subscriptionID[topic]) { 5342 _hub.unsubscribe(_subscriptionID[topic]); 5343 delete _subscriptionID[topic]; 5344 } 5345 }, 5346 5347 /** 5348 * @private 5349 * Make a request to the request channel to have the Master subscribe 5350 * to a node. 5351 * @param {String} node 5352 * The node to subscribe to. 5353 */ 5354 subscribeNode: function (node, handler) { 5355 if (handler && typeof handler !== "function") { 5356 throw new Error("ClientServices.subscribeNode: handler is not a function"); 5357 } 5358 5359 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5360 var data = { 5361 type: "SubscribeNodeReq", 5362 data: {node: node}, 5363 invokeID: _util.generateUUID() 5364 }, 5365 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5366 _this = this; 5367 5368 // We need to first subscribe to the response channel 5369 this.subscribe(responseTopic, function (rsp) { 5370 // Since this channel is only used for this singular request, 5371 // we are not interested anymore. 5372 // This is also critical to not leaking memory by having OpenAjax 5373 // store a bunch of orphaned callback handlers that enclose on 5374 // our entire ClientServices singleton 5375 _this.unsubscribe(responseTopic); 5376 if (handler) { 5377 handler(data.invokeID, rsp); 5378 } 5379 }); 5380 // Then publish the request on the request channel 5381 _hub.publish(_topics.REQUESTS, data); 5382 }, 5383 5384 /** 5385 * @private 5386 * Make a request to the request channel to have the Master unsubscribe 5387 * from a node. 5388 * @param {String} node 5389 * The node to unsubscribe from. 5390 */ 5391 unsubscribeNode: function (node, subid, handler) { 5392 if (handler && typeof handler !== "function") { 5393 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 5394 } 5395 5396 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5397 var data = { 5398 type: "UnsubscribeNodeReq", 5399 data: { 5400 node: node, 5401 subid: subid 5402 }, 5403 invokeID: _util.generateUUID() 5404 }, 5405 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5406 _this = this; 5407 5408 // We need to first subscribe to the response channel 5409 this.subscribe(responseTopic, function (rsp) { 5410 // Since this channel is only used for this singular request, 5411 // we are not interested anymore. 5412 // This is also critical to not leaking memory by having OpenAjax 5413 // store a bunch of orphaned callback handlers that enclose on 5414 // our entire ClientServices singleton 5415 _this.unsubscribe(responseTopic); 5416 if (handler) { 5417 handler(rsp); 5418 } 5419 }); 5420 // Then publish the request on the request channel 5421 _hub.publish(_topics.REQUESTS, data); 5422 }, 5423 5424 /** 5425 * @private 5426 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 5427 */ 5428 makeConnectionReq : function () { 5429 // Disallow others (non-masters) from administering BOSH connections that are not theirs 5430 if (_isMaster && _publisher) { 5431 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 5432 } else { 5433 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5434 } 5435 }, 5436 5437 /** 5438 * @private 5439 * Set's the global logger for this Client Services instance. 5440 * @param {Object} logger 5441 * Logger object with the following attributes defined:<ul> 5442 * <li><b>log:</b> function (msg) to simply log a message 5443 * </ul> 5444 */ 5445 setLogger: function (logger) { 5446 // We want to check the logger coming in so we don't have to check every time it is called. 5447 if (logger && typeof logger === "object" && typeof logger.log === "function") { 5448 _logger = logger; 5449 } else { 5450 // We are resetting it to an empty object so that _logger.log in .log is falsy. 5451 _logger = {}; 5452 } 5453 }, 5454 5455 /** 5456 * @private 5457 * Centralized logger.log method for external logger 5458 * @param {String} msg 5459 * Message to log 5460 */ 5461 log: _log, 5462 5463 /** 5464 * @class 5465 * Allow clients to make Finesse API requests and consume Finesse events by 5466 * calling a set of exposed functions. The Services layer will do the dirty 5467 * work of establishing a shared BOSH connection (for designated Master 5468 * modules), consuming events for client subscriptions, and constructing API 5469 * requests. 5470 * 5471 * @constructs 5472 */ 5473 _fakeConstuctor: function () { 5474 /* This is here so we can document init() as a method rather than as a constructor. */ 5475 }, 5476 5477 /** 5478 * Initiates the Client Services with the specified config parameters. 5479 * Enabling the Client Services as Master will trigger the establishment 5480 * of a BOSH event connection. 5481 * @param {finesse.gadget.Config} config 5482 * Configuration object containing properties used for making REST requests:<ul> 5483 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 5484 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 5485 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 5486 * appropriate authorization string is provided, otherwise it is 5487 * required.</li> 5488 * <li><b>password:</b> The password belonging to the user. This is an optional param as 5489 * long as the appropriate authorization string is provided, 5490 * otherwise it is required.</li> 5491 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 5492 * param is provided to allow the ability to hide the password 5493 * param. If provided, the id and the password extracted from this 5494 * string will be used over the config.id and config.password.</li> 5495 * </ul> 5496 * @throws {Error} If required constructor parameter is missing. 5497 * @example 5498 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 5499 */ 5500 init: function (config) { 5501 if (!_inited) { 5502 //Validate the properties within the config object if one is provided. 5503 if (!(typeof config === "object" && 5504 typeof config.host === "string" && config.host.length > 0 && config.restHost && 5505 (typeof config.authorization === "string" || 5506 (typeof config.id === "string")))) { 5507 throw new Error("Config object contains invalid properties."); 5508 } 5509 5510 // Initialize configuration 5511 _config = config; 5512 5513 // Set shortcuts 5514 _util = Utilities; 5515 _authModes = _util.getAuthModes(); 5516 _topics = Topics; 5517 5518 //TODO: document when this is properly supported 5519 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 5520 _hub = config.hub || gadgets.Hub; 5521 _io = config.io || gadgets.io; 5522 5523 //If the authorization string is provided, then use that to 5524 //extract the ID and the password. Otherwise use the ID and 5525 //password from the respective ID and password params. 5526 if (_config.authorization) { 5527 var creds = _util.getCredentials(_config.authorization); 5528 _config.id = creds.id; 5529 _config.password = creds.password; 5530 } 5531 else { 5532 _config.authorization = _util.b64Encode( 5533 _config.id + ":" + _config.password); 5534 } 5535 5536 //In case if gadgets create their own config instance , add systemAuthMode property inside config object 5537 if(!_config.systemAuthMode || _config.systemAuthMode === ""){ 5538 _getSystemAuthMode(); 5539 } 5540 5541 if(_config.systemAuthMode === _authModes.SSO){ 5542 _accessTokenRefreshHandler(undefined , {authToken : _util.getToken()}); 5543 if(!_config.authToken){ 5544 throw new Error("ClientServices.init() - Access token is unavailable inside Config object."); 5545 } 5546 5547 if (_hub){ 5548 _hub.subscribe(_topics.ACCESS_TOKEN_REFRESHED_EVENT, _accessTokenRefreshHandler); 5549 } 5550 } 5551 5552 _inited = true; 5553 5554 if (_hub) { 5555 //Subscribe to receive connection information. Since it is possible that 5556 //the client comes up after the Master comes up, the client will need 5557 //to make a request to have the Master send the latest connection info. 5558 //It would be possible that all clients get connection info again. 5559 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 5560 _makeConnectionInfoReq(); 5561 } 5562 } 5563 5564 //Return the CS object for object chaining. 5565 return this; 5566 }, 5567 5568 /** 5569 * @private 5570 * Initializes the BOSH component of this ClientServices instance. This establishes 5571 * the BOSH connection and will trigger the registered handlers as the connection 5572 * status changes respectively:<ul> 5573 * <li>registerOnConnectingHandler</li> 5574 * <li>registerOnConnectHandler</li> 5575 * <li>registerOnDisconnectHandler</li> 5576 * <li>registerOnDisconnectingHandler</li> 5577 * <li>registerOnReconnectingHandler</li> 5578 * <li>registerOnUnloadingHandler</li> 5579 * <ul> 5580 * 5581 * @param {Object} config 5582 * An object containing the following (optional) handlers for the request:<ul> 5583 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 5584 * This is used to construct the JID: user@domain.com</li> 5585 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 5586 * Available from the SystemInfo object. 5587 * This is used for creating or removing subscriptions.</li> 5588 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 5589 * </ul> 5590 */ 5591 initBosh: function (config) { 5592 //Validate the properties within the config object if one is provided. 5593 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 5594 throw new Error("Config object contains invalid properties."); 5595 } 5596 5597 // Mixin the required information for establishing the BOSH connection 5598 _config.xmppDomain = config.xmppDomain; 5599 _config.pubsubDomain = config.pubsubDomain; 5600 _config.resource = config.resource; 5601 5602 //Initiate Master launch sequence 5603 _becomeMaster(); 5604 }, 5605 5606 /** 5607 * @private 5608 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 5609 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 5610 * @param {Object} failoverMode 5611 * true if failing, false or something falsy when recovered 5612 */ 5613 setFailoverMode: function (failoverMode) { 5614 if (_isMaster) { 5615 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 5616 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 5617 }); 5618 } 5619 }, 5620 5621 /** 5622 * @private 5623 * Private accessor used to inject mocked private dependencies for unit testing 5624 */ 5625 _getTestObj: function () { 5626 return { 5627 setPublisher: function (publisher) { 5628 _publisher = publisher; 5629 } 5630 }; 5631 } 5632 }; 5633 }()); 5634 5635 window.finesse = window.finesse || {}; 5636 window.finesse.clientservices = window.finesse.clientservices || {}; 5637 window.finesse.clientservices.ClientServices = ClientServices; 5638 5639 return ClientServices; 5640 5641 }); 5642 /** 5643 * The following comment prevents JSLint errors concerning undefined global variables. 5644 * It tells JSLint that these identifiers are defined elsewhere. 5645 */ 5646 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 5647 5648 /** The following comment is to prevent jslint errors about 5649 * using variables before they are defined. 5650 */ 5651 /*global Handlebars */ 5652 5653 /** 5654 * JavaScript class to implement common notification 5655 * functionality. 5656 * 5657 * @requires Class 5658 * @requires finesse.FinesseBase 5659 */ 5660 /** @private */ 5661 define('restservices/Notifier',[ 5662 'FinesseBase', 5663 'clientservices/ClientServices' 5664 ], 5665 function (FinesseBase, ClientServices) { 5666 var Notifier = FinesseBase.extend({ 5667 /** 5668 * Initializes the notifier object. 5669 */ 5670 init : function () { 5671 this._super(); 5672 this._listenerCallback = []; 5673 }, 5674 5675 /** 5676 * Add a listener. 5677 * 5678 * @param callback_function 5679 * @param scope 5680 * is the callback function to add 5681 */ 5682 addListener : function (callback_function, scope) { 5683 var len = this._listenerCallback.length, i, cb, add = true; 5684 for (i = 0; i < len; i += 1) { 5685 cb = this._listenerCallback[i].callback; 5686 if (cb === callback_function) { 5687 // this callback already exists 5688 add = false; 5689 break; 5690 } 5691 } 5692 if (add) { 5693 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 5694 } 5695 }, 5696 5697 /** 5698 * Remove a listener. 5699 * 5700 * @param callback_function 5701 * is the callback function to remove 5702 * @return {Boolean} true if removed 5703 */ 5704 removeListener : function (callback_function) { 5705 5706 var result = false, len = this._listenerCallback.length, i, cb; 5707 for (i = len - 1; i >= 0; i -=1) { 5708 cb = this._listenerCallback[i].callback; 5709 if (cb === callback_function) { 5710 this._listenerCallback[i] = undefined; 5711 this._listenerCallback.splice(i, 1); 5712 result = true; 5713 break; 5714 } 5715 } 5716 5717 return result; 5718 }, 5719 5720 /** 5721 * Removes all listeners 5722 * @return {undefined} 5723 */ 5724 reset: function () { 5725 this._listenerCallback = []; 5726 }, 5727 5728 /** 5729 * Notify all listeners. 5730 * 5731 * @param obj 5732 * is the object that has changed 5733 */ 5734 notifyListeners : function (obj) { 5735 var len = this._listenerCallback.length, i, callbackFunction, scope; 5736 5737 for (i = 0; i < len; i += 1) { 5738 // Be sure that one bad callback does not prevent other listeners 5739 // from receiving. 5740 try { 5741 callbackFunction = this._listenerCallback[i].callback; 5742 scope = this._listenerCallback[i].scope; 5743 if (typeof callbackFunction === 'function') { 5744 callbackFunction.call(scope, obj); 5745 } 5746 } catch (err) { 5747 ClientServices.log("Exception caught: " + err); 5748 } 5749 } 5750 }, 5751 5752 /** 5753 * Gets a copy of the listeners. 5754 * @return changeListenerCopy (array of callbacks) 5755 */ 5756 getListeners : function () { 5757 var changeListenerCopy = [], len = this._listenerCallback.length, i; 5758 5759 for (i = 0; i < len; i += 1) { 5760 changeListenerCopy.push(this._listenerCallback[i].callback); 5761 } 5762 5763 return changeListenerCopy; 5764 }, 5765 5766 /** 5767 * Verifies that the handler is function. 5768 * @param handler to verify 5769 * @return the handler 5770 * @throws Error if not a function 5771 */ 5772 _isAFunction : function (handler) { 5773 if (handler === undefined || typeof handler === "function") { 5774 return handler; 5775 } else { 5776 throw new Error("handler must be a function"); 5777 } 5778 } 5779 }); 5780 5781 window.finesse = window.finesse || {}; 5782 window.finesse.restservices = window.finesse.restservices || {}; 5783 window.finesse.restservices.Notifier = Notifier; 5784 5785 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 5786 finesse.restservices = finesse.restservices || {}; 5787 5788 return Notifier; 5789 }); 5790 5791 /** 5792 * JavaScript base object that all REST objects should inherit 5793 * from because it encapsulates and provides the common functionality that 5794 * all REST objects need. 5795 * 5796 * @requires finesse.clientservices.ClientServices 5797 * @requires Class 5798 */ 5799 5800 /** @private */ 5801 define('restservices/RestBase',[ 5802 "FinesseBase", 5803 "utilities/Utilities", 5804 "restservices/Notifier", 5805 "clientservices/ClientServices", 5806 "clientservices/Topics" 5807 ], 5808 function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 5809 5810 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 5811 5812 doNotLog: false, 5813 5814 /** 5815 * Used by _processUpdate() and restRequest(). 5816 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 5817 * so that one of the callbacks can be fired when a corresponding event is 5818 * received inside _processUpdate(). 5819 * @private 5820 */ 5821 _pendingCallbacks: {}, 5822 5823 /** 5824 * Gets the REST class for the current object. This object throws an 5825 * exception because subtype must implement. 5826 * @throws {Error} because subtype must implement 5827 * @private 5828 */ 5829 getRestClass: function () { 5830 throw new Error("getRestClass(): Not implemented in subtype."); 5831 }, 5832 5833 /** 5834 * Gets the REST type for the current object. This object throws an 5835 * exception because subtype must implement. 5836 * @throws {Error} because subtype must implement. 5837 * @private 5838 */ 5839 getRestType: function () { 5840 throw new Error("getRestType(): Not implemented in subtype."); 5841 }, 5842 5843 /** 5844 * Gets the node path for the current object. 5845 * @private 5846 */ 5847 getXMPPNodePath: function () { 5848 return this.getRestUrl(); 5849 }, 5850 5851 /** 5852 * Boolean function that specifies whether the REST object supports 5853 * requests. True by default. Subclasses should override if false. 5854 * @private 5855 */ 5856 supportsRequests: true, 5857 5858 /** 5859 * Boolean function that specifies whether the REST object supports 5860 * subscriptions. True by default. Subclasses should override if false. 5861 * @private 5862 */ 5863 supportsSubscriptions: true, 5864 5865 /** 5866 * Boolean function that specifies whether the REST object should retain 5867 * a copy of the REST response. False by default. Subclasses should override if true. 5868 * @private 5869 */ 5870 keepRestResponse: false, 5871 5872 /** 5873 * Number that represents the REST Response status that is returned. Only 5874 * set if keepRestResponse is set to true. 5875 * @public 5876 */ 5877 restResponseStatus: null, 5878 5879 /** 5880 * Object to store additional headers to be sent with the REST Request, null by default. 5881 * @private 5882 */ 5883 extraHeaders: null, 5884 5885 /** 5886 * Boolean function that specifies whether the REST object explicitly 5887 * subscribes. False by default. Subclasses should override if true. 5888 * @private 5889 */ 5890 explicitSubscription: false, 5891 5892 /** 5893 * Boolean function that specifies whether subscribing should be 5894 * automatically done at construction. Defaults to true. 5895 * This be overridden at object construction, not by implementing subclasses 5896 * @private 5897 */ 5898 autoSubscribe: true, 5899 5900 /** 5901 * Private reference to default logger 5902 * @private 5903 */ 5904 _logger: { 5905 log: ClientServices.log, 5906 error: ClientServices.log 5907 }, 5908 5909 /** 5910 * @class 5911 * JavaScript representation of a REST object. Also exposes methods to operate 5912 * on the object against the server. This object is typically extended into individual 5913 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 5914 * 5915 * @constructor 5916 * @param {String} id 5917 * The ID that uniquely identifies the REST object. 5918 * @param {Object} callbacks 5919 * An object containing callbacks for instantiation and runtime 5920 * Callback to invoke upon successful instantiation, passes in REST object. 5921 * @param {Function} callbacks.onLoad(this) 5922 * Callback to invoke upon loading the data for the first time. 5923 * @param {Function} callbacks.onChange(this) 5924 * Callback to invoke upon successful update object (PUT) 5925 * @param {Function} callbacks.onAdd(this) 5926 * Callback to invoke upon successful update to add object (POST) 5927 * @param {Function} callbacks.onDelete(this) 5928 * Callback to invoke upon successful update to delete object (DELETE) 5929 * @param {Function} callbacks.onError(rsp) 5930 * Callback to invoke on update error (refresh or event) 5931 * as passed by finesse.restservices.RestBase.restRequest() 5932 * { 5933 * status: {Number} The HTTP status code returned 5934 * content: {String} Raw string of response 5935 * object: {Object} Parsed object of response 5936 * error: {Object} Wrapped exception that was caught 5937 * error.errorType: {String} Type of error that was caught 5938 * error.errorMessage: {String} Message associated with error 5939 * } 5940 * @param {RestBase} [restObj] 5941 * A RestBase parent object which this object has an association with. 5942 * @constructs 5943 */ 5944 init: function (options, callbacks, restObj) { 5945 /** 5946 * Initialize the base class 5947 */ 5948 var _this = this; 5949 5950 this._super(); 5951 5952 if (typeof options === "object") { 5953 this._id = options.id; 5954 this._restObj = options.parentObj; 5955 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 5956 this.doNotSubscribe = options.doNotSubscribe; 5957 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 5958 callbacks = { 5959 onLoad: options.onLoad, 5960 onChange: options.onChange, 5961 onAdd: options.onAdd, 5962 onDelete: options.onDelete, 5963 onError: options.onError 5964 }; 5965 } else { 5966 this._id = options; 5967 this._restObj = restObj; 5968 } 5969 5970 // Common stuff 5971 5972 this._data = {}; 5973 5974 //Contains the full rest response to be processed by upper layers if needed 5975 this._restResponse = undefined; 5976 5977 this._lastUpdate = {}; 5978 5979 this._util = Utilities; 5980 5981 //Should be correctly initialized in either a window OR gadget context 5982 this._config = finesse.container.Config; 5983 5984 // Setup all the notifiers - change, load and error. 5985 this._changeNotifier = new Notifier(); 5986 this._loadNotifier = new Notifier(); 5987 this._addNotifier = new Notifier(); 5988 this._deleteNotifier = new Notifier(); 5989 this._errorNotifier = new Notifier(); 5990 5991 this._loaded = false; 5992 5993 // Protect against null dereferencing of options allowing its 5994 // (nonexistent) keys to be read as undefined 5995 callbacks = callbacks || {}; 5996 5997 this.addHandler('load', callbacks.onLoad); 5998 this.addHandler('change', callbacks.onChange); 5999 this.addHandler('add', callbacks.onAdd); 6000 this.addHandler('delete', callbacks.onDelete); 6001 this.addHandler('error', callbacks.onError); 6002 6003 // Attempt to get the RestType then synchronize 6004 try { 6005 this.getRestType(); 6006 6007 // Only subscribe if this REST object supports subscriptions 6008 // and autoSubscribe was not requested to be disabled as a construction option 6009 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 6010 this.subscribe({ 6011 success: function () { 6012 //TODO: figure out how to use Function.call() or Function.apply() here... 6013 //this is exactly the same as the below else case other than the scope of "this" 6014 if (typeof options === "object" && options.data) { 6015 if (!_this._processObject(_this._normalize(options.data))) { 6016 // notify of error if we fail to construct 6017 _this._errorNotifier.notifyListeners(_this); 6018 } 6019 } else { 6020 // Only subscribe if this REST object supports requests 6021 if (_this.supportsRequests) { 6022 _this._synchronize(); 6023 } 6024 } 6025 }, 6026 error: function (err) { 6027 _this._errorNotifier.notifyListeners(err); 6028 } 6029 }); 6030 } else { 6031 if (typeof options === "object" && options.data) { 6032 if (!this._processObject(this._normalize(options.data))) { 6033 // notify of error if we fail to construct 6034 this._errorNotifier.notifyListeners(this); 6035 } 6036 } else { 6037 // Only subscribe if this REST object supports requests 6038 if (this.supportsRequests) { 6039 this._synchronize(); 6040 } 6041 } 6042 } 6043 6044 } catch (err) { 6045 this._logger.error('id=' + this._id + ': ' + err); 6046 } 6047 }, 6048 6049 /** 6050 * Determines if the object has a particular property. 6051 * @param obj is the object to examine 6052 * @param property is the property to check for 6053 * @returns {Boolean} 6054 */ 6055 hasProperty: function (obj, prop) { 6056 return (obj !== null) && (obj.hasOwnProperty(prop)); 6057 }, 6058 6059 /** 6060 * Gets a property from the object. 6061 * @param obj is the object to examine 6062 * @param property is the property to get 6063 * @returns {Property Value} or {Null} if not found 6064 */ 6065 getProperty: function (obj, property) { 6066 var result = null; 6067 6068 if (this.hasProperty(obj, property) === false) { 6069 result = null; 6070 } else { 6071 result = obj[property]; 6072 } 6073 return result; 6074 }, 6075 6076 /** 6077 * Utility to extracts the ID from the specified REST URI. This is with the 6078 * assumption that the ID is always the last element in the URI after the 6079 * "/" delimiter. 6080 * @param {String} restUri 6081 * The REST uri (i.e. /finesse/api/User/1000). 6082 * @private 6083 */ 6084 _extractId: function (restObj) { 6085 var obj, restUri = "", strLoc; 6086 for (obj in restObj) { 6087 if (restObj.hasOwnProperty(obj)) { 6088 restUri = restObj[obj].uri; 6089 break; 6090 } 6091 } 6092 return Utilities.getId(restUri); 6093 }, 6094 6095 /** 6096 * Gets the data for this object. 6097 * @returns {Object} which is contained in data 6098 */ 6099 getData: function () { 6100 return this._data; 6101 }, 6102 6103 /** 6104 * Gets the complete REST response to the request made 6105 * @returns {Object} which is contained in data 6106 * @private 6107 */ 6108 getRestResponse: function () { 6109 return this._restResponse; 6110 }, 6111 6112 /** 6113 * The REST URL in which this object can be referenced. 6114 * @return {String} 6115 * The REST URI for this object. 6116 * @private 6117 */ 6118 getRestUrl: function () { 6119 var 6120 restObj = this._restObj, 6121 restUrl = ""; 6122 6123 //Prepend the base REST object if one was provided. 6124 if (restObj instanceof RestBase) { 6125 restUrl += restObj.getRestUrl(); 6126 } 6127 //Otherwise prepend with the default webapp name. 6128 else { 6129 restUrl += "/finesse/api"; 6130 } 6131 6132 //Append the REST type. 6133 restUrl += "/" + this.getRestType(); 6134 6135 //Append ID if it is not undefined, null, or empty. 6136 if (this._id) { 6137 restUrl += "/" + this._id; 6138 } 6139 return restUrl; 6140 }, 6141 6142 /** 6143 * Getter for the id of this RestBase 6144 * @returns {String} 6145 * The id of this RestBase 6146 */ 6147 getId: function () { 6148 return this._id; 6149 }, 6150 6151 /** 6152 * Synchronize this object with the server using REST GET request. 6153 * @returns {Object} 6154 * { 6155 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6156 * } 6157 * @private 6158 */ 6159 _synchronize: function (retries) { 6160 // Fetch this REST object 6161 if (typeof this._id === "string") { 6162 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 6163 6164 return this._doGET( 6165 { 6166 success: function (rsp) { 6167 if (!_this._processResponse(rsp)) { 6168 if (retries > 0) { 6169 setTimeout(function () { 6170 _this._synchronize(retries - 1); 6171 }, _RETRY_INTERVAL); 6172 } else { 6173 _this._errorNotifier.notifyListeners(_this); 6174 } 6175 } else { 6176 // If this object was already "loaded" prior to 6177 // the _doGET request, then call the 6178 // changeNotifier 6179 if (isLoaded) { 6180 _this._changeNotifier.notifyListeners(_this); 6181 } 6182 } 6183 }, 6184 error: function (rsp) { 6185 if (retries > 0) { 6186 setTimeout(function () { 6187 _this._synchronize(retries - 1); 6188 }, _RETRY_INTERVAL); 6189 6190 } else { 6191 _this._errorNotifier.notifyListeners(rsp); 6192 } 6193 } 6194 } 6195 ); 6196 6197 } else { 6198 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 6199 } 6200 }, 6201 6202 /** 6203 * Adds an handler to this object. 6204 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 6205 * @param {String} notifierType 6206 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 6207 * @param {Function} callback 6208 * The function callback to invoke. 6209 * @example 6210 * // Handler for additions to the Dialogs collection object. 6211 * // When Dialog (a RestBase object) is created, add a change handler. 6212 * _handleDialogAdd = function(dialog) { 6213 * dialog.addHandler('change', _handleDialogChange); 6214 * } 6215 */ 6216 addHandler: function (notifierType, callback, scope) { 6217 var notifier = null; 6218 try { 6219 Utilities.validateHandler(callback); 6220 6221 notifier = this._getNotifierReference(notifierType); 6222 6223 notifier.addListener(callback, scope); 6224 6225 // If load handler is added and object has 6226 // already been loaded, invoke callback 6227 // immediately 6228 if (notifierType === 'load' && this._loaded) { 6229 callback.call((scope || window), this); 6230 } 6231 } catch (err) { 6232 this._logger.error('id=' + this._id + ': ' + err); 6233 } 6234 }, 6235 6236 /** 6237 * Removes a handler from this object. 6238 * @param {String} notifierType 6239 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 6240 * @param {Function} callback 6241 * The function to remove. 6242 */ 6243 removeHandler: function (notifierType, callback) { 6244 var notifier = null; 6245 try { 6246 Utilities.validateHandler(callback); 6247 6248 notifier = this._getNotifierReference(notifierType); 6249 6250 if (typeof(callback) === "undefined") 6251 { 6252 // Remove all listeners for the type 6253 notifier.reset(); 6254 } 6255 else 6256 { 6257 // Remove the specified listener 6258 finesse.utilities.Utilities.validateHandler(callback); 6259 notifier.removeListener(callback); 6260 } 6261 } catch (err) { 6262 this._logger.error('id=' + this._id + ': ' + err); 6263 } 6264 }, 6265 6266 /** 6267 * Utility method gating any operations that require complete instantiation 6268 * @throws Error 6269 * If this object was not fully instantiated yet 6270 * @returns {finesse.restservices.RestBase} 6271 * This RestBase object to allow cascading 6272 */ 6273 isLoaded: function () { 6274 if (!this._loaded) { 6275 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 6276 } 6277 return this; // Allow cascading 6278 }, 6279 6280 6281 6282 /** 6283 * Force an update on this object. Since an asynchronous GET is performed, 6284 * it is necessary to have an onChange handler registered in order to be 6285 * notified when the response of this returns. 6286 * @param {Integer} retries 6287 * The number or retry attempts to make. 6288 * @returns {Object} 6289 * { 6290 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request 6291 * } 6292 */ 6293 refresh: function (retries) { 6294 var _this = this; 6295 6296 if (this.explicitSubscription) { 6297 this._subscribeNode({ 6298 success: function () { 6299 //Disallow GETs if object doesn't support it. 6300 if (!_this.supportsRequests) { 6301 throw new Error("Object doesn't support request operations."); 6302 } 6303 6304 _this._synchronize(retries); 6305 6306 return this; // Allow cascading 6307 }, 6308 error: function (err) { 6309 _this._errorNotifier.notifyListeners(err); 6310 } 6311 }); 6312 } else { 6313 //Disallow GETs if object doesn't support it. 6314 if (!this.supportsRequests) { 6315 throw new Error("Object doesn't support request operations."); 6316 } 6317 6318 return this._synchronize(retries); 6319 } 6320 }, 6321 6322 /** 6323 * Utility method to validate against the known schema of this RestBase 6324 * @param {Object} obj 6325 * The object to validate 6326 * @returns {Boolean} 6327 * True if the object fits the schema of this object. This usually 6328 * means all required keys or nested objects are present. 6329 * False otherwise. 6330 * @private 6331 */ 6332 _validate: function (obj) { 6333 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 6334 if (!valid) 6335 { 6336 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 6337 } 6338 return valid; 6339 }, 6340 6341 /** 6342 * Utility method to fetch this RestBase from the server 6343 * @param {finesse.interfaces.RequestHandlers} handlers 6344 * An object containing the handlers for the request 6345 * @returns {Object} 6346 * { 6347 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6348 * } 6349 * @private 6350 */ 6351 _doGET: function (handlers) { 6352 return this.restRequest(this.getRestUrl(), handlers); 6353 }, 6354 6355 /** 6356 * Common update event handler used by the pubsub callback closure. 6357 * Processes the update event then notifies listeners. 6358 * @param {Object} scope 6359 * An object containing callbacks to handle the asynchronous get 6360 * @param {Object} update 6361 * An object containing callbacks to handle the asynchronous get 6362 * @private 6363 */ 6364 _updateEventHandler: function (scope, update) { 6365 if (scope._processUpdate(update)) { 6366 switch (update.object.Update.event) { 6367 case "POST": 6368 scope._addNotifier.notifyListeners(scope); 6369 break; 6370 case "PUT": 6371 scope._changeNotifier.notifyListeners(scope); 6372 break; 6373 case "DELETE": 6374 scope._deleteNotifier.notifyListeners(scope); 6375 break; 6376 } 6377 } 6378 }, 6379 6380 /** 6381 * Utility method to create a callback to be given to OpenAjax to invoke when a message 6382 * is published on the topic of our REST URL (also XEP-0060 node). 6383 * This needs to be its own defined method so that subclasses can have their own implementation. 6384 * @returns {Function} callback(update) 6385 * The callback to be invoked when an update event is received. This callback will 6386 * process the update and notify listeners. 6387 * @private 6388 */ 6389 _createPubsubCallback: function () { 6390 var _this = this; 6391 return function (update) { 6392 _this._updateEventHandler(_this, update); 6393 }; 6394 }, 6395 6396 /** 6397 * Subscribe to pubsub infra using the REST URL as the topic name. 6398 * @param {finesse.interfaces.RequestHandlers} handlers 6399 * An object containing the handlers for the request 6400 * @private 6401 */ 6402 subscribe: function (callbacks) { 6403 // Only need to do a subscription to client pubsub. No need to trigger 6404 // a subscription on the Finesse server due to implicit subscribe (at 6405 // least for now). 6406 var _this = this, 6407 topic = Topics.getTopic(this.getXMPPNodePath()), 6408 handlers, 6409 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 6410 6411 callbacks = callbacks || {}; 6412 6413 handlers = { 6414 /** @private */ 6415 success: function () { 6416 // Add item to the refresh list in ClientServices to refresh if 6417 // we recover due to our resilient connection. However, do 6418 // not add if doNotRefresh flag is set. 6419 if (!_this.doNotRefresh) { 6420 ClientServices.addToRefreshList(_this); 6421 } 6422 6423 if (typeof callbacks.success === "function") { 6424 callbacks.success(); 6425 } 6426 }, 6427 /** @private */ 6428 error: function (err) { 6429 if (successful) { 6430 ClientServices.unsubscribe(topic); 6431 } 6432 6433 if (typeof callbacks.error === "function") { 6434 callbacks.error(err); 6435 } 6436 } 6437 }; 6438 6439 // Request a node subscription only if this object requires explicit subscriptions 6440 if (this.explicitSubscription === true) { 6441 this._subscribeNode(handlers); 6442 } else { 6443 if (successful) { 6444 this._subid = "OpenAjaxOnly"; 6445 handlers.success(); 6446 } else { 6447 handlers.error(); 6448 } 6449 } 6450 6451 return this; 6452 }, 6453 6454 /** 6455 * Unsubscribe to pubsub infra using the REST URL as the topic name. 6456 * @param {finesse.interfaces.RequestHandlers} handlers 6457 * An object containing the handlers for the request 6458 * @private 6459 */ 6460 unsubscribe: function (callbacks) { 6461 // Only need to do a subscription to client pubsub. No need to trigger 6462 // a subscription on the Finesse server due to implicit subscribe (at 6463 // least for now). 6464 var _this = this, 6465 topic = Topics.getTopic(this.getRestUrl()), 6466 handlers; 6467 6468 // no longer keep track of object to refresh on reconnect 6469 ClientServices.removeFromRefreshList(_this); 6470 6471 callbacks = callbacks || {}; 6472 6473 handlers = { 6474 /** @private */ 6475 success: function () { 6476 if (typeof callbacks.success === "function") { 6477 callbacks.success(); 6478 } 6479 }, 6480 /** @private */ 6481 error: function (err) { 6482 if (typeof callbacks.error === "function") { 6483 callbacks.error(err); 6484 } 6485 } 6486 }; 6487 6488 if (this._subid) { 6489 ClientServices.unsubscribe(topic); 6490 // Request a node unsubscribe only if this object requires explicit subscriptions 6491 if (this.explicitSubscription === true) { 6492 this._unsubscribeNode(handlers); 6493 } else { 6494 this._subid = undefined; 6495 handlers.success(); 6496 } 6497 } else { 6498 handlers.success(); 6499 } 6500 6501 return this; 6502 }, 6503 6504 /** 6505 * Private utility to perform node subscribe requests for explicit subscriptions 6506 * @param {finesse.interfaces.RequestHandlers} handlers 6507 * An object containing the handlers for the request 6508 * @private 6509 */ 6510 _subscribeNode: function (callbacks) { 6511 var _this = this; 6512 6513 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6514 callbacks = callbacks || {}; 6515 6516 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 6517 if (err) { 6518 if (typeof callbacks.error === "function") { 6519 callbacks.error(err); 6520 } 6521 } else { 6522 // Store the subid on a successful subscribe 6523 _this._subid = subid; 6524 if (typeof callbacks.success === "function") { 6525 callbacks.success(); 6526 } 6527 } 6528 }); 6529 }, 6530 6531 /** 6532 * Private utility to perform node unsubscribe requests for explicit subscriptions 6533 * @param {finesse.interfaces.RequestHandlers} handlers 6534 * An object containing the handlers for the request 6535 * @private 6536 */ 6537 _unsubscribeNode: function (callbacks) { 6538 var _this = this; 6539 6540 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6541 callbacks = callbacks || {}; 6542 6543 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 6544 _this._subid = undefined; 6545 if (err) { 6546 if (typeof callbacks.error === "function") { 6547 callbacks.error(err); 6548 } 6549 } else { 6550 if (typeof callbacks.success === "function") { 6551 callbacks.success(); 6552 } 6553 } 6554 }); 6555 }, 6556 6557 /** 6558 * Validate and store the object into the internal data store. 6559 * @param {Object} object 6560 * The JavaScript object that should match of schema of this REST object. 6561 * @returns {Boolean} 6562 * True if the object was validated and stored successfully. 6563 * @private 6564 */ 6565 _processObject: function (object) { 6566 if (this._validate(object)) { 6567 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 6568 6569 // If loaded for the first time, call the load notifiers. 6570 if (!this._loaded) { 6571 this._loaded = true; 6572 this._loadNotifier.notifyListeners(this); 6573 } 6574 6575 return true; 6576 } 6577 return false; 6578 }, 6579 6580 /** 6581 * Normalize the object to mitigate the differences between the backend 6582 * and what this REST object should hold. For example, the backend sends 6583 * send an event with the root property name being lower case. In order to 6584 * match the GET, the property should be normalized to an upper case. 6585 * @param {Object} object 6586 * The object which should be normalized. 6587 * @returns {Object} 6588 * Return the normalized object. 6589 * @private 6590 */ 6591 _normalize: function (object) { 6592 var 6593 restType = this.getRestType(), 6594 // Get the REST object name with first character being lower case. 6595 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 6596 6597 // Normalize payload to match REST object. The payload for an update 6598 // use a lower case object name as oppose to upper case. Only normalize 6599 // if necessary. 6600 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 6601 //Since the object is going to be modified, clone the object so that 6602 //it doesn't affect others (due to OpenAjax publishing to other 6603 //subscriber. 6604 object = jQuery.extend(true, {}, object); 6605 6606 object[restType] = object[objRestType]; 6607 delete(object[objRestType]); 6608 } 6609 return object; 6610 }, 6611 6612 /** 6613 * Utility method to process the response of a successful get 6614 * @param {Object} rsp 6615 * The response of a successful get 6616 * @returns {Boolean} 6617 * True if the update was successfully processed (the response object 6618 * passed the schema validation) and updated the internal data cache, 6619 * false otherwise. 6620 * @private 6621 */ 6622 _processResponse: function (rsp) { 6623 try { 6624 if (this.keepRestResponse) { 6625 this._restResponse = rsp.content; 6626 this.restResponseStatus = rsp.status; 6627 } 6628 return this._processObject(rsp.object); 6629 } 6630 catch (err) { 6631 this._logger.error(this.getRestType() + ': ' + err); 6632 } 6633 return false; 6634 }, 6635 6636 /** 6637 * Method that is called at the end of _processUpdate() which by default 6638 * will just delete the requestId-to-callbacks mapping but can be overridden. 6639 * @param {String} requestId The requestId of the event 6640 */ 6641 _postProcessUpdateStrategy: function (requestId) { 6642 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 6643 delete this._pendingCallbacks[requestId]; 6644 }, 6645 6646 /** 6647 * Utility method to process the update notification. 6648 * @param {Object} update 6649 * The payload of an update notification. 6650 * @returns {Boolean} 6651 * True if the update was successfully processed (the update object 6652 * passed the schema validation) and updated the internal data cache, 6653 * false otherwise. 6654 * @private 6655 */ 6656 _processUpdate: function (update) { 6657 try { 6658 var updateObj, requestId, fakeResponse, receivedError; 6659 6660 // The backend will send the data object with a lower case. To be 6661 // consistent with what should be represented in this object, the 6662 // object name should be upper case. This will normalize the object. 6663 updateObj = this._normalize(update.object.Update.data); 6664 6665 // Store the last event. 6666 this._lastUpdate = update.object; 6667 6668 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 6669 6670 if (requestId && this._pendingCallbacks[requestId]) { 6671 6672 /* 6673 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 6674 * a simulated/"fake" AJAX response object from the information in the received event. 6675 * The constructed object should conform to the contract for response objects specified 6676 * in _createAjaxHandler(). 6677 */ 6678 fakeResponse = {}; 6679 6680 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 6681 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 6682 //doing a parse(stringify(update)). 6683 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 6684 6685 fakeResponse.object = {}; 6686 6687 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 6688 6689 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 6690 receivedError = updateObj.apiErrors.apiError; 6691 fakeResponse.object.ApiErrors = {}; 6692 fakeResponse.object.ApiErrors.ApiError = {}; 6693 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 6694 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 6695 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 6696 6697 /* 6698 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 6699 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6700 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 6701 **/ 6702 fakeResponse.status = 400; 6703 6704 } else { //Success case 6705 6706 fakeResponse.object = this._lastUpdate; 6707 6708 /* 6709 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 6710 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6711 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 6712 **/ 6713 fakeResponse.status = 200; 6714 } 6715 6716 try { 6717 6718 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 6719 this._pendingCallbacks[requestId].error(fakeResponse); 6720 } 6721 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 6722 /*else if (this._pendingCallbacks[requestId].success) { 6723 this._pendingCallbacks[requestId].success(fakeResponse); 6724 }*/ 6725 6726 } catch (callbackErr) { 6727 6728 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 6729 6730 } 6731 6732 this._postProcessUpdateStrategy(requestId); 6733 6734 } 6735 6736 return this._processObject(updateObj); 6737 } 6738 catch (err) { 6739 this._logger.error(this.getRestType() + ': ' + err); 6740 } 6741 return false; 6742 }, 6743 6744 /** 6745 * Utility method to create ajax response handler closures around the 6746 * provided callbacks. Callbacks should be passed through from .ajax(). 6747 * makeRequest is responsible for garbage collecting these closures. 6748 * @param {finesse.interfaces.RequestHandlers} handlers 6749 * An object containing the handlers for the request 6750 * @returns {Object} 6751 * { 6752 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns 6753 * callback: {function} Callback handler to be invoked when the response returns 6754 * } 6755 * @private 6756 */ 6757 _createAjaxHandler: function (options) { 6758 //We should not need to check this again since it has already been done in .restRequest() 6759 //options = options || {}; 6760 6761 //Flag to indicate whether or not to process the response 6762 var abort = false, 6763 6764 //Get a reference to the parent User object 6765 _this = this; 6766 6767 return { 6768 6769 abort: function () { 6770 abort = true; 6771 }, 6772 6773 callback: function (rsp) { 6774 6775 if (abort) { 6776 // Do not process the response 6777 return; 6778 } 6779 6780 var requestId, error = false, rspObj; 6781 6782 if (options.success || options.error) { 6783 rspObj = { 6784 status: rsp.rc, 6785 content: rsp.text 6786 }; 6787 6788 if (!_this.doNotLog) { 6789 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "'"); 6790 } 6791 6792 //Some responses may not have a body. 6793 if (rsp.text && rsp.text.length > 0) { 6794 try { 6795 rspObj.object = _this._util.xml2js(rsp.text); 6796 } catch (e) { 6797 error = true; 6798 rspObj.error = { 6799 errorType: "parseError", 6800 errorMessage: "Could not serialize XML: " + e 6801 }; 6802 } 6803 } else { 6804 rspObj.object = {}; 6805 } 6806 6807 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 6808 if (options.success) { 6809 options.success(rspObj); 6810 } 6811 } else { 6812 if (options.error) { 6813 options.error(rspObj); 6814 } 6815 } 6816 6817 /* 6818 * If a synchronous error happened after a non-GET request (usually a validation error), we 6819 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 6820 * will arrive later. The corresponding requestId should be present in the response headers. 6821 * 6822 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 6823 * 'requestId' below. 6824 **/ 6825 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 6826 requestId = rsp.headers.requestid[0]; 6827 if (_this._pendingCallbacks[requestId]) { 6828 delete _this._pendingCallbacks[requestId]; 6829 } 6830 } 6831 } 6832 } 6833 }; 6834 }, 6835 6836 /** 6837 * Utility method to make an asynchronous request 6838 * @param {String} url 6839 * The unencoded URL to which the request is sent (will be encoded) 6840 * @param {Object} options 6841 * An object containing additional options for the request. 6842 * @param {Object} options.content 6843 * An object to send in the content body of the request. Will be 6844 * serialized into XML before sending. 6845 * @param {String} options.method 6846 * The type of request. Defaults to "GET" when none is specified. 6847 * @param {Function} options.success(rsp) 6848 * A callback function to be invoked for a successful request. 6849 * { 6850 * status: {Number} The HTTP status code returned 6851 * content: {String} Raw string of response 6852 * object: {Object} Parsed object of response 6853 * } 6854 * @param {Function} options.error(rsp) 6855 * A callback function to be invoked for an unsuccessful request. 6856 * { 6857 * status: {Number} The HTTP status code returned 6858 * content: {String} Raw string of response 6859 * object: {Object} Parsed object of response 6860 * error: {Object} Wrapped exception that was caught 6861 * error.errorType: {String} Type of error that was caught 6862 * error.errorMessage: {String} Message associated with error 6863 * } 6864 * @returns {Object} 6865 * { 6866 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request 6867 * } 6868 * @private 6869 */ 6870 restRequest : function(url, options) { 6871 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; 6872 // Protect against null dereferencing of options 6873 // allowing its (nonexistent) keys to be read as 6874 // undefined 6875 options = options || {}; 6876 options.success = this._util 6877 .validateHandler(options.success); 6878 options.error = this._util 6879 .validateHandler(options.error); 6880 6881 // true if this should be a GET request, false 6882 // otherwise 6883 if (!options.method || options.method === "GET") { 6884 // Disable caching for GETs 6885 if (url.indexOf("?") > -1) { 6886 url += "&"; 6887 } else { 6888 url += "?"; 6889 } 6890 url += "nocache=" 6891 + this._util.currentTimeMillis(); 6892 } else { 6893 /** 6894 * If not GET, generate a requestID and add it 6895 * to the headers, then wrap callbacks into an 6896 * object and store it in _pendingCallbacks. If 6897 * we receive a synchronous error response 6898 * instead of a 202 as expected, the AJAX 6899 * handler will clean up _pendingCallbacks. 6900 */ 6901 /* 6902 * TODO: Clean up _pendingCallbacks if an entry 6903 * persists after a certain amount of time has 6904 * passed. In the block below, can store the 6905 * current time (new Date().getTime()) alongside 6906 * the callbacks in the new _pendingCallbacks 6907 * entry. Then iterate through a copty of 6908 * _pendingCallbacks, deleting all entries 6909 * inside _pendingCallbacks that are older than 6910 * a certain threshold (2 minutes for example.) 6911 * This solves a potential memory leak issue if 6912 * we never receive an event for a given stored 6913 * requestId; we don't want to store unfired 6914 * callbacks forever. 6915 */ 6916 /** @private */ 6917 options.uuid = this._util.generateUUID(); 6918 // params[gadgets.io.RequestParameters.HEADERS].requestId 6919 // = options.uuid; 6920 // By default, Shindig strips nearly all of the 6921 // response headers, but this parameter tells 6922 // Shindig 6923 // to send the headers through unmodified; we 6924 // need to be able to read the 'requestId' 6925 // header if we 6926 // get a synchronous error as a result of a 6927 // non-GET request. (See the bottom of 6928 // _createAjaxHandler().) 6929 // params[gadgets.io.RequestParameters.GET_FULL_HEADERS] 6930 // = "true"; 6931 this._pendingCallbacks[options.uuid] = {}; 6932 this._pendingCallbacks[options.uuid].success = options.success; 6933 this._pendingCallbacks[options.uuid].error = options.error; 6934 } 6935 encodedUrl = encodeURI(url) 6936 + (window.errorOnRestRequest ? "ERROR" : ""); 6937 ajaxHandler = this._createAjaxHandler(options); 6938 // ClientServices.makeRequest(encodedUrl, 6939 // ajaxHandler.callback, params); 6940 mtype = options.method || 'GET'; 6941 encodedUrl = scheme + "//" + host + ":" + port 6942 + encodedUrl; 6943 6944 if (typeof options.content === "object" 6945 && typeof options.content !== "undefined") { 6946 // Except get, all the other operations accepts 6947 // application/xml only. 6948 // @Consumes in all the webservices except GET 6949 // are having this. 6950 postdata = this._util.js2xml(options.content); 6951 if (postdata !== null && postdata !== "") { 6952 contentTypeAX = "application/xml"; 6953 } else { 6954 // in desktop, reason code GET request was 6955 // called with empty object content 6956 // if not able to parse any content to post 6957 // data, then it is GET only 6958 contentTypeAX = ""; 6959 dataTypeAX = "text"; 6960 } 6961 } else { 6962 // No content type for GET operation, by default 6963 // it will take text/plain || application/xml. 6964 // for dataType - GET will result plain text, 6965 // which we are taking as xml. 6966 // Queried list of @Produces from code base, all 6967 // the GET operations accepts text/plain OR 6968 // application/xml and produces application/xml 6969 contentTypeAX = ""; 6970 dataTypeAX = "text"; 6971 } 6972 auth = this._util.getAuthHeaderString(this._config); 6973 6974 if (!this.doNotLog) { 6975 this._logger.log(this.getRestType() 6976 + ": requestId='" + options.uuid 6977 + "', Making REST request: method=" 6978 + (options.method || "GET") + ", url='" 6979 + encodedUrl + "'"); 6980 } 6981 $ 6982 .ajax({ 6983 url : encodedUrl, 6984 headers : this.extraHeaders || {}, 6985 beforeSend : function(xhr) { 6986 xhr.setRequestHeader( 6987 "Authorization", auth); 6988 xhr.setRequestHeader("locale", 6989 locale); 6990 if (options.uuid) { 6991 xhr.setRequestHeader( 6992 "requestId", 6993 options.uuid); 6994 } 6995 }, 6996 dataType : dataTypeAX, // xml or json? 6997 contentType : contentTypeAX, 6998 type : mtype, 6999 data : postdata, 7000 timeout : 5000, 7001 success : function(response, status, 7002 xhr) { 7003 var rspObj = { 7004 rc : xhr.status, 7005 text : response, 7006 headers : { 7007 requestid : xhr 7008 .getResponseHeader("requestId") 7009 } 7010 }; 7011 ajaxHandler.callback(rspObj); 7012 }, 7013 error : function(jqXHR, request, error) { 7014 var resObj = { 7015 rc : jqXHR.status, 7016 text : jqXHR.responseText, 7017 headers : { 7018 requestid : jqXHR 7019 .getResponseHeader("requestId") 7020 } 7021 }; 7022 ajaxHandler.callback(resObj); 7023 } 7024 }); 7025 return { 7026 abort : ajaxHandler.abort 7027 }; 7028 }, 7029 7030 7031 /** 7032 * Retrieves a reference to a particular notifierType. 7033 * 7034 * @param notifierType 7035 * is a string which indicates the notifier to retrieve 7036 * ('load', 'change', 'add', 'delete', 'error') 7037 * @return {Notifier} 7038 * @private 7039 */ 7040 _getNotifierReference: function (notifierType) { 7041 var notifierReference = null; 7042 if (notifierType === 'load') { 7043 notifierReference = this._loadNotifier; 7044 } else if (notifierType === 'change') { 7045 notifierReference = this._changeNotifier; 7046 } else if (notifierType === 'add') { 7047 notifierReference = this._addNotifier; 7048 } else if (notifierType === 'delete') { 7049 notifierReference = this._deleteNotifier; 7050 } else if (notifierType === 'error') { 7051 notifierReference = this._errorNotifier; 7052 } else { 7053 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 7054 } 7055 7056 return notifierReference; 7057 } 7058 }); 7059 7060 window.finesse = window.finesse || {}; 7061 window.finesse.restservices = window.finesse.restservices || {}; 7062 window.finesse.restservices.RestBase = RestBase; 7063 7064 return RestBase; 7065 }); 7066 7067 /** The following comment is to prevent jslint errors about 7068 * using variables before they are defined. 7069 */ 7070 /*global finesse*/ 7071 7072 /** 7073 * JavaScript base object that all REST collection objects should 7074 * inherit from because it encapsulates and provides the common functionality 7075 * that all REST objects need. 7076 * 7077 * @requires finesse.clientservices.ClientServices 7078 * @requires Class 7079 * @requires finesse.FinesseBase 7080 * @requires finesse.restservices.RestBase 7081 */ 7082 7083 /** 7084 * @class 7085 * JavaScript representation of a REST collection object. 7086 * 7087 * @constructor 7088 * @param {Function} callbacks.onCollectionAdd(this) 7089 * Callback to invoke upon successful item addition to the collection. 7090 * @param {Function} callbacks.onCollectionDelete(this) 7091 * Callback to invoke upon successful item deletion from the collection. 7092 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 7093 */ 7094 /** @private */ 7095 define('restservices/RestCollectionBase',[ 7096 'restservices/RestBase', 7097 'utilities/Utilities', 7098 'restservices/Notifier' 7099 ], 7100 function (RestBase, Utilities, Notifier) { 7101 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 7102 7103 /** 7104 * Boolean function that specifies whether the collection handles subscribing 7105 * and propagation of events for the individual REST object items the 7106 * collection holds. False by default. Subclasses should override if true. 7107 * @private 7108 */ 7109 supportsRestItemSubscriptions: false, 7110 7111 /** 7112 * Gets the constructor the individual items that make of the collection. 7113 * For example, a Dialogs collection object will hold a list of Dialog items. 7114 * @throws Error because subtype must implement. 7115 * @private 7116 */ 7117 getRestItemClass: function () { 7118 throw new Error("getRestItemClass(): Not implemented in subtype."); 7119 }, 7120 7121 /** 7122 * Gets the REST type of the individual items that make of the collection. 7123 * For example, a Dialogs collection object will hold a list of Dialog items. 7124 * @throws Error because subtype must implement. 7125 * @private 7126 */ 7127 getRestItemType: function () { 7128 throw new Error("getRestItemType(): Not implemented in subtype."); 7129 }, 7130 7131 /** 7132 * The base REST URL in which items this object contains can be referenced. 7133 * @return {String} 7134 * The REST URI for items this object contains. 7135 * @private 7136 */ 7137 getRestItemBaseUrl: function () { 7138 var 7139 restUrl = "/finesse/api"; 7140 7141 //Append the REST type. 7142 restUrl += "/" + this.getRestItemType(); 7143 7144 return restUrl; 7145 }, 7146 7147 /* 7148 * Creates a new object from the given data 7149 * @param data - data object 7150 * @private 7151 */ 7152 _objectCreator: function (data) { 7153 var objectId = this._extractId(data), 7154 newRestObj = this._collection[objectId], 7155 _this = this; 7156 7157 //Prevent duplicate entries into collection. 7158 if (!newRestObj) { 7159 //Create a new REST object using the subtype defined by the 7160 //overridden method. 7161 newRestObj = new (this.getRestItemClass())({ 7162 doNotSubscribe: this.handlesItemSubscription, 7163 doNotRefresh: this.handlesItemRefresh, 7164 id: objectId, 7165 data: data, 7166 onLoad: function (newObj) { 7167 //Normalize and add REST object to collection datastore. 7168 _this._collection[objectId] = newObj; 7169 _this._collectionAddNotifier.notifyListeners(newObj); 7170 _this.length += 1; 7171 } 7172 }); 7173 } 7174 else { 7175 //If entry already exist in collection, process the new event, 7176 //and notify all change listeners since an existing object has 7177 //change. This could happen in the case when the Finesse server 7178 //cycles, and sends a snapshot of the user's calls. 7179 newRestObj._processObject(data); 7180 newRestObj._changeNotifier.notifyListeners(newRestObj); 7181 } 7182 }, 7183 7184 /* 7185 * Deletes and object and notifies its handlers 7186 * @param data - data object 7187 * @private 7188 */ 7189 _objectDeleter: function (data) { 7190 var objectId = this._extractId(data), 7191 object = this._collection[objectId]; 7192 if (object) { 7193 //Even though this is a delete, let's make sure the object we are passing has got good data 7194 object._processObject(data); 7195 //Notify listeners and delete from internal datastore. 7196 this._collectionDeleteNotifier.notifyListeners(object); 7197 delete this._collection[objectId]; 7198 this.length -= 1; 7199 } 7200 }, 7201 7202 /** 7203 * Creates an anonymous function for notifiying error listeners of a particular object 7204 * data. 7205 * @param obj - the objects whose error listeners to notify 7206 * @returns {Function} 7207 * Callback for notifying of errors 7208 * @private 7209 */ 7210 _createErrorNotifier: function (obj) { 7211 return function (err) { 7212 obj._errorNotifier.notifyListeners(err); 7213 }; 7214 }, 7215 7216 /** 7217 * Replaces the collection with a refreshed list using the passed in 7218 * data. 7219 * @param data - data object (usually this._data) 7220 * @private 7221 */ 7222 _buildRefreshedCollection: function (data) { 7223 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 7224 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7225 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7226 } else { 7227 dataArray = []; 7228 } 7229 7230 // iterate through each item in the new data and add to or update collection 7231 for (i = 0; i < dataArray.length; i += 1) { 7232 dataObject = {}; 7233 dataObject[this.getRestItemType()] = dataArray[i]; 7234 objectId = this._extractId(dataObject); 7235 7236 this._objectCreator(dataObject); 7237 newIds.push(objectId); 7238 7239 // resubscribe if the object requires an explicit subscription 7240 object = this._collection[objectId]; 7241 if (this.handlesItemRefresh && object.explicitSubscription) { 7242 object._subscribeNode({ 7243 error: this._createErrorNotifier(object) 7244 }); 7245 } 7246 } 7247 7248 // now clean up items (if any) that were removed 7249 for (objectId in this._collection) { 7250 if (this._collection.hasOwnProperty(objectId)) { 7251 foundFlag = false; 7252 for (i = newIds.length - 1; i >= 0; i -= 1) { 7253 if (newIds[i] === objectId) { 7254 foundFlag = true; 7255 break; 7256 } 7257 } 7258 // did not find in updated list, so delete it 7259 if (!foundFlag) { 7260 this._objectDeleter({'data': this._collection[objectId]._data}); 7261 } 7262 } 7263 } 7264 }, 7265 7266 /** 7267 * The actual refresh operation, refactored out so we don't have to repeat code 7268 * @private 7269 */ 7270 _RESTRefresh: function () { 7271 var _this = this; 7272 this._doGET({ 7273 success: function(rsp) { 7274 if (_this._processResponse(rsp)) { 7275 _this._buildRefreshedCollection(_this._data); 7276 } else { 7277 _this._errorNotifier.notifyListeners(_this); 7278 } 7279 }, 7280 error: function(rsp) { 7281 _this._errorNotifier.notifyListeners(rsp); 7282 } 7283 }); 7284 }, 7285 7286 /** 7287 * Force an update on this object. Since an asynchronous GET is performed, 7288 * it is necessary to have an onChange handler registered in order to be 7289 * notified when the response of this returns. 7290 * @returns {finesse.restservices.RestBaseCollection} 7291 * This RestBaseCollection object to allow cascading 7292 */ 7293 refresh: function() { 7294 var _this = this, isLoaded = this._loaded; 7295 7296 // resubscribe if the collection requires an explicit subscription 7297 if (this.explicitSubscription) { 7298 this._subscribeNode({ 7299 success: function () { 7300 _this._RESTRefresh(); 7301 }, 7302 error: function (err) { 7303 _this._errorNotifier.notifyListeners(err); 7304 } 7305 }); 7306 } else { 7307 this._RESTRefresh(); 7308 } 7309 7310 return this; // Allow cascading 7311 }, 7312 7313 /** 7314 * @private 7315 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 7316 * format of an array of {(Object Type): object} objects. For example, a 7317 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 7318 * @param skipOuterObject If {true} is passed in for this param, then the "data" 7319 * property is returned instead of an object with the 7320 * data appended. 7321 * @return {Array} 7322 */ 7323 extractCollectionData: function (skipOuterObject) { 7324 var restObjs, 7325 obj, 7326 result = [], 7327 _this = this; 7328 7329 if (this._data) 7330 { 7331 restObjs = this._data[this.getRestItemType()]; 7332 7333 if (restObjs) 7334 { 7335 // check if there are multiple objects to pass 7336 if (!$.isArray(restObjs)) 7337 { 7338 restObjs = [restObjs]; 7339 } 7340 7341 // if so, create an object for each and add to result array 7342 $.each(restObjs, function (id, object) { 7343 if (skipOuterObject === true) 7344 { 7345 obj = object; 7346 } 7347 else 7348 { 7349 obj = {}; 7350 obj[_this.getRestItemType()] = object; 7351 } 7352 result.push(obj); 7353 }); 7354 } 7355 7356 } 7357 7358 return result; 7359 }, 7360 7361 /** 7362 * For Finesse, collections are handled uniquely on a POST and 7363 * doesn't necessary follow REST conventions. A POST on a collection 7364 * doesn't mean that the collection has been created, it means that an 7365 * item has been added to the collection. This function will generate 7366 * a closure which will handle this logic appropriately. 7367 * @param {Object} scope 7368 * The scope of where the callback should be invoked. 7369 * @private 7370 */ 7371 _addHandlerCb: function (scope) { 7372 return function (restItem) { 7373 var data = restItem.extractCollectionData(); 7374 7375 $.each(data, function (id, object) { 7376 scope._objectCreator(object); 7377 }); 7378 }; 7379 }, 7380 7381 /** 7382 * For Finesse, collections are handled uniquely on a DELETE and 7383 * doesn't necessary follow REST conventions. A DELETE on a collection 7384 * doesn't mean that the collection has been deleted, it means that an 7385 * item has been deleted from the collection. This function will generate 7386 * a closure which will handle this logic appropriately. 7387 * @param {Object} scope 7388 * The scope of where the callback should be invoked. 7389 * @private 7390 */ 7391 _deleteHandlerCb: function (scope) { 7392 return function (restItem) { 7393 var data = restItem.extractCollectionData(); 7394 7395 $.each(data, function (id, obj) { 7396 scope._objectDeleter(obj); 7397 }); 7398 }; 7399 }, 7400 7401 /** 7402 * Utility method to process the update notification for Rest Items 7403 * that are children of the collection whose events are published to 7404 * the collection's node. 7405 * @param {Object} update 7406 * The payload of an update notification. 7407 * @returns {Boolean} 7408 * True if the update was successfully processed (the update object 7409 * passed the schema validation) and updated the internal data cache, 7410 * false otherwise. 7411 * @private 7412 */ 7413 _processRestItemUpdate: function (update) { 7414 var object, objectId, updateObj = update.object.Update; 7415 7416 //Extract the ID from the source if the Update was an error. 7417 if (updateObj.data.apiErrors) { 7418 objectId = Utilities.getId(updateObj.source); 7419 } 7420 //Otherwise extract from the data object itself. 7421 else { 7422 objectId = this._extractId(updateObj.data); 7423 } 7424 7425 object = this._collection[objectId]; 7426 if (object) { 7427 if (object._processUpdate(update)) { 7428 switch (updateObj.event) { 7429 case "POST": 7430 object._addNotifier.notifyListeners(object); 7431 break; 7432 case "PUT": 7433 object._changeNotifier.notifyListeners(object); 7434 break; 7435 case "DELETE": 7436 object._deleteNotifier.notifyListeners(object); 7437 break; 7438 } 7439 } 7440 } 7441 }, 7442 7443 /** 7444 * SUBCLASS IMPLEMENTATION (override): 7445 * For collections, this callback has the additional responsibility of passing events 7446 * of collection item updates to the item objects themselves. The collection needs to 7447 * do this because item updates are published to the collection's node. 7448 * @returns {Function} 7449 * The callback to be invoked when an update event is received 7450 * @private 7451 */ 7452 _createPubsubCallback: function () { 7453 var _this = this; 7454 return function (update) { 7455 //If the source of the update is our REST URL, this means the collection itself is modified 7456 if (update.object.Update.source === _this.getRestUrl()) { 7457 _this._updateEventHandler(_this, update); 7458 } else { 7459 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 7460 //rest item update of one of our children that was published on our node (OpenAjax topic) 7461 _this._processRestItemUpdate(update); 7462 } 7463 }; 7464 }, 7465 7466 /** 7467 * @class 7468 * This is the base collection object. 7469 * 7470 * @constructs 7471 * @augments finesse.restservices.RestBase 7472 * @see finesse.restservices.Contacts 7473 * @see finesse.restservices.Dialogs 7474 * @see finesse.restservices.PhoneBooks 7475 * @see finesse.restservices.Queues 7476 * @see finesse.restservices.WorkflowActions 7477 * @see finesse.restservices.Workflows 7478 * @see finesse.restservices.WrapUpReasons 7479 */ 7480 _fakeConstuctor: function () { 7481 /* This is here to hide the real init constructor from the public docs */ 7482 }, 7483 7484 /** 7485 * @private 7486 * @param {Object} options 7487 * An object with the following properties:<ul> 7488 * <li><b>id:</b> The id of the object being constructed</li> 7489 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 7490 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 7491 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 7492 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 7493 * This does not include adding and deleting members of the collection</li> 7494 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 7495 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 7496 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 7497 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7498 * <li><b>content:</b> {String} Raw string of response</li> 7499 * <li><b>object:</b> {Object} Parsed object of response</li> 7500 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7501 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7502 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7503 * </ul></li> 7504 * </ul></li> 7505 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7506 **/ 7507 init: function (options) { 7508 7509 options = options || {}; 7510 options.id = ""; 7511 7512 //Make internal datastore collection to hold a list of objects. 7513 this._collection = {}; 7514 this.length = 0; 7515 7516 //Collections will have additional callbacks that will be invoked when 7517 //an item has been added/deleted. 7518 this._collectionAddNotifier = new Notifier(); 7519 this._collectionDeleteNotifier = new Notifier(); 7520 7521 //Initialize the base class. 7522 this._super(options); 7523 7524 this.addHandler('collectionAdd', options.onCollectionAdd); 7525 this.addHandler('collectionDelete', options.onCollectionDelete); 7526 7527 //For Finesse, collections are handled uniquely on a POST/DELETE and 7528 //doesn't necessary follow REST conventions. A POST on a collection 7529 //doesn't mean that the collection has been created, it means that an 7530 //item has been added to the collection. A DELETE means that an item has 7531 //been removed from the collection. Due to this, we are attaching 7532 //special callbacks to the add/delete that will handle this logic. 7533 this.addHandler("add", this._addHandlerCb(this)); 7534 this.addHandler("delete", this._deleteHandlerCb(this)); 7535 }, 7536 7537 /** 7538 * Returns the collection. 7539 * @returns {Object} 7540 * The collection as an object 7541 */ 7542 getCollection: function () { 7543 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 7544 return this._collection; 7545 }, 7546 7547 /** 7548 * Utility method to build the internal collection data structure (object) based on provided data 7549 * @param {Object} data 7550 * The data to build the internal collection from 7551 * @private 7552 */ 7553 _buildCollection: function (data) { 7554 var i, object, objectId, dataArray; 7555 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7556 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7557 for (i = 0; i < dataArray.length; i += 1) { 7558 7559 object = {}; 7560 object[this.getRestItemType()] = dataArray[i]; 7561 objectId = this._extractId(object); 7562 this._collection[objectId] = new (this.getRestItemClass())({ 7563 doNotSubscribe: this.handlesItemSubscription, 7564 doNotRefresh: this.handlesItemRefresh, 7565 id: objectId, 7566 data: object 7567 }); 7568 this.length += 1; 7569 } 7570 } 7571 }, 7572 7573 /** 7574 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 7575 * Override this in subclasses if you need only object with certain attribute values. 7576 * @param {Object} item Item to test. 7577 * @return {Boolean} False to keep, true to filter out (discard); 7578 */ 7579 _filterOutItem: function (item) { 7580 return false; 7581 }, 7582 7583 /** 7584 * Validate and store the object into the internal data store. 7585 * SUBCLASS IMPLEMENTATION (override): 7586 * Performs collection specific logic to _buildCollection internally based on provided data 7587 * @param {Object} object 7588 * The JavaScript object that should match of schema of this REST object. 7589 * @returns {Boolean} 7590 * True if the object was validated and stored successfully. 7591 * @private 7592 */ 7593 _processObject: function (object) { 7594 var i, 7595 restItemType = this.getRestItemType(), 7596 items; 7597 if (this._validate(object)) { 7598 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7599 7600 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 7601 if (this._data) 7602 { 7603 items = this._data[restItemType]; 7604 7605 if (typeof(items) !== "undefined") 7606 { 7607 if (typeof(items.length) === "undefined") 7608 { 7609 // Single object 7610 if (this._filterOutItem(items)) 7611 { 7612 this._data[restItemType] = items = []; 7613 } 7614 7615 } 7616 else 7617 { 7618 // filter out objects 7619 for (i = items.length - 1; i !== -1; i = i - 1) 7620 { 7621 if (this._filterOutItem(items[i])) 7622 { 7623 items.splice(i, 1); 7624 } 7625 } 7626 } 7627 } 7628 } 7629 7630 // If loaded for the first time, call the load notifiers. 7631 if (!this._loaded) { 7632 this._buildCollection(this._data); 7633 this._loaded = true; 7634 this._loadNotifier.notifyListeners(this); 7635 } 7636 7637 return true; 7638 7639 } 7640 return false; 7641 }, 7642 7643 /** 7644 * Retrieves a reference to a particular notifierType. 7645 * @param {String} notifierType 7646 * Specifies the notifier to retrieve (load, change, error, add, delete) 7647 * @return {Notifier} The notifier object. 7648 */ 7649 _getNotifierReference: function (notifierType) { 7650 var notifierReference; 7651 7652 try { 7653 //Use the base method to get references for load/change/error. 7654 notifierReference = this._super(notifierType); 7655 } catch (err) { 7656 //Check for add/delete 7657 if (notifierType === "collectionAdd") { 7658 notifierReference = this._collectionAddNotifier; 7659 } else if (notifierType === "collectionDelete") { 7660 notifierReference = this._collectionDeleteNotifier; 7661 } else { 7662 //Rethrow exception from base class. 7663 throw err; 7664 } 7665 } 7666 return notifierReference; 7667 } 7668 }); 7669 7670 window.finesse = window.finesse || {}; 7671 window.finesse.restservices = window.finesse.restservices || {}; 7672 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 7673 7674 return RestCollectionBase; 7675 }); 7676 7677 /** 7678 * JavaScript representation of the Finesse Dialog object. 7679 * 7680 * @requires finesse.clientservices.ClientServices 7681 * @requires Class 7682 * @requires finesse.FinesseBase 7683 * @requires finesse.restservices.RestBase 7684 */ 7685 7686 /** @private */ 7687 define('restservices/DialogBase',[ 7688 'restservices/RestBase', 7689 'utilities/Utilities' 7690 ], 7691 function (RestBase, Utilities) { 7692 var DialogBase = RestBase.extend(/** @lends finesse.restservices.DialogBase.prototype */{ 7693 7694 /** 7695 * @class 7696 * A DialogBase is an attempted connection between or among multiple participants, 7697 * for example, a regular phone call, a chat, or an email. 7698 * 7699 * This object is typically extended into individual 7700 * REST Objects (like Dialog, MediaDialog, etc...), and shouldn't be used directly. 7701 * 7702 * @augments finesse.restservices.RestBase 7703 * @constructs 7704 */ 7705 _fakeConstuctor: function () { 7706 /* This is here to hide the real init constructor from the public docs */ 7707 }, 7708 7709 /** 7710 * @private 7711 * 7712 * @param {Object} options 7713 * An object with the following properties:<ul> 7714 * <li><b>id:</b> The id of the object being constructed</li> 7715 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7716 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7717 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7718 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7719 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7720 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7721 * <li><b>content:</b> {String} Raw string of response</li> 7722 * <li><b>object:</b> {Object} Parsed object of response</li> 7723 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7724 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7725 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7726 * </ul></li> 7727 * </ul></li> 7728 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7729 **/ 7730 init: function (options) { 7731 this._super(options); 7732 }, 7733 7734 /** 7735 * @private 7736 * Gets the REST class for the current object - this is the Dialog class. 7737 * @returns {Object} The Dialog class. 7738 */ 7739 getRestClass: function () { 7740 throw new Error("getRestClass(): Not implemented in subtype."); 7741 }, 7742 7743 /** 7744 * @private 7745 * The constant for agent device. 7746 */ 7747 _agentDeviceType: "AGENT_DEVICE", 7748 7749 /** 7750 * @private 7751 * Gets the REST type for the current object - this is a "Dialog". 7752 * @returns {String} The Dialog string. 7753 */ 7754 getRestType: function () { 7755 return "Dialog"; 7756 }, 7757 7758 /** 7759 * @private 7760 * Override default to indicate that this object doesn't support making 7761 * requests. 7762 */ 7763 supportsRequests: false, 7764 7765 /** 7766 * @private 7767 * Override default to indicate that this object doesn't support subscriptions. 7768 */ 7769 supportsSubscriptions: false, 7770 7771 7772 /** 7773 * Getter for the media type. 7774 * @returns {String} The media type. 7775 */ 7776 getMediaType: function () { 7777 this.isLoaded(); 7778 return this.getData().mediaType; 7779 }, 7780 7781 /** 7782 * @private 7783 * Getter for the uri. 7784 * @returns {String} The uri. 7785 */ 7786 getDialogUri: function () { 7787 this.isLoaded(); 7788 return this.getData().uri; 7789 }, 7790 7791 /** 7792 * Getter for the callType. 7793 * @deprecated Use getMediaProperties().callType instead. 7794 * @returns {String} The callType. 7795 */ 7796 getCallType: function () { 7797 this.isLoaded(); 7798 return this.getData().mediaProperties.callType; 7799 }, 7800 7801 7802 /** 7803 * Getter for the Dialog state. 7804 * @returns {String} The Dialog state. 7805 */ 7806 getState: function () { 7807 this.isLoaded(); 7808 return this.getData().state; 7809 }, 7810 7811 /** 7812 * Retrieves a list of participants within the Dialog object. 7813 * @returns {Object} Array list of participants. 7814 * Participant entity properties are as follows:<ul> 7815 * <li>state - The state of the Participant. 7816 * <li>stateCause - The state cause of the Participant. 7817 * <li>mediaAddress - The media address of the Participant. 7818 * <li>startTime - The start Time of the Participant. 7819 * <li>stateChangeTime - The time when participant state has changed. 7820 * <li>actions - These are the actions that a Participant can perform</ul> 7821 */ 7822 getParticipants: function () { 7823 this.isLoaded(); 7824 var participants = this.getData().participants.Participant; 7825 //Due to the nature of the XML->JSO converter library, a single 7826 //element in the XML array will be considered to an object instead of 7827 //a real array. This will handle those cases to ensure that an array is 7828 //always returned. 7829 7830 return Utilities.getArray(participants); 7831 }, 7832 7833 /** 7834 * This method retrieves the participant timer counters 7835 * 7836 * @param {String} participantExt Extension of participant. 7837 * @returns {Object} Array of Participants which contains properties :<ul> 7838 * <li>state - The state of the Participant. 7839 * <li>startTime - The start Time of the Participant. 7840 * <li>stateChangeTime - The time when participant state has changed.</ul> 7841 * 7842 */ 7843 getParticipantTimerCounters : function (participantExt) { 7844 var part, participantTimerCounters = {}, idx, participants; 7845 7846 participants = this.getParticipants(); 7847 7848 7849 //Loop through all the participants and find the right participant (based on participantExt) 7850 for(idx=0;idx<participants.length;idx=idx+1) 7851 { 7852 part = participants[idx]; 7853 7854 if (part.mediaAddress === participantExt) 7855 { 7856 participantTimerCounters.startTime= part.startTime; 7857 participantTimerCounters.stateChangeTime= part.stateChangeTime; 7858 participantTimerCounters.state= part.state; 7859 break; 7860 } 7861 } 7862 7863 return participantTimerCounters; 7864 }, 7865 7866 7867 /** 7868 * Retrieves a list of media properties from the dialog object. 7869 * @returns {Object} Map of call variables; names mapped to values. 7870 * Variables may include the following:<ul> 7871 * <li>dialedNumber: The number dialed. 7872 * <li>callType: The type of call. Call types include:<ul> 7873 * <li>ACD_IN 7874 * <li>PREROUTE_ACD_IN 7875 * <li>PREROUTE_DIRECT_AGENT 7876 * <li>TRANSFER 7877 * <li>OTHER_IN 7878 * <li>OUT 7879 * <li>AGENT_INSIDE 7880 * <li>CONSULT 7881 * <li>CONFERENCE 7882 * <li>SUPERVISOR_MONITOR 7883 * <li>OUTBOUND 7884 * <li>OUTBOUND_PREVIEW</ul> 7885 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 7886 * <li>wrapUpReason: A description of the call. 7887 * <li>queueNumber: queue ID of the call. 7888 * <li>queueName: queue name of the call. 7889 * <li>callKeyCallId: unique number of the call routed on a particular day. 7890 * <li>callKeyPrefix: represents the day when the call is routed. 7891 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 7892 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 7893 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 7894 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 7895 * <li>BACampaign 7896 * <li>BAAccountNumber 7897 * <li>BAResponse 7898 * <li>BAStatus<ul> 7899 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 7900 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 7901 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 7902 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 7903 * <li>BADialedListID 7904 * <li>BATimeZone 7905 * <li>BABuddyName</ul></ul> 7906 * 7907 */ 7908 getMediaProperties: function () { 7909 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 7910 7911 this.isLoaded(); 7912 7913 // We have to convert to jQuery object to do a proper compare 7914 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 7915 7916 if ((this._lastMediaPropertiesJQuery !== undefined) 7917 && (this._lastMediaPropertiesMap !== undefined) 7918 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 7919 7920 return this._lastMediaPropertiesMap; 7921 } 7922 7923 currentMediaPropertiesMap = {}; 7924 7925 mpData = this.getData().mediaProperties; 7926 7927 if (mpData) { 7928 if (mpData.callvariables && mpData.callvariables.CallVariable) { 7929 if (mpData.callvariables.CallVariable.length === undefined) { 7930 mpData.callvariables.CallVariable = [mpData.callvariables.CallVariable]; 7931 } 7932 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 7933 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 7934 }); 7935 } 7936 7937 jQuery.each(mpData, function (key, value) { 7938 if (key !== 'callvariables') { 7939 currentMediaPropertiesMap[key] = value; 7940 } 7941 }); 7942 } 7943 7944 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 7945 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 7946 7947 return this._lastMediaPropertiesMap; 7948 }, 7949 7950 7951 7952 /** 7953 * @private 7954 * Invoke a request to the server given a content body and handlers. 7955 * 7956 * @param {Object} contentBody 7957 * A JS object containing the body of the action request. 7958 * @param {finesse.interfaces.RequestHandlers} handlers 7959 * An object containing the handlers for the request 7960 */ 7961 _makeRequest: function (contentBody, handlers) { 7962 // Protect against null dereferencing of options allowing its 7963 // (nonexistent) keys to be read as undefined 7964 handlers = handlers || {}; 7965 7966 this.restRequest(this.getRestUrl(), { 7967 method: 'PUT', 7968 success: handlers.success, 7969 error: handlers.error, 7970 content: contentBody 7971 }); 7972 } 7973 7974 }); 7975 7976 window.finesse = window.finesse || {}; 7977 window.finesse.restservices = window.finesse.restservices || {}; 7978 window.finesse.restservices.DialogBase = DialogBase; 7979 7980 7981 return DialogBase; 7982 }); 7983 7984 /** 7985 * JavaScript representation of the Finesse Dialog object. 7986 * 7987 * @requires finesse.clientservices.ClientServices 7988 * @requires Class 7989 * @requires finesse.FinesseBase 7990 * @requires finesse.restservices.RestBase 7991 */ 7992 7993 /** @private */ 7994 define('restservices/Dialog',[ 7995 'restservices/DialogBase', 7996 'utilities/Utilities' 7997 ], 7998 function (DialogBase, Utilities) { 7999 var Dialog = DialogBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 8000 8001 /** 8002 * @class 8003 * A Dialog is an attempted connection between or among multiple participants, 8004 * for example, a regular phone call, a conference, or a silent monitor session. 8005 * 8006 * @augments finesse.restservices.DialogBase 8007 * @constructs 8008 */ 8009 _fakeConstuctor: function () { 8010 /* This is here to hide the real init constructor from the public docs */ 8011 }, 8012 8013 /** 8014 * @private 8015 * 8016 * @param {Object} options 8017 * An object with the following properties:<ul> 8018 * <li><b>id:</b> The id of the object being constructed</li> 8019 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8020 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8021 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8022 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8023 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8024 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8025 * <li><b>content:</b> {String} Raw string of response</li> 8026 * <li><b>object:</b> {Object} Parsed object of response</li> 8027 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8028 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8029 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8030 * </ul></li> 8031 * </ul></li> 8032 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8033 **/ 8034 init: function (options) { 8035 this._super(options); 8036 }, 8037 8038 /** 8039 * @private 8040 * Gets the REST class for the current object - this is the Dialog class. 8041 * @returns {Object} The Dialog class. 8042 */ 8043 getRestClass: function () { 8044 return Dialog; 8045 }, 8046 8047 /** 8048 * The requestId reaper timeout in ms 8049 */ 8050 REQUESTID_REAPER_TIMEOUT: 5000, 8051 8052 /** 8053 * Getter for the from address. 8054 * @returns {String} The from address. 8055 */ 8056 getFromAddress: function () { 8057 this.isLoaded(); 8058 return this.getData().fromAddress; 8059 }, 8060 8061 /** 8062 * Getter for the to address. 8063 * @returns {String} The to address. 8064 */ 8065 getToAddress: function () { 8066 this.isLoaded(); 8067 return this.getData().toAddress; 8068 }, 8069 8070 8071 /** 8072 * gets the participant timer counters 8073 * 8074 * @param {String} participantExt Extension of participant. 8075 * @returns {Object} Array of Participants which contains properties :<ul> 8076 * <li>state - The state of the Participant. 8077 * <li>startTime - The start Time of the Participant. 8078 * <li>stateChangeTime - The time when participant state has changed.</ul> 8079 */ 8080 getParticipantTimerCounters : function (participantExt) { 8081 var part, participantTimerCounters = {}, idx, participants; 8082 8083 participants = this.getParticipants(); 8084 8085 8086 //Loop through all the participants and find the right participant (based on participantExt) 8087 for(idx=0;idx<participants.length;idx=idx+1) 8088 { 8089 part = participants[idx]; 8090 8091 if (part.mediaAddress === participantExt) 8092 { 8093 participantTimerCounters.startTime= part.startTime; 8094 participantTimerCounters.stateChangeTime= part.stateChangeTime; 8095 participantTimerCounters.state= part.state; 8096 break; 8097 } 8098 } 8099 8100 return participantTimerCounters; 8101 }, 8102 8103 /** 8104 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 8105 * (It is not a CTI Route Point, IVR Port, or the caller) 8106 * 8107 * @param {String} filterExtension used to remove a single extension from the list 8108 * @returns {Object} Array of Participants that can be dropped. 8109 * Participant entity properties are as follows:<ul> 8110 * <li>state - The state of the Participant. 8111 * <li>stateCause - The state cause of the Participant. 8112 * <li>mediaAddress - The media address of the Participant. 8113 * <li>startTime - The start Time of the Participant. 8114 * <li>stateChangeTime - The time when participant state has changed. 8115 * <li>actions - These are the actions that a Participant can perform</ul> 8116 */ 8117 getDroppableParticipants: function (filterExtension) { 8118 this.isLoaded(); 8119 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 8120 8121 participants = this.getParticipants(); 8122 8123 if (filterExtension) 8124 { 8125 filterExtensionToRemove = filterExtension; 8126 } 8127 8128 //Loop through all the participants to remove non-agents & remove filterExtension 8129 //We could have removed filterExtension using splice, but we have to iterate through 8130 //the list anyway. 8131 for(idx=0;idx<participants.length;idx=idx+1) 8132 { 8133 part = participants[idx]; 8134 8135 //Skip the filterExtension 8136 if (part.mediaAddress !== filterExtensionToRemove) 8137 { 8138 callStateOk = this._isParticipantStateDroppable(part); 8139 8140 //Remove non-agents & make sure callstate 8141 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8142 { 8143 droppableParticipants.push(part); 8144 } 8145 } 8146 } 8147 8148 return Utilities.getArray(droppableParticipants); 8149 }, 8150 8151 _isParticipantStateDroppable : function (part) 8152 { 8153 var isParticipantStateDroppable = false; 8154 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 8155 { 8156 isParticipantStateDroppable = true; 8157 } 8158 8159 return isParticipantStateDroppable; 8160 }, 8161 8162 /** 8163 * Is the participant droppable 8164 * 8165 * @param {String} participantExt Extension of participant. 8166 * @returns {Boolean} True is droppable. 8167 */ 8168 isParticipantDroppable : function (participantExt) { 8169 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 8170 8171 droppableParticipants = this.getDroppableParticipants(); 8172 8173 if (droppableParticipants) 8174 { 8175 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 8176 { 8177 part = droppableParticipants[idx]; 8178 8179 if (part.mediaAddress === participantExt) 8180 { 8181 callStateOk = this._isParticipantStateDroppable(part); 8182 8183 //Remove non-agents & make sure callstate 8184 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8185 { 8186 isDroppable = true; 8187 break; 8188 } 8189 } 8190 } 8191 } 8192 8193 return isDroppable; 8194 }, 8195 8196 /** 8197 * Retrieves information about the currently scheduled callback, if any. 8198 * @returns {Object} If no callback has been set, will return undefined. If 8199 * a callback has been set, it will return a map with one or more of the 8200 * following entries, depending on what values have been set. 8201 * callbackTime - the callback time, if it has been set. 8202 * callbackNumber - the callback number, if it has been set. 8203 */ 8204 getCallbackInfo: function() { 8205 this.isLoaded(); 8206 return this.getData().scheduledCallbackInfo; 8207 }, 8208 8209 /** 8210 * Invoke a consult call out to a destination. 8211 * 8212 * @param {String} mediaAddress 8213 * The media address of the user performing the consult call. 8214 * @param {String} toAddress 8215 * The destination address of the consult call. 8216 * @param {finesse.interfaces.RequestHandlers} handlers 8217 * An object containing the handlers for the request 8218 */ 8219 makeConsultCall: function (mediaAddress, toAddress, handlers) { 8220 this.isLoaded(); 8221 var contentBody = {}; 8222 contentBody[this.getRestType()] = { 8223 "targetMediaAddress": mediaAddress, 8224 "toAddress": toAddress, 8225 "requestedAction": Dialog.Actions.CONSULT_CALL 8226 }; 8227 this._makeRequest(contentBody, handlers); 8228 return this; // Allow cascading 8229 }, 8230 8231 /** 8232 * Invoke a single step transfer request. 8233 * 8234 * @param {String} mediaAddress 8235 * The media address of the user performing the single step transfer. 8236 * @param {String} toAddress 8237 * The destination address of the single step transfer. 8238 * @param {finesse.interfaces.RequestHandlers} handlers 8239 * An object containing the handlers for the request 8240 */ 8241 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 8242 this.isLoaded(); 8243 var contentBody = {}; 8244 contentBody[this.getRestType()] = { 8245 "targetMediaAddress": mediaAddress, 8246 "toAddress": toAddress, 8247 "requestedAction": Dialog.Actions.TRANSFER_SST 8248 }; 8249 this._makeRequest(contentBody, handlers); 8250 return this; // Allow cascading 8251 }, 8252 8253 /** 8254 * Update this dialog's wrap-up reason. 8255 * 8256 * @param {String} wrapUpReason 8257 * The new wrap-up reason for this dialog 8258 * @param {finesse.interfaces.RequestHandlers} handlers 8259 * An object containing the handlers for the request 8260 */ 8261 updateWrapUpReason: function (wrapUpReason, options) 8262 { 8263 this.isLoaded(); 8264 var mediaProperties = 8265 { 8266 "wrapUpReason": wrapUpReason 8267 }; 8268 8269 options = options || {}; 8270 options.content = {}; 8271 options.content[this.getRestType()] = 8272 { 8273 "mediaProperties": mediaProperties, 8274 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 8275 }; 8276 options.method = "PUT"; 8277 this.restRequest(this.getRestUrl(), options); 8278 8279 return this; 8280 }, 8281 8282 /** 8283 * Invoke a request to server based on the action given. 8284 * @param {String} mediaAddress 8285 * The media address of the user performing the action. 8286 * @param {finesse.restservices.Dialog.Actions} action 8287 * The action string indicating the action to invoke on dialog. 8288 * @param {finesse.interfaces.RequestHandlers} handlers 8289 * An object containing the handlers for the request 8290 */ 8291 requestAction: function (mediaAddress, action, handlers) { 8292 this.isLoaded(); 8293 var contentBody = {}; 8294 contentBody[this.getRestType()] = { 8295 "targetMediaAddress": mediaAddress, 8296 "requestedAction": action 8297 }; 8298 this._makeRequest(contentBody, handlers); 8299 return this; // Allow cascading 8300 }, 8301 8302 /** 8303 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 8304 * 8305 * @param targetMediaAddress is the address to drop 8306 * @param {finesse.interfaces.RequestHandlers} handlers 8307 * An object containing the handlers for the request 8308 */ 8309 dropParticipant: function (targetMediaAddress, handlers) { 8310 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 8311 }, 8312 8313 /** 8314 * Invoke a request to server to send DTMF digit tones. 8315 * @param {String} mediaAddress 8316 * @param {finesse.interfaces.RequestHandlers} handlers 8317 * An object containing the handlers for the request 8318 * @param {String} digit 8319 * The digit which causes invocation of an action on the dialog. 8320 */ 8321 sendDTMFRequest: function (mediaAddress, handlers, digit) { 8322 this.isLoaded(); 8323 var contentBody = {}; 8324 contentBody[this.getRestType()] = { 8325 "targetMediaAddress": mediaAddress, 8326 "requestedAction": "SEND_DTMF", 8327 "actionParams": { 8328 "ActionParam": { 8329 "name": "dtmfString", 8330 "value": digit 8331 } 8332 } 8333 }; 8334 this._makeRequest(contentBody, handlers); 8335 return this; // Allow cascading 8336 }, 8337 8338 /** 8339 * Invoke a request to server to set the time for a callback. 8340 * @param {String} mediaAddress 8341 * @param {String} callbackTime 8342 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 8343 * (ex: 2013-12-24T23:59) 8344 * @param {finesse.interfaces.RequestHandlers} handlers 8345 * An object containing the handlers for the request 8346 */ 8347 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 8348 this.isLoaded(); 8349 var contentBody = {}; 8350 contentBody[this.getRestType()] = { 8351 "targetMediaAddress": mediaAddress, 8352 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8353 "actionParams": { 8354 "ActionParam": { 8355 "name": "callbackTime", 8356 "value": callbackTime 8357 } 8358 } 8359 }; 8360 this._makeRequest(contentBody, handlers); 8361 return this; // Allow cascading 8362 }, 8363 8364 /** 8365 * Invoke a request to server to set the number for a callback. 8366 * @param {String} mediaAddress 8367 * @param {String} callbackNumber 8368 * The requested number to call for the callback 8369 * @param {finesse.interfaces.RequestHandlers} handlers 8370 * An object containing the handlers for the request 8371 */ 8372 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 8373 this.isLoaded(); 8374 var contentBody = {}; 8375 contentBody[this.getRestType()] = { 8376 "targetMediaAddress": mediaAddress, 8377 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8378 "actionParams": { 8379 "ActionParam": { 8380 "name": "callbackNumber", 8381 "value": callbackNumber 8382 } 8383 } 8384 }; 8385 this._makeRequest(contentBody, handlers); 8386 return this; // Allow cascading 8387 }, 8388 8389 /** 8390 * Invoke a request to server to cancel a callback. 8391 * @param {String} mediaAddress 8392 * @param {finesse.interfaces.RequestHandlers} handlers 8393 * An object containing the handlers for the request 8394 */ 8395 cancelCallback: function (mediaAddress, handlers) { 8396 this.isLoaded(); 8397 var contentBody = {}; 8398 contentBody[this.getRestType()] = { 8399 "targetMediaAddress": mediaAddress, 8400 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 8401 }; 8402 this._makeRequest(contentBody, handlers); 8403 return this; // Allow cascading 8404 }, 8405 8406 /** 8407 * Invoke a request to server to reclassify the call type. 8408 * @param {String} mediaAddress 8409 * The media address of the user performing the consult call. 8410 * @param {String} classification 8411 * The classification to assign to the call. Valid values are "VOICE", "FAX", 8412 * "ANS_MACHINE", "INVALID", "BUSY" (CCX only), and "DO_NOT_CALL". 8413 * @param {finesse.interfaces.RequestHandlers} handlers 8414 * An object containing the handlers for the request 8415 */ 8416 reclassifyCall: function (mediaAddress, classification, handlers) { 8417 this.isLoaded(); 8418 var contentBody = {}; 8419 contentBody[this.getRestType()] = { 8420 "targetMediaAddress": mediaAddress, 8421 "requestedAction": Dialog.Actions.RECLASSIFY, 8422 "actionParams": { 8423 "ActionParam": { 8424 "name": "outboundClassification", 8425 "value": classification 8426 } 8427 } 8428 }; 8429 this._makeRequest(contentBody, handlers); 8430 return this; // Allow cascading 8431 }, 8432 8433 /** 8434 * Utility method to create a closure containing the requestId and the Dialogs object so 8435 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8436 * @param {String} requestId The requestId of the event 8437 * @return {Function} The function to be executed by setTimeout 8438 */ 8439 _createRequestIdReaper: function (requestId) { 8440 var that = this; 8441 return function () { 8442 that._logger.log("Dialog: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8443 delete that._pendingCallbacks[requestId]; 8444 }; 8445 }, 8446 8447 /** 8448 * Overriding implementation of the one in RestBase.js 8449 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8450 * @param {String} requestId The requestId of the event 8451 */ 8452 _postProcessUpdateStrategy: function (requestId) { 8453 this._logger.log("Dialog: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8454 var callbacksObj = this._pendingCallbacks[requestId]; 8455 if (callbacksObj && !callbacksObj.used) { 8456 this._logger.log("Dialog: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8457 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8458 callbacksObj.used = true; 8459 } 8460 } 8461 8462 }); 8463 8464 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 8465 /** 8466 * Drops the Participant from the Dialog. 8467 */ 8468 DROP: "DROP", 8469 /** 8470 * Answers a Dialog. 8471 */ 8472 ANSWER: "ANSWER", 8473 /** 8474 * Holds the Dialog. 8475 */ 8476 HOLD: "HOLD", 8477 /** 8478 * Barges into a Call Dialog. 8479 */ 8480 BARGE_CALL: "BARGE_CALL", 8481 /** 8482 * Allow as Supervisor to Drop a Participant from the Dialog. 8483 */ 8484 PARTICIPANT_DROP: "PARTICIPANT_DROP", 8485 /** 8486 * Makes a new Call Dialog. 8487 */ 8488 MAKE_CALL: "MAKE_CALL", 8489 /** 8490 * Retrieves a Dialog that is on Hold. 8491 */ 8492 RETRIEVE: "RETRIEVE", 8493 /** 8494 * Sets the time or number for a callback. Can be 8495 * either a new callback, or updating an existing one. 8496 */ 8497 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 8498 /** 8499 * Cancels a callback. 8500 */ 8501 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 8502 /** 8503 * Initiates a Consult Call. 8504 */ 8505 CONSULT_CALL: "CONSULT_CALL", 8506 /** 8507 * Initiates a Transfer of a Dialog. 8508 */ 8509 TRANSFER: "TRANSFER", 8510 /** 8511 * Initiates a Single-Step Transfer of a Dialog. 8512 */ 8513 TRANSFER_SST: "TRANSFER_SST", 8514 /** 8515 * Initiates a Conference of a Dialog. 8516 */ 8517 CONFERENCE: "CONFERENCE", 8518 /** 8519 * Changes classification for a call 8520 */ 8521 RECLASSIFY: "RECLASSIFY", 8522 /** 8523 * Updates data on a Call Dialog. 8524 */ 8525 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 8526 /** 8527 * Initiates a Recording on a Call Dialog. 8528 */ 8529 START_RECORDING : "START_RECORDING", 8530 /** 8531 * Sends DTMF (dialed digits) to a Call Dialog. 8532 */ 8533 DTMF : "SEND_DTMF", 8534 /** 8535 * Accepts a Dialog that is being Previewed. 8536 */ 8537 ACCEPT: "ACCEPT", 8538 /** 8539 * Rejects a Dialog. 8540 */ 8541 REJECT: "REJECT", 8542 /** 8543 * Closes a Dialog. 8544 */ 8545 CLOSE : "CLOSE", 8546 /** 8547 * @class Set of action constants for a Dialog. These should be used for 8548 * {@link finesse.restservices.Dialog#requestAction}. 8549 * @constructs 8550 */ 8551 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8552 }; 8553 8554 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 8555 /** 8556 * Indicates that the call is ringing at a device. 8557 */ 8558 ALERTING: "ALERTING", 8559 /** 8560 * Indicates that the phone is off the hook at a device. 8561 */ 8562 INITIATING: "INITIATING", 8563 /** 8564 * Indicates that the dialog has a least one active participant. 8565 */ 8566 ACTIVE: "ACTIVE", 8567 /** 8568 * Indicates that the dialog has no active participants. 8569 */ 8570 DROPPED: "DROPPED", 8571 /** 8572 * Indicates that the phone is dialing at the device. 8573 */ 8574 INITIATED: "INITIATED", 8575 /** 8576 * Indicates that the dialog has failed. 8577 * @see Dialog.ReasonStates 8578 */ 8579 FAILED: "FAILED", 8580 /** 8581 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 8582 */ 8583 ACCEPTED: "ACCEPTED", 8584 /** 8585 * @class Possible Dialog State constants. 8586 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 8587 * @constructs 8588 */ 8589 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8590 }; 8591 8592 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 8593 /** 8594 * Indicates that an incoming call is ringing on the device. 8595 */ 8596 ALERTING: "ALERTING", 8597 /** 8598 * Indicates that an outgoing call, not yet active, exists on the device. 8599 */ 8600 INITIATING: "INITIATING", 8601 /** 8602 * Indicates that the participant is active on the call. 8603 */ 8604 ACTIVE: "ACTIVE", 8605 /** 8606 * Indicates that the participant has dropped from the call. 8607 */ 8608 DROPPED: "DROPPED", 8609 /** 8610 * Indicates that the participant has held their connection to the call. 8611 */ 8612 HELD: "HELD", 8613 /** 8614 * Indicates that the phone is dialing at a device. 8615 */ 8616 INITIATED: "INITIATED", 8617 /** 8618 * Indicates that the call failed. 8619 * @see Dialog.ReasonStates 8620 */ 8621 FAILED: "FAILED", 8622 /** 8623 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 8624 */ 8625 WRAP_UP: "WRAP_UP", 8626 /** 8627 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 8628 */ 8629 ACCEPTED: "ACCEPTED", 8630 /** 8631 * @class Possible Dialog Participant State constants. 8632 * @constructs 8633 */ 8634 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8635 }; 8636 8637 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 8638 /** 8639 * Dialog was Busy. This will typically be for a Failed Dialog. 8640 */ 8641 BUSY: "BUSY", 8642 /** 8643 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 8644 */ 8645 BAD_DESTINATION: "BAD_DESTINATION", 8646 /** 8647 * All Other Reasons. This will typically be for a Failed Dialog. 8648 */ 8649 OTHER: "OTHER", 8650 /** 8651 * The Device Resource for the Dialog was not available. 8652 */ 8653 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 8654 /** 8655 * @class Possible dialog state reasons code constants. 8656 * @constructs 8657 */ 8658 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8659 }; 8660 8661 window.finesse = window.finesse || {}; 8662 window.finesse.restservices = window.finesse.restservices || {}; 8663 window.finesse.restservices.Dialog = Dialog; 8664 8665 8666 return Dialog; 8667 }); 8668 8669 /** 8670 * JavaScript representation of the Finesse Dialogs collection 8671 * object which contains a list of Dialog objects. 8672 * 8673 * @requires finesse.clientservices.ClientServices 8674 * @requires Class 8675 * @requires finesse.FinesseBase 8676 * @requires finesse.restservices.RestBase 8677 * @requires finesse.restservices.Dialog 8678 */ 8679 /** @private */ 8680 define('restservices/Dialogs',[ 8681 'restservices/RestCollectionBase', 8682 'restservices/Dialog' 8683 ], 8684 function (RestCollectionBase, Dialog) { 8685 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 8686 8687 /** 8688 * @class 8689 * JavaScript representation of a Dialogs collection object. Also exposes 8690 * methods to operate on the object against the server. 8691 * @augments finesse.restservices.RestCollectionBase 8692 * @constructs 8693 * @see finesse.restservices.Dialog 8694 * @example 8695 * _dialogs = _user.getDialogs( { 8696 * onCollectionAdd : _handleDialogAdd, 8697 * onCollectionDelete : _handleDialogDelete, 8698 * onLoad : _handleDialogsLoaded 8699 * }); 8700 * 8701 * _dialogCollection = _dialogs.getCollection(); 8702 * for (var dialogId in _dialogCollection) { 8703 * if (_dialogCollection.hasOwnProperty(dialogId)) { 8704 * _dialog = _dialogCollection[dialogId]; 8705 * etc... 8706 * } 8707 * } 8708 */ 8709 _fakeConstuctor: function () { 8710 /* This is here to hide the real init constructor from the public docs */ 8711 }, 8712 8713 /** 8714 * @private 8715 * @param {Object} options 8716 * An object with the following properties:<ul> 8717 * <li><b>id:</b> The id of the object being constructed</li> 8718 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8719 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8720 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8721 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8722 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8723 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8724 * <li><b>content:</b> {String} Raw string of response</li> 8725 * <li><b>object:</b> {Object} Parsed object of response</li> 8726 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8727 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8728 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8729 * </ul></li> 8730 * </ul></li> 8731 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8732 **/ 8733 init: function (options) { 8734 this._super(options); 8735 }, 8736 8737 /** 8738 * @private 8739 * Gets the REST class for the current object - this is the Dialogs class. 8740 */ 8741 getRestClass: function () { 8742 return Dialogs; 8743 }, 8744 8745 /** 8746 * @private 8747 * Gets the REST class for the objects that make up the collection. - this 8748 * is the Dialog class. 8749 */ 8750 getRestItemClass: function () { 8751 return Dialog; 8752 }, 8753 8754 /** 8755 * @private 8756 * Gets the REST type for the current object - this is a "Dialogs". 8757 */ 8758 getRestType: function () { 8759 return "Dialogs"; 8760 }, 8761 8762 /** 8763 * @private 8764 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 8765 */ 8766 getRestItemType: function () { 8767 return "Dialog"; 8768 }, 8769 8770 /** 8771 * @private 8772 * Override default to indicates that the collection doesn't support making 8773 * requests. 8774 */ 8775 supportsRequests: true, 8776 8777 /** 8778 * @private 8779 * Override default to indicates that the collection subscribes to its objects. 8780 */ 8781 supportsRestItemSubscriptions: true, 8782 8783 /** 8784 * The requestId reaper timeout in ms 8785 */ 8786 REQUESTID_REAPER_TIMEOUT: 5000, 8787 8788 /** 8789 * @private 8790 * Create a new Dialog in this collection 8791 * 8792 * @param {String} toAddress 8793 * The to address of the new Dialog 8794 * @param {String} fromAddress 8795 * The from address of the new Dialog 8796 * @param {finesse.interfaces.RequestHandlers} handlers 8797 * An object containing the (optional) handlers for the request. 8798 * @return {finesse.restservices.Dialogs} 8799 * This Dialogs object, to allow cascading. 8800 */ 8801 createNewCallDialog: function (toAddress, fromAddress, handlers) 8802 { 8803 var contentBody = {}; 8804 contentBody[this.getRestItemType()] = { 8805 "requestedAction": "MAKE_CALL", 8806 "toAddress": toAddress, 8807 "fromAddress": fromAddress 8808 }; 8809 8810 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8811 handlers = handlers || {}; 8812 8813 this.restRequest(this.getRestUrl(), { 8814 method: 'POST', 8815 success: handlers.success, 8816 error: handlers.error, 8817 content: contentBody 8818 }); 8819 return this; // Allow cascading 8820 }, 8821 8822 /** 8823 * @private 8824 * Create a new Dialog in this collection as a result of a requested action 8825 * 8826 * @param {String} toAddress 8827 * The to address of the new Dialog 8828 * @param {String} fromAddress 8829 * The from address of the new Dialog 8830 * @param {finesse.restservices.Dialog.Actions} actionType 8831 * The associated action to request for creating this new dialog 8832 * @param {finesse.interfaces.RequestHandlers} handlers 8833 * An object containing the (optional) handlers for the request. 8834 * @return {finesse.restservices.Dialogs} 8835 * This Dialogs object, to allow cascading. 8836 */ 8837 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 8838 { 8839 var contentBody = {}; 8840 this._isLoaded = true; 8841 8842 contentBody[this.getRestItemType()] = { 8843 "requestedAction": actionType, 8844 "toAddress": toAddress, 8845 "fromAddress": fromAddress 8846 }; 8847 8848 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8849 handlers = handlers || {}; 8850 8851 this.restRequest(this.getRestUrl(), { 8852 method: 'POST', 8853 success: handlers.success, 8854 error: handlers.error, 8855 content: contentBody 8856 }); 8857 return this; // Allow cascading 8858 }, 8859 8860 /** 8861 * @private 8862 * Create a new Dialog in this collection as a result of a requested action 8863 * @param {String} fromAddress 8864 * The from address of the new Dialog 8865 * @param {String} toAddress 8866 * The to address of the new Dialog 8867 * @param {finesse.restservices.Dialog.Actions} actionType 8868 * The associated action to request for creating this new dialog 8869 * @param {String} dialogUri 8870 * The associated uri of SUPERVISOR_MONITOR call 8871 * @param {finesse.interfaces.RequestHandlers} handlers 8872 * An object containing the (optional) handlers for the request. 8873 * @return {finesse.restservices.Dialogs} 8874 * This Dialogs object, to allow cascading. 8875 */ 8876 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 8877 this.isLoaded(); 8878 8879 var contentBody = {}; 8880 contentBody[this.getRestItemType()] = { 8881 "fromAddress": fromAddress, 8882 "toAddress": toAddress, 8883 "requestedAction": actionType, 8884 "associatedDialogUri": dialogURI 8885 8886 }; 8887 // (nonexistent) keys to be read as undefined 8888 handlers = handlers || {}; 8889 this.restRequest(this.getRestUrl(), { 8890 method: 'POST', 8891 success: handlers.success, 8892 error: handlers.error, 8893 content: contentBody 8894 }); 8895 return this; // Allow cascading 8896 }, 8897 8898 /** 8899 * Utility method to get the number of dialogs in this collection. 8900 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 8901 * 'SUPERVISOR_MONITOR' from the count. 8902 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 8903 * @return {Number} The number of dialogs in this collection. 8904 */ 8905 getDialogCount: function (excludeSilentMonitor) { 8906 this.isLoaded(); 8907 8908 var dialogId, count = 0; 8909 if (excludeSilentMonitor) { 8910 for (dialogId in this._collection) { 8911 if (this._collection.hasOwnProperty(dialogId)) { 8912 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 8913 count += 1; 8914 } 8915 } 8916 } 8917 8918 return count; 8919 } else { 8920 return this.length; 8921 } 8922 }, 8923 8924 /** 8925 * Utility method to create a closure containing the requestId and the Dialogs object so 8926 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8927 * @param {String} requestId The requestId of the event 8928 * @return {Function} The function to be executed by setTimeout 8929 */ 8930 _createRequestIdReaper: function (requestId) { 8931 var that = this; 8932 return function () { 8933 that._logger.log("Dialogs: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8934 delete that._pendingCallbacks[requestId]; 8935 }; 8936 }, 8937 8938 /** 8939 * Overriding implementation of the one in RestBase.js 8940 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8941 * @param {String} requestId The requestId of the event 8942 */ 8943 _postProcessUpdateStrategy: function (requestId) { 8944 this._logger.log("Dialogs: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8945 var callbacksObj = this._pendingCallbacks[requestId]; 8946 if (callbacksObj && !callbacksObj.used) { 8947 this._logger.log("Dialogs: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8948 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8949 callbacksObj.used = true; 8950 } 8951 } 8952 8953 }); 8954 8955 window.finesse = window.finesse || {}; 8956 window.finesse.restservices = window.finesse.restservices || {}; 8957 window.finesse.restservices.Dialogs = Dialogs; 8958 8959 return Dialogs; 8960 }); 8961 8962 /** 8963 * JavaScript representation of the Finesse ClientLog object 8964 * 8965 * @requires finesse.clientservices.ClientServices 8966 * @requires Class 8967 * @requires finesse.FinesseBase 8968 * @requires finesse.restservices.RestBase 8969 */ 8970 8971 /** The following comment is to prevent jslint errors about 8972 * using variables before they are defined. 8973 */ 8974 /** @private */ 8975 /*global finesse*/ 8976 8977 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 8978 8979 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 8980 /** 8981 * @private 8982 * Returns whether this object supports transport logs 8983 */ 8984 doNotLog : true, 8985 8986 explicitSubscription : true, 8987 8988 /** 8989 * @class 8990 * @private 8991 * JavaScript representation of a ClientLog object. Also exposes methods to operate 8992 * on the object against the server. 8993 * 8994 * @param {Object} options 8995 * An object with the following properties:<ul> 8996 * <li><b>id:</b> The id of the object being constructed</li> 8997 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8998 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8999 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9000 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9001 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9002 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9003 * <li><b>content:</b> {String} Raw string of response</li> 9004 * <li><b>object:</b> {Object} Parsed object of response</li> 9005 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9006 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9007 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9008 * </ul></li> 9009 * </ul></li> 9010 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9011 * @constructs 9012 * @augments finesse.restservices.RestBase 9013 **/ 9014 init: function (options) { 9015 this._super({ 9016 id: "", 9017 data: {clientLog : null}, 9018 onAdd: options.onAdd, 9019 onChange: options.onChange, 9020 onLoad: options.onLoad, 9021 onError: options.onError, 9022 parentObj: options.parentObj 9023 }); 9024 }, 9025 9026 /** 9027 * @private 9028 * Gets the REST class for the current object - this is the ClientLog object. 9029 */ 9030 getRestClass: function () { 9031 return ClientLog; 9032 }, 9033 9034 /** 9035 * @private 9036 * Gets the REST type for the current object - this is a "ClientLog". 9037 */ 9038 getRestType: function () 9039 { 9040 return "ClientLog"; 9041 }, 9042 9043 /** 9044 * @private 9045 * Gets the node path for the current object 9046 * @returns {String} The node path 9047 */ 9048 getXMPPNodePath: function () { 9049 return this.getRestUrl(); 9050 }, 9051 9052 /** 9053 * @private 9054 * Utility method to fetch this object from the server, however we 9055 * override it for ClientLog to not do anything because GET is not supported 9056 * for ClientLog object. 9057 */ 9058 _doGET: function (handlers) { 9059 return; 9060 }, 9061 9062 /** 9063 * @private 9064 * Invoke a request to the server given a content body and handlers. 9065 * 9066 * @param {Object} contentBody 9067 * A JS object containing the body of the action request. 9068 * @param {Object} handlers 9069 * An object containing the following (optional) handlers for the request:<ul> 9070 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 9071 * response object as its only parameter:<ul> 9072 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9073 * <li><b>content:</b> {String} Raw string of response</li> 9074 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 9075 * <li>A error callback function for an unsuccessful request to be invoked with the 9076 * error response object as its only parameter:<ul> 9077 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9078 * <li><b>content:</b> {String} Raw string of response</li> 9079 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 9080 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9081 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9082 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9083 * </ul></li> 9084 * </ul> 9085 */ 9086 sendLogs: function (contentBody, handlers) { 9087 // Protect against null dereferencing of options allowing its 9088 // (nonexistent) keys to be read as undefined 9089 handlers = handlers || {}; 9090 9091 this.restRequest(this.getRestUrl(), { 9092 method: 'POST', 9093 //success: handlers.success, 9094 error: handlers.error, 9095 content: contentBody 9096 }); 9097 } 9098 }); 9099 9100 window.finesse = window.finesse || {}; 9101 window.finesse.restservices = window.finesse.restservices || {}; 9102 window.finesse.restservices.ClientLog = ClientLog; 9103 9104 return ClientLog; 9105 }); 9106 9107 /** 9108 * JavaScript representation of the Finesse Queue object 9109 * @requires finesse.clientservices.ClientServices 9110 * @requires Class 9111 * @requires finesse.FinesseBase 9112 * @requires finesse.restservices.RestBase 9113 */ 9114 9115 /** @private */ 9116 define('restservices/Queue',[ 9117 'restservices/RestBase', 9118 'utilities/Utilities' 9119 ], 9120 function (RestBase, Utilities) { 9121 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 9122 9123 /** 9124 * @class 9125 * A Queue is a list of Contacts available to a User for quick dial. 9126 * 9127 * @augments finesse.restservices.RestBase 9128 * @constructs 9129 */ 9130 _fakeConstuctor: function () { 9131 /* This is here to hide the real init constructor from the public docs */ 9132 }, 9133 9134 /** 9135 * @private 9136 * JavaScript representation of a Queue object. Also exposes methods to operate 9137 * on the object against the server. 9138 * 9139 * @constructor 9140 * @param {String} id 9141 * Not required... 9142 * @param {Object} callbacks 9143 * An object containing callbacks for instantiation and runtime 9144 * @param {Function} callbacks.onLoad(this) 9145 * Callback to invoke upon successful instantiation 9146 * @param {Function} callbacks.onLoadError(rsp) 9147 * Callback to invoke on instantiation REST request error 9148 * as passed by finesse.clientservices.ClientServices.ajax() 9149 * { 9150 * status: {Number} The HTTP status code returned 9151 * content: {String} Raw string of response 9152 * object: {Object} Parsed object of response 9153 * error: {Object} Wrapped exception that was caught 9154 * error.errorType: {String} Type of error that was caught 9155 * error.errorMessage: {String} Message associated with error 9156 * } 9157 * @param {Function} callbacks.onChange(this) 9158 * Callback to invoke upon successful update 9159 * @param {Function} callbacks.onError(rsp) 9160 * Callback to invoke on update error (refresh or event) 9161 * as passed by finesse.clientservices.ClientServices.ajax() 9162 * { 9163 * status: {Number} The HTTP status code returned 9164 * content: {String} Raw string of response 9165 * object: {Object} Parsed object of response 9166 * error: {Object} Wrapped exception that was caught 9167 * error.errorType: {String} Type of error that was caught 9168 * error.errorMessage: {String} Message associated with error 9169 * } 9170 * 9171 */ 9172 init: function (id, callbacks, restObj) { 9173 this._super(id, callbacks, restObj); 9174 }, 9175 9176 /** 9177 * @private 9178 * Gets the REST class for the current object - this is the Queue object. 9179 */ 9180 getRestClass: function () { 9181 return Queue; 9182 }, 9183 9184 /** 9185 * @private 9186 * Gets the REST type for the current object - this is a "Queue". 9187 */ 9188 getRestType: function () { 9189 return "Queue"; 9190 }, 9191 9192 /** 9193 * @private 9194 * Returns whether this object supports subscriptions 9195 */ 9196 supportsSubscriptions: function () { 9197 return true; 9198 }, 9199 9200 /** 9201 * @private 9202 * Specifies whether this object's subscriptions need to be explicitly requested 9203 */ 9204 explicitSubscription: true, 9205 9206 /** 9207 * @private 9208 * Gets the node path for the current object - this is the team Users node 9209 * @returns {String} The node path 9210 */ 9211 getXMPPNodePath: function () { 9212 return this.getRestUrl(); 9213 }, 9214 9215 /** 9216 * Getter for the queue id 9217 * @returns {String} 9218 * The id of the Queue 9219 */ 9220 getId: function () { 9221 this.isLoaded(); 9222 return this._id; 9223 }, 9224 9225 /** 9226 * Getter for the queue name 9227 * @returns {String} 9228 * The name of the Queue 9229 */ 9230 getName: function () { 9231 this.isLoaded(); 9232 return this.getData().name; 9233 }, 9234 9235 /** 9236 * Getter for the queue statistics. 9237 * Supported statistics include:<br> 9238 * - callsInQueue<br> 9239 * - startTimeOfLongestCallInQueue<br> 9240 * <br> 9241 * These statistics can be accessed via dot notation:<br> 9242 * i.e.: getStatistics().callsInQueue 9243 * @returns {Object} 9244 * The Object with different statistics as properties. 9245 */ 9246 getStatistics: function () { 9247 this.isLoaded(); 9248 return this.getData().statistics; 9249 }, 9250 9251 /** 9252 * Parses a uriString to retrieve the id portion 9253 * @param {String} uriString 9254 * @return {String} id 9255 */ 9256 _parseIdFromUriString : function (uriString) { 9257 return Utilities.getId(uriString); 9258 } 9259 9260 }); 9261 9262 window.finesse = window.finesse || {}; 9263 window.finesse.restservices = window.finesse.restservices || {}; 9264 window.finesse.restservices.Queue = Queue; 9265 9266 return Queue; 9267 }); 9268 9269 /** 9270 * JavaScript representation of the Finesse Queues collection 9271 * object which contains a list of Queue objects. 9272 * @requires finesse.clientservices.ClientServices 9273 * @requires Class 9274 * @requires finesse.FinesseBase 9275 * @requires finesse.restservices.RestBase 9276 * @requires finesse.restservices.RestCollectionBase 9277 */ 9278 9279 /** 9280 * @class 9281 * JavaScript representation of a Queues collection object. 9282 * 9283 * @constructor 9284 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 9285 */ 9286 9287 /** @private */ 9288 define('restservices/Queues',[ 9289 'restservices/RestCollectionBase', 9290 'restservices/Queue' 9291 ], 9292 function (RestCollectionBase, Queue) { 9293 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 9294 9295 /** 9296 * @class 9297 * JavaScript representation of a Queues collection object. 9298 * @augments finesse.restservices.RestCollectionBase 9299 * @constructs 9300 * @see finesse.restservices.Queue 9301 * @example 9302 * _queues = _user.getQueues( { 9303 * onCollectionAdd : _handleQueueAdd, 9304 * onCollectionDelete : _handleQueueDelete, 9305 * onLoad : _handleQueuesLoaded 9306 * }); 9307 * 9308 * _queueCollection = _queues.getCollection(); 9309 * for (var queueId in _queueCollection) { 9310 * if (_queueCollection.hasOwnProperty(queueId)) { 9311 * _queue = _queueCollection[queueId]; 9312 * etc... 9313 * } 9314 * } 9315 */ 9316 _fakeConstuctor: function () { 9317 /* This is here to hide the real init constructor from the public docs */ 9318 }, 9319 9320 /** 9321 * @private 9322 * JavaScript representation of a Queues object. Also exposes 9323 * methods to operate on the object against the server. 9324 * 9325 * @param {Object} options 9326 * An object with the following properties:<ul> 9327 * <li><b>id:</b> The id of the object being constructed</li> 9328 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9329 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9330 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9331 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9332 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9333 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9334 * <li><b>content:</b> {String} Raw string of response</li> 9335 * <li><b>object:</b> {Object} Parsed object of response</li> 9336 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9337 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9338 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9339 * </ul></li> 9340 * </ul></li> 9341 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9342 **/ 9343 init: function (options) { 9344 this._super(options); 9345 }, 9346 9347 /** 9348 * @private 9349 * Gets xmpp node path. 9350 */ 9351 getXMPPNodePath: function () { 9352 return this.getRestUrl(); 9353 }, 9354 9355 /** 9356 * @private 9357 * Gets the REST class for the current object - this is the Queues class. 9358 */ 9359 getRestClass: function () { 9360 return Queues; 9361 }, 9362 9363 /** 9364 * @private 9365 * Gets the REST class for the objects that make up the collection. - this 9366 * is the Queue class. 9367 */ 9368 getRestItemClass: function () { 9369 return Queue; 9370 }, 9371 9372 /** 9373 * @private 9374 * Gets the REST type for the current object - this is a "Queues". 9375 */ 9376 getRestType: function () { 9377 return "Queues"; 9378 }, 9379 9380 /** 9381 * @private 9382 * Gets the REST type for the objects that make up the collection - this is "Queue". 9383 */ 9384 getRestItemType: function () { 9385 return "Queue"; 9386 }, 9387 9388 explicitSubscription: true, 9389 9390 handlesItemRefresh: true 9391 }); 9392 9393 window.finesse = window.finesse || {}; 9394 window.finesse.restservices = window.finesse.restservices || {}; 9395 window.finesse.restservices.Queues = Queues; 9396 9397 return Queues; 9398 }); 9399 9400 /** 9401 * JavaScript representation of the Finesse WrapUpReason object. 9402 * 9403 * @requires finesse.clientservices.ClientServices 9404 * @requires Class 9405 * @requires finesse.FinesseBase 9406 * @requires finesse.restservices.RestBase 9407 */ 9408 9409 /** @private */ 9410 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 9411 9412 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 9413 9414 /** 9415 * @class 9416 * A WrapUpReason is a code and description identifying a particular reason that a 9417 * User is in WORK (WrapUp) mode. 9418 * 9419 * @augments finesse.restservices.RestBase 9420 * @see finesse.restservices.User 9421 * @see finesse.restservices.User.States#WORK 9422 * @constructs 9423 */ 9424 _fakeConstuctor: function () { 9425 /* This is here to hide the real init constructor from the public docs */ 9426 }, 9427 9428 /** 9429 * @private 9430 * JavaScript representation of a WrapUpReason object. Also exposes 9431 * methods to operate on the object against the server. 9432 * 9433 * @param {Object} options 9434 * An object with the following properties:<ul> 9435 * <li><b>id:</b> The id of the object being constructed</li> 9436 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9437 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9438 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9439 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9440 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9441 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9442 * <li><b>content:</b> {String} Raw string of response</li> 9443 * <li><b>object:</b> {Object} Parsed object of response</li> 9444 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9445 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9446 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9447 * </ul></li> 9448 * </ul></li> 9449 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9450 **/ 9451 init: function (options) { 9452 this._super(options); 9453 }, 9454 9455 /** 9456 * @private 9457 * Gets the REST class for the current object - this is the WrapUpReason class. 9458 * @returns {Object} The WrapUpReason class. 9459 */ 9460 getRestClass: function () { 9461 return WrapUpReason; 9462 }, 9463 9464 /** 9465 * @private 9466 * Gets the REST type for the current object - this is a "WrapUpReason". 9467 * @returns {String} The WrapUpReason string. 9468 */ 9469 getRestType: function () { 9470 return "WrapUpReason"; 9471 }, 9472 9473 /** 9474 * @private 9475 * Gets the REST type for the current object - this is a "WrapUpReasons". 9476 * @returns {String} The WrapUpReasons string. 9477 */ 9478 getParentRestType: function () { 9479 return "WrapUpReasons"; 9480 }, 9481 9482 /** 9483 * @private 9484 * Override default to indicate that this object doesn't support making 9485 * requests. 9486 */ 9487 supportsRequests: false, 9488 9489 /** 9490 * @private 9491 * Override default to indicate that this object doesn't support subscriptions. 9492 */ 9493 supportsSubscriptions: false, 9494 9495 /** 9496 * Getter for the label. 9497 * @returns {String} The label. 9498 */ 9499 getLabel: function () { 9500 this.isLoaded(); 9501 return this.getData().label; 9502 }, 9503 9504 /** 9505 * @private 9506 * Getter for the forAll flag. 9507 * @returns {Boolean} True if global. 9508 */ 9509 getForAll: function () { 9510 this.isLoaded(); 9511 return this.getData().forAll; 9512 }, 9513 9514 /** 9515 * @private 9516 * Getter for the Uri value. 9517 * @returns {String} The Uri. 9518 */ 9519 getUri: function () { 9520 this.isLoaded(); 9521 return this.getData().uri; 9522 } 9523 }); 9524 9525 window.finesse = window.finesse || {}; 9526 window.finesse.restservices = window.finesse.restservices || {}; 9527 window.finesse.restservices.WrapUpReason = WrapUpReason; 9528 9529 return WrapUpReason; 9530 }); 9531 9532 /** 9533 * JavaScript representation of the Finesse WrapUpReasons collection 9534 * object which contains a list of WrapUpReason objects. 9535 * 9536 * @requires finesse.clientservices.ClientServices 9537 * @requires Class 9538 * @requires finesse.FinesseBase 9539 * @requires finesse.restservices.RestBase 9540 * @requires finesse.restservices.Dialog 9541 * @requires finesse.restservices.RestCollectionBase 9542 */ 9543 9544 /** @private */ 9545 define('restservices/WrapUpReasons',[ 9546 'restservices/RestCollectionBase', 9547 'restservices/WrapUpReason' 9548 ], 9549 function (RestCollectionBase, WrapUpReason) { 9550 9551 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 9552 9553 /** 9554 * @class 9555 * JavaScript representation of a WrapUpReasons collection object. 9556 * @augments finesse.restservices.RestCollectionBase 9557 * @constructs 9558 * @see finesse.restservices.WrapUpReason 9559 * @example 9560 * _wrapUpReasons = _user.getWrapUpReasons ( { 9561 * onCollectionAdd : _handleWrapUpReasonAdd, 9562 * onCollectionDelete : _handleWrapUpReasonDelete, 9563 * onLoad : _handleWrapUpReasonsLoaded 9564 * }); 9565 * 9566 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 9567 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 9568 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 9569 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 9570 * etc... 9571 * } 9572 * } 9573 */ 9574 _fakeConstuctor: function () { 9575 /* This is here to hide the real init constructor from the public docs */ 9576 }, 9577 9578 /** 9579 * @private 9580 * JavaScript representation of a WrapUpReasons collection object. Also exposes 9581 * methods to operate on the object against the server. 9582 * 9583 * @param {Object} options 9584 * An object with the following properties:<ul> 9585 * <li><b>id:</b> The id of the object being constructed</li> 9586 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9587 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9588 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9589 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9590 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9591 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9592 * <li><b>content:</b> {String} Raw string of response</li> 9593 * <li><b>object:</b> {Object} Parsed object of response</li> 9594 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9595 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9596 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9597 * </ul></li> 9598 * </ul></li> 9599 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9600 **/ 9601 init: function (options) { 9602 this._super(options); 9603 }, 9604 9605 /** 9606 * @private 9607 * Gets the REST class for the current object - this is the WrapUpReasons class. 9608 */ 9609 getRestClass: function () { 9610 return WrapUpReasons; 9611 }, 9612 9613 /** 9614 * @private 9615 * Gets the REST class for the objects that make up the collection. - this 9616 * is the WrapUpReason class. 9617 */ 9618 getRestItemClass: function () { 9619 return WrapUpReason; 9620 }, 9621 9622 /** 9623 * @private 9624 * Gets the REST type for the current object - this is a "WrapUpReasons". 9625 */ 9626 getRestType: function () { 9627 return "WrapUpReasons"; 9628 }, 9629 9630 /** 9631 * @private 9632 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 9633 */ 9634 getRestItemType: function () { 9635 return "WrapUpReason"; 9636 }, 9637 9638 /** 9639 * @private 9640 * Override default to indicates that the collection supports making 9641 * requests. 9642 */ 9643 supportsRequests: true, 9644 9645 /** 9646 * @private 9647 * Override default to indicate that this object doesn't support subscriptions. 9648 */ 9649 supportsRestItemSubscriptions: false, 9650 9651 /** 9652 * @private 9653 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 9654 * 9655 * @returns {finesse.restservices.WrapUpReasons} 9656 * This ReadyReasonCodes object to allow cascading. 9657 */ 9658 get: function () { 9659 // set loaded to false so it will rebuild the collection after the get 9660 this._loaded = false; 9661 // reset collection 9662 this._collection = {}; 9663 // perform get 9664 this._synchronize(); 9665 return this; 9666 } 9667 9668 }); 9669 9670 window.finesse = window.finesse || {}; 9671 window.finesse.restservices = window.finesse.restservices || {}; 9672 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 9673 9674 return WrapUpReasons; 9675 }); 9676 9677 /** 9678 * JavaScript representation of the Finesse Contact object. 9679 * @requires finesse.clientservices.ClientServices 9680 * @requires Class 9681 * @requires finesse.FinesseBase 9682 * @requires finesse.restservices.RestBase 9683 */ 9684 /** @private */ 9685 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 9686 9687 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 9688 9689 /** 9690 * @class 9691 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 9692 * a Phone Number, and a Description. 9693 * 9694 * @augments finesse.restservices.RestBase 9695 * @see finesse.restservices.PhoneBook 9696 * @constructs 9697 */ 9698 _fakeConstuctor: function () { 9699 /* This is here to hide the real init constructor from the public docs */ 9700 }, 9701 9702 /** 9703 * @private 9704 * @param {Object} options 9705 * An object with the following properties:<ul> 9706 * <li><b>id:</b> The id of the object being constructed</li> 9707 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9708 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9709 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9710 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9711 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9712 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9713 * <li><b>content:</b> {String} Raw string of response</li> 9714 * <li><b>object:</b> {Object} Parsed object of response</li> 9715 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9716 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9717 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9718 * </ul></li> 9719 * </ul></li> 9720 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9721 **/ 9722 init: function (options) { 9723 this._super(options); 9724 }, 9725 9726 /** 9727 * @private 9728 * Gets the REST class for the current object - this is the Contact class. 9729 * @returns {Object} The Contact class. 9730 */ 9731 getRestClass: function () { 9732 return Contact; 9733 }, 9734 9735 /** 9736 * @private 9737 * Gets the REST type for the current object - this is a "Contact". 9738 * @returns {String} The Contact string. 9739 */ 9740 getRestType: function () { 9741 return "Contact"; 9742 }, 9743 9744 /** 9745 * @private 9746 * Override default to indicate that this object doesn't support making 9747 * requests. 9748 */ 9749 supportsRequests: false, 9750 9751 /** 9752 * @private 9753 * Override default to indicate that this object doesn't support subscriptions. 9754 */ 9755 supportsSubscriptions: false, 9756 9757 /** 9758 * Getter for the firstName. 9759 * @returns {String} The firstName. 9760 */ 9761 getFirstName: function () { 9762 this.isLoaded(); 9763 return this.getData().firstName; 9764 }, 9765 9766 /** 9767 * Getter for the lastName. 9768 * @returns {String} The lastName. 9769 */ 9770 getLastName: function () { 9771 this.isLoaded(); 9772 return this.getData().lastName; 9773 }, 9774 9775 /** 9776 * Getter for the phoneNumber. 9777 * @returns {String} The phoneNumber. 9778 */ 9779 getPhoneNumber: function () { 9780 this.isLoaded(); 9781 return this.getData().phoneNumber; 9782 }, 9783 9784 /** 9785 * Getter for the description. 9786 * @returns {String} The description. 9787 */ 9788 getDescription: function () { 9789 this.isLoaded(); 9790 return this.getData().description; 9791 }, 9792 9793 /** @private */ 9794 createPutSuccessHandler: function(contact, contentBody, successHandler){ 9795 return function (rsp) { 9796 // Update internal structure based on response. Here we 9797 // inject the contentBody from the PUT request into the 9798 // rsp.object element to mimic a GET as a way to take 9799 // advantage of the existing _processResponse method. 9800 rsp.object = contentBody; 9801 contact._processResponse(rsp); 9802 9803 //Remove the injected Contact object before cascading response 9804 rsp.object = {}; 9805 9806 //cascade response back to consumer's response handler 9807 successHandler(rsp); 9808 }; 9809 }, 9810 9811 /** @private */ 9812 createPostSuccessHandler: function (contact, contentBody, successHandler) { 9813 return function (rsp) { 9814 rsp.object = contentBody; 9815 contact._processResponse(rsp); 9816 9817 //Remove the injected Contact object before cascading response 9818 rsp.object = {}; 9819 9820 //cascade response back to consumer's response handler 9821 successHandler(rsp); 9822 }; 9823 }, 9824 9825 /** 9826 * Add 9827 * @private 9828 */ 9829 add: function (newValues, handlers) { 9830 // this.isLoaded(); 9831 var contentBody = {}; 9832 9833 contentBody[this.getRestType()] = { 9834 "firstName": newValues.firstName, 9835 "lastName": newValues.lastName, 9836 "phoneNumber": newValues.phoneNumber, 9837 "description": newValues.description 9838 }; 9839 9840 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9841 handlers = handlers || {}; 9842 9843 this.restRequest(this.getRestUrl(), { 9844 method: 'POST', 9845 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9846 error: handlers.error, 9847 content: contentBody 9848 }); 9849 9850 return this; // Allow cascading 9851 }, 9852 9853 /** 9854 * Update 9855 * @private 9856 */ 9857 update: function (newValues, handlers) { 9858 this.isLoaded(); 9859 var contentBody = {}; 9860 9861 contentBody[this.getRestType()] = { 9862 "uri": this.getId(), 9863 "firstName": newValues.firstName, 9864 "lastName": newValues.lastName, 9865 "phoneNumber": newValues.phoneNumber, 9866 "description": newValues.description 9867 }; 9868 9869 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9870 handlers = handlers || {}; 9871 9872 this.restRequest(this.getRestUrl(), { 9873 method: 'PUT', 9874 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9875 error: handlers.error, 9876 content: contentBody 9877 }); 9878 9879 return this; // Allow cascading 9880 }, 9881 9882 9883 /** 9884 * Delete 9885 * @private 9886 */ 9887 "delete": function ( handlers) { 9888 this.isLoaded(); 9889 9890 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9891 handlers = handlers || {}; 9892 9893 this.restRequest(this.getRestUrl(), { 9894 method: 'DELETE', 9895 success: this.createPutSuccessHandler(this, {}, handlers.success), 9896 error: handlers.error, 9897 content: undefined 9898 }); 9899 9900 return this; // Allow cascading 9901 } 9902 }); 9903 9904 window.finesse = window.finesse || {}; 9905 window.finesse.restservices = window.finesse.restservices || {}; 9906 window.finesse.restservices.Contact = Contact; 9907 9908 return Contact; 9909 }); 9910 9911 /** 9912 * JavaScript representation of the Finesse Contacts collection 9913 * object which contains a list of Contact objects. 9914 * 9915 * @requires finesse.clientservices.ClientServices 9916 * @requires Class 9917 * @requires finesse.FinesseBase 9918 * @requires finesse.restservices.RestBase 9919 * @requires finesse.restservices.Dialog 9920 * @requires finesse.restservices.RestCollectionBase 9921 */ 9922 /** @private */ 9923 define('restservices/Contacts',[ 9924 'restservices/RestCollectionBase', 9925 'restservices/Contact' 9926 ], 9927 function (RestCollectionBase, Contact) { 9928 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 9929 9930 /** 9931 * @class 9932 * JavaScript representation of a Contacts collection object. Also exposes 9933 * methods to operate on the object against the server. 9934 * @augments finesse.restservices.RestCollectionBase 9935 * @constructs 9936 * @see finesse.restservices.Contact 9937 * @see finesse.restservices.PhoneBook 9938 * @example 9939 * _contacts = _phonebook.getContacts( { 9940 * onCollectionAdd : _handleContactAdd, 9941 * onCollectionDelete : _handleContactDelete, 9942 * onLoad : _handleContactsLoaded 9943 * }); 9944 * 9945 * _contactCollection = _contacts.getCollection(); 9946 * for (var contactId in _contactCollection) { 9947 * if (_contactCollection.hasOwnProperty(contactId)) { 9948 * _contact = _contactCollection[contactId]; 9949 * etc... 9950 * } 9951 * } 9952 */ 9953 _fakeConstuctor: function () { 9954 /* This is here to hide the real init constructor from the public docs */ 9955 }, 9956 9957 /** 9958 * @private 9959 * JavaScript representation of a Contacts collection object. Also exposes 9960 * methods to operate on the object against the server. 9961 * 9962 * @param {Object} options 9963 * An object with the following properties:<ul> 9964 * <li><b>id:</b> The id of the object being constructed</li> 9965 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9966 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9967 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9968 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9969 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9970 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9971 * <li><b>content:</b> {String} Raw string of response</li> 9972 * <li><b>object:</b> {Object} Parsed object of response</li> 9973 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9974 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9975 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9976 * </ul></li> 9977 * </ul></li> 9978 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9979 **/ 9980 init: function (options) { 9981 this._super(options); 9982 }, 9983 9984 /** 9985 * @private 9986 * Gets the REST class for the current object - this is the Contacts class. 9987 */ 9988 getRestClass: function () { 9989 return Contacts; 9990 }, 9991 9992 /** 9993 * @private 9994 * Gets the REST class for the objects that make up the collection. - this 9995 * is the Contact class. 9996 */ 9997 getRestItemClass: function () { 9998 return Contact; 9999 }, 10000 10001 /** 10002 * @private 10003 * Gets the REST type for the current object - this is a "Contacts". 10004 */ 10005 getRestType: function () { 10006 return "Contacts"; 10007 }, 10008 10009 /** 10010 * @private 10011 * Gets the REST type for the objects that make up the collection - this is "Contacts". 10012 */ 10013 getRestItemType: function () { 10014 return "Contact"; 10015 }, 10016 10017 /** 10018 * @private 10019 * Override default to indicates that the collection supports making 10020 * requests. 10021 */ 10022 supportsRequests: true, 10023 10024 /** 10025 * @private 10026 * Override default to indicates that the collection subscribes to its objects. 10027 */ 10028 supportsRestItemSubscriptions: false, 10029 10030 /** 10031 * @private 10032 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 10033 * 10034 * @returns {finesse.restservices.Contacts} 10035 * This Contacts object, to allow cascading. 10036 */ 10037 get: function () { 10038 // set loaded to false so it will rebuild the collection after the get 10039 this._loaded = false; 10040 // reset collection 10041 this._collection = {}; 10042 // perform get 10043 this._synchronize(); 10044 return this; 10045 } 10046 10047 }); 10048 10049 window.finesse = window.finesse || {}; 10050 window.finesse.restservices = window.finesse.restservices || {}; 10051 window.finesse.restservices.Contacts = Contacts; 10052 10053 10054 return Contacts; 10055 }); 10056 10057 /** 10058 * JavaScript representation of the Finesse PhoneBook object. 10059 * 10060 * @requires finesse.clientservices.ClientServices 10061 * @requires Class 10062 * @requires finesse.FinesseBase 10063 * @requires finesse.restservices.RestBase 10064 */ 10065 10066 /** @private */ 10067 define('restservices/PhoneBook',[ 10068 'restservices/RestBase', 10069 'restservices/Contacts' 10070 ], 10071 function (RestBase, Contacts) { 10072 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 10073 10074 _contacts: null, 10075 10076 /** 10077 * @class 10078 * A PhoneBook is a list of Contacts available to a User for quick dial. 10079 * 10080 * @augments finesse.restservices.RestBase 10081 * @see finesse.restservices.Contacts 10082 * @constructs 10083 */ 10084 _fakeConstuctor: function () { 10085 /* This is here to hide the real init constructor from the public docs */ 10086 }, 10087 10088 /** 10089 * @private 10090 * JavaScript representation of a PhoneBook object. Also exposes 10091 * methods to operate on the object against the server. 10092 * 10093 * @param {Object} options 10094 * An object with the following properties:<ul> 10095 * <li><b>id:</b> The id of the object being constructed</li> 10096 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10097 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10098 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10099 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10100 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10101 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10102 * <li><b>content:</b> {String} Raw string of response</li> 10103 * <li><b>object:</b> {Object} Parsed object of response</li> 10104 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10105 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10106 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10107 * </ul></li> 10108 * </ul></li> 10109 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10110 **/ 10111 init: function (options) { 10112 this._super(options); 10113 }, 10114 10115 /** 10116 * @private 10117 * Gets the REST class for the current object - this is the PhoneBook class. 10118 * @returns {Object} The PhoneBook class. 10119 */ 10120 getRestClass: function () { 10121 return PhoneBook; 10122 }, 10123 10124 /** 10125 * @private 10126 * Gets the REST type for the current object - this is a "PhoneBook". 10127 * @returns {String} The PhoneBook string. 10128 */ 10129 getRestType: function () { 10130 return "PhoneBook"; 10131 }, 10132 10133 /** 10134 * @private 10135 * Override default to indicate that this object doesn't support making 10136 * requests. 10137 */ 10138 supportsRequests: false, 10139 10140 /** 10141 * @private 10142 * Override default to indicate that this object doesn't support subscriptions. 10143 */ 10144 supportsSubscriptions: false, 10145 10146 /** 10147 * Getter for the name of the Phone Book. 10148 * @returns {String} The name. 10149 */ 10150 getName: function () { 10151 this.isLoaded(); 10152 return this.getData().name; 10153 }, 10154 10155 /** 10156 * Getter for the type flag. 10157 * @returns {String} The type. 10158 */ 10159 getType: function () { 10160 this.isLoaded(); 10161 return this.getData().type; 10162 }, 10163 10164 /** 10165 * @private 10166 * Getter for the Uri value. 10167 * @returns {String} The Uri. 10168 */ 10169 getUri: function () { 10170 this.isLoaded(); 10171 return this.getData().uri; 10172 }, 10173 10174 /** 10175 * Getter for a Contacts collection object that is associated with PhoneBook. 10176 * @param {finesse.interfaces.RequestHandlers} handlers 10177 * An object containing the handlers for the request 10178 * @returns {finesse.restservices.Contacts} 10179 * A Contacts collection object. 10180 */ 10181 getContacts: function (callbacks) { 10182 var options = callbacks || {}; 10183 options.parentObj = this; 10184 this.isLoaded(); 10185 10186 if (this._contacts === null) { 10187 this._contacts = new Contacts(options); 10188 } 10189 10190 return this._contacts; 10191 }, 10192 10193 /** 10194 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 10195 * @returns {String} uri to contacts 10196 * or {finesse.restservices.Contacts} collection 10197 */ 10198 getEmbeddedContacts: function(){ 10199 this.isLoaded(); 10200 return this.getData().contacts; 10201 }, 10202 10203 /** @private */ 10204 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 10205 return function (rsp) { 10206 // Update internal structure based on response. Here we 10207 // inject the contentBody from the PUT request into the 10208 // rsp.object element to mimic a GET as a way to take 10209 // advantage of the existing _processResponse method. 10210 rsp.object = contentBody; 10211 phonebook._processResponse(rsp); 10212 10213 //Remove the injected PhoneBook object before cascading response 10214 rsp.object = {}; 10215 10216 //cascade response back to consumer's response handler 10217 successHandler(rsp); 10218 }; 10219 }, 10220 10221 /** @private */ 10222 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 10223 return function (rsp) { 10224 rsp.object = contentBody; 10225 phonebook._processResponse(rsp); 10226 10227 //Remove the injected PhoneBook object before cascading response 10228 rsp.object = {}; 10229 10230 //cascade response back to consumer's response handler 10231 successHandler(rsp); 10232 }; 10233 }, 10234 10235 /** 10236 * @private 10237 * Add a PhoneBook. 10238 * @param {Object} newValues 10239 * @param {String} newValues.name Name of PhoneBook 10240 * @param {String} newValues.type Type of PhoneBook 10241 * @param {finesse.interfaces.RequestHandlers} handlers 10242 * An object containing the handlers for the request 10243 * @returns {finesse.restservices.PhoneBook} 10244 * This PhoneBook object, to allow cascading 10245 */ 10246 add: function (newValues, handlers) { 10247 // this.isLoaded(); 10248 var contentBody = {}; 10249 10250 contentBody[this.getRestType()] = { 10251 "name": newValues.name, 10252 "type": newValues.type 10253 }; 10254 10255 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10256 handlers = handlers || {}; 10257 10258 this.restRequest(this.getRestUrl(), { 10259 method: 'POST', 10260 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10261 error: handlers.error, 10262 content: contentBody 10263 }); 10264 10265 return this; // Allow cascading 10266 }, 10267 10268 /** 10269 * @private 10270 * Update a PhoneBook. 10271 * @param {Object} newValues 10272 * @param {String} newValues.name Name of PhoneBook 10273 * @param {String} newValues.type Type of PhoneBook 10274 * @param {finesse.interfaces.RequestHandlers} handlers 10275 * An object containing the handlers for the request 10276 * @returns {finesse.restservices.PhoneBook} 10277 * This PhoneBook object, to allow cascading 10278 */ 10279 update: function (newValues, handlers) { 10280 this.isLoaded(); 10281 var contentBody = {}; 10282 10283 contentBody[this.getRestType()] = { 10284 "uri": this.getId(), 10285 "name": newValues.name, 10286 "type": newValues.type 10287 }; 10288 10289 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10290 handlers = handlers || {}; 10291 10292 this.restRequest(this.getRestUrl(), { 10293 method: 'PUT', 10294 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10295 error: handlers.error, 10296 content: contentBody 10297 }); 10298 10299 return this; // Allow cascading 10300 }, 10301 10302 10303 /** 10304 * Delete a PhoneBook. 10305 * @param {finesse.interfaces.RequestHandlers} handlers 10306 * An object containing the handlers for the request 10307 * @returns {finesse.restservices.PhoneBook} 10308 * This PhoneBook object, to allow cascading 10309 */ 10310 "delete": function ( handlers) { 10311 this.isLoaded(); 10312 10313 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10314 handlers = handlers || {}; 10315 10316 this.restRequest(this.getRestUrl(), { 10317 method: 'DELETE', 10318 success: this.createPutSuccessHandler(this, {}, handlers.success), 10319 error: handlers.error, 10320 content: undefined 10321 }); 10322 10323 return this; // Allow cascading 10324 } 10325 10326 10327 10328 }); 10329 10330 window.finesse = window.finesse || {}; 10331 window.finesse.restservices = window.finesse.restservices || {}; 10332 window.finesse.restservices.PhoneBook = PhoneBook; 10333 10334 return PhoneBook; 10335 }); 10336 10337 /** 10338 * JavaScript representation of the Finesse PhoneBooks collection 10339 * object which contains a list of PhoneBook objects. 10340 * 10341 * @requires finesse.clientservices.ClientServices 10342 * @requires Class 10343 * @requires finesse.FinesseBase 10344 * @requires finesse.restservices.RestBase 10345 * @requires finesse.restservices.Dialog 10346 * @requires finesse.restservices.RestCollectionBase 10347 */ 10348 /** @private */ 10349 define('restservices/PhoneBooks',[ 10350 'restservices/RestCollectionBase', 10351 'restservices/PhoneBook' 10352 ], 10353 function (RestCollectionBase, PhoneBook) { 10354 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 10355 10356 /** 10357 * @class 10358 * JavaScript representation of a PhoneBooks collection object. 10359 * @augments finesse.restservices.RestCollectionBase 10360 * @constructs 10361 * @see finesse.restservices.PhoneBook 10362 * @see finesse.restservices.Contacts 10363 * @see finesse.restservices.Contact 10364 * @example 10365 * _phoneBooks = _user.getPhoneBooks( { 10366 * onCollectionAdd : _handlePhoneBookAdd, 10367 * onCollectionDelete : _handlePhoneBookDelete, 10368 * onLoad : _handlePhoneBooksLoaded 10369 * }); 10370 * 10371 * _phoneBookCollection = _phoneBooks.getCollection(); 10372 * for (var phoneBookId in _phoneBookCollection) { 10373 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 10374 * _phoneBook = _phoneBookCollection[phoneBookId]; 10375 * etc... 10376 * } 10377 * } 10378 */ 10379 _fakeConstuctor: function () { 10380 /* This is here to hide the real init constructor from the public docs */ 10381 }, 10382 10383 /** 10384 * @private 10385 * 10386 * @param {Object} options 10387 * An object with the following properties:<ul> 10388 * <li><b>id:</b> The id of the object being constructed</li> 10389 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10390 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10391 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10392 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10393 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10394 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10395 * <li><b>content:</b> {String} Raw string of response</li> 10396 * <li><b>object:</b> {Object} Parsed object of response</li> 10397 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10398 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10399 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10400 * </ul></li> 10401 * </ul></li> 10402 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10403 **/ 10404 init: function (options) { 10405 // Keep the REST response for PhoneBooks to check for 206 Partial Content. 10406 this.keepRestResponse = true; 10407 // Add in the Range header which is required for PhoneBooks API. 10408 this.extraHeaders = { "Range": "objects=1-1500" }; 10409 this._super(options); 10410 }, 10411 10412 /** 10413 * @private 10414 * Gets the REST class for the current object - this is the PhoneBooks class. 10415 */ 10416 getRestClass: function () { 10417 return PhoneBooks; 10418 }, 10419 10420 /** 10421 * @private 10422 * Gets the REST class for the objects that make up the collection. - this 10423 * is the PhoneBook class. 10424 */ 10425 getRestItemClass: function () { 10426 return PhoneBook; 10427 }, 10428 10429 /** 10430 * @private 10431 * Gets the REST type for the current object - this is a "PhoneBooks". 10432 */ 10433 getRestType: function () { 10434 return "PhoneBooks"; 10435 }, 10436 10437 /** 10438 * @private 10439 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 10440 */ 10441 getRestItemType: function () { 10442 return "PhoneBook"; 10443 }, 10444 10445 /** 10446 * @private 10447 * Override default to indicates that the collection supports making 10448 * requests. 10449 */ 10450 supportsRequests: true, 10451 10452 /** 10453 * @private 10454 * Override default to indicates that the collection subscribes to its objects. 10455 */ 10456 supportsRestItemSubscriptions: false, 10457 10458 /** 10459 * @private 10460 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 10461 * 10462 * @returns {finesse.restservices.PhoneBooks} 10463 * This PhoneBooks object, to allow cascading. 10464 */ 10465 get: function () { 10466 // set loaded to false so it will rebuild the collection after the get 10467 this._loaded = false; 10468 // reset collection 10469 this._collection = {}; 10470 // perform get 10471 this._synchronize(); 10472 return this; 10473 } 10474 10475 }); 10476 10477 window.finesse = window.finesse || {}; 10478 window.finesse.restservices = window.finesse.restservices || {}; 10479 window.finesse.restservices.PhoneBooks = PhoneBooks; 10480 10481 return PhoneBooks; 10482 }); 10483 10484 /** 10485 * JavaScript representation of the Finesse WorkflowAction object. 10486 * 10487 * @requires finesse.clientservices.ClientServices 10488 * @requires Class 10489 * @requires finesse.FinesseBase 10490 * @requires finesse.restservices.RestBase 10491 */ 10492 10493 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 10494 /*global define,finesse */ 10495 10496 /** @private */ 10497 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 10498 10499 var WorkflowAction = RestBase.extend({ 10500 10501 _contacts: null, 10502 10503 actionTypes: [ 10504 { 10505 name: 'BROWSER_POP', 10506 params: [ 10507 { 10508 name: 'windowName', 10509 type: 'text' 10510 }, 10511 { 10512 name: 'path', 10513 type: 'systemVariableSingleLineEditor' 10514 } 10515 ] 10516 }, 10517 { 10518 name: 'HTTP_REQUEST', 10519 params: [ 10520 { 10521 name: 'method', 10522 type: 'dropdown', 10523 values: ['POST', 'PUT'] 10524 }, 10525 { 10526 name: 'location', 10527 type: 'dropdown', 10528 values: ['FINESSE', 'OTHER'] 10529 }, 10530 { 10531 name: 'contentType', 10532 type: 'text' 10533 }, 10534 { 10535 name: 'path', 10536 type: 'systemVariableSingleLineEditor' 10537 }, 10538 { 10539 name: 'body', 10540 type: 'systemVariableMultiLineEditor' 10541 } 10542 ] 10543 } 10544 // more action type definitions here 10545 ], 10546 10547 /** 10548 * @class 10549 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 10550 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 10551 * 10552 * @augments finesse.restservices.RestBase 10553 * @see finesse.restservices.Workflow 10554 * @constructs 10555 */ 10556 _fakeConstuctor: function () { 10557 /* This is here to hide the real init constructor from the public docs */ 10558 }, 10559 10560 /** 10561 * @private 10562 * JavaScript representation of a WorkflowAction object. Also exposes 10563 * methods to operate on the object against the server. 10564 * 10565 * @param {Object} options 10566 * An object with the following properties:<ul> 10567 * <li><b>id:</b> The id of the object being constructed</li> 10568 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10569 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10570 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10571 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10572 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10573 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10574 * <li><b>content:</b> {String} Raw string of response</li> 10575 * <li><b>object:</b> {Object} Parsed object of response</li> 10576 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10577 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10578 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10579 * </ul></li> 10580 * </ul></li> 10581 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10582 **/ 10583 init: function (options) { 10584 this._super(options); 10585 }, 10586 10587 /** 10588 * @private 10589 * Gets the REST class for the current object - this is the WorkflowAction class. 10590 * @returns {Object} The WorkflowAction class. 10591 */ 10592 getRestClass: function () { 10593 return finesse.restservices.WorkflowAction; 10594 }, 10595 10596 /** 10597 * @private 10598 * Gets the REST type for the current object - this is a "WorkflowAction". 10599 * @returns {String} The WorkflowAction string. 10600 */ 10601 getRestType: function () { 10602 return "WorkflowAction"; 10603 }, 10604 10605 /** 10606 * @private 10607 * Override default to indicate that this object doesn't support making 10608 * requests. 10609 */ 10610 supportsRequests: false, 10611 10612 /** 10613 * @private 10614 * Override default to indicate that this object doesn't support subscriptions. 10615 */ 10616 supportsSubscriptions: false, 10617 10618 /** 10619 * Getter for the name. 10620 * @returns {String} The name. 10621 */ 10622 getName: function () { 10623 this.isLoaded(); 10624 return this.getData().name; 10625 }, 10626 10627 /** 10628 * Getter for the type flag. 10629 * @returns {String} The type. 10630 */ 10631 getType: function () { 10632 this.isLoaded(); 10633 return this.getData().type; 10634 }, 10635 10636 /** 10637 * @private 10638 * Getter for the Uri value. 10639 * @returns {String} The Uri. 10640 */ 10641 getUri: function () { 10642 this.isLoaded(); 10643 return this.getData().uri; 10644 }, 10645 10646 /** 10647 * @private 10648 * Getter for the handledBy value. 10649 * @returns {String} handledBy. 10650 */ 10651 getHandledBy: function () { 10652 this.isLoaded(); 10653 return this.getData().handledBy; 10654 }, 10655 10656 /** 10657 * Getter for the parameters. 10658 * @returns {Object} key = param name, value = param value 10659 */ 10660 getParams: function () { 10661 var map = {}, 10662 params = this.getData().params.Param, 10663 i, 10664 param; 10665 10666 for(i=0; i<params.length; i+=1){ 10667 param = params[i]; 10668 map[param.name] = param.value || ""; 10669 } 10670 10671 return map; 10672 }, 10673 10674 /** 10675 * Getter for the ActionVariables 10676 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 10677 */ 10678 getActionVariables: function() { 10679 var map = {}, 10680 actionVariablesParent = this.getData().actionVariables, 10681 actionVariables, 10682 i, 10683 actionVariable; 10684 10685 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 10686 return map; 10687 } 10688 actionVariables = actionVariablesParent.ActionVariable; 10689 10690 if(actionVariables.length > 0){ 10691 for(i=0; i<actionVariables.length; i+=1){ 10692 actionVariable = actionVariables[i]; 10693 // escape nulls to empty string 10694 actionVariable.name = actionVariable.name || ""; 10695 actionVariable.type = actionVariable.type || ""; 10696 actionVariable.node = actionVariable.node || ""; 10697 actionVariable.testValue = actionVariable.testValue || ""; 10698 map[actionVariable.name] = actionVariable; 10699 } 10700 } else { 10701 map[actionVariables.name] = actionVariables; 10702 } 10703 10704 return map; 10705 }, 10706 10707 /** @private */ 10708 createPutSuccessHandler: function(action, contentBody, successHandler){ 10709 return function (rsp) { 10710 // Update internal structure based on response. Here we 10711 // inject the contentBody from the PUT request into the 10712 // rsp.object element to mimic a GET as a way to take 10713 // advantage of the existing _processResponse method. 10714 rsp.object = contentBody; 10715 action._processResponse(rsp); 10716 10717 //Remove the injected WorkflowAction object before cascading response 10718 rsp.object = {}; 10719 10720 //cascade response back to consumer's response handler 10721 successHandler(rsp); 10722 }; 10723 }, 10724 10725 /** @private */ 10726 createPostSuccessHandler: function (action, contentBody, successHandler) { 10727 return function (rsp) { 10728 rsp.object = contentBody; 10729 action._processResponse(rsp); 10730 10731 //Remove the injected WorkflowAction object before cascading response 10732 rsp.object = {}; 10733 10734 //cascade response back to consumer's response handler 10735 successHandler(rsp); 10736 }; 10737 }, 10738 10739 /** 10740 * @private 10741 * Build params array out of all the values coming into add or update methods 10742 * paramMap is a map of params.. we need to translate it into an array of Param objects 10743 * where path and windowName are params for the BROWSER_POP type 10744 */ 10745 buildParamsForRest: function(paramMap){ 10746 var params = {"Param": []}, 10747 i; 10748 for(i in paramMap){ 10749 if(paramMap.hasOwnProperty(i)){ 10750 params.Param.push({name: i, value: paramMap[i]}); 10751 } 10752 } 10753 return params; 10754 }, 10755 10756 /** 10757 * @private 10758 * Build actionVariables array out of all the values coming into add or update methods 10759 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 10760 * where path and windowName are params for the BROWSER_POP type 10761 */ 10762 buildActionVariablesForRest: function(actionVariableMap){ 10763 var actionVariables = {"ActionVariable": []}, 10764 i, 10765 actionVariable; 10766 for(i in actionVariableMap){ 10767 if(actionVariableMap.hasOwnProperty(i)){ 10768 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 10769 actionVariable = { 10770 "name": actionVariableMap[i].name, 10771 "type": actionVariableMap[i].type, 10772 "node": actionVariableMap[i].node, 10773 "testValue": actionVariableMap[i].testValue 10774 }; 10775 actionVariables.ActionVariable.push(actionVariable); 10776 } 10777 } 10778 return actionVariables; 10779 }, 10780 10781 /** 10782 * Add 10783 */ 10784 add: function (newValues, handlers) { 10785 var contentBody = {}; 10786 10787 contentBody[this.getRestType()] = { 10788 "name": newValues.name, 10789 "type": newValues.type, 10790 "handledBy": newValues.handledBy, 10791 "params": this.buildParamsForRest(newValues.params), 10792 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10793 }; 10794 10795 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10796 handlers = handlers || {}; 10797 10798 this.restRequest(this.getRestUrl(), { 10799 method: 'POST', 10800 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10801 error: handlers.error, 10802 content: contentBody 10803 }); 10804 10805 return this; // Allow cascading 10806 }, 10807 10808 /** 10809 * @private 10810 * Update 10811 */ 10812 update: function (newValues, handlers) { 10813 this.isLoaded(); 10814 var contentBody = {}; 10815 10816 contentBody[this.getRestType()] = { 10817 "uri": this.getId(), 10818 "name": newValues.name, 10819 "type": newValues.type, 10820 "handledBy": newValues.handledBy, 10821 "params": this.buildParamsForRest(newValues.params), 10822 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10823 }; 10824 10825 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10826 handlers = handlers || {}; 10827 10828 this.restRequest(this.getRestUrl(), { 10829 method: 'PUT', 10830 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10831 error: handlers.error, 10832 content: contentBody 10833 }); 10834 10835 return this; // Allow cascading 10836 }, 10837 10838 10839 /** 10840 * @private 10841 * Delete 10842 */ 10843 "delete": function ( handlers) { 10844 this.isLoaded(); 10845 10846 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10847 handlers = handlers || {}; 10848 10849 this.restRequest(this.getRestUrl(), { 10850 method: 'DELETE', 10851 success: this.createPutSuccessHandler(this, {}, handlers.success), 10852 error: handlers.error, 10853 content: undefined 10854 }); 10855 10856 return this; // Allow cascading 10857 } 10858 10859 10860 10861 }); 10862 10863 window.finesse = window.finesse || {}; 10864 window.finesse.restservices = window.finesse.restservices || {}; 10865 window.finesse.restservices.WorkflowAction = WorkflowAction; 10866 10867 return WorkflowAction; 10868 }); 10869 10870 /** 10871 * JavaScript representation of the Finesse WorkflowActions collection 10872 * object which contains a list of WorkflowAction objects. 10873 * 10874 * @requires finesse.clientservices.ClientServices 10875 * @requires Class 10876 * @requires finesse.FinesseBase 10877 * @requires finesse.restservices.RestBase 10878 * @requires finesse.restservices.Dialog 10879 * @requires finesse.restservices.RestCollectionBase 10880 */ 10881 10882 /** @private */ 10883 define('restservices/WorkflowActions',[ 10884 'restservices/RestCollectionBase', 10885 'restservices/RestBase', 10886 'restservices/WorkflowAction' 10887 ], 10888 function (RestCollectionBase, RestBase, WorkflowAction) { 10889 10890 var WorkflowActions = RestCollectionBase.extend({ 10891 10892 /** 10893 * @class 10894 * JavaScript representation of a WorkflowActions collection object. 10895 * @augments finesse.restservices.RestCollectionBase 10896 * @constructs 10897 * @see finesse.restservices.WorkflowAction 10898 * @see finesse.restservices.Workflow 10899 * @see finesse.restservices.Workflows 10900 * @example 10901 * _workflowActions = _user.getWorkflowActions( { 10902 * onCollectionAdd : _handleWorkflowActionAdd, 10903 * onCollectionDelete : _handleWorkflowActionDelete, 10904 * onLoad : _handleWorkflowActionsLoaded 10905 * }); 10906 */ 10907 _fakeConstuctor: function () { 10908 /* This is here to hide the real init constructor from the public docs */ 10909 }, 10910 10911 /** 10912 * @private 10913 * JavaScript representation of a WorkflowActions collection object. Also exposes 10914 * methods to operate on the object against the server. 10915 * 10916 * @param {Object} options 10917 * An object with the following properties:<ul> 10918 * <li><b>id:</b> The id of the object being constructed</li> 10919 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10920 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10921 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10922 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10923 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10924 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10925 * <li><b>content:</b> {String} Raw string of response</li> 10926 * <li><b>object:</b> {Object} Parsed object of response</li> 10927 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10928 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10929 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10930 * </ul></li> 10931 * </ul></li> 10932 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10933 **/ 10934 init: function (options) { 10935 this._super(options); 10936 }, 10937 10938 /** 10939 * @private 10940 * Gets the REST class for the current object - this is the WorkflowActions class. 10941 */ 10942 getRestClass: function () { 10943 return WorkflowActions; 10944 }, 10945 10946 /** 10947 * @private 10948 * Gets the REST class for the objects that make up the collection. - this 10949 * is the WorkflowAction class. 10950 */ 10951 getRestItemClass: function () { 10952 return WorkflowAction; 10953 }, 10954 10955 /** 10956 * @private 10957 * Gets the REST type for the current object - this is a "WorkflowActions". 10958 */ 10959 getRestType: function () { 10960 return "WorkflowActions"; 10961 }, 10962 10963 /** 10964 * @private 10965 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 10966 */ 10967 getRestItemType: function () { 10968 return "WorkflowAction"; 10969 }, 10970 10971 /** 10972 * @private 10973 * Override default to indicates that the collection supports making 10974 * requests. 10975 */ 10976 supportsRequests: true, 10977 10978 /** 10979 * @private 10980 * Override default to indicates that the collection subscribes to its objects. 10981 */ 10982 supportsRestItemSubscriptions: false, 10983 10984 /** 10985 * @private 10986 * Retrieve the WorkflowActions. 10987 * 10988 * @returns {finesse.restservices.WorkflowActions} 10989 * This WorkflowActions object to allow cascading. 10990 */ 10991 get: function () { 10992 // set loaded to false so it will rebuild the collection after the get 10993 this._loaded = false; 10994 // reset collection 10995 this._collection = {}; 10996 // perform get 10997 this._synchronize(); 10998 return this; 10999 } 11000 }); 11001 11002 window.finesse = window.finesse || {}; 11003 window.finesse.restservices = window.finesse.restservices || {}; 11004 window.finesse.restservices.WorkflowActions = WorkflowActions; 11005 11006 return WorkflowActions; 11007 }); 11008 11009 /** 11010 * JavaScript representation of the Finesse Workflow object. 11011 * 11012 * @requires finesse.clientservices.ClientServices 11013 * @requires Class 11014 * @requires finesse.FinesseBase 11015 * @requires finesse.restservices.RestBase 11016 */ 11017 11018 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 11019 /*global define,finesse */ 11020 11021 /** @private */ 11022 define('restservices/Workflow',[ 11023 'restservices/RestBase', 11024 'restservices/WorkflowActions' 11025 ], 11026 function (RestBase, WorkflowActions) { 11027 11028 var Workflow = RestBase.extend({ 11029 11030 /** 11031 * @class 11032 * JavaScript representation of a Workflow object. Also exposes 11033 * methods to operate on the object against the server. 11034 * 11035 * @param {Object} options 11036 * An object with the following properties:<ul> 11037 * <li><b>id:</b> The id of the object being constructed</li> 11038 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11039 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11040 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11041 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11042 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11043 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11044 * <li><b>content:</b> {String} Raw string of response</li> 11045 * <li><b>object:</b> {Object} Parsed object of response</li> 11046 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11047 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11048 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11049 * </ul></li> 11050 * </ul></li> 11051 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11052 * @constructs 11053 **/ 11054 init: function (options) { 11055 this._super(options); 11056 }, 11057 11058 /** 11059 * @private 11060 * Gets the REST class for the current object - this is the Workflow class. 11061 * @returns {Object} The Workflow class. 11062 */ 11063 getRestClass: function () { 11064 return Workflow; 11065 }, 11066 11067 /** 11068 * @private 11069 * Gets the REST type for the current object - this is a "Workflow". 11070 * @returns {String} The Workflow string. 11071 */ 11072 getRestType: function () { 11073 return "Workflow"; 11074 }, 11075 11076 /** 11077 * @private 11078 * Override default to indicate that this object doesn't support making 11079 * requests. 11080 */ 11081 supportsRequests: false, 11082 11083 /** 11084 * @private 11085 * Override default to indicate that this object doesn't support subscriptions. 11086 */ 11087 supportsSubscriptions: false, 11088 11089 /** 11090 * @private 11091 * Getter for the Uri value. 11092 * @returns {String} The Uri. 11093 */ 11094 getUri: function () { 11095 this.isLoaded(); 11096 return this.getData().uri; 11097 }, 11098 11099 /** 11100 * Getter for the name. 11101 * @returns {String} The name. 11102 */ 11103 getName: function () { 11104 this.isLoaded(); 11105 return this.getData().name; 11106 }, 11107 11108 /** 11109 * Getter for the description. 11110 * @returns {String} The description. 11111 */ 11112 getDescription: function () { 11113 this.isLoaded(); 11114 return this.getData().description; 11115 }, 11116 11117 /** 11118 * Getter for the trigger set. 11119 * @returns {String} The trigger set. 11120 */ 11121 getTriggerSet: function () { 11122 this.isLoaded(); 11123 return this.getData().TriggerSet; 11124 }, 11125 11126 /** 11127 * Getter for the condition set. 11128 * @returns {String} The condition set. 11129 */ 11130 getConditionSet: function () { 11131 this.isLoaded(); 11132 return this.getData().ConditionSet; 11133 }, 11134 11135 /** 11136 * Getter for the assigned workflowActions. 11137 * @returns {String} The workflowActions object. 11138 */ 11139 getWorkflowActions: function () { 11140 this.isLoaded(); 11141 var workflowActions = this.getData().workflowActions; 11142 if (workflowActions === null) { 11143 workflowActions = ""; 11144 } 11145 return workflowActions; 11146 }, 11147 11148 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 11149 return function (rsp) { 11150 // Update internal structure based on response. Here we 11151 // inject the contentBody from the PUT request into the 11152 // rsp.object element to mimic a GET as a way to take 11153 // advantage of the existing _processResponse method. 11154 rsp.object = contentBody; 11155 workflow._processResponse(rsp); 11156 11157 //Remove the injected Workflow object before cascading response 11158 rsp.object = {}; 11159 11160 //cascade response back to consumer's response handler 11161 successHandler(rsp); 11162 }; 11163 }, 11164 11165 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 11166 return function (rsp) { 11167 rsp.object = contentBody; 11168 workflow._processResponse(rsp); 11169 11170 //Remove the injected Workflow object before cascading response 11171 rsp.object = {}; 11172 11173 //cascade response back to consumer's response handler 11174 successHandler(rsp); 11175 }; 11176 }, 11177 11178 /** 11179 * @private 11180 * Add 11181 */ 11182 add: function (newValues, handlers) { 11183 // this.isLoaded(); 11184 var contentBody = {}; 11185 11186 contentBody[this.getRestType()] = { 11187 "name": newValues.name, 11188 "description": newValues.description, 11189 "TriggerSet" : newValues.TriggerSet, 11190 "ConditionSet" : newValues.ConditionSet, 11191 "workflowActions" : newValues.workflowActions 11192 }; 11193 11194 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11195 handlers = handlers || {}; 11196 11197 this.restRequest(this.getRestUrl(), { 11198 method: 'POST', 11199 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11200 error: handlers.error, 11201 content: contentBody 11202 }); 11203 11204 return this; // Allow cascading 11205 }, 11206 11207 /** 11208 * @private 11209 * Update 11210 */ 11211 update: function (newValues, handlers) { 11212 this.isLoaded(); 11213 var contentBody = {}; 11214 11215 contentBody[this.getRestType()] = { 11216 "uri": this.getId(), 11217 "name": newValues.name, 11218 "description": newValues.description, 11219 "TriggerSet" : newValues.TriggerSet, 11220 "ConditionSet" : newValues.ConditionSet, 11221 "workflowActions" : newValues.workflowActions 11222 }; 11223 11224 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11225 handlers = handlers || {}; 11226 11227 this.restRequest(this.getRestUrl(), { 11228 method: 'PUT', 11229 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11230 error: handlers.error, 11231 content: contentBody 11232 }); 11233 11234 return this; // Allow cascading 11235 }, 11236 11237 11238 /** 11239 * @private 11240 * Delete 11241 */ 11242 "delete": function (handlers) { 11243 this.isLoaded(); 11244 11245 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11246 handlers = handlers || {}; 11247 11248 this.restRequest(this.getRestUrl(), { 11249 method: 'DELETE', 11250 success: this.createPutSuccessHandler(this, {}, handlers.success), 11251 error: handlers.error, 11252 content: undefined 11253 }); 11254 11255 return this; // Allow cascading 11256 } 11257 11258 11259 11260 }); 11261 11262 window.finesse = window.finesse || {}; 11263 window.finesse.restservices = window.finesse.restservices || {}; 11264 window.finesse.restservices.Workflow = Workflow; 11265 11266 return Workflow; 11267 }); 11268 11269 /** 11270 * JavaScript representation of the Finesse workflows collection 11271 * object which contains a list of workflow objects. 11272 * 11273 * @requires finesse.clientservices.ClientServices 11274 * @requires Class 11275 * @requires finesse.FinesseBase 11276 * @requires finesse.restservices.RestBase 11277 * @requires finesse.restservices.Dialog 11278 * @requires finesse.restservices.RestCollectionBase 11279 */ 11280 11281 /** @private */ 11282 define('restservices/Workflows',[ 11283 'restservices/RestCollectionBase', 11284 'restservices/RestBase', 11285 'restservices/Workflow' 11286 ], 11287 function (RestCollectionBase, RestBase, Workflow) { 11288 11289 var Workflows = RestCollectionBase.extend({ 11290 11291 /** 11292 * @class 11293 * JavaScript representation of a workflows collection object. Also exposes 11294 * methods to operate on the object against the server. 11295 * 11296 * @param {Object} options 11297 * An object with the following properties:<ul> 11298 * <li><b>id:</b> The id of the object being constructed</li> 11299 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11300 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11301 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11302 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11303 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11304 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11305 * <li><b>content:</b> {String} Raw string of response</li> 11306 * <li><b>object:</b> {Object} Parsed object of response</li> 11307 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11308 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11309 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11310 * </ul></li> 11311 * </ul></li> 11312 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11313 * @constructs 11314 **/ 11315 init: function (options) { 11316 this._super(options); 11317 }, 11318 11319 /** 11320 * @private 11321 * Gets the REST class for the current object - this is the workflows class. 11322 */ 11323 getRestClass: function () { 11324 return Workflows; 11325 }, 11326 11327 /** 11328 * @private 11329 * Gets the REST class for the objects that make up the collection. - this 11330 * is the workflow class. 11331 */ 11332 getRestItemClass: function () { 11333 return Workflow; 11334 }, 11335 11336 /** 11337 * @private 11338 * Gets the REST type for the current object - this is a "workflows". 11339 */ 11340 getRestType: function () { 11341 return "Workflows"; 11342 }, 11343 11344 /** 11345 * @private 11346 * Gets the REST type for the objects that make up the collection - this is "workflows". 11347 */ 11348 getRestItemType: function () { 11349 return "Workflow"; 11350 }, 11351 11352 /** 11353 * @private 11354 * Override default to indicates that the collection supports making requests. 11355 */ 11356 supportsRequests: true, 11357 11358 /** 11359 * @private 11360 * Override default to indicates that the collection does not subscribe to its objects. 11361 */ 11362 supportsRestItemSubscriptions: false, 11363 11364 /** 11365 * @private 11366 * Retrieve the workflows. This call will re-query the server and refresh the collection. 11367 * 11368 * @returns {finesse.restservices.workflows} 11369 * This workflows object to allow cascading. 11370 */ 11371 get: function () { 11372 // set loaded to false so it will rebuild the collection after the get 11373 this._loaded = false; 11374 // reset collection 11375 this._collection = {}; 11376 // perform get 11377 this._synchronize(); 11378 return this; 11379 } 11380 }); 11381 11382 window.finesse = window.finesse || {}; 11383 window.finesse.restservices = window.finesse.restservices || {}; 11384 window.finesse.restservices.Workflows = Workflows; 11385 11386 return Workflows; 11387 }); 11388 11389 /** 11390 * JavaScript representation of the Finesse MediaPropertiesLayout object for the Admin webapp. 11391 * @requires finesse.clientservices.ClientServices 11392 * @requires Class 11393 * @requires finesse.FinesseBase 11394 * @requires finesse.restservices.RestBase 11395 */ 11396 11397 /** The following comment is to prevent jslint errors about 11398 * using variables before they are defined. 11399 */ 11400 /*global finesse*/ 11401 11402 /** 11403 * @class 11404 * JavaScript representation of a MediaPropertiesLayout object for the Admin webapp. Also exposes 11405 * methods to operate on the object against the server. 11406 * 11407 * @constructor 11408 * @param {String} id 11409 * Not required... 11410 * @param {Object} callbacks 11411 * An object containing callbacks for instantiation and runtime 11412 * @param {Function} callbacks.onLoad(this) 11413 * Callback to invoke upon successful instantiation, passes in MediaPropertiesLayout object 11414 * @param {Function} callbacks.onLoadError(rsp) 11415 * Callback to invoke on instantiation REST request error 11416 * as passed by finesse.clientservices.ClientServices.ajax() 11417 * { 11418 * status: {Number} The HTTP status code returned 11419 * content: {String} Raw string of response 11420 * object: {Object} Parsed object of response 11421 * error: {Object} Wrapped exception that was caught 11422 * error.errorType: {String} Type of error that was caught 11423 * error.errorMessage: {String} Message associated with error 11424 * } 11425 * @param {Function} callbacks.onChange(this) 11426 * Callback to invoke upon successful update, passes in MediaPropertiesLayout object 11427 * @param {Function} callbacks.onError(rsp) 11428 * Callback to invoke on update error (refresh or event) 11429 * as passed by finesse.clientservices.ClientServices.ajax() 11430 * { 11431 * status: {Number} The HTTP status code returned 11432 * content: {String} Raw string of response 11433 * object: {Object} Parsed object of response 11434 * error: {Object} Wrapped exception that was caught 11435 * error.errorType: {String} Type of error that was caught 11436 * error.errorMessage: {String} Message associated with error 11437 * } 11438 */ 11439 11440 /** @private */ 11441 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 11442 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 11443 11444 /** 11445 * @class 11446 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 11447 * 11448 * @augments finesse.restservices.RestBase 11449 * @see finesse.restservices.Dialog#getMediaProperties 11450 * @see finesse.restservices.User#getMediaPropertiesLayout 11451 * @constructs 11452 */ 11453 _fakeConstuctor: function () { 11454 /* This is here to hide the real init constructor from the public docs */ 11455 }, 11456 11457 /** 11458 * @private 11459 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 11460 * methods to operate on the object against the server. 11461 * 11462 * @param {Object} options 11463 * An object with the following properties:<ul> 11464 * <li><b>id:</b> The id of the object being constructed</li> 11465 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11466 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11467 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11468 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11469 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11470 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11471 * <li><b>content:</b> {String} Raw string of response</li> 11472 * <li><b>object:</b> {Object} Parsed object of response</li> 11473 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11474 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11475 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11476 * </ul></li> 11477 * </ul></li> 11478 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11479 **/ 11480 init: function (options) { 11481 this._super(options); 11482 }, 11483 11484 /** 11485 * @private 11486 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 11487 */ 11488 getRestClass: function () { 11489 return MediaPropertiesLayout; 11490 }, 11491 11492 /** 11493 * @private 11494 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 11495 */ 11496 getRestType: function () { 11497 return "MediaPropertiesLayout"; 11498 }, 11499 11500 /** 11501 * @private 11502 * Returns whether this object supports subscriptions 11503 */ 11504 supportsSubscriptions: false, 11505 11506 /** 11507 * Getter for the name. 11508 * @returns {String} The name. 11509 */ 11510 getName: function () { 11511 this.isLoaded(); 11512 return this._data.name; 11513 }, 11514 11515 /** 11516 * Getter for the description. 11517 * @returns {String} The description. 11518 */ 11519 getDescription: function () { 11520 this.isLoaded(); 11521 return this._data.description || ""; 11522 }, 11523 11524 /** 11525 * Getter for the layout type (should be DEFAULT or CUSTOM). 11526 * @returns {String} The layout type. 11527 */ 11528 getType: function () { 11529 this.isLoaded(); 11530 return this._data.type || ""; 11531 }, 11532 11533 /** 11534 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 11535 * @returns {finesse.restservices.MediaPropertiesLayout} 11536 * This MediaPropertiesLayout object to allow cascading 11537 */ 11538 get: function () { 11539 this._synchronize(); 11540 11541 return this; //Allow cascading 11542 }, 11543 11544 /** 11545 * Gets the data for this object. 11546 * 11547 * Performs safe conversion from raw API data to ensure that the returned layout object 11548 * always has a header with correct entry fields, and exactly two columns with lists of entries. 11549 * 11550 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 11551 */ 11552 getData: function () { 11553 11554 var layout = this._data, result, _addColumnData; 11555 11556 result = this.getEmptyData(); 11557 result.name = layout.name; 11558 result.description = layout.description; 11559 result.type = layout.type; 11560 11561 /** 11562 * @private 11563 */ 11564 _addColumnData = function (entryData, colIndex) { 11565 11566 if (!entryData) { 11567 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 11568 entryData = {}; 11569 } else if (entryData.mediaProperty) { 11570 //If entryData contains the keys for a single entry rather than being a collection of entries, 11571 //rewrite it to be a collection containing a single entry 11572 entryData = { "": entryData }; 11573 } 11574 11575 //Add each of the entries in the list to the column 11576 jQuery.each(entryData, function (i, entryData) { 11577 11578 //If the entry has no displayName specified, explicitly set it to the empty string 11579 if (!entryData.displayName) { 11580 entryData.displayName = ""; 11581 } 11582 11583 result.columns[colIndex].push(entryData); 11584 11585 }); 11586 11587 }; 11588 11589 //The header should only contain a single entry 11590 if (layout.header && layout.header.entry) { 11591 11592 //If the entry has no displayName specified, explicitly set it to the empty string 11593 if (!layout.header.entry.displayName) { 11594 layout.header.entry.displayName = ""; 11595 } 11596 11597 result.header = layout.header.entry; 11598 11599 } else { 11600 11601 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 11602 11603 } 11604 11605 //If the column object contains an entry object that wasn't part of a list of entries, 11606 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 11607 //Force the entry object to be the 2nd element in an otherwise-empty list. 11608 if (layout.column && layout.column.entry) { 11609 layout.column = [ 11610 null, 11611 { "entry": layout.column.entry } 11612 ]; 11613 } 11614 11615 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 11616 11617 //Render left column entries 11618 if (layout.column[0] && layout.column[0].entry) { 11619 _addColumnData(layout.column[0].entry, 0); 11620 } 11621 11622 //Render right column entries 11623 if (layout.column[1] && layout.column[1].entry) { 11624 _addColumnData(layout.column[1].entry, 1); 11625 } 11626 11627 } 11628 11629 return result; 11630 11631 }, 11632 11633 /** 11634 * @private 11635 * Empty/template version of getData(). 11636 * 11637 * Used by getData(), and by callers of getData() in error cases. 11638 */ 11639 getEmptyData: function () { 11640 11641 return { 11642 header : { 11643 displayName: null, 11644 mediaProperty: null 11645 }, 11646 columns : [[], []] 11647 }; 11648 11649 }, 11650 11651 /** 11652 * Update the layout of this MediaPropertiesLayout 11653 * @param {Object} layout 11654 * The object representation of the layout you are setting 11655 * @param {finesse.interfaces.RequestHandlers} handlers 11656 * An object containing the handlers for the request 11657 * @returns {finesse.restservices.MediaPropertiesLayout} 11658 * This MediaPropertiesLayout object to allow cascading 11659 * @private 11660 */ 11661 update: function (newLayoutObject, handlers) { 11662 var contentBody = {}; 11663 11664 // Make sure type is kept the same 11665 newLayoutObject.type = this.getType(); 11666 11667 contentBody[this.getRestType()] = newLayoutObject; 11668 11669 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11670 handlers = handlers || {}; 11671 11672 this.restRequest(this.getRestUrl(), { 11673 method: 'PUT', 11674 success: handlers.success, 11675 error: handlers.error, 11676 content: contentBody 11677 }); 11678 11679 return this; // Allow cascading 11680 }, 11681 11682 /** 11683 * Create a new MediaPropertiesLayout object with the layout passed in 11684 * @param {Object} layout 11685 * The object representation of the layout you are creating 11686 * @param {finesse.interfaces.RequestHandlers} handlers 11687 * An object containing the handlers for the request 11688 * @returns {finesse.restservices.MediaPropertiesLayout} 11689 * This MediaPropertiesLayout object to allow cascading 11690 * @private 11691 */ 11692 add: function (layout, handlers) { 11693 var contentBody = {}; 11694 11695 contentBody[this.getRestType()] = layout; 11696 11697 handlers = handlers || {}; 11698 11699 this.restRequest(this.getRestUrl(), { 11700 method: 'POST', 11701 success: handlers.success, 11702 error: handlers.error, 11703 content: contentBody 11704 }); 11705 11706 return this; // Allow cascading 11707 }, 11708 11709 /** 11710 * Delete this MediaPropertiesLayout 11711 * @param {finesse.interfaces.RequestHandlers} handlers 11712 * An object containing the handlers for the request 11713 * @returns {finesse.restservices.MediaPropertiesLayout} 11714 * This MediaPropertiesLayout object to allow cascading 11715 * @private 11716 */ 11717 "delete": function (handlers) { 11718 handlers = handlers || {}; 11719 11720 this.restRequest(this.getRestUrl(), { 11721 method: 'DELETE', 11722 success: handlers.success, 11723 error: handlers.error, 11724 content: undefined 11725 }); 11726 11727 return this; // Allow cascading 11728 } 11729 11730 }); 11731 11732 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 11733 /** 11734 * @class Format of MediaPropertiesLayout Object.<br> 11735 * Object { <ul> 11736 * <li>header : { <ul> 11737 * <li>dispayName {String} 11738 * <li>mediaProperty {String}</ul>} 11739 * <li>columns : { <ul> 11740 * <li>[ [] , [] ] 11741 * </ul> 11742 * where column arrays consists of the same Object format as header.<br> 11743 * }</ul> 11744 * }<br> 11745 * @constructs 11746 */ 11747 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11748 11749 }; 11750 11751 window.finesse = window.finesse || {}; 11752 window.finesse.restservices = window.finesse.restservices || {}; 11753 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 11754 11755 return MediaPropertiesLayout; 11756 }); 11757 11758 /** 11759 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11760 * 11761 * @requires MediaPropertiesLayout 11762 * @requires ClientServices 11763 * @requires finesse.FinesseBase 11764 * @requires finesse.restservices.RestBase 11765 */ 11766 11767 /** The following comment is to prevent jslint errors about 11768 * using variables before they are defined. 11769 */ 11770 /*global finesse*/ 11771 11772 /** @private */ 11773 define('restservices/UserMediaPropertiesLayout',['restservices/MediaPropertiesLayout'], function (MediaPropertiesLayout) { 11774 var UserMediaPropertiesLayout = MediaPropertiesLayout.extend(/** @lends finesse.restservices.UserMediaPropertiesLayout.prototype */{ 11775 11776 /** 11777 * @class 11778 * JavaScript representation of a UserMediaPropertiesLayout collection object. Also exposes 11779 * methods to operate on the object against the server. 11780 * 11781 * @param {Object} options 11782 * An object with the following properties:<ul> 11783 * <li><b>id:</b> The id of the object being constructed</li> 11784 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11785 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11786 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11787 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11788 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11789 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11790 * <li><b>content:</b> {String} Raw string of response</li> 11791 * <li><b>object:</b> {Object} Parsed object of response</li> 11792 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11793 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11794 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11795 * </ul></li> 11796 * </ul></li> 11797 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11798 * @constructs 11799 **/ 11800 init: function (options) { 11801 this._super(options); 11802 }, 11803 11804 /** 11805 * @private 11806 * Gets the REST class for the current object - this is the UserMediaPropertiesLayout class. 11807 */ 11808 getRestClass: function () { 11809 return UserMediaPropertiesLayout; 11810 }, 11811 11812 /** 11813 * Overrides the parent class. Returns the url for the UserMediaPropertiesLayout resource 11814 */ 11815 getRestUrl: function () { 11816 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 11817 }, 11818 11819 /** 11820 * @private 11821 * Override to throw an error because we cannot do an update on the User's 11822 * MediaPropertiesLayout node 11823 */ 11824 update: function (layout, handlers) { 11825 throw new Error("update(): Cannot update layout for User's MediaPropertiesLayout"); 11826 }, 11827 11828 /** 11829 * @private 11830 * Override to throw an error because we cannot create a new layout on the User's 11831 * MediaPropertiesLayout node 11832 */ 11833 add: function (layout, handlers) { 11834 throw new Error("add(): Cannot create a new layout for User's MediaPropertiesLayout"); 11835 }, 11836 11837 /** 11838 * @private 11839 * Override to throw an error because we cannot delete the layout on the User's 11840 * MediaPropertiesLayout node 11841 */ 11842 "delete": function (layout, handlers) { 11843 throw new Error("delete(): Cannot delete the layout for User's MediaPropertiesLayout"); 11844 } 11845 11846 }); 11847 11848 window.finesse = window.finesse || {}; 11849 window.finesse.restservices = window.finesse.restservices || {}; 11850 window.finesse.restservices.UserMediaPropertiesLayout = UserMediaPropertiesLayout; 11851 11852 return UserMediaPropertiesLayout; 11853 }); 11854 11855 /** 11856 * JavaScript representation of the Finesse mediaPropertiesLayouts collection 11857 * object which contains a list of mediaPropertiesLayout objects. 11858 * 11859 * @requires finesse.clientservices.ClientServices 11860 * @requires Class 11861 * @requires finesse.FinesseBase 11862 * @requires finesse.restservices.RestBase 11863 * @requires finesse.restservices.Dialog 11864 * @requires finesse.restservices.RestCollectionBase 11865 */ 11866 11867 /** @private */ 11868 define('restservices/MediaPropertiesLayouts',[ 11869 'restservices/RestCollectionBase', 11870 'restservices/RestBase', 11871 'restservices/MediaPropertiesLayout' 11872 ], 11873 function (RestCollectionBase, RestBase, MediaPropertiesLayout) { 11874 11875 var MediaPropertiesLayouts = RestCollectionBase.extend({ 11876 11877 /** 11878 * @class 11879 * JavaScript representation of a mediaPropertiesLayouts collection object. Also exposes 11880 * methods to operate on the object against the server. 11881 * 11882 * @param {Object} options 11883 * An object with the following properties:<ul> 11884 * <li><b>id:</b> The id of the object being constructed</li> 11885 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11886 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11887 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11888 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11889 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11890 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11891 * <li><b>content:</b> {String} Raw string of response</li> 11892 * <li><b>object:</b> {Object} Parsed object of response</li> 11893 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11894 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11895 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11896 * </ul></li> 11897 * </ul></li> 11898 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11899 * @constructs 11900 **/ 11901 init: function (options) { 11902 this._super(options); 11903 }, 11904 11905 /** 11906 * @private 11907 * Gets the REST class for the current object - this is the mediaPropertiesLayouts class. 11908 */ 11909 getRestClass: function () { 11910 return MediaPropertiesLayouts; 11911 }, 11912 11913 /** 11914 * @private 11915 * Gets the REST class for the objects that make up the collection. - this 11916 * is the mediaPropertiesLayout class. 11917 */ 11918 getRestItemClass: function () { 11919 return MediaPropertiesLayout; 11920 }, 11921 11922 /** 11923 * @private 11924 * Gets the REST type for the current object - this is a "mediaPropertiesLayouts". 11925 */ 11926 getRestType: function () { 11927 return "MediaPropertiesLayouts"; 11928 }, 11929 11930 /** 11931 * @private 11932 * Gets the REST type for the objects that make up the collection - this is "mediaPropertiesLayouts". 11933 */ 11934 getRestItemType: function () { 11935 return "MediaPropertiesLayout"; 11936 }, 11937 11938 /** 11939 * @private 11940 * Override default to indicates that the collection supports making requests. 11941 */ 11942 supportsRequests: true, 11943 11944 /** 11945 * @private 11946 * Override default to indicates that the collection does not subscribe to its objects. 11947 */ 11948 supportsRestItemSubscriptions: false, 11949 11950 /** 11951 * @private 11952 * Retrieve the MediaPropertiesLayouts. This call will re-query the server and refresh the collection. 11953 * 11954 * @returns {finesse.restservices.MediaPropertiesLayouts} 11955 * This MediaPropertiesLayouts object to allow cascading. 11956 */ 11957 get: function () { 11958 // set loaded to false so it will rebuild the collection after the get 11959 this._loaded = false; 11960 // reset collection 11961 this._collection = {}; 11962 // perform get 11963 this._synchronize(); 11964 return this; 11965 } 11966 }); 11967 11968 window.finesse = window.finesse || {}; 11969 window.finesse.restservices = window.finesse.restservices || {}; 11970 window.finesse.restservices.MediaPropertiesLayouts = MediaPropertiesLayouts; 11971 11972 return MediaPropertiesLayouts; 11973 }); 11974 11975 /** 11976 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11977 * 11978 * @requires MediaPropertiesLayout 11979 * @requires ClientServices 11980 * @requires finesse.FinesseBase 11981 * @requires finesse.restservices.RestBase 11982 */ 11983 11984 /** The following comment is to prevent jslint errors about 11985 * using variables before they are defined. 11986 */ 11987 /*global finesse*/ 11988 11989 /** @private */ 11990 define('restservices/UserMediaPropertiesLayouts',[ 11991 'restservices/MediaPropertiesLayouts', 11992 'restservices/UserMediaPropertiesLayout' 11993 ], 11994 function (MediaPropertiesLayouts, UserMediaPropertiesLayout) { 11995 var UserMediaPropertiesLayouts = MediaPropertiesLayouts.extend(/** @lends finesse.restservices.UserMediaPropertiesLayouts.prototype */{ 11996 11997 /** 11998 * @class 11999 * JavaScript representation of a UserMediaPropertiesLayouts collection object. Also exposes 12000 * methods to operate on the object against the server. 12001 * 12002 * @param {Object} options 12003 * An object with the following properties:<ul> 12004 * <li><b>id:</b> The id of the object being constructed</li> 12005 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12006 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12007 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12008 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12009 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12010 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12011 * <li><b>content:</b> {String} Raw string of response</li> 12012 * <li><b>object:</b> {Object} Parsed object of response</li> 12013 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12014 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12015 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12016 * </ul></li> 12017 * </ul></li> 12018 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12019 * @constructs 12020 **/ 12021 init: function (options) { 12022 this._super(options); 12023 }, 12024 12025 /** 12026 * @private 12027 * Gets the REST class for the current object - this is the UserMediaPropertiesLayouts class. 12028 */ 12029 getRestClass: function () { 12030 return UserMediaPropertiesLayouts; 12031 }, 12032 12033 /** 12034 * @private 12035 * Gets the REST class for the objects that make up the collection. - this 12036 * is the UserMediaPropertiesLayout class. 12037 */ 12038 getRestItemClass: function() { 12039 return UserMediaPropertiesLayout; 12040 } 12041 }); 12042 12043 window.finesse = window.finesse || {}; 12044 window.finesse.restservices = window.finesse.restservices || {}; 12045 window.finesse.restservices.UserMediaPropertiesLayouts = UserMediaPropertiesLayouts; 12046 12047 return UserMediaPropertiesLayouts; 12048 }); 12049 12050 /** 12051 * JavaScript representation of the Finesse Dialog object for non-voice media. 12052 * 12053 * @requires finesse.restservices.DialogBase 12054 */ 12055 12056 /** @private */ 12057 define('restservices/MediaDialog',[ 12058 'restservices/DialogBase' 12059 ], 12060 function (DialogBase) { 12061 var MediaDialog = DialogBase.extend(/** @lends finesse.restservices.MediaDialog.prototype */{ 12062 12063 /** 12064 * @private 12065 * 12066 * Support requests so that applications can refresh non-voice dialogs when the media channel that the 12067 * dialog belongs to is interrupted. An event is not sent to update a dialog's actions when the media is 12068 * interrupted so a refresh is required so that the application can get an updated set of actions. 12069 */ 12070 supportsRequests: true, 12071 12072 /** 12073 * @class 12074 * A MediaDialog is an attempted connection between or among multiple participants, 12075 * for example, a chat or email. 12076 * 12077 * @augments finesse.restservices.DialogBase 12078 * @constructs 12079 */ 12080 _fakeConstuctor: function () { 12081 /* This is here to hide the real init constructor from the public docs */ 12082 }, 12083 12084 /** 12085 * @private 12086 * 12087 * @param {Object} options 12088 * An object with the following properties:<ul> 12089 * <li><b>id:</b> The id of the object being constructed</li> 12090 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12091 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12092 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12093 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12094 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12095 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12096 * <li><b>content:</b> {String} Raw string of response</li> 12097 * <li><b>object:</b> {Object} Parsed object of response</li> 12098 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12099 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12100 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12101 * </ul></li> 12102 * </ul></li> 12103 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12104 **/ 12105 init: function (options) { 12106 this._super(options); 12107 }, 12108 12109 /** 12110 * @private 12111 * Gets the REST class for the current object - this is the MediaDialog class. 12112 * @returns {Object} The Dialog class. 12113 */ 12114 getRestClass: function () { 12115 return MediaDialog; 12116 }, 12117 12118 /** 12119 * Transfers a Media Dialog to the target specified 12120 * @param {String} target script selector 12121 * The script selector to transfer the dialog. 12122 * @param {finesse.interfaces.RequestHandlers} handlers 12123 * An object containing the handlers for the request 12124 */ 12125 transfer: function(target, handlers) { 12126 this.setTaskState(MediaDialog.TaskActions.TRANSFER, handlers, target); 12127 }, 12128 12129 /** 12130 * Set the state on a Media Dialog based on the action given. 12131 * @param {finesse.restservices.MediaDialog.TaskActions} action 12132 * The action string indicating the action to invoke on a Media dialog. 12133 * @param {finesse.interfaces.RequestHandlers} handlers 12134 * An object containing the handlers for the request 12135 * @param {String} target 12136 * The target to transfer the dialog. Pass null if not transfer 12137 */ 12138 setTaskState: function (state,handlers,target) { 12139 this.isLoaded(); 12140 12141 var contentBody = {}; 12142 contentBody[this.getRestType()] = { 12143 "requestedAction": state, 12144 "target": target 12145 }; 12146 // (nonexistent) keys to be read as undefined 12147 handlers = handlers || {}; 12148 this.restRequest(this.getRestUrl(), { 12149 method: 'PUT', 12150 success: handlers.success, 12151 error: handlers.error, 12152 content: contentBody 12153 }); 12154 return this; // Allow cascading 12155 } 12156 12157 }); 12158 12159 MediaDialog.TaskActions = /** @lends finesse.restservices.MediaDialog.TaskActions.prototype */ { 12160 /** 12161 * Accept an incoming task. 12162 */ 12163 ACCEPT: "ACCEPT", 12164 /** 12165 * Start work on a task. 12166 */ 12167 START : "START", 12168 /** 12169 * Pause work on an active task. 12170 */ 12171 PAUSE: "PAUSE", 12172 /** 12173 * Resume work on a paused task. 12174 */ 12175 RESUME : "RESUME", 12176 /** 12177 * Wrap up work for a task. 12178 */ 12179 WRAP_UP : "WRAP_UP", 12180 /** 12181 * Transfer task to another target. 12182 */ 12183 TRANSFER : "TRANSFER", 12184 /** 12185 * End a task. 12186 */ 12187 CLOSE : "CLOSE", 12188 /** 12189 * @class Set of action constants for a Media Dialog. These should be used for 12190 * {@link finesse.restservices.MediaDialog#setTaskState}. 12191 * @constructs 12192 */ 12193 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12194 }; 12195 12196 12197 12198 MediaDialog.States = /** @lends finesse.restservices.MediaDialog.States.prototype */ { 12199 /** 12200 * Indicates that the task has been offered to an agent. 12201 */ 12202 OFFERED: "OFFERED", 12203 /** 12204 * Indicates that the user has started work on the task. 12205 */ 12206 ACTIVE: "ACTIVE", 12207 /** 12208 * Indicates that the user has paused work on the task. 12209 */ 12210 PAUSED: "PAUSED", 12211 /** 12212 * Indicates that the user is wrapping up the task. 12213 */ 12214 WRAPPING_UP: "WRAPPING_UP", 12215 /** 12216 * Indicates that the task was interrupted. 12217 */ 12218 INTERRUPTED: "INTERRUPTED", 12219 /** 12220 * Indicates that the task has ended. 12221 */ 12222 CLOSED: "CLOSED", 12223 /** 12224 * Indicates that the user has accepted the task. 12225 */ 12226 ACCEPTED: "ACCEPTED", 12227 /** 12228 * Finesse has recovered a task after a failure. It does not have enough information to build a complete set 12229 * of actions for the task so it only allows the user to end the task. 12230 */ 12231 UNKNOWN: "UNKNOWN", 12232 /** 12233 * @class Possible Dialog State constants. 12234 * The State flow of a typical in-bound Dialog is as follows: OFFERED, ACCEPTED, ACTIVE, CLOSED. 12235 * @constructs 12236 */ 12237 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12238 }; 12239 12240 MediaDialog.ParticipantStates = MediaDialog.States; 12241 12242 window.finesse = window.finesse || {}; 12243 window.finesse.restservices = window.finesse.restservices || {}; 12244 window.finesse.restservices.MediaDialog = MediaDialog; 12245 12246 12247 return MediaDialog; 12248 }); 12249 12250 /* Simple JavaScript Inheritance 12251 * By John Resig http://ejohn.org/ 12252 * MIT Licensed. 12253 */ 12254 // Inspired by base2 and Prototype 12255 define('restservices/../../thirdparty/Class',[], function () { 12256 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 12257 // The base Class implementation (does nothing) 12258 /** @private */ 12259 Class = function(){}; 12260 12261 // Create a new Class that inherits from this class 12262 /** @private */ 12263 Class.extend = function(prop) { 12264 var _super = this.prototype; 12265 12266 // Instantiate a base class (but only create the instance, 12267 // don't run the init constructor) 12268 initializing = true; 12269 var prototype = new this(); 12270 initializing = false; 12271 12272 // Copy the properties over onto the new prototype 12273 for (var name in prop) { 12274 // Check if we're overwriting an existing function 12275 prototype[name] = typeof prop[name] == "function" && 12276 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 12277 (function(name, fn){ 12278 return function() { 12279 var tmp = this._super; 12280 12281 // Add a new ._super() method that is the same method 12282 // but on the super-class 12283 this._super = _super[name]; 12284 12285 // The method only need to be bound temporarily, so we 12286 // remove it when we're done executing 12287 var ret = fn.apply(this, arguments); 12288 this._super = tmp; 12289 12290 return ret; 12291 }; 12292 })(name, prop[name]) : 12293 prop[name]; 12294 } 12295 12296 // The dummy class constructor 12297 /** @private */ 12298 function Class() { 12299 // All construction is actually done in the init method 12300 if ( !initializing && this.init ) 12301 this.init.apply(this, arguments); 12302 } 12303 12304 // Populate our constructed prototype object 12305 Class.prototype = prototype; 12306 12307 // Enforce the constructor to be what we expect 12308 Class.prototype.constructor = Class; 12309 12310 // And make this class extendable 12311 Class.extend = arguments.callee; 12312 12313 return Class; 12314 }; 12315 return Class; 12316 }); 12317 12318 /** 12319 * Class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects for non-voice 12320 * dialog events. 12321 * 12322 * @requires Class 12323 * @requires finesse.clientservices.ClientServices 12324 * @requires finesse.clientservices.Topics 12325 */ 12326 /** @private */ 12327 define('restservices/MediaDialogsSubscriptionManager',[ 12328 "../../thirdparty/Class", 12329 "clientservices/ClientServices", 12330 "clientservices/Topics" 12331 ], 12332 function (Class, ClientServices, Topics) { 12333 var MediaDialogsSubscriptionManager = Class.extend(/** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */{ 12334 12335 /** 12336 * Map used to track the MediaDialogs objects managed by this object. 12337 * @private 12338 */ 12339 _mediaDialogsMap: {}, 12340 12341 /** 12342 * The regex used to match the source of BOSH/XMPP events. If an event matches this source, the event will 12343 * be processed by the subscription manager. 12344 * @private 12345 */ 12346 _sourceRegEx: null, 12347 12348 /** 12349 * The subscription ID/handle for Media/Dialogs events. 12350 * @private 12351 */ 12352 _subscriptionId: null, 12353 12354 _fakeConstuctor: function () 12355 { 12356 /* This is here to hide the real init constructor from the public docs */ 12357 }, 12358 12359 /** 12360 * Create the regex used to match the source of BOSH/XMPP events. If an event matches this source, the event 12361 * will be processed by the subscription manager. 12362 * 12363 * @param {Object} restObj 12364 * The restObj whose REST URL will be used as the base of the regex. 12365 * 12366 * @returns {RegExp} 12367 * The regex used to match the source of XMPP events. 12368 * @private 12369 */ 12370 _makeSourceRegEx: function(restObj) 12371 { 12372 return new RegExp("^" + restObj.getRestUrl() + "/Media/[0-9]+/Dialogs$"); 12373 }, 12374 12375 /** 12376 * Return the media ID associated with the update. 12377 * 12378 * @param {Object} update 12379 * The content of the update event. 12380 * 12381 * @returns {String} 12382 * The media ID associated with the update. 12383 * @private 12384 */ 12385 _getMediaIdFromEventUpdate: function(update) 12386 { 12387 var parts = update.object.Update.source.split("/"); 12388 return parts[MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE]; 12389 }, 12390 12391 /** 12392 * Handler for update events. This handler forwards the update to the MediaDialogs object associated with 12393 * the media ID carried in the update event. 12394 * 12395 * @param {Object} update 12396 * The content of the update event. 12397 * 12398 * @private 12399 */ 12400 _updateEventHandler: function(update) 12401 { 12402 var mediaId = this._getMediaIdFromEventUpdate(update), 12403 mediaDialogs = this._mediaDialogsMap[mediaId]; 12404 12405 if ( mediaDialogs ) 12406 { 12407 mediaDialogs._updateEventHandler(mediaDialogs, update); 12408 } 12409 }, 12410 12411 /** 12412 * Return the media ID associated with the REST update. 12413 * 12414 * @param {Object} update 12415 * The content of the REST update. 12416 * 12417 * @returns {String} 12418 * The media ID associated with the update. 12419 * @private 12420 */ 12421 _getMediaIdFromRestUpdate: function(update) 12422 { 12423 return update.object.Update.data.dialog.mediaProperties.mediaId; 12424 }, 12425 12426 /** 12427 * Handler for REST updates. This handler forwards the update to the MediaDialogs object associated with 12428 * the media ID carried in the REST update. 12429 * 12430 * @param {Object} update 12431 * The content of the REST update. 12432 * 12433 * @private 12434 */ 12435 _processRestItemUpdate: function(update) 12436 { 12437 var mediaId = this._getMediaIdFromRestUpdate(update), 12438 mediaDialogs = this._mediaDialogsMap[mediaId]; 12439 12440 if ( mediaDialogs ) 12441 { 12442 mediaDialogs._processRestItemUpdate(update); 12443 } 12444 }, 12445 12446 /** 12447 * Utility method to create a callback to be given to OpenAjax to invoke when a message 12448 * is published on the topic of our REST URL (also XEP-0060 node). 12449 * This needs to be its own defined method so that subclasses can have their own implementation. 12450 * @returns {Function} callback(update) 12451 * The callback to be invoked when an update event is received. This callback will 12452 * process the update by notifying the MediaDialogs object associated with the media ID in the update. 12453 * 12454 * @private 12455 */ 12456 _createPubsubCallback: function () 12457 { 12458 var _this = this; 12459 return function (update) { 12460 //If the source of the update is our REST URL, this means the collection itself is modified 12461 if (update.object.Update.source.match(_this._sourceRegEx)) { 12462 _this._updateEventHandler(update); 12463 } else { 12464 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 12465 //rest item update of one of our children that was published on our node (OpenAjax topic) 12466 _this._processRestItemUpdate(update); 12467 } 12468 }; 12469 }, 12470 12471 /** 12472 * Track the MediaDialogs object so that events and REST updates signalled to this subscription manager 12473 * can be forwarded to the given MediaDialogs object. 12474 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to be tracked by the 12475 * subscription manager. 12476 * @private 12477 */ 12478 _manage: function(mediaDialogs) 12479 { 12480 this._mediaDialogsMap[mediaDialogs.getMedia().getMediaId()] = mediaDialogs; 12481 }, 12482 12483 /** 12484 * Stop tracking the MediaDialogs object. Events and REST updates signalled to this subscription manager 12485 * will no longer be forwarded to the given MediaDialogs object. 12486 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to no longer track. 12487 * @private 12488 */ 12489 _unManage: function(mediaDialogs) 12490 { 12491 var mediaId = mediaDialogs.getMedia().getMediaId(); 12492 if ( this._callbackMap[mediaId] ) 12493 { 12494 delete this._callbackMap[mediaId]; 12495 } 12496 }, 12497 12498 /** 12499 * @class 12500 * An internal class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects 12501 * for non-voice dialog events. 12502 * 12503 * @constructor 12504 * @param {RestBase} restObj 12505 * A RestBase object used to build the user portion of XMPP and REST paths. 12506 * @constructs 12507 */ 12508 init: function (restObj) 12509 { 12510 var _this; 12511 12512 this._sourceRegEx = this._makeSourceRegEx(restObj); 12513 }, 12514 12515 /** 12516 * Create the BOSH/XMPP subscription used for non-voice dialog events. Additionally, store the given 12517 * MediaDialogs object so that events for the object can be forwarded to it. 12518 * 12519 * @param {finesse.restservices.MediaDialogs} mediaDialogs a MediaDialogs object to manage (forward events) 12520 * @param {Object} callbacks an object containing success and error callbacks used to signal the result of 12521 * the subscription. 12522 * @returns {MediaDialogsSubscriptionManager} 12523 * @private 12524 */ 12525 subscribe: function (mediaDialogs, callbacks) 12526 { 12527 var topic = Topics.getTopic(mediaDialogs.getXMPPNodePath()), 12528 _this = this, 12529 handlers, 12530 successful; 12531 12532 callbacks = callbacks || {}; 12533 12534 handlers = { 12535 /** @private */ 12536 success: function () { 12537 // Add item to the refresh list in ClientServices to refresh if 12538 // we recover due to our resilient connection. 12539 ClientServices.addToRefreshList(_this); 12540 if (typeof callbacks.success === "function") { 12541 callbacks.success(); 12542 } 12543 }, 12544 /** @private */ 12545 error: function (err) { 12546 if (successful) { 12547 _this._unManage(mediaDialogs); 12548 ClientServices.unsubscribe(topic); 12549 } 12550 12551 if (typeof callbacks.error === "function") { 12552 callbacks.error(err); 12553 } 12554 } 12555 }; 12556 12557 this._manage(mediaDialogs); 12558 if ( this._subscriptionId ) 12559 { 12560 successful = true; 12561 } 12562 else 12563 { 12564 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 12565 if ( successful ) 12566 { 12567 this._subscriptionId = "OpenAjaxOnly"; 12568 } 12569 } 12570 12571 if (successful) { 12572 handlers.success(); 12573 } else { 12574 handlers.error(); 12575 } 12576 12577 return this; 12578 } 12579 }); 12580 12581 MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE = 6; 12582 12583 window.finesse = window.finesse || {}; 12584 window.finesse.restservices = window.finesse.restservices || {}; 12585 window.finesse.restservices.MediaDialogsSubscriptionManager = MediaDialogsSubscriptionManager; 12586 12587 return MediaDialogsSubscriptionManager; 12588 }); 12589 12590 /** 12591 * JavaScript representation of the Finesse MediaDialogs collection 12592 * object which contains a list of Dialog objects. 12593 * 12594 * @requires finesse.clientservices.ClientServices 12595 * @requires Class 12596 * @requires finesse.FinesseBase 12597 * @requires finesse.restservices.RestBase 12598 * @requires finesse.restservices.Dialogs 12599 * @requires finesse.restservices.MediaDialogsSubscriptionManager 12600 */ 12601 /** @private */ 12602 define('restservices/MediaDialogs',[ 12603 'restservices/RestCollectionBase', 12604 'restservices/RestBase', 12605 'restservices/Dialogs', 12606 'restservices/MediaDialog', 12607 'restservices/MediaDialogsSubscriptionManager' 12608 ], 12609 function (RestCollectionBase, RestBase, Dialogs, MediaDialog, MediaDialogsSubscriptionManager) { 12610 var MediaDialogs = Dialogs.extend(/** @lends finesse.restservices.MediaDialogs.prototype */{ 12611 12612 /** 12613 * @class 12614 * JavaScript representation of a collection of Dialogs for a specific non-voice Media. 12615 * @augments finesse.restservices.Dialogs 12616 * @constructs 12617 * @see finesse.restservices.Dialog 12618 * @example 12619 * _MediaDialogs = _media.getMediaDialogs( { 12620 * onCollectionAdd : _handleDialogAdd, 12621 * onCollectionDelete : _handleDialogDelete, 12622 * onLoad : _handleMediaDialogsLoaded 12623 * }); 12624 * 12625 * _dialogCollection = _MediaDialogs.getCollection(); 12626 * for (var dialogId in _dialogCollection) { 12627 * if (_dialogCollection.hasOwnProperty(dialogId)) { 12628 * _dialog = _dialogCollection[dialogId]; 12629 * etc... 12630 * } 12631 * } 12632 */ 12633 _fakeConstuctor: function () { 12634 /* This is here to hide the real init constructor from the public docs */ 12635 }, 12636 12637 /** 12638 * @private 12639 * @param {Object} options 12640 * An object with the following properties:<ul> 12641 * <li><b>id:</b> The id of the object being constructed</li> 12642 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12643 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12644 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12645 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12646 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12647 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12648 * <li><b>content:</b> {String} Raw string of response</li> 12649 * <li><b>object:</b> {Object} Parsed object of response</li> 12650 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12651 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12652 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12653 * </ul></li> 12654 * </ul></li> 12655 * <li><b>parentObj:</b> The parent object</li></ul> 12656 * <li><b>mediaObj:</b> The media object</li></ul> 12657 **/ 12658 init: function (options) { 12659 this._mediaObj = options.mediaObj; 12660 this._super(options); 12661 }, 12662 12663 getMedia: function() { 12664 return this._mediaObj; 12665 }, 12666 12667 /** 12668 * @private 12669 * Gets the REST class for the objects that make up the collection. - this 12670 * is the Dialog class. 12671 */ 12672 getRestItemClass: function () { 12673 return MediaDialog; 12674 }, 12675 12676 /** 12677 * @private 12678 * Gets the node path for the current object - this is the media node 12679 * @returns {String} The node path 12680 */ 12681 getXMPPNodePath: function () { 12682 var 12683 restObj = this._restObj, 12684 nodePath = ""; 12685 12686 //Prepend the base REST object if one was provided. 12687 if (restObj instanceof RestBase) { 12688 nodePath += restObj.getRestUrl(); 12689 } 12690 //Otherwise prepend with the default webapp name. 12691 else { 12692 nodePath += "/finesse/api"; 12693 } 12694 12695 //Append the REST type. 12696 nodePath += "/" + this.getRestType() + "/Media"; 12697 return nodePath; 12698 }, 12699 12700 /** 12701 * The REST URL in which this object can be referenced. 12702 * @return {String} 12703 * The REST URI for this object. 12704 * @private 12705 */ 12706 getRestUrl: function () { 12707 var 12708 restObj = this._mediaObj, 12709 restUrl = ""; 12710 12711 //Prepend the base REST object if one was provided. 12712 if (restObj instanceof RestBase) { 12713 restUrl += restObj.getRestUrl(); 12714 } 12715 //Otherwise prepend with the default webapp name. 12716 else { 12717 restUrl += "/finesse/api"; 12718 } 12719 12720 //Append the REST type. 12721 restUrl += "/" + this.getRestType(); 12722 12723 //Append ID if it is not undefined, null, or empty. 12724 if (this._id) { 12725 restUrl += "/" + this._id; 12726 } 12727 return restUrl; 12728 }, 12729 12730 /** 12731 * Overridden so that MediaDialogsSubscriptionManager can be used to share events across media dialogs. 12732 * 12733 * @param {Object} callbacks 12734 * An object containing success and error handlers for the subscription request. 12735 * @private 12736 */ 12737 subscribe: function (callbacks) 12738 { 12739 if ( !MediaDialogs.subscriptionManager ) 12740 { 12741 MediaDialogs.subscriptionManager = new MediaDialogsSubscriptionManager(this._restObj); 12742 } 12743 12744 MediaDialogs.subscriptionManager.subscribe(this, callbacks); 12745 12746 return this; 12747 } 12748 }); 12749 12750 MediaDialogs.subscriptionManager = /** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */ null; 12751 12752 window.finesse = window.finesse || {}; 12753 window.finesse.restservices = window.finesse.restservices || {}; 12754 window.finesse.restservices.MediaDialogs = MediaDialogs; 12755 12756 return MediaDialogs; 12757 }); 12758 12759 /** 12760 * Allows gadgets to call the log function to publish client logging messages over the hub. 12761 * 12762 * @requires OpenAjax 12763 */ 12764 /** @private */ 12765 define('cslogger/ClientLogger',[], function () { 12766 12767 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 12768 var _hub, _logTopic, _originId, _sessId, _host, 12769 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 12770 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 12771 12772 /** 12773 * Gets timestamp drift stored in sessionStorage 12774 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 12775 * @private 12776 */ 12777 getTsDrift = function() { 12778 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 12779 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 12780 } 12781 else { 12782 return undefined; 12783 } 12784 }, 12785 12786 /** 12787 * Sets timestamp drift in sessionStorage 12788 * @param delta is the timestamp drift between server.and client. 12789 * @private 12790 */ 12791 setTsDrift = function(delta) { 12792 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 12793 }, 12794 12795 /** 12796 * Gets Finesse server timezone offset from GMT in seconds 12797 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 12798 * @private 12799 */ 12800 getServerOffset = function() { 12801 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 12802 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 12803 } 12804 else { 12805 return undefined; 12806 } 12807 }, 12808 12809 /** 12810 * Sets server timezone offset 12811 * @param sec is the server timezone GMT offset in seconds. 12812 * @private 12813 */ 12814 setServerOffset = function(sec) { 12815 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 12816 }, 12817 12818 /** 12819 * Checks to see if we have a console. 12820 * @returns Whether the console object exists. 12821 * @private 12822 */ 12823 hasConsole = function () { 12824 try { 12825 if (window.console !== undefined) { 12826 return true; 12827 } 12828 } 12829 catch (err) { 12830 // ignore and return false 12831 } 12832 12833 return false; 12834 }, 12835 12836 /** 12837 * Gets a short form (6 character) session ID from sessionStorage 12838 * @private 12839 */ 12840 getSessId = function() { 12841 if (!_sessId) { 12842 //when _sessId not defined yet, initiate it 12843 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 12844 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 12845 } 12846 else { 12847 _sessId=" "; 12848 } 12849 } 12850 return _sessId; 12851 }, 12852 12853 /** 12854 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 12855 * @param num is the number to pad to 2 digits 12856 * @returns a two digit padded string 12857 * @private 12858 */ 12859 padTwoDigits = function (num) 12860 { 12861 return (num < 10) ? '0' + num : num; 12862 }, 12863 12864 /** 12865 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 12866 * @param num is the number to pad to 3 digits 12867 * @returns a three digit padded string 12868 * @private 12869 */ 12870 padThreeDigits = function (num) 12871 { 12872 if (num < 10) 12873 { 12874 return '00'+num; 12875 } 12876 else if (num < 100) 12877 { 12878 return '0'+num; 12879 } 12880 else 12881 { 12882 return num; 12883 } 12884 }, 12885 12886 /** 12887 * Compute the "hour" 12888 * 12889 * @param s is time in seconds 12890 * @returns {String} which is the hour 12891 * @private 12892 */ 12893 ho = function (s) { 12894 return ((s/60).toString()).split(".")[0]; 12895 }, 12896 12897 /** 12898 * Gets local timezone offset string. 12899 * 12900 * @param t is the time in seconds 12901 * @param s is the separator character between hours and minutes, e.g. ':' 12902 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 12903 * @private 12904 */ 12905 getGmtOffString = function (min,s) { 12906 var t, sign; 12907 if (min<0) { 12908 t = -min; 12909 sign = "-"; 12910 } 12911 else { 12912 t = min; 12913 sign = "+"; 12914 } 12915 12916 if (s===':') { 12917 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 12918 } 12919 else { 12920 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 12921 } 12922 }, 12923 12924 /** 12925 * Gets short form of a month name in English 12926 * 12927 * @param monthNum is zero-based month number 12928 * @returns {String} is short form of month name in English 12929 * @private 12930 */ 12931 getMonthShortStr = function (monthNum) { 12932 var result; 12933 try { 12934 result = MONTH[monthNum]; 12935 } 12936 catch (err) { 12937 if (hasConsole()) { 12938 window.console.log("Month must be between 0 and 11"); 12939 } 12940 } 12941 return result; 12942 }, 12943 12944 /** 12945 * Gets a timestamp. 12946 * @param aDate is a javascript Date object 12947 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 12948 * @private 12949 */ 12950 getDateTimeStamp = function (aDate) 12951 { 12952 var date, off, timeStr; 12953 if (aDate === null) { 12954 date = new Date(); 12955 } 12956 else { 12957 date = aDate; 12958 } 12959 off = -1*date.getTimezoneOffset(); 12960 timeStr = date.getFullYear().toString() + "-" + 12961 padTwoDigits(date.getMonth()+1) + "-" + 12962 padTwoDigits (date.getDate()) + "T"+ 12963 padTwoDigits(date.getHours()) + ":" + 12964 padTwoDigits(date.getMinutes()) + ":" + 12965 padTwoDigits(date.getSeconds())+"." + 12966 padThreeDigits(date.getMilliseconds()) + " "+ 12967 getGmtOffString(off, ':'); 12968 12969 return timeStr; 12970 }, 12971 12972 /** 12973 * Gets drift-adjusted timestamp. 12974 * @param aTimestamp is a timestamp in milliseconds 12975 * @param drift is a timestamp drift in milliseconds 12976 * @param serverOffset is a timezone GMT offset in minutes 12977 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 12978 * @private 12979 */ 12980 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 12981 { 12982 var date, timeStr, localOffset; 12983 if (aTimestamp === null) { 12984 return "--- -- ---- --:--:--.--- -----"; 12985 } 12986 else if (drift === undefined || serverOffset === undefined) { 12987 if (hasConsole()) { 12988 window.console.log("drift or serverOffset must be a number"); 12989 } 12990 return "--- -- ---- --:--:--.--- -----"; 12991 } 12992 else { 12993 //need to get a zone diff in minutes 12994 localOffset = (new Date()).getTimezoneOffset(); 12995 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 12996 timeStr = getMonthShortStr(date.getMonth()) + " "+ 12997 padTwoDigits (date.getDate())+ " "+ 12998 date.getFullYear().toString() + " "+ 12999 padTwoDigits(date.getHours()) + ":" + 13000 padTwoDigits(date.getMinutes()) + ":" + 13001 padTwoDigits(date.getSeconds())+"." + 13002 padThreeDigits(date.getMilliseconds())+" "+ 13003 getGmtOffString(serverOffset, ''); 13004 return timeStr; 13005 } 13006 }, 13007 13008 /** 13009 * Logs a message to a hidden textarea element on the page 13010 * 13011 * @param msg is the string to log. 13012 * @private 13013 */ 13014 writeToLogOutput = function (msg) { 13015 var logOutput = document.getElementById("finesseLogOutput"); 13016 13017 if (logOutput === null) 13018 { 13019 logOutput = document.createElement("textarea"); 13020 logOutput.id = "finesseLogOutput"; 13021 logOutput.style.display = "none"; 13022 document.body.appendChild(logOutput); 13023 } 13024 13025 if (logOutput.value === "") 13026 { 13027 logOutput.value = msg; 13028 } 13029 else 13030 { 13031 logOutput.value = logOutput.value + "\n" + msg; 13032 } 13033 }, 13034 13035 /* 13036 * Logs a message to console 13037 * @param str is the string to log. * @private 13038 */ 13039 logToConsole = function (str) 13040 { 13041 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 13042 now = new Date(); 13043 timeStr = getDateTimeStamp(now); 13044 if (getTsDrift() !== undefined) { 13045 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 13046 } 13047 else { 13048 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 13049 } 13050 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 13051 // Log to console 13052 if (hasConsole()) { 13053 window.console.log(msg); 13054 } 13055 13056 //Uncomment to print logs to hidden textarea. 13057 //writeToLogOutput(msg); 13058 13059 return msg; 13060 }; 13061 return { 13062 13063 /** 13064 * Publishes a Log Message over the hub. 13065 * 13066 * @param {String} message 13067 * The string to log. 13068 * @example 13069 * _clientLogger.log("This is some important message for MyGadget"); 13070 * 13071 */ 13072 log : function (message) { 13073 if(_hub) { 13074 _hub.publish(_logTopic, logToConsole(_originId + message)); 13075 } 13076 }, 13077 13078 /** 13079 * @class 13080 * Allows gadgets to call the log function to publish client logging messages over the hub. 13081 * 13082 * @constructs 13083 */ 13084 _fakeConstuctor: function () { 13085 /* This is here so we can document init() as a method rather than as a constructor. */ 13086 }, 13087 13088 /** 13089 * Initiates the client logger with a hub a gadgetId and gadget's config object. 13090 * @param {Object} hub 13091 * The hub to communicate with. 13092 * @param {String} gadgetId 13093 * A unique string to identify which gadget is doing the logging. 13094 * @param {finesse.gadget.Config} config 13095 * The config object used to get host name for that thirdparty gadget 13096 * @example 13097 * var _clientLogger = finesse.cslogger.ClientLogger; 13098 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 13099 * 13100 */ 13101 init: function (hub, gadgetId, config) { 13102 _hub = hub; 13103 _logTopic = "finesse.clientLogging." + gadgetId; 13104 _originId = gadgetId + " : "; 13105 if ((config === undefined) || (config === "undefined")) 13106 { 13107 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 13108 } 13109 else 13110 { 13111 _host = ((config && config.host)?config.host : "?.?.?.?"); 13112 } 13113 } 13114 }; 13115 }()); 13116 13117 window.finesse = window.finesse || {}; 13118 window.finesse.cslogger = window.finesse.cslogger || {}; 13119 window.finesse.cslogger.ClientLogger = ClientLogger; 13120 13121 finesse = finesse || {}; 13122 /** @namespace Supports writing messages to a central log. */ 13123 finesse.cslogger = finesse.cslogger || {}; 13124 13125 return ClientLogger; 13126 }); 13127 13128 /** 13129 * Utility class used to recover a media object after recovering from a connection or system failure. 13130 * 13131 * @requires Class 13132 * @requires finesse.restservices.Notifier 13133 * @requires finesse.clientservices.ClientServices 13134 * @requires finesse.restservices.Media 13135 */ 13136 13137 /** @private */ 13138 define('restservices/MediaOptionsHelper',['../../thirdparty/Class', 13139 '../utilities/Utilities', 13140 '../clientservices/ClientServices', 13141 '../cslogger/ClientLogger' 13142 ], 13143 function (Class, Utilities, ClientServices, ClientLogger) 13144 { 13145 /** 13146 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13147 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13148 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13149 */ 13150 var MediaOptionsHelper = Class.extend(/** @lends finesse.restservices.MediaOptionsHelper.prototype */ 13151 { 13152 /** 13153 * @private 13154 * 13155 * The media that this helper is responsible for recovering in case of failover. 13156 */ 13157 _media: null, 13158 13159 /** 13160 * @private 13161 * 13162 * The media options that this helper will ensure are set properly across failures. 13163 */ 13164 _mediaOptions: null, 13165 13166 /** 13167 * @private 13168 * 13169 * The current state of the failover recovery. 13170 */ 13171 _state: null, 13172 13173 /** 13174 * @class 13175 * 13176 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13177 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13178 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13179 * 13180 * @constructs 13181 */ 13182 _fakeConstuctor: function () 13183 { 13184 /* This is here to hide the real init constructor from the public docs */ 13185 }, 13186 13187 /** 13188 * Utility method to format a message logged by an instance of this class. 13189 * 13190 * @param {string} message the message to format 13191 * @returns {string} the given message prefixed with the name of this class and the ID of the Media object 13192 * associated with this class. 13193 * @private 13194 */ 13195 _formatLogMessage: function(message) 13196 { 13197 return "MediaOptionsHelper[" + this.media.getMediaId() + "]: " + message; 13198 }, 13199 13200 /** 13201 * Utility method to log an informational message. 13202 * 13203 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13204 * logger, this class will not log. 13205 * 13206 * @param {string} message the message to log 13207 * @private 13208 */ 13209 _log: function(message) 13210 { 13211 ClientLogger.log(this._formatLogMessage(message)); 13212 }, 13213 13214 /** 13215 * Utility method to log an error message. 13216 * 13217 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13218 * logger, this class will not log. 13219 * 13220 * @param {string} message the message to log 13221 * @private 13222 */ 13223 _error: function(message) 13224 { 13225 ClientLogger.error(this._formatLogMessage(message)); 13226 }, 13227 13228 /** 13229 * @private 13230 * 13231 * Set the running state of this failover helper. 13232 * 13233 * @param {String} newState the new state of the failover helper. 13234 */ 13235 _setState: function(newState) 13236 { 13237 this._state = newState; 13238 this._log("changed state to " + this._state); 13239 }, 13240 13241 /** 13242 * Check the given media object to see if the maxDialogLimit, interruptAction, and dialogLogoutAction options 13243 * need to be reset. These options need to be reset if the application specified login options and any of the 13244 * following conditions are true:<ul> 13245 * <li>the dialogLogoutAction in the given media object does not match the action set by the application</li> 13246 * <li>the interruptAction in the given media object does not match the action set by the application</li> 13247 * <li>the maxDialogLimit in the given media object does not match the limit set by the application</li></ul> 13248 * 13249 * @param {Object} media the media object to evaluate 13250 * @returns {*|{}|boolean} true if a login request should be sent to correct the media options 13251 * @private 13252 */ 13253 _shouldLoginToFixOptions: function(media) 13254 { 13255 return this._mediaOptions 13256 && media.isLoggedIn() 13257 && (media.getDialogLogoutAction() !== this._mediaOptions.dialogLogoutAction 13258 || media.getInterruptAction() !== this._mediaOptions.interruptAction 13259 || media.getMaxDialogLimit() !== this._mediaOptions.maxDialogLimit); 13260 }, 13261 13262 /** 13263 * @private 13264 * 13265 * Determine if the given response is an "agent already logged in" error. 13266 * 13267 * @param {Object} response the response to evaluate 13268 * 13269 * @returns {boolean} true if 13270 */ 13271 _agentIsAlreadyLoggedIn: function(response) 13272 { 13273 return response 13274 && response.object 13275 && response.object.ApiErrors 13276 && response.object.ApiErrors.ApiError 13277 && response.object.ApiErrors.ApiError.ErrorMessage === "E_ARM_STAT_AGENT_ALREADY_LOGGED_IN"; 13278 }, 13279 13280 /** 13281 * Determine if the given response to a media login request is successful. A response is successful under these 13282 * conditions:<ul> 13283 * <li>the response has a 202 status</li> 13284 * <li>the response has a 400 status and the error indicates the agent is already logged in</li> 13285 * </ul> 13286 * 13287 * @param {Object} loginResponse the response to evaluate 13288 * 13289 * @returns {*|boolean} true if the response status is 202 or if the response status is 400 and the error states 13290 * that the agent is already logged in. 13291 * @private 13292 */ 13293 _isSuccessfulLoginResponse: function(loginResponse) 13294 { 13295 return loginResponse && ((loginResponse.status === 202) || this._agentIsAlreadyLoggedIn(loginResponse)); 13296 }, 13297 13298 /** 13299 * Process a media load or change while in the connected state. This involves checking the media options to 13300 * ensure they are the same as those set by the application. 13301 * 13302 * @param {Object} media the media object that was loaded or changed. 13303 * @private 13304 */ 13305 _processConnectedState: function(media) 13306 { 13307 var _this = this, processResponse; 13308 13309 if ( this._shouldLoginToFixOptions(media) ) 13310 { 13311 processResponse = function(response) 13312 { 13313 _this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13314 13315 if ( !_this._isSuccessfulLoginResponse(response) ) 13316 { 13317 _this._error("failed to reset options: " + response.status + ": " + response.content); 13318 } 13319 }; 13320 13321 this._setState(MediaOptionsHelper.States.SETTING_OPTIONS); 13322 13323 this._log("logging in to fix options"); 13324 13325 this.media.login({ 13326 dialogLogoutAction: _this._mediaOptions.dialogLogoutAction, 13327 interruptAction: _this._mediaOptions.interruptAction, 13328 maxDialogLimit: _this._mediaOptions.maxDialogLimit, 13329 handlers: { 13330 success: processResponse, 13331 error: processResponse 13332 } 13333 }); 13334 } 13335 }, 13336 13337 /** 13338 * Process a media load or change while in the resetting options state. All that is done in this state is log a 13339 * message that a reset is already in progress. 13340 * 13341 * @param {Object} media the media object that was loaded or changed. 13342 * @private 13343 */ 13344 _processResettingOptionsState: function(media) 13345 { 13346 this._log("Resetting options is in progress"); 13347 }, 13348 13349 /** 13350 * Initialize a helper class used to recover media objects following connectivity or component failures related 13351 * to Finesse and/or CCE services. 13352 * 13353 * Initialize the failover helper to manage the recovery of the given media object. 13354 * 13355 * @param {Object} media the media object to recover 13356 * @param {Object} mediaOptions an object containing the media options used by the application:<ul> 13357 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13358 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13359 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13360 */ 13361 init: function (media, mediaOptions) 13362 { 13363 var _this = this, processMediaStateChange = function(media) 13364 { 13365 switch ( _this._state ) 13366 { 13367 case MediaOptionsHelper.States.MONITORING_OPTIONS: 13368 _this._processConnectedState(media); 13369 break; 13370 case MediaOptionsHelper.States.SETTING_OPTIONS: 13371 _this._processResettingOptionsState(media); 13372 break; 13373 default: 13374 _this._error("unexpected state: " + _this.state); 13375 break; 13376 } 13377 }; 13378 13379 this.media = media; 13380 13381 this._mediaOptions = mediaOptions || {}; 13382 13383 // The maxDialogLimit is a string in media events. Ensure _mediaOptions.maxDialogLimit is a string to 13384 // make sure it can be compared to the maxDialogLimit field in media events. 13385 // 13386 if ( this._mediaOptions.maxDialogLimit ) 13387 { 13388 this._mediaOptions.maxDialogLimit = this._mediaOptions.maxDialogLimit.toString(); 13389 } 13390 13391 // Add the media object to the refresh list so that ClientServices calls refresh on the media object when 13392 // the connection is reestablished. This will trigger processMediaStateChange() which will trigger a login 13393 // to restore media options if media options are different. 13394 // 13395 ClientServices.addToRefreshList(this.media); 13396 13397 this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13398 13399 this.media.addHandler('load', processMediaStateChange); 13400 this.media.addHandler('change', processMediaStateChange); 13401 13402 this._log("initialized"); 13403 } 13404 }); 13405 13406 /** 13407 * @private 13408 * 13409 * The states that a running MediaOptionsHelper executes. 13410 * 13411 * @type {{CONNECTED: string, RECOVERING: string, RECOVERY_LOGIN: string, _fakeConstructor: _fakeConstructor}} 13412 */ 13413 MediaOptionsHelper.States = /** @lends finesse.restservices.MediaOptionsHelper.States.prototype */ { 13414 13415 /** 13416 * The media is synchronized with the Finesse server. Media options are being monitored for changes. 13417 */ 13418 MONITORING_OPTIONS: "MONITORING_OPTIONS", 13419 13420 /** 13421 * A media login request has been sent to Finesse to set the correct media options. 13422 */ 13423 SETTING_OPTIONS: "SETTING_OPTIONS", 13424 13425 /** 13426 * @class Possible MediaOptionsHelper state values. 13427 * @constructs 13428 */ 13429 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13430 }; 13431 13432 window.finesse = window.finesse || {}; 13433 window.finesse.restservices = window.finesse.restservices || {}; 13434 window.finesse.restservices.MediaOptionsHelper = MediaOptionsHelper; 13435 13436 return MediaOptionsHelper; 13437 }); 13438 /** 13439 * JavaScript representation of the Finesse Media object 13440 * 13441 * @requires finesse.clientservices.ClientServices 13442 * @requires Class 13443 * @requires finesse.FinesseBase 13444 * @requires finesse.restservices.RestBase 13445 * @requires finesse.restservices.MediaDialogs 13446 * @requires finesse.restservices.MediaOptionsHelper 13447 */ 13448 13449 /** @private */ 13450 define('restservices/Media',[ 13451 'restservices/RestBase', 13452 'restservices/MediaDialogs', 13453 'restservices/MediaOptionsHelper' 13454 ], 13455 function (RestBase, MediaDialogs, MediaOptionsHelper) { 13456 var Media = RestBase.extend(/** @lends finesse.restservices.Media.prototype */{ 13457 13458 /** 13459 * Media objects support GET REST requests. 13460 */ 13461 supportsRequests: true, 13462 13463 /** 13464 * @private 13465 * The list of dialogs associated with this media. 13466 */ 13467 _mdialogs : null, 13468 13469 /** 13470 * @private 13471 * The options used to log into this media. 13472 */ 13473 _mediaOptions: null, 13474 13475 /** 13476 * @private 13477 * Object used to keep the maxDialogLimit, interruptAction, and dialogLogoutAction in-synch across fail-overs. 13478 */ 13479 _optionsHelper: null, 13480 13481 /** 13482 * @class 13483 * A Media represents a non-voice channel, 13484 * for example, a chat or a email. 13485 * 13486 * @augments finesse.restservices.RestBase 13487 * @constructs 13488 */ 13489 _fakeConstuctor: function () { 13490 /* This is here to hide the real init constructor from the public docs */ 13491 }, 13492 13493 /** 13494 * @private 13495 * 13496 * @param {Object} options 13497 * An object with the following properties:<ul> 13498 * <li><b>id:</b> The id of the object being constructed</li> 13499 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13500 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13501 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13502 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13503 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13504 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13505 * <li><b>content:</b> {String} Raw string of response</li> 13506 * <li><b>object:</b> {Object} Parsed object of response</li> 13507 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13508 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13509 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13510 * </ul></li> 13511 * </ul></li> 13512 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13513 **/ 13514 init: function (options) { 13515 this._super(options); 13516 }, 13517 13518 /** 13519 * @private 13520 * Utility method used to retrieve an attribute from this media object's underlying data. 13521 * 13522 * @param {String} attributeName the name of the attribute to retrieve 13523 * @returns {String} the value of the attribute or undefined if the attribute is not found 13524 */ 13525 _getDataAttribute: function(attributeName) { 13526 this.isLoaded(); 13527 return this.getData()[attributeName]; 13528 }, 13529 13530 /** 13531 * @private 13532 * Gets the REST class for the current object - this is the Media class. 13533 * @returns {Object} The Media class. 13534 */ 13535 getRestClass: function () { 13536 return Media; 13537 }, 13538 13539 /** 13540 * @private 13541 * Gets the REST type for the current object - this is a "Media". 13542 * @returns {String} The Media string. 13543 */ 13544 getRestType: function () { 13545 return "Media"; 13546 }, 13547 13548 /** 13549 * @private 13550 * Getter for the uri. 13551 * @returns {String} The uri. 13552 */ 13553 getMediaUri: function () { 13554 return this._getDataAttribute('uri'); 13555 }, 13556 13557 /** 13558 * Getter for the id. 13559 * @returns {String} The id. 13560 */ 13561 getId: function () { 13562 return this._getDataAttribute('id'); 13563 }, 13564 13565 /** 13566 * Getter for the name. 13567 * @returns {String} The name. 13568 */ 13569 getName: function () { 13570 return this._getDataAttribute('name'); 13571 }, 13572 13573 /** 13574 * Getter for the reason code id. 13575 * @returns {String} The reason code id. 13576 */ 13577 getReasonCodeId: function () { 13578 return this._getDataAttribute('reasonCodeId'); 13579 }, 13580 13581 /** 13582 * Getter for the reason code label. 13583 * @returns {String} The reason code label. 13584 */ 13585 getReasonCodeLabel: function() { 13586 this.isLoaded(); 13587 if (this.getData().reasonCode) { 13588 return this.getData.reasonCode.label; 13589 } 13590 return ""; 13591 }, 13592 13593 /** 13594 * Getter for the action to be taken in the event this media is interrupted. The action will be one of the 13595 * following:<ul> 13596 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on tasks in this media 13597 * until the media is no longer interrupted.</li> 13598 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on the task while the 13599 * media is interrupted.</li></ul> 13600 * @returns {*|Object} 13601 */ 13602 getInterruptAction: function() { 13603 return this._getDataAttribute('interruptAction'); 13604 }, 13605 13606 /** 13607 * Getter for the action to be taken in the event the agent logs out with dialogs associated with this media. 13608 * The action will be one of the following:<ul> 13609 * <li><b>CLOSE:</b> the dialog will be closed.</li> 13610 * <li><b>TRANSFER:</b> the dialog will be transferred to another agent.</li></ul> 13611 * @returns {*|Object} 13612 */ 13613 getDialogLogoutAction: function() { 13614 return this._getDataAttribute('dialogLogoutAction'); 13615 }, 13616 13617 /** 13618 * Getter for the state of the User on this Media. 13619 * @returns {String} 13620 * The current (or last fetched) state of the User on this Media 13621 * @see finesse.restservices.Media.States 13622 */ 13623 getState: function() { 13624 return this._getDataAttribute('state'); 13625 }, 13626 13627 /** 13628 * Getter for the Media id 13629 * @returns {String} The Media id 13630 */ 13631 getMediaId: function() { 13632 return this._getDataAttribute('id'); 13633 }, 13634 13635 /** 13636 * Getter for maximum number of dialogs allowed on this Media 13637 * @returns {String} The max number of Dialogs on this Media 13638 */ 13639 getMaxDialogLimit: function() { 13640 return this._getDataAttribute('maxDialogLimit'); 13641 }, 13642 13643 /** 13644 * Getter for whether or not this media is interruptible 13645 * @returns {Boolean} true if interruptible; false otherwise 13646 */ 13647 getInterruptible: function() { 13648 var interruptible = this._getDataAttribute('interruptible'); 13649 return interruptible === 'true'; 13650 }, 13651 13652 /** 13653 * Is the user interruptible on this Media. 13654 * @returns {Boolean} true if interruptible; false otherwise 13655 */ 13656 isInterruptible: function() { 13657 return this.getInterruptible(); 13658 }, 13659 13660 /** 13661 * Getter for routable field on this Media 13662 * @returns {Boolean} true if routable, false otherwise 13663 */ 13664 getRoutable: function() { 13665 var routable = this._getDataAttribute('routable'); 13666 return routable === 'true'; 13667 }, 13668 13669 /** 13670 * Is the user routable on this Media. 13671 * @returns {Boolean} true if routable, false otherwise 13672 */ 13673 isRoutable: function() { 13674 return this.getRoutable(); 13675 }, 13676 13677 /** 13678 * @param {Object} options 13679 * An object with the following properties:<ul> 13680 * <li><b>routable:</b> true if the agent is routable, false otherwise</li> 13681 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13682 * @returns {finesse.restservices.Media} 13683 * This Media object, to allow cascading 13684 */ 13685 setRoutable: function(params) { 13686 var handlers, contentBody = {}, 13687 restType = this.getRestType(), 13688 url = this.getRestUrl(); 13689 params = params || {}; 13690 13691 contentBody[restType] = { 13692 "routable": params.routable 13693 }; 13694 13695 handlers = params.handlers || {}; 13696 13697 this._makeRequest(contentBody, url, handlers); 13698 13699 return this; 13700 }, 13701 13702 /** 13703 * @private 13704 * Invoke a request to the server given a content body and handlers. 13705 * 13706 * @param {Object} contentBody 13707 * A JS object containing the body of the action request. 13708 * @param {finesse.interfaces.RequestHandlers} handlers 13709 * An object containing the handlers for the request 13710 */ 13711 _makeRequest: function (contentBody, url, handlers) { 13712 // Protect against null dereferencing of options allowing its 13713 // (nonexistent) keys to be read as undefined 13714 handlers = handlers || {}; 13715 13716 this.restRequest(url, { 13717 method: 'PUT', 13718 success: handlers.success, 13719 error: handlers.error, 13720 content: contentBody 13721 }); 13722 }, 13723 13724 /** 13725 * Return true if the params object contains one of the following:<ul> 13726 * <li>maxDialogLimit</li> 13727 * <li>interruptAction</li> 13728 * <li>dialogLogoutAction</li></ul> 13729 * 13730 * @param {Object} params the parameters to evaluate 13731 * @returns {*|Boolean} 13732 * @private 13733 */ 13734 _containsLoginOptions: function(params) { 13735 return params.maxDialogLimit || params.interruptAction || params.dialogLogoutAction; 13736 }, 13737 13738 /** 13739 * Create login parameters using the given algorithm:<ul> 13740 * <li>if loginOptions have not be set in the call to MediaList.getMedia(), use the params given by the application</li> 13741 * <li>if no params were set by the application, use the loginOptions set in the call to MediaList.getMedia()</li> 13742 * <li>if the parameters given by the application contains login options, use the parameters given by the application</li> 13743 * <li>if login options were given by the application but callbacks were given, create new login params with the 13744 * loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login params</li></ul> 13745 * 13746 * @param params the parameters specified by the application in the call to Media.login() 13747 * 13748 * @see finesse.restservices.Media#login 13749 * @see finesse.restservices.MediaList#getMedia 13750 * 13751 * @returns {Object} login parameters built based on the algorithm listed above. 13752 * @private 13753 */ 13754 _makeLoginOptions: function(params) { 13755 if ( !this._mediaOptions ) { 13756 // If loginOptions have not be set, use the params given by the application. 13757 // 13758 return params; 13759 } 13760 13761 if ( !params ) { 13762 // If there were no params given by the application, use the loginOptions set in the call to 13763 // MediaList.getMedia(). 13764 // 13765 return this._mediaOptions; 13766 } 13767 13768 if ( this._containsLoginOptions(params) ) { 13769 // if the parameters given by the application contains login options, use the parameters given by the 13770 // application. 13771 // 13772 return params; 13773 } 13774 13775 // If login options were given by the application but callbacks were given, create new login params with the 13776 // loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login 13777 // params. 13778 // 13779 return { 13780 maxDialogLimit: this._mediaOptions.maxDialogLimit, 13781 interruptAction: this._mediaOptions.interruptAction, 13782 dialogLogoutAction: this._mediaOptions.dialogLogoutAction, 13783 handlers: params.handlers 13784 }; 13785 }, 13786 13787 /** 13788 * Ensure that the maxDialogLimit, interruptAction, and dialogLogoutAction options are kept in synch across 13789 * fail-overs for this media object. 13790 * @private 13791 */ 13792 _ensureOptionsAreInSynch: function() { 13793 if ( !this._optionsHelper && this._mediaOptions ) { 13794 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13795 } 13796 }, 13797 13798 /** 13799 * Log the agent into this media. 13800 * 13801 * @param {Object} options 13802 * An object with the following properties:<ul> 13803 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13804 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13805 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 13806 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13807 * 13808 * If maxDialogLimit, interruptAction, and dialogLogoutAction are not present, loginOptions specified in the 13809 * call to finesse.restservices.MediaList.getMedia() will be used. 13810 * 13811 * @see finesse.restservices.MediaList#getMedia 13812 * 13813 * @returns {finesse.restservices.Media} 13814 * This Media object, to allow cascading 13815 */ 13816 login: function(params) { 13817 this.setState(Media.States.LOGIN, null, this._makeLoginOptions(params)); 13818 return this; // Allow cascading 13819 }, 13820 13821 /** 13822 * Perform a logout for a user on this media. 13823 * @param {String} reasonCode 13824 * The reason this user is logging out of this media. Pass null for no reason. 13825 * @param {finesse.interfaces.RequestHandlers} handlers 13826 * An object containing the handlers for the request 13827 * @returns {finesse.restservices.Media} 13828 * This Media object, to allow cascading 13829 */ 13830 logout: function(reasonCode, params) { 13831 var state = Media.States.LOGOUT; 13832 return this.setState(state, reasonCode, params); 13833 }, 13834 13835 /** 13836 * Set the state of the user on this Media. 13837 * @param {String} newState 13838 * The state you are setting 13839 * @param {ReasonCode} reasonCode 13840 * The reason this user is changing state for this media. Pass null for no reason. 13841 * @param {finesse.interfaces.RequestHandlers} handlers 13842 * An object containing the handlers for the request 13843 * @see finesse.restservices.User.States 13844 * @returns {finesse.restservices.Media} 13845 * This Media object, to allow cascading 13846 */ 13847 setState: function(state, reasonCode, params) { 13848 var handlers, reasonCodeId, contentBody = {}, 13849 restType = this.getRestType(), 13850 url = this.getRestUrl(); 13851 params = params || {}; 13852 13853 if(reasonCode) { 13854 reasonCodeId = reasonCode.id; 13855 } 13856 13857 contentBody[restType] = { 13858 "state": state, 13859 "maxDialogLimit": params.maxDialogLimit, 13860 "interruptAction": params.interruptAction, 13861 "dialogLogoutAction": params.dialogLogoutAction, 13862 "reasonCodeId": reasonCodeId 13863 }; 13864 13865 handlers = params.handlers || {}; 13866 13867 this._makeRequest(contentBody, url, handlers); 13868 13869 return this; 13870 }, 13871 13872 /** 13873 * Getter for a MediaDialogs collection object that is associated with User on this Media. 13874 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 13875 * applicable when Object has not been previously created). 13876 * @returns {finesse.restservices.MediaDialogs} 13877 * A MediaDialogs collection object. 13878 */ 13879 getMediaDialogs: function (callbacks) { 13880 var options = callbacks || {}; 13881 options.parentObj = this._restObj; 13882 options.mediaObj = this; 13883 this.isLoaded(); 13884 13885 if (this._mdialogs === null) { 13886 this._mdialogs = new MediaDialogs(options); 13887 } 13888 13889 return this._mdialogs; 13890 }, 13891 13892 /** 13893 * Refresh the dialog collection associated with this media. 13894 */ 13895 refreshMediaDialogs: function() { 13896 if ( this._mdialogs ) { 13897 this._mdialogs.refresh(); 13898 } 13899 }, 13900 13901 /** 13902 * Set the maxDialogLimit, interruptAction, and dialogLogoutAction settings that the application will use for 13903 * this media. In the event of a failure, these options will be set on the new Finesse server. 13904 * 13905 * @param {Object} mediaOptions an object with the following properties:<ul> 13906 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13907 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13908 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13909 */ 13910 setMediaOptions: function(mediaOptions) { 13911 if ( mediaOptions ) { 13912 this._mediaOptions = mediaOptions; 13913 if ( !this._optionsHelper ) { 13914 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13915 } 13916 } 13917 }, 13918 13919 /** 13920 * Refresh this media object and optionally refresh the list of media dialogs associated with this object. 13921 * 13922 * @param {Integer} retries the number of times to retry synchronizing this media object. 13923 */ 13924 refresh: function(retries) { 13925 retries = retries || 1; 13926 this._synchronize(retries); 13927 this.refreshMediaDialogs(); 13928 }, 13929 13930 /** 13931 * Is the user in work state on this Media. 13932 * @returns {boolean} returns true if the media is in work state; false otherwise 13933 */ 13934 isInWorkState: function() { 13935 return this.getState() === Media.States.WORK; 13936 }, 13937 13938 /** 13939 * Is the user in any state except LOGOUT on this Media. 13940 * @returns {boolean} returns true if the agent is in any state except LOGOUT in this media 13941 */ 13942 isLoggedIn: function() { 13943 var state = this.getState(); 13944 return state && state !== Media.States.LOGOUT; 13945 } 13946 }); 13947 13948 Media.States = /** @lends finesse.restservices.Media.States.prototype */ { 13949 /** 13950 * User Login on a non-voice Media. Note that while this is an action, is not technically a state, since a 13951 * logged-in User will always be in a specific state (READY, NOT_READY, etc.). 13952 */ 13953 LOGIN: "LOGIN", 13954 /** 13955 * User is logged out of this Media. 13956 */ 13957 LOGOUT: "LOGOUT", 13958 /** 13959 * User is not ready on this Media. 13960 */ 13961 NOT_READY: "NOT_READY", 13962 /** 13963 * User is ready for activity on this Media. 13964 */ 13965 READY: "READY", 13966 /** 13967 * User has a dialog coming in, but has not accepted it. 13968 */ 13969 RESERVED: "RESERVED", 13970 /** 13971 * The dialogs in this media have been interrupted by a dialog in a non-interruptible media. 13972 */ 13973 INTERRUPTED: "INTERRUPTED", 13974 /** 13975 * User enters this state when failing over from one Finesse to the other or when failing over from one 13976 * PG side to the other. 13977 */ 13978 WORK: "WORK", 13979 /** 13980 * @class Possible Media state values. 13981 * @constructs 13982 */ 13983 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13984 13985 }; 13986 13987 window.finesse = window.finesse || {}; 13988 window.finesse.restservices = window.finesse.restservices || {}; 13989 window.finesse.restservices.Media = Media; 13990 13991 return Media; 13992 }); 13993 /** 13994 * JavaScript representation of the Finesse Media collection 13995 * object which contains a list of Media objects. 13996 * 13997 * @requires finesse.clientservices.ClientServices 13998 * @requires Class 13999 * @requires finesse.FinesseBase 14000 * @requires finesse.restservices.RestBase 14001 * @requires finesse.restservices.Media 14002 */ 14003 /** @private */ 14004 define('restservices/MediaList',[ 14005 'restservices/RestCollectionBase', 14006 'restservices/Media', 14007 'restservices/RestBase', 14008 'utilities/Utilities' 14009 ], 14010 function (RestCollectionBase, Media, RestBase, Utilities) { 14011 var MediaList = RestCollectionBase.extend(/** @lends finesse.restservices.MediaList.prototype */{ 14012 14013 /** 14014 * @class 14015 * JavaScript representation of a MediaList collection object. 14016 * @augments finesse.restservices.RestCollectionBase 14017 * @constructs 14018 * @see finesse.restservices.Media 14019 * @example 14020 * mediaList = _user.getMediaList( { 14021 * onCollectionAdd : _handleMediaAdd, 14022 * onCollectionDelete : _handleMediaDelete, 14023 * onLoad : _handleMediaListLoaded 14024 * }); 14025 * 14026 * _mediaCollection = mediaList.getCollection(); 14027 * for (var mediaId in _mediaCollection) { 14028 * if (_mediaCollection.hasOwnProperty(mediaId)) { 14029 * media = _mediaCollection[mediaId]; 14030 * etc... 14031 * } 14032 * } 14033 */ 14034 _fakeConstuctor: function () { 14035 /* This is here to hide the real init constructor from the public docs */ 14036 }, 14037 14038 /** 14039 * @private 14040 * @param {Object} options 14041 * An object with the following properties:<ul> 14042 * <li><b>id:</b> The id of the object being constructed</li> 14043 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14044 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14045 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14046 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14047 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14048 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14049 * <li><b>content:</b> {String} Raw string of response</li> 14050 * <li><b>object:</b> {Object} Parsed object of response</li> 14051 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14052 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14053 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14054 * </ul></li> 14055 * </ul></li> 14056 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14057 **/ 14058 init: function (options) { 14059 this._super(options); 14060 }, 14061 14062 /** 14063 * @private 14064 * Gets the REST class for the current object - this is the MediaList class. 14065 */ 14066 getRestClass: function () { 14067 return MediaList; 14068 }, 14069 14070 /** 14071 * @private 14072 * Gets the REST class for the objects that make up the collection. - this 14073 * is the Media class. 14074 */ 14075 getRestItemClass: function () { 14076 return Media; 14077 }, 14078 14079 /** 14080 * @private 14081 * Gets the REST type for the current object - this is a "MediaList". 14082 */ 14083 getRestType: function () { 14084 return "MediaList"; 14085 }, 14086 14087 /** 14088 * @private 14089 * Gets the REST type for the objects that make up the collection - this is "Media". 14090 */ 14091 getRestItemType: function () { 14092 return "Media"; 14093 }, 14094 14095 /** 14096 * @private 14097 * Override default to indicates that the collection doesn't support making 14098 * requests. 14099 */ 14100 supportsRequests: true, 14101 14102 /** 14103 * @private 14104 * Override default to indicates that the collection subscribes to its objects. 14105 */ 14106 supportsRestItemSubscriptions: true, 14107 14108 /** 14109 * The REST URL in which this object can be referenced. 14110 * @return {String} 14111 * The REST URI for this object. 14112 * @private 14113 */ 14114 getRestUrl: function () { 14115 var 14116 restObj = this._restObj, 14117 restUrl = ""; 14118 14119 //Prepend the base REST object if one was provided. 14120 if (restObj instanceof RestBase) { 14121 restUrl += restObj.getRestUrl(); 14122 } 14123 //Otherwise prepend with the default webapp name. 14124 else { 14125 restUrl += "/finesse/api"; 14126 } 14127 14128 //Append the REST type. (Media not MediaList) 14129 restUrl += "/" + this.getRestItemType(); 14130 14131 //Append ID if it is not undefined, null, or empty. 14132 if (this._id) { 14133 restUrl += "/" + this._id; 14134 } 14135 14136 return restUrl; 14137 }, 14138 14139 /** 14140 * Getter for a Media from the MediaList collection. 14141 * * @param {Object} options 14142 * An object with the following properties:<ul> 14143 * <li><b>id:</b> The id of the media to fetch</li> 14144 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14145 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14146 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14147 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14148 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14149 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14150 * <li><b>content:</b> {String} Raw string of response</li> 14151 * <li><b>object:</b> {Object} Parsed object of response</li> 14152 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14153 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14154 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14155 * </ul></li> 14156 * </ul></li> 14157 * <li><b>mediaOptions:</b> {Object} An object with the following properties:<ul> 14158 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 14159 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14160 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14161 * </li></ul> 14162 * 14163 * @returns {finesse.restservices.Media} 14164 * A Media object. 14165 */ 14166 getMedia: function (options) { 14167 this.isLoaded(); 14168 options = options || {}; 14169 var objectId = options.id, 14170 media = this._collection[objectId]; 14171 14172 //throw error if media not found 14173 if(!media) { 14174 throw new Error("No media found with id: " + objectId); 14175 } 14176 14177 media.addHandler('load', options.onLoad); 14178 media.addHandler('change', options.onChange); 14179 media.addHandler('add', options.onAdd); 14180 media.addHandler('delete', options.onDelete); 14181 media.addHandler('error', options.onError); 14182 14183 media.setMediaOptions(options.mediaOptions); 14184 14185 return media; 14186 }, 14187 14188 /** 14189 * Utility method to build the internal collection data structure (object) based on provided data 14190 * @param {Object} data 14191 * The data to build the internal collection from 14192 * @private 14193 */ 14194 _buildCollection: function (data) { 14195 //overriding this from RestBaseCollection because we need to pass in parentObj 14196 //because restUrl is finesse/api/User/<useri>/Media/<mediaId> 14197 //other objects like dialog have finesse/api/Dialog/<dialogId> 14198 14199 var i, object, objectId, dataArray; 14200 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 14201 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 14202 for (i = 0; i < dataArray.length; i += 1) { 14203 14204 object = {}; 14205 object[this.getRestItemType()] = dataArray[i]; 14206 14207 //get id from object.id instead of uri, since uri is not there for some reason 14208 objectId = object[this.getRestItemType()].id;//this._extractId(object); 14209 14210 //create the Media Object 14211 this._collection[objectId] = new (this.getRestItemClass())({ 14212 id: objectId, 14213 data: object, 14214 parentObj: this._restObj 14215 }); 14216 this.length += 1; 14217 } 14218 } 14219 } 14220 }); 14221 14222 window.finesse = window.finesse || {}; 14223 window.finesse.restservices = window.finesse.restservices || {}; 14224 window.finesse.restservices.MediaList = MediaList; 14225 14226 return MediaList; 14227 }); 14228 14229 /** 14230 * JavaScript representation of the Finesse ReasonCodes collection 14231 * object which contains a list of ReasonCodes objects. 14232 * 14233 * @requires Class 14234 */ 14235 14236 /** @private */ 14237 define('restservices/ReasonCodes',[], function () { 14238 14239 var ReasonCodes = Class.extend(/** @lends finesse.restservices.ReasonCodes.prototype */{ 14240 14241 /** 14242 * @class 14243 * JavaScript representation of a ReasonCodes collection object. 14244 * @augments Class 14245 * @constructs 14246 * @example 14247 * _user.getNotReadyReasonCodes({ 14248 * success: handleNotReadyReasonCodesSuccess, 14249 * error: handleNotReadyReasonCodesError 14250 * }); 14251 * 14252 * handleNotReadyReasonCodesSuccess = function(reasonCodes) { 14253 * for(var i = 0; i < reasonCodes.length; i++) { 14254 * var reasonCode = reasonCodes[i]; 14255 * var reasonCodeId = reasonCode.id; 14256 * var reasonCodeLabel = reasonCode.label; 14257 * } 14258 * } 14259 * } 14260 */ 14261 14262 _fakeConstuctor: function () { 14263 /* This is here to hide the real init constructor from the public docs */ 14264 } 14265 14266 }); 14267 14268 window.finesse = window.finesse || {}; 14269 window.finesse.restservices = window.finesse.restservices || {}; 14270 window.finesse.restservices.ReasonCodes = ReasonCodes; 14271 14272 return ReasonCodes; 14273 }); 14274 14275 /** 14276 * JavaScript representation of the Finesse User object 14277 * 14278 * @requires finesse.clientservices.ClientServices 14279 * @requires Class 14280 * @requires finesse.FinesseBase 14281 * @requires finesse.restservices.RestBase 14282 */ 14283 14284 /** @private */ 14285 define('restservices/User',[ 14286 'restservices/RestBase', 14287 'restservices/Dialogs', 14288 'restservices/ClientLog', 14289 'restservices/Queues', 14290 'restservices/WrapUpReasons', 14291 'restservices/PhoneBooks', 14292 'restservices/Workflows', 14293 'restservices/UserMediaPropertiesLayout', 14294 'restservices/UserMediaPropertiesLayouts', 14295 'restservices/Media', 14296 'restservices/MediaList', 14297 'restservices/ReasonCodes', 14298 'utilities/Utilities' 14299 ], 14300 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, UserMediaPropertiesLayout, UserMediaPropertiesLayouts, Media, MediaList, ReasonCodes, Utilities) { 14301 14302 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 14303 14304 _dialogs : null, 14305 _clientLogObj : null, 14306 _wrapUpReasons : null, 14307 _phoneBooks : null, 14308 _workflows : null, 14309 _mediaPropertiesLayout : null, 14310 _mediaPropertiesLayouts : null, 14311 _queues : null, 14312 media : null, 14313 mediaList : null, 14314 14315 /** 14316 * @class 14317 * The User represents a Finesse Agent or Supervisor. 14318 * 14319 * @param {Object} options 14320 * An object with the following properties:<ul> 14321 * <li><b>id:</b> The id of the object being constructed</li> 14322 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14323 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14324 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14325 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14326 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14327 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14328 * <li><b>content:</b> {String} Raw string of response</li> 14329 * <li><b>object:</b> {Object} Parsed object of response</li> 14330 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14331 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14332 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14333 * </ul></li> 14334 * </ul></li> 14335 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14336 * @augments finesse.restservices.RestBase 14337 * @constructs 14338 * @example 14339 * _user = new finesse.restservices.User({ 14340 * id: _id, 14341 * onLoad : _handleUserLoad, 14342 * onChange : _handleUserChange 14343 * }); 14344 **/ 14345 init: function (options) { 14346 this._super(options); 14347 }, 14348 14349 Callbacks: {}, 14350 14351 /** 14352 * @private 14353 * Gets the REST class for the current object - this is the User object. 14354 */ 14355 getRestClass: function () { 14356 return User; 14357 }, 14358 14359 /** 14360 * @private 14361 * Gets the REST type for the current object - this is a "User". 14362 */ 14363 getRestType: function () { 14364 return "User"; 14365 }, 14366 /** 14367 * @private 14368 * overloading this to return URI 14369 */ 14370 getXMPPNodePath: function () { 14371 return this.getRestUrl(); 14372 }, 14373 /** 14374 * @private 14375 * Returns whether this object supports subscriptions 14376 */ 14377 supportsSubscriptions: function () { 14378 return true; 14379 }, 14380 14381 /** 14382 * Getter for the firstName of this User. 14383 * @returns {String} 14384 * The firstName for this User 14385 */ 14386 getFirstName: function () { 14387 this.isLoaded(); 14388 return Utilities.convertNullToEmptyString(this.getData().firstName); 14389 }, 14390 14391 14392 /** 14393 * Getter for the reasonCode of this User. 14394 * 14395 * @returns {Object} The reasonCode for the state of the User<br> 14396 * The contents may include the following:<ul> 14397 * <li>uri: The URI for the reason code object. 14398 * <li>id: The unique ID for the reason code. 14399 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14400 * <li>code: The numeric reason code value. 14401 * <li>label: The label for the reason code. 14402 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14403 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14404 * </ul> 14405 * 14406 */ 14407 getReasonCode : function() { 14408 this.isLoaded(); 14409 return this.getData().reasonCode; 14410 }, 14411 14412 /** 14413 * Getter for the pending state reasonCode of this User. 14414 * 14415 * @returns {Object} The reasonCode for the pending state of the User.<br> 14416 * The contents may include the following:<ul> 14417 * <li>uri: The URI for the reason code object. 14418 * <li>id: The unique ID for the reason code. 14419 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14420 * <li>code: The numeric reason code value. 14421 * <li>label: The label for the reason code. 14422 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14423 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14424 * </ul> 14425 */ 14426 getPendingStateReasonCode : function() { 14427 this.isLoaded(); 14428 return this.getData().pendingStateReasonCode; 14429 }, 14430 14431 14432 /** 14433 * Getter for the reasonCode of this User. 14434 * 14435 * @returns {Boolean} True if the reason code for the state of the user 14436 * is a system generated reason code. 14437 */ 14438 isReasonCodeReserved : function() { 14439 var resonCode = this.getReasonCode(); 14440 if (resonCode) { 14441 return resonCode.systemCode === "true" ? true 14442 : false; 14443 } 14444 return false; 14445 }, 14446 14447 14448 /** 14449 * Getter for the lastName of this User. 14450 * @returns {String} 14451 * The lastName for this User 14452 */ 14453 getLastName: function () { 14454 this.isLoaded(); 14455 return Utilities.convertNullToEmptyString(this.getData().lastName); 14456 }, 14457 14458 /** 14459 * Getter for the full name of this User. 14460 * The full name is of the format "FirstName LastName" (for example: "John Doe") 14461 * 14462 * @returns {String} 14463 * The full name of this User. 14464 */ 14465 getFullName : function() { 14466 this.isLoaded(); 14467 14468 /* 14469 * Currently, the expected format is "FirstName LastName", but can differ later 14470 * to something "LastName, FirstName". 14471 * To accommodate such, we use formatString utility method so that, if required, 14472 * the same can be achieved just by flipping the format place holders as follows: 14473 * "{1}, {0}" 14474 * Also, the function could be enhanced to take the format parameter. 14475 */ 14476 var fmt = "{0} {1}"; 14477 return Utilities.formatString(fmt, 14478 Utilities.convertNullToEmptyString(this.getData().firstName), 14479 Utilities.convertNullToEmptyString(this.getData().lastName)); 14480 }, 14481 14482 /** 14483 * Getter for the extension of this User. 14484 * @returns {String} 14485 * The extension, if any, of this User 14486 */ 14487 getExtension: function () { 14488 this.isLoaded(); 14489 return Utilities.convertNullToEmptyString(this.getData().extension); 14490 }, 14491 14492 /** 14493 * Getter for the id of the Team of this User 14494 * @returns {String} 14495 * The current (or last fetched) id of the Team of this User 14496 */ 14497 getTeamId: function () { 14498 this.isLoaded(); 14499 return this.getData().teamId; 14500 }, 14501 14502 /** 14503 * Getter for the name of the Team of this User 14504 * @returns {String} 14505 * The current (or last fetched) name of the Team of this User 14506 */ 14507 getTeamName: function () { 14508 this.isLoaded(); 14509 return this.getData().teamName; 14510 }, 14511 14512 /** 14513 * Is user an agent? 14514 * @returns {Boolean} True if user has role of agent, else false. 14515 */ 14516 hasAgentRole: function () { 14517 this.isLoaded(); 14518 return this.hasRole("Agent"); 14519 }, 14520 14521 /** 14522 * Is user a supervisor? 14523 * @returns {Boolean} True if user has role of supervisor, else false. 14524 */ 14525 hasSupervisorRole: function () { 14526 this.isLoaded(); 14527 return this.hasRole("Supervisor"); 14528 }, 14529 14530 /** 14531 * @private 14532 * Checks to see if user has "theRole" 14533 * @returns {Boolean} 14534 */ 14535 hasRole: function (theRole) { 14536 this.isLoaded(); 14537 var result = false, i, roles, len; 14538 14539 roles = this.getData().roles.role; 14540 len = roles.length; 14541 if (typeof roles === 'string') { 14542 if (roles === theRole) { 14543 result = true; 14544 } 14545 } else { 14546 for (i = 0; i < len ; i = i + 1) { 14547 if (roles[i] === theRole) { 14548 result = true; 14549 break; 14550 } 14551 } 14552 } 14553 14554 return result; 14555 }, 14556 14557 /** 14558 * Getter for the pending state of this User. 14559 * @returns {String} 14560 * The pending state of this User 14561 * @see finesse.restservices.User.States 14562 */ 14563 getPendingState: function () { 14564 this.isLoaded(); 14565 return Utilities.convertNullToEmptyString(this.getData().pendingState); 14566 }, 14567 14568 /** 14569 * Getter for the state of this User. 14570 * @returns {String} 14571 * The current (or last fetched) state of this User 14572 * @see finesse.restservices.User.States 14573 */ 14574 getState: function () { 14575 this.isLoaded(); 14576 return this.getData().state; 14577 }, 14578 14579 /** 14580 * Getter for the media state of this User. 14581 * @returns {String} 14582 * The current (or last fetched) media state of this User 14583 * Will be applicable only in CCX deployments 14584 * When the agent is talking on a manual outbound call, it returns busy 14585 * The value will not be present in other cases. 14586 */ 14587 getMediaState: function () { 14588 this.isLoaded(); 14589 /* There is an assertion that the value should not be undefined while setting the value in datastore. Hence setting it to null*/ 14590 if(this.getData().mediaState === undefined) { 14591 this.getData().mediaState = null; 14592 } 14593 14594 return this.getData().mediaState; 14595 }, 14596 14597 /** 14598 * Getter for the state change time of this User. 14599 * @returns {String} 14600 * The state change time of this User 14601 */ 14602 getStateChangeTime: function () { 14603 this.isLoaded(); 14604 return this.getData().stateChangeTime; 14605 }, 14606 14607 /** 14608 * Getter for the wrap-up mode of this User. 14609 * @see finesse.restservices.User.WrapUpMode 14610 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 14611 */ 14612 getWrapUpOnIncoming: function () { 14613 this.isLoaded(); 14614 return this.getData().settings.wrapUpOnIncoming; 14615 }, 14616 14617 /** 14618 * Is User required to go into wrap-up? 14619 * @see finesse.restservices.User.WrapUpMode 14620 * @return {Boolean} 14621 * True if this agent is required to go into wrap-up. 14622 */ 14623 isWrapUpRequired: function () { 14624 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 14625 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 14626 }, 14627 14628 /** 14629 * Checks to see if the user is considered a mobile agent by checking for 14630 * the existence of the mobileAgent node. 14631 * @returns {Boolean} 14632 * True if this agent is a mobile agent. 14633 */ 14634 isMobileAgent: function () { 14635 this.isLoaded(); 14636 var ma = this.getData().mobileAgent; 14637 return ma !== null && typeof ma === "object"; 14638 }, 14639 14640 /** 14641 * Getter for the mobile agent work mode. 14642 * @returns {finesse.restservices.User.WorkMode} 14643 * If available, return the mobile agent work mode, otherwise null. 14644 */ 14645 getMobileAgentMode: function () { 14646 this.isLoaded(); 14647 if (this.isMobileAgent()) { 14648 return this.getData().mobileAgent.mode; 14649 } 14650 return null; 14651 }, 14652 14653 /** 14654 * Getter for the mobile agent dial number. 14655 * @returns {String} 14656 * If available, return the mobile agent dial number, otherwise null. 14657 */ 14658 getMobileAgentDialNumber: function () { 14659 this.isLoaded(); 14660 if (this.isMobileAgent()) { 14661 return this.getData().mobileAgent.dialNumber; 14662 } 14663 return null; 14664 }, 14665 14666 /** 14667 * Getter for a Dialogs collection object that is associated with User. 14668 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14669 * applicable when Object has not been previously created). 14670 * @returns {finesse.restservices.Dialogs} 14671 * A Dialogs collection object. 14672 */ 14673 getDialogs: function (callbacks) { 14674 var options = callbacks || {}; 14675 options.parentObj = this; 14676 this.isLoaded(); 14677 14678 if (this._dialogs === null) { 14679 this._dialogs = new Dialogs(options); 14680 } 14681 14682 return this._dialogs; 14683 }, 14684 14685 /** 14686 * Getter for Media collection object that is associated with User. 14687 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14688 * applicable when Object has not been previously created). 14689 * @returns {finesse.restservices.MediaList} 14690 * A Media Dialogs collection object. 14691 */ 14692 getMediaList: function (callbacks) { 14693 var options = callbacks || {}; 14694 options.parentObj = this; 14695 this.isLoaded(); 14696 14697 if (this.mediaList === null) { 14698 this.mediaList = new MediaList(options); 14699 } 14700 14701 return this.mediaList; 14702 }, 14703 14704 /** 14705 * @private 14706 * Getter for a ClientLog object that is associated with User. 14707 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14708 * applicable when Object has not been previously created). 14709 * @returns {finesse.restservices.ClientLog} 14710 * A ClientLog collection object. 14711 */ 14712 getClientLog: function (callbacks) { 14713 var options = callbacks || {}; 14714 options.parentObj = this; 14715 this.isLoaded(); 14716 14717 if (this._clientLogObj === null) { 14718 this._clientLogObj = new ClientLog(options); 14719 } 14720 else { 14721 if(options.onLoad && typeof options.onLoad === "function") { 14722 options.onLoad(this._clientLogObj); 14723 } 14724 } 14725 return this._clientLogObj; 14726 }, 14727 14728 /** 14729 * Getter for a Queues collection object that is associated with User. 14730 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14731 * applicable when Object has not been previously created). 14732 * @returns {finesse.restservices.Queues} 14733 * A Queues collection object. 14734 */ 14735 getQueues: function (callbacks) { 14736 var options = callbacks || {}; 14737 options.parentObj = this; 14738 this.isLoaded(); 14739 14740 if (this._queues === null) { 14741 this._queues = new Queues(options); 14742 } 14743 14744 return this._queues; 14745 }, 14746 14747 /** 14748 * Getter for a WrapUpReasons collection object that is associated with User. 14749 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14750 * applicable when Object has not been previously created). 14751 * @returns {finesse.restservices.WrapUpReasons} 14752 * A WrapUpReasons collection object. 14753 */ 14754 getWrapUpReasons: function (callbacks) { 14755 var options = callbacks || {}; 14756 options.parentObj = this; 14757 this.isLoaded(); 14758 14759 if (this._wrapUpReasons === null) { 14760 this._wrapUpReasons = new WrapUpReasons(options); 14761 } 14762 14763 return this._wrapUpReasons; 14764 }, 14765 14766 /** 14767 * Getter for a PhoneBooks collection object that is associated with User. 14768 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14769 * applicable when Object has not been previously created). 14770 * @returns {finesse.restservices.PhoneBooks} 14771 * A PhoneBooks collection object. 14772 */ 14773 getPhoneBooks: function (callbacks) { 14774 var options = callbacks || {}; 14775 options.parentObj = this; 14776 this.isLoaded(); 14777 14778 if (this._phoneBooks === null) { 14779 this._phoneBooks = new PhoneBooks(options); 14780 } 14781 14782 return this._phoneBooks; 14783 }, 14784 14785 /** 14786 * @private 14787 * Loads the Workflows collection object that is associated with User and 14788 * 'returns' them to the caller via the handlers. 14789 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14790 * applicable when Object has not been previously created). 14791 * @see finesse.restservices.Workflow 14792 * @see finesse.restservices.Workflows 14793 * @see finesse.restservices.RestCollectionBase 14794 */ 14795 loadWorkflows: function (callbacks) { 14796 var options = callbacks || {}; 14797 options.parentObj = this; 14798 this.isLoaded(); 14799 14800 if (this._workflows === null) { 14801 this._workflows = new Workflows(options); 14802 } else { 14803 this._workflows.refresh(); 14804 } 14805 14806 }, 14807 14808 /** 14809 * Getter for a UserMediaPropertiesLayout object that is associated with User. 14810 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14811 * applicable when Object has not been previously created). 14812 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14813 * The UserMediaPropertiesLayout object associated with this user 14814 */ 14815 getMediaPropertiesLayout: function (callbacks) { 14816 var options = callbacks || {}; 14817 options.parentObj = this; 14818 options.id = this._id; 14819 14820 this.isLoaded(); 14821 if (this._mediaPropertiesLayout === null) { 14822 this._mediaPropertiesLayout = new UserMediaPropertiesLayout(options); 14823 } 14824 return this._mediaPropertiesLayout; 14825 }, 14826 14827 /** 14828 * Getter for a UserMediaPropertiesLayouts object that is associated with User. 14829 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14830 * applicable when Object has not been previously created). 14831 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14832 * The UserMediaPropertiesLayout object associated with this user 14833 */ 14834 getMediaPropertiesLayouts: function (callbacks) { 14835 var options = callbacks || {}; 14836 options.parentObj = this; 14837 14838 this.isLoaded(); 14839 if (this._mediaPropertiesLayouts === null) { 14840 this._mediaPropertiesLayouts = new UserMediaPropertiesLayouts(options); 14841 } 14842 return this._mediaPropertiesLayouts; 14843 }, 14844 14845 /** 14846 * Getter for the supervised Teams this User (Supervisor) supervises, if any. 14847 * @see finesse.restservices.Team 14848 * @returns {Array} 14849 * An array of Teams supervised by this User (Supervisor) 14850 */ 14851 getSupervisedTeams: function () { 14852 this.isLoaded(); 14853 14854 try { 14855 return Utilities.getArray(this.getData().teams.Team); 14856 } catch (e) { 14857 return []; 14858 } 14859 14860 }, 14861 14862 /** 14863 * Perform an agent login for this user, associating him with the 14864 * specified extension. 14865 * @param {Object} params 14866 * An object containing properties for agent login. 14867 * @param {String} params.extension 14868 * The extension to associate with this user 14869 * @param {Object} [params.mobileAgent] 14870 * A mobile agent object containing the mode and dial number properties. 14871 * @param {finesse.interfaces.RequestHandlers} params.handlers 14872 * @see finesse.interfaces.RequestHandlers 14873 * @returns {finesse.restservices.User} 14874 * This User object, to allow cascading 14875 * @private 14876 */ 14877 _login: function (params) { 14878 var handlers, contentBody = {}, 14879 restType = this.getRestType(); 14880 14881 // Protect against null dereferencing. 14882 params = params || {}; 14883 14884 contentBody[restType] = { 14885 "state": User.States.LOGIN, 14886 "extension": params.extension 14887 }; 14888 14889 // Create mobile agent node if available. 14890 if (typeof params.mobileAgent === "object") { 14891 contentBody[restType].mobileAgent = { 14892 "mode": params.mobileAgent.mode, 14893 "dialNumber": params.mobileAgent.dialNumber 14894 }; 14895 } 14896 14897 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 14898 handlers = params.handlers || {}; 14899 14900 this.restRequest(this.getRestUrl(), { 14901 method: 'PUT', 14902 success: handlers.success, 14903 error: handlers.error, 14904 content: contentBody 14905 }); 14906 14907 return this; // Allow cascading 14908 }, 14909 14910 /** 14911 * Perform an agent login for this user, associating him with the 14912 * specified extension. 14913 * @param {String} extension 14914 * The extension to associate with this user 14915 * @param {finesse.interfaces.RequestHandlers} handlers 14916 * An object containing the handlers for the request 14917 * @returns {finesse.restservices.User} 14918 * This User object, to allow cascading 14919 */ 14920 login: function (extension, handlers) { 14921 this.isLoaded(); 14922 var params = { 14923 "extension": extension, 14924 "handlers": handlers 14925 }; 14926 return this._login(params); 14927 }, 14928 14929 14930 14931 14932 /** 14933 * Perform an agent login for this user, associating him with the 14934 * specified extension. 14935 * @param {String} extension 14936 * The extension to associate with this user 14937 * @param {String} mode 14938 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 14939 * @param {String} extension 14940 * The external dial number desired to be used by the mobile agent. 14941 * @param {finesse.interfaces.RequestHandlers} handlers 14942 * An object containing the handlers for the request 14943 * @returns {finesse.restservices.User} 14944 * This User object, to allow cascading 14945 */ 14946 loginMobileAgent: function (extension, mode, dialNumber, handlers) { 14947 this.isLoaded(); 14948 var params = { 14949 "extension": extension, 14950 "mobileAgent": { 14951 "mode": mode, 14952 "dialNumber": dialNumber 14953 }, 14954 "handlers": handlers 14955 }; 14956 return this._login(params); 14957 }, 14958 14959 14960 _updateMobileAgent: function (params) { 14961 var handlers, contentBody = {}, 14962 restType = this.getRestType(); 14963 14964 params = params || {}; 14965 14966 contentBody[restType] = { 14967 }; 14968 14969 if (typeof params.mobileAgent === "object") { 14970 contentBody[restType].mobileAgent = { 14971 "mode": params.mobileAgent.mode, 14972 "dialNumber": params.mobileAgent.dialNumber 14973 }; 14974 } 14975 14976 handlers = params.handlers || {}; 14977 14978 this.restRequest(this.getRestUrl(), { 14979 method: 'PUT', 14980 success: handlers.success, 14981 error: handlers.error, 14982 content: contentBody 14983 }); 14984 14985 return this; 14986 }, 14987 14988 /** 14989 * Update user object in Finesse with agent's mobile login information (Refer defect CSCvc35407) 14990 * @param {String} mode 14991 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 14992 * @param {String} dialNumber 14993 * The external dial number desired to be used by the mobile agent. 14994 * @param {finesse.interfaces.RequestHandlers} handlers 14995 * An object containing the handlers for the request 14996 * @returns {finesse.restservices.User} 14997 * This User object, to allow cascading 14998 */ 14999 updateToMobileAgent: function (mode, dialNumber, handlers) { 15000 this.isLoaded(); 15001 var params = { 15002 "mobileAgent": { 15003 "mode": mode, 15004 "dialNumber": dialNumber 15005 }, 15006 "handlers": handlers 15007 }; 15008 return this._updateMobileAgent(params); 15009 }, 15010 15011 /** 15012 * Perform an agent logout for this user. 15013 * @param {String} reasonCode 15014 * The reason this user is logging out. Pass null for no reason. 15015 * @param {finesse.interfaces.RequestHandlers} handlers 15016 * An object containing the handlers for the request 15017 * @returns {finesse.restservices.User} 15018 * This User object, to allow cascading 15019 */ 15020 logout: function (reasonCode, handlers) { 15021 return this.setState("LOGOUT", reasonCode, handlers); 15022 }, 15023 15024 /** 15025 * Set the state of the user. 15026 * @param {String} newState 15027 * The state you are setting 15028 * @param {ReasonCode} reasonCode 15029 * The reason this user is logging out. Pass null for no reason. 15030 * @param {finesse.interfaces.RequestHandlers} handlers 15031 * An object containing the handlers for the request 15032 * @see finesse.restservices.User.States 15033 * @returns {finesse.restservices.User} 15034 * This User object, to allow cascading 15035 */ 15036 setState: function (newState, reasonCode, handlers) { 15037 this.isLoaded(); 15038 15039 var options, contentBody = {}; 15040 15041 if (!reasonCode) { 15042 if(newState === "LOGOUT"){ 15043 contentBody[this.getRestType()] = { 15044 "state": newState, 15045 "logoutAllMedia": "true" 15046 }; 15047 } 15048 else{ 15049 contentBody[this.getRestType()] = { 15050 "state": newState 15051 }; 15052 } 15053 15054 } else { 15055 if(newState === "LOGOUT"){ 15056 contentBody[this.getRestType()] = { 15057 "state": newState, 15058 "reasonCodeId": reasonCode.id, 15059 "logoutAllMedia": "true" 15060 }; 15061 } 15062 else{ 15063 contentBody[this.getRestType()] = { 15064 "state": newState, 15065 "reasonCodeId": reasonCode.id 15066 }; 15067 } 15068 15069 } 15070 15071 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15072 handlers = handlers || {}; 15073 15074 options = { 15075 method: 'PUT', 15076 success: handlers.success, 15077 error: handlers.error, 15078 content: contentBody 15079 }; 15080 15081 // After removing the selective 202 handling, we should be able to just use restRequest 15082 this.restRequest(this.getRestUrl(), options); 15083 15084 return this; // Allow cascading 15085 }, 15086 15087 /** 15088 * Make call to a particular phone number. 15089 * 15090 * @param {String} 15091 * The number to call 15092 * @param {finesse.interfaces.RequestHandlers} handlers 15093 * An object containing the handlers for the request 15094 * @returns {finesse.restservices.User} 15095 * This User object, to allow cascading 15096 */ 15097 makeCall: function (number, handlers) { 15098 this.isLoaded(); 15099 15100 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 15101 15102 return this; // Allow cascading 15103 }, 15104 15105 /** 15106 * Make a silent monitor call to a particular agent's phone number. 15107 * 15108 * @param {String} 15109 * The number to call 15110 * @param {finesse.interfaces.RequestHandlers} handlers 15111 * An object containing the handlers for the request 15112 * @returns {finesse.restservices.User} 15113 * This User object, to allow cascading 15114 */ 15115 makeSMCall: function (number, handlers) { 15116 this.isLoaded(); 15117 15118 var actionType = "SILENT_MONITOR"; 15119 15120 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 15121 15122 return this; // Allow cascading 15123 }, 15124 15125 15126 /** 15127 * Make a silent monitor call to a particular agent's phone number. 15128 * 15129 * @param {String} 15130 * The number to call 15131 * @param {String} dialogUri 15132 * The associated dialog uri of SUPERVISOR_MONITOR call 15133 * @param {finesse.interfaces.RequestHandlers} handlers 15134 * An object containing the handlers for the request 15135 * @see finesse.restservices.dialog 15136 * @returns {finesse.restservices.User} 15137 * This User object, to allow cascading 15138 */ 15139 makeBargeCall:function (number, dialogURI, handlers) { 15140 this.isLoaded(); 15141 var actionType = "BARGE_CALL"; 15142 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 15143 15144 return this; // Allow cascading 15145 }, 15146 15147 /** 15148 * Returns true if the user's current state will result in a pending state change. A pending state 15149 * change is a request to change state that does not result in an immediate state change. For 15150 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 15151 * agent will not change state until the call ends. 15152 * 15153 * The current set of states that result in pending state changes is as follows: 15154 * TALKING 15155 * HOLD 15156 * RESERVED_OUTBOUND_PREVIEW 15157 * @returns {Boolean} True if there is a pending state change. 15158 * @see finesse.restservices.User.States 15159 */ 15160 isPendingStateChange: function () { 15161 var state = this.getState(); 15162 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 15163 }, 15164 15165 /** 15166 * Returns true if the user's current state is WORK or WORK_READY. This is used so 15167 * that a pending state is not cleared when moving into wrap up (work) mode. 15168 * Note that we don't add this as a pending state, since changes while in wrap up 15169 * occur immediately (and we don't want any "pending state" to flash on screen. 15170 * 15171 * @see finesse.restservices.User.States 15172 * @returns {Boolean} True if user is in wrap-up mode. 15173 */ 15174 isWrapUp: function () { 15175 var state = this.getState(); 15176 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 15177 }, 15178 15179 /** 15180 * @private 15181 * Parses a uriString to retrieve the id portion 15182 * @param {String} uriString 15183 * @return {String} id 15184 */ 15185 _parseIdFromUriString : function (uriString) { 15186 return Utilities.getId(uriString); 15187 }, 15188 15189 /** 15190 * Gets the user's Reason Code label. Works for both Not Ready and 15191 * Logout reason codes 15192 * 15193 * @return {String} the reason code label, or empty string if none 15194 */ 15195 getReasonCodeLabel : function() { 15196 this.isLoaded(); 15197 15198 if (this.getData().reasonCode) { 15199 return this.getData().reasonCode.label; 15200 } else { 15201 return ""; 15202 } 15203 }, 15204 15205 /** 15206 * Gets the user's Not Ready reason code. 15207 * 15208 * @return {String} Reason Code Id, or undefined if not set or 15209 * indeterminate 15210 */ 15211 getNotReadyReasonCodeId : function () { 15212 this.isLoaded(); 15213 15214 var reasoncodeIdResult, finesseServerReasonCodeId; 15215 finesseServerReasonCodeId = this.getData().reasonCodeId; 15216 15217 //FinesseServer will give "-l" => we will set to undefined (for convenience) 15218 if (finesseServerReasonCodeId !== "-1") { 15219 reasoncodeIdResult = finesseServerReasonCodeId; 15220 } 15221 15222 return reasoncodeIdResult; 15223 }, 15224 15225 /** 15226 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 15227 * Note that there is no return value; use the success handler to process a 15228 * valid return. 15229 * @param {finesse.interfaces.RequestHandlers} handlers 15230 * An object containing the handlers for the request 15231 * @param {String} reasonCodeId The id for the reason code to lookup 15232 * 15233 */ 15234 getReasonCodeById : function (handlers, reasonCodeId) 15235 { 15236 var self = this, contentBody, reasonCode, url; 15237 contentBody = {}; 15238 15239 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 15240 this.restRequest(url, { 15241 method: 'GET', 15242 success: function (rsp) { 15243 reasonCode = { 15244 uri: rsp.object.ReasonCode.uri, 15245 label: rsp.object.ReasonCode.label, 15246 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 15247 }; 15248 handlers.success(reasonCode); 15249 }, 15250 error: function (rsp) { 15251 handlers.error(rsp); 15252 }, 15253 content: contentBody 15254 }); 15255 }, 15256 15257 /** 15258 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 15259 * @param {String} type (LOGOUT or NOT_READY) 15260 * @param {finesse.interfaces.RequestHandlers} handlers 15261 * An object containing the handlers for the request 15262 */ 15263 _getReasonCodesByType : function (type, handlers) 15264 { 15265 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 15266 15267 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 15268 this.restRequest(url, { 15269 method: 'GET', 15270 success: function (rsp) { 15271 reasonCodes = []; 15272 15273 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 15274 if (reasonCodeArray === undefined) { 15275 reasonCodes = undefined; 15276 } else if (reasonCodeArray[0] !== undefined) { 15277 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 15278 reasonCodes[i] = { 15279 label: rsp.object.ReasonCodes.ReasonCode[i].label, 15280 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 15281 }; 15282 } 15283 } else { 15284 reasonCodes[0] = { 15285 label: rsp.object.ReasonCodes.ReasonCode.label, 15286 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 15287 }; 15288 } 15289 handlers.success(reasonCodes); 15290 }, 15291 error: function (rsp) { 15292 handlers.error(rsp); 15293 }, 15294 content: contentBody 15295 }); 15296 }, 15297 15298 /** 15299 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15300 * reason codes. Note that there is no return value; use the success handler to 15301 * process a valid return. 15302 * 15303 * <pre class="code"> 15304 * _user.getSignoutReasonCodes({ 15305 * success: handleSignoutReasonCodesSuccess, 15306 * error: handleSignoutReasonCodesError 15307 * }); 15308 * </pre> 15309 * 15310 * @see finesse.restservices.ReasonCodes 15311 * 15312 * @param {finesse.interfaces.RequestHandlers} handlers 15313 * An object containing the handlers for the request 15314 */ 15315 getSignoutReasonCodes : function (handlers) 15316 { 15317 this._getReasonCodesByType("LOGOUT", handlers); 15318 }, 15319 15320 /** 15321 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15322 * reason codes. Note that there is no return value; use the success handler to 15323 * process a valid return. 15324 * 15325 * <pre class="code"> 15326 * _user.getNotReadyReasonCodes({ 15327 * success: handleNotReadyReasonCodesSuccess, 15328 * error: handleNotReadyReasonCodesError 15329 * }); 15330 * </pre> 15331 * 15332 * @see finesse.restservices.ReasonCodes 15333 * 15334 * @param {finesse.interfaces.RequestHandlers} handlers 15335 * An object containing the handlers for the request 15336 */ 15337 getNotReadyReasonCodes : function (handlers) 15338 { 15339 this._getReasonCodesByType("NOT_READY", handlers); 15340 } 15341 }); 15342 User.MediaStates = /** @lends finesse.restservices.User.MediaStates.prototype */ { 15343 /** 15344 * Will be applicable only in CCX deployments 15345 * When the agent is talking on a manual outbound call 15346 */ 15347 BUSY: "BUSY", 15348 /** 15349 * @class Possible Agent Media States. 15350 * @constructs 15351 */ 15352 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15353 15354 }; 15355 User.States = /** @lends finesse.restservices.User.States.prototype */ { 15356 /** 15357 * User Login. Note that while this is an action, is not technically a state, since a 15358 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 15359 */ 15360 LOGIN: "LOGIN", 15361 /** 15362 * User is logged out. 15363 */ 15364 LOGOUT: "LOGOUT", 15365 /** 15366 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 15367 */ 15368 NOT_READY: "NOT_READY", 15369 /** 15370 * User is ready for calls. 15371 */ 15372 READY: "READY", 15373 /** 15374 * User has a call coming in, but has not answered it. 15375 */ 15376 RESERVED: "RESERVED", 15377 /** 15378 * User has an outbound call being made, but has not been connected to it. 15379 */ 15380 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 15381 /** 15382 * User has an outbound call's preview information being displayed, but has not acted on it. 15383 */ 15384 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 15385 /** 15386 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 15387 */ 15388 TALKING: "TALKING", 15389 /** 15390 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 15391 */ 15392 HOLD: "HOLD", 15393 /** 15394 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 15395 */ 15396 WORK: "WORK", 15397 /** 15398 * This is the same as WORK, except that after time out user becomes READY. 15399 */ 15400 WORK_READY: "WORK_READY", 15401 /** 15402 * @class Possible User state values. 15403 * @constructs 15404 */ 15405 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15406 15407 }; 15408 15409 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 15410 /** 15411 * Mobile agent is connected (dialed) for each incoming call received. 15412 */ 15413 CALL_BY_CALL: "CALL_BY_CALL", 15414 /** 15415 * Mobile agent is connected (dialed) at login. 15416 */ 15417 NAILED_CONNECTION: "NAILED_CONNECTION", 15418 /** 15419 * @class Possible Mobile Agent Work Mode Types. 15420 * @constructs 15421 */ 15422 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15423 15424 }; 15425 15426 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 15427 /** 15428 * Agent must go into wrap-up when call ends 15429 */ 15430 REQUIRED: "REQUIRED", 15431 /** 15432 * Agent must go into wrap-up when call ends and must enter wrap-up data 15433 */ 15434 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 15435 /** 15436 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 15437 */ 15438 OPTIONAL: "OPTIONAL", 15439 /** 15440 * Agent is not allowed to go into wrap-up when call ends. 15441 */ 15442 NOT_ALLOWED: "NOT_ALLOWED", 15443 /** 15444 * @class Possible Wrap-up Mode Types. 15445 * @constructs 15446 */ 15447 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15448 15449 }; 15450 15451 window.finesse = window.finesse || {}; 15452 window.finesse.restservices = window.finesse.restservices || {}; 15453 window.finesse.restservices.User = User; 15454 15455 return User; 15456 }); 15457 15458 /** 15459 * JavaScript representation of the Finesse Users collection 15460 * object which contains a list of Users objects. 15461 * 15462 * @requires finesse.clientservices.ClientServices 15463 * @requires Class 15464 * @requires finesse.FinesseBase 15465 * @requires finesse.restservices.RestBase 15466 * @requires finesse.restservices.RestCollectionBase 15467 * @requires finesse.restservices.User 15468 */ 15469 15470 /** @private */ 15471 define('restservices/Users',[ 15472 'restservices/RestCollectionBase', 15473 'restservices/RestBase', 15474 'restservices/User' 15475 ], 15476 function (RestCollectionBase, RestBase, User) { 15477 15478 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 15479 15480 /** 15481 * @class 15482 * JavaScript representation of a Users collection object. 15483 * While there is no method provided to retrieve all Users, this collection is 15484 * used to return the Users in a supervised Team. 15485 * @augments finesse.restservices.RestCollectionBase 15486 * @constructs 15487 * @see finesse.restservices.Team 15488 * @see finesse.restservices.User 15489 * @see finesse.restservices.User#getSupervisedTeams 15490 * @example 15491 * // Note: The following method gets an Array of Teams, not a Collection. 15492 * _teams = _user.getSupervisedTeams(); 15493 * if (_teams.length > 0) { 15494 * _team0Users = _teams[0].getUsers(); 15495 * } 15496 */ 15497 _fakeConstuctor: function () { 15498 /* This is here to hide the real init constructor from the public docs */ 15499 }, 15500 15501 /** 15502 * @private 15503 * JavaScript representation of the Finesse Users collection 15504 * object which contains a list of Users objects. 15505 * 15506 * @param {Object} options 15507 * An object with the following properties:<ul> 15508 * <li><b>id:</b> The id of the object being constructed</li> 15509 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15510 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15511 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15512 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15513 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15514 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15515 * <li><b>content:</b> {String} Raw string of response</li> 15516 * <li><b>object:</b> {Object} Parsed object of response</li> 15517 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15518 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15519 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15520 * </ul></li> 15521 * </ul></li> 15522 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15523 **/ 15524 init: function (options) { 15525 this._super(options); 15526 }, 15527 15528 /** 15529 * @private 15530 * Gets the REST class for the current object - this is the Users class. 15531 */ 15532 getRestClass: function () { 15533 return Users; 15534 }, 15535 15536 /** 15537 * @private 15538 * Gets the REST class for the objects that make up the collection. - this 15539 * is the User class. 15540 */ 15541 getRestItemClass: function () { 15542 return User; 15543 }, 15544 15545 /** 15546 * @private 15547 * Gets the REST type for the current object - this is a "Users". 15548 */ 15549 getRestType: function () { 15550 return "Users"; 15551 }, 15552 15553 /** 15554 * @private 15555 * Gets the REST type for the objects that make up the collection - this is "User". 15556 */ 15557 getRestItemType: function () { 15558 return "User"; 15559 }, 15560 15561 /** 15562 * @private 15563 * Gets the node path for the current object - this is the team Users node 15564 * @returns {String} The node path 15565 */ 15566 getXMPPNodePath: function () { 15567 return this.getRestUrl(); 15568 }, 15569 15570 /** 15571 * @private 15572 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 15573 * This needs to be done because the GET /Team/id/Users API is missing 15574 * @returns {Users} This Users (collection) object to allow cascading 15575 */ 15576 _doGET: function (handlers) { 15577 var _this = this; 15578 handlers = handlers || {}; 15579 // Only do this for /Team/id/Users 15580 if (this._restObj && this._restObj.getRestType() === "Team") { 15581 this._restObj._doGET({ 15582 success: function (rspObj) { 15583 // Making sure the response was a valid Team 15584 if (_this._restObj._validate(rspObj.object)) { 15585 // Shimmying the response to look like a Users collection by extracting it from the Team response 15586 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 15587 handlers.success(rspObj); 15588 } else { 15589 handlers.error(rspObj); 15590 } 15591 }, 15592 error: handlers.error 15593 }); 15594 return this; // Allow cascading 15595 } else { 15596 return this._super(handlers); 15597 } 15598 }, 15599 15600 /** 15601 * @private 15602 * Override default to indicates that the collection doesn't support making 15603 * requests. 15604 */ 15605 supportsRequests: false, 15606 15607 /** 15608 * @private 15609 * Indicates that this collection handles the subscription for its items 15610 */ 15611 handlesItemSubscription: true, 15612 15613 /** 15614 * @private 15615 * Override default to indicate that we need to subscribe explicitly 15616 */ 15617 explicitSubscription: true 15618 15619 }); 15620 15621 window.finesse = window.finesse || {}; 15622 window.finesse.restservices = window.finesse.restservices || {}; 15623 window.finesse.restservices.Users = Users; 15624 15625 return Users; 15626 }); 15627 15628 /** 15629 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 15630 * 15631 * @requires finesse.clientservices.ClientServices 15632 * @requires Class 15633 * @requires finesse.FinesseBase 15634 * @requires finesse.restservices.RestBase 15635 */ 15636 15637 /** @private */ 15638 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 15639 15640 var TeamNotReadyReasonCode = RestBase.extend( { 15641 15642 /** 15643 * @class 15644 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 15645 * methods to operate on the object against the server. 15646 * 15647 * @param {Object} options 15648 * An object with the following properties:<ul> 15649 * <li><b>id:</b> The id of the object being constructed</li> 15650 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15651 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15652 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15653 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15654 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15655 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15656 * <li><b>content:</b> {String} Raw string of response</li> 15657 * <li><b>object:</b> {Object} Parsed object of response</li> 15658 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15659 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15660 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15661 * </ul></li> 15662 * </ul></li> 15663 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15664 * @constructs 15665 **/ 15666 init: function (options) { 15667 this._super(options); 15668 }, 15669 15670 /** 15671 * @private 15672 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 15673 * @returns {Object} The TeamNotReadyReasonCode class. 15674 */ 15675 getRestClass: function () { 15676 return TeamNotReadyReasonCode; 15677 }, 15678 15679 /** 15680 * @private 15681 * Gets the REST type for the current object - this is a "ReasonCode". 15682 * @returns {String} The ReasonCode string. 15683 */ 15684 getRestType: function () { 15685 return "ReasonCode"; 15686 }, 15687 15688 /** 15689 * @private 15690 * Override default to indicate that this object doesn't support making 15691 * requests. 15692 */ 15693 supportsRequests: false, 15694 15695 /** 15696 * @private 15697 * Override default to indicate that this object doesn't support subscriptions. 15698 */ 15699 supportsSubscriptions: false, 15700 15701 /** 15702 * Getter for the category. 15703 * @returns {String} The category. 15704 */ 15705 getCategory: function () { 15706 this.isLoaded(); 15707 return this.getData().category; 15708 }, 15709 15710 /** 15711 * Getter for the code. 15712 * @returns {String} The code. 15713 */ 15714 getCode: function () { 15715 this.isLoaded(); 15716 return this.getData().code; 15717 }, 15718 15719 /** 15720 * Getter for the label. 15721 * @returns {String} The label. 15722 */ 15723 getLabel: function () { 15724 this.isLoaded(); 15725 return this.getData().label; 15726 }, 15727 15728 /** 15729 * Getter for the forAll value. 15730 * @returns {String} The forAll. 15731 */ 15732 getForAll: function () { 15733 this.isLoaded(); 15734 return this.getData().forAll; 15735 }, 15736 15737 /** 15738 * Getter for the Uri value. 15739 * @returns {String} The Uri. 15740 */ 15741 getUri: function () { 15742 this.isLoaded(); 15743 return this.getData().uri; 15744 } 15745 15746 }); 15747 15748 window.finesse = window.finesse || {}; 15749 window.finesse.restservices = window.finesse.restservices || {}; 15750 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 15751 15752 return TeamNotReadyReasonCode; 15753 }); 15754 15755 /** 15756 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 15757 * object which contains a list of TeamNotReadyReasonCode objects. 15758 * 15759 * @requires finesse.clientservices.ClientServices 15760 * @requires Class 15761 * @requires finesse.FinesseBase 15762 * @requires finesse.restservices.RestBase 15763 * @requires finesse.restservices.Dialog 15764 * @requires finesse.restservices.RestCollectionBase 15765 */ 15766 15767 /** @private */ 15768 define('restservices/TeamNotReadyReasonCodes',[ 15769 'restservices/RestCollectionBase', 15770 'restservices/RestBase', 15771 'restservices/TeamNotReadyReasonCode' 15772 ], 15773 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 15774 15775 var TeamNotReadyReasonCodes = RestCollectionBase.extend( { 15776 15777 /** 15778 * @class 15779 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 15780 * methods to operate on the object against the server. 15781 * 15782 * @param {Object} options 15783 * An object with the following properties:<ul> 15784 * <li><b>id:</b> The id of the object being constructed</li> 15785 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15786 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15787 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15788 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15789 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15790 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15791 * <li><b>content:</b> {String} Raw string of response</li> 15792 * <li><b>object:</b> {Object} Parsed object of response</li> 15793 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15794 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15795 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15796 * </ul></li> 15797 * </ul></li> 15798 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15799 * @augments finesse.restservices.RestCollectionBase 15800 * @constructs 15801 **/ 15802 init: function (options) { 15803 this._super(options); 15804 }, 15805 15806 /** 15807 * @private 15808 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 15809 */ 15810 getRestClass: function () { 15811 return TeamNotReadyReasonCodes; 15812 }, 15813 15814 /** 15815 * @private 15816 * Gets the REST class for the objects that make up the collection. - this 15817 * is the TeamNotReadyReasonCode class. 15818 */ 15819 getRestItemClass: function () { 15820 return TeamNotReadyReasonCode; 15821 }, 15822 15823 /** 15824 * @private 15825 * Gets the REST type for the current object - this is a "ReasonCodes". 15826 */ 15827 getRestType: function () { 15828 return "ReasonCodes"; 15829 }, 15830 15831 /** 15832 * @private 15833 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 15834 */ 15835 getRestUrl: function () { 15836 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 15837 var restObj = this._restObj, 15838 restUrl = ""; 15839 //Prepend the base REST object if one was provided. 15840 //Otherwise prepend with the default webapp name. 15841 if (restObj instanceof RestBase) { 15842 restUrl += restObj.getRestUrl(); 15843 } 15844 else { 15845 restUrl += "/finesse/api"; 15846 } 15847 //Append the REST type. 15848 restUrl += "/ReasonCodes?category=NOT_READY"; 15849 //Append ID if it is not undefined, null, or empty. 15850 if (this._id) { 15851 restUrl += "/" + this._id; 15852 } 15853 return restUrl; 15854 }, 15855 15856 /** 15857 * @private 15858 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 15859 */ 15860 getRestItemType: function () { 15861 return "ReasonCode"; 15862 }, 15863 15864 /** 15865 * @private 15866 * Override default to indicates that the collection supports making 15867 * requests. 15868 */ 15869 supportsRequests: true, 15870 15871 /** 15872 * @private 15873 * Override default to indicate that this object doesn't support subscriptions. 15874 */ 15875 supportsRestItemSubscriptions: false, 15876 15877 /** 15878 * @private 15879 * Retrieve the Not Ready Reason Codes. 15880 * 15881 * @returns {TeamNotReadyReasonCodes} 15882 * This TeamNotReadyReasonCodes object to allow cascading. 15883 */ 15884 get: function () { 15885 // set loaded to false so it will rebuild the collection after the get 15886 this._loaded = false; 15887 // reset collection 15888 this._collection = {}; 15889 // perform get 15890 this._synchronize(); 15891 return this; 15892 }, 15893 15894 /** 15895 * @private 15896 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 15897 * @param {Object} reasonCodes 15898 * @param {String} contentBody 15899 * @param successHandler 15900 * @return {function} 15901 */ 15902 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 15903 return function (rsp) { 15904 // Update internal structure based on response. Here we 15905 // inject the contentBody from the PUT request into the 15906 // rsp.object element to mimic a GET as a way to take 15907 // advantage of the existing _processResponse method. 15908 rsp.object = contentBody; 15909 reasonCodes._processResponse(rsp); 15910 15911 //Remove the injected contentBody object before cascading response 15912 rsp.object = {}; 15913 15914 //cascade response back to consumer's response handler 15915 successHandler(rsp); 15916 }; 15917 }, 15918 15919 /** 15920 * @private 15921 * Perform the REST API PUT call to update the reason code assignments for the team 15922 * @param {string[]} newValues 15923 * @param handlers 15924 */ 15925 update: function (newValues, handlers) { 15926 this.isLoaded(); 15927 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 15928 15929 contentBody[this.getRestType()] = { 15930 }; 15931 15932 for (i in newValues) { 15933 if (newValues.hasOwnProperty(i)) { 15934 innerObject = { 15935 "uri": newValues[i] 15936 }; 15937 contentBodyInner.push(innerObject); 15938 } 15939 } 15940 15941 contentBody[this.getRestType()] = { 15942 "ReasonCode" : contentBodyInner 15943 }; 15944 15945 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15946 handlers = handlers || {}; 15947 15948 this.restRequest(this.getRestUrl(), { 15949 method: 'PUT', 15950 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 15951 error: handlers.error, 15952 content: contentBody 15953 }); 15954 15955 return this; // Allow cascading 15956 } 15957 }); 15958 15959 window.finesse = window.finesse || {}; 15960 window.finesse.restservices = window.finesse.restservices || {}; 15961 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 15962 15963 return TeamNotReadyReasonCodes; 15964 }); 15965 15966 /** 15967 * JavaScript representation of the Finesse Team Wrap Up Reason object. 15968 * 15969 * @requires finesse.clientservices.ClientServices 15970 * @requires Class 15971 * @requires finesse.FinesseBase 15972 * @requires finesse.restservices.RestBase 15973 */ 15974 /** @private */ 15975 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 15976 15977 var TeamWrapUpReason = RestBase.extend({ 15978 15979 /** 15980 * @class 15981 * JavaScript representation of a TeamWrapUpReason object. Also exposes 15982 * methods to operate on the object against the server. 15983 * 15984 * @param {Object} options 15985 * An object with the following properties:<ul> 15986 * <li><b>id:</b> The id of the object being constructed</li> 15987 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15988 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15989 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15990 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15991 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15992 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15993 * <li><b>content:</b> {String} Raw string of response</li> 15994 * <li><b>object:</b> {Object} Parsed object of response</li> 15995 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15996 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15997 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15998 * </ul></li> 15999 * </ul></li> 16000 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16001 * @constructs 16002 **/ 16003 init: function (options) { 16004 this._super(options); 16005 }, 16006 16007 /** 16008 * @private 16009 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 16010 * @returns {Object} The TeamWrapUpReason class. 16011 */ 16012 getRestClass: function () { 16013 return TeamWrapUpReason; 16014 }, 16015 16016 /** 16017 * @private 16018 * Gets the REST type for the current object - this is a "WrapUpReason". 16019 * @returns {String} The WrapUpReason string. 16020 */ 16021 getRestType: function () { 16022 return "WrapUpReason"; 16023 }, 16024 16025 /** 16026 * @private 16027 * Override default to indicate that this object doesn't support making 16028 * requests. 16029 */ 16030 supportsRequests: false, 16031 16032 /** 16033 * @private 16034 * Override default to indicate that this object doesn't support subscriptions. 16035 */ 16036 supportsSubscriptions: false, 16037 16038 /** 16039 * Getter for the label. 16040 * @returns {String} The label. 16041 */ 16042 getLabel: function () { 16043 this.isLoaded(); 16044 return this.getData().label; 16045 }, 16046 16047 /** 16048 * @private 16049 * Getter for the forAll value. 16050 * @returns {Boolean} True if global 16051 */ 16052 getForAll: function () { 16053 this.isLoaded(); 16054 return this.getData().forAll; 16055 }, 16056 16057 /** 16058 * @private 16059 * Getter for the Uri value. 16060 * @returns {String} The Uri. 16061 */ 16062 getUri: function () { 16063 this.isLoaded(); 16064 return this.getData().uri; 16065 } 16066 }); 16067 16068 window.finesse = window.finesse || {}; 16069 window.finesse.restservices = window.finesse.restservices || {}; 16070 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 16071 16072 return TeamWrapUpReason; 16073 }); 16074 16075 /** 16076 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 16077 * object which contains a list of Wrap-Up Reasons objects. 16078 * 16079 * @requires finesse.clientservices.ClientServices 16080 * @requires Class 16081 * @requires finesse.FinesseBase 16082 * @requires finesse.restservices.RestBase 16083 * @requires finesse.restservices.Dialog 16084 * @requires finesse.restservices.RestCollectionBase 16085 */ 16086 /** @private */ 16087 define('restservices/TeamWrapUpReasons',[ 16088 'restservices/RestCollectionBase', 16089 'restservices/RestBase', 16090 'restservices/TeamWrapUpReason' 16091 ], 16092 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 16093 16094 var TeamWrapUpReasons = RestCollectionBase.extend({ 16095 16096 /** 16097 * @class 16098 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 16099 * methods to operate on the object against the server. 16100 * 16101 * @param {Object} options 16102 * An object with the following properties:<ul> 16103 * <li><b>id:</b> The id of the object being constructed</li> 16104 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16105 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16106 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16107 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16108 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16109 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16110 * <li><b>content:</b> {String} Raw string of response</li> 16111 * <li><b>object:</b> {Object} Parsed object of response</li> 16112 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16113 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16114 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16115 * </ul></li> 16116 * </ul></li> 16117 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16118 * @constructs 16119 **/ 16120 init: function (options) { 16121 this._super(options); 16122 }, 16123 16124 /** 16125 * @private 16126 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 16127 */ 16128 getRestClass: function () { 16129 return TeamWrapUpReasons; 16130 }, 16131 16132 /** 16133 * @private 16134 * Gets the REST class for the objects that make up the collection. - this 16135 * is the TeamWrapUpReason class. 16136 */ 16137 getRestItemClass: function () { 16138 return TeamWrapUpReason; 16139 }, 16140 16141 /** 16142 * @private 16143 * Gets the REST type for the current object - this is a "WrapUpReasons". 16144 */ 16145 getRestType: function () { 16146 return "WrapUpReasons"; 16147 }, 16148 16149 /** 16150 * @private 16151 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 16152 */ 16153 getRestItemType: function () { 16154 return "WrapUpReason"; 16155 }, 16156 16157 /** 16158 * @private 16159 * Override default to indicates that the collection supports making 16160 * requests. 16161 */ 16162 supportsRequests: true, 16163 16164 /** 16165 * @private 16166 * Override default to indicate that this object doesn't support subscriptions. 16167 */ 16168 supportsRestItemSubscriptions: false, 16169 16170 /** 16171 * Retrieve the Team Wrap Up Reasons. 16172 * 16173 * @returns {finesse.restservices.TeamWrapUpReasons} 16174 * This TeamWrapUpReasons object to allow cascading. 16175 */ 16176 get: function () { 16177 // set loaded to false so it will rebuild the collection after the get 16178 this._loaded = false; 16179 // reset collection 16180 this._collection = {}; 16181 // perform get 16182 this._synchronize(); 16183 return this; 16184 }, 16185 16186 /** 16187 * Set up the PutSuccessHandler for TeamWrapUpReasons 16188 * @param {Object} wrapUpReasons 16189 * @param {Object} contentBody 16190 * @param successHandler 16191 * @returns response 16192 */ 16193 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 16194 return function (rsp) { 16195 // Update internal structure based on response. Here we 16196 // inject the contentBody from the PUT request into the 16197 // rsp.object element to mimic a GET as a way to take 16198 // advantage of the existing _processResponse method. 16199 rsp.object = contentBody; 16200 16201 wrapUpReasons._processResponse(rsp); 16202 16203 //Remove the injected contentBody object before cascading response 16204 rsp.object = {}; 16205 16206 //cascade response back to consumer's response handler 16207 successHandler(rsp); 16208 }; 16209 }, 16210 16211 /** 16212 * Perform the REST API PUT call to update the reason code assignments for the team 16213 * @param {String Array} newValues 16214 * @param handlers 16215 * @returns {Object} this 16216 */ 16217 update: function (newValues, handlers) { 16218 this.isLoaded(); 16219 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16220 16221 contentBody[this.getRestType()] = { 16222 }; 16223 16224 for (i in newValues) { 16225 if (newValues.hasOwnProperty(i)) { 16226 innerObject = { 16227 "uri": newValues[i] 16228 }; 16229 contentBodyInner.push(innerObject); 16230 } 16231 } 16232 16233 contentBody[this.getRestType()] = { 16234 "WrapUpReason" : contentBodyInner 16235 }; 16236 16237 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16238 handlers = handlers || {}; 16239 16240 this.restRequest(this.getRestUrl(), { 16241 method: 'PUT', 16242 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16243 error: handlers.error, 16244 content: contentBody 16245 }); 16246 16247 return this; // Allow cascading 16248 } 16249 }); 16250 16251 window.finesse = window.finesse || {}; 16252 window.finesse.restservices = window.finesse.restservices || {}; 16253 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 16254 16255 return TeamWrapUpReasons; 16256 }); 16257 16258 /** 16259 * JavaScript representation of a TeamSignOutReasonCode. 16260 * 16261 * @requires finesse.clientservices.ClientServices 16262 * @requires Class 16263 * @requires finesse.FinesseBase 16264 * @requires finesse.restservices.RestBase 16265 */ 16266 16267 /** @private */ 16268 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 16269 var TeamSignOutReasonCode = RestBase.extend({ 16270 16271 /** 16272 * @class 16273 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 16274 * methods to operate on the object against the server. 16275 * 16276 * @param {Object} options 16277 * An object with the following properties:<ul> 16278 * <li><b>id:</b> The id of the object being constructed</li> 16279 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16280 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16281 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16282 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16283 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16284 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16285 * <li><b>content:</b> {String} Raw string of response</li> 16286 * <li><b>object:</b> {Object} Parsed object of response</li> 16287 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16288 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16289 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16290 * </ul></li> 16291 * </ul></li> 16292 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16293 * @constructs 16294 * @ignore 16295 **/ 16296 init: function (options) { 16297 this._super(options); 16298 }, 16299 16300 /** 16301 * @private 16302 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 16303 * @returns {Object} The TeamSignOutReasonCode class. 16304 */ 16305 getRestClass: function () { 16306 return TeamSignOutReasonCode; 16307 }, 16308 16309 /** 16310 * @private 16311 * Gets the REST type for the current object - this is a "ReasonCode". 16312 * @returns {String} The ReasonCode string. 16313 */ 16314 getRestType: function () { 16315 return "ReasonCode"; 16316 }, 16317 16318 /** 16319 * @private 16320 * Override default to indicate that this object doesn't support making 16321 * requests. 16322 */ 16323 supportsRequests: false, 16324 16325 /** 16326 * @private 16327 * Override default to indicate that this object doesn't support subscriptions. 16328 */ 16329 supportsSubscriptions: false, 16330 16331 /** 16332 * Getter for the category. 16333 * @returns {String} The category. 16334 */ 16335 getCategory: function () { 16336 this.isLoaded(); 16337 return this.getData().category; 16338 }, 16339 16340 /** 16341 * Getter for the code. 16342 * @returns {String} The code. 16343 */ 16344 getCode: function () { 16345 this.isLoaded(); 16346 return this.getData().code; 16347 }, 16348 16349 /** 16350 * Getter for the label. 16351 * @returns {String} The label. 16352 */ 16353 getLabel: function () { 16354 this.isLoaded(); 16355 return this.getData().label; 16356 }, 16357 16358 /** 16359 * Getter for the forAll value. 16360 * @returns {String} The forAll. 16361 */ 16362 getForAll: function () { 16363 this.isLoaded(); 16364 return this.getData().forAll; 16365 }, 16366 16367 /** 16368 * Getter for the Uri value. 16369 * @returns {String} The Uri. 16370 */ 16371 getUri: function () { 16372 this.isLoaded(); 16373 return this.getData().uri; 16374 } 16375 16376 }); 16377 16378 window.finesse = window.finesse || {}; 16379 window.finesse.restservices = window.finesse.restservices || {}; 16380 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 16381 16382 return TeamSignOutReasonCode; 16383 }); 16384 16385 /** 16386 * JavaScript representation of the TeamSignOutReasonCodes collection 16387 * object which contains a list of TeamSignOutReasonCode objects. 16388 * 16389 * @requires finesse.clientservices.ClientServices 16390 * @requires Class 16391 * @requires finesse.FinesseBase 16392 * @requires finesse.restservices.RestBase 16393 * @requires finesse.restservices.Dialog 16394 * @requires finesse.restservices.RestCollectionBase 16395 */ 16396 16397 /** @private */ 16398 define('restservices/TeamSignOutReasonCodes',[ 16399 'restservices/RestCollectionBase', 16400 'restservices/RestBase', 16401 'restservices/TeamSignOutReasonCode' 16402 ], 16403 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 16404 16405 var TeamSignOutReasonCodes = RestCollectionBase.extend({ 16406 /** 16407 * @class 16408 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 16409 * methods to operate on the object against the server. 16410 * 16411 * @param {Object} options 16412 * An object with the following properties:<ul> 16413 * <li><b>id:</b> The id of the object being constructed</li> 16414 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16415 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16416 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16417 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16418 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16419 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16420 * <li><b>content:</b> {String} Raw string of response</li> 16421 * <li><b>object:</b> {Object} Parsed object of response</li> 16422 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16423 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16424 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16425 * </ul></li> 16426 * </ul></li> 16427 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16428 * @constructs 16429 **/ 16430 init: function (options) { 16431 this._super(options); 16432 }, 16433 16434 /** 16435 * @private 16436 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 16437 */ 16438 getRestClass: function () { 16439 return TeamSignOutReasonCodes; 16440 }, 16441 16442 /** 16443 * @private 16444 * Gets the REST class for the objects that make up the collection. - this 16445 * is the TeamSignOutReasonCode class. 16446 */ 16447 getRestItemClass: function () { 16448 return TeamSignOutReasonCode; 16449 }, 16450 16451 /** 16452 * @private 16453 * Gets the REST type for the current object - this is a "ReasonCodes". 16454 */ 16455 getRestType: function () { 16456 return "ReasonCodes"; 16457 }, 16458 16459 /** 16460 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 16461 */ 16462 getRestUrl: function () { 16463 var restObj = this._restObj, restUrl = ""; 16464 16465 //Prepend the base REST object if one was provided. 16466 //Otherwise prepend with the default webapp name. 16467 if (restObj instanceof RestBase) { 16468 restUrl += restObj.getRestUrl(); 16469 } else { 16470 restUrl += "/finesse/api"; 16471 } 16472 //Append the REST type. 16473 restUrl += "/ReasonCodes?category=LOGOUT"; 16474 //Append ID if it is not undefined, null, or empty. 16475 if (this._id) { 16476 restUrl += "/" + this._id; 16477 } 16478 return restUrl; 16479 }, 16480 16481 /** 16482 * @private 16483 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16484 */ 16485 getRestItemType: function () { 16486 return "ReasonCode"; 16487 }, 16488 16489 /** 16490 * @private 16491 * Override default to indicates that the collection supports making requests. 16492 */ 16493 supportsRequests: true, 16494 16495 /** 16496 * @private 16497 * Override default to indicates that the collection does not subscribe to its objects. 16498 */ 16499 supportsRestItemSubscriptions: false, 16500 16501 /** 16502 * Retrieve the Sign Out Reason Codes. 16503 * 16504 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16505 * This TeamSignOutReasonCodes object to allow cascading. 16506 */ 16507 get: function () { 16508 // set loaded to false so it will rebuild the collection after the get 16509 this._loaded = false; 16510 // reset collection 16511 this._collection = {}; 16512 // perform get 16513 this._synchronize(); 16514 return this; 16515 }, 16516 16517 /* We only use PUT and GET on Reason Code team assignments 16518 * @param {Object} contact 16519 * @param {Object} contentBody 16520 * @param {Function} successHandler 16521 */ 16522 createPutSuccessHandler: function (contact, contentBody, successHandler) { 16523 return function (rsp) { 16524 // Update internal structure based on response. Here we 16525 // inject the contentBody from the PUT request into the 16526 // rsp.object element to mimic a GET as a way to take 16527 // advantage of the existing _processResponse method. 16528 rsp.object = contentBody; 16529 contact._processResponse(rsp); 16530 16531 //Remove the injected contentBody object before cascading response 16532 rsp.object = {}; 16533 16534 //cascade response back to consumer's response handler 16535 successHandler(rsp); 16536 }; 16537 }, 16538 16539 /** 16540 * Update - This should be all that is needed. 16541 * @param {Object} newValues 16542 * @param {Object} handlers 16543 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16544 * This TeamSignOutReasonCodes object to allow cascading. 16545 */ 16546 update: function (newValues, handlers) { 16547 this.isLoaded(); 16548 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16549 16550 contentBody[this.getRestType()] = { 16551 }; 16552 16553 for (i in newValues) { 16554 if (newValues.hasOwnProperty(i)) { 16555 innerObject = { 16556 "uri": newValues[i] 16557 }; 16558 contentBodyInner.push(innerObject); 16559 } 16560 } 16561 16562 contentBody[this.getRestType()] = { 16563 "ReasonCode" : contentBodyInner 16564 }; 16565 16566 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16567 handlers = handlers || {}; 16568 16569 this.restRequest(this.getRestUrl(), { 16570 method: 'PUT', 16571 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16572 error: handlers.error, 16573 content: contentBody 16574 }); 16575 16576 return this; // Allow cascading 16577 } 16578 16579 }); 16580 16581 window.finesse = window.finesse || {}; 16582 window.finesse.restservices = window.finesse.restservices || {}; 16583 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 16584 16585 return TeamSignOutReasonCodes; 16586 }); 16587 16588 /** 16589 * JavaScript representation of the Finesse PhoneBook Assignment object. 16590 * 16591 * @requires finesse.clientservices.ClientServices 16592 * @requires Class 16593 * @requires finesse.FinesseBase 16594 * @requires finesse.restservices.RestBase 16595 */ 16596 16597 /** 16598 * The following comment prevents JSLint errors concerning undefined global variables. 16599 * It tells JSLint that these identifiers are defined elsewhere. 16600 */ 16601 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16602 16603 /** The following comment is to prevent jslint errors about 16604 * using variables before they are defined. 16605 */ 16606 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16607 16608 /** @private */ 16609 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 16610 var TeamPhoneBook = RestBase.extend({ 16611 16612 /** 16613 * @class 16614 * JavaScript representation of a PhoneBook object. Also exposes 16615 * methods to operate on the object against the server. 16616 * 16617 * @param {Object} options 16618 * An object with the following properties:<ul> 16619 * <li><b>id:</b> The id of the object being constructed</li> 16620 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16621 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16622 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16623 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16624 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16625 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16626 * <li><b>content:</b> {String} Raw string of response</li> 16627 * <li><b>object:</b> {Object} Parsed object of response</li> 16628 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16629 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16630 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16631 * </ul></li> 16632 * </ul></li> 16633 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16634 * @constructs 16635 **/ 16636 init: function (options) { 16637 this._super(options); 16638 }, 16639 16640 /** 16641 * @private 16642 * Gets the REST class for the current object - this is the PhoneBooks class. 16643 * @returns {Object} The PhoneBooks class. 16644 */ 16645 getRestClass: function () { 16646 return TeamPhoneBook; 16647 }, 16648 16649 /** 16650 * @private 16651 * Gets the REST type for the current object - this is a "PhoneBook". 16652 * @returns {String} The PhoneBook string. 16653 */ 16654 getRestType: function () { 16655 return "PhoneBook"; 16656 }, 16657 16658 /** 16659 * @private 16660 * Override default to indicate that this object doesn't support making 16661 * requests. 16662 */ 16663 supportsRequests: false, 16664 16665 /** 16666 * @private 16667 * Override default to indicate that this object doesn't support subscriptions. 16668 */ 16669 supportsSubscriptions: false, 16670 16671 /** 16672 * Getter for the name. 16673 * @returns {String} The name. 16674 */ 16675 getName: function () { 16676 this.isLoaded(); 16677 return this.getData().name; 16678 }, 16679 16680 /** 16681 * Getter for the Uri value. 16682 * @returns {String} The Uri. 16683 */ 16684 getUri: function () { 16685 this.isLoaded(); 16686 return this.getData().uri; 16687 } 16688 16689 }); 16690 16691 window.finesse = window.finesse || {}; 16692 window.finesse.restservices = window.finesse.restservices || {}; 16693 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 16694 16695 return TeamPhoneBook; 16696 }); 16697 16698 /** 16699 * JavaScript representation of the Finesse PhoneBook Assignments collection 16700 * object which contains a list of Not Ready Reason Codes objects. 16701 * 16702 * @requires finesse.clientservices.ClientServices 16703 * @requires Class 16704 * @requires finesse.FinesseBase 16705 * @requires finesse.restservices.RestBase 16706 * @requires finesse.restservices.Dialog 16707 * @requires finesse.restservices.RestCollectionBase 16708 */ 16709 16710 /** 16711 * The following comment prevents JSLint errors concerning undefined global variables. 16712 * It tells JSLint that these identifiers are defined elsewhere. 16713 */ 16714 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16715 16716 /** The following comment is to prevent jslint errors about 16717 * using variables before they are defined. 16718 */ 16719 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16720 16721 /** @private */ 16722 define('restservices/TeamPhoneBooks',[ 16723 'restservices/RestCollectionBase', 16724 'restservices/RestBase', 16725 'restservices/TeamPhoneBook' 16726 ], 16727 function (RestCollectionBase, RestBase, TeamPhoneBook) { 16728 var TeamPhoneBooks = RestCollectionBase.extend({ 16729 16730 /** 16731 * @class 16732 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 16733 * methods to operate on the object against the server. 16734 * 16735 * @param {Object} options 16736 * An object with the following properties:<ul> 16737 * <li><b>id:</b> The id of the object being constructed</li> 16738 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16739 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16740 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16741 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16742 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16743 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16744 * <li><b>content:</b> {String} Raw string of response</li> 16745 * <li><b>object:</b> {Object} Parsed object of response</li> 16746 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16747 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16748 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16749 * </ul></li> 16750 * </ul></li> 16751 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16752 * @constructs 16753 **/ 16754 init: function (options) { 16755 this._super(options); 16756 }, 16757 16758 /** 16759 * @private 16760 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 16761 */ 16762 getRestClass: function () { 16763 return TeamPhoneBooks; 16764 }, 16765 16766 /** 16767 * @private 16768 * Gets the REST class for the objects that make up the collection. - this 16769 * is the TeamPhoneBooks class. 16770 */ 16771 getRestItemClass: function () { 16772 return TeamPhoneBook; 16773 }, 16774 16775 /** 16776 * @private 16777 * Gets the REST type for the current object - this is a "ReasonCodes". 16778 */ 16779 getRestType: function () { 16780 return "PhoneBooks"; 16781 }, 16782 16783 /** 16784 * Overrides the parent class. Returns the url for the PhoneBooks resource 16785 */ 16786 getRestUrl: function () { 16787 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 16788 var restObj = this._restObj, 16789 restUrl = ""; 16790 //Prepend the base REST object if one was provided. 16791 if (restObj instanceof RestBase) { 16792 restUrl += restObj.getRestUrl(); 16793 } 16794 //Otherwise prepend with the default webapp name. 16795 else { 16796 restUrl += "/finesse/api"; 16797 } 16798 //Append the REST type. 16799 restUrl += "/PhoneBooks"; 16800 //Append ID if it is not undefined, null, or empty. 16801 if (this._id) { 16802 restUrl += "/" + this._id; 16803 } 16804 return restUrl; 16805 }, 16806 16807 /** 16808 * @private 16809 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16810 */ 16811 getRestItemType: function () { 16812 return "PhoneBook"; 16813 }, 16814 16815 /** 16816 * @private 16817 * Override default to indicates that the collection supports making 16818 * requests. 16819 */ 16820 supportsRequests: true, 16821 16822 /** 16823 * @private 16824 * Override default to indicates that the collection subscribes to its objects. 16825 */ 16826 supportsRestItemSubscriptions: false, 16827 16828 /** 16829 * Retrieve the Not Ready Reason Codes. 16830 * 16831 * @returns {finesse.restservices.TeamPhoneBooks} 16832 * This TeamPhoneBooks object to allow cascading. 16833 */ 16834 get: function () { 16835 // set loaded to false so it will rebuild the collection after the get 16836 /** @private */ 16837 this._loaded = false; 16838 // reset collection 16839 /** @private */ 16840 this._collection = {}; 16841 // perform get 16842 this._synchronize(); 16843 return this; 16844 }, 16845 16846 /* We only use PUT and GET on Reason Code team assignments 16847 */ 16848 createPutSuccessHandler: function(contact, contentBody, successHandler){ 16849 return function (rsp) { 16850 // Update internal structure based on response. Here we 16851 // inject the contentBody from the PUT request into the 16852 // rsp.object element to mimic a GET as a way to take 16853 // advantage of the existing _processResponse method. 16854 rsp.object = contentBody; 16855 contact._processResponse(rsp); 16856 16857 //Remove the injected Contact object before cascading response 16858 rsp.object = {}; 16859 16860 //cascade response back to consumer's response handler 16861 successHandler(rsp); 16862 }; 16863 }, 16864 16865 /** 16866 * Update - This should be all that is needed. 16867 */ 16868 update: function (newValues, handlers) { 16869 this.isLoaded(); 16870 var contentBody = {}, contentBodyInner = [], i, innerObject; 16871 16872 contentBody[this.getRestType()] = { 16873 }; 16874 16875 for (i in newValues) { 16876 if (newValues.hasOwnProperty(i)) { 16877 innerObject = {}; 16878 innerObject = { 16879 "uri": newValues[i] 16880 }; 16881 contentBodyInner.push(innerObject); 16882 } 16883 } 16884 16885 contentBody[this.getRestType()] = { 16886 "PhoneBook" : contentBodyInner 16887 }; 16888 16889 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16890 handlers = handlers || {}; 16891 16892 this.restRequest(this.getRestUrl(), { 16893 method: 'PUT', 16894 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16895 error: handlers.error, 16896 content: contentBody 16897 }); 16898 16899 return this; // Allow cascading 16900 } 16901 16902 }); 16903 16904 window.finesse = window.finesse || {}; 16905 window.finesse.restservices = window.finesse.restservices || {}; 16906 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 16907 16908 return TeamPhoneBooks; 16909 }); 16910 16911 /** 16912 * JavaScript representation of the Finesse LayoutConfig object 16913 * @requires ClientServices 16914 * @requires finesse.FinesseBase 16915 * @requires finesse.restservices.RestBase 16916 */ 16917 16918 /** @private */ 16919 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 16920 /** @private */ 16921 var LayoutConfig = RestBase.extend({ 16922 16923 /** 16924 * @class 16925 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 16926 * on the object against the server. 16927 * 16928 * @param {String} id 16929 * Not required... 16930 * @param {Object} callbacks 16931 * An object containing callbacks for instantiation and runtime 16932 * @param {Function} callbacks.onLoad(this) 16933 * Callback to invoke upon successful instantiation 16934 * @param {Function} callbacks.onLoadError(rsp) 16935 * Callback to invoke on instantiation REST request error 16936 * as passed by finesse.clientservices.ClientServices.ajax() 16937 * { 16938 * status: {Number} The HTTP status code returned 16939 * content: {String} Raw string of response 16940 * object: {Object} Parsed object of response 16941 * error: {Object} Wrapped exception that was caught 16942 * error.errorType: {String} Type of error that was caught 16943 * error.errorMessage: {String} Message associated with error 16944 * } 16945 * @param {Function} callbacks.onChange(this) 16946 * Callback to invoke upon successful update 16947 * @param {Function} callbacks.onError(rsp) 16948 * Callback to invoke on update error (refresh or event) 16949 * as passed by finesse.clientservices.ClientServices.ajax() 16950 * { 16951 * status: {Number} The HTTP status code returned 16952 * content: {String} Raw string of response 16953 * object: {Object} Parsed object of response 16954 * error: {Object} Wrapped exception that was caught 16955 * error.errorType: {String} Type of error that was caught 16956 * error.errorMessage: {String} Message associated with error 16957 * } 16958 * 16959 * @constructs 16960 */ 16961 init: function (callbacks) { 16962 this._super("", callbacks); 16963 //when post is performed and id is empty 16964 /*if (id === "") { 16965 this._loaded = true; 16966 }*/ 16967 this._layoutxml = {}; 16968 }, 16969 16970 /** 16971 * Returns REST class of LayoutConfig object 16972 */ 16973 getRestClass: function () { 16974 return LayoutConfig; 16975 }, 16976 16977 /** 16978 * The type of this REST object is LayoutConfig 16979 */ 16980 getRestType: function () { 16981 return "LayoutConfig"; 16982 }, 16983 16984 /** 16985 * Gets the REST URL of this object. 16986 * 16987 * If the parent has an id, the id is appended. 16988 * On occasions of POST, it will not have an id. 16989 */ 16990 getRestUrl: function () { 16991 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 16992 /*if (this._id) { 16993 layoutUri = layoutUri + "/" + this._id; 16994 }*/ 16995 return layoutUri; 16996 }, 16997 16998 /** 16999 * This API does not support subscription 17000 */ 17001 supportsSubscriptions: false, 17002 17003 keepRestResponse: true, 17004 17005 17006 /** 17007 * Gets finesselayout.xml retrieved from the API call 17008 */ 17009 getLayoutxml: function () { 17010 this.isLoaded(); 17011 var layoutxml = this.getData().layoutxml; 17012 17013 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 17014 layoutxml = layoutxml.replace(/&/g,"&"); 17015 17016 return layoutxml; 17017 }, 17018 17019 /** 17020 * Gets the type of this LayoutConfig object 17021 */ 17022 /* 17023 getType: function () { 17024 this.isLoaded(); 17025 return this.getData().type; 17026 },*/ 17027 17028 /** 17029 * Retrieve the LayoutConfig settings. 17030 * If the id is not provided the API call will fail. 17031 * @returns {LayoutConfig} 17032 * This LayoutConfig object to allow cascading. 17033 */ 17034 get: function () { 17035 this._synchronize(); 17036 return this; 17037 }, 17038 17039 /** 17040 * Closure handle updating of the internal data for the LayoutConfig object 17041 * upon a successful update (PUT) request before calling the intended 17042 * success handler provided by the consumer 17043 * 17044 * @param {Object} 17045 * layoutconfig Reference to this LayoutConfig object 17046 * @param {Object} 17047 * LayoutConfig Object that contains the settings to be 17048 * submitted in the api request 17049 * @param {Function} 17050 * successHandler The success handler specified by the consumer 17051 * of this object 17052 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 17053 */ 17054 17055 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17056 return function (rsp) { 17057 // Update internal structure based on response. Here we 17058 // inject the contentBody from the PUT request into the 17059 // rsp.object element to mimic a GET as a way to take 17060 // advantage of the existing _processResponse method. 17061 rsp.content = contentBody; 17062 rsp.object.LayoutConfig = {}; 17063 rsp.object.LayoutConfig.finesseLayout = contentBody; 17064 layoutconfig._processResponse(rsp); 17065 17066 //Remove the injected layoutConfig object before cascading response 17067 rsp.object.LayoutConfig = {}; 17068 17069 //cascade response back to consumer's response handler 17070 successHandler(rsp); 17071 }; 17072 }, 17073 17074 /** 17075 * Update LayoutConfig 17076 * @param {Object} finesselayout 17077 * The XML for FinesseLayout being stored 17078 * 17079 * @param {Object} handlers 17080 * An object containing callback handlers for the request. Optional. 17081 * @param {Function} options.success(rsp) 17082 * A callback function to be invoked for a successful request. 17083 * { 17084 * status: {Number} The HTTP status code returned 17085 * content: {String} Raw string of response 17086 * object: {Object} Parsed object of response 17087 * } 17088 * @param {Function} options.error(rsp) 17089 * A callback function to be invoked for an unsuccessful request. 17090 * { 17091 * status: {Number} The HTTP status code returned 17092 * content: {String} Raw string of response 17093 * object: {Object} Parsed object of response (HTTP errors) 17094 * error: {Object} Wrapped exception that was caught 17095 * error.errorType: {String} Type of error that was caught 17096 * error.errorMessage: {String} Message associated with error 17097 * } 17098 * @returns {finesse.restservices.LayoutConfig} 17099 * This LayoutConfig object to allow cascading 17100 */ 17101 17102 update: function (layoutxml, handlers) { 17103 this.isLoaded(); 17104 17105 17106 var contentBody = {}, 17107 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17108 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17109 17110 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17111 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 17112 17113 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17114 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17115 17116 contentBody[this.getRestType()] = { 17117 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17118 }; 17119 17120 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17121 handlers = handlers || {}; 17122 17123 this.restRequest(this.getRestUrl(), { 17124 method: 'PUT', 17125 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 17126 error: handlers.error, 17127 content: contentBody 17128 }); 17129 17130 return this; // Allow cascading 17131 } 17132 17133 /** 17134 *TODO createPostSuccessHandler needs to be debugged to make it working 17135 * Closure handle creating new LayoutConfig object 17136 * upon a successful create (POST) request before calling the intended 17137 * success handler provided by the consumer 17138 * 17139 * @param {Object} 17140 * layoutconfig Reference to this LayoutConfig object 17141 * @param {Object} 17142 * LayoutConfig Object that contains the settings to be 17143 * submitted in the api request 17144 * @param {Function} 17145 * successHandler The success handler specified by the consumer 17146 * of this object 17147 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 17148 */ 17149 /* 17150 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17151 return function (rsp) { 17152 17153 rsp.object = contentBody; 17154 layoutconfig._processResponse(rsp); 17155 17156 //Remove the injected layoutConfig object before cascading response 17157 rsp.object = {}; 17158 17159 //cascade response back to consumer's response handler 17160 successHandler(rsp); 17161 }; 17162 }, */ 17163 17164 /** 17165 * TODO Method needs to be debugged to make POST working 17166 * Add LayoutConfig 17167 * @param {Object} finesselayout 17168 * The XML for FinesseLayout being stored 17169 * 17170 * @param {Object} handlers 17171 * An object containing callback handlers for the request. Optional. 17172 * @param {Function} options.success(rsp) 17173 * A callback function to be invoked for a successful request. 17174 * { 17175 * status: {Number} The HTTP status code returned 17176 * content: {String} Raw string of response 17177 * object: {Object} Parsed object of response 17178 * } 17179 * @param {Function} options.error(rsp) 17180 * A callback function to be invoked for an unsuccessful request. 17181 * { 17182 * status: {Number} The HTTP status code returned 17183 * content: {String} Raw string of response 17184 * object: {Object} Parsed object of response (HTTP errors) 17185 * error: {Object} Wrapped exception that was caught 17186 * error.errorType: {String} Type of error that was caught 17187 * error.errorMessage: {String} Message associated with error 17188 * } 17189 * @returns {finesse.restservices.LayoutConfig} 17190 * This LayoutConfig object to allow cascading 17191 */ 17192 /* 17193 add: function (layoutxml, handlers) { 17194 this.isLoaded(); 17195 var contentBody = {}; 17196 17197 17198 contentBody[this.getRestType()] = { 17199 "layoutxml": layoutxml, 17200 "type": "current" 17201 }; 17202 17203 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17204 handlers = handlers || {}; 17205 17206 this.restRequest(this.getRestUrl(), { 17207 method: 'POST', 17208 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 17209 error: handlers.error, 17210 content: contentBody 17211 }); 17212 17213 return this; // Allow cascading 17214 } */ 17215 }); 17216 17217 window.finesse = window.finesse || {}; 17218 window.finesse.restservices = window.finesse.restservices || {}; 17219 window.finesse.restservices.LayoutConfig = LayoutConfig; 17220 17221 return LayoutConfig; 17222 17223 }); 17224 17225 /** 17226 * JavaScript representation of the Finesse LayoutConfig object for a Team. 17227 * 17228 * @requires finesse.clientservices.ClientServices 17229 * @requires Class 17230 * @requires finesse.FinesseBase 17231 * @requires finesse.restservices.RestBase 17232 * @requires finesse.utilities.Utilities 17233 * @requires finesse.restservices.LayoutConfig 17234 */ 17235 17236 /** The following comment is to prevent jslint errors about 17237 * using variables before they are defined. 17238 */ 17239 /*global Exception */ 17240 17241 /** @private */ 17242 define('restservices/TeamLayoutConfig',[ 17243 'restservices/RestBase', 17244 'utilities/Utilities', 17245 'restservices/LayoutConfig' 17246 ], 17247 function (RestBase, Utilities, LayoutConfig) { 17248 17249 var TeamLayoutConfig = RestBase.extend({ 17250 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 17251 keepRestResponse: true, 17252 17253 /** 17254 * @class 17255 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 17256 * methods to operate on the object against the server. 17257 * 17258 * @param {Object} options 17259 * An object with the following properties:<ul> 17260 * <li><b>id:</b> The id of the object being constructed</li> 17261 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17262 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17263 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17264 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17265 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17266 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17267 * <li><b>content:</b> {String} Raw string of response</li> 17268 * <li><b>object:</b> {Object} Parsed object of response</li> 17269 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17270 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17271 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17272 * </ul></li> 17273 * </ul></li> 17274 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17275 * @constructs 17276 **/ 17277 init: function (options) { 17278 this._super(options); 17279 }, 17280 17281 /** 17282 * @private 17283 * Gets the REST class for the current object - this is the LayoutConfigs class. 17284 * @returns {Object} The LayoutConfigs class. 17285 */ 17286 getRestClass: function () { 17287 return TeamLayoutConfig; 17288 }, 17289 17290 /** 17291 * @private 17292 * Gets the REST type for the current object - this is a "LayoutConfig". 17293 * @returns {String} The LayoutConfig string. 17294 */ 17295 getRestType: function () { 17296 return "TeamLayoutConfig"; 17297 }, 17298 17299 /** 17300 * @private 17301 * Override default to indicate that this object doesn't support making 17302 * requests. 17303 */ 17304 supportsRequests: false, 17305 17306 /** 17307 * @private 17308 * Override default to indicate that this object doesn't support subscriptions. 17309 */ 17310 supportsSubscriptions: false, 17311 17312 /** 17313 * Getter for the category. 17314 * @returns {String} The category. 17315 */ 17316 getLayoutXML: function () { 17317 this.isLoaded(); 17318 var layoutxml = this.getData().layoutxml; 17319 17320 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 17321 layoutxml = layoutxml.replace(/&/g,"&"); 17322 17323 return layoutxml; 17324 }, 17325 17326 /** 17327 * Getter for the code. 17328 * @returns {String} The code. 17329 */ 17330 getUseDefault: function () { 17331 this.isLoaded(); 17332 return this.getData().useDefault; 17333 }, 17334 17335 /** 17336 * Retrieve the TeamLayoutConfig. 17337 * 17338 * @returns {finesse.restservices.TeamLayoutConfig} 17339 */ 17340 get: function () { 17341 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17342 this._id = "0"; 17343 // set loaded to false so it will rebuild the collection after the get 17344 this._loaded = false; 17345 // reset collection 17346 this._collection = {}; 17347 // perform get 17348 this._synchronize(); 17349 return this; 17350 }, 17351 17352 createPutSuccessHandler: function(contact, contentBody, successHandler){ 17353 return function (rsp) { 17354 // Update internal structure based on response. Here we 17355 // inject the contentBody from the PUT request into the 17356 // rsp.object element to mimic a GET as a way to take 17357 // advantage of the existing _processResponse method. 17358 rsp.object = contentBody; 17359 contact._processResponse(rsp); 17360 17361 //Remove the injected Contact object before cascading response 17362 rsp.object = {}; 17363 17364 //cascade response back to consumer's response handler 17365 successHandler(rsp); 17366 }; 17367 }, 17368 17369 put: function (newValues, handlers) { 17370 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17371 this._id = "0"; 17372 this.isLoaded(); 17373 17374 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17375 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 17376 contentBody = {}, 17377 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17378 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17379 17380 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17381 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17382 17383 contentBody[this.getRestType()] = { 17384 "useDefault": newValues.useDefault, 17385 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 17386 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17387 }; 17388 17389 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17390 handlers = handlers || {}; 17391 17392 this.restRequest(this.getRestUrl(), { 17393 method: 'PUT', 17394 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17395 error: handlers.error, 17396 content: contentBody 17397 }); 17398 17399 return this; // Allow cascading 17400 }, 17401 17402 getRestUrl: function(){ 17403 // return team's url + /LayoutConfig 17404 // eg: /api/Team/1/LayoutConfig 17405 if(this._restObj === undefined){ 17406 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 17407 } 17408 return this._restObj.getRestUrl() + '/LayoutConfig'; 17409 } 17410 17411 }); 17412 17413 window.finesse = window.finesse || {}; 17414 window.finesse.restservices = window.finesse.restservices || {}; 17415 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 17416 17417 return TeamLayoutConfig; 17418 }); 17419 17420 /** 17421 * JavaScript representation of a TeamWorkflow. 17422 * 17423 * @requires finesse.clientservices.ClientServices 17424 * @requires Class 17425 * @requires finesse.FinesseBase 17426 * @requires finesse.restservices.RestBase 17427 */ 17428 /** @private */ 17429 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 17430 17431 var TeamWorkflow = RestBase.extend({ 17432 17433 /** 17434 * @class 17435 * JavaScript representation of a TeamWorkflow object. Also exposes 17436 * methods to operate on the object against the server. 17437 * 17438 * @param {Object} options 17439 * An object with the following properties:<ul> 17440 * <li><b>id:</b> The id of the object being constructed</li> 17441 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17442 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17443 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17444 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17445 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17446 * <li><b>status:</b> {Number} The HTTP status description returned</li> 17447 * <li><b>content:</b> {String} Raw string of response</li> 17448 * <li><b>object:</b> {Object} Parsed object of response</li> 17449 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17450 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17451 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17452 * </ul></li> 17453 * </ul></li> 17454 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17455 * @constructs 17456 **/ 17457 init: function (options) { 17458 this._super(options); 17459 }, 17460 17461 /** 17462 * @private 17463 * Gets the REST class for the current object - this is the TeamWorkflow class. 17464 * @returns {Object} The TeamWorkflow class. 17465 */ 17466 getRestClass: function () { 17467 return TeamWorkflow; 17468 }, 17469 17470 /** 17471 * @private 17472 * Gets the REST type for the current object - this is a "Workflow". 17473 * @returns {String} The Workflow string. 17474 */ 17475 getRestType: function () { 17476 return "Workflow"; 17477 }, 17478 17479 /** 17480 * @private 17481 * Override default to indicate that this object doesn't support making 17482 * requests. 17483 */ 17484 supportsRequests: false, 17485 17486 /** 17487 * @private 17488 * Override default to indicate that this object doesn't support subscriptions. 17489 */ 17490 supportsSubscriptions: false, 17491 17492 /** 17493 * Getter for the name. 17494 * @returns {String} The name. 17495 */ 17496 getName: function () { 17497 this.isLoaded(); 17498 return this.getData().name; 17499 }, 17500 17501 /** 17502 * Getter for the description. 17503 * @returns {String} The description. 17504 */ 17505 getDescription: function () { 17506 this.isLoaded(); 17507 return this.getData().description; 17508 }, 17509 17510 /** 17511 * Getter for the Uri value. 17512 * @returns {String} The Uri. 17513 */ 17514 getUri: function () { 17515 this.isLoaded(); 17516 return this.getData().uri; 17517 } 17518 17519 }); 17520 17521 window.finesse = window.finesse || {}; 17522 window.finesse.restservices = window.finesse.restservices || {}; 17523 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 17524 17525 return TeamWorkflow; 17526 }); 17527 17528 /** 17529 * JavaScript representation of the TeamWorkflows collection 17530 * object which contains a list of TeamWorkflow objects. 17531 * 17532 * @requires finesse.clientservices.ClientServices 17533 * @requires Class 17534 * @requires finesse.FinesseBase 17535 * @requires finesse.restservices.RestBase 17536 * @requires finesse.restservices.Dialog 17537 * @requires finesse.restservices.RestCollectionBase 17538 */ 17539 /** @private */ 17540 define('restservices/TeamWorkflows',[ 17541 'restservices/RestCollectionBase', 17542 'restservices/TeamWorkflow', 17543 'restservices/RestBase' 17544 ], 17545 function (RestCollectionBase, TeamWorkflow, RestBase) { 17546 17547 var TeamWorkflows = RestCollectionBase.extend({ 17548 17549 /** 17550 * @class 17551 * JavaScript representation of a TeamWorkflows collection object. Also exposes 17552 * methods to operate on the object against the server. 17553 * 17554 * @param {Object} options 17555 * An object with the following properties:<ul> 17556 * <li><b>id:</b> The id of the object being constructed</li> 17557 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17558 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17559 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17560 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17561 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17562 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17563 * <li><b>content:</b> {String} Raw string of response</li> 17564 * <li><b>object:</b> {Object} Parsed object of response</li> 17565 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17566 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17567 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17568 * </ul></li> 17569 * </ul></li> 17570 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17571 * @constructs 17572 **/ 17573 init: function (options) { 17574 this._super(options); 17575 }, 17576 17577 /** 17578 * @private 17579 * Gets the REST class for the current object - this is the TeamWorkflows class. 17580 */ 17581 getRestClass: function () { 17582 return TeamWorkflows; 17583 }, 17584 17585 /** 17586 * @private 17587 * Gets the REST class for the objects that make up the collection. - this 17588 * is the TeamWorkflow class. 17589 */ 17590 getRestItemClass: function () { 17591 return TeamWorkflow; 17592 }, 17593 17594 /** 17595 * @private 17596 * Gets the REST type for the current object - this is a "Workflows". 17597 */ 17598 getRestType: function () { 17599 return "Workflows"; 17600 }, 17601 17602 /** 17603 * Overrides the parent class. Returns the url for the Workflows resource 17604 */ 17605 getRestUrl: function () { 17606 var restObj = this._restObj, restUrl = ""; 17607 17608 //Prepend the base REST object if one was provided. 17609 //Otherwise prepend with the default webapp name. 17610 if (restObj instanceof RestBase) { 17611 restUrl += restObj.getRestUrl(); 17612 } else { 17613 restUrl += "/finesse/api/Team"; 17614 } 17615 //Append ID if it is not undefined, null, or empty. 17616 if (this._id) { 17617 restUrl += "/" + this._id; 17618 } 17619 //Append the REST type. 17620 restUrl += "/Workflows"; 17621 17622 return restUrl; 17623 }, 17624 17625 /** 17626 * @private 17627 * Gets the REST type for the objects that make up the collection - this is "Workflow". 17628 */ 17629 getRestItemType: function () { 17630 return "Workflow"; 17631 }, 17632 17633 /** 17634 * @private 17635 * Override default to indicates that the collection supports making requests. 17636 */ 17637 supportsRequests: true, 17638 17639 /** 17640 * @private 17641 * Override default to indicates that the collection does not subscribe to its objects. 17642 */ 17643 supportsRestItemSubscriptions: false, 17644 17645 /** 17646 * Retrieve the Sign Out Reason Codes. 17647 * 17648 * @returns {finesse.restservices.TeamWorkflows} 17649 * This TeamWorkflows object to allow cascading. 17650 */ 17651 get: function () { 17652 // set loaded to false so it will rebuild the collection after the get 17653 this._loaded = false; 17654 // reset collection 17655 this._collection = {}; 17656 // perform get 17657 this._synchronize(); 17658 return this; 17659 }, 17660 17661 /* We only use PUT and GET on Reason Code team assignments 17662 * @param {Object} contact 17663 * @param {Object} contentBody 17664 * @param {Function} successHandler 17665 */ 17666 createPutSuccessHandler: function (contact, contentBody, successHandler) { 17667 return function (rsp) { 17668 // Update internal structure based on response. Here we 17669 // inject the contentBody from the PUT request into the 17670 // rsp.object element to mimic a GET as a way to take 17671 // advantage of the existing _processResponse method. 17672 rsp.object = contentBody; 17673 contact._processResponse(rsp); 17674 17675 //Remove the injected contentBody object before cascading response 17676 rsp.object = {}; 17677 17678 //cascade response back to consumer's response handler 17679 successHandler(rsp); 17680 }; 17681 }, 17682 17683 /** 17684 * Update - This should be all that is needed. 17685 * @param {Object} newValues 17686 * @param {Object} handlers 17687 * @returns {finesse.restservices.TeamWorkflows} 17688 * This TeamWorkflows object to allow cascading. 17689 */ 17690 update: function (newValues, handlers) { 17691 this.isLoaded(); 17692 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17693 17694 contentBody[this.getRestType()] = { 17695 }; 17696 17697 for (i in newValues) { 17698 if (newValues.hasOwnProperty(i)) { 17699 innerObject = { 17700 "uri": newValues[i] 17701 }; 17702 contentBodyInner.push(innerObject); 17703 } 17704 } 17705 17706 contentBody[this.getRestType()] = { 17707 "Workflow" : contentBodyInner 17708 }; 17709 17710 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17711 handlers = handlers || {}; 17712 17713 this.restRequest(this.getRestUrl(), { 17714 method: 'PUT', 17715 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17716 error: handlers.error, 17717 content: contentBody 17718 }); 17719 17720 return this; // Allow cascading 17721 } 17722 17723 }); 17724 17725 window.finesse = window.finesse || {}; 17726 window.finesse.restservices = window.finesse.restservices || {}; 17727 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 17728 17729 return TeamWorkflows; 17730 }); 17731 17732 /** 17733 * JavaScript representation of the Finesse Team REST object. 17734 * 17735 * @requires finesse.clientservices.ClientServices 17736 * @requires Class 17737 * @requires finesse.FinesseBase 17738 * @requires finesse.restservices.RestBase 17739 * @requires finesse.restservices.RestCollectionBase 17740 * @requires finesse.restservices.User 17741 * @requires finesse.restservices.Users 17742 */ 17743 17744 /** 17745 * The following comment prevents JSLint errors concerning undefined global variables. 17746 * It tells JSLint that these identifiers are defined elsewhere. 17747 */ 17748 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 17749 17750 /** The following comment is to prevent jslint errors about 17751 * using variables before they are defined. 17752 */ 17753 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 17754 17755 /** @private */ 17756 define('restservices/Team',[ 17757 'restservices/RestBase', 17758 'utilities/Utilities', 17759 'restservices/Users', 17760 'restservices/TeamNotReadyReasonCodes', 17761 'restservices/TeamWrapUpReasons', 17762 'restservices/TeamSignOutReasonCodes', 17763 'restservices/TeamPhoneBooks', 17764 'restservices/TeamLayoutConfig', 17765 'restservices/TeamWorkflows' 17766 ], 17767 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows) { 17768 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 17769 17770 _teamLayoutConfig: null, 17771 17772 /** 17773 * @class 17774 * A Team is a set of Agent Users, typically supervised by one or more Supervisor Users. 17775 * 17776 * @augments finesse.restservices.RestBase 17777 * @see finesse.restservices.User#getSupervisedTeams 17778 * @see finesse.restservices.Users 17779 * @constructs 17780 */ 17781 _fakeConstuctor: function () { 17782 /* This is here to hide the real init constructor from the public docs */ 17783 }, 17784 17785 /** 17786 * @private 17787 * @class 17788 * JavaScript representation of a Team object. Also exposes methods to operate 17789 * on the object against the server. 17790 * 17791 * @param {Object} options 17792 * An object with the following properties:<ul> 17793 * <li><b>id:</b> The id of the object being constructed</li> 17794 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17795 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17796 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17797 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17798 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17799 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17800 * <li><b>content:</b> {String} Raw string of response</li> 17801 * <li><b>object:</b> {Object} Parsed object of response</li> 17802 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17803 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17804 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17805 * </ul></li> 17806 * </ul></li> 17807 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17808 **/ 17809 init: function (options) { 17810 this._super(options); 17811 }, 17812 17813 /** 17814 * @private 17815 * Gets the REST class for the current object - this is the Team class. 17816 * @returns {Object} The Team constructor. 17817 */ 17818 getRestClass: function () { 17819 return finesse.restesrvices.Team; 17820 }, 17821 17822 /** 17823 * @private 17824 * Gets the REST type for the current object - this is a "Team". 17825 * @returns {String} The Team string. 17826 */ 17827 getRestType: function () { 17828 return "Team"; 17829 }, 17830 17831 /** 17832 * @private 17833 * Override default to indicate that this object doesn't support making 17834 * requests. 17835 */ 17836 supportsSubscriptions: false, 17837 17838 /** 17839 * Getter for the team id. 17840 * @returns {String} The team id. 17841 */ 17842 getId: function () { 17843 this.isLoaded(); 17844 return this.getData().id; 17845 }, 17846 17847 /** 17848 * Getter for the team name. 17849 * @returns {String} The team name 17850 */ 17851 getName: function () { 17852 this.isLoaded(); 17853 return this.getData().name; 17854 }, 17855 17856 /** 17857 * @private 17858 * Getter for the team uri. 17859 * @returns {String} The team uri 17860 */ 17861 getUri: function () { 17862 this.isLoaded(); 17863 return this.getData().uri; 17864 }, 17865 17866 /** 17867 * Constructs and returns a collection of Users. 17868 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers. 17869 * @returns {finesse.restservices.Users} Users collection of User objects. 17870 */ 17871 getUsers: function (options) { 17872 this.isLoaded(); 17873 options = options || {}; 17874 17875 options.parentObj = this; 17876 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 17877 // for Users needs the "Users" key to validate the provided payload matches the class type. 17878 options.data = this.getData(); 17879 17880 return new Users(options); 17881 }, 17882 17883 /** 17884 * @private 17885 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 17886 * @param callbacks 17887 * @returns {teamNotReadyReasonCodes} 17888 * A teamNotReadyReasonCodes collection object. 17889 */ 17890 getTeamNotReadyReasonCodes: function (callbacks) { 17891 var options = callbacks || {}; 17892 options.parentObj = this; 17893 this.isLoaded(); 17894 17895 if (!this._teamNotReadyReasonCodes) { 17896 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 17897 } 17898 17899 return this._teamNotReadyReasonCodes; 17900 }, 17901 17902 /** 17903 * @private 17904 * Getter for a teamWrapUpReasons collection object that is associated with Team. 17905 * @param callbacks 17906 * @returns {teamWrapUpReasons} 17907 * A teamWrapUpReasons collection object. 17908 */ 17909 getTeamWrapUpReasons: function (callbacks) { 17910 var options = callbacks || {}; 17911 options.parentObj = this; 17912 this.isLoaded(); 17913 17914 if (!this._teamWrapUpReasons) { 17915 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 17916 } 17917 17918 return this._teamWrapUpReasons; 17919 }, 17920 17921 /** 17922 * @private 17923 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 17924 * @param callbacks 17925 * @returns {teamSignOutReasonCodes} 17926 * A teamSignOutReasonCodes collection object. 17927 */ 17928 17929 getTeamSignOutReasonCodes: function (callbacks) { 17930 var options = callbacks || {}; 17931 options.parentObj = this; 17932 this.isLoaded(); 17933 17934 if (!this._teamSignOutReasonCodes) { 17935 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 17936 } 17937 17938 return this._teamSignOutReasonCodes; 17939 }, 17940 17941 /** 17942 * @private 17943 * Getter for a teamPhoneBooks collection object that is associated with Team. 17944 * @param callbacks 17945 * @returns {teamPhoneBooks} 17946 * A teamPhoneBooks collection object. 17947 */ 17948 getTeamPhoneBooks: function (callbacks) { 17949 var options = callbacks || {}; 17950 options.parentObj = this; 17951 this.isLoaded(); 17952 17953 if (!this._phonebooks) { 17954 this._phonebooks = new TeamPhoneBooks(options); 17955 } 17956 17957 return this._phonebooks; 17958 }, 17959 17960 /** 17961 * @private 17962 * Getter for a teamWorkflows collection object that is associated with Team. 17963 * @param callbacks 17964 * @returns {teamWorkflows} 17965 * A teamWorkflows collection object. 17966 */ 17967 getTeamWorkflows: function (callbacks) { 17968 var options = callbacks || {}; 17969 options.parentObj = this; 17970 this.isLoaded(); 17971 17972 if (!this._workflows) { 17973 this._workflows = new TeamWorkflows(options); 17974 } 17975 17976 return this._workflows; 17977 }, 17978 17979 /** 17980 * @private 17981 * Getter for a teamLayoutConfig object that is associated with Team. 17982 * @param callbacks 17983 * @returns {teamLayoutConfig} 17984 */ 17985 getTeamLayoutConfig: function (callbacks) { 17986 var options = callbacks || {}; 17987 options.parentObj = this; 17988 this.isLoaded(); 17989 17990 if (this._teamLayoutConfig === null) { 17991 this._teamLayoutConfig = new TeamLayoutConfig(options); 17992 } 17993 17994 return this._teamLayoutConfig; 17995 } 17996 17997 }); 17998 17999 window.finesse = window.finesse || {}; 18000 window.finesse.restservices = window.finesse.restservices || {}; 18001 window.finesse.restservices.Team = Team; 18002 18003 return Team; 18004 }); 18005 18006 /** 18007 * JavaScript representation of the Finesse Teams collection. 18008 * object which contains a list of Team objects 18009 * @requires finesse.clientservices.ClientServices 18010 * @requires Class 18011 * @requires finesse.FinesseBase 18012 * @requires finesse.restservices.RestBase 18013 * @requires finesse.restservices.RestCollectionBase 18014 */ 18015 18016 /** @private */ 18017 define('restservices/Teams',[ 18018 'restservices/RestCollectionBase', 18019 'restservices/Team' 18020 ], 18021 function (RestCollectionBase, Team) { 18022 /** @private */ 18023 var Teams = RestCollectionBase.extend({ 18024 18025 /** 18026 * @class 18027 * JavaScript representation of a Teams collection object. Also exposes methods to operate 18028 * on the object against the server. 18029 * 18030 * @param {Object} options 18031 * An object with the following properties:<ul> 18032 * <li><b>id:</b> The id of the object being constructed</li> 18033 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18034 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18035 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18036 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18037 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18038 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18039 * <li><b>content:</b> {String} Raw string of response</li> 18040 * <li><b>object:</b> {Object} Parsed object of response</li> 18041 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18042 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18043 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18044 * </ul></li> 18045 * </ul></li> 18046 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18047 * @constructs 18048 **/ 18049 init: function (options) { 18050 this._super(options); 18051 }, 18052 18053 /** 18054 * @private 18055 * Gets the REST class for the current object - this is the Teams class. 18056 * @returns {Object} The Teams constructor. 18057 */ 18058 getRestClass: function () { 18059 return Teams; 18060 }, 18061 18062 /** 18063 * @private 18064 * Gets the REST class for the objects that make up the collection. - this 18065 * is the Team class. 18066 */ 18067 getRestItemClass: function () { 18068 return Team; 18069 }, 18070 18071 /** 18072 * @private 18073 * Gets the REST type for the current object - this is a "Teams". 18074 * @returns {String} The Teams string. 18075 */ 18076 getRestType: function () { 18077 return "Teams"; 18078 }, 18079 18080 /** 18081 * @private 18082 * Gets the REST type for the objects that make up the collection - this is "Team". 18083 */ 18084 getRestItemType: function () { 18085 return "Team"; 18086 }, 18087 18088 /** 18089 * @private 18090 * Override default to indicates that the collection supports making 18091 * requests. 18092 */ 18093 supportsRequests: true, 18094 18095 /** 18096 * @private 18097 * Override default to indicate that this object doesn't support subscriptions. 18098 */ 18099 supportsRestItemSubscriptions: false, 18100 18101 /** 18102 * @private 18103 * Retrieve the Teams. This call will re-query the server and refresh the collection. 18104 * 18105 * @returns {finesse.restservices.Teams} 18106 * This Teams object to allow cascading. 18107 */ 18108 get: function () { 18109 // set loaded to false so it will rebuild the collection after the get 18110 this._loaded = false; 18111 // reset collection 18112 this._collection = {}; 18113 // perform get 18114 this._synchronize(); 18115 return this; 18116 } 18117 18118 }); 18119 18120 window.finesse = window.finesse || {}; 18121 window.finesse.restservices = window.finesse.restservices || {}; 18122 window.finesse.restservices.Teams = Teams; 18123 18124 return Teams; 18125 }); 18126 18127 /** 18128 * JavaScript representation of the Finesse SystemInfo object 18129 * 18130 * @requires finesse.clientservices.ClientServices 18131 * @requires Class 18132 * @requires finesse.FinesseBase 18133 * @requires finesse.restservices.RestBase 18134 */ 18135 18136 /** @private */ 18137 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 18138 18139 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 18140 /** 18141 * @private 18142 * Returns whether this object supports subscriptions 18143 */ 18144 supportsSubscriptions: false, 18145 18146 doNotRefresh: true, 18147 18148 /** 18149 * @class 18150 * JavaScript representation of a SystemInfo object. 18151 * 18152 * @augments finesse.restservices.RestBase 18153 * @see finesse.restservices.SystemInfo.Statuses 18154 * @constructs 18155 */ 18156 _fakeConstuctor: function () { 18157 /* This is here to hide the real init constructor from the public docs */ 18158 }, 18159 18160 /** 18161 * @private 18162 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 18163 * on the object against the server. 18164 * 18165 * @param {Object} options 18166 * An object with the following properties:<ul> 18167 * <li><b>id:</b> The id of the object being constructed</li> 18168 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18169 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18170 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18171 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18172 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18173 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18174 * <li><b>content:</b> {String} Raw string of response</li> 18175 * <li><b>object:</b> {Object} Parsed object of response</li> 18176 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18177 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18178 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18179 * </ul></li> 18180 * </ul></li> 18181 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18182 **/ 18183 init: function (id, callbacks, restObj) 18184 { 18185 this._super(id, callbacks, restObj); 18186 }, 18187 18188 /** 18189 * @private 18190 * Gets the REST class for the current object - this is the SystemInfo object. 18191 */ 18192 getRestClass: function () { 18193 return SystemInfo; 18194 }, 18195 18196 /** 18197 * @private 18198 * Gets the REST type for the current object - this is a "SystemInfo". 18199 */ 18200 getRestType: function () 18201 { 18202 return "SystemInfo"; 18203 }, 18204 18205 _validate: function (obj) 18206 { 18207 return true; 18208 }, 18209 18210 /** 18211 * Returns the status of the Finesse system. 18212 * IN_SERVICE if the Finesse API reports that it is in service, 18213 * OUT_OF_SERVICE otherwise. 18214 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 18215 */ 18216 getStatus: function () { 18217 this.isLoaded(); 18218 return this.getData().status; 18219 }, 18220 18221 /** 18222 * Returns the reason due to which Finesse is OUT OF SERVICE. 18223 * It returns empty string when Finesse status is IN_SERVICE. 18224 * @returns {String} statusReason if finesse is OUT OF SERVICE , or empty string otherwise. 18225 */ 18226 getStatusReason: function () { 18227 this.isLoaded(); 18228 return this.getData().statusReason; 18229 }, 18230 18231 /** 18232 * Returns the current timestamp from this SystemInfo object. 18233 * This is used to calculate time drift delta between server and client. 18234 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 18235 */ 18236 getCurrentTimestamp: function () { 18237 this.isLoaded(); 18238 return this.getData().currentTimestamp; 18239 }, 18240 18241 /** 18242 * Getter for the xmpp domain of the system. 18243 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 18244 */ 18245 getXmppDomain: function () { 18246 this.isLoaded(); 18247 return this.getData().xmppDomain; 18248 }, 18249 18250 /** 18251 * Getter for the xmpp pubsub domain of the system. 18252 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 18253 */ 18254 getXmppPubSubDomain: function () { 18255 this.isLoaded(); 18256 return this.getData().xmppPubSubDomain; 18257 }, 18258 18259 /** 18260 * Getter for the deployment type (UCCE or UCCX). 18261 * @returns {String} "UCCE" or "UCCX" 18262 */ 18263 getDeploymentType: function () { 18264 this.isLoaded(); 18265 return this.getData().deploymentType; 18266 }, 18267 18268 /** 18269 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 18270 * @returns {Boolean} True for single node deployments, false otherwise. 18271 */ 18272 isSingleNode: function () { 18273 var secondary = this.getData().secondaryNode; 18274 if (secondary && secondary.host) { 18275 return false; 18276 } 18277 return true; 18278 }, 18279 18280 /** 18281 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 18282 * This is useful for getting the FQDN of the current Finesse server. 18283 * @param {String} ...arguments[]... - any number of arguments to match against 18284 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 18285 */ 18286 getThisHost: function () { 18287 var i, 18288 primary = this.getData().primaryNode, 18289 secondary = this.getData().secondaryNode; 18290 18291 for (i = 0; (i < arguments.length); i = i + 1) { 18292 if (primary && arguments[i] === primary.host) { 18293 return primary.host; 18294 } else if (secondary && arguments[i] === secondary.host) { 18295 return secondary.host; 18296 } 18297 } 18298 }, 18299 18300 /** 18301 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 18302 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 18303 * @param {String} arguments - any number of arguments to match against 18304 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 18305 */ 18306 getAlternateHost: function () { 18307 var i, 18308 isPrimary = false, 18309 primary = this.getData().primaryNode, 18310 secondary = this.getData().secondaryNode, 18311 xmppDomain = this.getData().xmppDomain, 18312 alternateHost; 18313 18314 if (primary && primary.host) { 18315 if (xmppDomain === primary.host) { 18316 isPrimary = true; 18317 } 18318 if (secondary && secondary.host) { 18319 if (isPrimary) { 18320 return secondary.host; 18321 } 18322 return primary.host; 18323 } 18324 } 18325 }, 18326 18327 /** 18328 * Gets the peripheral ID that Finesse is connected to. The peripheral 18329 * ID is the ID of the PG Routing Client (PIM). 18330 * 18331 * @returns {String} The peripheral Id if UCCE, or empty string otherwise. 18332 */ 18333 getPeripheralId : function () { 18334 this.isLoaded(); 18335 18336 var peripheralId = this.getData().peripheralId; 18337 if (peripheralId === null) { 18338 return ""; 18339 } else { 18340 return this.getData().peripheralId; 18341 } 18342 }, 18343 18344 /** 18345 * Gets the license. Only apply to UCCX. 18346 * 18347 * @returns {String} The license if UCCX, or empty string otherwise. 18348 */ 18349 getlicense : function () { 18350 this.isLoaded(); 18351 return this.getData().license || ""; 18352 }, 18353 18354 /** 18355 * Gets the systemAuthMode for the current deployment 18356 * 18357 * @returns {String} The System auth mode for current deployment 18358 */ 18359 getSystemAuthMode : function() { 18360 this.isLoaded(); 18361 return this.getData().systemAuthMode; 18362 } 18363 }); 18364 18365 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 18366 /** 18367 * Finesse is in service. 18368 */ 18369 IN_SERVICE: "IN_SERVICE", 18370 /** 18371 * Finesse is not in service. 18372 */ 18373 OUT_OF_SERVICE: "OUT_OF_SERVICE", 18374 /** 18375 * @class SystemInfo status values. 18376 * @constructs 18377 */ 18378 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18379 18380 }; 18381 18382 window.finesse = window.finesse || {}; 18383 window.finesse.restservices = window.finesse.restservices || {}; 18384 window.finesse.restservices.SystemInfo = SystemInfo; 18385 18386 return SystemInfo; 18387 }); 18388 18389 define('restservices/DialogLogoutActions',[], function () 18390 { 18391 var DialogLogoutActions = /** @lends finesse.restservices.DialogLogoutActions.prototype */ { 18392 18393 /** 18394 * Set this action to close active dialogs when the agent logs out. 18395 */ 18396 CLOSE: "CLOSE", 18397 18398 /** 18399 * Set this action to transfer active dialogs when the agent logs out. 18400 */ 18401 TRANSFER: "TRANSFER", 18402 18403 /** 18404 * @class Actions used to handle tasks that are associated with a given media at logout time. 18405 * 18406 * @constructs 18407 */ 18408 _fakeConstructor: function () 18409 { 18410 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18411 18412 /** 18413 * Is the given action a valid dialog logout action. 18414 * 18415 * @param {String} action the action to evaluate 18416 * @returns {Boolean} true if the action is valid; false otherwise 18417 */ 18418 isValidAction: function(action) 18419 { 18420 if ( !action ) 18421 { 18422 return false; 18423 } 18424 18425 return DialogLogoutActions.hasOwnProperty(action.toUpperCase()); 18426 } 18427 }; 18428 18429 window.finesse = window.finesse || {}; 18430 window.finesse.restservices = window.finesse.restservices || {}; 18431 window.finesse.restservices.DialogLogoutActions = DialogLogoutActions; 18432 18433 return DialogLogoutActions; 18434 }); 18435 define('restservices/InterruptActions',[], function () 18436 { 18437 var InterruptActions = /** @lends finesse.restservices.InterruptActions.prototype */ 18438 { 18439 /** 18440 * The interrupt will be accepted and the agent will not work on dialogs in this media until the media is no longer interrupted. 18441 */ 18442 ACCEPT: "ACCEPT", 18443 18444 /** 18445 * the interrupt will be ignored and the agent is allowed to work on dialogs while the media is interrupted. 18446 */ 18447 IGNORE: "IGNORE", 18448 18449 /** 18450 * @class 18451 * 18452 * The action to be taken in the event this media is interrupted. The action will be one of the following:<ul> 18453 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on dialogs in this media 18454 * until the media is no longer interrupted.</li> 18455 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on dialogs while the 18456 * media is interrupted.</li></ul> 18457 * 18458 * @constructs 18459 */ 18460 _fakeConstructor: function () 18461 { 18462 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18463 18464 /** 18465 * Is the given action a valid dialog logout action. 18466 * 18467 * @param {String} action the action to evaluate 18468 * @returns {Boolean} true if the action is valid; false otherwise 18469 */ 18470 isValidAction: function(action) 18471 { 18472 if ( !action ) 18473 { 18474 return false; 18475 } 18476 18477 return InterruptActions.hasOwnProperty(action.toUpperCase()); 18478 } 18479 }; 18480 18481 window.finesse = window.finesse || {}; 18482 window.finesse.restservices = window.finesse.restservices || {}; 18483 window.finesse.restservices.InterruptActions = InterruptActions; 18484 18485 return InterruptActions; 18486 }); 18487 /** 18488 * JavaScript representation of the ReasonCode lookup object. 18489 * This has got reason code related APIs like looking up a reason code with 18490 * its code value and category. 18491 * 18492 */ 18493 18494 /** @private */ 18495 define('restservices/ReasonCodeLookup',['restservices/RestBase', 'utilities/Utilities'], function (RestBase, Utilities) { 18496 18497 var ReasonCodeLookup = RestBase.extend(/** @lends finesse.restservices.ReasonCodeLookup.prototype */{ 18498 /** 18499 * @private 18500 * Returns whether this object supports subscriptions 18501 */ 18502 supportsSubscriptions: false, 18503 18504 doNotRefresh: true, 18505 18506 autoSubscribe: false, 18507 18508 supportsRequests: false, 18509 18510 /** 18511 * @class 18512 * JavaScript representation of a ReasonCodeLookup object. 18513 * 18514 * @constructs 18515 */ 18516 _fakeConstuctor: function () { 18517 /* This is here to hide the real init constructor from the public docs */ 18518 }, 18519 18520 /** 18521 * @private 18522 * JavaScript representation of a ReasonCodeLookup object. Also exposes methods to operate 18523 * on the object against the server. 18524 * 18525 * @param {Object} options 18526 * An object with the following properties:<ul> 18527 * <li><b>id:</b> The id of the object being constructed</li> 18528 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18529 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18530 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18531 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18532 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18533 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18534 * <li><b>content:</b> {String} Raw string of response</li> 18535 * <li><b>object:</b> {Object} Parsed object of response</li> 18536 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18537 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18538 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18539 * </ul></li> 18540 * </ul></li> 18541 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18542 **/ 18543 init: function (options){ 18544 this._super(options); 18545 }, 18546 18547 /** 18548 * Do get disabled. 18549 */ 18550 doGet: function(handlers) { 18551 return; 18552 }, 18553 18554 /** 18555 * @private 18556 * Gets the REST class for the current object - this is the ReasonCodeLookup object. 18557 */ 18558 getRestClass: function () { 18559 return ReasonCodeLookup; 18560 }, 18561 18562 /** 18563 * @private 18564 * Gets the REST type for the current object - this is a "ReasonCodeLookup". 18565 */ 18566 getRestType: function () { 18567 return "ReasonCode"; 18568 }, 18569 18570 18571 /** 18572 * @private 18573 * Parses a uriString to retrieve the id portion 18574 * @param {String} uriString 18575 * @return {String} id 18576 */ 18577 _parseIdFromUriString : function (uriString) { 18578 return Utilities.getId(uriString); 18579 }, 18580 18581 /** 18582 * Performs a GET against the Finesse server looking up the reason code 18583 * with its reason code value, and category specified. 18584 * Note that there is no return value; use the success handler to process a 18585 * valid return. 18586 * 18587 * @param {finesse.interfaces.RequestHandlers} handlers 18588 * An object containing the handlers for the request 18589 * @param {String} reasonCodeValue The code for the reason code to lookup 18590 * @param {String} reasonCodeCategory The category for the reason code to lookup 18591 * 18592 */ 18593 lookupReasonCode : function (handlers, reasonCodeValue, reasonCodeCategory) { 18594 var self = this, contentBody, reasonCode, url; 18595 contentBody = {}; 18596 18597 url = this.getRestUrl(); 18598 url = url + "?category=" + reasonCodeCategory + "&code=" + reasonCodeValue; 18599 this.restRequest(url, { 18600 method: 'GET', 18601 success: function (rsp) { 18602 reasonCode = { 18603 uri: rsp.object.ReasonCode.uri, 18604 label: rsp.object.ReasonCode.label, 18605 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 18606 }; 18607 handlers.success(reasonCode); 18608 }, 18609 error: function (rsp) { 18610 handlers.error(rsp); 18611 }, 18612 content: contentBody 18613 }); 18614 }, 18615 18616 }); 18617 18618 18619 window.finesse = window.finesse || {}; 18620 window.finesse.restservices = window.finesse.restservices || {}; 18621 window.finesse.restservices.ReasonCodeLookup = ReasonCodeLookup; 18622 18623 return ReasonCodeLookup; 18624 }); 18625 18626 /** 18627 * Provides standard way resolve message keys with substitution 18628 * 18629 * @requires finesse.container.I18n or gadgets.Prefs 18630 */ 18631 18632 // Add Utilities to the finesse.utilities namespace 18633 define('utilities/I18n',['utilities/Utilities'], function (Utilities) { 18634 var I18n = (function () { 18635 18636 /** 18637 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18638 * @private 18639 */ 18640 var _getMsg; 18641 18642 return { 18643 /** 18644 * Provides a message resolver for this utility singleton. 18645 * @param {Function} getMsg 18646 * A function that returns a string given a message key. 18647 * If the key is not found, this function must return 18648 * something that tests false (i.e. undefined or ""). 18649 */ 18650 setGetter : function (getMsg) { 18651 _getMsg = getMsg; 18652 }, 18653 18654 /** 18655 * Resolves the given message key, also performing substitution. 18656 * This generic utility will use a custom function to resolve the key 18657 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 18658 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18659 * upon the first invocation and store that reference for efficiency. 18660 * 18661 * Since this will construct a new gadgets.Prefs object, it is recommended 18662 * for gadgets to explicitly provide the setter to prevent duplicate 18663 * gadgets.Prefs objects. This does not apply if your gadget does not need 18664 * access to gadgets.Prefs other than getMsg. 18665 * 18666 * @param {String} key 18667 * The key to lookup 18668 * @param {String} arguments 18669 * Arguments for substitution 18670 * @returns {String/Function} 18671 * The resolved string if successful, otherwise a function that returns 18672 * a '???' string that can also be casted into a string. 18673 */ 18674 getString : function (key) { 18675 var prefs, i, retStr, noMsg, getFailed = "", args; 18676 if (!_getMsg) { 18677 if (finesse.container && finesse.container.I18n) { 18678 _getMsg = finesse.container.I18n.getMsg; 18679 } else if (gadgets) { 18680 prefs = new gadgets.Prefs(); 18681 _getMsg = prefs.getMsg; 18682 } 18683 } 18684 18685 try { 18686 retStr = _getMsg(key); 18687 } catch (e) { 18688 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 18689 } 18690 18691 if (retStr) { 18692 // Lookup was successful, perform substitution (if any) 18693 args = [ retStr ]; 18694 for (i = 1; i < arguments.length; i += 1) { 18695 args.push(arguments[i]); 18696 } 18697 return Utilities.formatString.apply(this, args); 18698 } 18699 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 18700 /** @private */ 18701 noMsg = function () { 18702 return "???" + key + "???" + getFailed; 18703 }; 18704 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 18705 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 18706 /** @private */ 18707 noMsg.toString = function () { 18708 return "???" + key + "???" + getFailed; 18709 }; 18710 return noMsg; 18711 18712 } 18713 }; 18714 }()); 18715 18716 window.finesse = window.finesse || {}; 18717 window.finesse.utilities = window.finesse.utilities || {}; 18718 window.finesse.utilities.I18n = I18n; 18719 18720 return I18n; 18721 }); 18722 18723 /** 18724 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 18725 * 18726 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 18727 */ 18728 // Add Utilities to the finesse.utilities namespace 18729 define('utilities/Logger',[], function () { 18730 var Logger = (function () { 18731 18732 var 18733 18734 /** @private **/ 18735 debugOn, 18736 18737 /** 18738 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 18739 * @param num is the number to pad to 2 digits 18740 * @returns a two digit padded string 18741 * @private 18742 */ 18743 padTwoDigits = function (num) { 18744 return (num < 10) ? '0' + num : num; 18745 }, 18746 18747 /** 18748 * Checks to see if we have a console - this allows us to support Firefox or IE. 18749 * @returns {Boolean} True for Firefox, False for IE 18750 * @private 18751 */ 18752 hasConsole = function () { 18753 var retval = false; 18754 try 18755 { 18756 if (window.console !== undefined) 18757 { 18758 retval = true; 18759 } 18760 } 18761 catch (err) 18762 { 18763 retval = false; 18764 } 18765 18766 return retval; 18767 }, 18768 18769 /** 18770 * Gets a timestamp. 18771 * @returns {String} is a timestamp in the following format: HH:MM:SS 18772 * @private 18773 */ 18774 getTimeStamp = function () { 18775 var date = new Date(), timeStr; 18776 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 18777 18778 return timeStr; 18779 }; 18780 18781 return { 18782 /** 18783 * Enable debug mode. Debug mode may impact performance on the UI. 18784 * 18785 * @param {Boolean} enable 18786 * True to enable debug logging. 18787 * @private 18788 */ 18789 setDebug : function (enable) { 18790 debugOn = enable; 18791 }, 18792 18793 /** 18794 * Logs a string as DEBUG. 18795 * 18796 * @param str is the string to log. 18797 * @private 18798 */ 18799 log : function (str) { 18800 var timeStr = getTimeStamp(); 18801 18802 if (debugOn) { 18803 if (hasConsole()) 18804 { 18805 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 18806 } 18807 } 18808 }, 18809 18810 /** 18811 * Logs a string as INFO. 18812 * 18813 * @param str is the string to log. 18814 * @private 18815 */ 18816 info : function (str) { 18817 var timeStr = getTimeStamp(); 18818 18819 if (hasConsole()) 18820 { 18821 window.console.info(timeStr + ": " + "INFO" + " - " + str); 18822 } 18823 }, 18824 18825 /** 18826 * Logs a string as WARN. 18827 * 18828 * @param str is the string to log. 18829 * @private 18830 */ 18831 warn : function (str) { 18832 var timeStr = getTimeStamp(); 18833 18834 if (hasConsole()) 18835 { 18836 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 18837 } 18838 }, 18839 /** 18840 * Logs a string as ERROR. 18841 * 18842 * @param str is the string to log. 18843 * @private 18844 */ 18845 error : function (str) { 18846 var timeStr = getTimeStamp(); 18847 18848 if (hasConsole()) 18849 { 18850 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 18851 } 18852 } 18853 }; 18854 }()); 18855 18856 return Logger; 18857 }); 18858 18859 /** 18860 * BackSpaceHandler.js: provides functionality to prevent the page from navigating back and hence losing the unsaved data when backspace is pressed. 18861 * 18862 */ 18863 define('utilities/BackSpaceHandler',[], function () { 18864 var eventCallback = function(event) { 18865 var doPrevent = false, d = event.srcElement || event.target; 18866 if (event.keyCode === 8) { 18867 if ((d.tagName.toUpperCase() === 'INPUT' && (d.type 18868 .toUpperCase() === 'TEXT' 18869 || d.type.toUpperCase() === 'PASSWORD' 18870 || d.type.toUpperCase() === 'FILE' 18871 || d.type.toUpperCase() === 'SEARCH' 18872 || d.type.toUpperCase() === 'EMAIL' 18873 || d.type.toUpperCase() === 'NUMBER' || d.type 18874 .toUpperCase() === 'DATE')) 18875 || d.tagName.toUpperCase() === 'TEXTAREA') { 18876 doPrevent = d.readOnly || d.disabled; 18877 } else { 18878 doPrevent = true; 18879 } 18880 } 18881 18882 if (doPrevent) { 18883 event.preventDefault(); 18884 } 18885 }; 18886 18887 if (window.addEventListener) { 18888 window.addEventListener('keydown', eventCallback); 18889 } else if (window.attachEvent) { 18890 window.attachEvent('onkeydown', eventCallback); 18891 } else { 18892 window.console.error("Unable to attach backspace handler event "); 18893 } 18894 }); 18895 /* using variables before they are defined. 18896 */ 18897 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 18898 18899 /** 18900 * Allows each gadget to communicate with the server to send logs. 18901 */ 18902 18903 /** 18904 * @class 18905 * @private 18906 * Allows each product to initialize its method of storage 18907 */ 18908 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 18909 18910 var FinesseLogger = (function () { 18911 18912 var 18913 18914 /** 18915 * Array use to collect ongoing logs in memory 18916 * @private 18917 */ 18918 _logArray = [], 18919 18920 /** 18921 * The final data string sent to the server, =_logArray.join 18922 * @private 18923 */ 18924 _logStr = "", 18925 18926 /** 18927 * Keep track of size of log 18928 * @private 18929 */ 18930 _logSize = 0, 18931 18932 /** 18933 * Flag to keep track show/hide of send log link 18934 * @private 18935 */ 18936 _sendLogShown = false, 18937 18938 /** 18939 * Flag to keep track if local log initialized 18940 * @private 18941 */ 18942 _loggingInitialized = false, 18943 18944 18945 /** 18946 * local log size limit 18947 * @private 18948 */ 18949 _maxLocalStorageSize = 5000000, 18950 18951 /** 18952 * half local log size limit 18953 * @private 18954 */ 18955 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 18956 18957 18958 /** 18959 * threshold for purge 18960 * @private 18961 */ 18962 _purgeStartPercent = 0.75, 18963 18964 /** 18965 * log item prefix 18966 * @private 18967 */ 18968 _linePrefix = null, 18969 18970 /** 18971 * locallog session 18972 * @private 18973 */ 18974 _session = null, 18975 18976 /** 18977 * Flag to keep track show/hide of send log link 18978 * @private 18979 */ 18980 _sessionKey = null, 18981 /** 18982 * Log session metadata 18983 * @private 18984 */ 18985 _logInfo = {}, 18986 18987 /** 18988 * Flag to find sessions 18989 * @private 18990 */ 18991 _findSessionsObj = null, 18992 18993 /** 18994 * Wrap up console.log esp. for IE9 18995 * @private 18996 */ 18997 _myConsoleLog = function (str) { 18998 if (window.console !== undefined) { 18999 window.console.log(str); 19000 } 19001 }, 19002 /** 19003 * Initialize the Local Logging 19004 * @private 19005 */ 19006 _initLogging = function () { 19007 if (_loggingInitialized) { 19008 return; 19009 } 19010 //Build a new store 19011 _session = sessionStorage.getItem("finSessKey"); 19012 //if the _session is null or empty, skip the init 19013 if (!_session) { 19014 return; 19015 } 19016 _sessionKey = "Fi"+_session; 19017 _linePrefix = _sessionKey + "_"; 19018 _logInfo = {}; 19019 _logInfo.name = _session; 19020 _logInfo.size = 0; 19021 _logInfo.head = 0; 19022 _logInfo.tail = 0; 19023 _logInfo.startTime = new Date().getTime(); 19024 _loggingInitialized = true; 19025 _initSessionList(); 19026 }, 19027 19028 /** 19029 * get total data size 19030 * 19031 * @return {Integer} which is the amount of data stored in local storage. 19032 * @private 19033 */ 19034 _getTotalData = function () 19035 { 19036 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 19037 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19038 if (!sessionsInfoStr) { 19039 return 0; 19040 } 19041 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19042 19043 for (sessName in sessionsInfoObj.sessions) 19044 { 19045 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 19046 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 19047 if (!sessLogInfoStr) { 19048 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 19049 } 19050 else { 19051 sessLogInfoObj = JSON.parse(sessLogInfoStr); 19052 totalData = totalData + sessLogInfoObj.size; 19053 } 19054 } 19055 } 19056 19057 return totalData; 19058 }, 19059 19060 /** 19061 * Remove lines from tail up until store size decreases to half of max size limit. 19062 * 19063 * @private 19064 */ 19065 _purgeCurrentSession = function() { 19066 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 19067 curStoreSize = _getTotalData(); 19068 if (curStoreSize < _halfMaxLocalStorageSize) { 19069 return; 19070 } 19071 logInfoStr = localStorage.getItem(_sessionKey); 19072 if (!logInfoStr) { 19073 return; 19074 } 19075 theLogInfo = JSON.parse(logInfoStr); 19076 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 19077 while(curStoreSize > _halfMaxLocalStorageSize) { 19078 try { 19079 tailKey = _sessionKey+"_"+theLogInfo.tail; 19080 line = localStorage.getItem(tailKey); 19081 if (line) { 19082 purgedSize = purgedSize +line.length; 19083 localStorage.removeItem(tailKey); 19084 curStoreSize = curStoreSize - line.length; 19085 theLogInfo.size = theLogInfo.size - line.length; 19086 } 19087 } 19088 catch (err) { 19089 _myConsoleLog("purgeCurrentSession encountered err="+err); 19090 } 19091 if (theLogInfo.tail < theLogInfo.head) { 19092 theLogInfo.tail = theLogInfo.tail + 1; 19093 } 19094 else { 19095 break; 19096 } 19097 } 19098 //purge stops here, we need to update session's meta data in storage 19099 secLogInfoStr = localStorage.getItem(_sessionKey); 19100 if (!secLogInfoStr) { 19101 //somebody cleared the localStorage 19102 return; 19103 } 19104 19105 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 19106 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 19107 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 19108 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 19109 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 19110 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 19111 }, 19112 19113 /** 19114 * Purge a session 19115 * 19116 * @param sessionName is the name of the session 19117 * @return {Integer} which is the current amount of data purged 19118 * @private 19119 */ 19120 _purgeSession = function (sessionName) { 19121 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 19122 //Get the session logInfo 19123 logInfoStr = localStorage.getItem("Fi" + sessionName); 19124 if (!logInfoStr) { 19125 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 19126 return 0; 19127 } 19128 theLogInfo = JSON.parse(logInfoStr); 19129 19130 //Note: This assumes that we don't crash in the middle of purging 19131 //=> if we do then it should get deleted next time 19132 //Purge tail->head 19133 while (theLogInfo.tail <= theLogInfo.head) 19134 { 19135 try { 19136 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 19137 theLogInfo.tail = theLogInfo.tail + 1; 19138 } 19139 catch (err) { 19140 _myConsoleLog("In _purgeSession err="+err); 19141 break; 19142 } 19143 } 19144 19145 //Remove the entire session 19146 localStorage.removeItem("Fi" + sessionName); 19147 19148 //Update FinesseSessionsInfo 19149 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19150 if (!sessionsInfoStr) { 19151 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 19152 return 0; 19153 } 19154 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19155 if (sessionsInfoObj.sessions !== null) 19156 { 19157 delete sessionsInfoObj.sessions[sessionName]; 19158 19159 sessionsInfoObj.total = sessionsInfoObj.total - 1; 19160 sessionsInfoObj.lastWrittenBy = _session; 19161 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 19162 } 19163 19164 return theLogInfo.size; 19165 }, 19166 19167 /** 19168 * purge old sessions 19169 * 19170 * @param storeSize 19171 * @return {Boolean} whether purging reaches its target 19172 * @private 19173 */ 19174 _purgeOldSessions = function (storeSize) { 19175 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 19176 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19177 if (!sessionsInfoStr) { 19178 _myConsoleLog("Could not get FinesseSessionsInfo"); 19179 return true; 19180 } 19181 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19182 curStoreSize = _getTotalData(); 19183 19184 activeSession = _session; 19185 sessions = sessionsInfoObj.sessions; 19186 for (sessName in sessions) { 19187 if (sessions.hasOwnProperty(sessName)) { 19188 if (sessName !== activeSession) { 19189 purgedSize = purgedSize + _purgeSession(sessName); 19190 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 19191 return true; 19192 } 19193 } 19194 } 19195 } 19196 //purge is not done, so return false 19197 return false; 19198 }, 19199 19200 /** 19201 * handle insert error 19202 * 19203 * @param error 19204 * @private 19205 */ 19206 _insertLineHandleError = function (error) { 19207 _myConsoleLog(error); 19208 }, 19209 19210 /** 19211 * check storage data size and if need purge 19212 * @private 19213 */ 19214 _checkSizeAndPurge = function () { 19215 var purgeIsDone=false, totalSize = _getTotalData(); 19216 if (totalSize > 0.75*_maxLocalStorageSize) { 19217 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 19218 purgeIsDone = _purgeOldSessions(totalSize); 19219 if (purgeIsDone) { 19220 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 19221 } 19222 else { 19223 //after all old sessions purged, still need purge 19224 totalSize = _getTotalData(); 19225 if (totalSize > 0.75*_maxLocalStorageSize) { 19226 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 19227 _purgeCurrentSession(); 19228 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 19229 } 19230 } 19231 } 19232 }, 19233 19234 /** 19235 * check if the session is already in meta data 19236 * 19237 * @param metaData 19238 * @param sessionName 19239 * @return {Boolean} true if session has metaData (false otherwise) 19240 * @private 19241 */ 19242 _sessionsInfoContains = function (metaData, sessionName) { 19243 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 19244 return true; 19245 } 19246 return false; 19247 }, 19248 19249 19250 /** 19251 * setup sessions in local storage 19252 * 19253 * @param logInfo 19254 * @private 19255 */ 19256 _getAndSetNumberOfSessions = function (logInfo) { 19257 var numOfSessionsPass1, numOfSessionsPass2, l; 19258 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 19259 if (numOfSessionsPass1 === null) { 19260 //Init first time 19261 numOfSessionsPass1 = {}; 19262 numOfSessionsPass1.total = 1; 19263 numOfSessionsPass1.sessions = {}; 19264 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 19265 numOfSessionsPass1.lastWrittenBy = logInfo.name; 19266 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 19267 } 19268 else { 19269 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 19270 //check if the session is already in the FinesseSessionSInfo 19271 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 19272 return; 19273 } 19274 //Save numOfSessionsPass1 19275 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 19276 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 19277 numOfSessionsPass1.lastWrittenBy = logInfo.name; 19278 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 19279 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 19280 if (!numOfSessionsPass2) { 19281 _myConsoleLog("Could not get FinesseSessionsInfo"); 19282 return; 19283 } 19284 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 19285 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 19286 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 19287 //// _myConsoleLog("Rebuild sessions"); 19288 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 19289 ////} 19290 ////else { 19291 //// _sessionTimerId = null; 19292 ////callback(numOfSessionsPass2.sessions); 19293 ////} 19294 } 19295 if (!localStorage.getItem(_sessionKey)) { 19296 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 19297 } 19298 }, 19299 19300 19301 /** 19302 * init session list 19303 * @private 19304 */ 19305 _initSessionList = function () { 19306 _getAndSetNumberOfSessions(_logInfo); 19307 }, 19308 19309 /** 19310 * do the real store of log line 19311 * 19312 * @param line 19313 * @private 19314 */ 19315 _persistLine = function (line) { 19316 var key, logInfoStr; 19317 logInfoStr = localStorage.getItem(_sessionKey); 19318 if (logInfoStr === null) { 19319 return; 19320 } 19321 _logInfo = JSON.parse(logInfoStr); 19322 _logInfo.head = _logInfo.head + 1; 19323 key = _linePrefix + _logInfo.head; 19324 localStorage.setItem(key, line); 19325 //Save the size 19326 _logInfo.size = _logInfo.size + line.length; 19327 if (_logInfo.tail === 0) { 19328 _logInfo.tail = _logInfo.head; 19329 } 19330 19331 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 19332 _checkSizeAndPurge(); 19333 }, 19334 19335 /** 19336 * Insert a line into the localStorage. 19337 * 19338 * @param line line to be inserted 19339 * @private 19340 */ 19341 _insertLine = function (line) { 19342 //_myConsoleLog("_insertLine: [" + line + "]"); 19343 //Write the next line to localStorage 19344 try { 19345 //Persist the line 19346 _persistLine(line); 19347 } 19348 catch (err) { 19349 _myConsoleLog("error in _insertLine(), err="+err); 19350 //_insertLineHandleError(err); 19351 } 19352 }, 19353 19354 19355 /** 19356 * Clear the local storage 19357 * @private 19358 */ 19359 _clearLocalStorage = function() { 19360 localStorage.clear(); 19361 19362 }, 19363 19364 /** 19365 * Collect logs when onCollect called 19366 * 19367 * @param data 19368 * @private 19369 */ 19370 _collectMethod = function(data) { 19371 //Size of log should not exceed 1.5MB 19372 var info, maxLength = 1572864; 19373 19374 //add size buffer equal to the size of info to be added when publish 19375 info = Utilities.getSanitizedUserAgentString() + " "; 19376 info = escape(info); 19377 19378 //If log was empty previously, fade in buttons 19379 if (!_sendLogShown) { 19380 //call the fadeInSendLog() in Footer 19381 finesse.modules.Footer.sendLogAppear(); 19382 _sendLogShown = true; 19383 _logSize = info.length; 19384 } 19385 19386 //if local storage logging is enabled, then insert the log into local storage 19387 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 19388 if (data) { 19389 if (data.length>0 && data.substring(0,1) === '\n') { 19390 _insertLine(data.substring(1)); 19391 } 19392 else { 19393 _insertLine(data); 19394 } 19395 } 19396 } 19397 19398 //escape all data to get accurate size (shindig will escape when it builds request) 19399 //escape 6 special chars for XML: &<>"'\n 19400 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 19401 data = escape(data+"\n"); 19402 19403 if (data.length < maxLength){ 19404 //make room for new data if log is exceeding max length 19405 while (_logSize + data.length > maxLength) { 19406 _logSize -= (_logArray.shift()).length; 19407 } 19408 } 19409 19410 //Else push the log into memory, increment the log size 19411 _logArray.push(data); 19412 19413 //inc the size accordingly 19414 _logSize+=data.length; 19415 19416 }; 19417 19418 return { 19419 19420 /** 19421 * @private 19422 * Initiate FinesseLogger. 19423 */ 19424 init: function () { 19425 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 19426 _initLogging(); 19427 }, 19428 19429 /** 19430 * @private 19431 * Clear all items stored in localStorage. 19432 */ 19433 clear : function () { 19434 _clearLocalStorage(); 19435 }, 19436 19437 /** 19438 * @private 19439 * Initialize the local storage logging. 19440 */ 19441 initLocalLog: function () { 19442 _initLogging(); 19443 }, 19444 19445 /** 19446 * @private 19447 * Inserts a line into the localStorage. 19448 * @param line to insert 19449 */ 19450 localLog : function (line) { 19451 _insertLine(line); 19452 }, 19453 19454 /** 19455 * @ignore 19456 * Publish logs to server and clear the memory 19457 * 19458 * @param userObj 19459 * @param options 19460 * @param callBack 19461 */ 19462 publish: function(userObj, options, callBack) { 19463 // Avoid null references. 19464 options = options || {}; 19465 callBack = callBack || {}; 19466 19467 if (callBack.sending === "function") { 19468 callBack.sending(); 19469 } 19470 19471 //logs the basic version and machine info and escaped new line 19472 _logStr = Utilities.getSanitizedUserAgentString() + " "; 19473 19474 //join the logs to correct string format 19475 _logStr += unescape(_logArray.join("")); 19476 19477 //turning log string to JSON obj 19478 var logObj = { 19479 ClientLog: { 19480 logData : _logStr //_logStr 19481 } 19482 }, 19483 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 19484 /** @private */ 19485 options.onAdd = function(){ 19486 tmpOnAdd(); 19487 _logArray.length = 0; _logSize =0; 19488 _sendLogShown = false; 19489 }; 19490 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 19491 /** @private */ 19492 options.onLoad = function (clientLogObj) { 19493 clientLogObj.sendLogs(logObj,{ 19494 error: callBack.error 19495 }); 19496 }; 19497 19498 userObj.getClientLog(options); 19499 } 19500 }; 19501 }()); 19502 19503 window.finesse = window.finesse || {}; 19504 window.finesse.cslogger = window.finesse.cslogger || {}; 19505 /** @private */ 19506 window.finesse.cslogger.FinesseLogger = FinesseLogger; 19507 19508 return FinesseLogger; 19509 }); 19510 19511 /** 19512 * Contains a list of topics used for containerservices pubsub. 19513 * 19514 */ 19515 19516 /** 19517 * @class 19518 * Contains a list of topics with some utility functions. 19519 */ 19520 /** @private */ 19521 define('containerservices/Topics',[], function () { 19522 19523 var Topics = (function () { 19524 19525 /** 19526 * The namespace prepended to all Finesse topics. 19527 */ 19528 this.namespace = "finesse.containerservices"; 19529 19530 /** 19531 * @private 19532 * Gets the full topic name with the ContainerServices namespace prepended. 19533 * @param {String} topic 19534 * The topic category. 19535 * @returns {String} 19536 * The full topic name with prepended namespace. 19537 */ 19538 var _getNSTopic = function (topic) { 19539 return this.namespace + "." + topic; 19540 }; 19541 19542 19543 19544 /** @scope finesse.containerservices.Topics */ 19545 return { 19546 /** 19547 * @private 19548 * request channel. */ 19549 REQUESTS: _getNSTopic("requests"), 19550 19551 /** 19552 * @private 19553 * reload gadget channel. */ 19554 RELOAD_GADGET: _getNSTopic("reloadGadget"), 19555 19556 /** 19557 * @private 19558 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 19559 */ 19560 getTopic: function (restUri) { 19561 //The topic should not start with '/' else it will get replaced with 19562 //'.' which is invalid. 19563 //Thus, remove '/' if it is at the beginning of the string 19564 if (restUri.indexOf('/') === 0) { 19565 restUri = restUri.substr(1); 19566 } 19567 19568 //Replace every instance of "/" with ".". This is done to follow the 19569 //OpenAjaxHub topic name convention. 19570 return restUri.replace(/\//g, "."); 19571 } 19572 }; 19573 }()); 19574 19575 window.finesse = window.finesse || {}; 19576 window.finesse.containerservices = window.finesse.containerservices || {}; 19577 window.finesse.containerservices.Topics = Topics; 19578 19579 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 19580 finesse.containerservices = finesse.containerservices || {}; 19581 19582 return Topics; 19583 }); 19584 19585 /** The following comment is to prevent jslint errors about 19586 * using variables before they are defined. 19587 */ 19588 /*global finesse*/ 19589 19590 /** 19591 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 19592 * 19593 * @requires OpenAjax, finesse.containerservices.Topics 19594 */ 19595 19596 /** @private */ 19597 define('containerservices/MasterPublisher',[ 19598 "utilities/Utilities", 19599 "containerservices/Topics" 19600 ], 19601 function (Utilities, Topics) { 19602 19603 var MasterPublisher = function () { 19604 19605 var 19606 19607 /** 19608 * Reference to the gadget pubsub Hub instance. 19609 * @private 19610 */ 19611 _hub = gadgets.Hub, 19612 19613 /** 19614 * Reference to the Topics class. 19615 * @private 19616 */ 19617 _topics = Topics, 19618 19619 /** 19620 * Reference to conversion utilities class. 19621 * @private 19622 */ 19623 _utils = Utilities, 19624 19625 /** 19626 * References to ClientServices logger methods 19627 * @private 19628 */ 19629 _logger = { 19630 log: finesse.clientservices.ClientServices.log 19631 }, 19632 19633 /** 19634 * The types of possible request types supported when listening to the 19635 * requests channel. Each request type could result in different operations. 19636 * @private 19637 */ 19638 _REQTYPES = { 19639 ACTIVETAB: "ActiveTabReq", 19640 SET_ACTIVETAB: "SetActiveTabReq", 19641 RELOAD_GADGET: "ReloadGadgetReq" 19642 }, 19643 19644 /** 19645 * Handles client requests made to the request topic. The type of the 19646 * request is described in the "type" property within the data payload. Each 19647 * type can result in a different operation. 19648 * @param {String} topic 19649 * The topic which data was published to. 19650 * @param {Object} data 19651 * The data containing requests information published by clients. 19652 * @param {String} data.type 19653 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 19654 * @param {Object} data.data 19655 * May contain data relevant for the particular requests. 19656 * @param {String} [data.invokeID] 19657 * The ID used to identify the request with the response. The invoke ID 19658 * will be included in the data in the publish to the topic. It is the 19659 * responsibility of the client to correlate the published data to the 19660 * request made by using the invoke ID. 19661 * @private 19662 */ 19663 _clientRequestHandler = function (topic, data) { 19664 19665 //Ensure a valid data object with "type" and "data" properties. 19666 if (typeof data === "object" && 19667 typeof data.type === "string" && 19668 typeof data.data === "object") { 19669 switch (data.type) { 19670 case _REQTYPES.ACTIVETAB: 19671 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 19672 break; 19673 case _REQTYPES.SET_ACTIVETAB: 19674 if (typeof data.data.id === "string") { 19675 _logger.log("Handling request to activate tab: " + data.data.id); 19676 if (!finesse.container.Tabs.activateTab(data.data.id)) { 19677 _logger.log("No tab found with id: " + data.data.id); 19678 } 19679 } 19680 break; 19681 case _REQTYPES.RELOAD_GADGET: 19682 _hub.publish("finesse.containerservices.reloadGadget", data.data); 19683 break; 19684 default: 19685 break; 19686 } 19687 } 19688 }; 19689 19690 (function () { 19691 19692 //Listen to a request channel to respond to any requests made by other 19693 //clients because the Master may have access to useful information. 19694 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 19695 }()); 19696 19697 //BEGIN TEST CODE// 19698 /** 19699 * Test code added to expose private functions that are used by unit test 19700 * framework. This section of code is removed during the build process 19701 * before packaging production code. The [begin|end]TestSection are used 19702 * by the build to identify the section to strip. 19703 * @ignore 19704 */ 19705 this.beginTestSection = 0; 19706 19707 /** 19708 * @ignore 19709 */ 19710 this.getTestObject = function () { 19711 //Load mock dependencies. 19712 var _mock = new MockControl(); 19713 _hub = _mock.createMock(gadgets.Hub); 19714 19715 return { 19716 //Expose mock dependencies 19717 mock: _mock, 19718 hub: _hub, 19719 19720 //Expose internal private functions 19721 reqtypes: _REQTYPES, 19722 19723 clientRequestHandler: _clientRequestHandler 19724 19725 }; 19726 }; 19727 19728 19729 /** 19730 * @ignore 19731 */ 19732 this.endTestSection = 0; 19733 //END TEST CODE// 19734 }; 19735 19736 window.finesse = window.finesse || {}; 19737 window.finesse.containerservices = window.finesse.containerservices || {}; 19738 window.finesse.containerservices.MasterPublisher = MasterPublisher; 19739 19740 return MasterPublisher; 19741 }); 19742 19743 /** 19744 * JavaScript representation of the Finesse WorkflowActionEvent object. 19745 * 19746 * @requires finesse.FinesseBase 19747 */ 19748 19749 /** The following comment is to prevent jslint errors about 19750 * using variables before they are defined. 19751 */ 19752 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19753 /** @private */ 19754 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 19755 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 19756 /** 19757 * Reference to the WorkflowActionEvent name 19758 * This will be set by setWorkflowActionEvent 19759 * @private 19760 */ 19761 _name: null, 19762 19763 /** 19764 * Reference to the WorkflowActionEvent type 19765 * This will be set by setWorkflowActionEvent 19766 * @private 19767 */ 19768 _type: null, 19769 19770 /** 19771 * Reference to the WorkflowActionEvent handledBy value 19772 * This will be set by setWorkflowActionEvent 19773 * @private 19774 */ 19775 _handledBy: null, 19776 19777 /** 19778 * Reference to the WorkflowActionEvent params array 19779 * This will be set by setWorkflowActionEvent 19780 * @private 19781 */ 19782 _params: [], 19783 19784 /** 19785 * Reference to the WorkflowActionEvent actionVariables array 19786 * This will be set by setWorkflowActionEvent 19787 * @private 19788 */ 19789 _actionVariables: [], 19790 19791 /** 19792 * @class 19793 * JavaScript representation of a WorkflowActionEvent object. 19794 * The WorkflowActionEvent object is delivered as the payload of 19795 * a WorkflowAction callback. This can be subscribed to by using 19796 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19797 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 19798 * Gadgets should key on events with a handleBy value of "OTHER". 19799 * 19800 * @constructs 19801 **/ 19802 init: function () { 19803 this._super(); 19804 }, 19805 19806 /** 19807 * Validate that the passed in object is a WorkflowActionEvent object 19808 * and sets the variables if it is 19809 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 19810 * it validates successfully. 19811 * @returns {Boolean} Whether it is valid or not. 19812 * @private 19813 */ 19814 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 19815 var returnValue; 19816 19817 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 19818 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 19819 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 19820 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 19821 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 19822 this._name = maybeWorkflowActionEvent.name; 19823 this._type = maybeWorkflowActionEvent.type; 19824 this._handledBy = maybeWorkflowActionEvent.handledBy; 19825 this._params = maybeWorkflowActionEvent.params; 19826 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 19827 returnValue = true; 19828 } else { 19829 returnValue = false; 19830 } 19831 19832 return returnValue; 19833 }, 19834 19835 /** 19836 * Getter for the WorkflowActionEvent name. 19837 * @returns {String} The name of the WorkflowAction. 19838 */ 19839 getName: function () { 19840 // escape nulls to empty string 19841 return this._name || ""; 19842 }, 19843 19844 /** 19845 * Getter for the WorkflowActionEvent type. 19846 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 19847 */ 19848 getType: function () { 19849 // escape nulls to empty string 19850 return this._type || ""; 19851 }, 19852 19853 /** 19854 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 19855 * events with a handleBy of "OTHER". 19856 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 19857 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 19858 */ 19859 getHandledBy: function () { 19860 // escape nulls to empty string 19861 return this._handledBy || ""; 19862 }, 19863 19864 19865 /** 19866 * Getter for the WorkflowActionEvent Params map. 19867 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 19868 * BROWSER_POP<ul> 19869 * <li>windowName : Name of window to pop into, or blank to always open new window. 19870 * <li>path : URL to open.</ul> 19871 * HTTP_REQUEST<ul> 19872 * <li>method : "PUT" or "POST". 19873 * <li>location : "FINESSE" or "OTHER". 19874 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 19875 * <li>path : Request URL. 19876 * <li>body : Request content for POST requests.</ul> 19877 */ 19878 getParams: function () { 19879 var map = {}, 19880 params = this._params, 19881 i, 19882 param; 19883 19884 if (params === null || params.length === 0) { 19885 return map; 19886 } 19887 19888 for (i = 0; i < params.length; i += 1) { 19889 param = params[i]; 19890 // escape nulls to empty string 19891 param.name = param.name || ""; 19892 param.value = param.value || ""; 19893 param.expandedValue = param.expandedValue || ""; 19894 map[param.name] = param; 19895 } 19896 19897 return map; 19898 }, 19899 19900 /** 19901 * Getter for the WorkflowActionEvent ActionVariables map 19902 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 19903 */ 19904 getActionVariables: function() { 19905 var map = {}, 19906 actionVariables = this._actionVariables, 19907 i, 19908 actionVariable; 19909 19910 if (actionVariables === null || actionVariables.length === 0) { 19911 return map; 19912 } 19913 19914 for (i = 0; i < actionVariables.length; i += 1) { 19915 actionVariable = actionVariables[i]; 19916 // escape nulls to empty string 19917 actionVariable.name = actionVariable.name || ""; 19918 actionVariable.type = actionVariable.type || ""; 19919 actionVariable.node = actionVariable.node || ""; 19920 actionVariable.testValue = actionVariable.testValue || ""; 19921 actionVariable.actualValue = actionVariable.actualValue || ""; 19922 map[actionVariable.name] = actionVariable; 19923 } 19924 19925 return map; 19926 } 19927 }); 19928 19929 19930 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 19931 /** 19932 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 19933 * with the action, but first and foremost Finesse will handle this WorkflowAction. 19934 */ 19935 FINESSE: "FINESSE", 19936 19937 /** 19938 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 19939 * ignore this action and expects Gadget Developers to take action. 19940 */ 19941 OTHER: "OTHER", 19942 19943 /** 19944 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 19945 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 19946 * @constructs 19947 */ 19948 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19949 }; 19950 19951 window.finesse = window.finesse || {}; 19952 window.finesse.containerservices = window.finesse.containerservices || {}; 19953 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 19954 19955 return WorkflowActionEvent; 19956 }); 19957 19958 /** 19959 * JavaScript representation of the Finesse TimerTickEvent 19960 * 19961 * @requires finesse.FinesseBase 19962 */ 19963 19964 /** The following comment is to prevent jslint errors about 19965 * using variables before they are defined. 19966 */ 19967 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19968 /** @private */ 19969 define('containerservices/TimerTickEvent',[ 19970 "FinesseBase" 19971 ], 19972 function (FinesseBase) { 19973 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 19974 /** 19975 * date the TimerTickEvent was queued 19976 * @private 19977 */ 19978 _dateQueued: null, 19979 19980 /** 19981 * the frequency of the timer tick (in miiliseconds) 19982 * @private 19983 */ 19984 _tickFrequency: 1000, 19985 19986 /** 19987 * @class 19988 * JavaScript representation of a TimerTickEvent object. 19989 * The TimerTickEvent object is delivered as the payload of 19990 * a TimerTickEvent callback. This can be subscribed to by using 19991 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19992 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 19993 * 19994 * @constructs 19995 **/ 19996 init: function (tickFrequency, dateQueued) { 19997 this._super(); 19998 19999 this._tickFrequency = tickFrequency; 20000 this._dateQueued = dateQueued; 20001 }, 20002 20003 /** 20004 * Get the "tickFrequency" field 20005 * @param {int} which is the "TickFrequency" field 20006 * @private 20007 */ 20008 getTickFrequency: function () { 20009 return this._tickFrequency; 20010 }, 20011 20012 /** 20013 * Getter for the TimerTickEvent "DateQueued" field. 20014 * @returns {Date} which is a Date object when the TimerTickEvent was queued 20015 */ 20016 getDateQueued: function () { 20017 return this._dateQueued; 20018 } 20019 20020 }); 20021 20022 window.finesse = window.finesse || {}; 20023 window.finesse.containerservices = window.finesse.containerservices || {}; 20024 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 20025 20026 return TimerTickEvent; 20027 }); 20028 20029 /** 20030 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 20031 * 20032 * @requires finesse.FinesseBase 20033 */ 20034 20035 /** The following comment is to prevent jslint errors about 20036 * using variables before they are defined. 20037 */ 20038 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20039 /** @private */ 20040 define('containerservices/GadgetViewChangedEvent',[ 20041 "FinesseBase" 20042 ], 20043 function (FinesseBase) { 20044 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 20045 /** 20046 * Reference to the gadget id 20047 * @private 20048 */ 20049 _gadgetId: null, 20050 20051 /** 20052 * Reference to the tab id 20053 * @private 20054 */ 20055 _tabId: null, 20056 20057 /** 20058 * Reference to the maxAvailableHeight 20059 * @private 20060 */ 20061 _maxAvailableHeight: null, 20062 20063 /** 20064 * Reference to the view 20065 * E.g. 'default' or 'canvas' 20066 * @private 20067 */ 20068 _view: null, 20069 20070 /** 20071 * @class 20072 * JavaScript representation of a GadgetViewChangedEvent object. 20073 * The GadgetViewChangedEvent object is delivered as the payload of 20074 * a GadgetViewChangedEvent callback. This can be subscribed to by using 20075 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20076 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 20077 * 20078 * @constructs 20079 **/ 20080 init: function (gadgetId, tabId, maxAvailableHeight, view) { 20081 this._super(); 20082 20083 this._gadgetId = gadgetId; 20084 this._tabId = tabId; 20085 this._maxAvailableHeight = maxAvailableHeight; 20086 this._view = view; 20087 }, 20088 20089 /** 20090 * Getter for the gadget id. 20091 * @returns {String} The identifier for the gadget changing view. 20092 */ 20093 getGadgetId: function () { 20094 // escape nulls to empty string 20095 return this._gadgetId || ""; 20096 }, 20097 20098 /** 20099 * Getter for the maximum available height. 20100 * @returns {String} The maximum available height for the gadget's view. 20101 */ 20102 getMaxAvailableHeight: function () { 20103 // escape nulls to empty string 20104 return this._maxAvailableHeight || ""; 20105 }, 20106 20107 /** 20108 * Getter for the tab id. 20109 * @returns {String} The identifier for the tab where the gadget changing view resides. 20110 */ 20111 getTabId: function () { 20112 // escape nulls to empty string 20113 return this._tabId || ""; 20114 }, 20115 20116 /** 20117 * Getter for the view. 20118 * @returns {String} The view type the gadget is changing to. 20119 */ 20120 getView: function () { 20121 // escape nulls to empty string 20122 return this._view || ""; 20123 } 20124 }); 20125 20126 window.finesse = window.finesse || {}; 20127 window.finesse.containerservices = window.finesse.containerservices || {}; 20128 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 20129 20130 return GadgetViewChangedEvent; 20131 }); 20132 20133 /** 20134 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 20135 * 20136 * @requires finesse.FinesseBase 20137 */ 20138 20139 /** The following comment is to prevent jslint errors about 20140 * using variables before they are defined. 20141 */ 20142 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20143 /** @private */ 20144 define('containerservices/MaxAvailableHeightChangedEvent',[ 20145 "FinesseBase" 20146 ], 20147 function (FinesseBase) { 20148 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 20149 20150 /** 20151 * Reference to the maxAvailableHeight 20152 * @private 20153 */ 20154 _maxAvailableHeight: null, 20155 20156 /** 20157 * @class 20158 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 20159 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 20160 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 20161 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20162 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 20163 * 20164 * @constructs 20165 **/ 20166 init: function (maxAvailableHeight) { 20167 this._super(); 20168 20169 this._maxAvailableHeight = maxAvailableHeight; 20170 }, 20171 20172 /** 20173 * Getter for the maximum available height. 20174 * @returns {String} The maximum available height for a gadget in canvas view 20175 */ 20176 getMaxAvailableHeight: function () { 20177 // escape nulls to empty string 20178 return this._maxAvailableHeight || ""; 20179 } 20180 }); 20181 20182 window.finesse = window.finesse || {}; 20183 window.finesse.containerservices = window.finesse.containerservices || {}; 20184 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 20185 20186 return MaxAvailableHeightChangedEvent; 20187 }); 20188 20189 /** 20190 * Exposes a set of API wrappers that will hide the dirty work of 20191 * constructing Finesse API requests and consuming Finesse events. 20192 * 20193 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 20194 */ 20195 20196 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 20197 /*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 */ 20198 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 20199 /** @private */ 20200 define('containerservices/ContainerServices',[ 20201 "utilities/Utilities", 20202 "restservices/Notifier", 20203 "containerservices/Topics", 20204 "containerservices/MasterPublisher", 20205 "containerservices/WorkflowActionEvent", 20206 "containerservices/TimerTickEvent", 20207 "containerservices/GadgetViewChangedEvent", 20208 "containerservices/MaxAvailableHeightChangedEvent" 20209 ], 20210 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 20211 20212 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 20213 20214 var 20215 20216 /** 20217 * Shortcut reference to the Utilities singleton 20218 * This will be set by init() 20219 * @private 20220 */ 20221 _util, 20222 20223 /** 20224 * Shortcut reference to the gadget pubsub Hub instance. 20225 * This will be set by init() 20226 * @private 20227 */ 20228 _hub, 20229 20230 /** 20231 * Boolean whether this instance is master or not 20232 * @private 20233 */ 20234 _master = false, 20235 20236 /** 20237 * Whether the Client Services have been initiated yet. 20238 * @private 20239 */ 20240 _inited = false, 20241 20242 /** 20243 * References to ClientServices logger methods 20244 * @private 20245 */ 20246 _logger = { 20247 log: finesse.clientservices.ClientServices.log 20248 }, 20249 20250 /** 20251 * Stores the list of subscription IDs for all subscriptions so that it 20252 * could be retrieve for unsubscriptions. 20253 * @private 20254 */ 20255 _subscriptionID = {}, 20256 20257 /** 20258 * Reference to the gadget's parent container 20259 * @private 20260 */ 20261 _container, 20262 20263 /** 20264 * Reference to the MasterPublisher 20265 * @private 20266 */ 20267 _publisher, 20268 20269 /** 20270 * Object that will contain the Notifiers 20271 * @private 20272 */ 20273 _notifiers = {}, 20274 20275 /** 20276 * Reference to the tabId that is associated with the gadget 20277 * @private 20278 */ 20279 _myTab = null, 20280 20281 /** 20282 * Reference to the visibility of current gadget 20283 * @private 20284 */ 20285 _visible = false, 20286 20287 /** 20288 * Reference for auth modes constants. 20289 * @private 20290 */ 20291 _authModes, 20292 20293 /** 20294 * Shortcut reference to the Topics class. 20295 * This will be set by init() 20296 * @private 20297 */ 20298 _topics, 20299 20300 /** 20301 * Associates a topic name with the private handler function. 20302 * Adding a new topic requires that you add this association here 20303 * in to keep addHandler generic. 20304 * @param {String} topic : Specifies the callback to retrieve 20305 * @return {Function} The callback function associated with the topic param. 20306 * @private 20307 */ 20308 _topicCallback = function (topic) { 20309 var callback, notifier; 20310 switch (topic) 20311 { 20312 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 20313 callback = _tabTracker; 20314 break; 20315 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 20316 callback = _workflowActionEventTracker; 20317 break; 20318 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 20319 callback = _masterReloader; 20320 break; 20321 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 20322 callback = _gadgetViewChanged; 20323 break; 20324 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 20325 callback = _maxAvailableHeightChanged; 20326 break; 20327 case finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT: 20328 callback = _accessTokenRefreshed; 20329 break; 20330 default: 20331 callback = function (param) { 20332 var data = null; 20333 20334 notifier = _getNotifierReference(topic); 20335 20336 if (arguments.length === 1) { 20337 data = param; 20338 } else { 20339 data = arguments; 20340 } 20341 notifier.notifyListeners(data); 20342 }; 20343 } 20344 return callback; 20345 }, 20346 20347 /** 20348 * Ensure that ClientServices have been inited. 20349 * @private 20350 */ 20351 _isInited = function () { 20352 if (!_inited) { 20353 throw new Error("ContainerServices needs to be inited."); 20354 } 20355 }, 20356 20357 /** 20358 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 20359 * @param {String} topic : Specifies the notifier to retrieve 20360 * @return {Notifier} The notifier object. 20361 * @private 20362 */ 20363 _getNotifierReference = function (topic) { 20364 if (!_notifiers.hasOwnProperty(topic)) 20365 { 20366 _notifiers[topic] = new Notifier(); 20367 } 20368 20369 return _notifiers[topic]; 20370 }, 20371 20372 /** 20373 * Utility function to make a subscription to a particular topic. Only one 20374 * callback function is registered to a particular topic at any time. 20375 * @param {String} topic 20376 * The full topic name. The topic name should follow the OpenAjax 20377 * convention using dot notation (ex: finesse.api.User.1000). 20378 * @param {Function} callback 20379 * The function that should be invoked with the data when an event 20380 * is delivered to the specific topic. 20381 * @returns {Boolean} 20382 * True if the subscription was made successfully and the callback was 20383 * been registered. False if the subscription already exist, the 20384 * callback was not overwritten. 20385 * @private 20386 */ 20387 _subscribe = function (topic, callback) { 20388 _isInited(); 20389 20390 //Ensure that the same subscription isn't made twice. 20391 if (!_subscriptionID[topic]) { 20392 //Store the subscription ID using the topic name as the key. 20393 _subscriptionID[topic] = _hub.subscribe(topic, 20394 //Invoke the callback just with the data object. 20395 function (topic, data) { 20396 callback(data); 20397 }); 20398 return true; 20399 } 20400 return false; 20401 }, 20402 20403 /** 20404 * Unsubscribe from a particular topic. 20405 * @param {String} topic : The full topic name. 20406 * @private 20407 */ 20408 _unsubscribe = function (topic) { 20409 _isInited(); 20410 20411 //Unsubscribe from the topic using the subscription ID recorded when 20412 //the subscription was made, then delete the ID from data structure. 20413 _hub.unsubscribe(_subscriptionID[topic]); 20414 delete _subscriptionID[topic]; 20415 }, 20416 20417 /** 20418 * Get my tab id. 20419 * @returns {String} tabid : The tabid of this container/gadget. 20420 * @private 20421 */ 20422 _getMyTab = function () { 20423 if (_myTab === null) 20424 { 20425 try { 20426 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 20427 } catch (err) { 20428 _logger.log("Error accessing current tab: " + err.message); 20429 _myTab = null; 20430 } 20431 } 20432 return _myTab; 20433 }, 20434 20435 /** 20436 * Callback function that is called when an activeTab message is posted to the Hub. 20437 * Notifies listener functions if this tab is the one that was just made active. 20438 * @param {String} tabId : The tabId which was just made visible. 20439 * @private 20440 */ 20441 _tabTracker = function(tabId) { 20442 if (tabId === _getMyTab()) { 20443 if(!_visible) { 20444 _visible = true; 20445 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 20446 } 20447 } else { 20448 _visible = false; 20449 } 20450 }, 20451 20452 /** 20453 * Make a request to set a particular tab active. This 20454 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20455 * to ensure the gadget gets properly initialized. 20456 * @param {String} tabId 20457 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20458 * @private 20459 */ 20460 _activateTab = function ( tabId ) { 20461 _logger.log("Sending request to activate tab: " + tabId); 20462 if(_hub){ 20463 var data = { 20464 type: "SetActiveTabReq", 20465 data: { id: tabId }, 20466 invokeID: (new Date()).getTime() 20467 }; 20468 _hub.publish(_topics.REQUESTS, data); 20469 } else { 20470 throw new Error("Hub is not defined."); 20471 } 20472 20473 }, 20474 20475 /** 20476 * Callback function that is called when a gadget view changed message is posted to the Hub. 20477 * @private 20478 */ 20479 _gadgetViewChanged = function (data) { 20480 if (data) { 20481 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 20482 data.gadgetId, 20483 data.tabId, 20484 data.maxAvailableHeight, 20485 data.view); 20486 20487 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 20488 } 20489 }, 20490 20491 /** 20492 * Callback function that is called when a max available height changed message is posted to the Hub. 20493 * @private 20494 */ 20495 _maxAvailableHeightChanged = function (data) { 20496 if (data) { 20497 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 20498 data.maxAvailableHeight); 20499 20500 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 20501 } 20502 }, 20503 20504 /** 20505 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 20506 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 20507 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 20508 * @private 20509 */ 20510 _workflowActionEventTracker = function(workflowActionEvent) { 20511 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 20512 20513 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 20514 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 20515 } 20516 // else 20517 // { 20518 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 20519 // } 20520 20521 }, 20522 20523 /** 20524 * Callback function that is called when a reloadGadget event message is posted to the Hub. 20525 * 20526 * Grabs the id of the gadget we want to reload from the data and reload it! 20527 * 20528 * @param {String} topic 20529 * which topic the event came on (unused) 20530 * @param {Object} data 20531 * the data published with the event 20532 * @private 20533 */ 20534 _masterReloader = function (topic, data) { 20535 var gadgetId = data.gadgetId; 20536 if (gadgetId) { 20537 _container.reloadGadget(gadgetId); 20538 } 20539 }, 20540 20541 /** 20542 * Pulls the gadget id from the url parameters 20543 * @return {String} id of the gadget 20544 * @private 20545 */ 20546 _findMyGadgetId = function () { 20547 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 20548 return gadgets.util.getUrlParameters().mid; 20549 } 20550 }; 20551 20552 return { 20553 /** 20554 * @class 20555 * This class provides container-level services for gadget developers, exposing container events by 20556 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 20557 * event handling (add/remove). 20558 * @example 20559 * containerServices = finesse.containerservices.ContainerServices.init(); 20560 * containerServices.addHandler( 20561 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 20562 * function() { 20563 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 20564 * // automatically adjust the height of the gadget to show the html 20565 * gadgets.window.adjustHeight(); 20566 * }); 20567 * containerServices.makeActiveTabReq(); 20568 * 20569 * @constructs 20570 */ 20571 _fakeConstuctor: function () { 20572 /* This is here so we can document init() as a method rather than as a constructor. */ 20573 }, 20574 20575 /** 20576 * Initialize ContainerServices for use in gadget. 20577 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 20578 * @returns ContainerServices instance. 20579 */ 20580 init: function (master) { 20581 if (!_inited) { 20582 _inited = true; 20583 // Set shortcuts 20584 _util = Utilities; 20585 _authModes = _util.getAuthModes(); 20586 20587 //init the hub only when it's available 20588 if(gadgets.Hub) { 20589 _hub = gadgets.Hub; 20590 } 20591 20592 if(Topics) { 20593 _topics = Topics; 20594 } 20595 20596 if (master) { 20597 _master = true; 20598 _container = finesse.container.Container; 20599 _publisher = new MasterPublisher(); 20600 20601 // subscribe for reloading gadget events 20602 // we only want the master ContainerServices handling these events 20603 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 20604 } else { 20605 _container = parent.finesse.container.Container; 20606 } 20607 } 20608 20609 this.makeActiveTabReq(); 20610 20611 //Return the CS object for object chaining. 20612 return this; 20613 }, 20614 20615 /** 20616 * Shows the jQuery UI Dialog with the specified parameters. The following are the 20617 * default parameters: <ul> 20618 * <li> Title of "Cisco Finesse".</li> 20619 * <li>Message of "A generic error has occured".</li> 20620 * <li>The only button, "Ok", closes the dialog.</li> 20621 * <li>Modal (blocks other dialogs).</li> 20622 * <li>Not draggable.</li> 20623 * <li>Fixed size.</li></ul> 20624 * @param {Object} options 20625 * An object containing additional options for the dialog. 20626 * @param {String/Boolean} options.title 20627 * Title to use. undefined defaults to "Cisco Finesse". false to hide 20628 * @param {Function} options.close 20629 * A function to invoke when the dialog is closed. 20630 * @param {String} options.message 20631 * The message to display in the dialog. 20632 * Defaults to "A generic error has occurred." 20633 * @param {Boolean} options.isBlocking 20634 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 20635 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 20636 * @see finesse.containerservices.ContainerServices#hideDialog 20637 */ 20638 showDialog: function(options) { 20639 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 20640 return _container.showDialog(options); 20641 } 20642 }, 20643 20644 /** 20645 * Hides the jQuery UI Dialog. 20646 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 20647 * @see finesse.containerservices.ContainerServices#showDialog 20648 */ 20649 hideDialog: function() { 20650 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 20651 return _container.hideDialog(); 20652 } 20653 }, 20654 20655 /** 20656 * Reloads the current gadget. 20657 * For use from within a gadget only. 20658 */ 20659 reloadMyGadget: function () { 20660 var topic, gadgetId, data; 20661 20662 if (!_master) { 20663 // first unsubscribe this gadget from all topics on the hub 20664 for (topic in _notifiers) { 20665 if (_notifiers.hasOwnProperty(topic)) { 20666 _unsubscribe(topic); 20667 delete _notifiers[topic]; 20668 } 20669 } 20670 20671 // send an asynch request to the hub to tell the master container 20672 // services that we want to refresh this gadget 20673 gadgetId = _findMyGadgetId(); 20674 data = { 20675 type: "ReloadGadgetReq", 20676 data: {gadgetId: gadgetId}, 20677 invokeID: (new Date()).getTime() 20678 }; 20679 _hub.publish(_topics.REQUESTS, data); 20680 } 20681 }, 20682 20683 /** 20684 * Updates the url for this gadget and then reload it. 20685 * 20686 * This allows the gadget to be reloaded from a different location 20687 * than what is uploaded to the current server. For example, this 20688 * would be useful for 3rd party gadgets to implement their own failover 20689 * mechanisms. 20690 * 20691 * For use from within a gadget only. 20692 * 20693 * @param {String} url 20694 * url from which to reload gadget 20695 */ 20696 reloadMyGadgetFromUrl: function (url) { 20697 if (!_master) { 20698 var gadgetId = _findMyGadgetId(); 20699 20700 // update the url in the container 20701 _container.modifyGadgetUrl(gadgetId, url); 20702 20703 // reload it 20704 this.reloadMyGadget(); 20705 } 20706 }, 20707 20708 /** 20709 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 20710 * will be invoked when that topic is notified. 20711 * @param {String} topic 20712 * The Hub topic to which we are listening. 20713 * @param {Function} callback 20714 * The callback function to invoke. 20715 * @see finesse.containerservices.ContainerServices.Topics 20716 * @see finesse.containerservices.ContainerServices#removeHandler 20717 */ 20718 addHandler: function (topic, callback) { 20719 _isInited(); 20720 var notifier = null; 20721 20722 try { 20723 // For backwards compatibility... 20724 if (topic === "tabVisible") { 20725 if (window.console && typeof window.console.log === "function") { 20726 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 20727 } 20728 20729 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 20730 } 20731 20732 // Add the callback to the notifier. 20733 _util.validateHandler(callback); 20734 20735 notifier = _getNotifierReference(topic); 20736 20737 notifier.addListener(callback); 20738 20739 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 20740 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 20741 // to only when necessary. 20742 _subscribe(topic, _topicCallback(topic)); 20743 20744 } catch (err) { 20745 throw new Error("addHandler(): " + err); 20746 } 20747 }, 20748 20749 /** 20750 * Removes a previously-added handler for one of the supported topics. 20751 * @param {String} topic 20752 * The Hub topic from which we are removing the callback. 20753 * @param {Function} callback 20754 * The name of the callback function to remove. 20755 * @see finesse.containerservices.ContainerServices.Topics 20756 * @see finesse.containerservices.ContainerServices#addHandler 20757 */ 20758 removeHandler: function(topic, callback) { 20759 var notifier = null; 20760 20761 try { 20762 _util.validateHandler(callback); 20763 20764 notifier = _getNotifierReference(topic); 20765 20766 notifier.removeListener(callback); 20767 } catch (err) { 20768 throw new Error("removeHandler(): " + err); 20769 } 20770 }, 20771 20772 /** 20773 * Wrapper API for publishing data on the Openajax hub 20774 * @param {String} topic 20775 * The Hub topic to which we are publishing. 20776 * @param {Object} data 20777 * The data to be published on the hub. 20778 */ 20779 publish : function(topic , data){ 20780 if(_hub){ 20781 _hub.publish(topic, data); 20782 } else { 20783 throw new Error("Hub is not defined."); 20784 } 20785 }, 20786 20787 /** 20788 * Returns the visibility of current gadget. Note that this 20789 * will not be set until after the initialization of the gadget. 20790 * @return {Boolean} The visibility of current gadget. 20791 */ 20792 tabVisible: function(){ 20793 return _visible; 20794 }, 20795 20796 /** 20797 * Make a request to check the current tab. The 20798 * activeTab event will be invoked if on the active tab. This 20799 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20800 * to ensure the gadget gets properly initialized. 20801 */ 20802 makeActiveTabReq : function () { 20803 if(_hub){ 20804 var data = { 20805 type: "ActiveTabReq", 20806 data: {}, 20807 invokeID: (new Date()).getTime() 20808 }; 20809 _hub.publish(_topics.REQUESTS, data); 20810 } else { 20811 throw new Error("Hub is not defined."); 20812 } 20813 20814 }, 20815 20816 /** 20817 * Make a request to set a particular tab active. This 20818 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20819 * to ensure the gadget gets properly initialized. 20820 * @param {String} tabId 20821 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20822 */ 20823 activateTab : function (tabId) { 20824 _activateTab(tabId); 20825 }, 20826 20827 /** 20828 * Make a request to set this container's tab active. This 20829 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20830 * to ensure the gadget gets properly initialized. 20831 */ 20832 activateMyTab : function () { 20833 _activateTab( _getMyTab() ); 20834 }, 20835 20836 /** 20837 * Get the tabId of my container/gadget. 20838 * @returns {String} tabid : The tabid of this container/gadget. 20839 */ 20840 getMyTabId : function () { 20841 return _getMyTab(); 20842 }, 20843 20844 /** 20845 * Gets the id of the gadget. 20846 * @returns {number} the id of the gadget 20847 */ 20848 getMyGadgetId : function () { 20849 return _findMyGadgetId(); 20850 }, 20851 20852 //BEGIN TEST CODE// 20853 /** 20854 * Test code added to expose private functions that are used by unit test 20855 * framework. This section of code is removed during the build process 20856 * before packaging production code. The [begin|end]TestSection are used 20857 * by the build to identify the section to strip. 20858 * @ignore 20859 */ 20860 beginTestSection : 0, 20861 20862 /** 20863 * @ignore 20864 */ 20865 getTestObject: function () { 20866 //Load mock dependencies. 20867 var _mock = new MockControl(); 20868 _util = _mock.createMock(Utilities); 20869 _hub = _mock.createMock(gadgets.Hub); 20870 _inited = true; 20871 return { 20872 //Expose mock dependencies 20873 mock: _mock, 20874 hub: _hub, 20875 util: _util, 20876 addHandler: this.addHandler, 20877 removeHandler: this.removeHandler 20878 }; 20879 }, 20880 20881 /** 20882 * @ignore 20883 */ 20884 endTestSection: 0 20885 //END TEST CODE// 20886 }; 20887 }()); 20888 20889 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 20890 /** 20891 * Topic for subscribing to be notified when the active tab changes. 20892 * The provided callback will be invoked when the tab that the gadget 20893 * that subscribes with this becomes active. To ensure code is called 20894 * when the gadget is already on the active tab use the 20895 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 20896 * method. 20897 */ 20898 ACTIVE_TAB: "finesse.containerservices.activeTab", 20899 20900 /** 20901 * Topic for WorkflowAction events traffic. 20902 * The provided callback will be invoked when a WorkflowAction needs 20903 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 20904 * that can be used to interrogate the WorkflowAction and determine to use or not. 20905 */ 20906 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 20907 20908 /** 20909 * Topic for Timer Tick event. 20910 * The provided callback will be invoked when this event is fired. 20911 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 20912 */ 20913 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 20914 20915 /** 20916 * Topic for Reload Gadget events traffic. 20917 * Only the master ContainerServices instance will handle this event. 20918 */ 20919 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 20920 20921 /** 20922 * Topic for listening to gadget view changed events. 20923 * The provided callback will be invoked when a gadget changes view. 20924 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 20925 */ 20926 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 20927 20928 /** 20929 * Topic for listening to max available height changed events. 20930 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 20931 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 20932 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 20933 */ 20934 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 20935 20936 /** 20937 * @class This is the set of Topics used for subscribing for events from ContainerServices. 20938 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 20939 * 20940 * @constructs 20941 */ 20942 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20943 }; 20944 20945 window.finesse = window.finesse || {}; 20946 window.finesse.containerservices = window.finesse.containerservices || {}; 20947 window.finesse.containerservices.ContainerServices = ContainerServices; 20948 20949 return ContainerServices; 20950 }); 20951 /** 20952 * FinesseToaster is a utility class to show toaster notification in Finesse. 20953 * FinesseToaster leverages HTML5 Notification API to display Toaster 20954 * Notification. 20955 * 20956 */ 20957 20958 define('containerservices/FinesseToaster',[],function() { 20959 20960 var FinesseToaster = (function() { 20961 /** @lends finesse.containerservices.FinesseToaster.prototype */ 20962 20963 var 20964 20965 /** How long the toaster will be displayed by default. Default timeout is 8 seconds */ 20966 AUTO_CLOSE_TIME = 8000, 20967 20968 /** PERMISSION_GRANTED constant for granted string */ 20969 PERMISSION_GRANTED = 'granted', 20970 20971 /** PERMISSION_DEFAULT constant for default string */ 20972 PERMISSION_DEFAULT = 'default', 20973 20974 /** PERMISSION_DENIED constant for denied string */ 20975 PERMISSION_DENIED = 'denied', 20976 20977 /** ICON_PATH constant for holding path icon images */ 20978 ICON_PATH = '/desktop/theme/finesse/images/modules/', 20979 20980 /** 20981 * Shortcut reference to finesse.cslogger.ClientLogger singleton This 20982 * will be set by init(), it should already be initialized by 20983 * PageServices 20984 * 20985 * @private 20986 */ 20987 _logger, 20988 20989 /** 20990 * Boolean variable to determine if finesse toaster is enabled 20991 * @private 20992 */ 20993 _isToasterDisabled = false, 20994 20995 /** 20996 * Boolean variable to determine if finesse toaster is initialized 20997 * @private 20998 */ 20999 _isToasterInitialized, 21000 21001 /** 21002 * this function check of provided parameter is a javascript function. 21003 * 21004 * @private 21005 */ 21006 isFunction = function(param) { 21007 if (typeof param === "function") { 21008 return true; 21009 } 21010 return false; 21011 }, 21012 21013 /** 21014 * _createNotification creates Notification instance 21015 * 21016 * @param {String} 21017 * title title string should be displayed in the Toaster 21018 * @param {Object} 21019 * options JSON object for notification options. 21020 */ 21021 _createNotification = function(title, options) { 21022 var notification = new window.Notification(title, options); 21023 return notification; 21024 }, 21025 21026 /** 21027 * _setAutoClose set the auto close time for toaster, it checks if 21028 * client code passed custom time out for the toaster, otherwise it set 21029 * the AUTO_CLOSE_TIME 21030 * 21031 * @param {Object} 21032 * notification window.Notification that creates Html5 21033 * Notification. 21034 * @param {String} 21035 * autoClose autoClose time of the Toaster 21036 * @return toasterTimeout Toaster Timeout 21037 */ 21038 _setAutoClose = function(notification, autoClose) { 21039 21040 // check if custom close time passed other wise set 21041 // DEFAULT_AUTO_CLOSE 21042 var autoCloseTime = (autoClose && !isNaN(autoClose)) ? autoClose 21043 : AUTO_CLOSE_TIME, 21044 // set the time out for notification toaster 21045 toasterTimer = setTimeout(function() { 21046 notification.close(); 21047 }, autoCloseTime); 21048 21049 return toasterTimer; 21050 21051 }, 21052 21053 /** This method will request permission to display Toaster. */ 21054 _requestPermission = function() { 21055 // If they are not denied (i.e. default) 21056 if (window.Notification 21057 && window.Notification.permission !== PERMISSION_DENIED) { 21058 // Request permission 21059 window.Notification 21060 .requestPermission(function(status) { 21061 21062 // Change based on user's decision 21063 if (window.Notification.permission !== status) { 21064 window.Notification.permission = status; 21065 } 21066 _logger 21067 .log("FinesseToaster.requestPermission(): request permission status " 21068 + status); 21069 21070 }); 21071 21072 } else { 21073 _logger 21074 .log("FinesseToaster.requestPermission(): Notification not supported or permission denied."); 21075 } 21076 21077 }, 21078 21079 /** 21080 * This method will add onclick and onerror listener to Notification. 21081 * on click of toaster the gadget which originally had focus may loose 21082 * the focus. To get the back the focus on any element inside the gadget 21083 * use the on click callback handler. 21084 * 21085 * @param {Object} 21086 * notification window.Notification that creates Html5 21087 * Notification. 21088 * @param {Object} 21089 * options JSON object for notification options. 21090 */ 21091 _addToasterListeners = function(notification, options, toasterTimer) { 21092 // this is onlcik handler of toaster. this handler will be invoked 21093 // on click of toaster 21094 notification.onclick = function() { 21095 // in case of manually closed toaster, stop the notification 21096 // auto close method to be invoked 21097 clearTimeout(toasterTimer); 21098 // This will maximize/activate chrome browser on click of 21099 // toaster. this handling required only in case of Chrome 21100 if (window.chrome) { 21101 parent.focus(); 21102 } 21103 21104 if (options && options.onclick) { 21105 if (isFunction(options.onclick)) { 21106 options.onclick(); 21107 } else { 21108 throw new Error("onclick callback must be a function"); 21109 } 21110 } 21111 21112 //close toaster upon click 21113 this.close(); 21114 21115 }; 21116 21117 // this is onerror handler of toaster, if there is any error while 21118 // loading toaster this hadnler will be invoked 21119 notification.onerror = function() { 21120 if (options && options.onerror) { 21121 if (isFunction(options.onerror)) { 21122 options.onerror(); 21123 } else { 21124 throw new Error("onerror callback must be a function"); 21125 } 21126 } 21127 }; 21128 }; 21129 21130 return { 21131 21132 /** 21133 * @class 21134 * FinesseToaster is a utility class to show toaster 21135 * notification in Finesse. FinesseToaster leverages <a 21136 * href="https://www.w3.org/TR/notifications/">HTML5 21137 * Notification</a> API to display Toaster Notification. 21138 * <p> <a 21139 * href="https://developer.mozilla.org/en/docs/Web/API/notification#Browser_compatibility">For 21140 * HTML5 Notification API and browser compatibility, please click 21141 * here.</a></p> 21142 * 21143 * @constructs 21144 */ 21145 _fakeConstuctor : function() { 21146 21147 }, 21148 /** 21149 * TOASTER_DEFAULT_ICONS constants has list of predefined icons (e.g INCOMING_CALL_ICON). 21150 * <p><b>Constant list</b></p> 21151 * <ul> 21152 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CALL_ICON</li> 21153 * </ul> 21154 */ 21155 TOASTER_DEFAULT_ICONS : { 21156 INCOMING_CALL_ICON : ICON_PATH + "incoming_call.png" 21157 }, 21158 21159 /** 21160 * <b>showToaster </b>: shows Toaster Notification. 21161 * 21162 * @param {String} 21163 * <b>title</b> : title string should be displayed in the Toaster 21164 * @param {Object} 21165 * options is JSON object for notification options. 21166 * <ul> 21167 * <li><b>options</b> = { </li> 21168 * <li><b>body</b> : The body string of the notification as 21169 * specified in the options parameter of the constructor.</li> 21170 * <li><b>icon</b>: The URL of the image used as an icon of the 21171 * notification as specified in the options parameter of 21172 * the constructor.</li> 21173 * <li><b>autoClose</b> : custom auto close time of the toaster</li> 21174 * <li><b>showWhenVisible</b> : 'true' toaster shows up even when page is 21175 * visible,'false' toaster shows up only when page is invisible </li> 21176 * <li> }</li> 21177 * </ul> 21178 * 21179 */ 21180 showToaster : function(title, options) { 21181 21182 if(!_isToasterInitialized){ 21183 throw new Error("FinesseToaster.showToaster() : Finesse toaster is not initialized"); 21184 } 21185 21186 if(_isToasterDisabled){ 21187 _logger.log("FinesseToaster.showToaster() : FinesseToaster is disabled"); 21188 return; 21189 } 21190 21191 var notification, toasterTimer; 21192 21193 // If notifications are granted show the notification 21194 if (window.Notification 21195 && window.Notification.permission === PERMISSION_GRANTED) { 21196 21197 // document.hasFocus() used over document.hidden to keep the consistent behavior across mozilla/chrome 21198 if (document.hasFocus() === false 21199 || options.showWhenVisible) { 21200 if (_logger && AUTO_CLOSE_TIME > -1) { 21201 notification = _createNotification(title, options); 21202 21203 // set the auto close time out of the toaster 21204 toasterTimer = _setAutoClose(notification, 21205 options.autoClose); 21206 21207 // and Toaster Event listeners. eg. onclick , onerror. 21208 _addToasterListeners(notification, options, 21209 toasterTimer); 21210 } 21211 } else { 21212 _logger 21213 .log("FinesseToaster supressed : Page is visible and FineeseToaster.options.showWhenVisible is false"); 21214 } 21215 21216 } 21217 21218 return notification; 21219 }, 21220 21221 /** 21222 * initialize FininseToaster and inject dependencies. this method 21223 * will also request permission in browser from user to display 21224 * Toaster Notification. 21225 * 21226 *@param {Object} 21227 * Could be finesse.container.Config or finesse.gadget.Config based on where it is getting initialized from. 21228 * 21229 * @param {Object} 21230 * finesse.cslogger.ClientLogger 21231 * @return finesse.containerservices.FinesseToaster 21232 */ 21233 init : function(config , logger) { 21234 21235 _isToasterInitialized = true; 21236 21237 // This is for injecting mocked logger. 21238 if (logger) { 21239 _logger = logger; 21240 } else { 21241 _logger = finesse.cslogger.ClientLogger; 21242 } 21243 21244 //set default toaster notification timeout 21245 if (config && config.toasterNotificationTimeout) { 21246 AUTO_CLOSE_TIME = Number(config.toasterNotificationTimeout) * 1000; 21247 21248 if(AUTO_CLOSE_TIME === 0){ 21249 //Finesse toaster has been disabled 21250 _isToasterDisabled = true; 21251 } 21252 } 21253 21254 // Request permission 21255 _requestPermission(); 21256 return finesse.containerservices.FinesseToaster; 21257 } 21258 }; 21259 21260 }()); 21261 21262 window.finesse = window.finesse || {}; 21263 window.finesse.containerservices = window.finesse.containerservices || {}; 21264 window.finesse.containerservices.FinesseToaster = FinesseToaster; 21265 21266 return FinesseToaster; 21267 }); 21268 21269 /** 21270 * This "interface" is just a way to easily jsdoc the Object callback handlers. 21271 * 21272 * @requires finesse.clientservices.ClientServices 21273 * @requires Class 21274 */ 21275 /** @private */ 21276 define('interfaces/RestObjectHandlers',[ 21277 "FinesseBase", 21278 "utilities/Utilities", 21279 "restservices/Notifier", 21280 "clientservices/ClientServices", 21281 "clientservices/Topics" 21282 ], 21283 function () { 21284 21285 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 21286 21287 return { 21288 21289 /** 21290 * @class 21291 * This "interface" defines REST Object callback handlers, passed as an argument to 21292 * Object getter methods in cases where the Object is going to be created. 21293 * 21294 * @param {Object} [handlers] 21295 * An object containing callback handlers for instantiation and runtime 21296 * Callback to invoke upon successful instantiation, passes in REST object. 21297 * @param {Function} [handlers.onLoad(this)] 21298 * Callback to invoke upon loading the data for the first time. 21299 * @param {Function} [handlers.onChange(this)] 21300 * Callback to invoke upon successful update object (PUT) 21301 * @param {Function} [handlers.onAdd(this)] 21302 * Callback to invoke upon successful update to add object (POST) 21303 * @param {Function} [handlers.onDelete(this)] 21304 * Callback to invoke upon successful update to delete object (DELETE) 21305 * @param {Function} [handlers.onError(rsp)] 21306 * Callback to invoke on update error (refresh or event) 21307 * as passed by finesse.restservices.RestBase.restRequest()<br> 21308 * {<br> 21309 * status: {Number} The HTTP status code returned<br> 21310 * content: {String} Raw string of response<br> 21311 * object: {Object} Parsed object of response<br> 21312 * error: {Object} Wrapped exception that was caught<br> 21313 * error.errorType: {String} Type of error that was caught<br> 21314 * error.errorMessage: {String} Message associated with error<br> 21315 * }<br> 21316 * <br> 21317 * Note that RestCollections have two additional callback handlers:<br> 21318 * <br> 21319 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 21320 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 21321 21322 * @constructs 21323 */ 21324 _fakeConstuctor: function () { 21325 /* This is here to enable jsdoc to document this as a class. */ 21326 } 21327 }; 21328 }()); 21329 21330 window.finesse = window.finesse || {}; 21331 window.finesse.interfaces = window.finesse.interfaces || {}; 21332 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 21333 21334 return RestObjectHandlers; 21335 21336 }); 21337 21338 21339 /** 21340 * This "interface" is just a way to easily jsdoc the REST request handlers. 21341 * 21342 * @requires finesse.clientservices.ClientServices 21343 * @requires Class 21344 */ 21345 /** @private */ 21346 define('interfaces/RequestHandlers',[ 21347 "FinesseBase", 21348 "utilities/Utilities", 21349 "restservices/Notifier", 21350 "clientservices/ClientServices", 21351 "clientservices/Topics" 21352 ], 21353 function () { 21354 21355 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 21356 21357 return { 21358 21359 /** 21360 * @class 21361 * This "interface" defines REST Object callback handlers, passed as an argument to 21362 * Object getter methods in cases where the Object is going to be created. 21363 * 21364 * @param {Object} handlers 21365 * An object containing the following (optional) handlers for the request:<ul> 21366 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 21367 * response object as its only parameter:<ul> 21368 * <li><b>status:</b> {Number} The HTTP status code returned</li> 21369 * <li><b>content:</b> {String} Raw string of response</li> 21370 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 21371 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 21372 * error response object as its only parameter:<ul> 21373 * <li><b>status:</b> {Number} The HTTP status code returned</li> 21374 * <li><b>content:</b> {String} Raw string of response</li> 21375 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 21376 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 21377 * <li><b>errorType:</b> {String} Type of error that was caught</li> 21378 * <li><b>errorMessage:</b> {String} Message associated with error</li> 21379 * </ul></li> 21380 * </ul> 21381 21382 * @constructs 21383 */ 21384 _fakeConstuctor: function () { 21385 /* This is here to enable jsdoc to document this as a class. */ 21386 } 21387 }; 21388 }()); 21389 21390 window.finesse = window.finesse || {}; 21391 window.finesse.interfaces = window.finesse.interfaces || {}; 21392 window.finesse.interfaces.RequestHandlers = RequestHandlers; 21393 21394 finesse = finesse || {}; 21395 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 21396 finesse.interfaces = finesse.interfaces || {}; 21397 21398 return RequestHandlers; 21399 21400 }); 21401 21402 21403 21404 define('gadget/Config',[ 21405 "utilities/Utilities" 21406 ], function (Utilities) { 21407 var Config = (function () { /** @lends finesse.gadget.Config.prototype */ 21408 21409 if (gadgets && gadgets.Prefs) { 21410 21411 var _prefs = new gadgets.Prefs(); 21412 21413 return { 21414 /** 21415 * The base64 encoded "id:password" string used for authentication. 21416 */ 21417 authorization: Utilities.getUserAuthString(), 21418 21419 /** 21420 * The auth token string used for authentication in SSO deployments. 21421 */ 21422 authToken: Utilities.getToken(), 21423 21424 /** 21425 * The country code of the client (derived from locale). 21426 */ 21427 country: _prefs.getString("country"), 21428 21429 /** 21430 * The language code of the client (derived from locale). 21431 */ 21432 language: _prefs.getString("language"), 21433 21434 /** 21435 * The locale of the client. 21436 */ 21437 locale: _prefs.getString("locale"), 21438 21439 /** 21440 * The Finesse server IP/host as reachable from the browser. 21441 */ 21442 host: _prefs.getString("host"), 21443 21444 /** 21445 * The Finesse server host's port reachable from the browser. 21446 */ 21447 hostPort: _prefs.getString("hostPort"), 21448 21449 /** 21450 * The extension of the user. 21451 */ 21452 extension: _prefs.getString("extension"), 21453 21454 /** 21455 * One of the work modes found in {@link finesse.restservices.User.WorkMode}, or something false (undefined) for a normal login. 21456 */ 21457 mobileAgentMode: _prefs.getString("mobileAgentMode"), 21458 21459 /** 21460 * The dial number to use for mobile agent, or something false (undefined) for a normal login. 21461 */ 21462 mobileAgentDialNumber: _prefs.getString("mobileAgentDialNumber"), 21463 21464 /** 21465 * The domain of the XMPP server. 21466 */ 21467 xmppDomain: _prefs.getString("xmppDomain"), 21468 21469 /** 21470 * The pub sub domain where the pub sub service is running. 21471 */ 21472 pubsubDomain: _prefs.getString("pubsubDomain"), 21473 21474 /** 21475 * The Finesse API IP/host as reachable from the gadget container. 21476 */ 21477 restHost: _prefs.getString("restHost"), 21478 21479 /** 21480 * The type of HTTP protocol (http or https). 21481 */ 21482 scheme: _prefs.getString("scheme"), 21483 21484 /** 21485 * The localhost fully qualified domain name. 21486 */ 21487 localhostFQDN: _prefs.getString("localhostFQDN"), 21488 21489 /** 21490 * The localhost port. 21491 */ 21492 localhostPort: _prefs.getString("localhostPort"), 21493 21494 /** 21495 * The id of the team the user belongs to. 21496 */ 21497 teamId: _prefs.getString("teamId"), 21498 21499 /** 21500 * The name of the team the user belongs to. 21501 */ 21502 teamName: _prefs.getString("teamName"), 21503 21504 /** 21505 * The drift time between the client and the server in milliseconds. 21506 */ 21507 clientDriftInMillis: _prefs.getInt("clientDriftInMillis"), 21508 21509 /** 21510 * The client compatibility mode configuration (true if it is or false otherwise). 21511 */ 21512 compatibilityMode: _prefs.getString("compatibilityMode"), 21513 21514 /** 21515 * The peripheral Id that Finesse is connected to. 21516 */ 21517 peripheralId: _prefs.getString("peripheralId"), 21518 21519 /** 21520 * The auth mode of the finesse deployment. 21521 */ 21522 systemAuthMode: _prefs.getString("systemAuthMode"), 21523 21524 21525 /** 21526 * The time for which fineese toaster stay on the browser. 21527 */ 21528 toasterNotificationTimeout: _prefs.getString("toasterNotificationTimeout"), 21529 21530 /** 21531 * @class 21532 * The Config object for gadgets within the Finesse desktop container which 21533 * contains configuration data provided by the container page. 21534 * @constructs 21535 */ 21536 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 21537 21538 }; 21539 } else { 21540 return {}; 21541 } 21542 }()); 21543 21544 /** Assign to container and gadget namespace to have config available in both */ 21545 window.finesse = window.finesse || {}; 21546 window.finesse.container = window.finesse.container || {}; 21547 window.finesse.container.Config = window.finesse.container.Config || Config; 21548 21549 window.finesse.gadget = window.finesse.gadget || {}; 21550 window.finesse.gadget.Config = Config; 21551 21552 return Config; 21553 }); 21554 define('finesse',[ 21555 'restservices/Users', 21556 'restservices/Teams', 21557 'restservices/SystemInfo', 21558 'restservices/Media', 21559 'restservices/MediaDialogs', 21560 'restservices/DialogLogoutActions', 21561 'restservices/InterruptActions', 21562 'restservices/ReasonCodeLookup', 21563 'utilities/I18n', 21564 'utilities/Logger', 21565 'utilities/SaxParser', 21566 'utilities/BackSpaceHandler', 21567 'cslogger/ClientLogger', 21568 'cslogger/FinesseLogger', 21569 'containerservices/ContainerServices', 21570 'containerservices/FinesseToaster', 21571 'interfaces/RestObjectHandlers', 21572 'interfaces/RequestHandlers', 21573 'gadget/Config' 21574 ], 21575 function () { 21576 return window.finesse; 21577 }); 21578 21579 require(["finesse"]); 21580 return require('finesse'); })); 21581 21582 // Prevent other JS files from wiping out window.finesse from the namespace 21583 var finesse = window.finesse;