1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 10.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 * A PhoneBook is a list of Contacts available to a User for quick dial. 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 * Takes a string (typically from window.location) and finds the value which corresponds to a name. For 2490 * example: http://www.company.com/?param1=value1¶m2=value2 2491 * 2492 * @param str is the string to search 2493 * @param name is the name to search for 2494 */ 2495 getParameterByName : function(str, name) { 2496 var regex, results; 2497 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 2498 regex = new RegExp("[\\?&]" + name + "=([^]*)"); 2499 results = regex.exec(str); 2500 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 2501 }, 2502 2503 /** 2504 * @private 2505 * Gets the user Authorization String from the window session. 2506 * @returns the Authorization String 2507 * 2508 */ 2509 getUserAuthString: function () { 2510 var authString = window.sessionStorage.getItem('userFinesseAuth'); 2511 return authString; 2512 }, 2513 2514 /** 2515 * @private 2516 * Adds a new cookie to the page with a default domain. 2517 * @param {String} key 2518 * the key to assign a value to 2519 * @param {String} value 2520 * the value to assign to the key 2521 * @param {Number} days 2522 * number of days (from current) until the cookie should expire 2523 */ 2524 addCookie : function (key, value, days) { 2525 var date, expires = "", 2526 cookie = key + "=" + escape(value); 2527 if (typeof days === "number") { 2528 date = new Date(); 2529 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 2530 cookie += "; expires=" + date.toGMTString(); 2531 } 2532 document.cookie = cookie + "; path=/"; 2533 }, 2534 2535 /** 2536 * @private 2537 * Get the value of a cookie given a key. 2538 * @param {String} key 2539 * a key to lookup 2540 * @returns {String} 2541 * the value mapped to a key, null if key doesn't exist 2542 */ 2543 getCookie : function (key) { 2544 var i, pairs, pair; 2545 if (document.cookie) { 2546 pairs = document.cookie.split(";"); 2547 for (i = 0; i < pairs.length; i += 1) { 2548 pair = this.trim(pairs[i]).split("="); 2549 if (pair[0] === key) { 2550 return unescape(pair[1]); 2551 } 2552 } 2553 } 2554 return null; 2555 }, 2556 2557 /** 2558 * @private 2559 * Deletes the cookie mapped to specified key. 2560 * @param {String} key 2561 * the key to delete 2562 */ 2563 deleteCookie : function (key) { 2564 this.addCookie(key, "", -1); 2565 }, 2566 2567 /** 2568 * @private 2569 * Case insensitive sort for use with arrays or Dojox stores 2570 * @param {String} a 2571 * first value 2572 * @param {String} b 2573 * second value 2574 */ 2575 caseInsensitiveSort: function (a, b) { 2576 var ret = 0, emptyString = ""; 2577 a = a + emptyString; 2578 b = b + emptyString; 2579 a = a.toLowerCase(); 2580 b = b.toLowerCase(); 2581 if (a > b) { 2582 ret = 1; 2583 } 2584 if (a < b) { 2585 ret = -1; 2586 } 2587 return ret; 2588 }, 2589 2590 /** 2591 * @private 2592 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 2593 * 2594 * The displayWjitFunc function will be called once and only once when the div for our wijit 2595 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 2596 * throw exceptions and do not render properly if they are created in a display:none div. 2597 * If our gadget is visisble the function will be called immediately. 2598 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 2599 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 2600 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 2601 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 2602 * end up waiting for the node to become visisble anyway. 2603 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 2604 */ 2605 onGadgetFirstVisible: function (displayWjitFunc) { 2606 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2607 try { 2608 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2609 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2610 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2611 2612 // Loop through all of the tab panels to find one that has our gadget id 2613 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2614 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2615 if (q.length > 0) { // You found it 2616 panelNode = node; 2617 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2618 active = dojo.hasClass(panelNode, "active"); 2619 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2620 return; 2621 } 2622 }); 2623 // If panel is already active - execute the function - we're done 2624 if (active) { 2625 //?console.log(frameId + " is visible display it"); 2626 setTimeout(displayWjitFunc); 2627 } 2628 // If its not visible - wait for the active class to show up. 2629 else { 2630 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2631 iterval = setInterval(dojo.hitch(this, function () { 2632 if (dojo.hasClass(panelNode, "active")) { 2633 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2634 clearInterval(iterval); 2635 setTimeout(displayWjitFunc); 2636 } 2637 }), 250); 2638 } 2639 } catch (err) { 2640 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2641 } 2642 }, 2643 2644 /** 2645 * @private 2646 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2647 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2648 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2649 */ 2650 downloadFile : function (url) { 2651 var iframe = document.getElementById("download_iframe"); 2652 2653 if (!iframe) 2654 { 2655 iframe = document.createElement("iframe"); 2656 $(document.body).append(iframe); 2657 $(iframe).css("display", "none"); 2658 } 2659 2660 iframe.src = url; 2661 }, 2662 2663 /** 2664 * @private 2665 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2666 */ 2667 bitMask: { 2668 /** @private */ 2669 isSet: function (value, mask) { 2670 return (value & mask) === mask; 2671 }, 2672 /** 2673 * Returns true if all flags in the intArray are set on the specified value 2674 * @private 2675 */ 2676 all: function (value, intArray) { 2677 var i = intArray.length; 2678 if (typeof(i) === "undefined") 2679 { 2680 intArray = [intArray]; 2681 i = 1; 2682 } 2683 while ((i = i - 1) !== -1) 2684 { 2685 if (!this.isSet(value, intArray[i])) 2686 { 2687 return false; 2688 } 2689 } 2690 return true; 2691 }, 2692 /** 2693 * @private 2694 * Returns true if any flags in the intArray are set on the specified value 2695 */ 2696 any: function (value, intArray) { 2697 var i = intArray.length; 2698 if (typeof(i) === "undefined") 2699 { 2700 intArray = [intArray]; 2701 i = 1; 2702 } 2703 while ((i = i - 1) !== -1) 2704 { 2705 if (this.isSet(value, intArray[i])) 2706 { 2707 return true; 2708 } 2709 } 2710 return false; 2711 } 2712 }, 2713 2714 /** @private */ 2715 renderDojoGridOffScreen: function (grid) { 2716 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2717 $(document.body).append(offscreenDiv); 2718 grid.placeAt(offscreenDiv); 2719 grid.startup(); 2720 document.body.removeChild(offscreenDiv); 2721 return grid; 2722 }, 2723 2724 /** @private */ 2725 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2726 var timerId = null, 2727 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2728 theInputControl = theControl.find("input"), 2729 theClearButton = theControl.find("a"), 2730 inputControlWidthWithClear = 204, 2731 inputControlWidthNoClear = 230, 2732 sPreviousInput = theInputControl.val(), 2733 /** @private **/ 2734 toggleClearButton = function(){ 2735 if (theInputControl.val() === "") { 2736 theClearButton.hide(); 2737 theControl.removeClass("input-append"); 2738 theInputControl.width(inputControlWidthNoClear); 2739 } else { 2740 theInputControl.width(inputControlWidthWithClear); 2741 theClearButton.show(); 2742 theControl.addClass("input-append"); 2743 } 2744 }; 2745 2746 // set placeholder text 2747 theInputControl.attr('placeholder', placeholderText); 2748 2749 theInputControl.unbind('keyup').bind('keyup', function() { 2750 if (sPreviousInput !== theInputControl.val()) { 2751 window.clearTimeout(timerId); 2752 sPreviousInput = theInputControl.val(); 2753 timerId = window.setTimeout(function() { 2754 changeCallback.call((callbackScope || window), theInputControl.val()); 2755 theInputControl[0].focus(); 2756 }, callbackDelay); 2757 } 2758 2759 toggleClearButton(); 2760 }); 2761 2762 theClearButton.bind('click', function() { 2763 theInputControl.val(''); 2764 changeCallback.call((callbackScope || window), ''); 2765 2766 toggleClearButton(); 2767 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2768 }); 2769 2770 theInputControl.val(""); 2771 toggleClearButton(); 2772 }, 2773 2774 DataTables: { 2775 /** @private */ 2776 createDataTable: function (options, dataTableOptions) { 2777 var grid, 2778 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 2779 headerRow = table.find("tr"), 2780 defaultOptions = { 2781 "aaData": [], 2782 "bPaginate": false, 2783 "bLengthChange": false, 2784 "bFilter": false, 2785 "bInfo": false, 2786 "sScrollY": "176", 2787 "oLanguage": { 2788 "sEmptyTable": "", 2789 "sZeroRecords": "" 2790 } 2791 }, 2792 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 2793 columnDefs = [], 2794 columnFormatter; 2795 2796 // Create a header cell for each column, and set up the datatable definition for the column 2797 $(options.columns).each(function (index, column) { 2798 headerRow.append($("<th></th>")); 2799 columnDefs[index] = { 2800 "mData": column.propertyName, 2801 "sTitle": column.columnHeader, 2802 "sWidth": column.width, 2803 "aTargets": [index], 2804 "bSortable": column.sortable, 2805 "bVisible": column.visible, 2806 "mRender": column.render 2807 }; 2808 if (typeof(column.renderFunction) === "function") 2809 { 2810 /** @ignore **/ 2811 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 2812 var returnValue; 2813 2814 //Apply column render logic to value before applying extra render function 2815 if (typeof(column.render) === "function") 2816 { 2817 value = column.render.call(value, value, value); 2818 } 2819 2820 if (typeof(type) === "string") 2821 { 2822 switch (type) 2823 { 2824 case "undefined": 2825 case "sort": 2826 returnValue = value; 2827 break; 2828 case "set": 2829 throw new Error("Unsupported set data in Finesse Grid"); 2830 case "filter": 2831 case "display": 2832 case "type": 2833 returnValue = column.renderFunction.call(dataObject, value, dataObject); 2834 break; 2835 default: 2836 break; 2837 } 2838 } 2839 else 2840 { 2841 throw new Error("type param not specified in Finesse DataTable mData"); 2842 } 2843 2844 return returnValue; 2845 }; 2846 } 2847 }); 2848 gridOptions.aoColumnDefs = columnDefs; 2849 2850 // Set the height 2851 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 2852 { 2853 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 2854 } 2855 2856 // Place it into the DOM 2857 if (typeof(options.container) !== "undefined" && options.container !== null) 2858 { 2859 $(options.container).append(table); 2860 } 2861 2862 // Create the DataTable 2863 table.dataTable(gridOptions); 2864 2865 return table; 2866 } 2867 }, 2868 2869 /** 2870 * @private 2871 * Sets a dojo button to the specified disable state, removing it from 2872 * the tab order if disabling, and restoring it to the tab order if enabling. 2873 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 2874 * @param {bool} disabled 2875 */ 2876 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 2877 var labelNode, 2878 tabIndex; 2879 2880 dojoButton.set("disabled", disabled); 2881 2882 // Remove the tabindex attribute on disabled buttons, store it, 2883 // and replace it when it becomes enabled again 2884 labelNode = $("#" + dojoButton.id + "_label"); 2885 if (disabled) 2886 { 2887 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 2888 labelNode.removeAttr("tabindex"); 2889 } 2890 else 2891 { 2892 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 2893 if (typeof(tabIndex) === "string") 2894 { 2895 labelNode.attr("tabindex", Number(tabIndex)); 2896 } 2897 } 2898 }, 2899 2900 /** 2901 * @private 2902 * Measures the given text using the supplied fontFamily and fontSize 2903 * @param {string} text text to measure 2904 * @param {string} fontFamily 2905 * @param {string} fontSize 2906 * @return {number} pixel width 2907 */ 2908 measureText: function (text, fontFamily, fontSize) { 2909 var width, 2910 element = $("<div></div>").text(text).css({ 2911 "fontSize": fontSize, 2912 "fontFamily": fontFamily 2913 }).addClass("offscreen").appendTo(document.body); 2914 2915 width = element.width(); 2916 element.remove(); 2917 2918 return width; 2919 }, 2920 2921 /** 2922 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 2923 * needing to resize down in IE. This gets around that by calculating the height 2924 * manually and passing it in. 2925 * @return {undefined} 2926 */ 2927 "adjustGadgetHeight": function () { 2928 var bScrollHeight = $("body").height() + 20; 2929 gadgets.window.adjustHeight(bScrollHeight); 2930 }, 2931 2932 /** 2933 * Private helper method for converting a javascript object to xml, where the values of the elements are 2934 * appropriately escaped for XML. 2935 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 2936 * there is no whitespace between elements. 2937 * @param object The javascript object to convert to XML. 2938 * @returns The XML string. 2939 * @private 2940 */ 2941 _json2xmlWithEscape: function(object) { 2942 var that = this, 2943 xml = "", 2944 m, 2945 /** @private **/ 2946 toXmlHelper = function(value, name) { 2947 var xml = "", 2948 i, 2949 m; 2950 if (value instanceof Array) { 2951 for (i = 0; i < value.length; ++i) { 2952 xml += toXmlHelper(value[i], name); 2953 } 2954 } 2955 else if (typeof value === "object") { 2956 xml += "<" + name + ">"; 2957 for (m in value) { 2958 if (value.hasOwnProperty(m)) { 2959 xml += toXmlHelper(value[m], m); 2960 } 2961 } 2962 xml += "</" + name + ">"; 2963 } 2964 else { 2965 // is a leaf node 2966 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 2967 "</" + name + ">"; 2968 } 2969 return xml; 2970 }; 2971 for (m in object) { 2972 if (object.hasOwnProperty(m)) { 2973 xml += toXmlHelper(object[m], m); 2974 } 2975 } 2976 return xml; 2977 }, 2978 2979 /** 2980 * Private method for returning a sanitized version of the user agent string. 2981 * @returns the user agent string, but sanitized! 2982 * @private 2983 */ 2984 getSanitizedUserAgentString: function () { 2985 return this.translateXMLEntities(navigator.userAgent, true); 2986 }, 2987 2988 /** 2989 * Use JQuery's implementation of Promises (Deferred) to execute code when 2990 * multiple async processes have finished. An example use: 2991 * 2992 * var asyncProcess1 = $.Deferred(), 2993 * asyncProcess2 = $.Deferred(); 2994 * 2995 * finesse.utilities.Utilities.whenAllDone(asyncProcess1, asyncProcess2) // WHEN both asyncProcess1 and asyncProcess2 are resolved or rejected ... 2996 * .then( 2997 * // First function passed to then() is called when all async processes are complete, regardless of errors 2998 * function () { 2999 * console.log("all processes completed"); 3000 * }, 3001 * // Second function passed to then() is called if any async processed threw an exception 3002 * function (failures) { // Array of failure messages 3003 * console.log("Number of failed async processes: " + failures.length); 3004 * }); 3005 * 3006 * Found at: 3007 * http://stackoverflow.com/a/15094263/1244030 3008 * 3009 * Pass in any number of $.Deferred instances. 3010 * @returns {Object} 3011 */ 3012 whenAllDone: function () { 3013 var deferreds = [], 3014 result = $.Deferred(); 3015 3016 $.each(arguments, function(i, current) { 3017 var currentDeferred = $.Deferred(); 3018 current.then(function() { 3019 currentDeferred.resolve(false, arguments); 3020 }, function() { 3021 currentDeferred.resolve(true, arguments); 3022 }); 3023 deferreds.push(currentDeferred); 3024 }); 3025 3026 $.when.apply($, deferreds).then(function() { 3027 var failures = [], 3028 successes = []; 3029 3030 $.each(arguments, function(i, args) { 3031 // If we resolved with `true` as the first parameter 3032 // we have a failure, a success otherwise 3033 var target = args[0] ? failures : successes, 3034 data = args[1]; 3035 // Push either all arguments or the only one 3036 target.push(data.length === 1 ? data[0] : args); 3037 }); 3038 3039 if(failures.length) { 3040 return result.reject.apply(result, failures); 3041 } 3042 3043 return result.resolve.apply(result, successes); 3044 }); 3045 3046 return result; 3047 } 3048 }; 3049 3050 window.finesse = window.finesse || {}; 3051 window.finesse.utilities = window.finesse.utilities || {}; 3052 window.finesse.utilities.Utilities = Utilities; 3053 3054 return Utilities; 3055 }); 3056 3057 /** The following comment is to prevent jslint errors about 3058 * using variables before they are defined. 3059 */ 3060 /*global finesse*/ 3061 3062 /** 3063 * Initiated by the Master to create a shared BOSH connection. 3064 * 3065 * @requires Utilities 3066 */ 3067 3068 /** 3069 * @class 3070 * Establishes a shared event connection by creating a communication tunnel 3071 * with the notification server and consume events which could be published. 3072 * Public functions are exposed to register to the connection status information 3073 * and events. 3074 * @constructor 3075 * @param {String} host 3076 * The host name/ip of the Finesse server. 3077 * @throws {Error} If required constructor parameter is missing. 3078 */ 3079 /** @private */ 3080 define('clientservices/MasterTunnel',["utilities/Utilities"], function (Utilities) { 3081 var MasterTunnel = function (host, scheme) { 3082 if (typeof host !== "string" || host.length === 0) { 3083 throw new Error("Required host parameter missing."); 3084 } 3085 3086 var 3087 3088 /** 3089 * Flag to indicate whether the tunnel frame is loaded. 3090 * @private 3091 */ 3092 _isTunnelLoaded = false, 3093 3094 /** 3095 * Short reference to the Finesse utility. 3096 * @private 3097 */ 3098 _util = Utilities, 3099 3100 /** 3101 * The URL with host and port to the Finesse server. 3102 * @private 3103 */ 3104 _tunnelOrigin, 3105 3106 /** 3107 * Location of the tunnel HTML URL. 3108 * @private 3109 */ 3110 _tunnelURL, 3111 3112 /** 3113 * The port on which to connect to the Finesse server to load the eventing resources. 3114 * @private 3115 */ 3116 _tunnelOriginPort, 3117 3118 /** 3119 * Flag to indicate whether we have processed the tunnel config yet. 3120 * @private 3121 */ 3122 _isTunnelConfigInit = false, 3123 3124 /** 3125 * The tunnel frame window object. 3126 * @private 3127 */ 3128 _tunnelFrame, 3129 3130 /** 3131 * The handler registered with the object to be invoked when an event is 3132 * delivered by the notification server. 3133 * @private 3134 */ 3135 _eventHandler, 3136 3137 /** 3138 * The handler registered with the object to be invoked when presence is 3139 * delivered by the notification server. 3140 * @private 3141 */ 3142 _presenceHandler, 3143 3144 /** 3145 * The handler registered with the object to be invoked when the BOSH 3146 * connection has changed states. The object will contain the "status" 3147 * property and a "resourceID" property only if "status" is "connected". 3148 * @private 3149 */ 3150 _connInfoHandler, 3151 3152 /** 3153 * The last connection status published by the JabberWerx library. 3154 * @private 3155 */ 3156 _statusCache, 3157 3158 /** 3159 * The last event sent by notification server. 3160 * @private 3161 */ 3162 _eventCache, 3163 3164 /** 3165 * The ID of the user logged into notification server. 3166 * @private 3167 */ 3168 _id, 3169 3170 /** 3171 * The domain of the XMPP server, representing the portion of the JID 3172 * following '@': userid@domain.com 3173 * @private 3174 */ 3175 _xmppDomain, 3176 3177 /** 3178 * The password of the user logged into notification server. 3179 * @private 3180 */ 3181 _password, 3182 3183 /** 3184 * The jid of the pubsub service on the XMPP server 3185 * @private 3186 */ 3187 _pubsubDomain, 3188 3189 /** 3190 * The resource to use for the BOSH connection. 3191 * @private 3192 */ 3193 _resource, 3194 3195 /** 3196 * The resource ID identifying the client device (that we receive from the server). 3197 * @private 3198 */ 3199 _resourceID, 3200 3201 /** 3202 * The different types of messages that could be sent to the parent frame. 3203 * The types here should be understood by the parent frame and used to 3204 * identify how the message is formatted. 3205 * @private 3206 */ 3207 _TYPES = { 3208 EVENT: 0, 3209 ID: 1, 3210 PASSWORD: 2, 3211 RESOURCEID: 3, 3212 STATUS: 4, 3213 XMPPDOMAIN: 5, 3214 PUBSUBDOMAIN: 6, 3215 SUBSCRIBE: 7, 3216 UNSUBSCRIBE: 8, 3217 PRESENCE: 9, 3218 CONNECT_REQ: 10 3219 }, 3220 3221 _handlers = { 3222 subscribe: {}, 3223 unsubscribe: {} 3224 }, 3225 3226 3227 /** 3228 * Create a connection info object. 3229 * @returns {Object} 3230 * A connection info object containing a "status" and "resourceID". 3231 * @private 3232 */ 3233 _createConnInfoObj = function () { 3234 return { 3235 status: _statusCache, 3236 resourceID: _resourceID 3237 }; 3238 }, 3239 3240 /** 3241 * Utility function which sends a message to the dynamic tunnel frame 3242 * event frame formatted as follows: "type|message". 3243 * @param {Number} type 3244 * The category type of the message. 3245 * @param {String} message 3246 * The message to be sent to the tunnel frame. 3247 * @private 3248 */ 3249 _sendMessage = function (type, message) { 3250 message = type + "|" + message; 3251 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 3252 }, 3253 3254 /** 3255 * Utility to process the response of a subscribe request from 3256 * the tunnel frame, then invoking the stored callback handler 3257 * with the respective data (error, when applicable) 3258 * @param {String} data 3259 * The response in the format of "node[|error]" 3260 * @private 3261 */ 3262 _processSubscribeResponse = function (data) { 3263 var dataArray = data.split("|"), 3264 node = dataArray[0], 3265 err; 3266 3267 //Error is optionally the second item in the array 3268 if (dataArray.length) { 3269 err = dataArray[1]; 3270 } 3271 3272 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3273 if (_handlers.subscribe[node]) { 3274 _handlers.subscribe[node](err); 3275 delete _handlers.subscribe[node]; 3276 } 3277 }, 3278 3279 /** 3280 * Utility to process the response of an unsubscribe request from 3281 * the tunnel frame, then invoking the stored callback handler 3282 * with the respective data (error, when applicable) 3283 * @param {String} data 3284 * The response in the format of "node[|error]" 3285 * @private 3286 */ 3287 _processUnsubscribeResponse = function (data) { 3288 var dataArray = data.split("|"), 3289 node = dataArray[0], 3290 err; 3291 3292 //Error is optionally the second item in the array 3293 if (dataArray.length) { 3294 err = dataArray[1]; 3295 } 3296 3297 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3298 if (_handlers.unsubscribe[node]) { 3299 _handlers.unsubscribe[node](err); 3300 delete _handlers.unsubscribe[node]; 3301 } 3302 }, 3303 3304 /** 3305 * Handler for messages delivered by window.postMessage. Listens for events 3306 * published by the notification server, connection status published by 3307 * the JabberWerx library, and the resource ID created when the BOSH 3308 * connection has been established. 3309 * @param {Object} e 3310 * The message object as provided by the window.postMessage feature. 3311 * @private 3312 */ 3313 _messageHandler = function (e) { 3314 var 3315 3316 //Extract the message type and message data. The expected format is 3317 //"type|data" where type is a number represented by the TYPES object. 3318 delimPos = e.data.indexOf("|"), 3319 type = Number(e.data.substr(0, delimPos)), 3320 data = e.data.substr(delimPos + 1); 3321 3322 //Accepts messages and invoke the correct registered handlers. 3323 switch (type) { 3324 case _TYPES.EVENT: 3325 _eventCache = data; 3326 if (typeof _eventHandler === "function") { 3327 _eventHandler(data); 3328 } 3329 break; 3330 case _TYPES.STATUS: 3331 _statusCache = data; 3332 3333 //A "loaded" status means that the frame is ready to accept 3334 //credentials for establishing a BOSH connection. 3335 if (data === "loaded") { 3336 _isTunnelLoaded = true; 3337 if(_resource) { 3338 _sendMessage(_TYPES.RESOURCEID, _resource); 3339 } 3340 _sendMessage(_TYPES.ID, _id); 3341 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 3342 _sendMessage(_TYPES.PASSWORD, _password); 3343 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 3344 } else if (typeof _connInfoHandler === "function") { 3345 _connInfoHandler(_createConnInfoObj()); 3346 } 3347 break; 3348 case _TYPES.RESOURCEID: 3349 _resourceID = data; 3350 break; 3351 case _TYPES.SUBSCRIBE: 3352 _processSubscribeResponse(data); 3353 break; 3354 case _TYPES.UNSUBSCRIBE: 3355 _processUnsubscribeResponse(data); 3356 break; 3357 case _TYPES.PRESENCE: 3358 if (typeof _presenceHandler === "function") { 3359 _presenceHandler(data); 3360 } 3361 break; 3362 default: 3363 break; 3364 } 3365 }, 3366 3367 /** 3368 * Initialize the tunnel config so that the url can be http or https with the appropriate port 3369 * @private 3370 */ 3371 _initTunnelConfig = function () { 3372 if (_isTunnelConfigInit === true) { 3373 return; 3374 } 3375 3376 //Initialize tunnel origin 3377 //Determine tunnel origin based on host and scheme 3378 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 3379 if (scheme) { 3380 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 3381 } else { 3382 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 3383 } 3384 _tunnelURL = _tunnelOrigin + "/tunnel/"; 3385 3386 _isTunnelConfigInit = true; 3387 }, 3388 3389 /** 3390 * Create the tunnel iframe which establishes the shared BOSH connection. 3391 * Messages are sent across frames using window.postMessage. 3392 * @private 3393 */ 3394 _createTunnel = function () { 3395 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 3396 iframe = document.createElement("iframe"); 3397 iframe.style.display = "none"; 3398 iframe.setAttribute("id", tunnelID); 3399 iframe.setAttribute("name", tunnelID); 3400 iframe.setAttribute("src", _tunnelURL); 3401 document.body.appendChild(iframe); 3402 _tunnelFrame = window.frames[tunnelID]; 3403 }; 3404 3405 /** 3406 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 3407 * @private 3408 */ 3409 this.makeConnectReq = function () { 3410 _sendMessage(_TYPES.PASSWORD, _password); 3411 }; 3412 3413 /** 3414 * @private 3415 * Returns the host of the Finesse server. 3416 * @returns {String} 3417 * The host specified during the creation of the object. 3418 */ 3419 this.getHost = function () { 3420 return host; 3421 }; 3422 3423 /** 3424 * @private 3425 * The resource ID of the user who is logged into the notification server. 3426 * @returns {String} 3427 * The resource ID generated by the notification server. 3428 */ 3429 this.getResourceID = function () { 3430 return _resourceID; 3431 }; 3432 3433 /** 3434 * @private 3435 * Indicates whether the tunnel frame is loaded. 3436 * @returns {Boolean} 3437 * True if the tunnel frame is loaded, false otherwise. 3438 */ 3439 this.isTunnelLoaded = function () { 3440 return _isTunnelLoaded; 3441 }; 3442 3443 /** 3444 * @private 3445 * The location of the tunnel HTML URL. 3446 * @returns {String} 3447 * The location of the tunnel HTML URL. 3448 */ 3449 this.getTunnelURL = function () { 3450 return _tunnelURL; 3451 }; 3452 3453 /** 3454 * @private 3455 * Tunnels a subscribe request to the eventing iframe. 3456 * @param {String} node 3457 * The node to subscribe to 3458 * @param {Function} handler 3459 * Handler to invoke upon success or failure 3460 */ 3461 this.subscribe = function (node, handler) { 3462 if (handler && typeof handler !== "function") { 3463 throw new Error("Parameter is not a function."); 3464 } 3465 _handlers.subscribe[node] = handler; 3466 _sendMessage(_TYPES.SUBSCRIBE, node); 3467 }; 3468 3469 /** 3470 * @private 3471 * Tunnels an unsubscribe request to the eventing iframe. 3472 * @param {String} node 3473 * The node to unsubscribe from 3474 * @param {Function} handler 3475 * Handler to invoke upon success or failure 3476 */ 3477 this.unsubscribe = function (node, handler) { 3478 if (handler && typeof handler !== "function") { 3479 throw new Error("Parameter is not a function."); 3480 } 3481 _handlers.unsubscribe[node] = handler; 3482 _sendMessage(_TYPES.UNSUBSCRIBE, node); 3483 }; 3484 3485 /** 3486 * @private 3487 * Registers a handler to be invoked when an event is delivered. Only one 3488 * is registered at a time. If there has already been an event that was 3489 * delivered, the handler will be invoked immediately. 3490 * @param {Function} handler 3491 * Invoked when an event is delivered through the event connection. 3492 */ 3493 this.registerEventHandler = function (handler) { 3494 if (typeof handler !== "function") { 3495 throw new Error("Parameter is not a function."); 3496 } 3497 _eventHandler = handler; 3498 if (_eventCache) { 3499 handler(_eventCache); 3500 } 3501 }; 3502 3503 /** 3504 * @private 3505 * Unregisters the event handler completely. 3506 */ 3507 this.unregisterEventHandler = function () { 3508 _eventHandler = undefined; 3509 }; 3510 3511 /** 3512 * @private 3513 * Registers a handler to be invoked when a presence event is delivered. Only one 3514 * is registered at a time. 3515 * @param {Function} handler 3516 * Invoked when a presence event is delivered through the event connection. 3517 */ 3518 this.registerPresenceHandler = function (handler) { 3519 if (typeof handler !== "function") { 3520 throw new Error("Parameter is not a function."); 3521 } 3522 _presenceHandler = handler; 3523 }; 3524 3525 /** 3526 * @private 3527 * Unregisters the presence event handler completely. 3528 */ 3529 this.unregisterPresenceHandler = function () { 3530 _presenceHandler = undefined; 3531 }; 3532 3533 /** 3534 * @private 3535 * Registers a handler to be invoked when a connection status changes. The 3536 * object passed will contain a "status" property, and a "resourceID" 3537 * property, which will contain the most current resource ID assigned to 3538 * the client. If there has already been an event that was delivered, the 3539 * handler will be invoked immediately. 3540 * @param {Function} handler 3541 * Invoked when a connection status changes. 3542 */ 3543 this.registerConnectionInfoHandler = function (handler) { 3544 if (typeof handler !== "function") { 3545 throw new Error("Parameter is not a function."); 3546 } 3547 _connInfoHandler = handler; 3548 if (_statusCache) { 3549 handler(_createConnInfoObj()); 3550 } 3551 }; 3552 3553 /** 3554 * @private 3555 * Unregisters the connection information handler. 3556 */ 3557 this.unregisterConnectionInfoHandler = function () { 3558 _connInfoHandler = undefined; 3559 }; 3560 3561 /** 3562 * @private 3563 * Start listening for events and create a event tunnel for the shared BOSH 3564 * connection. 3565 * @param {String} id 3566 * The ID of the user for the notification server. 3567 * @param {String} password 3568 * The password of the user for the notification server. 3569 * @param {String} xmppDomain 3570 * The XMPP domain of the notification server 3571 * @param {String} pubsubDomain 3572 * The location (JID) of the XEP-0060 PubSub service 3573 * @param {String} resource 3574 * The resource to connect to the notification servier with. 3575 */ 3576 this.init = function (id, password, xmppDomain, pubsubDomain, resource) { 3577 3578 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string") { 3579 throw new Error("Invalid or missing required parameters."); 3580 } 3581 3582 _initTunnelConfig(); 3583 3584 _id = id; 3585 _password = password; 3586 _xmppDomain = xmppDomain; 3587 _pubsubDomain = pubsubDomain; 3588 _resource = resource; 3589 3590 //Attach a listener for messages sent from tunnel frame. 3591 _util.receiveMessage(_messageHandler, _tunnelOrigin); 3592 3593 //Create the tunnel iframe which will establish the shared connection. 3594 _createTunnel(); 3595 }; 3596 3597 //BEGIN TEST CODE// 3598 // /** 3599 // * Test code added to expose private functions that are used by unit test 3600 // * framework. This section of code is removed during the build process 3601 // * before packaging production code. The [begin|end]TestSection are used 3602 // * by the build to identify the section to strip. 3603 // * @ignore 3604 // */ 3605 // this.beginTestSection = 0; 3606 // 3607 // /** 3608 // * @ignore 3609 // */ 3610 // this.getTestObject = function () { 3611 // //Load mock dependencies. 3612 // var _mock = new MockControl(); 3613 // _util = _mock.createMock(finesse.utilities.Utilities); 3614 // 3615 // return { 3616 // //Expose mock dependencies 3617 // mock: _mock, 3618 // util: _util, 3619 // 3620 // //Expose internal private functions 3621 // types: _TYPES, 3622 // createConnInfoObj: _createConnInfoObj, 3623 // sendMessage: _sendMessage, 3624 // messageHandler: _messageHandler, 3625 // createTunnel: _createTunnel, 3626 // handlers: _handlers, 3627 // initTunnelConfig : _initTunnelConfig 3628 // }; 3629 // }; 3630 // 3631 // /** 3632 // * @ignore 3633 // */ 3634 // this.endTestSection = 0; 3635 // //END TEST CODE// 3636 }; 3637 3638 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 3639 finesse.clientservices = finesse.clientservices || {}; 3640 3641 window.finesse = window.finesse || {}; 3642 window.finesse.clientservices = window.finesse.clientservices || {}; 3643 window.finesse.clientservices.MasterTunnel = MasterTunnel; 3644 3645 return MasterTunnel; 3646 3647 }); 3648 3649 /** 3650 * Contains a list of topics used for client side pubsub. 3651 * 3652 */ 3653 3654 /** @private */ 3655 define('clientservices/Topics',[], function () { 3656 3657 var Topics = (function () { 3658 3659 /** 3660 * @private 3661 * The namespace prepended to all Finesse topics. 3662 */ 3663 this.namespace = "finesse.info"; 3664 3665 /** 3666 * @private 3667 * Gets the full topic name with the Finesse namespace prepended. 3668 * @param {String} topic 3669 * The topic category. 3670 * @returns {String} 3671 * The full topic name with prepended namespace. 3672 */ 3673 var _getNSTopic = function (topic) { 3674 return this.namespace + "." + topic; 3675 }; 3676 3677 /** @scope finesse.clientservices.Topics */ 3678 return { 3679 /** 3680 * @private 3681 * Client side request channel. 3682 */ 3683 REQUESTS: _getNSTopic("requests"), 3684 3685 /** 3686 * @private 3687 * Client side response channel. 3688 */ 3689 RESPONSES: _getNSTopic("responses"), 3690 3691 /** 3692 * @private 3693 * Connection status. 3694 */ 3695 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 3696 3697 /** 3698 * @private 3699 * Presence channel 3700 */ 3701 PRESENCE: _getNSTopic("presence"), 3702 3703 /** 3704 * @private 3705 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 3706 */ 3707 getTopic: function (restUri) { 3708 //The topic should not start with '/' else it will get replaced with 3709 //'.' which is invalid. 3710 //Thus, remove '/' if it is at the beginning of the string 3711 if (restUri.indexOf('/') === 0) { 3712 restUri = restUri.substr(1); 3713 } 3714 3715 //Replace every instance of "/" with ".". This is done to follow the 3716 //OpenAjaxHub topic name convention. 3717 return restUri.replace(/\//g, "."); 3718 } 3719 }; 3720 }()); 3721 window.finesse = window.finesse || {}; 3722 window.finesse.clientservices = window.finesse.clientservices || {}; 3723 /** @private */ 3724 window.finesse.clientservices.Topics = Topics; 3725 3726 return Topics; 3727 }); 3728 3729 /** The following comment is to prevent jslint errors about 3730 * using variables before they are defined. 3731 */ 3732 /*global finesse*/ 3733 3734 /** 3735 * Registers with the MasterTunnel to receive events, which it 3736 * could publish to the OpenAjax gadget pubsub infrastructure. 3737 * 3738 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 3739 */ 3740 3741 /** @private */ 3742 define('clientservices/MasterPublisher',[ 3743 "clientservices/MasterTunnel", 3744 "clientservices/Topics", 3745 "utilities/Utilities" 3746 ], 3747 function (MasterTunnel, Topics, Utilities) { 3748 3749 var MasterPublisher = function (tunnel, hub) { 3750 if (!(tunnel instanceof MasterTunnel)) { 3751 throw new Error("Required tunnel object missing or invalid."); 3752 } 3753 3754 var 3755 3756 ClientServices = finesse.clientservices.ClientServices, 3757 3758 /** 3759 * Reference to the gadget pubsub Hub instance. 3760 * @private 3761 */ 3762 _hub = hub, 3763 3764 /** 3765 * Reference to the Topics class. 3766 * @private 3767 */ 3768 _topics = Topics, 3769 3770 /** 3771 * Reference to conversion utilities class. 3772 * @private 3773 */ 3774 _utils = Utilities, 3775 3776 /** 3777 * References to ClientServices logger methods 3778 * @private 3779 */ 3780 _logger = { 3781 log: ClientServices.log 3782 }, 3783 3784 /** 3785 * Store the passed in tunnel. 3786 * @private 3787 */ 3788 _tunnel = tunnel, 3789 3790 /** 3791 * Caches the connection info event so that it could be published if there 3792 * is a request for it. 3793 * @private 3794 */ 3795 _connInfoCache = {}, 3796 3797 /** 3798 * The types of possible request types supported when listening to the 3799 * requests channel. Each request type could result in different operations. 3800 * @private 3801 */ 3802 _REQTYPES = { 3803 CONNECTIONINFO: "ConnectionInfoReq", 3804 SUBSCRIBE: "SubscribeNodeReq", 3805 UNSUBSCRIBE: "UnsubscribeNodeReq", 3806 CONNECT: "ConnectionReq" 3807 }, 3808 3809 /** 3810 * Will store list of nodes that have OF subscriptions created 3811 * _nodesList[node][subscribing].reqIds[subid] 3812 * _nodesList[node][active].reqIds[subid] 3813 * _nodesList[node][unsubscribing].reqIds[subid] 3814 * _nodesList[node][holding].reqIds[subid] 3815 * @private 3816 */ 3817 _nodesList = {}, 3818 3819 /** 3820 * The states that a subscription can be in 3821 * @private 3822 */ 3823 _CHANNELSTATES = { 3824 UNINITIALIZED: "Uninitialized", 3825 PENDING: "Pending", 3826 OPERATIONAL: "Operational" 3827 }, 3828 3829 /** 3830 * Checks if the payload is JSON 3831 * @returns {Boolean} 3832 * @private 3833 */ 3834 _isJsonPayload = function(event) { 3835 var delimStart, delimEnd, retval = false; 3836 3837 try { 3838 delimStart = event.indexOf('{'); 3839 delimEnd = event.lastIndexOf('}'); 3840 3841 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 3842 retval = true; //event contains JSON payload 3843 } 3844 } catch (err) { 3845 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 3846 } 3847 return retval; 3848 }, 3849 3850 /** 3851 * Parses a JSON event and then publishes. 3852 * 3853 * @param {String} event 3854 * The full event payload. 3855 * @throws {Error} If the payload object is malformed. 3856 * @private 3857 */ 3858 _parseAndPublishJSONEvent = function(event) { 3859 var topic, eventObj, publishEvent, 3860 delimPos = event.indexOf("{"), 3861 node, parser, 3862 eventJson = event, 3863 returnObj = {node: null, data: null}; 3864 3865 try { 3866 //Extract and strip the node path from the message 3867 if (delimPos > 0) 3868 { 3869 //We need to decode the URI encoded node path 3870 //TODO: make sure this is kosher with OpenAjax topic naming 3871 node = decodeURI(event.substr(0, delimPos)); 3872 eventJson = event.substr(delimPos); 3873 3874 //Converting the node path to openAjaxhub topic 3875 topic = _topics.getTopic(node); 3876 3877 returnObj.node = node; 3878 returnObj.payload = eventJson; 3879 } 3880 else 3881 { 3882 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 3883 throw new Error("node is not given in postMessage: " + eventJson); 3884 } 3885 3886 parser = _utils.getJSONParser(); 3887 3888 eventObj = parser.parse(eventJson); 3889 returnObj.data = eventObj; 3890 3891 } catch (err) { 3892 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 3893 throw new Error("Malformed event payload : " + err); 3894 } 3895 3896 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 3897 3898 publishEvent = {content : event, object : eventObj }; 3899 3900 //Publish event to proper event topic. 3901 if (topic && eventObj) { 3902 _hub.publish(topic, publishEvent); 3903 } 3904 }, 3905 3906 /** 3907 * Parses an XML event and then publishes. 3908 * 3909 * @param {String} event 3910 * The full event payload. 3911 * @throws {Error} If the payload object is malformed. 3912 * @private 3913 */ 3914 _parseAndPublishXMLEvent = function(event) { 3915 var topic, eventObj, publishEvent, restTopic, 3916 delimPos = event.indexOf("<"), 3917 node, 3918 eventXml = event; 3919 3920 try { 3921 //Extract and strip the node path from the message 3922 if (delimPos > 0) { 3923 //We need to decode the URI encoded node path 3924 //TODO: make sure this is kosher with OpenAjax topic naming 3925 node = decodeURI(event.substr(0, delimPos)); 3926 eventXml = event.substr(delimPos); 3927 //Converting the node path to openAjaxhub topic 3928 topic = _topics.getTopic(node); 3929 } else { 3930 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 3931 throw new Error("node is not given in postMessage: " + eventXml); 3932 } 3933 3934 eventObj = _utils.xml2JsObj(eventXml); 3935 3936 } catch (err) { 3937 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 3938 throw new Error("Malformed event payload : " + err); 3939 } 3940 3941 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 3942 3943 publishEvent = {content : event, object : eventObj }; 3944 3945 //Publish event to proper event topic. 3946 if (topic && eventObj) { 3947 _hub.publish(topic, publishEvent); 3948 } 3949 }, 3950 3951 /** 3952 * Publishes events to the appropriate topic. The topic name is determined 3953 * by fetching the source value from the event. 3954 * @param {String} event 3955 * The full event payload. 3956 * @throws {Error} If the payload object is malformed. 3957 * @private 3958 */ 3959 _eventHandler = function (event) { 3960 3961 //Handle JSON or XML events 3962 if (!_isJsonPayload(event)) 3963 { 3964 //XML 3965 _parseAndPublishXMLEvent(event); 3966 } 3967 else 3968 { 3969 //JSON 3970 _parseAndPublishJSONEvent(event); 3971 } 3972 }, 3973 3974 3975 /** 3976 * Handler for when presence events are sent through the MasterTunnel. 3977 * @returns {Object} 3978 * A presence xml event. 3979 * @private 3980 */ 3981 _presenceHandler = function (event) { 3982 var eventObj = _utils.xml2JsObj(event), publishEvent; 3983 3984 publishEvent = {content : event, object : eventObj}; 3985 3986 if (eventObj) { 3987 _hub.publish(_topics.PRESENCE, publishEvent); 3988 } 3989 }, 3990 3991 /** 3992 * Clone the connection info object from cache. 3993 * @returns {Object} 3994 * A connection info object containing a "status" and "resourceID". 3995 * @private 3996 */ 3997 _cloneConnInfoObj = function () { 3998 if (_connInfoCache) { 3999 return { 4000 status: _connInfoCache.status, 4001 resourceID: _connInfoCache.resourceID 4002 }; 4003 } else { 4004 return null; 4005 } 4006 }, 4007 4008 /** 4009 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4010 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4011 * @private 4012 */ 4013 _cleanupPendingRequests = function () { 4014 var node, curSubid, errObj = { 4015 error: { 4016 errorType: "Disconnected", 4017 errorMessage: "Outstanding request will never complete." 4018 } 4019 }; 4020 4021 // Iterate through all outstanding subscribe requests to notify them that it will never return 4022 for (node in _nodesList) { 4023 if (_nodesList.hasOwnProperty(node)) { 4024 for (curSubid in _nodesList[node].subscribing.reqIds) { 4025 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4026 // Notify this outstanding subscribe request to give up and error out 4027 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4028 } 4029 } 4030 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4031 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4032 // Notify this outstanding unsubscribe request to give up and error out 4033 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4034 } 4035 } 4036 } 4037 } 4038 }, 4039 4040 /** 4041 * Publishes the connection info to the connection info topic. 4042 * @param {Object} connInfo 4043 * The connection info object containing the status and resource ID. 4044 * @private 4045 */ 4046 _connInfoHandler = function (connInfo) { 4047 var before = _connInfoCache.status; 4048 _connInfoCache = connInfo; 4049 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4050 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4051 if (before === "connected" && connInfo.status !== "connected") { 4052 // Fail all pending requests when we transition to disconnected 4053 _cleanupPendingRequests(); 4054 } 4055 }, 4056 4057 4058 /** 4059 * Utility method to bookkeep node subscription requests and determine 4060 * whehter it is necessary to tunnel the request to JabberWerx. 4061 * @param {String} node 4062 * The node of interest 4063 * @param {String} reqId 4064 * A unique string identifying the request/subscription 4065 * @private 4066 */ 4067 _subscribeNode = function (node, subid) { 4068 if (_connInfoCache.status !== "connected") { 4069 _hub.publish(_topics.RESPONSES + "." + subid, { 4070 error: { 4071 errorType: "Not connected", 4072 errorMessage: "Cannot subscribe without connection." 4073 } 4074 }); 4075 return; 4076 } 4077 // NODE DOES NOT YET EXIST 4078 if (!_nodesList[node]) { 4079 _nodesList[node] = { 4080 "subscribing": { 4081 "reqIds": {}, 4082 "length": 0 4083 }, 4084 "active": { 4085 "reqIds": {}, 4086 "length": 0 4087 }, 4088 "unsubscribing": { 4089 "reqIds": {}, 4090 "length": 0 4091 }, 4092 "holding": { 4093 "reqIds": {}, 4094 "length": 0 4095 } 4096 }; 4097 } 4098 if (_nodesList[node].active.length === 0) { 4099 if (_nodesList[node].unsubscribing.length === 0) { 4100 if (_nodesList[node].subscribing.length === 0) { 4101 _nodesList[node].subscribing.reqIds[subid] = true; 4102 _nodesList[node].subscribing.length += 1; 4103 4104 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4105 _tunnel.subscribe(node, function (err) { 4106 var errObj, curSubid; 4107 if (err) { 4108 errObj = { 4109 subscribe: { 4110 content: err 4111 } 4112 }; 4113 4114 try { 4115 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4116 } catch (e) { 4117 errObj.error = { 4118 errorType: "parseError", 4119 errorMessage: "Could not serialize XML: " + e 4120 }; 4121 } 4122 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4123 } else { 4124 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4125 } 4126 4127 for (curSubid in _nodesList[node].subscribing.reqIds) { 4128 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4129 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4130 if (!err) { 4131 _nodesList[node].active.reqIds[curSubid] = true; 4132 _nodesList[node].active.length += 1; 4133 } 4134 delete _nodesList[node].subscribing.reqIds[curSubid]; 4135 _nodesList[node].subscribing.length -= 1; 4136 } 4137 } 4138 }); 4139 4140 } else { //other ids are subscribing 4141 _nodesList[node].subscribing.reqIds[subid] = true; 4142 _nodesList[node].subscribing.length += 1; 4143 } 4144 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 4145 _nodesList[node].holding.reqIds[subid] = true; 4146 _nodesList[node].holding.length += 1; 4147 } 4148 } else { // The node has active subscriptions; add this subid and return successful response 4149 _nodesList[node].active.reqIds[subid] = true; 4150 _nodesList[node].active.length += 1; 4151 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4152 } 4153 }, 4154 4155 /** 4156 * Utility method to bookkeep node unsubscribe requests and determine 4157 * whehter it is necessary to tunnel the request to JabberWerx. 4158 * @param {String} node 4159 * The node to unsubscribe from 4160 * @param {String} reqId 4161 * A unique string identifying the subscription to remove 4162 * @private 4163 */ 4164 _unsubscribeNode = function (node, subid) { 4165 if (!_nodesList[node]) { //node DNE, publish success response 4166 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4167 } else { 4168 if (_connInfoCache.status !== "connected") { 4169 _hub.publish(_topics.RESPONSES + "." + subid, { 4170 error: { 4171 errorType: "Not connected", 4172 errorMessage: "Cannot unsubscribe without connection." 4173 } 4174 }); 4175 return; 4176 } 4177 if (_nodesList[node].active.length > 1) { 4178 delete _nodesList[node].active.reqIds[subid]; 4179 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4180 _nodesList[node].active.length -= 1; 4181 } else if (_nodesList[node].active.length === 1) { // transition subid from active category to unsubscribing category 4182 _nodesList[node].unsubscribing.reqIds[subid] = true; 4183 _nodesList[node].unsubscribing.length += 1; 4184 delete _nodesList[node].active.reqIds[subid]; 4185 _nodesList[node].active.length -= 1; 4186 4187 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 4188 _tunnel.unsubscribe(node, function (err) { 4189 var errObj, curSubid; 4190 if (err) { 4191 errObj = { 4192 subscribe: { 4193 content: err 4194 } 4195 }; 4196 4197 try { 4198 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4199 } catch (e) { 4200 errObj.error = { 4201 errorType: "parseError", 4202 errorMessage: "Could not serialize XML: " + e 4203 }; 4204 } 4205 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 4206 } else { 4207 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 4208 } 4209 4210 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4211 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4212 // publish to all subids whether unsubscribe failed or succeeded 4213 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4214 if (!err) { 4215 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4216 _nodesList[node].unsubscribing.length -= 1; 4217 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 4218 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4219 _nodesList[node].unsubscribing.length -= 1; 4220 } 4221 } 4222 } 4223 4224 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 4225 for (curSubid in _nodesList[node].holding.reqIds) { 4226 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 4227 delete _nodesList[node].holding.reqIds[curSubid]; 4228 _nodesList[node].holding.length -= 1; 4229 _subscribeNode(node, curSubid); 4230 } 4231 } 4232 } 4233 }); 4234 } else { // length <= 0? 4235 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4236 } 4237 } 4238 }, 4239 4240 /** 4241 * Handles client requests to establish a BOSH connection. 4242 * @param {String} id 4243 * id of the xmpp user 4244 * @param {String} password 4245 * password of the xmpp user 4246 * @param {String} xmppDomain 4247 * xmppDomain of the xmpp user account 4248 * @private 4249 */ 4250 _connect = function (id, password, xmppDomain) { 4251 _tunnel.makeConnectReq(id, password, xmppDomain); 4252 }, 4253 4254 /** 4255 * Handles client requests made to the request topic. The type of the 4256 * request is described in the "type" property within the data payload. Each 4257 * type can result in a different operation. 4258 * @param {String} topic 4259 * The topic which data was published to. 4260 * @param {Object} data 4261 * The data containing requests information published by clients. 4262 * @param {String} data.type 4263 * The type of the request. Supported: "ConnectionInfoReq" 4264 * @param {Object} data.data 4265 * May contain data relevant for the particular requests. 4266 * @param {String} [data.invokeID] 4267 * The ID used to identify the request with the response. The invoke ID 4268 * will be included in the data in the publish to the topic. It is the 4269 * responsibility of the client to correlate the published data to the 4270 * request made by using the invoke ID. 4271 * @private 4272 */ 4273 _clientRequestHandler = function (topic, data) { 4274 var dataCopy; 4275 4276 //Ensure a valid data object with "type" and "data" properties. 4277 if (typeof data === "object" && 4278 typeof data.type === "string" && 4279 typeof data.data === "object") { 4280 switch (data.type) { 4281 case _REQTYPES.CONNECTIONINFO: 4282 //It is possible that Slave clients come up before the Master 4283 //client. If that is the case, the Slaves will need to make a 4284 //request for the Master to send the latest connection info to the 4285 //connectionInfo topic. 4286 dataCopy = _cloneConnInfoObj(); 4287 if (dataCopy) { 4288 if (data.invokeID !== undefined) { 4289 dataCopy.invokeID = data.invokeID; 4290 } 4291 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 4292 } 4293 break; 4294 case _REQTYPES.SUBSCRIBE: 4295 if (typeof data.data.node === "string") { 4296 _subscribeNode(data.data.node, data.invokeID); 4297 } 4298 break; 4299 case _REQTYPES.UNSUBSCRIBE: 4300 if (typeof data.data.node === "string") { 4301 _unsubscribeNode(data.data.node, data.invokeID); 4302 } 4303 break; 4304 case _REQTYPES.CONNECT: 4305 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 4306 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 4307 break; 4308 default: 4309 break; 4310 } 4311 } 4312 }; 4313 4314 (function () { 4315 //Register to receive events and connection status from tunnel. 4316 _tunnel.registerEventHandler(_eventHandler); 4317 _tunnel.registerPresenceHandler(_presenceHandler); 4318 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 4319 4320 //Listen to a request channel to respond to any requests made by other 4321 //clients because the Master may have access to useful information. 4322 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 4323 }()); 4324 4325 /** 4326 * @private 4327 * Handles client requests to establish a BOSH connection. 4328 * @param {String} id 4329 * id of the xmpp user 4330 * @param {String} password 4331 * password of the xmpp user 4332 * @param {String} xmppDomain 4333 * xmppDomain of the xmpp user account 4334 */ 4335 this.connect = function (id, password, xmppDomain) { 4336 _connect(id, password, xmppDomain); 4337 }; 4338 4339 /** 4340 * @private 4341 * Resets the list of explicit subscriptions 4342 */ 4343 this.wipeout = function () { 4344 _cleanupPendingRequests(); 4345 _nodesList = {}; 4346 }; 4347 4348 //BEGIN TEST CODE// 4349 /** 4350 * Test code added to expose private functions that are used by unit test 4351 * framework. This section of code is removed during the build process 4352 * before packaging production code. The [begin|end]TestSection are used 4353 * by the build to identify the section to strip. 4354 * @ignore 4355 */ 4356 this.beginTestSection = 0; 4357 4358 /** 4359 * @ignore 4360 */ 4361 this.getTestObject = function () { 4362 //Load mock dependencies. 4363 var _mock = new MockControl(); 4364 _hub = _mock.createMock(gadgets.Hub); 4365 _tunnel = _mock.createMock(); 4366 4367 return { 4368 //Expose mock dependencies 4369 mock: _mock, 4370 hub: _hub, 4371 tunnel: _tunnel, 4372 setTunnel: function (tunnel) { 4373 _tunnel = tunnel; 4374 }, 4375 getTunnel: function () { 4376 return _tunnel; 4377 }, 4378 4379 //Expose internal private functions 4380 reqtypes: _REQTYPES, 4381 eventHandler: _eventHandler, 4382 presenceHandler: _presenceHandler, 4383 4384 subscribeNode: _subscribeNode, 4385 unsubscribeNode: _unsubscribeNode, 4386 4387 getNodeList: function () { 4388 return _nodesList; 4389 }, 4390 setNodeList: function (nodelist) { 4391 _nodesList = nodelist; 4392 }, 4393 4394 cloneConnInfoObj: _cloneConnInfoObj, 4395 connInfoHandler: _connInfoHandler, 4396 clientRequestHandler: _clientRequestHandler 4397 4398 }; 4399 }; 4400 4401 4402 /** 4403 * @ignore 4404 */ 4405 this.endTestSection = 0; 4406 //END TEST CODE// 4407 4408 }; 4409 4410 window.finesse = window.finesse || {}; 4411 window.finesse.clientservices = window.finesse.clientservices || {}; 4412 window.finesse.clientservices.MasterPublisher = MasterPublisher; 4413 4414 return MasterPublisher; 4415 }); 4416 4417 /** The following comment is to prevent jslint errors about 4418 * using variables before they are defined. 4419 */ 4420 /*global publisher:true */ 4421 4422 /** 4423 * Exposes a set of API wrappers that will hide the dirty work of 4424 * constructing Finesse API requests and consuming Finesse events. 4425 * 4426 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 4427 */ 4428 4429 4430 /** 4431 * Allow clients to make Finesse API requests and consume Finesse events by 4432 * calling a set of exposed functions. The Services layer will do the dirty 4433 * work of establishing a shared BOSH connection (for designated Master 4434 * modules), consuming events for client subscriptions, and constructing API 4435 * requests. 4436 */ 4437 /** @private */ 4438 define('clientservices/ClientServices',[ 4439 "clientservices/MasterTunnel", 4440 "clientservices/MasterPublisher", 4441 "clientservices/Topics", 4442 "utilities/Utilities" 4443 ], 4444 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 4445 4446 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 4447 var 4448 4449 /** 4450 * Shortcut reference to the master tunnel 4451 * @private 4452 */ 4453 _tunnel, 4454 4455 _publisher, 4456 4457 /** 4458 * Shortcut reference to the finesse.utilities.Utilities singleton 4459 * This will be set by init() 4460 * @private 4461 */ 4462 _util, 4463 4464 /** 4465 * Shortcut reference to the gadgets.io object. 4466 * This will be set by init() 4467 * @private 4468 */ 4469 _io, 4470 4471 /** 4472 * Shortcut reference to the gadget pubsub Hub instance. 4473 * This will be set by init() 4474 * @private 4475 */ 4476 _hub, 4477 4478 /** 4479 * Logger object set externally by setLogger, defaults to nothing. 4480 * @private 4481 */ 4482 _logger = {}, 4483 4484 /** 4485 * Shortcut reference to the Topics class. 4486 * This will be set by init() 4487 * @private 4488 */ 4489 _topics, 4490 4491 /** 4492 * Config object needed to initialize this library 4493 * This must be set by init() 4494 * @private 4495 */ 4496 _config, 4497 4498 /** 4499 * @private 4500 * Whether or not this ClientService instance is a Master. 4501 */ 4502 _isMaster = false, 4503 4504 /** 4505 * @private 4506 * Whether the Client Services have been initiated yet. 4507 */ 4508 _inited = false, 4509 4510 /** 4511 * Stores the list of subscription IDs for all subscriptions so that it 4512 * could be retrieve for unsubscriptions. 4513 * @private 4514 */ 4515 _subscriptionID = {}, 4516 4517 /** 4518 * The possible states of the JabberWerx BOSH connection. 4519 * @private 4520 */ 4521 _STATUS = { 4522 CONNECTING: "connecting", 4523 CONNECTED: "connected", 4524 DISCONNECTED: "disconnected", 4525 DISCONNECTED_CONFLICT: "conflict", 4526 DISCONNECTED_UNAUTHORIZED: "unauthorized", 4527 DISCONNECTING: "disconnecting", 4528 RECONNECTING: "reconnecting", 4529 UNLOADING: "unloading", 4530 FAILING: "failing", 4531 RECOVERED: "recovered" 4532 }, 4533 4534 _failoverMode = false, 4535 4536 /** 4537 * Handler function to be invoked when BOSH connection is connecting. 4538 * @private 4539 */ 4540 _onConnectingHandler, 4541 4542 /** 4543 * Handler function to be invoked when BOSH connection is connected 4544 * @private 4545 */ 4546 _onConnectHandler, 4547 4548 /** 4549 * Handler function to be invoked when BOSH connection is disconnecting. 4550 * @private 4551 */ 4552 _onDisconnectingHandler, 4553 4554 /** 4555 * Handler function to be invoked when the BOSH is disconnected. 4556 * @private 4557 */ 4558 _onDisconnectHandler, 4559 4560 /** 4561 * Handler function to be invoked when the BOSH is reconnecting. 4562 * @private 4563 */ 4564 _onReconnectingHandler, 4565 4566 /** 4567 * Handler function to be invoked when the BOSH is unloading. 4568 * @private 4569 */ 4570 _onUnloadingHandler, 4571 4572 /** 4573 * Contains a cache of the latest connection info containing the current 4574 * state of the BOSH connection and the resource ID. 4575 * @private 4576 */ 4577 _connInfo, 4578 4579 /** 4580 * Keeps track of all the objects that need to be refreshed when we recover 4581 * due to our resilient connection. Only objects that we subscribe to will 4582 * be added to this list. 4583 * @private 4584 */ 4585 _refreshList = [], 4586 4587 /** 4588 * @private 4589 * Centralized logger.log method for external logger 4590 * @param {String} msg 4591 * Message to log 4592 */ 4593 _log = function (msg) { 4594 // If the external logger throws up, it stops here. 4595 try { 4596 if (_logger.log) { 4597 _logger.log("[ClientServices] " + msg); 4598 } 4599 } catch (e) { } 4600 }, 4601 4602 /** 4603 * Go through each object in the _refreshList and call its refresh() function 4604 * @private 4605 */ 4606 _refreshObjects = function () { 4607 var i; 4608 4609 // wipe out the explicit subscription list before we refresh objects 4610 if (_publisher) { 4611 _publisher.wipeout(); 4612 } 4613 4614 // refresh each item in the refresh list 4615 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4616 _log("Refreshing " + _refreshList[i].getRestUrl()); 4617 _refreshList[i].refresh(10); 4618 } 4619 }, 4620 4621 /** 4622 * Handler to process connection info publishes. 4623 * @param {Object} data 4624 * The connection info data object. 4625 * @param {String} data.status 4626 * The BOSH connection status. 4627 * @param {String} data.resourceID 4628 * The resource ID for the connection. 4629 * @private 4630 */ 4631 _connInfoHandler = function (data) { 4632 4633 //Invoke registered handler depending on status received. Due to the 4634 //request topic where clients can make request for the Master to publish 4635 //the connection info, there is a chance that duplicate connection info 4636 //events may be sent, so ensure that there has been a state change 4637 //before invoking the handlers. 4638 if (_connInfo === undefined || _connInfo.status !== data.status) { 4639 _connInfo = data; 4640 switch (data.status) { 4641 case _STATUS.CONNECTING: 4642 if (_isMaster && _onConnectingHandler) { 4643 _onConnectingHandler(); 4644 } 4645 break; 4646 case _STATUS.CONNECTED: 4647 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 4648 _onConnectHandler(); 4649 } 4650 break; 4651 case _STATUS.DISCONNECTED: 4652 if (_isMaster && _onDisconnectHandler) { 4653 _onDisconnectHandler(); 4654 } 4655 break; 4656 case _STATUS.DISCONNECTED_CONFLICT: 4657 if (_isMaster && _onDisconnectHandler) { 4658 _onDisconnectHandler("conflict"); 4659 } 4660 break; 4661 case _STATUS.DISCONNECTED_UNAUTHORIZED: 4662 if (_isMaster && _onDisconnectHandler) { 4663 _onDisconnectHandler("unauthorized"); 4664 } 4665 break; 4666 case _STATUS.DISCONNECTING: 4667 if (_isMaster && _onDisconnectingHandler) { 4668 _onDisconnectingHandler(); 4669 } 4670 break; 4671 case _STATUS.RECONNECTING: 4672 if (_isMaster && _onReconnectingHandler) { 4673 _onReconnectingHandler(); 4674 } 4675 break; 4676 case _STATUS.UNLOADING: 4677 if (_isMaster && _onUnloadingHandler) { 4678 _onUnloadingHandler(); 4679 } 4680 break; 4681 case _STATUS.FAILING: 4682 if (!_isMaster) { 4683 // Stop 4684 _failoverMode = true; 4685 if (_onDisconnectHandler) { 4686 _onDisconnectHandler(); 4687 } 4688 } 4689 break; 4690 case _STATUS.RECOVERED: 4691 if (!_isMaster) { 4692 _failoverMode = false; 4693 if (_onConnectHandler) { 4694 _onConnectHandler(); 4695 } 4696 } 4697 // Whenever we are recovered, we need to refresh any objects 4698 // that are stored. 4699 _refreshObjects(); 4700 break; 4701 } 4702 } 4703 }, 4704 4705 /** 4706 * Ensure that ClientServices have been inited. 4707 * @private 4708 */ 4709 _isInited = function () { 4710 if (!_inited) { 4711 throw new Error("ClientServices needs to be inited."); 4712 } 4713 }, 4714 4715 /** 4716 * Have the client become the Master by initiating a tunnel to a shared 4717 * event BOSH connection. The Master is responsible for publishing all 4718 * events to the pubsub infrastructure. 4719 * @private 4720 */ 4721 _becomeMaster = function () { 4722 _tunnel = new MasterTunnel(_config.host, _config.scheme); 4723 _publisher = new MasterPublisher(_tunnel, _hub); 4724 _tunnel.init(_config.id, _config.password, _config.xmppDomain, _config.pubsubDomain, _config.resource); 4725 _isMaster = true; 4726 }, 4727 4728 /** 4729 * Make a request to the request channel to have the Master publish the 4730 * connection info object. 4731 * @private 4732 */ 4733 _makeConnectionInfoReq = function () { 4734 var data = { 4735 type: "ConnectionInfoReq", 4736 data: {}, 4737 invokeID: (new Date()).getTime() 4738 }; 4739 _hub.publish(_topics.REQUESTS, data); 4740 }, 4741 4742 /** 4743 * Utility method to register a handler which is associated with a 4744 * particular connection status. 4745 * @param {String} status 4746 * The connection status string. 4747 * @param {Function} handler 4748 * The handler to associate with a particular connection status. 4749 * @throws {Error} 4750 * If the handler provided is not a function. 4751 * @private 4752 */ 4753 _registerHandler = function (status, handler) { 4754 if (typeof handler === "function") { 4755 if (_connInfo && _connInfo.status === status) { 4756 handler(); 4757 } 4758 switch (status) { 4759 case _STATUS.CONNECTING: 4760 _onConnectingHandler = handler; 4761 break; 4762 case _STATUS.CONNECTED: 4763 _onConnectHandler = handler; 4764 break; 4765 case _STATUS.DISCONNECTED: 4766 _onDisconnectHandler = handler; 4767 break; 4768 case _STATUS.DISCONNECTING: 4769 _onDisconnectingHandler = handler; 4770 break; 4771 case _STATUS.RECONNECTING: 4772 _onReconnectingHandler = handler; 4773 break; 4774 case _STATUS.UNLOADING: 4775 _onUnloadingHandler = handler; 4776 break; 4777 } 4778 4779 } else { 4780 throw new Error("Callback is not a function"); 4781 } 4782 }; 4783 4784 return { 4785 4786 /** 4787 * @private 4788 * Adds an item to the list to be refreshed upon reconnect 4789 * @param {RestBase} object - rest object to be refreshed 4790 */ 4791 addToRefreshList: function (object) { 4792 _refreshList.push(object); 4793 }, 4794 4795 /** 4796 * @private 4797 * Removes the given item from the refresh list 4798 * @param {RestBase} object - rest object to be removed 4799 */ 4800 removeFromRefreshList: function (object) { 4801 var i; 4802 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4803 if (_refreshList[i] === object) { 4804 _refreshList.splice(i, 1); 4805 break; 4806 } 4807 } 4808 }, 4809 4810 /** 4811 * @private 4812 * The location of the tunnel HTML URL. 4813 * @returns {String} 4814 * The location of the tunnel HTML URL. 4815 */ 4816 getTunnelURL: function () { 4817 return _tunnel.getTunnelURL(); 4818 }, 4819 4820 /** 4821 * @private 4822 * Indicates whether the tunnel frame is loaded. 4823 * @returns {Boolean} 4824 * True if the tunnel frame is loaded, false otherwise. 4825 */ 4826 isTunnelLoaded: function () { 4827 return _tunnel.isTunnelLoaded(); 4828 }, 4829 4830 /** 4831 * @private 4832 * Indicates whether the ClientServices instance is a Master. 4833 * @returns {Boolean} 4834 * True if this instance of ClientServices is a Master, false otherwise. 4835 */ 4836 isMaster: function () { 4837 return _isMaster; 4838 }, 4839 4840 /** 4841 * @private 4842 * Get the resource ID. An ID is only available if the BOSH connection has 4843 * been able to connect successfully. 4844 * @returns {String} 4845 * The resource ID string. Null if the BOSH connection was never 4846 * successfully created and/or the resource ID has not been associated. 4847 */ 4848 getResourceID: function () { 4849 if (_connInfo !== undefined) { 4850 return _connInfo.resourceID; 4851 } 4852 return null; 4853 }, 4854 4855 /* 4856 getHub: function () { 4857 return _hub; 4858 }, 4859 */ 4860 /** 4861 * @private 4862 * Add a callback to be invoked when the BOSH connection is attempting 4863 * to connect. If the connection is already trying to connect, the 4864 * callback will be invoked immediately. 4865 * @param {Function} handler 4866 * An empty param function to be invoked on connecting. Only one 4867 * handler can be registered at a time. Handlers already registered 4868 * will be overwritten. 4869 */ 4870 registerOnConnectingHandler: function (handler) { 4871 _registerHandler(_STATUS.CONNECTING, handler); 4872 }, 4873 4874 /** 4875 * @private 4876 * Removes the on connecting callback that was registered. 4877 */ 4878 unregisterOnConnectingHandler: function () { 4879 _onConnectingHandler = undefined; 4880 }, 4881 4882 /** 4883 * Add a callback to be invoked when all of the following conditions are met: 4884 * <ul> 4885 * <li>When Finesse goes IN_SERVICE</li> 4886 * <li>The BOSH connection is established</li> 4887 * <li>The Finesse user presence becomes available</li> 4888 * </ul> 4889 * If all these conditions are met at the time this function is called, then 4890 * the handler will be invoked immediately. 4891 * @param {Function} handler 4892 * An empty param function to be invoked on connect. Only one handler 4893 * can be registered at a time. Handlers already registered will be 4894 * overwritten. 4895 * @example 4896 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 4897 */ 4898 registerOnConnectHandler: function (handler) { 4899 _registerHandler(_STATUS.CONNECTED, handler); 4900 }, 4901 4902 /** 4903 * @private 4904 * Removes the on connect callback that was registered. 4905 */ 4906 unregisterOnConnectHandler: function () { 4907 _onConnectHandler = undefined; 4908 }, 4909 4910 /** 4911 * Add a callback to be invoked when any of the following occurs: 4912 * <ul> 4913 * <li>Finesse is no longer IN_SERVICE</li> 4914 * <li>The BOSH connection is lost</li> 4915 * <li>The presence of the Finesse user is no longer available</li> 4916 * </ul> 4917 * If any of these conditions are met at the time this function is 4918 * called, the callback will be invoked immediately. 4919 * @param {Function} handler 4920 * An empty param function to be invoked on disconnected. Only one 4921 * handler can be registered at a time. Handlers already registered 4922 * will be overwritten. 4923 * @example 4924 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 4925 */ 4926 registerOnDisconnectHandler: function (handler) { 4927 _registerHandler(_STATUS.DISCONNECTED, handler); 4928 }, 4929 4930 /** 4931 * @private 4932 * Removes the on disconnect callback that was registered. 4933 */ 4934 unregisterOnDisconnectHandler: function () { 4935 _onDisconnectHandler = undefined; 4936 }, 4937 4938 /** 4939 * @private 4940 * Add a callback to be invoked when the BOSH is currently disconnecting. If 4941 * the connection is already disconnecting, invoke the callback immediately. 4942 * @param {Function} handler 4943 * An empty param function to be invoked on disconnected. Only one 4944 * handler can be registered at a time. Handlers already registered 4945 * will be overwritten. 4946 */ 4947 registerOnDisconnectingHandler: function (handler) { 4948 _registerHandler(_STATUS.DISCONNECTING, handler); 4949 }, 4950 4951 /** 4952 * @private 4953 * Removes the on disconnecting callback that was registered. 4954 */ 4955 unregisterOnDisconnectingHandler: function () { 4956 _onDisconnectingHandler = undefined; 4957 }, 4958 4959 /** 4960 * @private 4961 * Add a callback to be invoked when the BOSH connection is attempting 4962 * to connect. If the connection is already trying to connect, the 4963 * callback will be invoked immediately. 4964 * @param {Function} handler 4965 * An empty param function to be invoked on connecting. Only one 4966 * handler can be registered at a time. Handlers already registered 4967 * will be overwritten. 4968 */ 4969 registerOnReconnectingHandler: function (handler) { 4970 _registerHandler(_STATUS.RECONNECTING, handler); 4971 }, 4972 4973 /** 4974 * @private 4975 * Removes the on reconnecting callback that was registered. 4976 */ 4977 unregisterOnReconnectingHandler: function () { 4978 _onReconnectingHandler = undefined; 4979 }, 4980 4981 /** 4982 * @private 4983 * Add a callback to be invoked when the BOSH connection is unloading 4984 * 4985 * @param {Function} handler 4986 * An empty param function to be invoked on connecting. Only one 4987 * handler can be registered at a time. Handlers already registered 4988 * will be overwritten. 4989 */ 4990 registerOnUnloadingHandler: function (handler) { 4991 _registerHandler(_STATUS.UNLOADING, handler); 4992 }, 4993 4994 /** 4995 * @private 4996 * Removes the on unloading callback that was registered. 4997 */ 4998 unregisterOnUnloadingHandler: function () { 4999 _onUnloadingHandler = undefined; 5000 }, 5001 5002 /** 5003 * @private 5004 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5005 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5006 * configuration is encapsulated in here anyways. 5007 * This removes the dependency 5008 * @param {String} url 5009 * The relative url to make the request to (the host from the passed in config will be 5010 * appended). It is expected that any encoding to the URL is already done. 5011 * @param {Function} handler 5012 * Callback handler for makeRequest to invoke when the response returns. 5013 * Completely passed through to gadgets.io.makeRequest 5014 * @param {Object} params 5015 * The params object that gadgets.io.makeRequest expects. Authorization and locale 5016 * headers are mixed in. 5017 */ 5018 makeRequest: function (url, handler, params) { 5019 var requestedScheme, scheme = "http"; 5020 5021 // ClientServices needs to be initialized with a config for restHost, auth, and locale 5022 _isInited(); 5023 5024 // Allow mixin of auth and locale headers 5025 params = params || {}; 5026 5027 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 5028 // using GET http method because then the params are added to the url as query params, which 5029 // exposes the authorization string in the url. This is a placeholder until oauth comes in 5030 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 5031 5032 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 5033 5034 // Add Basic auth to request header 5035 params[gadgets.io.RequestParameters.HEADERS].Authorization = "Basic " + _config.authorization; 5036 //Locale 5037 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 5038 5039 //Allow clients to override the scheme: 5040 // - If not specified => we use HTTP 5041 // - If null specified => we use _config.scheme 5042 // - Otherwise => we use whatever they provide 5043 requestedScheme = params.SCHEME; 5044 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 5045 if (requestedScheme === null) { 5046 scheme = _config.scheme; 5047 } else { 5048 scheme = requestedScheme; 5049 } 5050 } 5051 5052 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 5053 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 5054 }, 5055 5056 /** 5057 * @private 5058 * Utility function to make a subscription to a particular topic. Only one 5059 * callback function is registered to a particular topic at any time. 5060 * @param {String} topic 5061 * The full topic name. The topic name should follow the OpenAjax 5062 * convention using dot notation (ex: finesse.api.User.1000). 5063 * @param {Function} callback 5064 * The function that should be invoked with the data when an event 5065 * is delivered to the specific topic. 5066 * @returns {Boolean} 5067 * True if the subscription was made successfully and the callback was 5068 * been registered. False if the subscription already exist, the 5069 * callback was not overwritten. 5070 */ 5071 subscribe: function (topic, callback, disableDuringFailover) { 5072 _isInited(); 5073 5074 //Ensure that the same subscription isn't made twice. 5075 if (!_subscriptionID[topic]) { 5076 //Store the subscription ID using the topic name as the key. 5077 _subscriptionID[topic] = _hub.subscribe(topic, 5078 //Invoke the callback just with the data object. 5079 function (topic, data) { 5080 if (!disableDuringFailover || _isMaster || !_failoverMode) { 5081 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 5082 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 5083 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 5084 // - Master instance will get all events regardless, because it is responsible for managing failover 5085 // - If we are not in a failover mode, everything goes 5086 // _refreshObjects will reconcile anything that was missed once we are back in action 5087 callback(data); 5088 } 5089 }); 5090 return true; 5091 } 5092 return false; 5093 }, 5094 5095 /** 5096 * @private 5097 * Unsubscribe from a particular topic. 5098 * @param {String} topic 5099 * The full topic name. 5100 */ 5101 unsubscribe: function (topic) { 5102 _isInited(); 5103 5104 //Unsubscribe from the topic using the subscription ID recorded when 5105 //the subscription was made, then delete the ID from data structure. 5106 if (_subscriptionID[topic]) { 5107 _hub.unsubscribe(_subscriptionID[topic]); 5108 delete _subscriptionID[topic]; 5109 } 5110 }, 5111 5112 /** 5113 * @private 5114 * Make a request to the request channel to have the Master subscribe 5115 * to a node. 5116 * @param {String} node 5117 * The node to subscribe to. 5118 */ 5119 subscribeNode: function (node, handler) { 5120 if (handler && typeof handler !== "function") { 5121 throw new Error("ClientServices.subscribeNode: handler is not a function"); 5122 } 5123 5124 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5125 var data = { 5126 type: "SubscribeNodeReq", 5127 data: {node: node}, 5128 invokeID: _util.generateUUID() 5129 }, 5130 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5131 _this = this; 5132 5133 // We need to first subscribe to the response channel 5134 this.subscribe(responseTopic, function (rsp) { 5135 // Since this channel is only used for this singular request, 5136 // we are not interested anymore. 5137 // This is also critical to not leaking memory by having OpenAjax 5138 // store a bunch of orphaned callback handlers that enclose on 5139 // our entire ClientServices singleton 5140 _this.unsubscribe(responseTopic); 5141 if (handler) { 5142 handler(data.invokeID, rsp); 5143 } 5144 }); 5145 // Then publish the request on the request channel 5146 _hub.publish(_topics.REQUESTS, data); 5147 }, 5148 5149 /** 5150 * @private 5151 * Make a request to the request channel to have the Master unsubscribe 5152 * from a node. 5153 * @param {String} node 5154 * The node to unsubscribe from. 5155 */ 5156 unsubscribeNode: function (node, subid, handler) { 5157 if (handler && typeof handler !== "function") { 5158 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 5159 } 5160 5161 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5162 var data = { 5163 type: "UnsubscribeNodeReq", 5164 data: { 5165 node: node, 5166 subid: subid 5167 }, 5168 invokeID: _util.generateUUID() 5169 }, 5170 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5171 _this = this; 5172 5173 // We need to first subscribe to the response channel 5174 this.subscribe(responseTopic, function (rsp) { 5175 // Since this channel is only used for this singular request, 5176 // we are not interested anymore. 5177 // This is also critical to not leaking memory by having OpenAjax 5178 // store a bunch of orphaned callback handlers that enclose on 5179 // our entire ClientServices singleton 5180 _this.unsubscribe(responseTopic); 5181 if (handler) { 5182 handler(rsp); 5183 } 5184 }); 5185 // Then publish the request on the request channel 5186 _hub.publish(_topics.REQUESTS, data); 5187 }, 5188 5189 /** 5190 * @private 5191 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 5192 */ 5193 makeConnectionReq : function () { 5194 // Disallow others (non-masters) from administering BOSH connections that are not theirs 5195 if (_isMaster && _publisher) { 5196 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 5197 } else { 5198 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5199 } 5200 }, 5201 5202 /** 5203 * @private 5204 * Set's the global logger for this Client Services instance. 5205 * @param {Object} logger 5206 * Logger object with the following attributes defined:<ul> 5207 * <li><b>log:</b> function (msg) to simply log a message 5208 * </ul> 5209 */ 5210 setLogger: function (logger) { 5211 // We want to check the logger coming in so we don't have to check every time it is called. 5212 if (logger && typeof logger === "object" && typeof logger.log === "function") { 5213 _logger = logger; 5214 } else { 5215 // We are resetting it to an empty object so that _logger.log in .log is falsy. 5216 _logger = {}; 5217 } 5218 }, 5219 5220 /** 5221 * @private 5222 * Centralized logger.log method for external logger 5223 * @param {String} msg 5224 * Message to log 5225 */ 5226 log: _log, 5227 5228 /** 5229 * @class 5230 * Allow clients to make Finesse API requests and consume Finesse events by 5231 * calling a set of exposed functions. The Services layer will do the dirty 5232 * work of establishing a shared BOSH connection (for designated Master 5233 * modules), consuming events for client subscriptions, and constructing API 5234 * requests. 5235 * 5236 * @constructs 5237 */ 5238 _fakeConstuctor: function () { 5239 /* This is here so we can document init() as a method rather than as a constructor. */ 5240 }, 5241 5242 /** 5243 * Initiates the Client Services with the specified config parameters. 5244 * Enabling the Client Services as Master will trigger the establishment 5245 * of a BOSH event connection. 5246 * @param {Object} config 5247 * Configuration object containing properties used for making REST requests:<ul> 5248 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 5249 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 5250 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 5251 * appropriate authorization string is provided, otherwise it is 5252 * required.</li> 5253 * <li><b>password:</b> The password belonging to the user. This is an optional param as 5254 * long as the appropriate authorization string is provided, 5255 * otherwise it is required.</li> 5256 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 5257 * param is provided to allow the ability to hide the password 5258 * param. If provided, the id and the password extracted from this 5259 * string will be used over the config.id and config.password.</li> 5260 * </ul> 5261 * @throws {Error} If required constructor parameter is missing. 5262 * @example 5263 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 5264 */ 5265 init: function (config) { 5266 if (!_inited) { 5267 //Validate the properties within the config object if one is provided. 5268 if (!(typeof config === "object" && 5269 typeof config.host === "string" && config.host.length > 0 && config.restHost && 5270 (typeof config.authorization === "string" || 5271 (typeof config.id === "string" && 5272 typeof config.password === "string")))) { 5273 throw new Error("Config object contains invalid properties."); 5274 } 5275 5276 // Initialize configuration 5277 _config = config; 5278 5279 // Set shortcuts 5280 _util = Utilities; 5281 _topics = Topics; 5282 5283 //TODO: document when this is properly supported 5284 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 5285 _hub = config.hub || gadgets.Hub; 5286 _io = config.io || gadgets.io; 5287 5288 //If the authorization string is provided, then use that to 5289 //extract the ID and the password. Otherwise use the ID and 5290 //password from the respective ID and password params. 5291 if (_config.authorization) { 5292 var creds = _util.getCredentials(_config.authorization); 5293 _config.id = creds.id; 5294 _config.password = creds.password; 5295 } 5296 else { 5297 _config.authorization = _util.b64Encode( 5298 _config.id + ":" + _config.password); 5299 } 5300 5301 _inited = true; 5302 5303 if (_hub) { 5304 //Subscribe to receive connection information. Since it is possible that 5305 //the client comes up after the Master comes up, the client will need 5306 //to make a request to have the Master send the latest connection info. 5307 //It would be possible that all clients get connection info again. 5308 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 5309 _makeConnectionInfoReq(); 5310 } 5311 } 5312 5313 //Return the CS object for object chaining. 5314 return this; 5315 }, 5316 5317 /** 5318 * @private 5319 * Initializes the BOSH component of this ClientServices instance. This establishes 5320 * the BOSH connection and will trigger the registered handlers as the connection 5321 * status changes respectively:<ul> 5322 * <li>registerOnConnectingHandler</li> 5323 * <li>registerOnConnectHandler</li> 5324 * <li>registerOnDisconnectHandler</li> 5325 * <li>registerOnDisconnectingHandler</li> 5326 * <li>registerOnReconnectingHandler</li> 5327 * <li>registerOnUnloadingHandler</li> 5328 * <ul> 5329 * 5330 * @param {Object} config 5331 * An object containing the following (optional) handlers for the request:<ul> 5332 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 5333 * This is used to construct the JID: user@domain.com</li> 5334 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 5335 * Available from the SystemInfo object. 5336 * This is used for creating or removing subscriptions.</li> 5337 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 5338 * </ul> 5339 */ 5340 initBosh: function (config) { 5341 //Validate the properties within the config object if one is provided. 5342 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 5343 throw new Error("Config object contains invalid properties."); 5344 } 5345 5346 // Mixin the required information for establishing the BOSH connection 5347 _config.xmppDomain = config.xmppDomain; 5348 _config.pubsubDomain = config.pubsubDomain; 5349 _config.resource = config.resource; 5350 5351 //Initiate Master launch sequence 5352 _becomeMaster(); 5353 }, 5354 5355 /** 5356 * @private 5357 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 5358 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 5359 * @param {Object} failoverMode 5360 * true if failing, false or something falsy when recovered 5361 */ 5362 setFailoverMode: function (failoverMode) { 5363 if (_isMaster) { 5364 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 5365 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 5366 }); 5367 } 5368 }, 5369 5370 /** 5371 * @private 5372 * Private accessor used to inject mocked private dependencies for unit testing 5373 */ 5374 _getTestObj: function () { 5375 return { 5376 setPublisher: function (publisher) { 5377 _publisher = publisher; 5378 } 5379 }; 5380 } 5381 }; 5382 }()); 5383 5384 window.finesse = window.finesse || {}; 5385 window.finesse.clientservices = window.finesse.clientservices || {}; 5386 window.finesse.clientservices.ClientServices = ClientServices; 5387 5388 return ClientServices; 5389 5390 }); 5391 5392 /** 5393 * The following comment prevents JSLint errors concerning undefined global variables. 5394 * It tells JSLint that these identifiers are defined elsewhere. 5395 */ 5396 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 5397 5398 /** The following comment is to prevent jslint errors about 5399 * using variables before they are defined. 5400 */ 5401 /*global Handlebars */ 5402 5403 /** 5404 * JavaScript class to implement common notification 5405 * functionality. 5406 * 5407 * @requires Class 5408 * @requires finesse.FinesseBase 5409 */ 5410 /** @private */ 5411 define('restservices/Notifier',[ 5412 'FinesseBase', 5413 'clientservices/ClientServices' 5414 ], 5415 function (FinesseBase, ClientServices) { 5416 var Notifier = FinesseBase.extend({ 5417 /** 5418 * Initializes the notifier object. 5419 */ 5420 init : function () { 5421 this._super(); 5422 this._listenerCallback = []; 5423 }, 5424 5425 /** 5426 * Add a listener. 5427 * 5428 * @param callback_function 5429 * @param scope 5430 * is the callback function to add 5431 */ 5432 addListener : function (callback_function, scope) { 5433 var len = this._listenerCallback.length, i, cb, add = true; 5434 for (i = 0; i < len; i += 1) { 5435 cb = this._listenerCallback[i].callback; 5436 if (cb === callback_function) { 5437 // this callback already exists 5438 add = false; 5439 break; 5440 } 5441 } 5442 if (add) { 5443 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 5444 } 5445 }, 5446 5447 /** 5448 * Remove a listener. 5449 * 5450 * @param callback_function 5451 * is the callback function to remove 5452 * @return {Boolean} true if removed 5453 */ 5454 removeListener : function (callback_function) { 5455 5456 var result = false, len = this._listenerCallback.length, i, cb; 5457 for (i = len - 1; i >= 0; i -=1) { 5458 cb = this._listenerCallback[i].callback; 5459 if (cb === callback_function) { 5460 this._listenerCallback[i] = undefined; 5461 this._listenerCallback.splice(i, 1); 5462 result = true; 5463 break; 5464 } 5465 } 5466 5467 return result; 5468 }, 5469 5470 /** 5471 * Removes all listeners 5472 * @return {undefined} 5473 */ 5474 reset: function () { 5475 this._listenerCallback = []; 5476 }, 5477 5478 /** 5479 * Notify all listeners. 5480 * 5481 * @param obj 5482 * is the object that has changed 5483 */ 5484 notifyListeners : function (obj) { 5485 var len = this._listenerCallback.length, i, callbackFunction, scope; 5486 5487 for (i = 0; i < len; i += 1) { 5488 // Be sure that one bad callback does not prevent other listeners 5489 // from receiving. 5490 try { 5491 callbackFunction = this._listenerCallback[i].callback; 5492 scope = this._listenerCallback[i].scope; 5493 if (typeof callbackFunction === 'function') { 5494 callbackFunction.call(scope, obj); 5495 } 5496 } catch (err) { 5497 ClientServices.log("Exception caught: " + err); 5498 } 5499 } 5500 }, 5501 5502 /** 5503 * Gets a copy of the listeners. 5504 * @return changeListenerCopy (array of callbacks) 5505 */ 5506 getListeners : function () { 5507 var changeListenerCopy = [], len = this._listenerCallback.length, i; 5508 5509 for (i = 0; i < len; i += 1) { 5510 changeListenerCopy.push(this._listenerCallback[i].callback); 5511 } 5512 5513 return changeListenerCopy; 5514 }, 5515 5516 /** 5517 * Verifies that the handler is function. 5518 * @param handler to verify 5519 * @return the handler 5520 * @throws Error if not a function 5521 */ 5522 _isAFunction : function (handler) { 5523 if (handler === undefined || typeof handler === "function") { 5524 return handler; 5525 } else { 5526 throw new Error("handler must be a function"); 5527 } 5528 } 5529 }); 5530 5531 window.finesse = window.finesse || {}; 5532 window.finesse.restservices = window.finesse.restservices || {}; 5533 window.finesse.restservices.Notifier = Notifier; 5534 5535 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 5536 finesse.restservices = finesse.restservices || {}; 5537 5538 return Notifier; 5539 }); 5540 5541 /** 5542 * JavaScript base object that all REST objects should inherit 5543 * from because it encapsulates and provides the common functionality that 5544 * all REST objects need. 5545 * 5546 * @requires finesse.clientservices.ClientServices 5547 * @requires Class 5548 */ 5549 5550 /** @private */ 5551 define('restservices/RestBase',[ 5552 "FinesseBase", 5553 "utilities/Utilities", 5554 "restservices/Notifier", 5555 "clientservices/ClientServices", 5556 "clientservices/Topics" 5557 ], 5558 function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 5559 5560 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 5561 5562 doNotLog: false, 5563 5564 /** 5565 * Used by _processUpdate() and restRequest(). 5566 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 5567 * so that one of the callbacks can be fired when a corresponding event is 5568 * received inside _processUpdate(). 5569 * @private 5570 */ 5571 _pendingCallbacks: {}, 5572 5573 /** 5574 * Gets the REST class for the current object. This object throws an 5575 * exception because subtype must implement. 5576 * @throws {Error} because subtype must implement 5577 * @private 5578 */ 5579 getRestClass: function () { 5580 throw new Error("getRestClass(): Not implemented in subtype."); 5581 }, 5582 5583 /** 5584 * Gets the REST type for the current object. This object throws an 5585 * exception because subtype must implement. 5586 * @throws {Error} because subtype must implement. 5587 * @private 5588 */ 5589 getRestType: function () { 5590 throw new Error("getRestType(): Not implemented in subtype."); 5591 }, 5592 5593 /** 5594 * Gets the node path for the current object. This object throws an 5595 * exception because subtype must implement. 5596 * @throws {Error} because subtype must implement. 5597 * @private 5598 */ 5599 getXMPPNodePath: function () { 5600 throw new Error("getXMPPNodePath(): Not implemented in subtype."); 5601 }, 5602 5603 /** 5604 * Boolean function that specifies whether the REST object supports 5605 * requests. True by default. Subclasses should override if false. 5606 * @private 5607 */ 5608 supportsRequests: true, 5609 5610 /** 5611 * Boolean function that specifies whether the REST object supports 5612 * subscriptions. True by default. Subclasses should override if false. 5613 * @private 5614 */ 5615 supportsSubscriptions: true, 5616 5617 /** 5618 * Boolean function that specifies whether the REST object should retain 5619 * a copy of the REST response. False by default. Subclasses should override if true. 5620 * @private 5621 */ 5622 keepRestResponse: false, 5623 5624 /** 5625 * Boolean function that specifies whether the REST object explicitly 5626 * subscribes. False by default. Subclasses should override if true. 5627 * @private 5628 */ 5629 explicitSubscription: false, 5630 5631 /** 5632 * Boolean function that specifies whether subscribing should be 5633 * automatically done at construction. Defaults to true. 5634 * This be overridden at object construction, not by implementing subclasses 5635 * @private 5636 */ 5637 autoSubscribe: true, 5638 5639 /** 5640 * Private reference to default logger 5641 * @private 5642 */ 5643 _logger: { 5644 log: ClientServices.log, 5645 error: ClientServices.log 5646 }, 5647 5648 /** 5649 * @class 5650 * JavaScript representation of a REST object. Also exposes methods to operate 5651 * on the object against the server. This object is typically extended into individual 5652 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 5653 * 5654 * @constructor 5655 * @param {String} id 5656 * The ID that uniquely identifies the REST object. 5657 * @param {Object} callbacks 5658 * An object containing callbacks for instantiation and runtime 5659 * Callback to invoke upon successful instantiation, passes in REST object. 5660 * @param {Function} callbacks.onLoad(this) 5661 * Callback to invoke upon loading the data for the first time. 5662 * @param {Function} callbacks.onChange(this) 5663 * Callback to invoke upon successful update object (PUT) 5664 * @param {Function} callbacks.onAdd(this) 5665 * Callback to invoke upon successful update to add object (POST) 5666 * @param {Function} callbacks.onDelete(this) 5667 * Callback to invoke upon successful update to delete object (DELETE) 5668 * @param {Function} callbacks.onError(rsp) 5669 * Callback to invoke on update error (refresh or event) 5670 * as passed by finesse.restservices.RestBase.restRequest() 5671 * { 5672 * status: {Number} The HTTP status code returned 5673 * content: {String} Raw string of response 5674 * object: {Object} Parsed object of response 5675 * error: {Object} Wrapped exception that was caught 5676 * error.errorType: {String} Type of error that was caught 5677 * error.errorMessage: {String} Message associated with error 5678 * } 5679 * @param {RestBase} [restObj] 5680 * A RestBase parent object which this object has an association with. 5681 * @constructs 5682 */ 5683 init: function (options, callbacks, restObj) { 5684 /** 5685 * Initialize the base class 5686 */ 5687 var _this = this; 5688 5689 this._super(); 5690 5691 if (typeof options === "object") { 5692 this._id = options.id; 5693 this._restObj = options.parentObj; 5694 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 5695 this.doNotSubscribe = options.doNotSubscribe; 5696 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 5697 callbacks = { 5698 onLoad: options.onLoad, 5699 onChange: options.onChange, 5700 onAdd: options.onAdd, 5701 onDelete: options.onDelete, 5702 onError: options.onError 5703 }; 5704 } else { 5705 this._id = options; 5706 this._restObj = restObj; 5707 } 5708 5709 // Common stuff 5710 5711 this._data = {}; 5712 5713 //Contains the full rest response to be processed by upper layers if needed 5714 this._restResponse = undefined; 5715 5716 this._lastUpdate = {}; 5717 5718 this._util = Utilities; 5719 5720 //Should be correctly initialized in either a window OR gadget context 5721 this._config = finesse.container.Config; 5722 5723 // Setup all the notifiers - change, load and error. 5724 this._changeNotifier = new Notifier(); 5725 this._loadNotifier = new Notifier(); 5726 this._addNotifier = new Notifier(); 5727 this._deleteNotifier = new Notifier(); 5728 this._errorNotifier = new Notifier(); 5729 5730 this._loaded = false; 5731 5732 // Protect against null dereferencing of options allowing its 5733 // (nonexistent) keys to be read as undefined 5734 callbacks = callbacks || {}; 5735 5736 this.addHandler('load', callbacks.onLoad); 5737 this.addHandler('change', callbacks.onChange); 5738 this.addHandler('add', callbacks.onAdd); 5739 this.addHandler('delete', callbacks.onDelete); 5740 this.addHandler('error', callbacks.onError); 5741 5742 // Attempt to get the RestType then synchronize 5743 try { 5744 this.getRestType(); 5745 5746 // Only subscribe if this REST object supports subscriptions 5747 // and autoSubscribe was not requested to be disabled as a construction option 5748 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 5749 this.subscribe({ 5750 success: function () { 5751 //TODO: figure out how to use Function.call() or Function.apply() here... 5752 //this is exactly the same as the below else case other than the scope of "this" 5753 if (typeof options === "object" && options.data) { 5754 if (!_this._processObject(_this._normalize(options.data))) { 5755 // notify of error if we fail to construct 5756 _this._errorNotifier.notifyListeners(_this); 5757 } 5758 } else { 5759 // Only subscribe if this REST object supports requests 5760 if (_this.supportsRequests) { 5761 _this._synchronize(); 5762 } 5763 } 5764 }, 5765 error: function (err) { 5766 _this._errorNotifier.notifyListeners(err); 5767 } 5768 }); 5769 } else { 5770 if (typeof options === "object" && options.data) { 5771 if (!this._processObject(this._normalize(options.data))) { 5772 // notify of error if we fail to construct 5773 this._errorNotifier.notifyListeners(this); 5774 } 5775 } else { 5776 // Only subscribe if this REST object supports requests 5777 if (this.supportsRequests) { 5778 this._synchronize(); 5779 } 5780 } 5781 } 5782 5783 } catch (err) { 5784 this._logger.error('id=' + this._id + ': ' + err); 5785 } 5786 }, 5787 5788 /** 5789 * Determines if the object has a particular property. 5790 * @param obj is the object to examine 5791 * @param property is the property to check for 5792 * @returns {Boolean} 5793 */ 5794 hasProperty: function (obj, prop) { 5795 return (obj !== null) && (obj.hasOwnProperty(prop)); 5796 }, 5797 5798 /** 5799 * Gets a property from the object. 5800 * @param obj is the object to examine 5801 * @param property is the property to get 5802 * @returns {Property Value} or {Null} if not found 5803 */ 5804 getProperty: function (obj, property) { 5805 var result = null; 5806 5807 if (this.hasProperty(obj, property) === false) { 5808 result = null; 5809 } else { 5810 result = obj[property]; 5811 } 5812 return result; 5813 }, 5814 5815 /** 5816 * Utility to extracts the ID from the specified REST URI. This is with the 5817 * assumption that the ID is always the last element in the URI after the 5818 * "/" delimiter. 5819 * @param {String} restUri 5820 * The REST uri (i.e. /finesse/api/User/1000). 5821 * @private 5822 */ 5823 _extractId: function (restObj) { 5824 var obj, restUri = "", strLoc; 5825 for (obj in restObj) { 5826 if (restObj.hasOwnProperty(obj)) { 5827 restUri = restObj[obj].uri; 5828 break; 5829 } 5830 } 5831 return Utilities.getId(restUri); 5832 }, 5833 5834 /** 5835 * Gets the data for this object. 5836 * @returns {Object} which is contained in data 5837 */ 5838 getData: function () { 5839 return this._data; 5840 }, 5841 5842 /** 5843 * Gets the complete REST response to the request made 5844 * @returns {Object} which is contained in data 5845 * @private 5846 */ 5847 getRestResponse: function () { 5848 return this._restResponse; 5849 }, 5850 5851 /** 5852 * The REST URL in which this object can be referenced. 5853 * @return {String} 5854 * The REST URI for this object. 5855 * @private 5856 */ 5857 getRestUrl: function () { 5858 var 5859 restObj = this._restObj, 5860 restUrl = ""; 5861 5862 //Prepend the base REST object if one was provided. 5863 if (restObj instanceof RestBase) { 5864 restUrl += restObj.getRestUrl(); 5865 } 5866 //Otherwise prepend with the default webapp name. 5867 else { 5868 restUrl += "/finesse/api"; 5869 } 5870 5871 //Append the REST type. 5872 restUrl += "/" + this.getRestType(); 5873 5874 //Append ID if it is not undefined, null, or empty. 5875 if (this._id) { 5876 restUrl += "/" + this._id; 5877 } 5878 return restUrl; 5879 }, 5880 5881 /** 5882 * Getter for the id of this RestBase 5883 * @returns {String} 5884 * The id of this RestBase 5885 */ 5886 getId: function () { 5887 return this._id; 5888 }, 5889 5890 /** 5891 * Synchronize this object with the server using REST GET request. 5892 * @returns {Object} 5893 * { 5894 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 5895 * } 5896 * @private 5897 */ 5898 _synchronize: function (retries) { 5899 // Fetch this REST object 5900 if (typeof this._id === "string") { 5901 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 5902 5903 return this._doGET( 5904 { 5905 success: function (rsp) { 5906 if (!_this._processResponse(rsp)) { 5907 if (retries > 0) { 5908 setTimeout(function () { 5909 _this._synchronize(retries - 1); 5910 }, _RETRY_INTERVAL); 5911 } else { 5912 _this._errorNotifier.notifyListeners(_this); 5913 } 5914 } else { 5915 // If this object was already "loaded" prior to 5916 // the _doGET request, then call the 5917 // changeNotifier 5918 if (isLoaded) { 5919 _this._changeNotifier.notifyListeners(_this); 5920 } 5921 } 5922 }, 5923 error: function (rsp) { 5924 if (retries > 0) { 5925 setTimeout(function () { 5926 _this._synchronize(retries - 1); 5927 }, _RETRY_INTERVAL); 5928 5929 } else { 5930 _this._errorNotifier.notifyListeners(rsp); 5931 } 5932 } 5933 } 5934 ); 5935 5936 } else { 5937 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 5938 } 5939 }, 5940 5941 /** 5942 * Adds an handler to this object. 5943 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 5944 * @param {String} notifierType 5945 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 5946 * @param {Function} callback 5947 * The function callback to invoke. 5948 * @example 5949 * // Handler for additions to the Dialogs collection object. 5950 * // When Dialog (a RestBase object) is created, add a change handler. 5951 * _handleDialogAdd = function(dialog) { 5952 * dialog.addHandler('change', _handleDialogChange); 5953 * } 5954 */ 5955 addHandler: function (notifierType, callback, scope) { 5956 var notifier = null; 5957 try { 5958 Utilities.validateHandler(callback); 5959 5960 notifier = this._getNotifierReference(notifierType); 5961 5962 notifier.addListener(callback, scope); 5963 5964 // If load handler is added and object has 5965 // already been loaded, invoke callback 5966 // immediately 5967 if (notifierType === 'load' && this._loaded) { 5968 callback.call((scope || window), this); 5969 } 5970 } catch (err) { 5971 this._logger.error('id=' + this._id + ': ' + err); 5972 } 5973 }, 5974 5975 /** 5976 * Removes a handler from this object. 5977 * @param {String} notifierType 5978 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 5979 * @param {Function} callback 5980 * The function to remove. 5981 */ 5982 removeHandler: function (notifierType, callback) { 5983 var notifier = null; 5984 try { 5985 Utilities.validateHandler(callback); 5986 5987 notifier = this._getNotifierReference(notifierType); 5988 5989 if (typeof(callback) === "undefined") 5990 { 5991 // Remove all listeners for the type 5992 notifier.reset(); 5993 } 5994 else 5995 { 5996 // Remove the specified listener 5997 finesse.utilities.Utilities.validateHandler(callback); 5998 notifier.removeListener(callback); 5999 } 6000 } catch (err) { 6001 this._logger.error('id=' + this._id + ': ' + err); 6002 } 6003 }, 6004 6005 /** 6006 * Utility method gating any operations that require complete instantiation 6007 * @throws Error 6008 * If this object was not fully instantiated yet 6009 * @returns {finesse.restservices.RestBase} 6010 * This RestBase object to allow cascading 6011 */ 6012 isLoaded: function () { 6013 if (!this._loaded) { 6014 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 6015 } 6016 return this; // Allow cascading 6017 }, 6018 6019 6020 6021 /** 6022 * Force an update on this object. Since an asynchronous GET is performed, 6023 * it is necessary to have an onChange handler registered in order to be 6024 * notified when the response of this returns. 6025 * @param {Integer} retries 6026 * The number or retry attempts to make. 6027 * @returns {Object} 6028 * { 6029 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request 6030 * } 6031 */ 6032 refresh: function (retries) { 6033 var _this = this; 6034 6035 if (this.explicitSubscription) { 6036 this._subscribeNode({ 6037 success: function () { 6038 //Disallow GETs if object doesn't support it. 6039 if (!_this.supportsRequests) { 6040 throw new Error("Object doesn't support request operations."); 6041 } 6042 6043 _this._synchronize(retries); 6044 6045 return this; // Allow cascading 6046 }, 6047 error: function (err) { 6048 _this._errorNotifier.notifyListeners(err); 6049 } 6050 }); 6051 } else { 6052 //Disallow GETs if object doesn't support it. 6053 if (!this.supportsRequests) { 6054 throw new Error("Object doesn't support request operations."); 6055 } 6056 6057 return this._synchronize(retries); 6058 } 6059 }, 6060 6061 /** 6062 * Utility method to validate against the known schema of this RestBase 6063 * @param {Object} obj 6064 * The object to validate 6065 * @returns {Boolean} 6066 * True if the object fits the schema of this object. This usually 6067 * means all required keys or nested objects are present. 6068 * False otherwise. 6069 * @private 6070 */ 6071 _validate: function (obj) { 6072 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 6073 if (!valid) 6074 { 6075 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 6076 } 6077 return valid; 6078 }, 6079 6080 /** 6081 * Utility method to fetch this RestBase from the server 6082 * @param {finesse.interfaces.RequestHandlers} handlers 6083 * An object containing the handlers for the request 6084 * @returns {Object} 6085 * { 6086 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6087 * } 6088 * @private 6089 */ 6090 _doGET: function (handlers) { 6091 return this.restRequest(this.getRestUrl(), handlers); 6092 }, 6093 6094 /** 6095 * Common update event handler used by the pubsub callback closure. 6096 * Processes the update event then notifies listeners. 6097 * @param {Object} scope 6098 * An object containing callbacks to handle the asynchronous get 6099 * @param {Object} update 6100 * An object containing callbacks to handle the asynchronous get 6101 * @private 6102 */ 6103 _updateEventHandler: function (scope, update) { 6104 if (scope._processUpdate(update)) { 6105 switch (update.object.Update.event) { 6106 case "POST": 6107 scope._addNotifier.notifyListeners(scope); 6108 break; 6109 case "PUT": 6110 scope._changeNotifier.notifyListeners(scope); 6111 break; 6112 case "DELETE": 6113 scope._deleteNotifier.notifyListeners(scope); 6114 break; 6115 } 6116 } 6117 }, 6118 6119 /** 6120 * Utility method to create a callback to be given to OpenAjax to invoke when a message 6121 * is published on the topic of our REST URL (also XEP-0060 node). 6122 * This needs to be its own defined method so that subclasses can have their own implementation. 6123 * @returns {Function} callback(update) 6124 * The callback to be invoked when an update event is received. This callback will 6125 * process the update and notify listeners. 6126 * @private 6127 */ 6128 _createPubsubCallback: function () { 6129 var _this = this; 6130 return function (update) { 6131 _this._updateEventHandler(_this, update); 6132 }; 6133 }, 6134 6135 /** 6136 * Subscribe to pubsub infra using the REST URL as the topic name. 6137 * @param {finesse.interfaces.RequestHandlers} handlers 6138 * An object containing the handlers for the request 6139 * @private 6140 */ 6141 subscribe: function (callbacks) { 6142 // Only need to do a subscription to client pubsub. No need to trigger 6143 // a subscription on the Finesse server due to implicit subscribe (at 6144 // least for now). 6145 var _this = this, 6146 topic = Topics.getTopic(this.getRestUrl()), 6147 handlers, 6148 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 6149 6150 callbacks = callbacks || {}; 6151 6152 handlers = { 6153 /** @private */ 6154 success: function () { 6155 // Add item to the refresh list in ClientServices to refresh if 6156 // we recover due to our resilient connection. However, do 6157 // not add if doNotRefresh flag is set. 6158 if (!_this.doNotRefresh) { 6159 ClientServices.addToRefreshList(_this); 6160 } 6161 6162 if (typeof callbacks.success === "function") { 6163 callbacks.success(); 6164 } 6165 }, 6166 /** @private */ 6167 error: function (err) { 6168 if (successful) { 6169 ClientServices.unsubscribe(topic); 6170 } 6171 6172 if (typeof callbacks.error === "function") { 6173 callbacks.error(err); 6174 } 6175 } 6176 }; 6177 6178 // Request a node subscription only if this object requires explicit subscriptions 6179 if (this.explicitSubscription === true) { 6180 this._subscribeNode(handlers); 6181 } else { 6182 if (successful) { 6183 this._subid = "OpenAjaxOnly"; 6184 handlers.success(); 6185 } else { 6186 handlers.error(); 6187 } 6188 } 6189 6190 return this; 6191 }, 6192 6193 /** 6194 * Unsubscribe to pubsub infra using the REST URL as the topic name. 6195 * @param {finesse.interfaces.RequestHandlers} handlers 6196 * An object containing the handlers for the request 6197 * @private 6198 */ 6199 unsubscribe: function (callbacks) { 6200 // Only need to do a subscription to client pubsub. No need to trigger 6201 // a subscription on the Finesse server due to implicit subscribe (at 6202 // least for now). 6203 var _this = this, 6204 topic = Topics.getTopic(this.getRestUrl()), 6205 handlers; 6206 6207 // no longer keep track of object to refresh on reconnect 6208 ClientServices.removeFromRefreshList(_this); 6209 6210 callbacks = callbacks || {}; 6211 6212 handlers = { 6213 /** @private */ 6214 success: function () { 6215 if (typeof callbacks.success === "function") { 6216 callbacks.success(); 6217 } 6218 }, 6219 /** @private */ 6220 error: function (err) { 6221 if (typeof callbacks.error === "function") { 6222 callbacks.error(err); 6223 } 6224 } 6225 }; 6226 6227 if (this._subid) { 6228 ClientServices.unsubscribe(topic); 6229 // Request a node unsubscribe only if this object requires explicit subscriptions 6230 if (this.explicitSubscription === true) { 6231 this._unsubscribeNode(handlers); 6232 } else { 6233 this._subid = undefined; 6234 handlers.success(); 6235 } 6236 } else { 6237 handlers.success(); 6238 } 6239 6240 return this; 6241 }, 6242 6243 /** 6244 * Private utility to perform node subscribe requests for explicit subscriptions 6245 * @param {finesse.interfaces.RequestHandlers} handlers 6246 * An object containing the handlers for the request 6247 * @private 6248 */ 6249 _subscribeNode: function (callbacks) { 6250 var _this = this; 6251 6252 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6253 callbacks = callbacks || {}; 6254 6255 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 6256 if (err) { 6257 if (typeof callbacks.error === "function") { 6258 callbacks.error(err); 6259 } 6260 } else { 6261 // Store the subid on a successful subscribe 6262 _this._subid = subid; 6263 if (typeof callbacks.success === "function") { 6264 callbacks.success(); 6265 } 6266 } 6267 }); 6268 }, 6269 6270 /** 6271 * Private utility to perform node unsubscribe requests for explicit subscriptions 6272 * @param {finesse.interfaces.RequestHandlers} handlers 6273 * An object containing the handlers for the request 6274 * @private 6275 */ 6276 _unsubscribeNode: function (callbacks) { 6277 var _this = this; 6278 6279 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6280 callbacks = callbacks || {}; 6281 6282 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 6283 _this._subid = undefined; 6284 if (err) { 6285 if (typeof callbacks.error === "function") { 6286 callbacks.error(err); 6287 } 6288 } else { 6289 if (typeof callbacks.success === "function") { 6290 callbacks.success(); 6291 } 6292 } 6293 }); 6294 }, 6295 6296 /** 6297 * Validate and store the object into the internal data store. 6298 * @param {Object} object 6299 * The JavaScript object that should match of schema of this REST object. 6300 * @returns {Boolean} 6301 * True if the object was validated and stored successfully. 6302 * @private 6303 */ 6304 _processObject: function (object) { 6305 if (this._validate(object)) { 6306 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 6307 6308 // If loaded for the first time, call the load notifiers. 6309 if (!this._loaded) { 6310 this._loaded = true; 6311 this._loadNotifier.notifyListeners(this); 6312 } 6313 6314 return true; 6315 } 6316 return false; 6317 }, 6318 6319 /** 6320 * Normalize the object to mitigate the differences between the backend 6321 * and what this REST object should hold. For example, the backend sends 6322 * send an event with the root property name being lower case. In order to 6323 * match the GET, the property should be normalized to an upper case. 6324 * @param {Object} object 6325 * The object which should be normalized. 6326 * @returns {Object} 6327 * Return the normalized object. 6328 * @private 6329 */ 6330 _normalize: function (object) { 6331 var 6332 restType = this.getRestType(), 6333 // Get the REST object name with first character being lower case. 6334 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 6335 6336 // Normalize payload to match REST object. The payload for an update 6337 // use a lower case object name as oppose to upper case. Only normalize 6338 // if necessary. 6339 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 6340 //Since the object is going to be modified, clone the object so that 6341 //it doesn't affect others (due to OpenAjax publishing to other 6342 //subscriber. 6343 object = jQuery.extend(true, {}, object); 6344 6345 object[restType] = object[objRestType]; 6346 delete(object[objRestType]); 6347 } 6348 return object; 6349 }, 6350 6351 /** 6352 * Utility method to process the response of a successful get 6353 * @param {Object} rsp 6354 * The response of a successful get 6355 * @returns {Boolean} 6356 * True if the update was successfully processed (the response object 6357 * passed the schema validation) and updated the internal data cache, 6358 * false otherwise. 6359 * @private 6360 */ 6361 _processResponse: function (rsp) { 6362 try { 6363 if (this.keepRestResponse) { 6364 this._restResponse = rsp.content; 6365 } 6366 return this._processObject(rsp.object); 6367 } 6368 catch (err) { 6369 this._logger.error(this.getRestType() + ': ' + err); 6370 } 6371 return false; 6372 }, 6373 6374 /** 6375 * Utility method to process the update notification. 6376 * @param {Object} update 6377 * The payload of an update notification. 6378 * @returns {Boolean} 6379 * True if the update was successfully processed (the update object 6380 * passed the schema validation) and updated the internal data cache, 6381 * false otherwise. 6382 * @private 6383 */ 6384 _processUpdate: function (update) { 6385 try { 6386 var updateObj, requestId, fakeResponse, receivedError; 6387 6388 // The backend will send the data object with a lower case. To be 6389 // consistent with what should be represented in this object, the 6390 // object name should be upper case. This will normalize the object. 6391 updateObj = this._normalize(update.object.Update.data); 6392 6393 // Store the last event. 6394 this._lastUpdate = update.object; 6395 6396 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 6397 6398 if (requestId && this._pendingCallbacks[requestId]) { 6399 6400 /* 6401 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 6402 * a simulated/"fake" AJAX response object from the information in the received event. 6403 * The constructed object should conform to the contract for response objects specified 6404 * in _createAjaxHandler(). 6405 */ 6406 fakeResponse = {}; 6407 6408 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 6409 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 6410 //doing a parse(stringify(update)). 6411 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 6412 6413 fakeResponse.object = {}; 6414 6415 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 6416 6417 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 6418 receivedError = updateObj.apiErrors.apiError; 6419 fakeResponse.object.ApiErrors = {}; 6420 fakeResponse.object.ApiErrors.ApiError = {}; 6421 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 6422 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 6423 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 6424 6425 /* 6426 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 6427 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6428 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 6429 **/ 6430 fakeResponse.status = 400; 6431 6432 } else { //Success case 6433 6434 fakeResponse.object = this._lastUpdate; 6435 6436 /* 6437 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 6438 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6439 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 6440 **/ 6441 fakeResponse.status = 200; 6442 } 6443 6444 try { 6445 6446 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 6447 this._pendingCallbacks[requestId].error(fakeResponse); 6448 } 6449 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 6450 /*else if (this._pendingCallbacks[requestId].success) { 6451 this._pendingCallbacks[requestId].success(fakeResponse); 6452 }*/ 6453 6454 } catch (callbackErr) { 6455 6456 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 6457 6458 } 6459 6460 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 6461 delete this._pendingCallbacks[requestId]; 6462 6463 } else { 6464 this._logger.log(this.getRestType() + ": Received the following event with an invalid or unknown requestId:"); 6465 this._logger.log(gadgets.json.stringify(update)); 6466 } 6467 6468 return this._processObject(updateObj); 6469 } 6470 catch (err) { 6471 this._logger.error(this.getRestType() + ': ' + err); 6472 } 6473 return false; 6474 }, 6475 6476 /** 6477 * Utility method to create ajax response handler closures around the 6478 * provided callbacks. Callbacks should be passed through from .ajax(). 6479 * makeRequest is responsible for garbage collecting these closures. 6480 * @param {finesse.interfaces.RequestHandlers} handlers 6481 * An object containing the handlers for the request 6482 * @returns {Object} 6483 * { 6484 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns 6485 * callback: {function} Callback handler to be invoked when the response returns 6486 * } 6487 * @private 6488 */ 6489 _createAjaxHandler: function (options) { 6490 //We should not need to check this again since it has already been done in .restRequest() 6491 //options = options || {}; 6492 6493 //Flag to indicate whether or not to process the response 6494 var abort = false, 6495 6496 //Get a reference to the parent User object 6497 _this = this; 6498 6499 return { 6500 6501 abort: function () { 6502 abort = true; 6503 }, 6504 6505 callback: function (rsp) { 6506 6507 if (abort) { 6508 // Do not process the response 6509 return; 6510 } 6511 6512 var requestId, error = false, rspObj; 6513 6514 if (options.success || options.error) { 6515 rspObj = { 6516 status: rsp.rc, 6517 content: rsp.text 6518 }; 6519 6520 if (!_this.doNotLog) { 6521 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "'"); 6522 } 6523 6524 //Some responses may not have a body. 6525 if (rsp.text && rsp.text.length > 0) { 6526 try { 6527 rspObj.object = _this._util.xml2js(rsp.text); 6528 } catch (e) { 6529 error = true; 6530 rspObj.error = { 6531 errorType: "parseError", 6532 errorMessage: "Could not serialize XML: " + e 6533 }; 6534 } 6535 } else { 6536 rspObj.object = {}; 6537 } 6538 6539 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 6540 if (options.success) { 6541 options.success(rspObj); 6542 } 6543 } else { 6544 if (options.error) { 6545 options.error(rspObj); 6546 } 6547 } 6548 6549 /* 6550 * If a synchronous error happened after a non-GET request (usually a validation error), we 6551 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 6552 * will arrive later. The corresponding requestId should be present in the response headers. 6553 * 6554 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 6555 * 'requestId' below. 6556 **/ 6557 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 6558 requestId = rsp.headers.requestid[0]; 6559 if (_this._pendingCallbacks[requestId]) { 6560 delete _this._pendingCallbacks[requestId]; 6561 } 6562 } 6563 } 6564 } 6565 }; 6566 }, 6567 6568 /** 6569 * Utility method to make an asynchronous request 6570 * @param {String} url 6571 * The unencoded URL to which the request is sent (will be encoded) 6572 * @param {Object} options 6573 * An object containing additional options for the request. 6574 * @param {Object} options.content 6575 * An object to send in the content body of the request. Will be 6576 * serialized into XML before sending. 6577 * @param {String} options.method 6578 * The type of request. Defaults to "GET" when none is specified. 6579 * @param {Function} options.success(rsp) 6580 * A callback function to be invoked for a successful request. 6581 * { 6582 * status: {Number} The HTTP status code returned 6583 * content: {String} Raw string of response 6584 * object: {Object} Parsed object of response 6585 * } 6586 * @param {Function} options.error(rsp) 6587 * A callback function to be invoked for an unsuccessful request. 6588 * { 6589 * status: {Number} The HTTP status code returned 6590 * content: {String} Raw string of response 6591 * object: {Object} Parsed object of response 6592 * error: {Object} Wrapped exception that was caught 6593 * error.errorType: {String} Type of error that was caught 6594 * error.errorMessage: {String} Message associated with error 6595 * } 6596 * @returns {Object} 6597 * { 6598 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request 6599 * } 6600 * @private 6601 */ 6602 restRequest: function (url, options) { 6603 6604 var params, encodedUrl, ajaxHandler; 6605 6606 params = {}; 6607 6608 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 6609 options = options || {}; 6610 options.success = this._util.validateHandler(options.success); 6611 options.error = this._util.validateHandler(options.error); 6612 6613 // Request Headers 6614 params[gadgets.io.RequestParameters.HEADERS] = {}; 6615 6616 // HTTP method is a passthrough to gadgets.io.makeRequest, makeRequest defaults to GET 6617 params[gadgets.io.RequestParameters.METHOD] = options.method; 6618 6619 //true if this should be a GET request, false otherwise 6620 if (!options.method || options.method === "GET") { 6621 //Disable caching for GETs 6622 if (url.indexOf("?") > -1) { 6623 url += "&"; 6624 } else { 6625 url += "?"; 6626 } 6627 url += "nocache=" + this._util.currentTimeMillis(); 6628 } else { 6629 /** 6630 * If not GET, generate a requestID and add it to the headers, then wrap 6631 * callbacks into an object and store it in _pendingCallbacks. 6632 * If we receive a synchronous error response instead of a 202 as expected, 6633 * the AJAX handler will clean up _pendingCallbacks. 6634 **/ 6635 /* 6636 * TODO: Clean up _pendingCallbacks if an entry persists after a certain amount of time has passed. 6637 * In the block below, can store the current time (new Date().getTime()) alongside the 6638 * callbacks in the new _pendingCallbacks entry. Then iterate through a copty of _pendingCallbacks, 6639 * deleting all entries inside _pendingCallbacks that are older than a certain threshold (2 minutes for example.) 6640 * This solves a potential memory leak issue if we never receive an event for a given stored requestId; 6641 * we don't want to store unfired callbacks forever. 6642 */ 6643 /** @private */ 6644 options.uuid = this._util.generateUUID(); 6645 params[gadgets.io.RequestParameters.HEADERS].requestId = options.uuid; 6646 //By default, Shindig strips nearly all of the response headers, but this parameter tells Shindig 6647 //to send the headers through unmodified; we need to be able to read the 'requestId' header if we 6648 //get a synchronous error as a result of a non-GET request. (See the bottom of _createAjaxHandler().) 6649 params[gadgets.io.RequestParameters.GET_FULL_HEADERS] = "true"; 6650 this._pendingCallbacks[options.uuid] = {}; 6651 this._pendingCallbacks[options.uuid].success = options.success; 6652 this._pendingCallbacks[options.uuid].error = options.error; 6653 } 6654 6655 //debugger; 6656 // NAT: IGNORE this until you hit save after changing assignments, then 6657 // pause here and set window.errorOnRequest to true, step past the next line, 6658 // and then set it to false. True value will throw an error when saving assignments. 6659 encodedUrl = encodeURI(url) + (window.errorOnRestRequest ? "ERROR" : ""); 6660 6661 if (!this.doNotLog) { 6662 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', Making REST request: method=" + (options.method || "GET") + ", url='" + encodedUrl + "'"); 6663 } 6664 6665 // Content Body 6666 if (typeof options.content === "object") { 6667 // Content Type 6668 params[gadgets.io.RequestParameters.HEADERS]["Content-Type"] = "application/xml"; 6669 // Content 6670 params[gadgets.io.RequestParameters.POST_DATA] = this._util.js2xml(options.content); 6671 6672 if (!this.doNotLog) { 6673 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', POST_DATA='" + params[gadgets.io.RequestParameters.POST_DATA] + "'"); 6674 } 6675 } 6676 6677 ajaxHandler = this._createAjaxHandler(options); 6678 ClientServices.makeRequest(encodedUrl, ajaxHandler.callback, params); 6679 6680 return { 6681 abort: ajaxHandler.abort 6682 }; 6683 }, 6684 6685 /** 6686 * Retrieves a reference to a particular notifierType. 6687 * @param notifierType is a string which indicates the notifier to retrieve 6688 * ('load', 'change', 'add', 'delete', 'error') 6689 * @return {Notifier} 6690 * @private 6691 */ 6692 _getNotifierReference: function (notifierType) { 6693 var notifierReference = null; 6694 if (notifierType === 'load') { 6695 notifierReference = this._loadNotifier; 6696 } else if (notifierType === 'change') { 6697 notifierReference = this._changeNotifier; 6698 } else if (notifierType === 'add') { 6699 notifierReference = this._addNotifier; 6700 } else if (notifierType === 'delete') { 6701 notifierReference = this._deleteNotifier; 6702 } else if (notifierType === 'error') { 6703 notifierReference = this._errorNotifier; 6704 } else { 6705 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 6706 } 6707 6708 return notifierReference; 6709 } 6710 }); 6711 6712 window.finesse = window.finesse || {}; 6713 window.finesse.restservices = window.finesse.restservices || {}; 6714 window.finesse.restservices.RestBase = RestBase; 6715 6716 return RestBase; 6717 }); 6718 6719 /** The following comment is to prevent jslint errors about 6720 * using variables before they are defined. 6721 */ 6722 /*global finesse*/ 6723 6724 /** 6725 * JavaScript base object that all REST collection objects should 6726 * inherit from because it encapsulates and provides the common functionality 6727 * that all REST objects need. 6728 * 6729 * @requires finesse.clientservices.ClientServices 6730 * @requires Class 6731 * @requires finesse.FinesseBase 6732 * @requires finesse.restservices.RestBase 6733 */ 6734 6735 /** 6736 * @class 6737 * JavaScript representation of a REST collection object. 6738 * 6739 * @constructor 6740 * @param {Function} callbacks.onCollectionAdd(this) 6741 * Callback to invoke upon successful item addition to the collection. 6742 * @param {Function} callbacks.onCollectionDelete(this) 6743 * Callback to invoke upon successful item deletion from the collection. 6744 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 6745 */ 6746 /** @private */ 6747 define('restservices/RestCollectionBase',[ 6748 'restservices/RestBase', 6749 'utilities/Utilities', 6750 'restservices/Notifier' 6751 ], 6752 function (RestBase, Utilities, Notifier) { 6753 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 6754 6755 /** 6756 * Boolean function that specifies whether the collection handles subscribing 6757 * and propagation of events for the individual REST object items the 6758 * collection holds. False by default. Subclasses should override if true. 6759 * @private 6760 */ 6761 supportsRestItemSubscriptions: false, 6762 6763 /** 6764 * Gets the constructor the individual items that make of the collection. 6765 * For example, a Dialogs collection object will hold a list of Dialog items. 6766 * @throws Error because subtype must implement. 6767 * @private 6768 */ 6769 getRestItemClass: function () { 6770 throw new Error("getRestItemClass(): Not implemented in subtype."); 6771 }, 6772 6773 /** 6774 * Gets the REST type of the individual items that make of the collection. 6775 * For example, a Dialogs collection object will hold a list of Dialog items. 6776 * @throws Error because subtype must implement. 6777 * @private 6778 */ 6779 getRestItemType: function () { 6780 throw new Error("getRestItemType(): Not implemented in subtype."); 6781 }, 6782 6783 /** 6784 * The base REST URL in which items this object contains can be referenced. 6785 * @return {String} 6786 * The REST URI for items this object contains. 6787 * @private 6788 */ 6789 getRestItemBaseUrl: function () { 6790 var 6791 restUrl = "/finesse/api"; 6792 6793 //Append the REST type. 6794 restUrl += "/" + this.getRestItemType(); 6795 6796 return restUrl; 6797 }, 6798 6799 /* 6800 * Creates a new object from the given data 6801 * @param data - data object 6802 * @private 6803 */ 6804 _objectCreator: function (data) { 6805 var objectId = this._extractId(data), 6806 newRestObj = this._collection[objectId], 6807 _this = this; 6808 6809 //Prevent duplicate entries into collection. 6810 if (!newRestObj) { 6811 //Create a new REST object using the subtype defined by the 6812 //overridden method. 6813 newRestObj = new (this.getRestItemClass())({ 6814 doNotSubscribe: this.handlesItemSubscription, 6815 doNotRefresh: this.handlesItemRefresh, 6816 id: objectId, 6817 data: data, 6818 onLoad: function (newObj) { 6819 //Normalize and add REST object to collection datastore. 6820 _this._collection[objectId] = newObj; 6821 _this._collectionAddNotifier.notifyListeners(newObj); 6822 _this.length += 1; 6823 } 6824 }); 6825 } 6826 else { 6827 //If entry already exist in collection, process the new event, 6828 //and notify all change listeners since an existing object has 6829 //change. This could happen in the case when the Finesse server 6830 //cycles, and sends a snapshot of the user's calls. 6831 newRestObj._processObject(data); 6832 newRestObj._changeNotifier.notifyListeners(newRestObj); 6833 } 6834 }, 6835 6836 /* 6837 * Deletes and object and notifies its handlers 6838 * @param data - data object 6839 * @private 6840 */ 6841 _objectDeleter: function (data) { 6842 var objectId = this._extractId(data), 6843 object = this._collection[objectId]; 6844 if (object) { 6845 //Even though this is a delete, let's make sure the object we are passing has got good data 6846 object._processObject(data); 6847 //Notify listeners and delete from internal datastore. 6848 this._collectionDeleteNotifier.notifyListeners(object); 6849 delete this._collection[objectId]; 6850 this.length -= 1; 6851 } 6852 }, 6853 6854 /** 6855 * Creates an anonymous function for notifiying error listeners of a particular object 6856 * data. 6857 * @param obj - the objects whose error listeners to notify 6858 * @returns {Function} 6859 * Callback for notifying of errors 6860 * @private 6861 */ 6862 _createErrorNotifier: function (obj) { 6863 return function (err) { 6864 obj._errorNotifier.notifyListeners(err); 6865 }; 6866 }, 6867 6868 /** 6869 * Replaces the collection with a refreshed list using the passed in 6870 * data. 6871 * @param data - data object (usually this._data) 6872 * @private 6873 */ 6874 _buildRefreshedCollection: function (data) { 6875 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 6876 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 6877 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 6878 } else { 6879 dataArray = []; 6880 } 6881 6882 // iterate through each item in the new data and add to or update collection 6883 for (i = 0; i < dataArray.length; i += 1) { 6884 dataObject = {}; 6885 dataObject[this.getRestItemType()] = dataArray[i]; 6886 objectId = this._extractId(dataObject); 6887 6888 this._objectCreator(dataObject); 6889 newIds.push(objectId); 6890 6891 // resubscribe if the object requires an explicit subscription 6892 object = this._collection[objectId]; 6893 if (this.handlesItemRefresh && object.explicitSubscription) { 6894 object._subscribeNode({ 6895 error: this._createErrorNotifier(object) 6896 }); 6897 } 6898 } 6899 6900 // now clean up items (if any) that were removed 6901 for (objectId in this._collection) { 6902 if (this._collection.hasOwnProperty(objectId)) { 6903 foundFlag = false; 6904 for (i = newIds.length - 1; i >= 0; i -= 1) { 6905 if (newIds[i] === objectId) { 6906 foundFlag = true; 6907 break; 6908 } 6909 } 6910 // did not find in updated list, so delete it 6911 if (!foundFlag) { 6912 this._objectDeleter({'data': this._collection[objectId]._data}); 6913 } 6914 } 6915 } 6916 }, 6917 6918 /** 6919 * The actual refresh operation, refactored out so we don't have to repeat code 6920 * @private 6921 */ 6922 _RESTRefresh: function () { 6923 var _this = this; 6924 this._doGET({ 6925 success: function(rsp) { 6926 if (_this._processResponse(rsp)) { 6927 _this._buildRefreshedCollection(_this._data); 6928 } else { 6929 _this._errorNotifier.notifyListeners(_this); 6930 } 6931 }, 6932 error: function(rsp) { 6933 _this._errorNotifier.notifyListeners(rsp); 6934 } 6935 }); 6936 }, 6937 6938 /** 6939 * Force an update on this object. Since an asynchronous GET is performed, 6940 * it is necessary to have an onChange handler registered in order to be 6941 * notified when the response of this returns. 6942 * @returns {finesse.restservices.RestBaseCollection} 6943 * This RestBaseCollection object to allow cascading 6944 */ 6945 refresh: function() { 6946 var _this = this, isLoaded = this._loaded; 6947 6948 // resubscribe if the collection requires an explicit subscription 6949 if (this.explicitSubscription) { 6950 this._subscribeNode({ 6951 success: function () { 6952 _this._RESTRefresh(); 6953 }, 6954 error: function (err) { 6955 _this._errorNotifier.notifyListeners(err); 6956 } 6957 }); 6958 } else { 6959 this._RESTRefresh(); 6960 } 6961 6962 return this; // Allow cascading 6963 }, 6964 6965 /** 6966 * @private 6967 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 6968 * format of an array of {(Object Type): object} objects. For example, a 6969 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 6970 * @param skipOuterObject If {true} is passed in for this param, then the "data" 6971 * property is returned instead of an object with the 6972 * data appended. 6973 * @return {Array} 6974 */ 6975 extractCollectionData: function (skipOuterObject) { 6976 var restObjs, 6977 obj, 6978 result = [], 6979 _this = this; 6980 6981 if (this._data) 6982 { 6983 restObjs = this._data[this.getRestItemType()]; 6984 6985 if (restObjs) 6986 { 6987 // check if there are multiple objects to pass 6988 if (!$.isArray(restObjs)) 6989 { 6990 restObjs = [restObjs]; 6991 } 6992 6993 // if so, create an object for each and add to result array 6994 $.each(restObjs, function (id, object) { 6995 if (skipOuterObject === true) 6996 { 6997 obj = object; 6998 } 6999 else 7000 { 7001 obj = {}; 7002 obj[_this.getRestItemType()] = object; 7003 } 7004 result.push(obj); 7005 }); 7006 } 7007 7008 } 7009 7010 return result; 7011 }, 7012 7013 /** 7014 * For Finesse, collections are handled uniquely on a POST and 7015 * doesn't necessary follow REST conventions. A POST on a collection 7016 * doesn't mean that the collection has been created, it means that an 7017 * item has been added to the collection. This function will generate 7018 * a closure which will handle this logic appropriately. 7019 * @param {Object} scope 7020 * The scope of where the callback should be invoked. 7021 * @private 7022 */ 7023 _addHandlerCb: function (scope) { 7024 return function (restItem) { 7025 var data = restItem.extractCollectionData(); 7026 7027 $.each(data, function (id, object) { 7028 scope._objectCreator(object); 7029 }); 7030 }; 7031 }, 7032 7033 /** 7034 * For Finesse, collections are handled uniquely on a DELETE and 7035 * doesn't necessary follow REST conventions. A DELETE on a collection 7036 * doesn't mean that the collection has been deleted, it means that an 7037 * item has been deleted from the collection. This function will generate 7038 * a closure which will handle this logic appropriately. 7039 * @param {Object} scope 7040 * The scope of where the callback should be invoked. 7041 * @private 7042 */ 7043 _deleteHandlerCb: function (scope) { 7044 return function (restItem) { 7045 var data = restItem.extractCollectionData(); 7046 7047 $.each(data, function (id, obj) { 7048 scope._objectDeleter(obj); 7049 }); 7050 }; 7051 }, 7052 7053 /** 7054 * Utility method to process the update notification for Rest Items 7055 * that are children of the collection whose events are published to 7056 * the collection's node. 7057 * @param {Object} update 7058 * The payload of an update notification. 7059 * @returns {Boolean} 7060 * True if the update was successfully processed (the update object 7061 * passed the schema validation) and updated the internal data cache, 7062 * false otherwise. 7063 * @private 7064 */ 7065 _processRestItemUpdate: function (update) { 7066 var object, objectId, updateObj = update.object.Update; 7067 7068 //Extract the ID from the source if the Update was an error. 7069 if (updateObj.data.apiErrors) { 7070 objectId = Utilities.getId(updateObj.source); 7071 } 7072 //Otherwise extract from the data object itself. 7073 else { 7074 objectId = this._extractId(updateObj.data); 7075 } 7076 7077 object = this._collection[objectId]; 7078 if (object) { 7079 if (object._processUpdate(update)) { 7080 switch (updateObj.event) { 7081 case "POST": 7082 object._addNotifier.notifyListeners(object); 7083 break; 7084 case "PUT": 7085 object._changeNotifier.notifyListeners(object); 7086 break; 7087 case "DELETE": 7088 object._deleteNotifier.notifyListeners(object); 7089 break; 7090 } 7091 } 7092 } 7093 }, 7094 7095 /** 7096 * SUBCLASS IMPLEMENTATION (override): 7097 * For collections, this callback has the additional responsibility of passing events 7098 * of collection item updates to the item objects themselves. The collection needs to 7099 * do this because item updates are published to the collection's node. 7100 * @returns {Function} 7101 * The callback to be invoked when an update event is received 7102 * @private 7103 */ 7104 _createPubsubCallback: function () { 7105 var _this = this; 7106 return function (update) { 7107 //If the source of the update is our REST URL, this means the collection itself is modified 7108 if (update.object.Update.source === _this.getRestUrl()) { 7109 _this._updateEventHandler(_this, update); 7110 } else { 7111 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 7112 //rest item update of one of our children that was published on our node (OpenAjax topic) 7113 _this._processRestItemUpdate(update); 7114 } 7115 }; 7116 }, 7117 7118 /** 7119 * @class 7120 * This is the base collection object. 7121 * 7122 * @constructs 7123 * @augments finesse.restservices.RestBase 7124 * @see finesse.restservices.Contacts 7125 * @see finesse.restservices.Dialogs 7126 * @see finesse.restservices.PhoneBooks 7127 * @see finesse.restservices.Queues 7128 * @see finesse.restservices.WorkflowActions 7129 * @see finesse.restservices.Workflows 7130 * @see finesse.restservices.WrapUpReasons 7131 */ 7132 _fakeConstuctor: function () { 7133 /* This is here to hide the real init constructor from the public docs */ 7134 }, 7135 7136 /** 7137 * @private 7138 * @param {Object} options 7139 * An object with the following properties:<ul> 7140 * <li><b>id:</b> The id of the object being constructed</li> 7141 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 7142 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 7143 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 7144 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 7145 * This does not include adding and deleting members of the collection</li> 7146 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 7147 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 7148 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 7149 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7150 * <li><b>content:</b> {String} Raw string of response</li> 7151 * <li><b>object:</b> {Object} Parsed object of response</li> 7152 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7153 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7154 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7155 * </ul></li> 7156 * </ul></li> 7157 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7158 **/ 7159 init: function (options) { 7160 7161 options = options || {}; 7162 options.id = ""; 7163 7164 //Make internal datastore collection to hold a list of objects. 7165 this._collection = {}; 7166 this.length = 0; 7167 7168 //Collections will have additional callbacks that will be invoked when 7169 //an item has been added/deleted. 7170 this._collectionAddNotifier = new Notifier(); 7171 this._collectionDeleteNotifier = new Notifier(); 7172 7173 //Initialize the base class. 7174 this._super(options); 7175 7176 this.addHandler('collectionAdd', options.onCollectionAdd); 7177 this.addHandler('collectionDelete', options.onCollectionDelete); 7178 7179 //For Finesse, collections are handled uniquely on a POST/DELETE and 7180 //doesn't necessary follow REST conventions. A POST on a collection 7181 //doesn't mean that the collection has been created, it means that an 7182 //item has been added to the collection. A DELETE means that an item has 7183 //been removed from the collection. Due to this, we are attaching 7184 //special callbacks to the add/delete that will handle this logic. 7185 this.addHandler("add", this._addHandlerCb(this)); 7186 this.addHandler("delete", this._deleteHandlerCb(this)); 7187 }, 7188 7189 /** 7190 * Returns the collection. 7191 * @returns {Object} 7192 * The collection as an object 7193 */ 7194 getCollection: function () { 7195 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 7196 return this._collection; 7197 }, 7198 7199 /** 7200 * Utility method to build the internal collection data structure (object) based on provided data 7201 * @param {Object} data 7202 * The data to build the internal collection from 7203 * @private 7204 */ 7205 _buildCollection: function (data) { 7206 var i, object, objectId, dataArray; 7207 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7208 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7209 for (i = 0; i < dataArray.length; i += 1) { 7210 7211 object = {}; 7212 object[this.getRestItemType()] = dataArray[i]; 7213 objectId = this._extractId(object); 7214 this._collection[objectId] = new (this.getRestItemClass())({ 7215 doNotSubscribe: this.handlesItemSubscription, 7216 doNotRefresh: this.handlesItemRefresh, 7217 id: objectId, 7218 data: object 7219 }); 7220 this.length += 1; 7221 } 7222 } 7223 }, 7224 7225 /** 7226 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 7227 * Override this in subclasses if you need only object with certain attribute values. 7228 * @param {Object} item Item to test. 7229 * @return {Boolean} False to keep, true to filter out (discard); 7230 */ 7231 _filterOutItem: function (item) { 7232 return false; 7233 }, 7234 7235 /** 7236 * Validate and store the object into the internal data store. 7237 * SUBCLASS IMPLEMENTATION (override): 7238 * Performs collection specific logic to _buildCollection internally based on provided data 7239 * @param {Object} object 7240 * The JavaScript object that should match of schema of this REST object. 7241 * @returns {Boolean} 7242 * True if the object was validated and stored successfully. 7243 * @private 7244 */ 7245 _processObject: function (object) { 7246 var i, 7247 restItemType = this.getRestItemType(), 7248 items; 7249 if (this._validate(object)) { 7250 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7251 7252 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 7253 if (this._data) 7254 { 7255 items = this._data[restItemType]; 7256 7257 if (typeof(items) !== "undefined") 7258 { 7259 if (typeof(items.length) === "undefined") 7260 { 7261 // Single object 7262 if (this._filterOutItem(items)) 7263 { 7264 this._data[restItemType] = items = []; 7265 } 7266 7267 } 7268 else 7269 { 7270 // filter out objects 7271 for (i = items.length - 1; i !== -1; i = i - 1) 7272 { 7273 if (this._filterOutItem(items[i])) 7274 { 7275 items.splice(i, 1); 7276 } 7277 } 7278 } 7279 } 7280 } 7281 7282 // If loaded for the first time, call the load notifiers. 7283 if (!this._loaded) { 7284 this._buildCollection(this._data); 7285 this._loaded = true; 7286 this._loadNotifier.notifyListeners(this); 7287 } 7288 7289 return true; 7290 7291 } 7292 return false; 7293 }, 7294 7295 /** 7296 * Retrieves a reference to a particular notifierType. 7297 * @param {String} notifierType 7298 * Specifies the notifier to retrieve (load, change, error, add, delete) 7299 * @return {Notifier} The notifier object. 7300 */ 7301 _getNotifierReference: function (notifierType) { 7302 var notifierReference; 7303 7304 try { 7305 //Use the base method to get references for load/change/error. 7306 notifierReference = this._super(notifierType); 7307 } catch (err) { 7308 //Check for add/delete 7309 if (notifierType === "collectionAdd") { 7310 notifierReference = this._collectionAddNotifier; 7311 } else if (notifierType === "collectionDelete") { 7312 notifierReference = this._collectionDeleteNotifier; 7313 } else { 7314 //Rethrow exception from base class. 7315 throw err; 7316 } 7317 } 7318 return notifierReference; 7319 } 7320 }); 7321 7322 window.finesse = window.finesse || {}; 7323 window.finesse.restservices = window.finesse.restservices || {}; 7324 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 7325 7326 return RestCollectionBase; 7327 }); 7328 7329 /** 7330 * JavaScript representation of the Finesse Dialog object. 7331 * 7332 * @requires finesse.clientservices.ClientServices 7333 * @requires Class 7334 * @requires finesse.FinesseBase 7335 * @requires finesse.restservices.RestBase 7336 */ 7337 7338 /** @private */ 7339 define('restservices/Dialog',[ 7340 'restservices/RestBase', 7341 'utilities/Utilities' 7342 ], 7343 function (RestBase, Utilities) { 7344 var Dialog = RestBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 7345 7346 /** 7347 * @class 7348 * A Dialog is an attempted connection between or among multiple participants, 7349 * for example, a regular phone call, a conference, or a silent monitor session. 7350 * 7351 * @augments finesse.restservices.RestBase 7352 * @constructs 7353 */ 7354 _fakeConstuctor: function () { 7355 /* This is here to hide the real init constructor from the public docs */ 7356 }, 7357 7358 /** 7359 * @private 7360 * 7361 * @param {Object} options 7362 * An object with the following properties:<ul> 7363 * <li><b>id:</b> The id of the object being constructed</li> 7364 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7365 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7366 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7367 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7368 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7369 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7370 * <li><b>content:</b> {String} Raw string of response</li> 7371 * <li><b>object:</b> {Object} Parsed object of response</li> 7372 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7373 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7374 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7375 * </ul></li> 7376 * </ul></li> 7377 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7378 **/ 7379 init: function (options) { 7380 this._super(options); 7381 }, 7382 7383 /** 7384 * @private 7385 * Gets the REST class for the current object - this is the Dialog class. 7386 * @returns {Object} The Dialog class. 7387 */ 7388 getRestClass: function () { 7389 return Dialog; 7390 }, 7391 7392 /** 7393 * @private 7394 * The constant for agent device. 7395 */ 7396 _agentDeviceType: "AGENT_DEVICE", 7397 7398 /** 7399 * @private 7400 * Gets the REST type for the current object - this is a "Dialog". 7401 * @returns {String} The Dialog string. 7402 */ 7403 getRestType: function () { 7404 return "Dialog"; 7405 }, 7406 7407 /** 7408 * @private 7409 * Override default to indicate that this object doesn't support making 7410 * requests. 7411 */ 7412 supportsRequests: false, 7413 7414 /** 7415 * @private 7416 * Override default to indicate that this object doesn't support subscriptions. 7417 */ 7418 supportsSubscriptions: false, 7419 7420 /** 7421 * Getter for the from address. 7422 * @returns {String} The from address. 7423 */ 7424 getFromAddress: function () { 7425 this.isLoaded(); 7426 return this.getData().fromAddress; 7427 }, 7428 7429 /** 7430 * Getter for the to address. 7431 * @returns {String} The to address. 7432 */ 7433 getToAddress: function () { 7434 this.isLoaded(); 7435 return this.getData().toAddress; 7436 }, 7437 7438 /** 7439 * Getter for the media type. 7440 * @returns {String} The media type. 7441 */ 7442 getMediaType: function () { 7443 this.isLoaded(); 7444 return this.getData().mediaType; 7445 }, 7446 7447 /** 7448 * @private 7449 * Getter for the uri. 7450 * @returns {String} The uri. 7451 */ 7452 getDialogUri: function () { 7453 this.isLoaded(); 7454 return this.getData().uri; 7455 }, 7456 7457 /** 7458 * Getter for the callType. 7459 * @deprecated Use getMediaProperties().callType instead. 7460 * @returns {String} The callType. 7461 */ 7462 getCallType: function () { 7463 this.isLoaded(); 7464 return this.getData().mediaProperties.callType; 7465 }, 7466 7467 /** 7468 * Getter for the DNIS. This is usually the actual number dialed. 7469 * @deprecated Use getMediaProperties().DNIS instead. 7470 * @returns {String} The callType. 7471 */ 7472 getDNIS: function () { 7473 this.isLoaded(); 7474 return this.getData().mediaProperties.DNIS; 7475 }, 7476 7477 /** 7478 * Getter for the Dialog state. 7479 * @returns {String} The Dialog state. 7480 */ 7481 getState: function () { 7482 this.isLoaded(); 7483 return this.getData().state; 7484 }, 7485 7486 /** 7487 * Retrieves a list of participants within the Dialog object. 7488 * @returns {Object} Array list of participants. 7489 */ 7490 getParticipants: function () { 7491 this.isLoaded(); 7492 var participants = this.getData().participants.Participant; 7493 //Due to the nature of the XML->JSO converter library, a single 7494 //element in the XML array will be considered to an object instead of 7495 //a real array. This will handle those cases to ensure that an array is 7496 //always returned. 7497 7498 return Utilities.getArray(participants); 7499 }, 7500 7501 /** 7502 * gets the participant timer counters 7503 * 7504 * @param {String} participantExt Extension of participant. 7505 * @returns {Object} which contains state, startTime, and stateChangeTime fields 7506 */ 7507 getParticipantTimerCounters : function (participantExt) { 7508 var part, participantTimerCounters = {}, idx, participants; 7509 7510 participants = this.getParticipants(); 7511 7512 7513 //Loop through all the participants and find the right participant (based on participantExt) 7514 for(idx=0;idx<participants.length;idx=idx+1) 7515 { 7516 part = participants[idx]; 7517 7518 if (part.mediaAddress === participantExt) 7519 { 7520 participantTimerCounters.startTime= part.startTime; 7521 participantTimerCounters.stateChangeTime= part.stateChangeTime; 7522 participantTimerCounters.state= part.state; 7523 break; 7524 } 7525 } 7526 7527 return participantTimerCounters; 7528 }, 7529 7530 /** 7531 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 7532 * (It is not a CTI Route Point, IVR Port, or the caller) 7533 * 7534 * @param {String} filterExtension used to remove a single extension from the list 7535 * @returns {Array} Participants which is an array of all participants which can be dropped. 7536 */ 7537 getDroppableParticipants: function (filterExtension) { 7538 this.isLoaded(); 7539 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 7540 7541 participants = this.getParticipants(); 7542 7543 if (filterExtension) 7544 { 7545 filterExtensionToRemove = filterExtension; 7546 } 7547 7548 //Loop through all the participants to remove non-agents & remove filterExtension 7549 //We could have removed filterExtension using splice, but we have to iterate through 7550 //the list anyway. 7551 for(idx=0;idx<participants.length;idx=idx+1) 7552 { 7553 part = participants[idx]; 7554 7555 //Skip the filterExtension 7556 if (part.mediaAddress !== filterExtensionToRemove) 7557 { 7558 callStateOk = this._isParticipantStateDroppable(part); 7559 7560 //Remove non-agents & make sure callstate 7561 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 7562 { 7563 droppableParticipants.push(part); 7564 } 7565 } 7566 } 7567 7568 return Utilities.getArray(droppableParticipants); 7569 }, 7570 7571 _isParticipantStateDroppable : function (part) 7572 { 7573 var isParticipantStateDroppable = false; 7574 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 7575 { 7576 isParticipantStateDroppable = true; 7577 } 7578 7579 return isParticipantStateDroppable; 7580 }, 7581 7582 /** 7583 * Is the participant droppable 7584 * 7585 * @param {String} participantExt Extension of participant. 7586 * @returns {Boolean} True is droppable. 7587 */ 7588 isParticipantDroppable : function (participantExt) { 7589 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 7590 7591 droppableParticipants = this.getDroppableParticipants(); 7592 7593 if (droppableParticipants) 7594 { 7595 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 7596 { 7597 part = droppableParticipants[idx]; 7598 7599 if (part.mediaAddress === participantExt) 7600 { 7601 callStateOk = this._isParticipantStateDroppable(part); 7602 7603 //Remove non-agents & make sure callstate 7604 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 7605 { 7606 isDroppable = true; 7607 break; 7608 } 7609 } 7610 } 7611 } 7612 7613 return isDroppable; 7614 }, 7615 7616 /** 7617 * Retrieves a list of media properties from the dialog object. 7618 * @returns {Object} Map of call variables; names mapped to values. 7619 * Variables may include the following:<ul> 7620 * <li>dialedNumber: The number dialed. 7621 * <li>callType: The type of call. Call types include:<ul> 7622 * <li>ACD_IN 7623 * <li>PREROUTE_ACD_IN 7624 * <li>PREROUTE_DIRECT_AGENT 7625 * <li>TRANSFER 7626 * <li>OTHER_IN 7627 * <li>OUT 7628 * <li>AGENT_INSIDE 7629 * <li>CONSULT 7630 * <li>CONFERENCE 7631 * <li>SUPERVISOR_MONITOR 7632 * <li>OUTBOUND 7633 * <li>OUTBOUND_PREVIEW</ul> 7634 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 7635 * <li>wrapUpReason: A description of the call. 7636 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 7637 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 7638 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 7639 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 7640 * <li>BACampaign 7641 * <li>BAAccountNumber 7642 * <li>BAResponse 7643 * <li>BAStatus<ul> 7644 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 7645 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 7646 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 7647 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 7648 * <li>BADialedListID 7649 * <li>BATimeZone 7650 * <li>BABuddyName</ul></ul> 7651 * 7652 */ 7653 getMediaProperties: function () { 7654 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 7655 7656 this.isLoaded(); 7657 7658 // We have to convert to jQuery object to do a proper compare 7659 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 7660 7661 if ((this._lastMediaPropertiesJQuery !== undefined) 7662 && (this._lastMediaPropertiesMap !== undefined) 7663 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 7664 7665 return this._lastMediaPropertiesMap; 7666 } 7667 7668 currentMediaPropertiesMap = {}; 7669 7670 mpData = this.getData().mediaProperties; 7671 7672 if (mpData) { 7673 if (mpData.callvariables && mpData.callvariables.CallVariable) { 7674 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 7675 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 7676 }); 7677 } 7678 7679 jQuery.each(mpData, function (key, value) { 7680 if (key !== 'callvariables') { 7681 currentMediaPropertiesMap[key] = value; 7682 } 7683 }); 7684 } 7685 7686 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 7687 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 7688 7689 return this._lastMediaPropertiesMap; 7690 }, 7691 7692 /** 7693 * Retrieves information about the currently scheduled callback, if any. 7694 * @returns {Object} If no callback has been set, will return undefined. If 7695 * a callback has been set, it will return a map with one or more of the 7696 * following entries, depending on what values have been set. 7697 * callbackTime - the callback time, if it has been set. 7698 * callbackNumber - the callback number, if it has been set. 7699 */ 7700 getCallbackInfo: function() { 7701 this.isLoaded(); 7702 return this.getData().scheduledCallbackInfo; 7703 }, 7704 7705 /** 7706 * @private 7707 * Invoke a request to the server given a content body and handlers. 7708 * 7709 * @param {Object} contentBody 7710 * A JS object containing the body of the action request. 7711 * @param {finesse.interfaces.RequestHandlers} handlers 7712 * An object containing the handlers for the request 7713 */ 7714 _makeRequest: function (contentBody, handlers) { 7715 // Protect against null dereferencing of options allowing its 7716 // (nonexistent) keys to be read as undefined 7717 handlers = handlers || {}; 7718 7719 this.restRequest(this.getRestUrl(), { 7720 method: 'PUT', 7721 success: handlers.success, 7722 error: handlers.error, 7723 content: contentBody 7724 }); 7725 }, 7726 7727 /** 7728 * Invoke a consult call out to a destination. 7729 * 7730 * @param {String} mediaAddress 7731 * The media address of the user performing the consult call. 7732 * @param {String} toAddress 7733 * The destination address of the consult call. 7734 * @param {finesse.interfaces.RequestHandlers} handlers 7735 * An object containing the handlers for the request 7736 */ 7737 makeConsultCall: function (mediaAddress, toAddress, handlers) { 7738 this.isLoaded(); 7739 var contentBody = {}; 7740 contentBody[this.getRestType()] = { 7741 "targetMediaAddress": mediaAddress, 7742 "toAddress": toAddress, 7743 "requestedAction": Dialog.Actions.CONSULT_CALL 7744 }; 7745 this._makeRequest(contentBody, handlers); 7746 return this; // Allow cascading 7747 }, 7748 7749 /** 7750 * Invoke a single step transfer request. 7751 * 7752 * @param {String} mediaAddress 7753 * The media address of the user performing the single step transfer. 7754 * @param {String} toAddress 7755 * The destination address of the single step transfer. 7756 * @param {finesse.interfaces.RequestHandlers} handlers 7757 * An object containing the handlers for the request 7758 */ 7759 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 7760 this.isLoaded(); 7761 var contentBody = {}; 7762 contentBody[this.getRestType()] = { 7763 "targetMediaAddress": mediaAddress, 7764 "toAddress": toAddress, 7765 "requestedAction": Dialog.Actions.TRANSFER_SST 7766 }; 7767 this._makeRequest(contentBody, handlers); 7768 return this; // Allow cascading 7769 }, 7770 7771 /** 7772 * Update this dialog's wrap-up reason. 7773 * 7774 * @param {String} wrapUpReason 7775 * The new wrap-up reason for this dialog 7776 * @param {finesse.interfaces.RequestHandlers} handlers 7777 * An object containing the handlers for the request 7778 */ 7779 updateWrapUpReason: function (wrapUpReason, options) 7780 { 7781 this.isLoaded(); 7782 var mediaProperties = 7783 { 7784 "wrapUpReason": wrapUpReason 7785 }; 7786 7787 options = options || {}; 7788 options.content = {}; 7789 options.content[this.getRestType()] = 7790 { 7791 "mediaProperties": mediaProperties, 7792 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 7793 }; 7794 options.method = "PUT"; 7795 this.restRequest(this.getRestUrl(), options); 7796 7797 return this; 7798 }, 7799 7800 /** 7801 * Invoke a request to server based on the action given. 7802 * @param {String} mediaAddress 7803 * The media address of the user performing the action. 7804 * @param {finesse.restservices.Dialog.Actions} action 7805 * The action string indicating the action to invoke on dialog. 7806 * @param {finesse.interfaces.RequestHandlers} handlers 7807 * An object containing the handlers for the request 7808 */ 7809 requestAction: function (mediaAddress, action, handlers) { 7810 this.isLoaded(); 7811 var contentBody = {}; 7812 contentBody[this.getRestType()] = { 7813 "targetMediaAddress": mediaAddress, 7814 "requestedAction": action 7815 }; 7816 this._makeRequest(contentBody, handlers); 7817 return this; // Allow cascading 7818 }, 7819 7820 /** 7821 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 7822 * 7823 * @param targetMediaAddress is the address to drop 7824 * @param {finesse.interfaces.RequestHandlers} handlers 7825 * An object containing the handlers for the request 7826 */ 7827 dropParticipant: function (targetMediaAddress, handlers) { 7828 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 7829 }, 7830 7831 /** 7832 * Invoke a request to server to send DTMF digit tones. 7833 * @param {String} mediaAddress 7834 * @param {String} action 7835 * The action string indicating the action to invoke on dialog. 7836 * @param {finesse.interfaces.RequestHandlers} handlers 7837 * An object containing the handlers for the request 7838 */ 7839 sendDTMFRequest: function (mediaAddress, handlers, digit) { 7840 this.isLoaded(); 7841 var contentBody = {}; 7842 contentBody[this.getRestType()] = { 7843 "targetMediaAddress": mediaAddress, 7844 "requestedAction": "SEND_DTMF", 7845 "actionParams": { 7846 "ActionParam": { 7847 "name": "dtmfString", 7848 "value": digit 7849 } 7850 } 7851 }; 7852 this._makeRequest(contentBody, handlers); 7853 return this; // Allow cascading 7854 }, 7855 7856 /** 7857 * Invoke a request to server to set the time for a callback. 7858 * @param {String} mediaAddress 7859 * @param {String} callbackTime 7860 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 7861 * (ex: 2013-12-24T23:59) 7862 * @param {finesse.interfaces.RequestHandlers} handlers 7863 * An object containing the handlers for the request 7864 */ 7865 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 7866 this.isLoaded(); 7867 var contentBody = {}; 7868 contentBody[this.getRestType()] = { 7869 "targetMediaAddress": mediaAddress, 7870 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 7871 "actionParams": { 7872 "ActionParam": { 7873 "name": "callbackTime", 7874 "value": callbackTime 7875 } 7876 } 7877 }; 7878 this._makeRequest(contentBody, handlers); 7879 return this; // Allow cascading 7880 }, 7881 7882 /** 7883 * Invoke a request to server to set the number for a callback. 7884 * @param {String} mediaAddress 7885 * @param {String} callbackNumber 7886 * The requested number to call for the callback 7887 * @param {finesse.interfaces.RequestHandlers} handlers 7888 * An object containing the handlers for the request 7889 */ 7890 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 7891 this.isLoaded(); 7892 var contentBody = {}; 7893 contentBody[this.getRestType()] = { 7894 "targetMediaAddress": mediaAddress, 7895 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 7896 "actionParams": { 7897 "ActionParam": { 7898 "name": "callbackNumber", 7899 "value": callbackNumber 7900 } 7901 } 7902 }; 7903 this._makeRequest(contentBody, handlers); 7904 return this; // Allow cascading 7905 }, 7906 7907 /** 7908 * Invoke a request to server to cancel a callback. 7909 * @param {String} mediaAddress 7910 * @param {finesse.interfaces.RequestHandlers} handlers 7911 * An object containing the handlers for the request 7912 */ 7913 cancelCallback: function (mediaAddress, handlers) { 7914 this.isLoaded(); 7915 var contentBody = {}; 7916 contentBody[this.getRestType()] = { 7917 "targetMediaAddress": mediaAddress, 7918 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 7919 }; 7920 this._makeRequest(contentBody, handlers); 7921 return this; // Allow cascading 7922 }, 7923 7924 /** 7925 * Invoke a request to server to reclassify the call type. 7926 * @param {String} mediaAddress 7927 * @param {String} callbackNumber 7928 * The requested number to call for the callback 7929 * @param {finesse.interfaces.RequestHandlers} handlers 7930 * An object containing the handlers for the request 7931 */ 7932 reclassifyCall: function (mediaAddress, classification, handlers) { 7933 this.isLoaded(); 7934 var contentBody = {}; 7935 contentBody[this.getRestType()] = { 7936 "targetMediaAddress": mediaAddress, 7937 "requestedAction": Dialog.Actions.RECLASSIFY, 7938 "actionParams": { 7939 "ActionParam": { 7940 "name": "outboundClassification", 7941 "value": classification 7942 } 7943 } 7944 }; 7945 this._makeRequest(contentBody, handlers); 7946 return this; // Allow cascading 7947 } 7948 7949 7950 }); 7951 7952 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 7953 /** 7954 * Drops the Participant from the Dialog. 7955 */ 7956 DROP: "DROP", 7957 /** 7958 * Answers a Dialog. 7959 */ 7960 ANSWER: "ANSWER", 7961 /** 7962 * Holds the Dialog. 7963 */ 7964 HOLD: "HOLD", 7965 /** 7966 * Barges into a Call Dialog. 7967 */ 7968 BARGE_CALL: "BARGE_CALL", 7969 /** 7970 * Allow as Supervisor to Drop a Participant from the Dialog. 7971 */ 7972 PARTICIPANT_DROP: "PARTICIPANT_DROP", 7973 /** 7974 * Makes a new Call Dialog. 7975 */ 7976 MAKE_CALL: "MAKE_CALL", 7977 /** 7978 * Retrieves a Dialog that is on Hold. 7979 */ 7980 RETRIEVE: "RETRIEVE", 7981 /** 7982 * Sets the time or number for a callback. Can be 7983 * either a new callback, or updating an existing one. 7984 */ 7985 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 7986 /** 7987 * Cancels a callback. 7988 */ 7989 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 7990 /** 7991 * Initiates a Consult Call. 7992 */ 7993 CONSULT_CALL: "CONSULT_CALL", 7994 /** 7995 * Initiates a Transfer of a Dialog. 7996 */ 7997 TRANSFER: "TRANSFER", 7998 /** 7999 * Initiates a Single-Step Transfer of a Dialog. 8000 */ 8001 TRANSFER_SST: "TRANSFER_SST", 8002 /** 8003 * Initiates a Conference of a Dialog. 8004 */ 8005 CONFERENCE: "CONFERENCE", 8006 /** 8007 * Changes classification for a call 8008 */ 8009 RECLASSIFY: "RECLASSIFY", 8010 /** 8011 * Updates data on a Call Dialog. 8012 */ 8013 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 8014 /** 8015 * Initiates a Recording on a Call Dialog. 8016 */ 8017 START_RECORDING : "START_RECORDING", 8018 /** 8019 * Sends DTMF (dialed digits) to a Call Dialog. 8020 */ 8021 DTMF : "SEND_DTMF", 8022 /** 8023 * Accepts a Dialog that is being Previewed. 8024 */ 8025 ACCEPT: "ACCEPT", 8026 /** 8027 * Rejects a Dialog. 8028 */ 8029 REJECT: "REJECT", 8030 /** 8031 * Closes a Dialog. 8032 */ 8033 CLOSE : "CLOSE", 8034 /** 8035 * @class Set of action constants for a Dialog. These should be used for 8036 * {@link finesse.restservices.Dialog#requestAction}. 8037 * @constructs 8038 */ 8039 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8040 }; 8041 8042 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 8043 /** 8044 * Indicates that the call is ringing at a device. 8045 */ 8046 ALERTING: "ALERTING", 8047 /** 8048 * Indicates that the phone is off the hook at a device. 8049 */ 8050 INITIATING: "INITIATING", 8051 /** 8052 * Indicates that the dialog has a least one active participant. 8053 */ 8054 ACTIVE: "ACTIVE", 8055 /** 8056 * Indicates that the dialog has no active participants. 8057 */ 8058 DROPPED: "DROPPED", 8059 /** 8060 * Indicates that the phone is dialing at the device. 8061 */ 8062 INITIATED: "INITIATED", 8063 /** 8064 * Indicates that the dialog has failed. 8065 * @see Dialog.ReasonStates 8066 */ 8067 FAILED: "FAILED", 8068 /** 8069 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 8070 */ 8071 ACCEPTED: "ACCEPTED", 8072 /** 8073 * @class Possible Dialog State constants. 8074 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 8075 * @constructs 8076 */ 8077 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8078 }; 8079 8080 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 8081 /** 8082 * Indicates that an incoming call is ringing on the device. 8083 */ 8084 ALERTING: "ALERTING", 8085 /** 8086 * Indicates that an outgoing call, not yet active, exists on the device. 8087 */ 8088 INITIATING: "INITIATING", 8089 /** 8090 * Indicates that the participant is active on the call. 8091 */ 8092 ACTIVE: "ACTIVE", 8093 /** 8094 * Indicates that the participant has dropped from the call. 8095 */ 8096 DROPPED: "DROPPED", 8097 /** 8098 * Indicates that the participant has held their connection to the call. 8099 */ 8100 HELD: "HELD", 8101 /** 8102 * Indicates that the phone is dialing at a device. 8103 */ 8104 INITIATED: "INITIATED", 8105 /** 8106 * Indicates that the call failed. 8107 * @see Dialog.ReasonStates 8108 */ 8109 FAILED: "FAILED", 8110 /** 8111 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 8112 */ 8113 WRAP_UP: "WRAP_UP", 8114 /** 8115 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 8116 */ 8117 ACCEPTED: "ACCEPTED", 8118 /** 8119 * @class Possible Dialog Participant State constants. 8120 * @constructs 8121 */ 8122 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8123 }; 8124 8125 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 8126 /** 8127 * Dialog was Busy. This will typically be for a Failed Dialog. 8128 */ 8129 BUSY: "BUSY", 8130 /** 8131 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 8132 */ 8133 BAD_DESTINATION: "BAD_DESTINATION", 8134 /** 8135 * All Other Reasons. This will typically be for a Failed Dialog. 8136 */ 8137 OTHER: "OTHER", 8138 /** 8139 * The Device Resource for the Dialog was not available. 8140 */ 8141 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 8142 /** 8143 * @class Possible dialog state reasons code constants. 8144 * @constructs 8145 */ 8146 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8147 }; 8148 8149 window.finesse = window.finesse || {}; 8150 window.finesse.restservices = window.finesse.restservices || {}; 8151 window.finesse.restservices.Dialog = Dialog; 8152 8153 8154 return Dialog; 8155 }); 8156 8157 /** 8158 * JavaScript representation of the Finesse Dialogs collection 8159 * object which contains a list of Dialog objects. 8160 * 8161 * @requires finesse.clientservices.ClientServices 8162 * @requires Class 8163 * @requires finesse.FinesseBase 8164 * @requires finesse.restservices.RestBase 8165 * @requires finesse.restservices.Dialog 8166 */ 8167 /** @private */ 8168 define('restservices/Dialogs',[ 8169 'restservices/RestCollectionBase', 8170 'restservices/Dialog' 8171 ], 8172 function (RestCollectionBase, Dialog) { 8173 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 8174 8175 /** 8176 * @class 8177 * JavaScript representation of a Dialogs collection object. Also exposes 8178 * methods to operate on the object against the server. 8179 * @augments finesse.restservices.RestCollectionBase 8180 * @constructs 8181 * @see finesse.restservices.Dialog 8182 * @example 8183 * _dialogs = _user.getDialogs( { 8184 * onCollectionAdd : _handleDialogAdd, 8185 * onCollectionDelete : _handleDialogDelete, 8186 * onLoad : _handleDialogsLoaded 8187 * }); 8188 * 8189 * _dialogCollection = _dialogs.getCollection(); 8190 * for (var dialogId in _dialogCollection) { 8191 * if (_dialogCollection.hasOwnProperty(dialogId)) { 8192 * _dialog = _dialogCollection[dialogId]; 8193 * etc... 8194 * } 8195 * } 8196 */ 8197 _fakeConstuctor: function () { 8198 /* This is here to hide the real init constructor from the public docs */ 8199 }, 8200 8201 /** 8202 * @private 8203 * @param {Object} options 8204 * An object with the following properties:<ul> 8205 * <li><b>id:</b> The id of the object being constructed</li> 8206 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8207 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8208 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8209 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8210 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8211 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8212 * <li><b>content:</b> {String} Raw string of response</li> 8213 * <li><b>object:</b> {Object} Parsed object of response</li> 8214 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8215 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8216 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8217 * </ul></li> 8218 * </ul></li> 8219 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8220 **/ 8221 init: function (options) { 8222 this._super(options); 8223 }, 8224 8225 /** 8226 * @private 8227 * Gets the REST class for the current object - this is the Dialogs class. 8228 */ 8229 getRestClass: function () { 8230 return Dialogs; 8231 }, 8232 8233 /** 8234 * @private 8235 * Gets the REST class for the objects that make up the collection. - this 8236 * is the Dialog class. 8237 */ 8238 getRestItemClass: function () { 8239 return Dialog; 8240 }, 8241 8242 /** 8243 * @private 8244 * Gets the REST type for the current object - this is a "Dialogs". 8245 */ 8246 getRestType: function () { 8247 return "Dialogs"; 8248 }, 8249 8250 /** 8251 * @private 8252 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 8253 */ 8254 getRestItemType: function () { 8255 return "Dialog"; 8256 }, 8257 8258 /** 8259 * @private 8260 * Override default to indicates that the collection doesn't support making 8261 * requests. 8262 */ 8263 supportsRequests: true, 8264 8265 /** 8266 * @private 8267 * Override default to indicates that the collection subscribes to its objects. 8268 */ 8269 supportsRestItemSubscriptions: true, 8270 8271 /** 8272 * @private 8273 * Create a new Dialog in this collection 8274 * 8275 * @param {String} toAddress 8276 * The to address of the new Dialog 8277 * @param {String} fromAddress 8278 * The from address of the new Dialog 8279 * @param {finesse.interfaces.RequestHandlers} handlers 8280 * An object containing the (optional) handlers for the request. 8281 * @return {finesse.restservices.Dialogs} 8282 * This Dialogs object, to allow cascading. 8283 */ 8284 createNewCallDialog: function (toAddress, fromAddress, handlers) 8285 { 8286 var contentBody = {}; 8287 contentBody[this.getRestItemType()] = { 8288 "requestedAction": "MAKE_CALL", 8289 "toAddress": toAddress, 8290 "fromAddress": fromAddress 8291 }; 8292 8293 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8294 handlers = handlers || {}; 8295 8296 this.restRequest(this.getRestUrl(), { 8297 method: 'POST', 8298 success: handlers.success, 8299 error: handlers.error, 8300 content: contentBody 8301 }); 8302 return this; // Allow cascading 8303 }, 8304 8305 /** 8306 * @private 8307 * Create a new Dialog in this collection as a result of a requested action 8308 * 8309 * @param {String} toAddress 8310 * The to address of the new Dialog 8311 * @param {String} fromAddress 8312 * The from address of the new Dialog 8313 * @param {finesse.restservices.Dialog.Actions} actionType 8314 * The associated action to request for creating this new dialog 8315 * @param {finesse.interfaces.RequestHandlers} handlers 8316 * An object containing the (optional) handlers for the request. 8317 * @return {finesse.restservices.Dialogs} 8318 * This Dialogs object, to allow cascading. 8319 */ 8320 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 8321 { 8322 var contentBody = {}; 8323 this._isLoaded = true; 8324 8325 contentBody[this.getRestItemType()] = { 8326 "requestedAction": actionType, 8327 "toAddress": toAddress, 8328 "fromAddress": fromAddress 8329 }; 8330 8331 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8332 handlers = handlers || {}; 8333 8334 this.restRequest(this.getRestUrl(), { 8335 method: 'POST', 8336 success: handlers.success, 8337 error: handlers.error, 8338 content: contentBody 8339 }); 8340 return this; // Allow cascading 8341 }, 8342 8343 /** 8344 * @private 8345 * Create a new Dialog in this collection as a result of a requested action 8346 * @param {String} fromAddress 8347 * The from address of the new Dialog 8348 * @param {String} toAddress 8349 * The to address of the new Dialog 8350 * @param {finesse.restservices.Dialog.Actions} actionType 8351 * The associated action to request for creating this new dialog 8352 * @param {String} dialogUri 8353 * The associated uri of SUPERVISOR_MONITOR call 8354 * @param {finesse.interfaces.RequestHandlers} handlers 8355 * An object containing the (optional) handlers for the request. 8356 * @return {finesse.restservices.Dialogs} 8357 * This Dialogs object, to allow cascading. 8358 */ 8359 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 8360 this.isLoaded(); 8361 8362 var contentBody = {}; 8363 contentBody[this.getRestItemType()] = { 8364 "fromAddress": fromAddress, 8365 "toAddress": toAddress, 8366 "requestedAction": actionType, 8367 "associatedDialogUri": dialogURI 8368 8369 }; 8370 // (nonexistent) keys to be read as undefined 8371 handlers = handlers || {}; 8372 this.restRequest(this.getRestUrl(), { 8373 method: 'POST', 8374 success: handlers.success, 8375 error: handlers.error, 8376 content: contentBody 8377 }); 8378 return this; // Allow cascading 8379 }, 8380 8381 /** 8382 * Utility method to get the number of dialogs in this collection. 8383 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 8384 * 'SUPERVISOR_MONITOR' from the count. 8385 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 8386 * @return {Number} The number of dialogs in this collection. 8387 */ 8388 getDialogCount: function (excludeSilentMonitor) { 8389 this.isLoaded(); 8390 8391 var dialogId, count = 0; 8392 if (excludeSilentMonitor) { 8393 for (dialogId in this._collection) { 8394 if (this._collection.hasOwnProperty(dialogId)) { 8395 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 8396 count += 1; 8397 } 8398 } 8399 } 8400 8401 return count; 8402 } else { 8403 return this.length; 8404 } 8405 } 8406 8407 }); 8408 8409 window.finesse = window.finesse || {}; 8410 window.finesse.restservices = window.finesse.restservices || {}; 8411 window.finesse.restservices.Dialogs = Dialogs; 8412 8413 return Dialogs; 8414 }); 8415 8416 /** 8417 * JavaScript representation of the Finesse ClientLog object 8418 * 8419 * @requires finesse.clientservices.ClientServices 8420 * @requires Class 8421 * @requires finesse.FinesseBase 8422 * @requires finesse.restservices.RestBase 8423 */ 8424 8425 /** The following comment is to prevent jslint errors about 8426 * using variables before they are defined. 8427 */ 8428 /** @private */ 8429 /*global finesse*/ 8430 8431 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 8432 8433 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 8434 /** 8435 * @private 8436 * Returns whether this object supports transport logs 8437 */ 8438 doNotLog : true, 8439 8440 doNotRefresh: true, 8441 8442 explicitSubscription : true, 8443 8444 /** 8445 * @class 8446 * @private 8447 * JavaScript representation of a ClientLog object. Also exposes methods to operate 8448 * on the object against the server. 8449 * 8450 * @param {Object} options 8451 * An object with the following properties:<ul> 8452 * <li><b>id:</b> The id of the object being constructed</li> 8453 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8454 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8455 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8456 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8457 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8458 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8459 * <li><b>content:</b> {String} Raw string of response</li> 8460 * <li><b>object:</b> {Object} Parsed object of response</li> 8461 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8462 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8463 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8464 * </ul></li> 8465 * </ul></li> 8466 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8467 * @constructs 8468 * @augments finesse.restservices.RestBase 8469 **/ 8470 init: function (options) { 8471 this._super({ 8472 id: "", 8473 data: {clientLog : null}, 8474 onAdd: options.onAdd, 8475 onChange: options.onChange, 8476 onLoad: options.onLoad, 8477 onError: options.onError, 8478 parentObj: options.parentObj 8479 }); 8480 }, 8481 8482 /** 8483 * @private 8484 * Gets the REST class for the current object - this is the ClientLog object. 8485 */ 8486 getRestClass: function () { 8487 return ClientLog; 8488 }, 8489 8490 /** 8491 * @private 8492 * Gets the REST type for the current object - this is a "ClientLog". 8493 */ 8494 getRestType: function () 8495 { 8496 return "ClientLog"; 8497 }, 8498 8499 /** 8500 * @private 8501 * Gets the node path for the current object 8502 * @returns {String} The node path 8503 */ 8504 getXMPPNodePath: function () { 8505 return this.getRestUrl(); 8506 }, 8507 8508 /** 8509 * @private 8510 * Invoke a request to the server given a content body and handlers. 8511 * 8512 * @param {Object} contentBody 8513 * A JS object containing the body of the action request. 8514 * @param {Object} handlers 8515 * An object containing the following (optional) handlers for the request:<ul> 8516 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 8517 * response object as its only parameter:<ul> 8518 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8519 * <li><b>content:</b> {String} Raw string of response</li> 8520 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 8521 * <li>A error callback function for an unsuccessful request to be invoked with the 8522 * error response object as its only parameter:<ul> 8523 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8524 * <li><b>content:</b> {String} Raw string of response</li> 8525 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 8526 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8527 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8528 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8529 * </ul></li> 8530 * </ul> 8531 */ 8532 sendLogs: function (contentBody, handlers) { 8533 // Protect against null dereferencing of options allowing its 8534 // (nonexistent) keys to be read as undefined 8535 handlers = handlers || {}; 8536 8537 this.restRequest(this.getRestUrl(), { 8538 method: 'POST', 8539 //success: handlers.success, 8540 error: handlers.error, 8541 content: contentBody 8542 }); 8543 } 8544 }); 8545 8546 window.finesse = window.finesse || {}; 8547 window.finesse.restservices = window.finesse.restservices || {}; 8548 window.finesse.restservices.ClientLog = ClientLog; 8549 8550 return ClientLog; 8551 }); 8552 8553 /** 8554 * JavaScript representation of the Finesse Queue object 8555 * @requires finesse.clientservices.ClientServices 8556 * @requires Class 8557 * @requires finesse.FinesseBase 8558 * @requires finesse.restservices.RestBase 8559 */ 8560 8561 /** @private */ 8562 define('restservices/Queue',[ 8563 'restservices/RestBase', 8564 'utilities/Utilities' 8565 ], 8566 function (RestBase, Utilities) { 8567 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 8568 8569 /** 8570 * @class 8571 * A Queue is a list of Contacts available to a User for quick dial. 8572 * 8573 * @augments finesse.restservices.RestBase 8574 * @constructs 8575 */ 8576 _fakeConstuctor: function () { 8577 /* This is here to hide the real init constructor from the public docs */ 8578 }, 8579 8580 /** 8581 * @private 8582 * JavaScript representation of a Queue object. Also exposes methods to operate 8583 * on the object against the server. 8584 * 8585 * @constructor 8586 * @param {String} id 8587 * Not required... 8588 * @param {Object} callbacks 8589 * An object containing callbacks for instantiation and runtime 8590 * @param {Function} callbacks.onLoad(this) 8591 * Callback to invoke upon successful instantiation 8592 * @param {Function} callbacks.onLoadError(rsp) 8593 * Callback to invoke on instantiation REST request error 8594 * as passed by finesse.clientservices.ClientServices.ajax() 8595 * { 8596 * status: {Number} The HTTP status code returned 8597 * content: {String} Raw string of response 8598 * object: {Object} Parsed object of response 8599 * error: {Object} Wrapped exception that was caught 8600 * error.errorType: {String} Type of error that was caught 8601 * error.errorMessage: {String} Message associated with error 8602 * } 8603 * @param {Function} callbacks.onChange(this) 8604 * Callback to invoke upon successful update 8605 * @param {Function} callbacks.onError(rsp) 8606 * Callback to invoke on update error (refresh or event) 8607 * as passed by finesse.clientservices.ClientServices.ajax() 8608 * { 8609 * status: {Number} The HTTP status code returned 8610 * content: {String} Raw string of response 8611 * object: {Object} Parsed object of response 8612 * error: {Object} Wrapped exception that was caught 8613 * error.errorType: {String} Type of error that was caught 8614 * error.errorMessage: {String} Message associated with error 8615 * } 8616 * 8617 */ 8618 init: function (id, callbacks, restObj) { 8619 this._super(id, callbacks, restObj); 8620 }, 8621 8622 /** 8623 * @private 8624 * Gets the REST class for the current object - this is the Queue object. 8625 */ 8626 getRestClass: function () { 8627 return Queue; 8628 }, 8629 8630 /** 8631 * @private 8632 * Gets the REST type for the current object - this is a "Queue". 8633 */ 8634 getRestType: function () { 8635 return "Queue"; 8636 }, 8637 8638 /** 8639 * @private 8640 * Returns whether this object supports subscriptions 8641 */ 8642 supportsSubscriptions: function () { 8643 return true; 8644 }, 8645 8646 /** 8647 * @private 8648 * Specifies whether this object's subscriptions need to be explicitly requested 8649 */ 8650 explicitSubscription: true, 8651 8652 /** 8653 * @private 8654 * Gets the node path for the current object - this is the team Users node 8655 * @returns {String} The node path 8656 */ 8657 getXMPPNodePath: function () { 8658 return this.getRestUrl(); 8659 }, 8660 8661 /** 8662 * Getter for the queue id 8663 * @returns {String} 8664 * The id of the Queue 8665 */ 8666 getId: function () { 8667 this.isLoaded(); 8668 return this._id; 8669 }, 8670 8671 /** 8672 * Getter for the queue name 8673 * @returns {String} 8674 * The name of the Queue 8675 */ 8676 getName: function () { 8677 this.isLoaded(); 8678 return this.getData().name; 8679 }, 8680 8681 /** 8682 * Getter for the queue statistics. 8683 * Supported statistics include:<br> 8684 * - callsInQueue<br> 8685 * - startTimeOfLongestCallInQueue<br> 8686 * <br> 8687 * These statistics can be accessed via dot notation:<br> 8688 * i.e.: getStatistics().callsInQueue 8689 * @returns {Object} 8690 * The Object with different statistics as properties. 8691 */ 8692 getStatistics: function () { 8693 this.isLoaded(); 8694 return this.getData().statistics; 8695 }, 8696 8697 /** 8698 * Parses a uriString to retrieve the id portion 8699 * @param {String} uriString 8700 * @return {String} id 8701 */ 8702 _parseIdFromUriString : function (uriString) { 8703 return Utilities.getId(uriString); 8704 } 8705 8706 }); 8707 8708 window.finesse = window.finesse || {}; 8709 window.finesse.restservices = window.finesse.restservices || {}; 8710 window.finesse.restservices.Queue = Queue; 8711 8712 return Queue; 8713 }); 8714 8715 /** 8716 * JavaScript representation of the Finesse Queues collection 8717 * object which contains a list of Queue objects. 8718 * @requires finesse.clientservices.ClientServices 8719 * @requires Class 8720 * @requires finesse.FinesseBase 8721 * @requires finesse.restservices.RestBase 8722 * @requires finesse.restservices.RestCollectionBase 8723 */ 8724 8725 /** 8726 * @class 8727 * JavaScript representation of a Queues collection object. 8728 * 8729 * @constructor 8730 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 8731 */ 8732 8733 /** @private */ 8734 define('restservices/Queues',[ 8735 'restservices/RestCollectionBase', 8736 'restservices/Queue' 8737 ], 8738 function (RestCollectionBase, Queue) { 8739 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 8740 8741 /** 8742 * @class 8743 * JavaScript representation of a Queues collection object. 8744 * @augments finesse.restservices.RestCollectionBase 8745 * @constructs 8746 * @see finesse.restservices.Queue 8747 * @example 8748 * _queues = _user.getQueues( { 8749 * onCollectionAdd : _handleQueueAdd, 8750 * onCollectionDelete : _handleQueueDelete, 8751 * onLoad : _handleQueuesLoaded 8752 * }); 8753 * 8754 * _queueCollection = _queues.getCollection(); 8755 * for (var queueId in _queueCollection) { 8756 * if (_queueCollection.hasOwnProperty(queueId)) { 8757 * _queue = _queueCollection[queueId]; 8758 * etc... 8759 * } 8760 * } 8761 */ 8762 _fakeConstuctor: function () { 8763 /* This is here to hide the real init constructor from the public docs */ 8764 }, 8765 8766 /** 8767 * @private 8768 * JavaScript representation of a Queues object. Also exposes 8769 * methods to operate on the object against the server. 8770 * 8771 * @param {Object} options 8772 * An object with the following properties:<ul> 8773 * <li><b>id:</b> The id of the object being constructed</li> 8774 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8775 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8776 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8777 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8778 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8779 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8780 * <li><b>content:</b> {String} Raw string of response</li> 8781 * <li><b>object:</b> {Object} Parsed object of response</li> 8782 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8783 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8784 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8785 * </ul></li> 8786 * </ul></li> 8787 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8788 **/ 8789 init: function (options) { 8790 this._super(options); 8791 }, 8792 8793 /** 8794 * @private 8795 * Gets xmpp node path. 8796 */ 8797 getXMPPNodePath: function () { 8798 return this.getRestUrl(); 8799 }, 8800 8801 /** 8802 * @private 8803 * Gets the REST class for the current object - this is the Queues class. 8804 */ 8805 getRestClass: function () { 8806 return Queues; 8807 }, 8808 8809 /** 8810 * @private 8811 * Gets the REST class for the objects that make up the collection. - this 8812 * is the Queue class. 8813 */ 8814 getRestItemClass: function () { 8815 return Queue; 8816 }, 8817 8818 /** 8819 * @private 8820 * Gets the REST type for the current object - this is a "Queues". 8821 */ 8822 getRestType: function () { 8823 return "Queues"; 8824 }, 8825 8826 /** 8827 * @private 8828 * Gets the REST type for the objects that make up the collection - this is "Queue". 8829 */ 8830 getRestItemType: function () { 8831 return "Queue"; 8832 }, 8833 8834 explicitSubscription: true, 8835 8836 handlesItemRefresh: true 8837 }); 8838 8839 window.finesse = window.finesse || {}; 8840 window.finesse.restservices = window.finesse.restservices || {}; 8841 window.finesse.restservices.Queues = Queues; 8842 8843 return Queues; 8844 }); 8845 8846 /** 8847 * JavaScript representation of the Finesse WrapUpReason object. 8848 * 8849 * @requires finesse.clientservices.ClientServices 8850 * @requires Class 8851 * @requires finesse.FinesseBase 8852 * @requires finesse.restservices.RestBase 8853 */ 8854 8855 /** @private */ 8856 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 8857 8858 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 8859 8860 /** 8861 * @class 8862 * A WrapUpReason is a code and description identifying a particular reason that a 8863 * User is in WORK (WrapUp) mode. 8864 * 8865 * @augments finesse.restservices.RestBase 8866 * @see finesse.restservices.User 8867 * @see finesse.restservices.User.States#WORK 8868 * @constructs 8869 */ 8870 _fakeConstuctor: function () { 8871 /* This is here to hide the real init constructor from the public docs */ 8872 }, 8873 8874 /** 8875 * @private 8876 * JavaScript representation of a WrapUpReason object. Also exposes 8877 * methods to operate on the object against the server. 8878 * 8879 * @param {Object} options 8880 * An object with the following properties:<ul> 8881 * <li><b>id:</b> The id of the object being constructed</li> 8882 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8883 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8884 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8885 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8886 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8887 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8888 * <li><b>content:</b> {String} Raw string of response</li> 8889 * <li><b>object:</b> {Object} Parsed object of response</li> 8890 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8891 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8892 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8893 * </ul></li> 8894 * </ul></li> 8895 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8896 **/ 8897 init: function (options) { 8898 this._super(options); 8899 }, 8900 8901 /** 8902 * @private 8903 * Gets the REST class for the current object - this is the WrapUpReason class. 8904 * @returns {Object} The WrapUpReason class. 8905 */ 8906 getRestClass: function () { 8907 return WrapUpReason; 8908 }, 8909 8910 /** 8911 * @private 8912 * Gets the REST type for the current object - this is a "WrapUpReason". 8913 * @returns {String} The WrapUpReason string. 8914 */ 8915 getRestType: function () { 8916 return "WrapUpReason"; 8917 }, 8918 8919 /** 8920 * @private 8921 * Gets the REST type for the current object - this is a "WrapUpReasons". 8922 * @returns {String} The WrapUpReasons string. 8923 */ 8924 getParentRestType: function () { 8925 return "WrapUpReasons"; 8926 }, 8927 8928 /** 8929 * @private 8930 * Override default to indicate that this object doesn't support making 8931 * requests. 8932 */ 8933 supportsRequests: false, 8934 8935 /** 8936 * @private 8937 * Override default to indicate that this object doesn't support subscriptions. 8938 */ 8939 supportsSubscriptions: false, 8940 8941 /** 8942 * Getter for the label. 8943 * @returns {String} The label. 8944 */ 8945 getLabel: function () { 8946 this.isLoaded(); 8947 return this.getData().label; 8948 }, 8949 8950 /** 8951 * @private 8952 * Getter for the forAll flag. 8953 * @returns {Boolean} True if global. 8954 */ 8955 getForAll: function () { 8956 this.isLoaded(); 8957 return this.getData().forAll; 8958 }, 8959 8960 /** 8961 * @private 8962 * Getter for the Uri value. 8963 * @returns {String} The Uri. 8964 */ 8965 getUri: function () { 8966 this.isLoaded(); 8967 return this.getData().uri; 8968 } 8969 }); 8970 8971 window.finesse = window.finesse || {}; 8972 window.finesse.restservices = window.finesse.restservices || {}; 8973 window.finesse.restservices.WrapUpReason = WrapUpReason; 8974 8975 return WrapUpReason; 8976 }); 8977 8978 /** 8979 * JavaScript representation of the Finesse WrapUpReasons collection 8980 * object which contains a list of WrapUpReason objects. 8981 * 8982 * @requires finesse.clientservices.ClientServices 8983 * @requires Class 8984 * @requires finesse.FinesseBase 8985 * @requires finesse.restservices.RestBase 8986 * @requires finesse.restservices.Dialog 8987 * @requires finesse.restservices.RestCollectionBase 8988 */ 8989 8990 /** @private */ 8991 define('restservices/WrapUpReasons',[ 8992 'restservices/RestCollectionBase', 8993 'restservices/WrapUpReason' 8994 ], 8995 function (RestCollectionBase, WrapUpReason) { 8996 8997 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 8998 8999 /** 9000 * @class 9001 * JavaScript representation of a WrapUpReasons collection object. 9002 * @augments finesse.restservices.RestCollectionBase 9003 * @constructs 9004 * @see finesse.restservices.WrapUpReason 9005 * @example 9006 * _wrapUpReasons = _user.getWrapUpReasons ( { 9007 * onCollectionAdd : _handleWrapUpReasonAdd, 9008 * onCollectionDelete : _handleWrapUpReasonDelete, 9009 * onLoad : _handleWrapUpReasonsLoaded 9010 * }); 9011 * 9012 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 9013 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 9014 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 9015 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 9016 * etc... 9017 * } 9018 * } 9019 */ 9020 _fakeConstuctor: function () { 9021 /* This is here to hide the real init constructor from the public docs */ 9022 }, 9023 9024 /** 9025 * @private 9026 * JavaScript representation of a WrapUpReasons collection object. Also exposes 9027 * methods to operate on the object against the server. 9028 * 9029 * @param {Object} options 9030 * An object with the following properties:<ul> 9031 * <li><b>id:</b> The id of the object being constructed</li> 9032 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9033 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9034 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9035 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9036 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9037 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9038 * <li><b>content:</b> {String} Raw string of response</li> 9039 * <li><b>object:</b> {Object} Parsed object of response</li> 9040 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9041 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9042 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9043 * </ul></li> 9044 * </ul></li> 9045 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9046 **/ 9047 init: function (options) { 9048 this._super(options); 9049 }, 9050 9051 /** 9052 * @private 9053 * Gets the REST class for the current object - this is the WrapUpReasons class. 9054 */ 9055 getRestClass: function () { 9056 return WrapUpReasons; 9057 }, 9058 9059 /** 9060 * @private 9061 * Gets the REST class for the objects that make up the collection. - this 9062 * is the WrapUpReason class. 9063 */ 9064 getRestItemClass: function () { 9065 return WrapUpReason; 9066 }, 9067 9068 /** 9069 * @private 9070 * Gets the REST type for the current object - this is a "WrapUpReasons". 9071 */ 9072 getRestType: function () { 9073 return "WrapUpReasons"; 9074 }, 9075 9076 /** 9077 * @private 9078 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 9079 */ 9080 getRestItemType: function () { 9081 return "WrapUpReason"; 9082 }, 9083 9084 /** 9085 * @private 9086 * Override default to indicates that the collection supports making 9087 * requests. 9088 */ 9089 supportsRequests: true, 9090 9091 /** 9092 * @private 9093 * Override default to indicate that this object doesn't support subscriptions. 9094 */ 9095 supportsRestItemSubscriptions: false, 9096 9097 /** 9098 * @private 9099 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 9100 * 9101 * @returns {finesse.restservices.WrapUpReasons} 9102 * This ReadyReasonCodes object to allow cascading. 9103 */ 9104 get: function () { 9105 // set loaded to false so it will rebuild the collection after the get 9106 this._loaded = false; 9107 // reset collection 9108 this._collection = {}; 9109 // perform get 9110 this._synchronize(); 9111 return this; 9112 } 9113 9114 }); 9115 9116 window.finesse = window.finesse || {}; 9117 window.finesse.restservices = window.finesse.restservices || {}; 9118 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 9119 9120 return WrapUpReasons; 9121 }); 9122 9123 /** 9124 * JavaScript representation of the Finesse Contact object. 9125 * @requires finesse.clientservices.ClientServices 9126 * @requires Class 9127 * @requires finesse.FinesseBase 9128 * @requires finesse.restservices.RestBase 9129 */ 9130 /** @private */ 9131 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 9132 9133 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 9134 9135 /** 9136 * @class 9137 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 9138 * a Phone Number, and a Description. 9139 * 9140 * @augments finesse.restservices.RestBase 9141 * @see finesse.restservices.PhoneBook 9142 * @constructs 9143 */ 9144 _fakeConstuctor: function () { 9145 /* This is here to hide the real init constructor from the public docs */ 9146 }, 9147 9148 /** 9149 * @private 9150 * @param {Object} options 9151 * An object with the following properties:<ul> 9152 * <li><b>id:</b> The id of the object being constructed</li> 9153 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9154 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9155 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9156 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9157 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9158 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9159 * <li><b>content:</b> {String} Raw string of response</li> 9160 * <li><b>object:</b> {Object} Parsed object of response</li> 9161 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9162 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9163 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9164 * </ul></li> 9165 * </ul></li> 9166 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9167 **/ 9168 init: function (options) { 9169 this._super(options); 9170 }, 9171 9172 /** 9173 * @private 9174 * Gets the REST class for the current object - this is the Contact class. 9175 * @returns {Object} The Contact class. 9176 */ 9177 getRestClass: function () { 9178 return Contact; 9179 }, 9180 9181 /** 9182 * @private 9183 * Gets the REST type for the current object - this is a "Contact". 9184 * @returns {String} The Contact string. 9185 */ 9186 getRestType: function () { 9187 return "Contact"; 9188 }, 9189 9190 /** 9191 * @private 9192 * Override default to indicate that this object doesn't support making 9193 * requests. 9194 */ 9195 supportsRequests: false, 9196 9197 /** 9198 * @private 9199 * Override default to indicate that this object doesn't support subscriptions. 9200 */ 9201 supportsSubscriptions: false, 9202 9203 /** 9204 * Getter for the firstName. 9205 * @returns {String} The firstName. 9206 */ 9207 getFirstName: function () { 9208 this.isLoaded(); 9209 return this.getData().firstName; 9210 }, 9211 9212 /** 9213 * Getter for the lastName. 9214 * @returns {String} The lastName. 9215 */ 9216 getLastName: function () { 9217 this.isLoaded(); 9218 return this.getData().lastName; 9219 }, 9220 9221 /** 9222 * Getter for the phoneNumber. 9223 * @returns {String} The phoneNumber. 9224 */ 9225 getPhoneNumber: function () { 9226 this.isLoaded(); 9227 return this.getData().phoneNumber; 9228 }, 9229 9230 /** 9231 * Getter for the description. 9232 * @returns {String} The description. 9233 */ 9234 getDescription: function () { 9235 this.isLoaded(); 9236 return this.getData().description; 9237 }, 9238 9239 /** @private */ 9240 createPutSuccessHandler: function(contact, contentBody, successHandler){ 9241 return function (rsp) { 9242 // Update internal structure based on response. Here we 9243 // inject the contentBody from the PUT request into the 9244 // rsp.object element to mimic a GET as a way to take 9245 // advantage of the existing _processResponse method. 9246 rsp.object = contentBody; 9247 contact._processResponse(rsp); 9248 9249 //Remove the injected Contact object before cascading response 9250 rsp.object = {}; 9251 9252 //cascade response back to consumer's response handler 9253 successHandler(rsp); 9254 }; 9255 }, 9256 9257 /** @private */ 9258 createPostSuccessHandler: function (contact, contentBody, successHandler) { 9259 return function (rsp) { 9260 rsp.object = contentBody; 9261 contact._processResponse(rsp); 9262 9263 //Remove the injected Contact object before cascading response 9264 rsp.object = {}; 9265 9266 //cascade response back to consumer's response handler 9267 successHandler(rsp); 9268 }; 9269 }, 9270 9271 /** 9272 * Add 9273 * @private 9274 */ 9275 add: function (newValues, handlers) { 9276 // this.isLoaded(); 9277 var contentBody = {}; 9278 9279 contentBody[this.getRestType()] = { 9280 "firstName": newValues.firstName, 9281 "lastName": newValues.lastName, 9282 "phoneNumber": newValues.phoneNumber, 9283 "description": newValues.description 9284 }; 9285 9286 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9287 handlers = handlers || {}; 9288 9289 this.restRequest(this.getRestUrl(), { 9290 method: 'POST', 9291 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9292 error: handlers.error, 9293 content: contentBody 9294 }); 9295 9296 return this; // Allow cascading 9297 }, 9298 9299 /** 9300 * Update 9301 * @private 9302 */ 9303 update: function (newValues, handlers) { 9304 this.isLoaded(); 9305 var contentBody = {}; 9306 9307 contentBody[this.getRestType()] = { 9308 "uri": this.getId(), 9309 "firstName": newValues.firstName, 9310 "lastName": newValues.lastName, 9311 "phoneNumber": newValues.phoneNumber, 9312 "description": newValues.description 9313 }; 9314 9315 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9316 handlers = handlers || {}; 9317 9318 this.restRequest(this.getRestUrl(), { 9319 method: 'PUT', 9320 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9321 error: handlers.error, 9322 content: contentBody 9323 }); 9324 9325 return this; // Allow cascading 9326 }, 9327 9328 9329 /** 9330 * Delete 9331 * @private 9332 */ 9333 "delete": function ( handlers) { 9334 this.isLoaded(); 9335 9336 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9337 handlers = handlers || {}; 9338 9339 this.restRequest(this.getRestUrl(), { 9340 method: 'DELETE', 9341 success: this.createPutSuccessHandler(this, {}, handlers.success), 9342 error: handlers.error, 9343 content: undefined 9344 }); 9345 9346 return this; // Allow cascading 9347 } 9348 }); 9349 9350 window.finesse = window.finesse || {}; 9351 window.finesse.restservices = window.finesse.restservices || {}; 9352 window.finesse.restservices.Contact = Contact; 9353 9354 return Contact; 9355 }); 9356 9357 /** 9358 * JavaScript representation of the Finesse Contacts collection 9359 * object which contains a list of Contact objects. 9360 * 9361 * @requires finesse.clientservices.ClientServices 9362 * @requires Class 9363 * @requires finesse.FinesseBase 9364 * @requires finesse.restservices.RestBase 9365 * @requires finesse.restservices.Dialog 9366 * @requires finesse.restservices.RestCollectionBase 9367 */ 9368 /** @private */ 9369 define('restservices/Contacts',[ 9370 'restservices/RestCollectionBase', 9371 'restservices/Contact' 9372 ], 9373 function (RestCollectionBase, Contact) { 9374 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 9375 9376 /** 9377 * @class 9378 * JavaScript representation of a Contacts collection object. Also exposes 9379 * methods to operate on the object against the server. 9380 * @augments finesse.restservices.RestCollectionBase 9381 * @constructs 9382 * @see finesse.restservices.Contact 9383 * @see finesse.restservices.PhoneBook 9384 * @example 9385 * _contacts = _phonebook.getContacts( { 9386 * onCollectionAdd : _handleContactAdd, 9387 * onCollectionDelete : _handleContactDelete, 9388 * onLoad : _handleContactsLoaded 9389 * }); 9390 * 9391 * _contactCollection = _contacts.getCollection(); 9392 * for (var contactId in _contactCollection) { 9393 * if (_contactCollection.hasOwnProperty(contactId)) { 9394 * _contact = _contactCollection[contactId]; 9395 * etc... 9396 * } 9397 * } 9398 */ 9399 _fakeConstuctor: function () { 9400 /* This is here to hide the real init constructor from the public docs */ 9401 }, 9402 9403 /** 9404 * @private 9405 * JavaScript representation of a Contacts collection object. Also exposes 9406 * methods to operate on the object against the server. 9407 * 9408 * @param {Object} options 9409 * An object with the following properties:<ul> 9410 * <li><b>id:</b> The id of the object being constructed</li> 9411 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9412 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9413 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9414 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9415 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9416 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9417 * <li><b>content:</b> {String} Raw string of response</li> 9418 * <li><b>object:</b> {Object} Parsed object of response</li> 9419 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9420 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9421 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9422 * </ul></li> 9423 * </ul></li> 9424 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9425 **/ 9426 init: function (options) { 9427 this._super(options); 9428 }, 9429 9430 /** 9431 * @private 9432 * Gets the REST class for the current object - this is the Contacts class. 9433 */ 9434 getRestClass: function () { 9435 return Contacts; 9436 }, 9437 9438 /** 9439 * @private 9440 * Gets the REST class for the objects that make up the collection. - this 9441 * is the Contact class. 9442 */ 9443 getRestItemClass: function () { 9444 return Contact; 9445 }, 9446 9447 /** 9448 * @private 9449 * Gets the REST type for the current object - this is a "Contacts". 9450 */ 9451 getRestType: function () { 9452 return "Contacts"; 9453 }, 9454 9455 /** 9456 * @private 9457 * Gets the REST type for the objects that make up the collection - this is "Contacts". 9458 */ 9459 getRestItemType: function () { 9460 return "Contact"; 9461 }, 9462 9463 /** 9464 * @private 9465 * Override default to indicates that the collection supports making 9466 * requests. 9467 */ 9468 supportsRequests: true, 9469 9470 /** 9471 * @private 9472 * Override default to indicates that the collection subscribes to its objects. 9473 */ 9474 supportsRestItemSubscriptions: false, 9475 9476 /** 9477 * @private 9478 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 9479 * 9480 * @returns {finesse.restservices.Contacts} 9481 * This Contacts object, to allow cascading. 9482 */ 9483 get: function () { 9484 // set loaded to false so it will rebuild the collection after the get 9485 this._loaded = false; 9486 // reset collection 9487 this._collection = {}; 9488 // perform get 9489 this._synchronize(); 9490 return this; 9491 } 9492 9493 }); 9494 9495 window.finesse = window.finesse || {}; 9496 window.finesse.restservices = window.finesse.restservices || {}; 9497 window.finesse.restservices.Contacts = Contacts; 9498 9499 9500 return Contacts; 9501 }); 9502 9503 /** 9504 * JavaScript representation of the Finesse PhoneBook object. 9505 * 9506 * @requires finesse.clientservices.ClientServices 9507 * @requires Class 9508 * @requires finesse.FinesseBase 9509 * @requires finesse.restservices.RestBase 9510 */ 9511 9512 /** @private */ 9513 define('restservices/PhoneBook',[ 9514 'restservices/RestBase', 9515 'restservices/Contacts' 9516 ], 9517 function (RestBase, Contacts) { 9518 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 9519 9520 _contacts: null, 9521 9522 /** 9523 * @class 9524 * A PhoneBook is a list of Contacts available to a User for quick dial. 9525 * 9526 * @augments finesse.restservices.RestBase 9527 * @see finesse.restservices.Contacts 9528 * @constructs 9529 */ 9530 _fakeConstuctor: function () { 9531 /* This is here to hide the real init constructor from the public docs */ 9532 }, 9533 9534 /** 9535 * @private 9536 * JavaScript representation of a PhoneBook object. Also exposes 9537 * methods to operate on the object against the server. 9538 * 9539 * @param {Object} options 9540 * An object with the following properties:<ul> 9541 * <li><b>id:</b> The id of the object being constructed</li> 9542 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9543 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9544 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9545 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9546 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9547 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9548 * <li><b>content:</b> {String} Raw string of response</li> 9549 * <li><b>object:</b> {Object} Parsed object of response</li> 9550 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9551 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9552 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9553 * </ul></li> 9554 * </ul></li> 9555 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9556 **/ 9557 init: function (options) { 9558 this._super(options); 9559 }, 9560 9561 /** 9562 * @private 9563 * Gets the REST class for the current object - this is the PhoneBook class. 9564 * @returns {Object} The PhoneBook class. 9565 */ 9566 getRestClass: function () { 9567 return PhoneBook; 9568 }, 9569 9570 /** 9571 * @private 9572 * Gets the REST type for the current object - this is a "PhoneBook". 9573 * @returns {String} The PhoneBook string. 9574 */ 9575 getRestType: function () { 9576 return "PhoneBook"; 9577 }, 9578 9579 /** 9580 * @private 9581 * Override default to indicate that this object doesn't support making 9582 * requests. 9583 */ 9584 supportsRequests: false, 9585 9586 /** 9587 * @private 9588 * Override default to indicate that this object doesn't support subscriptions. 9589 */ 9590 supportsSubscriptions: false, 9591 9592 /** 9593 * Getter for the name of the Phone Book. 9594 * @returns {String} The name. 9595 */ 9596 getName: function () { 9597 this.isLoaded(); 9598 return this.getData().name; 9599 }, 9600 9601 /** 9602 * Getter for the type flag. 9603 * @returns {String} The type. 9604 */ 9605 getType: function () { 9606 this.isLoaded(); 9607 return this.getData().type; 9608 }, 9609 9610 /** 9611 * @private 9612 * Getter for the Uri value. 9613 * @returns {String} The Uri. 9614 */ 9615 getUri: function () { 9616 this.isLoaded(); 9617 return this.getData().uri; 9618 }, 9619 9620 /** 9621 * Getter for a Contacts collection object that is associated with PhoneBook. 9622 * @param {finesse.interfaces.RequestHandlers} handlers 9623 * An object containing the handlers for the request 9624 * @returns {finesse.restservices.Contacts} 9625 * A Contacts collection object. 9626 */ 9627 getContacts: function (callbacks) { 9628 var options = callbacks || {}; 9629 options.parentObj = this; 9630 this.isLoaded(); 9631 9632 if (this._contacts === null) { 9633 this._contacts = new Contacts(options); 9634 } 9635 9636 return this._contacts; 9637 }, 9638 9639 /** 9640 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 9641 * @returns {String} uri to contacts 9642 * or {finesse.restservices.Contacts} collection 9643 */ 9644 getEmbeddedContacts: function(){ 9645 this.isLoaded(); 9646 return this.getData().contacts; 9647 }, 9648 9649 /** @private */ 9650 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 9651 return function (rsp) { 9652 // Update internal structure based on response. Here we 9653 // inject the contentBody from the PUT request into the 9654 // rsp.object element to mimic a GET as a way to take 9655 // advantage of the existing _processResponse method. 9656 rsp.object = contentBody; 9657 phonebook._processResponse(rsp); 9658 9659 //Remove the injected PhoneBook object before cascading response 9660 rsp.object = {}; 9661 9662 //cascade response back to consumer's response handler 9663 successHandler(rsp); 9664 }; 9665 }, 9666 9667 /** @private */ 9668 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 9669 return function (rsp) { 9670 rsp.object = contentBody; 9671 phonebook._processResponse(rsp); 9672 9673 //Remove the injected PhoneBook object before cascading response 9674 rsp.object = {}; 9675 9676 //cascade response back to consumer's response handler 9677 successHandler(rsp); 9678 }; 9679 }, 9680 9681 /** 9682 * @private 9683 * Add a PhoneBook. 9684 * @param {Object} newValues 9685 * @param {String} newValues.name Name of PhoneBook 9686 * @param {String} newValues.type Type of PhoneBook 9687 * @param {finesse.interfaces.RequestHandlers} handlers 9688 * An object containing the handlers for the request 9689 * @returns {finesse.restservices.PhoneBook} 9690 * This PhoneBook object, to allow cascading 9691 */ 9692 add: function (newValues, handlers) { 9693 // this.isLoaded(); 9694 var contentBody = {}; 9695 9696 contentBody[this.getRestType()] = { 9697 "name": newValues.name, 9698 "type": newValues.type 9699 }; 9700 9701 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9702 handlers = handlers || {}; 9703 9704 this.restRequest(this.getRestUrl(), { 9705 method: 'POST', 9706 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9707 error: handlers.error, 9708 content: contentBody 9709 }); 9710 9711 return this; // Allow cascading 9712 }, 9713 9714 /** 9715 * @private 9716 * Update a PhoneBook. 9717 * @param {Object} newValues 9718 * @param {String} newValues.name Name of PhoneBook 9719 * @param {String} newValues.type Type of PhoneBook 9720 * @param {finesse.interfaces.RequestHandlers} handlers 9721 * An object containing the handlers for the request 9722 * @returns {finesse.restservices.PhoneBook} 9723 * This PhoneBook object, to allow cascading 9724 */ 9725 update: function (newValues, handlers) { 9726 this.isLoaded(); 9727 var contentBody = {}; 9728 9729 contentBody[this.getRestType()] = { 9730 "uri": this.getId(), 9731 "name": newValues.name, 9732 "type": newValues.type 9733 }; 9734 9735 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9736 handlers = handlers || {}; 9737 9738 this.restRequest(this.getRestUrl(), { 9739 method: 'PUT', 9740 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9741 error: handlers.error, 9742 content: contentBody 9743 }); 9744 9745 return this; // Allow cascading 9746 }, 9747 9748 9749 /** 9750 * Delete a PhoneBook. 9751 * @param {finesse.interfaces.RequestHandlers} handlers 9752 * An object containing the handlers for the request 9753 * @returns {finesse.restservices.PhoneBook} 9754 * This PhoneBook object, to allow cascading 9755 */ 9756 "delete": function ( handlers) { 9757 this.isLoaded(); 9758 9759 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9760 handlers = handlers || {}; 9761 9762 this.restRequest(this.getRestUrl(), { 9763 method: 'DELETE', 9764 success: this.createPutSuccessHandler(this, {}, handlers.success), 9765 error: handlers.error, 9766 content: undefined 9767 }); 9768 9769 return this; // Allow cascading 9770 } 9771 9772 9773 9774 }); 9775 9776 window.finesse = window.finesse || {}; 9777 window.finesse.restservices = window.finesse.restservices || {}; 9778 window.finesse.restservices.PhoneBook = PhoneBook; 9779 9780 return PhoneBook; 9781 }); 9782 9783 /** 9784 * JavaScript representation of the Finesse PhoneBooks collection 9785 * object which contains a list of PhoneBook objects. 9786 * 9787 * @requires finesse.clientservices.ClientServices 9788 * @requires Class 9789 * @requires finesse.FinesseBase 9790 * @requires finesse.restservices.RestBase 9791 * @requires finesse.restservices.Dialog 9792 * @requires finesse.restservices.RestCollectionBase 9793 */ 9794 /** @private */ 9795 define('restservices/PhoneBooks',[ 9796 'restservices/RestCollectionBase', 9797 'restservices/PhoneBook' 9798 ], 9799 function (RestCollectionBase, PhoneBook) { 9800 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 9801 9802 /** 9803 * @class 9804 * JavaScript representation of a PhoneBooks collection object. 9805 * @augments finesse.restservices.RestCollectionBase 9806 * @constructs 9807 * @see finesse.restservices.PhoneBook 9808 * @see finesse.restservices.Contacts 9809 * @see finesse.restservices.Contact 9810 * @example 9811 * _phoneBooks = _user.getPhoneBooks( { 9812 * onCollectionAdd : _handlePhoneBookAdd, 9813 * onCollectionDelete : _handlePhoneBookDelete, 9814 * onLoad : _handlePhoneBooksLoaded 9815 * }); 9816 * 9817 * _phoneBookCollection = _phoneBooks.getCollection(); 9818 * for (var phoneBookId in _phoneBookCollection) { 9819 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 9820 * _phoneBook = _phoneBookCollection[phoneBookId]; 9821 * etc... 9822 * } 9823 * } 9824 */ 9825 _fakeConstuctor: function () { 9826 /* This is here to hide the real init constructor from the public docs */ 9827 }, 9828 9829 /** 9830 * @private 9831 * 9832 * @param {Object} options 9833 * An object with the following properties:<ul> 9834 * <li><b>id:</b> The id of the object being constructed</li> 9835 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9836 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9837 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9838 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9839 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9840 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9841 * <li><b>content:</b> {String} Raw string of response</li> 9842 * <li><b>object:</b> {Object} Parsed object of response</li> 9843 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9844 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9845 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9846 * </ul></li> 9847 * </ul></li> 9848 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9849 **/ 9850 init: function (options) { 9851 this._super(options); 9852 }, 9853 9854 /** 9855 * @private 9856 * Gets the REST class for the current object - this is the PhoneBooks class. 9857 */ 9858 getRestClass: function () { 9859 return PhoneBooks; 9860 }, 9861 9862 /** 9863 * @private 9864 * Gets the REST class for the objects that make up the collection. - this 9865 * is the PhoneBook class. 9866 */ 9867 getRestItemClass: function () { 9868 return PhoneBook; 9869 }, 9870 9871 /** 9872 * @private 9873 * Gets the REST type for the current object - this is a "PhoneBooks". 9874 */ 9875 getRestType: function () { 9876 return "PhoneBooks"; 9877 }, 9878 9879 /** 9880 * @private 9881 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 9882 */ 9883 getRestItemType: function () { 9884 return "PhoneBook"; 9885 }, 9886 9887 /** 9888 * @private 9889 * Override default to indicates that the collection supports making 9890 * requests. 9891 */ 9892 supportsRequests: true, 9893 9894 /** 9895 * @private 9896 * Override default to indicates that the collection subscribes to its objects. 9897 */ 9898 supportsRestItemSubscriptions: false, 9899 9900 /** 9901 * @private 9902 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 9903 * 9904 * @returns {finesse.restservices.PhoneBooks} 9905 * This PhoneBooks object, to allow cascading. 9906 */ 9907 get: function () { 9908 // set loaded to false so it will rebuild the collection after the get 9909 this._loaded = false; 9910 // reset collection 9911 this._collection = {}; 9912 // perform get 9913 this._synchronize(); 9914 return this; 9915 } 9916 9917 }); 9918 9919 window.finesse = window.finesse || {}; 9920 window.finesse.restservices = window.finesse.restservices || {}; 9921 window.finesse.restservices.PhoneBooks = PhoneBooks; 9922 9923 return PhoneBooks; 9924 }); 9925 9926 /** 9927 * JavaScript representation of the Finesse WorkflowAction object. 9928 * 9929 * @requires finesse.clientservices.ClientServices 9930 * @requires Class 9931 * @requires finesse.FinesseBase 9932 * @requires finesse.restservices.RestBase 9933 */ 9934 9935 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 9936 /*global define,finesse */ 9937 9938 /** @private */ 9939 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 9940 9941 var WorkflowAction = RestBase.extend({ 9942 9943 _contacts: null, 9944 9945 actionTypes: [ 9946 { 9947 name: 'BROWSER_POP', 9948 params: [ 9949 { 9950 name: 'windowName', 9951 type: 'text' 9952 }, 9953 { 9954 name: 'path', 9955 type: 'systemVariableSingleLineEditor' 9956 } 9957 ] 9958 }, 9959 { 9960 name: 'HTTP_REQUEST', 9961 params: [ 9962 { 9963 name: 'method', 9964 type: 'dropdown', 9965 values: ['POST', 'PUT'] 9966 }, 9967 { 9968 name: 'location', 9969 type: 'dropdown', 9970 values: ['FINESSE', 'OTHER'] 9971 }, 9972 { 9973 name: 'contentType', 9974 type: 'text' 9975 }, 9976 { 9977 name: 'path', 9978 type: 'systemVariableSingleLineEditor' 9979 }, 9980 { 9981 name: 'body', 9982 type: 'systemVariableMultiLineEditor' 9983 } 9984 ] 9985 } 9986 // more action type definitions here 9987 ], 9988 9989 /** 9990 * @class 9991 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 9992 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 9993 * 9994 * @augments finesse.restservices.RestBase 9995 * @see finesse.restservices.Workflow 9996 * @constructs 9997 */ 9998 _fakeConstuctor: function () { 9999 /* This is here to hide the real init constructor from the public docs */ 10000 }, 10001 10002 /** 10003 * @private 10004 * JavaScript representation of a WorkflowAction object. Also exposes 10005 * methods to operate on the object against the server. 10006 * 10007 * @param {Object} options 10008 * An object with the following properties:<ul> 10009 * <li><b>id:</b> The id of the object being constructed</li> 10010 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10011 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10012 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10013 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10014 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10015 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10016 * <li><b>content:</b> {String} Raw string of response</li> 10017 * <li><b>object:</b> {Object} Parsed object of response</li> 10018 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10019 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10020 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10021 * </ul></li> 10022 * </ul></li> 10023 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10024 **/ 10025 init: function (options) { 10026 this._super(options); 10027 }, 10028 10029 /** 10030 * @private 10031 * Gets the REST class for the current object - this is the WorkflowAction class. 10032 * @returns {Object} The WorkflowAction class. 10033 */ 10034 getRestClass: function () { 10035 return finesse.restservices.WorkflowAction; 10036 }, 10037 10038 /** 10039 * @private 10040 * Gets the REST type for the current object - this is a "WorkflowAction". 10041 * @returns {String} The WorkflowAction string. 10042 */ 10043 getRestType: function () { 10044 return "WorkflowAction"; 10045 }, 10046 10047 /** 10048 * @private 10049 * Override default to indicate that this object doesn't support making 10050 * requests. 10051 */ 10052 supportsRequests: false, 10053 10054 /** 10055 * @private 10056 * Override default to indicate that this object doesn't support subscriptions. 10057 */ 10058 supportsSubscriptions: false, 10059 10060 /** 10061 * Getter for the name. 10062 * @returns {String} The name. 10063 */ 10064 getName: function () { 10065 this.isLoaded(); 10066 return this.getData().name; 10067 }, 10068 10069 /** 10070 * Getter for the type flag. 10071 * @returns {String} The type. 10072 */ 10073 getType: function () { 10074 this.isLoaded(); 10075 return this.getData().type; 10076 }, 10077 10078 /** 10079 * @private 10080 * Getter for the Uri value. 10081 * @returns {String} The Uri. 10082 */ 10083 getUri: function () { 10084 this.isLoaded(); 10085 return this.getData().uri; 10086 }, 10087 10088 /** 10089 * @private 10090 * Getter for the handledBy value. 10091 * @returns {String} handledBy. 10092 */ 10093 getHandledBy: function () { 10094 this.isLoaded(); 10095 return this.getData().handledBy; 10096 }, 10097 10098 /** 10099 * Getter for the parameters. 10100 * @returns {Object} key = param name, value = param value 10101 */ 10102 getParams: function () { 10103 var map = {}, 10104 params = this.getData().params.Param, 10105 i, 10106 param; 10107 10108 for(i=0; i<params.length; i+=1){ 10109 param = params[i]; 10110 map[param.name] = param.value || ""; 10111 } 10112 10113 return map; 10114 }, 10115 10116 /** 10117 * Getter for the ActionVariables 10118 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 10119 */ 10120 getActionVariables: function() { 10121 var map = {}, 10122 actionVariablesParent = this.getData().actionVariables, 10123 actionVariables, 10124 i, 10125 actionVariable; 10126 10127 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 10128 return map; 10129 } 10130 actionVariables = actionVariablesParent.ActionVariable; 10131 10132 if(actionVariables.length > 0){ 10133 for(i=0; i<actionVariables.length; i+=1){ 10134 actionVariable = actionVariables[i]; 10135 // escape nulls to empty string 10136 actionVariable.name = actionVariable.name || ""; 10137 actionVariable.type = actionVariable.type || ""; 10138 actionVariable.node = actionVariable.node || ""; 10139 actionVariable.testValue = actionVariable.testValue || ""; 10140 map[actionVariable.name] = actionVariable; 10141 } 10142 } else { 10143 map[actionVariables.name] = actionVariables; 10144 } 10145 10146 return map; 10147 }, 10148 10149 /** @private */ 10150 createPutSuccessHandler: function(action, contentBody, successHandler){ 10151 return function (rsp) { 10152 // Update internal structure based on response. Here we 10153 // inject the contentBody from the PUT request into the 10154 // rsp.object element to mimic a GET as a way to take 10155 // advantage of the existing _processResponse method. 10156 rsp.object = contentBody; 10157 action._processResponse(rsp); 10158 10159 //Remove the injected WorkflowAction object before cascading response 10160 rsp.object = {}; 10161 10162 //cascade response back to consumer's response handler 10163 successHandler(rsp); 10164 }; 10165 }, 10166 10167 /** @private */ 10168 createPostSuccessHandler: function (action, contentBody, successHandler) { 10169 return function (rsp) { 10170 rsp.object = contentBody; 10171 action._processResponse(rsp); 10172 10173 //Remove the injected WorkflowAction object before cascading response 10174 rsp.object = {}; 10175 10176 //cascade response back to consumer's response handler 10177 successHandler(rsp); 10178 }; 10179 }, 10180 10181 /** 10182 * @private 10183 * Build params array out of all the values coming into add or update methods 10184 * paramMap is a map of params.. we need to translate it into an array of Param objects 10185 * where path and windowName are params for the BROWSER_POP type 10186 */ 10187 buildParamsForRest: function(paramMap){ 10188 var params = {"Param": []}, 10189 i; 10190 for(i in paramMap){ 10191 if(paramMap.hasOwnProperty(i)){ 10192 params.Param.push({name: i, value: paramMap[i]}); 10193 } 10194 } 10195 return params; 10196 }, 10197 10198 /** 10199 * @private 10200 * Build actionVariables array out of all the values coming into add or update methods 10201 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 10202 * where path and windowName are params for the BROWSER_POP type 10203 */ 10204 buildActionVariablesForRest: function(actionVariableMap){ 10205 var actionVariables = {"ActionVariable": []}, 10206 i, 10207 actionVariable; 10208 for(i in actionVariableMap){ 10209 if(actionVariableMap.hasOwnProperty(i)){ 10210 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 10211 actionVariable = { 10212 "name": actionVariableMap[i].name, 10213 "type": actionVariableMap[i].type, 10214 "node": actionVariableMap[i].node, 10215 "testValue": actionVariableMap[i].testValue 10216 }; 10217 actionVariables.ActionVariable.push(actionVariable); 10218 } 10219 } 10220 return actionVariables; 10221 }, 10222 10223 /** 10224 * Add 10225 */ 10226 add: function (newValues, handlers) { 10227 var contentBody = {}; 10228 10229 contentBody[this.getRestType()] = { 10230 "name": newValues.name, 10231 "type": newValues.type, 10232 "handledBy": newValues.handledBy, 10233 "params": this.buildParamsForRest(newValues.params), 10234 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10235 }; 10236 10237 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10238 handlers = handlers || {}; 10239 10240 this.restRequest(this.getRestUrl(), { 10241 method: 'POST', 10242 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10243 error: handlers.error, 10244 content: contentBody 10245 }); 10246 10247 return this; // Allow cascading 10248 }, 10249 10250 /** 10251 * @private 10252 * Update 10253 */ 10254 update: function (newValues, handlers) { 10255 this.isLoaded(); 10256 var contentBody = {}; 10257 10258 contentBody[this.getRestType()] = { 10259 "uri": this.getId(), 10260 "name": newValues.name, 10261 "type": newValues.type, 10262 "handledBy": newValues.handledBy, 10263 "params": this.buildParamsForRest(newValues.params), 10264 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10265 }; 10266 10267 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10268 handlers = handlers || {}; 10269 10270 this.restRequest(this.getRestUrl(), { 10271 method: 'PUT', 10272 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10273 error: handlers.error, 10274 content: contentBody 10275 }); 10276 10277 return this; // Allow cascading 10278 }, 10279 10280 10281 /** 10282 * @private 10283 * Delete 10284 */ 10285 "delete": function ( handlers) { 10286 this.isLoaded(); 10287 10288 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10289 handlers = handlers || {}; 10290 10291 this.restRequest(this.getRestUrl(), { 10292 method: 'DELETE', 10293 success: this.createPutSuccessHandler(this, {}, handlers.success), 10294 error: handlers.error, 10295 content: undefined 10296 }); 10297 10298 return this; // Allow cascading 10299 } 10300 10301 10302 10303 }); 10304 10305 window.finesse = window.finesse || {}; 10306 window.finesse.restservices = window.finesse.restservices || {}; 10307 window.finesse.restservices.WorkflowAction = WorkflowAction; 10308 10309 return WorkflowAction; 10310 }); 10311 10312 /** 10313 * JavaScript representation of the Finesse WorkflowActions collection 10314 * object which contains a list of WorkflowAction objects. 10315 * 10316 * @requires finesse.clientservices.ClientServices 10317 * @requires Class 10318 * @requires finesse.FinesseBase 10319 * @requires finesse.restservices.RestBase 10320 * @requires finesse.restservices.Dialog 10321 * @requires finesse.restservices.RestCollectionBase 10322 */ 10323 10324 /** @private */ 10325 define('restservices/WorkflowActions',[ 10326 'restservices/RestCollectionBase', 10327 'restservices/RestBase', 10328 'restservices/WorkflowAction' 10329 ], 10330 function (RestCollectionBase, RestBase, WorkflowAction) { 10331 10332 var WorkflowActions = RestCollectionBase.extend({ 10333 10334 /** 10335 * @class 10336 * JavaScript representation of a WorkflowActions collection object. 10337 * @augments finesse.restservices.RestCollectionBase 10338 * @constructs 10339 * @see finesse.restservices.WorkflowAction 10340 * @see finesse.restservices.Workflow 10341 * @see finesse.restservices.Workflows 10342 * @example 10343 * _workflowActions = _user.getWorkflowActions( { 10344 * onCollectionAdd : _handleWorkflowActionAdd, 10345 * onCollectionDelete : _handleWorkflowActionDelete, 10346 * onLoad : _handleWorkflowActionsLoaded 10347 * }); 10348 */ 10349 _fakeConstuctor: function () { 10350 /* This is here to hide the real init constructor from the public docs */ 10351 }, 10352 10353 /** 10354 * @private 10355 * JavaScript representation of a WorkflowActions collection object. Also exposes 10356 * methods to operate on the object against the server. 10357 * 10358 * @param {Object} options 10359 * An object with the following properties:<ul> 10360 * <li><b>id:</b> The id of the object being constructed</li> 10361 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10362 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10363 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10364 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10365 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10366 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10367 * <li><b>content:</b> {String} Raw string of response</li> 10368 * <li><b>object:</b> {Object} Parsed object of response</li> 10369 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10370 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10371 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10372 * </ul></li> 10373 * </ul></li> 10374 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10375 **/ 10376 init: function (options) { 10377 this._super(options); 10378 }, 10379 10380 /** 10381 * @private 10382 * Gets the REST class for the current object - this is the WorkflowActions class. 10383 */ 10384 getRestClass: function () { 10385 return WorkflowActions; 10386 }, 10387 10388 /** 10389 * @private 10390 * Gets the REST class for the objects that make up the collection. - this 10391 * is the WorkflowAction class. 10392 */ 10393 getRestItemClass: function () { 10394 return WorkflowAction; 10395 }, 10396 10397 /** 10398 * @private 10399 * Gets the REST type for the current object - this is a "WorkflowActions". 10400 */ 10401 getRestType: function () { 10402 return "WorkflowActions"; 10403 }, 10404 10405 /** 10406 * @private 10407 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 10408 */ 10409 getRestItemType: function () { 10410 return "WorkflowAction"; 10411 }, 10412 10413 /** 10414 * @private 10415 * Override default to indicates that the collection supports making 10416 * requests. 10417 */ 10418 supportsRequests: true, 10419 10420 /** 10421 * @private 10422 * Override default to indicates that the collection subscribes to its objects. 10423 */ 10424 supportsRestItemSubscriptions: false, 10425 10426 /** 10427 * @private 10428 * Retrieve the WorkflowActions. 10429 * 10430 * @returns {finesse.restservices.WorkflowActions} 10431 * This WorkflowActions object to allow cascading. 10432 */ 10433 get: function () { 10434 // set loaded to false so it will rebuild the collection after the get 10435 this._loaded = false; 10436 // reset collection 10437 this._collection = {}; 10438 // perform get 10439 this._synchronize(); 10440 return this; 10441 } 10442 }); 10443 10444 window.finesse = window.finesse || {}; 10445 window.finesse.restservices = window.finesse.restservices || {}; 10446 window.finesse.restservices.WorkflowActions = WorkflowActions; 10447 10448 return WorkflowActions; 10449 }); 10450 10451 /** 10452 * JavaScript representation of the Finesse Workflow object. 10453 * 10454 * @requires finesse.clientservices.ClientServices 10455 * @requires Class 10456 * @requires finesse.FinesseBase 10457 * @requires finesse.restservices.RestBase 10458 */ 10459 10460 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 10461 /*global define,finesse */ 10462 10463 /** @private */ 10464 define('restservices/Workflow',[ 10465 'restservices/RestBase', 10466 'restservices/WorkflowActions' 10467 ], 10468 function (RestBase, WorkflowActions) { 10469 10470 var Workflow = RestBase.extend({ 10471 10472 /** 10473 * @class 10474 * JavaScript representation of a Workflow object. Also exposes 10475 * methods to operate on the object against the server. 10476 * 10477 * @param {Object} options 10478 * An object with the following properties:<ul> 10479 * <li><b>id:</b> The id of the object being constructed</li> 10480 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10481 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10482 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10483 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10484 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10485 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10486 * <li><b>content:</b> {String} Raw string of response</li> 10487 * <li><b>object:</b> {Object} Parsed object of response</li> 10488 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10489 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10490 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10491 * </ul></li> 10492 * </ul></li> 10493 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10494 * @constructs 10495 **/ 10496 init: function (options) { 10497 this._super(options); 10498 }, 10499 10500 /** 10501 * @private 10502 * Gets the REST class for the current object - this is the Workflow class. 10503 * @returns {Object} The Workflow class. 10504 */ 10505 getRestClass: function () { 10506 return Workflow; 10507 }, 10508 10509 /** 10510 * @private 10511 * Gets the REST type for the current object - this is a "Workflow". 10512 * @returns {String} The Workflow string. 10513 */ 10514 getRestType: function () { 10515 return "Workflow"; 10516 }, 10517 10518 /** 10519 * @private 10520 * Override default to indicate that this object doesn't support making 10521 * requests. 10522 */ 10523 supportsRequests: false, 10524 10525 /** 10526 * @private 10527 * Override default to indicate that this object doesn't support subscriptions. 10528 */ 10529 supportsSubscriptions: false, 10530 10531 /** 10532 * @private 10533 * Getter for the Uri value. 10534 * @returns {String} The Uri. 10535 */ 10536 getUri: function () { 10537 this.isLoaded(); 10538 return this.getData().uri; 10539 }, 10540 10541 /** 10542 * Getter for the name. 10543 * @returns {String} The name. 10544 */ 10545 getName: function () { 10546 this.isLoaded(); 10547 return this.getData().name; 10548 }, 10549 10550 /** 10551 * Getter for the description. 10552 * @returns {String} The description. 10553 */ 10554 getDescription: function () { 10555 this.isLoaded(); 10556 return this.getData().description; 10557 }, 10558 10559 /** 10560 * Getter for the trigger set. 10561 * @returns {String} The trigger set. 10562 */ 10563 getTriggerSet: function () { 10564 this.isLoaded(); 10565 return this.getData().TriggerSet; 10566 }, 10567 10568 /** 10569 * Getter for the condition set. 10570 * @returns {String} The condition set. 10571 */ 10572 getConditionSet: function () { 10573 this.isLoaded(); 10574 return this.getData().ConditionSet; 10575 }, 10576 10577 /** 10578 * Getter for the assigned workflowActions. 10579 * @returns {String} The workflowActions object. 10580 */ 10581 getWorkflowActions: function () { 10582 this.isLoaded(); 10583 var workflowActions = this.getData().workflowActions; 10584 if (workflowActions === null) { 10585 workflowActions = ""; 10586 } 10587 return workflowActions; 10588 }, 10589 10590 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 10591 return function (rsp) { 10592 // Update internal structure based on response. Here we 10593 // inject the contentBody from the PUT request into the 10594 // rsp.object element to mimic a GET as a way to take 10595 // advantage of the existing _processResponse method. 10596 rsp.object = contentBody; 10597 workflow._processResponse(rsp); 10598 10599 //Remove the injected Workflow object before cascading response 10600 rsp.object = {}; 10601 10602 //cascade response back to consumer's response handler 10603 successHandler(rsp); 10604 }; 10605 }, 10606 10607 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 10608 return function (rsp) { 10609 rsp.object = contentBody; 10610 workflow._processResponse(rsp); 10611 10612 //Remove the injected Workflow object before cascading response 10613 rsp.object = {}; 10614 10615 //cascade response back to consumer's response handler 10616 successHandler(rsp); 10617 }; 10618 }, 10619 10620 /** 10621 * @private 10622 * Add 10623 */ 10624 add: function (newValues, handlers) { 10625 // this.isLoaded(); 10626 var contentBody = {}; 10627 10628 contentBody[this.getRestType()] = { 10629 "name": newValues.name, 10630 "description": newValues.description, 10631 "TriggerSet" : newValues.TriggerSet, 10632 "ConditionSet" : newValues.ConditionSet, 10633 "workflowActions" : newValues.workflowActions 10634 }; 10635 10636 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10637 handlers = handlers || {}; 10638 10639 this.restRequest(this.getRestUrl(), { 10640 method: 'POST', 10641 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10642 error: handlers.error, 10643 content: contentBody 10644 }); 10645 10646 return this; // Allow cascading 10647 }, 10648 10649 /** 10650 * @private 10651 * Update 10652 */ 10653 update: function (newValues, handlers) { 10654 this.isLoaded(); 10655 var contentBody = {}; 10656 10657 contentBody[this.getRestType()] = { 10658 "uri": this.getId(), 10659 "name": newValues.name, 10660 "description": newValues.description, 10661 "TriggerSet" : newValues.TriggerSet, 10662 "ConditionSet" : newValues.ConditionSet, 10663 "workflowActions" : newValues.workflowActions 10664 }; 10665 10666 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10667 handlers = handlers || {}; 10668 10669 this.restRequest(this.getRestUrl(), { 10670 method: 'PUT', 10671 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10672 error: handlers.error, 10673 content: contentBody 10674 }); 10675 10676 return this; // Allow cascading 10677 }, 10678 10679 10680 /** 10681 * @private 10682 * Delete 10683 */ 10684 "delete": function (handlers) { 10685 this.isLoaded(); 10686 10687 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10688 handlers = handlers || {}; 10689 10690 this.restRequest(this.getRestUrl(), { 10691 method: 'DELETE', 10692 success: this.createPutSuccessHandler(this, {}, handlers.success), 10693 error: handlers.error, 10694 content: undefined 10695 }); 10696 10697 return this; // Allow cascading 10698 } 10699 10700 10701 10702 }); 10703 10704 window.finesse = window.finesse || {}; 10705 window.finesse.restservices = window.finesse.restservices || {}; 10706 window.finesse.restservices.Workflow = Workflow; 10707 10708 return Workflow; 10709 }); 10710 10711 /** 10712 * JavaScript representation of the Finesse workflows collection 10713 * object which contains a list of workflow objects. 10714 * 10715 * @requires finesse.clientservices.ClientServices 10716 * @requires Class 10717 * @requires finesse.FinesseBase 10718 * @requires finesse.restservices.RestBase 10719 * @requires finesse.restservices.Dialog 10720 * @requires finesse.restservices.RestCollectionBase 10721 */ 10722 10723 /** @private */ 10724 define('restservices/Workflows',[ 10725 'restservices/RestCollectionBase', 10726 'restservices/RestBase', 10727 'restservices/Workflow' 10728 ], 10729 function (RestCollectionBase, RestBase, Workflow) { 10730 10731 var Workflows = RestCollectionBase.extend({ 10732 10733 /** 10734 * @class 10735 * JavaScript representation of a workflows collection object. Also exposes 10736 * methods to operate on the object against the server. 10737 * 10738 * @param {Object} options 10739 * An object with the following properties:<ul> 10740 * <li><b>id:</b> The id of the object being constructed</li> 10741 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10742 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10743 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10744 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10745 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10746 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10747 * <li><b>content:</b> {String} Raw string of response</li> 10748 * <li><b>object:</b> {Object} Parsed object of response</li> 10749 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10750 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10751 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10752 * </ul></li> 10753 * </ul></li> 10754 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10755 * @constructs 10756 **/ 10757 init: function (options) { 10758 this._super(options); 10759 }, 10760 10761 /** 10762 * @private 10763 * Gets the REST class for the current object - this is the workflows class. 10764 */ 10765 getRestClass: function () { 10766 return Workflows; 10767 }, 10768 10769 /** 10770 * @private 10771 * Gets the REST class for the objects that make up the collection. - this 10772 * is the workflow class. 10773 */ 10774 getRestItemClass: function () { 10775 return Workflow; 10776 }, 10777 10778 /** 10779 * @private 10780 * Gets the REST type for the current object - this is a "workflows". 10781 */ 10782 getRestType: function () { 10783 return "Workflows"; 10784 }, 10785 10786 /** 10787 * @private 10788 * Gets the REST type for the objects that make up the collection - this is "workflows". 10789 */ 10790 getRestItemType: function () { 10791 return "Workflow"; 10792 }, 10793 10794 /** 10795 * @private 10796 * Override default to indicates that the collection supports making requests. 10797 */ 10798 supportsRequests: true, 10799 10800 /** 10801 * @private 10802 * Override default to indicates that the collection does not subscribe to its objects. 10803 */ 10804 supportsRestItemSubscriptions: false, 10805 10806 /** 10807 * @private 10808 * Retrieve the workflows. This call will re-query the server and refresh the collection. 10809 * 10810 * @returns {finesse.restservices.workflows} 10811 * This workflows object to allow cascading. 10812 */ 10813 get: function () { 10814 // set loaded to false so it will rebuild the collection after the get 10815 this._loaded = false; 10816 // reset collection 10817 this._collection = {}; 10818 // perform get 10819 this._synchronize(); 10820 return this; 10821 } 10822 }); 10823 10824 window.finesse = window.finesse || {}; 10825 window.finesse.restservices = window.finesse.restservices || {}; 10826 window.finesse.restservices.Workflows = Workflows; 10827 10828 return Workflows; 10829 }); 10830 10831 /** 10832 * JavaScript representation of the Finesse MediaPropertiesLayout object 10833 * 10834 * @requires finesse.clientservices.ClientServices 10835 * @requires Class 10836 * @requires finesse.FinesseBase 10837 * @requires finesse.restservices.RestBase 10838 */ 10839 10840 /** The following comment is to prevent jslint errors about 10841 * using variables before they are defined. 10842 */ 10843 /*global finesse*/ 10844 10845 /** @private */ 10846 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 10847 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 10848 10849 /** 10850 * @class 10851 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 10852 * 10853 * @augments finesse.restservices.RestBase 10854 * @see finesse.restservices.Dialog#getMediaProperties 10855 * @see finesse.restservices.User#getMediaPropertiesLayout 10856 * @constructs 10857 */ 10858 _fakeConstuctor: function () { 10859 /* This is here to hide the real init constructor from the public docs */ 10860 }, 10861 10862 /** 10863 * @private 10864 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 10865 * methods to operate on the object against the server. 10866 * 10867 * @param {Object} options 10868 * An object with the following properties:<ul> 10869 * <li><b>id:</b> The id of the object being constructed</li> 10870 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10871 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10872 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10873 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10874 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10875 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10876 * <li><b>content:</b> {String} Raw string of response</li> 10877 * <li><b>object:</b> {Object} Parsed object of response</li> 10878 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10879 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10880 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10881 * </ul></li> 10882 * </ul></li> 10883 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10884 **/ 10885 init: function (options) { 10886 this._super(options); 10887 }, 10888 10889 /** 10890 * @private 10891 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 10892 */ 10893 getRestClass: function () { 10894 return MediaPropertiesLayout; 10895 }, 10896 10897 /** 10898 * @private 10899 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 10900 */ 10901 getRestType: function () { 10902 return "MediaPropertiesLayout"; 10903 }, 10904 10905 /** 10906 * @private 10907 * Overrides the parent class. Returns the url for the MediaPropertiesLayout resource 10908 */ 10909 getRestUrl: function () { 10910 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 10911 }, 10912 10913 /** 10914 * @private 10915 * Returns whether this object supports subscriptions 10916 */ 10917 supportsSubscriptions: false, 10918 10919 /** 10920 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 10921 * @returns {finesse.restservices.MediaPropertiesLayout} 10922 * This MediaPropertiesLayout object to allow cascading 10923 */ 10924 get: function () { 10925 this._synchronize(); 10926 10927 return this; //Allow cascading 10928 }, 10929 10930 /** 10931 * Gets the data for this object. 10932 * 10933 * Performs safe conversion from raw API data to ensure that the returned layout object 10934 * always has a header with correct entry fields, and exactly two columns with lists of entries. 10935 * 10936 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 10937 */ 10938 getData: function () { 10939 10940 var layout = this._data, result, _addColumnData; 10941 10942 result = this.getEmptyData(); 10943 10944 /** 10945 * @private 10946 */ 10947 _addColumnData = function (entryData, colIndex) { 10948 10949 if (!entryData) { 10950 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 10951 entryData = {}; 10952 } else if (entryData.mediaProperty) { 10953 //If entryData contains the keys for a single entry rather than being a collection of entries, 10954 //rewrite it to be a collection containing a single entry 10955 entryData = { "": entryData }; 10956 } 10957 10958 //Add each of the entries in the list to the column 10959 jQuery.each(entryData, function (i, entryData) { 10960 10961 //If the entry has no displayName specified, explicitly set it to the empty string 10962 if (!entryData.displayName) { 10963 entryData.displayName = ""; 10964 } 10965 10966 result.columns[colIndex].push(entryData); 10967 10968 }); 10969 10970 }; 10971 10972 //The header should only contain a single entry 10973 if (layout.header && layout.header.entry) { 10974 10975 //If the entry has no displayName specified, explicitly set it to the empty string 10976 if (!layout.header.entry.displayName) { 10977 layout.header.entry.displayName = ""; 10978 } 10979 10980 result.header = layout.header.entry; 10981 10982 } else { 10983 10984 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 10985 10986 } 10987 10988 //If the column object contains an entry object that wasn't part of a list of entries, 10989 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 10990 //Force the entry object to be the 2nd element in an otherwise-empty list. 10991 if (layout.column && layout.column.entry) { 10992 layout.column = [ 10993 null, 10994 { "entry": layout.column.entry } 10995 ]; 10996 } 10997 10998 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 10999 11000 //Render left column entries 11001 if (layout.column[0] && layout.column[0].entry) { 11002 _addColumnData(layout.column[0].entry, 0); 11003 } 11004 11005 //Render right column entries 11006 if (layout.column[1] && layout.column[1].entry) { 11007 _addColumnData(layout.column[1].entry, 1); 11008 } 11009 11010 } 11011 11012 return result; 11013 11014 }, 11015 11016 /** 11017 * @private 11018 * Empty/template version of getData(). 11019 * 11020 * Used by getData(), and by callers of getData() in error cases. 11021 */ 11022 getEmptyData: function () { 11023 11024 return { 11025 header : { 11026 displayName: null, 11027 mediaProperty: null 11028 }, 11029 columns : [[], []] 11030 }; 11031 11032 }, 11033 11034 /** 11035 * @private 11036 * Set the layout of this MediaPropertiesLayout. 11037 * @param {String} layout 11038 * The layout you are setting 11039 * @param {finesse.interfaces.RequestHandlers} handlers 11040 * An object containing the handlers for the request 11041 * @returns {finesse.restservices.MediaPropertiesLayout} 11042 * This MediaPropertiesLayout object to allow cascading 11043 */ 11044 setLayout: function (layout, handlers) { 11045 11046 var contentBody = {}; 11047 11048 contentBody[this.getRestType()] = layout; 11049 11050 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11051 handlers = handlers || {}; 11052 11053 this.restRequest(this.getRestUrl(), { 11054 method: 'PUT', 11055 success: handlers.success, 11056 error: handlers.error, 11057 content: contentBody 11058 }); 11059 11060 return this; // Allow cascading 11061 } 11062 11063 }); 11064 11065 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 11066 /** 11067 * @class Format of MediaPropertiesLayout Object.<br> 11068 * Object { <ul> 11069 * <li>header : { <ul> 11070 * <li>dispayName {String} 11071 * <li>mediaProperty {String}</ul>} 11072 * <li>columns : { <ul> 11073 * <li>[ [] , [] ] 11074 * </ul> 11075 * where column arrays consists of the same Object format as header.<br> 11076 * }</ul> 11077 * }<br> 11078 * @constructs 11079 */ 11080 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11081 11082 }; 11083 11084 window.finesse = window.finesse || {}; 11085 window.finesse.restservices = window.finesse.restservices || {}; 11086 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 11087 11088 return MediaPropertiesLayout; 11089 }); 11090 11091 /** 11092 * JavaScript representation of the Finesse User object 11093 * 11094 * @requires finesse.clientservices.ClientServices 11095 * @requires Class 11096 * @requires finesse.FinesseBase 11097 * @requires finesse.restservices.RestBase 11098 */ 11099 11100 /** @private */ 11101 define('restservices/User',[ 11102 'restservices/RestBase', 11103 'restservices/Dialogs', 11104 'restservices/ClientLog', 11105 'restservices/Queues', 11106 'restservices/WrapUpReasons', 11107 'restservices/PhoneBooks', 11108 'restservices/Workflows', 11109 'restservices/MediaPropertiesLayout', 11110 'utilities/Utilities' 11111 ], 11112 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, MediaPropertiesLayout, Utilities) { 11113 11114 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 11115 11116 _dialogs : null, 11117 _clientLogObj : null, 11118 _wrapUpReasons : null, 11119 _phoneBooks : null, 11120 _workflows : null, 11121 _mediaPropertiesLayout : null, 11122 _queues : null, 11123 11124 /** 11125 * @class 11126 * The User represents a Finesse Agent or Supervisor. 11127 * 11128 * @param {Object} options 11129 * An object with the following properties:<ul> 11130 * <li><b>id:</b> The id of the object being constructed</li> 11131 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 11132 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 11133 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 11134 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 11135 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 11136 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11137 * <li><b>content:</b> {String} Raw string of response</li> 11138 * <li><b>object:</b> {Object} Parsed object of response</li> 11139 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11140 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11141 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11142 * </ul></li> 11143 * </ul></li> 11144 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11145 * @augments finesse.restservices.RestBase 11146 * @constructs 11147 * @example 11148 * _user = new finesse.restservices.User({ 11149 * id: _id, 11150 * onLoad : _handleUserLoad, 11151 * onChange : _handleUserChange 11152 * }); 11153 **/ 11154 init: function (options) { 11155 this._super(options); 11156 }, 11157 11158 Callbacks: {}, 11159 11160 /** 11161 * @private 11162 * Gets the REST class for the current object - this is the User object. 11163 */ 11164 getRestClass: function () { 11165 return User; 11166 }, 11167 11168 /** 11169 * @private 11170 * Gets the REST type for the current object - this is a "User". 11171 */ 11172 getRestType: function () { 11173 return "User"; 11174 }, 11175 /** 11176 * @private 11177 * overloading this to return URI 11178 */ 11179 getXMPPNodePath: function () { 11180 return this.getRestUrl(); 11181 }, 11182 /** 11183 * @private 11184 * Returns whether this object supports subscriptions 11185 */ 11186 supportsSubscriptions: function () { 11187 return true; 11188 }, 11189 11190 /** 11191 * Getter for the firstName of this User. 11192 * @returns {String} 11193 * The firstName for this User 11194 */ 11195 getFirstName: function () { 11196 this.isLoaded(); 11197 return Utilities.convertNullToEmptyString(this.getData().firstName); 11198 }, 11199 11200 /** 11201 * Getter for the lastName of this User. 11202 * @returns {String} 11203 * The lastName for this User 11204 */ 11205 getLastName: function () { 11206 this.isLoaded(); 11207 return Utilities.convertNullToEmptyString(this.getData().lastName); 11208 }, 11209 11210 /** 11211 * Getter for the extension of this User. 11212 * @returns {String} 11213 * The extension, if any, of this User 11214 */ 11215 getExtension: function () { 11216 this.isLoaded(); 11217 return Utilities.convertNullToEmptyString(this.getData().extension); 11218 }, 11219 11220 /** 11221 * Getter for the id of the Team of this User 11222 * @returns {String} 11223 * The current (or last fetched) id of the Team of this User 11224 */ 11225 getTeamId: function () { 11226 this.isLoaded(); 11227 return this.getData().teamId; 11228 }, 11229 11230 /** 11231 * Getter for the name of the Team of this User 11232 * @returns {String} 11233 * The current (or last fetched) name of the Team of this User 11234 */ 11235 getTeamName: function () { 11236 this.isLoaded(); 11237 return this.getData().teamName; 11238 }, 11239 11240 /** 11241 * Is user an agent? 11242 * @returns {Boolean} True if user has role of agent, else false. 11243 */ 11244 hasAgentRole: function () { 11245 this.isLoaded(); 11246 return this.hasRole("Agent"); 11247 }, 11248 11249 /** 11250 * Is user a supervisor? 11251 * @returns {Boolean} True if user has role of supervisor, else false. 11252 */ 11253 hasSupervisorRole: function () { 11254 this.isLoaded(); 11255 return this.hasRole("Supervisor"); 11256 }, 11257 11258 /** 11259 * @private 11260 * Checks to see if user has "theRole" 11261 * @returns {Boolean} 11262 */ 11263 hasRole: function (theRole) { 11264 this.isLoaded(); 11265 var result = false, i, roles, len; 11266 11267 roles = this.getData().roles.role; 11268 len = roles.length; 11269 if (typeof roles === 'string') { 11270 if (roles === theRole) { 11271 result = true; 11272 } 11273 } else { 11274 for (i = 0; i < len ; i = i + 1) { 11275 if (roles[i] === theRole) { 11276 result = true; 11277 break; 11278 } 11279 } 11280 } 11281 11282 return result; 11283 }, 11284 11285 /** 11286 * Getter for the pending state of this User. 11287 * @returns {String} 11288 * The pending state of this User 11289 * @see finesse.restservices.User.States 11290 */ 11291 getPendingState: function () { 11292 this.isLoaded(); 11293 return Utilities.convertNullToEmptyString(this.getData().pendingState); 11294 }, 11295 11296 /** 11297 * Getter for the state of this User. 11298 * @returns {String} 11299 * The current (or last fetched) state of this User 11300 * @see finesse.restservices.User.States 11301 */ 11302 getState: function () { 11303 this.isLoaded(); 11304 return this.getData().state; 11305 }, 11306 11307 /** 11308 * Getter for the state change time of this User. 11309 * @returns {String} 11310 * The state change time of this User 11311 */ 11312 getStateChangeTime: function () { 11313 this.isLoaded(); 11314 return this.getData().stateChangeTime; 11315 }, 11316 11317 /** 11318 * Getter for the wrap-up mode of this User. 11319 * @see finesse.restservices.User.WrapUpMode 11320 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 11321 */ 11322 getWrapUpOnIncoming: function () { 11323 this.isLoaded(); 11324 return this.getData().settings.wrapUpOnIncoming; 11325 }, 11326 11327 /** 11328 * Is User required to go into wrap-up? 11329 * @see finesse.restservices.User.WrapUpMode 11330 * @return {Boolean} 11331 * True if this agent is required to go into wrap-up. 11332 */ 11333 isWrapUpRequired: function () { 11334 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 11335 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 11336 }, 11337 11338 /** 11339 * Checks to see if the user is considered a mobile agent by checking for 11340 * the existence of the mobileAgent node. 11341 * @returns {Boolean} 11342 * True if this agent is a mobile agent. 11343 */ 11344 isMobileAgent: function () { 11345 this.isLoaded(); 11346 var ma = this.getData().mobileAgent; 11347 return ma !== null && typeof ma === "object"; 11348 }, 11349 11350 /** 11351 * Getter for the mobile agent work mode. 11352 * @returns {finesse.restservices.User.WorkMode} 11353 * If available, return the mobile agent work mode, otherwise null. 11354 */ 11355 getMobileAgentMode: function () { 11356 this.isLoaded(); 11357 if (this.isMobileAgent()) { 11358 return this.getData().mobileAgent.mode; 11359 } 11360 return null; 11361 }, 11362 11363 /** 11364 * Getter for the mobile agent dial number. 11365 * @returns {String} 11366 * If available, return the mobile agent dial number, otherwise null. 11367 */ 11368 getMobileAgentDialNumber: function () { 11369 this.isLoaded(); 11370 if (this.isMobileAgent()) { 11371 return this.getData().mobileAgent.dialNumber; 11372 } 11373 return null; 11374 }, 11375 11376 /** 11377 * Getter for a Dialogs collection object that is associated with User. 11378 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11379 * applicable when Object has not been previously created). 11380 * @returns {finesse.restservices.Dialogs} 11381 * A Dialogs collection object. 11382 */ 11383 getDialogs: function (callbacks) { 11384 var options = callbacks || {}; 11385 options.parentObj = this; 11386 this.isLoaded(); 11387 11388 if (this._dialogs === null) { 11389 this._dialogs = new Dialogs(options); 11390 } 11391 11392 return this._dialogs; 11393 }, 11394 11395 /** 11396 * @private 11397 * Getter for a ClientLog object that is associated with User. 11398 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11399 * applicable when Object has not been previously created). 11400 * @returns {finesse.restservices.ClientLog} 11401 * A ClientLog collection object. 11402 */ 11403 getClientLog: function (callbacks) { 11404 var options = callbacks || {}; 11405 options.parentObj = this; 11406 this.isLoaded(); 11407 11408 if (this._clientLogObj === null) { 11409 this._clientLogObj = new ClientLog(options); 11410 } 11411 else { 11412 if(options.onLoad && typeof options.onLoad === "function") { 11413 options.onLoad(this._clientLogObj); 11414 } 11415 } 11416 return this._clientLogObj; 11417 }, 11418 11419 /** 11420 * Getter for a Queues collection object that is associated with User. 11421 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11422 * applicable when Object has not been previously created). 11423 * @returns {finesse.restservices.Queues} 11424 * A Queues collection object. 11425 */ 11426 getQueues: function (callbacks) { 11427 var options = callbacks || {}; 11428 options.parentObj = this; 11429 this.isLoaded(); 11430 11431 if (this._queues === null) { 11432 this._queues = new Queues(options); 11433 } 11434 11435 return this._queues; 11436 }, 11437 11438 /** 11439 * Getter for a WrapUpReasons collection object that is associated with User. 11440 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11441 * applicable when Object has not been previously created). 11442 * @returns {finesse.restservices.WrapUpReasons} 11443 * A WrapUpReasons collection object. 11444 */ 11445 getWrapUpReasons: function (callbacks) { 11446 var options = callbacks || {}; 11447 options.parentObj = this; 11448 this.isLoaded(); 11449 11450 if (this._wrapUpReasons === null) { 11451 this._wrapUpReasons = new WrapUpReasons(options); 11452 } 11453 11454 return this._wrapUpReasons; 11455 }, 11456 11457 /** 11458 * Getter for a PhoneBooks collection object that is associated with User. 11459 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11460 * applicable when Object has not been previously created). 11461 * @returns {finesse.restservices.PhoneBooks} 11462 * A PhoneBooks collection object. 11463 */ 11464 getPhoneBooks: function (callbacks) { 11465 var options = callbacks || {}; 11466 options.parentObj = this; 11467 this.isLoaded(); 11468 11469 if (this._phoneBooks === null) { 11470 this._phoneBooks = new PhoneBooks(options); 11471 } 11472 11473 return this._phoneBooks; 11474 }, 11475 11476 /** 11477 * @private 11478 * Loads the Workflows collection object that is associated with User and 11479 * 'returns' them to the caller via the handlers. 11480 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11481 * applicable when Object has not been previously created). 11482 * @see finesse.restservices.Workflow 11483 * @see finesse.restservices.Workflows 11484 * @see finesse.restservices.RestCollectionBase 11485 */ 11486 loadWorkflows: function (callbacks) { 11487 var options = callbacks || {}; 11488 options.parentObj = this; 11489 this.isLoaded(); 11490 11491 if (this._workflows === null) { 11492 this._workflows = new Workflows(options); 11493 } else { 11494 this._workflows.refresh(); 11495 } 11496 11497 }, 11498 11499 /** 11500 * Getter for a MediaPropertiesLayout object that is associated with User. 11501 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 11502 * applicable when Object has not been previously created). 11503 * @returns {finesse.restservices.MediaPropertiesLayout} 11504 * The MediaPropertiesLayout object associated with this user 11505 */ 11506 getMediaPropertiesLayout: function (callbacks) { 11507 var options = callbacks || {}; 11508 options.parentObj = this; 11509 options.id = this._id; 11510 11511 this.isLoaded(); 11512 if (this._mediaPropertiesLayout === null) { 11513 this._mediaPropertiesLayout = new MediaPropertiesLayout(options); 11514 } 11515 return this._mediaPropertiesLayout; 11516 }, 11517 11518 /** 11519 * Getter for the supervised Teams this User (Supervisor) supervises, if any. 11520 * @see finesse.restservices.Team 11521 * @returns {Array} 11522 * An array of Teams supervised by this User (Supervisor) 11523 */ 11524 getSupervisedTeams: function () { 11525 this.isLoaded(); 11526 11527 try { 11528 return Utilities.getArray(this.getData().teams.Team); 11529 } catch (e) { 11530 return []; 11531 } 11532 11533 }, 11534 11535 /** 11536 * Perform an agent login for this user, associating him with the 11537 * specified extension. 11538 * @param {Object} params 11539 * An object containing properties for agent login. 11540 * @param {String} params.extension 11541 * The extension to associate with this user 11542 * @param {Object} [params.mobileAgent] 11543 * A mobile agent object containing the mode and dial number properties. 11544 * @param {finesse.interfaces.RequestHandlers} params.handlers 11545 * @see finesse.interfaces.RequestHandlers 11546 * @returns {finesse.restservices.User} 11547 * This User object, to allow cascading 11548 * @private 11549 */ 11550 _login: function (params) { 11551 var handlers, contentBody = {}, 11552 restType = this.getRestType(); 11553 11554 // Protect against null dereferencing. 11555 params = params || {}; 11556 11557 contentBody[restType] = { 11558 "state": User.States.LOGIN, 11559 "extension": params.extension 11560 }; 11561 11562 // Create mobile agent node if available. 11563 if (typeof params.mobileAgent === "object") { 11564 contentBody[restType].mobileAgent = { 11565 "mode": params.mobileAgent.mode, 11566 "dialNumber": params.mobileAgent.dialNumber 11567 }; 11568 } 11569 11570 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11571 handlers = params.handlers || {}; 11572 11573 this.restRequest(this.getRestUrl(), { 11574 method: 'PUT', 11575 success: handlers.success, 11576 error: handlers.error, 11577 content: contentBody 11578 }); 11579 11580 return this; // Allow cascading 11581 }, 11582 11583 /** 11584 * Perform an agent login for this user, associating him with the 11585 * specified extension. 11586 * @param {String} extension 11587 * The extension to associate with this user 11588 * @param {finesse.interfaces.RequestHandlers} handlers 11589 * An object containing the handlers for the request 11590 * @returns {finesse.restservices.User} 11591 * This User object, to allow cascading 11592 */ 11593 login: function (extension, handlers) { 11594 this.isLoaded(); 11595 var params = { 11596 "extension": extension, 11597 "handlers": handlers 11598 }; 11599 return this._login(params); 11600 }, 11601 11602 /** 11603 * Perform an agent login for this user, associating him with the 11604 * specified extension. 11605 * @param {String} extension 11606 * The extension to associate with this user 11607 * @param {String} mode 11608 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 11609 * @param {String} extension 11610 * The external dial number desired to be used by the mobile agent. 11611 * @param {finesse.interfaces.RequestHandlers} handlers 11612 * An object containing the handlers for the request 11613 * @returns {finesse.restservices.User} 11614 * This User object, to allow cascading 11615 */ 11616 loginMobileAgent: function (extension, mode, dialNumber, handlers) { 11617 this.isLoaded(); 11618 var params = { 11619 "extension": extension, 11620 "mobileAgent": { 11621 "mode": mode, 11622 "dialNumber": dialNumber 11623 }, 11624 "handlers": handlers 11625 }; 11626 return this._login(params); 11627 }, 11628 11629 /** 11630 * Perform an agent logout for this user. 11631 * @param {String} reasonCode 11632 * The reason this user is logging out. Pass null for no reason. 11633 * @param {finesse.interfaces.RequestHandlers} handlers 11634 * An object containing the handlers for the request 11635 * @returns {finesse.restservices.User} 11636 * This User object, to allow cascading 11637 */ 11638 logout: function (reasonCode, handlers) { 11639 return this.setState("LOGOUT", reasonCode, handlers); 11640 }, 11641 11642 /** 11643 * Set the state of the user. 11644 * @param {String} newState 11645 * The state you are setting 11646 * @param {ReasonCode} reasonCode 11647 * The reason this user is logging out. Pass null for no reason. 11648 * @param {finesse.interfaces.RequestHandlers} handlers 11649 * An object containing the handlers for the request 11650 * @see finesse.restservices.User.States 11651 * @returns {finesse.restservices.User} 11652 * This User object, to allow cascading 11653 */ 11654 setState: function (newState, reasonCode, handlers) { 11655 this.isLoaded(); 11656 11657 var options, contentBody = {}; 11658 11659 if (!reasonCode) { 11660 contentBody[this.getRestType()] = { 11661 "state": newState 11662 }; 11663 } else { 11664 contentBody[this.getRestType()] = { 11665 "state": newState, 11666 "reasonCodeId": reasonCode.id 11667 }; 11668 } 11669 11670 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11671 handlers = handlers || {}; 11672 11673 options = { 11674 method: 'PUT', 11675 success: handlers.success, 11676 error: handlers.error, 11677 content: contentBody 11678 }; 11679 11680 // After removing the selective 202 handling, we should be able to just use restRequest 11681 this.restRequest(this.getRestUrl(), options); 11682 11683 return this; // Allow cascading 11684 }, 11685 11686 /** 11687 * Make call to a particular phone number. 11688 * 11689 * @param {String} 11690 * The number to call 11691 * @param {finesse.interfaces.RequestHandlers} handlers 11692 * An object containing the handlers for the request 11693 * @returns {finesse.restservices.User} 11694 * This User object, to allow cascading 11695 */ 11696 makeCall: function (number, handlers) { 11697 this.isLoaded(); 11698 11699 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 11700 11701 return this; // Allow cascading 11702 }, 11703 11704 /** 11705 * Make a silent monitor call to a particular agent's phone number. 11706 * 11707 * @param {String} 11708 * The number to call 11709 * @param {finesse.interfaces.RequestHandlers} handlers 11710 * An object containing the handlers for the request 11711 * @returns {finesse.restservices.User} 11712 * This User object, to allow cascading 11713 */ 11714 makeSMCall: function (number, handlers) { 11715 this.isLoaded(); 11716 11717 var actionType = "SILENT_MONITOR"; 11718 11719 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 11720 11721 return this; // Allow cascading 11722 }, 11723 11724 11725 /** 11726 * Make a silent monitor call to a particular agent's phone number. 11727 * 11728 * @param {String} 11729 * The number to call 11730 * @param {String} dialogUri 11731 * The associated dialog uri of SUPERVISOR_MONITOR call 11732 * @param {finesse.interfaces.RequestHandlers} handlers 11733 * An object containing the handlers for the request 11734 * @see finesse.restservices.dialog 11735 * @returns {finesse.restservices.User} 11736 * This User object, to allow cascading 11737 */ 11738 makeBargeCall:function (number, dialogURI, handlers) { 11739 this.isLoaded(); 11740 var actionType = "BARGE_CALL"; 11741 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 11742 11743 return this; // Allow cascading 11744 }, 11745 11746 /** 11747 * Returns true if the user's current state will result in a pending state change. A pending state 11748 * change is a request to change state that does not result in an immediate state change. For 11749 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 11750 * agent will not change state until the call ends. 11751 * 11752 * The current set of states that result in pending state changes is as follows: 11753 * TALKING 11754 * HOLD 11755 * RESERVED_OUTBOUND_PREVIEW 11756 * @returns {Boolean} True if there is a pending state change. 11757 * @see finesse.restservices.User.States 11758 */ 11759 isPendingStateChange: function () { 11760 var state = this.getState(); 11761 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 11762 }, 11763 11764 /** 11765 * Returns true if the user's current state is WORK or WORK_READY. This is used so 11766 * that a pending state is not cleared when moving into wrap up (work) mode. 11767 * Note that we don't add this as a pending state, since changes while in wrap up 11768 * occur immediately (and we don't want any "pending state" to flash on screen. 11769 * 11770 * @see finesse.restservices.User.States 11771 * @returns {Boolean} True if user is in wrap-up mode. 11772 */ 11773 isWrapUp: function () { 11774 var state = this.getState(); 11775 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 11776 }, 11777 11778 /** 11779 * @private 11780 * Parses a uriString to retrieve the id portion 11781 * @param {String} uriString 11782 * @return {String} id 11783 */ 11784 _parseIdFromUriString : function (uriString) { 11785 return Utilities.getId(uriString); 11786 }, 11787 11788 /** 11789 * Gets the user's Reason Code label. 11790 * Works for both Not Ready and Logout reason codes 11791 * @return {String} the reason code label, or empty string if none 11792 */ 11793 getReasonCodeLabel : function () { 11794 this.isLoaded(); 11795 11796 if (this.getData().reasonCode) { 11797 return this.getData().reasonCode.label; 11798 } else { 11799 return ""; 11800 } 11801 }, 11802 11803 /** 11804 * Gets the user's Not Ready reason code. 11805 * @return {String} Reason Code Id, or undefined if not set or indeterminate 11806 */ 11807 getNotReadyReasonCodeId : function () { 11808 this.isLoaded(); 11809 11810 var reasoncodeIdResult, finesseServerReasonCodeId; 11811 finesseServerReasonCodeId = this.getData().reasonCodeId; 11812 11813 //FinesseServer will give "-l" => we will set to undefined (for convenience) 11814 if (finesseServerReasonCodeId !== "-1") { 11815 reasoncodeIdResult = finesseServerReasonCodeId; 11816 } 11817 11818 return reasoncodeIdResult; 11819 }, 11820 11821 /** 11822 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 11823 * Note that there is no return value; use the success handler to process a 11824 * valid return. 11825 * @param {finesse.interfaces.RequestHandlers} handlers 11826 * An object containing the handlers for the request 11827 * @param {String} reasonCodeId The id for the reason code to lookup 11828 * 11829 */ 11830 getReasonCodeById : function (handlers, reasonCodeId) 11831 { 11832 var self = this, contentBody, reasonCode, url; 11833 contentBody = {}; 11834 11835 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 11836 this.restRequest(url, { 11837 method: 'GET', 11838 success: function (rsp) { 11839 reasonCode = { 11840 uri: rsp.object.ReasonCode.uri, 11841 label: rsp.object.ReasonCode.label, 11842 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 11843 }; 11844 handlers.success(reasonCode); 11845 }, 11846 error: function (rsp) { 11847 handlers.error(rsp); 11848 }, 11849 content: contentBody 11850 }); 11851 }, 11852 11853 /** 11854 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 11855 * @param {String} type (LOGOUT or NOT_READY) 11856 * @param {finesse.interfaces.RequestHandlers} handlers 11857 * An object containing the handlers for the request 11858 */ 11859 _getReasonCodesByType : function (type, handlers) 11860 { 11861 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 11862 11863 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 11864 this.restRequest(url, { 11865 method: 'GET', 11866 success: function (rsp) { 11867 reasonCodes = []; 11868 11869 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 11870 if (reasonCodeArray === undefined) { 11871 reasonCodes = undefined; 11872 } else if (reasonCodeArray[0] !== undefined) { 11873 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 11874 reasonCodes[i] = { 11875 label: rsp.object.ReasonCodes.ReasonCode[i].label, 11876 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 11877 }; 11878 } 11879 } else { 11880 reasonCodes[0] = { 11881 label: rsp.object.ReasonCodes.ReasonCode.label, 11882 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 11883 }; 11884 } 11885 handlers.success(reasonCodes); 11886 }, 11887 error: function (rsp) { 11888 handlers.error(rsp); 11889 }, 11890 content: contentBody 11891 }); 11892 }, 11893 11894 /** 11895 * Performs a GET against Finesse server retrieving all the Signout reason codes. 11896 * Note that there is no return value; use the success handler to process a 11897 * valid return. 11898 * @param {finesse.interfaces.RequestHandlers} handlers 11899 * An object containing the handlers for the request 11900 */ 11901 getSignoutReasonCodes : function (handlers) 11902 { 11903 this._getReasonCodesByType("LOGOUT", handlers); 11904 }, 11905 11906 /** 11907 * Performs a GET against Finesse server retrieving all the Not Ready reason codes. 11908 * Note that there is no return value; use the success handler to process a 11909 * valid return. 11910 * @param {finesse.interfaces.RequestHandlers} handlers 11911 * An object containing the handlers for the request 11912 */ 11913 getNotReadyReasonCodes : function (handlers) 11914 { 11915 this._getReasonCodesByType("NOT_READY", handlers); 11916 } 11917 }); 11918 11919 User.States = /** @lends finesse.restservices.User.States.prototype */ { 11920 /** 11921 * User Login. Note that while this is an action, is not technically a state, since a 11922 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 11923 */ 11924 LOGIN: "LOGIN", 11925 /** 11926 * User is logged out. 11927 */ 11928 LOGOUT: "LOGOUT", 11929 /** 11930 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 11931 */ 11932 NOT_READY: "NOT_READY", 11933 /** 11934 * User is ready for calls. 11935 */ 11936 READY: "READY", 11937 /** 11938 * User has a call coming in, but has not answered it. 11939 */ 11940 RESERVED: "RESERVED", 11941 /** 11942 * User has an outbound call being made, but has not been connected to it. 11943 */ 11944 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 11945 /** 11946 * User has an outbound call's preview information being displayed, but has not acted on it. 11947 */ 11948 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 11949 /** 11950 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 11951 */ 11952 TALKING: "TALKING", 11953 /** 11954 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 11955 */ 11956 HOLD: "HOLD", 11957 /** 11958 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 11959 */ 11960 WORK: "WORK", 11961 /** 11962 * This is the same as WORK, except that after time out user becomes READY. 11963 */ 11964 WORK_READY: "WORK_READY", 11965 /** 11966 * @class Possible User state values. 11967 * @constructs 11968 */ 11969 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11970 11971 }; 11972 11973 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 11974 /** 11975 * Mobile agent is connected (dialed) for each incoming call received. 11976 */ 11977 CALL_BY_CALL: "CALL_BY_CALL", 11978 /** 11979 * Mobile agent is connected (dialed) at login. 11980 */ 11981 NAILED_CONNECTION: "NAILED_CONNECTION", 11982 /** 11983 * @class Possible Mobile Agent Work Mode Types. 11984 * @constructs 11985 */ 11986 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11987 11988 }; 11989 11990 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 11991 /** 11992 * Agent must go into wrap-up when call ends 11993 */ 11994 REQUIRED: "REQUIRED", 11995 /** 11996 * Agent must go into wrap-up when call ends and must enter wrap-up data 11997 */ 11998 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 11999 /** 12000 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 12001 */ 12002 OPTIONAL: "OPTIONAL", 12003 /** 12004 * Agent is not allowed to go into wrap-up when call ends. 12005 */ 12006 NOT_ALLOWED: "NOT_ALLOWED", 12007 /** 12008 * @class Possible Wrap-up Mode Types. 12009 * @constructs 12010 */ 12011 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12012 12013 }; 12014 12015 window.finesse = window.finesse || {}; 12016 window.finesse.restservices = window.finesse.restservices || {}; 12017 window.finesse.restservices.User = User; 12018 12019 return User; 12020 }); 12021 12022 /** 12023 * JavaScript representation of the Finesse Users collection 12024 * object which contains a list of Users objects. 12025 * 12026 * @requires finesse.clientservices.ClientServices 12027 * @requires Class 12028 * @requires finesse.FinesseBase 12029 * @requires finesse.restservices.RestBase 12030 * @requires finesse.restservices.RestCollectionBase 12031 * @requires finesse.restservices.User 12032 */ 12033 12034 /** @private */ 12035 define('restservices/Users',[ 12036 'restservices/RestCollectionBase', 12037 'restservices/RestBase', 12038 'restservices/User' 12039 ], 12040 function (RestCollectionBase, RestBase, User) { 12041 12042 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 12043 12044 /** 12045 * @class 12046 * JavaScript representation of a Users collection object. 12047 * While there is no method provided to retrieve all Users, this collection is 12048 * used to return the Users in a supervised Team. 12049 * @augments finesse.restservices.RestCollectionBase 12050 * @constructs 12051 * @see finesse.restservices.Team 12052 * @see finesse.restservices.User 12053 * @see finesse.restservices.User#getSupervisedTeams 12054 * @example 12055 * // Note: The following method gets an Array of Teams, not a Collection. 12056 * _teams = _user.getSupervisedTeams(); 12057 * if (_teams.length > 0) { 12058 * _team0Users = _teams[0].getUsers(); 12059 * } 12060 */ 12061 _fakeConstuctor: function () { 12062 /* This is here to hide the real init constructor from the public docs */ 12063 }, 12064 12065 /** 12066 * @private 12067 * JavaScript representation of the Finesse Users collection 12068 * object which contains a list of Users objects. 12069 * 12070 * @param {Object} options 12071 * An object with the following properties:<ul> 12072 * <li><b>id:</b> The id of the object being constructed</li> 12073 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12074 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12075 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12076 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12077 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12078 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12079 * <li><b>content:</b> {String} Raw string of response</li> 12080 * <li><b>object:</b> {Object} Parsed object of response</li> 12081 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12082 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12083 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12084 * </ul></li> 12085 * </ul></li> 12086 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12087 **/ 12088 init: function (options) { 12089 this._super(options); 12090 }, 12091 12092 /** 12093 * @private 12094 * Gets the REST class for the current object - this is the Users class. 12095 */ 12096 getRestClass: function () { 12097 return Users; 12098 }, 12099 12100 /** 12101 * @private 12102 * Gets the REST class for the objects that make up the collection. - this 12103 * is the User class. 12104 */ 12105 getRestItemClass: function () { 12106 return User; 12107 }, 12108 12109 /** 12110 * @private 12111 * Gets the REST type for the current object - this is a "Users". 12112 */ 12113 getRestType: function () { 12114 return "Users"; 12115 }, 12116 12117 /** 12118 * @private 12119 * Gets the REST type for the objects that make up the collection - this is "User". 12120 */ 12121 getRestItemType: function () { 12122 return "User"; 12123 }, 12124 12125 /** 12126 * @private 12127 * Gets the node path for the current object - this is the team Users node 12128 * @returns {String} The node path 12129 */ 12130 getXMPPNodePath: function () { 12131 return this.getRestUrl(); 12132 }, 12133 12134 /** 12135 * @private 12136 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 12137 * This needs to be done because the GET /Team/id/Users API is missing 12138 * @returns {Users} This Users (collection) object to allow cascading 12139 */ 12140 _doGET: function (handlers) { 12141 var _this = this; 12142 handlers = handlers || {}; 12143 // Only do this for /Team/id/Users 12144 if (this._restObj && this._restObj.getRestType() === "Team") { 12145 this._restObj._doGET({ 12146 success: function (rspObj) { 12147 // Making sure the response was a valid Team 12148 if (_this._restObj._validate(rspObj.object)) { 12149 // Shimmying the response to look like a Users collection by extracting it from the Team response 12150 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 12151 handlers.success(rspObj); 12152 } else { 12153 handlers.error(rspObj); 12154 } 12155 }, 12156 error: handlers.error 12157 }); 12158 return this; // Allow cascading 12159 } else { 12160 return this._super(handlers); 12161 } 12162 }, 12163 12164 /** 12165 * @private 12166 * Override default to indicates that the collection doesn't support making 12167 * requests. 12168 */ 12169 supportsRequests: false, 12170 12171 /** 12172 * @private 12173 * Indicates that this collection handles the subscription for its items 12174 */ 12175 handlesItemSubscription: true, 12176 12177 /** 12178 * @private 12179 * Override default to indicate that we need to subscribe explicitly 12180 */ 12181 explicitSubscription: true 12182 12183 }); 12184 12185 window.finesse = window.finesse || {}; 12186 window.finesse.restservices = window.finesse.restservices || {}; 12187 window.finesse.restservices.Users = Users; 12188 12189 return Users; 12190 }); 12191 12192 /** 12193 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 12194 * 12195 * @requires finesse.clientservices.ClientServices 12196 * @requires Class 12197 * @requires finesse.FinesseBase 12198 * @requires finesse.restservices.RestBase 12199 */ 12200 12201 /** @private */ 12202 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 12203 12204 var TeamNotReadyReasonCode = RestBase.extend( { 12205 12206 /** 12207 * @class 12208 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 12209 * methods to operate on the object against the server. 12210 * 12211 * @param {Object} options 12212 * An object with the following properties:<ul> 12213 * <li><b>id:</b> The id of the object being constructed</li> 12214 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12215 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12216 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12217 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12218 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12219 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12220 * <li><b>content:</b> {String} Raw string of response</li> 12221 * <li><b>object:</b> {Object} Parsed object of response</li> 12222 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12223 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12224 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12225 * </ul></li> 12226 * </ul></li> 12227 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12228 * @constructs 12229 **/ 12230 init: function (options) { 12231 this._super(options); 12232 }, 12233 12234 /** 12235 * @private 12236 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 12237 * @returns {Object} The TeamNotReadyReasonCode class. 12238 */ 12239 getRestClass: function () { 12240 return TeamNotReadyReasonCode; 12241 }, 12242 12243 /** 12244 * @private 12245 * Gets the REST type for the current object - this is a "ReasonCode". 12246 * @returns {String} The ReasonCode string. 12247 */ 12248 getRestType: function () { 12249 return "ReasonCode"; 12250 }, 12251 12252 /** 12253 * @private 12254 * Override default to indicate that this object doesn't support making 12255 * requests. 12256 */ 12257 supportsRequests: false, 12258 12259 /** 12260 * @private 12261 * Override default to indicate that this object doesn't support subscriptions. 12262 */ 12263 supportsSubscriptions: false, 12264 12265 /** 12266 * Getter for the category. 12267 * @returns {String} The category. 12268 */ 12269 getCategory: function () { 12270 this.isLoaded(); 12271 return this.getData().category; 12272 }, 12273 12274 /** 12275 * Getter for the code. 12276 * @returns {String} The code. 12277 */ 12278 getCode: function () { 12279 this.isLoaded(); 12280 return this.getData().code; 12281 }, 12282 12283 /** 12284 * Getter for the label. 12285 * @returns {String} The label. 12286 */ 12287 getLabel: function () { 12288 this.isLoaded(); 12289 return this.getData().label; 12290 }, 12291 12292 /** 12293 * Getter for the forAll value. 12294 * @returns {String} The forAll. 12295 */ 12296 getForAll: function () { 12297 this.isLoaded(); 12298 return this.getData().forAll; 12299 }, 12300 12301 /** 12302 * Getter for the Uri value. 12303 * @returns {String} The Uri. 12304 */ 12305 getUri: function () { 12306 this.isLoaded(); 12307 return this.getData().uri; 12308 } 12309 12310 }); 12311 12312 window.finesse = window.finesse || {}; 12313 window.finesse.restservices = window.finesse.restservices || {}; 12314 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 12315 12316 return TeamNotReadyReasonCode; 12317 }); 12318 12319 /** 12320 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 12321 * object which contains a list of TeamNotReadyReasonCode objects. 12322 * 12323 * @requires finesse.clientservices.ClientServices 12324 * @requires Class 12325 * @requires finesse.FinesseBase 12326 * @requires finesse.restservices.RestBase 12327 * @requires finesse.restservices.Dialog 12328 * @requires finesse.restservices.RestCollectionBase 12329 */ 12330 12331 /** @private */ 12332 define('restservices/TeamNotReadyReasonCodes',[ 12333 'restservices/RestCollectionBase', 12334 'restservices/RestBase', 12335 'restservices/TeamNotReadyReasonCode' 12336 ], 12337 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 12338 12339 var TeamNotReadyReasonCodes = RestCollectionBase.extend( { 12340 12341 /** 12342 * @class 12343 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 12344 * methods to operate on the object against the server. 12345 * 12346 * @param {Object} options 12347 * An object with the following properties:<ul> 12348 * <li><b>id:</b> The id of the object being constructed</li> 12349 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12350 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12351 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12352 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12353 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12354 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12355 * <li><b>content:</b> {String} Raw string of response</li> 12356 * <li><b>object:</b> {Object} Parsed object of response</li> 12357 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12358 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12359 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12360 * </ul></li> 12361 * </ul></li> 12362 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12363 * @augments finesse.restservices.RestCollectionBase 12364 * @constructs 12365 **/ 12366 init: function (options) { 12367 this._super(options); 12368 }, 12369 12370 /** 12371 * @private 12372 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 12373 */ 12374 getRestClass: function () { 12375 return TeamNotReadyReasonCodes; 12376 }, 12377 12378 /** 12379 * @private 12380 * Gets the REST class for the objects that make up the collection. - this 12381 * is the TeamNotReadyReasonCode class. 12382 */ 12383 getRestItemClass: function () { 12384 return TeamNotReadyReasonCode; 12385 }, 12386 12387 /** 12388 * @private 12389 * Gets the REST type for the current object - this is a "ReasonCodes". 12390 */ 12391 getRestType: function () { 12392 return "ReasonCodes"; 12393 }, 12394 12395 /** 12396 * @private 12397 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 12398 */ 12399 getRestUrl: function () { 12400 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 12401 var restObj = this._restObj, 12402 restUrl = ""; 12403 //Prepend the base REST object if one was provided. 12404 //Otherwise prepend with the default webapp name. 12405 if (restObj instanceof RestBase) { 12406 restUrl += restObj.getRestUrl(); 12407 } 12408 else { 12409 restUrl += "/finesse/api"; 12410 } 12411 //Append the REST type. 12412 restUrl += "/ReasonCodes?category=NOT_READY"; 12413 //Append ID if it is not undefined, null, or empty. 12414 if (this._id) { 12415 restUrl += "/" + this._id; 12416 } 12417 return restUrl; 12418 }, 12419 12420 /** 12421 * @private 12422 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 12423 */ 12424 getRestItemType: function () { 12425 return "ReasonCode"; 12426 }, 12427 12428 /** 12429 * @private 12430 * Override default to indicates that the collection supports making 12431 * requests. 12432 */ 12433 supportsRequests: true, 12434 12435 /** 12436 * @private 12437 * Override default to indicate that this object doesn't support subscriptions. 12438 */ 12439 supportsRestItemSubscriptions: false, 12440 12441 /** 12442 * @private 12443 * Retrieve the Not Ready Reason Codes. 12444 * 12445 * @returns {TeamNotReadyReasonCodes} 12446 * This TeamNotReadyReasonCodes object to allow cascading. 12447 */ 12448 get: function () { 12449 // set loaded to false so it will rebuild the collection after the get 12450 this._loaded = false; 12451 // reset collection 12452 this._collection = {}; 12453 // perform get 12454 this._synchronize(); 12455 return this; 12456 }, 12457 12458 /** 12459 * @private 12460 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 12461 * @param {Object} reasonCodes 12462 * @param {String} contentBody 12463 * @param successHandler 12464 * @return {function} 12465 */ 12466 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 12467 return function (rsp) { 12468 // Update internal structure based on response. Here we 12469 // inject the contentBody from the PUT request into the 12470 // rsp.object element to mimic a GET as a way to take 12471 // advantage of the existing _processResponse method. 12472 rsp.object = contentBody; 12473 reasonCodes._processResponse(rsp); 12474 12475 //Remove the injected contentBody object before cascading response 12476 rsp.object = {}; 12477 12478 //cascade response back to consumer's response handler 12479 successHandler(rsp); 12480 }; 12481 }, 12482 12483 /** 12484 * @private 12485 * Perform the REST API PUT call to update the reason code assignments for the team 12486 * @param {string[]} newValues 12487 * @param handlers 12488 */ 12489 update: function (newValues, handlers) { 12490 this.isLoaded(); 12491 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 12492 12493 contentBody[this.getRestType()] = { 12494 }; 12495 12496 for (i in newValues) { 12497 if (newValues.hasOwnProperty(i)) { 12498 innerObject = { 12499 "uri": newValues[i] 12500 }; 12501 contentBodyInner.push(innerObject); 12502 } 12503 } 12504 12505 contentBody[this.getRestType()] = { 12506 "ReasonCode" : contentBodyInner 12507 }; 12508 12509 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12510 handlers = handlers || {}; 12511 12512 this.restRequest(this.getRestUrl(), { 12513 method: 'PUT', 12514 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12515 error: handlers.error, 12516 content: contentBody 12517 }); 12518 12519 return this; // Allow cascading 12520 } 12521 }); 12522 12523 window.finesse = window.finesse || {}; 12524 window.finesse.restservices = window.finesse.restservices || {}; 12525 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 12526 12527 return TeamNotReadyReasonCodes; 12528 }); 12529 12530 /** 12531 * JavaScript representation of the Finesse Team Wrap Up Reason object. 12532 * 12533 * @requires finesse.clientservices.ClientServices 12534 * @requires Class 12535 * @requires finesse.FinesseBase 12536 * @requires finesse.restservices.RestBase 12537 */ 12538 /** @private */ 12539 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 12540 12541 var TeamWrapUpReason = RestBase.extend({ 12542 12543 /** 12544 * @class 12545 * JavaScript representation of a TeamWrapUpReason object. Also exposes 12546 * methods to operate on the object against the server. 12547 * 12548 * @param {Object} options 12549 * An object with the following properties:<ul> 12550 * <li><b>id:</b> The id of the object being constructed</li> 12551 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12552 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12553 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12554 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12555 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12556 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12557 * <li><b>content:</b> {String} Raw string of response</li> 12558 * <li><b>object:</b> {Object} Parsed object of response</li> 12559 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12560 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12561 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12562 * </ul></li> 12563 * </ul></li> 12564 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12565 * @constructs 12566 **/ 12567 init: function (options) { 12568 this._super(options); 12569 }, 12570 12571 /** 12572 * @private 12573 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 12574 * @returns {Object} The TeamWrapUpReason class. 12575 */ 12576 getRestClass: function () { 12577 return TeamWrapUpReason; 12578 }, 12579 12580 /** 12581 * @private 12582 * Gets the REST type for the current object - this is a "WrapUpReason". 12583 * @returns {String} The WrapUpReason string. 12584 */ 12585 getRestType: function () { 12586 return "WrapUpReason"; 12587 }, 12588 12589 /** 12590 * @private 12591 * Override default to indicate that this object doesn't support making 12592 * requests. 12593 */ 12594 supportsRequests: false, 12595 12596 /** 12597 * @private 12598 * Override default to indicate that this object doesn't support subscriptions. 12599 */ 12600 supportsSubscriptions: false, 12601 12602 /** 12603 * Getter for the label. 12604 * @returns {String} The label. 12605 */ 12606 getLabel: function () { 12607 this.isLoaded(); 12608 return this.getData().label; 12609 }, 12610 12611 /** 12612 * @private 12613 * Getter for the forAll value. 12614 * @returns {Boolean} True if global 12615 */ 12616 getForAll: function () { 12617 this.isLoaded(); 12618 return this.getData().forAll; 12619 }, 12620 12621 /** 12622 * @private 12623 * Getter for the Uri value. 12624 * @returns {String} The Uri. 12625 */ 12626 getUri: function () { 12627 this.isLoaded(); 12628 return this.getData().uri; 12629 } 12630 }); 12631 12632 window.finesse = window.finesse || {}; 12633 window.finesse.restservices = window.finesse.restservices || {}; 12634 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 12635 12636 return TeamWrapUpReason; 12637 }); 12638 12639 /** 12640 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 12641 * object which contains a list of Wrap-Up Reasons objects. 12642 * 12643 * @requires finesse.clientservices.ClientServices 12644 * @requires Class 12645 * @requires finesse.FinesseBase 12646 * @requires finesse.restservices.RestBase 12647 * @requires finesse.restservices.Dialog 12648 * @requires finesse.restservices.RestCollectionBase 12649 */ 12650 /** @private */ 12651 define('restservices/TeamWrapUpReasons',[ 12652 'restservices/RestCollectionBase', 12653 'restservices/RestBase', 12654 'restservices/TeamWrapUpReason' 12655 ], 12656 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 12657 12658 var TeamWrapUpReasons = RestCollectionBase.extend({ 12659 12660 /** 12661 * @class 12662 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 12663 * methods to operate on the object against the server. 12664 * 12665 * @param {Object} options 12666 * An object with the following properties:<ul> 12667 * <li><b>id:</b> The id of the object being constructed</li> 12668 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12669 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12670 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12671 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12672 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12673 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12674 * <li><b>content:</b> {String} Raw string of response</li> 12675 * <li><b>object:</b> {Object} Parsed object of response</li> 12676 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12677 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12678 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12679 * </ul></li> 12680 * </ul></li> 12681 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12682 * @constructs 12683 **/ 12684 init: function (options) { 12685 this._super(options); 12686 }, 12687 12688 /** 12689 * @private 12690 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 12691 */ 12692 getRestClass: function () { 12693 return TeamWrapUpReasons; 12694 }, 12695 12696 /** 12697 * @private 12698 * Gets the REST class for the objects that make up the collection. - this 12699 * is the TeamWrapUpReason class. 12700 */ 12701 getRestItemClass: function () { 12702 return TeamWrapUpReason; 12703 }, 12704 12705 /** 12706 * @private 12707 * Gets the REST type for the current object - this is a "WrapUpReasons". 12708 */ 12709 getRestType: function () { 12710 return "WrapUpReasons"; 12711 }, 12712 12713 /** 12714 * @private 12715 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 12716 */ 12717 getRestItemType: function () { 12718 return "WrapUpReason"; 12719 }, 12720 12721 /** 12722 * @private 12723 * Override default to indicates that the collection supports making 12724 * requests. 12725 */ 12726 supportsRequests: true, 12727 12728 /** 12729 * @private 12730 * Override default to indicate that this object doesn't support subscriptions. 12731 */ 12732 supportsRestItemSubscriptions: false, 12733 12734 /** 12735 * Retrieve the Team Wrap Up Reasons. 12736 * 12737 * @returns {finesse.restservices.TeamWrapUpReasons} 12738 * This TeamWrapUpReasons object to allow cascading. 12739 */ 12740 get: function () { 12741 // set loaded to false so it will rebuild the collection after the get 12742 this._loaded = false; 12743 // reset collection 12744 this._collection = {}; 12745 // perform get 12746 this._synchronize(); 12747 return this; 12748 }, 12749 12750 /** 12751 * Set up the PutSuccessHandler for TeamWrapUpReasons 12752 * @param {Object} wrapUpReasons 12753 * @param {Object} contentBody 12754 * @param successHandler 12755 * @returns response 12756 */ 12757 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 12758 return function (rsp) { 12759 // Update internal structure based on response. Here we 12760 // inject the contentBody from the PUT request into the 12761 // rsp.object element to mimic a GET as a way to take 12762 // advantage of the existing _processResponse method. 12763 rsp.object = contentBody; 12764 12765 wrapUpReasons._processResponse(rsp); 12766 12767 //Remove the injected contentBody object before cascading response 12768 rsp.object = {}; 12769 12770 //cascade response back to consumer's response handler 12771 successHandler(rsp); 12772 }; 12773 }, 12774 12775 /** 12776 * Perform the REST API PUT call to update the reason code assignments for the team 12777 * @param {String Array} newValues 12778 * @param handlers 12779 * @returns {Object} this 12780 */ 12781 update: function (newValues, handlers) { 12782 this.isLoaded(); 12783 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 12784 12785 contentBody[this.getRestType()] = { 12786 }; 12787 12788 for (i in newValues) { 12789 if (newValues.hasOwnProperty(i)) { 12790 innerObject = { 12791 "uri": newValues[i] 12792 }; 12793 contentBodyInner.push(innerObject); 12794 } 12795 } 12796 12797 contentBody[this.getRestType()] = { 12798 "WrapUpReason" : contentBodyInner 12799 }; 12800 12801 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12802 handlers = handlers || {}; 12803 12804 this.restRequest(this.getRestUrl(), { 12805 method: 'PUT', 12806 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12807 error: handlers.error, 12808 content: contentBody 12809 }); 12810 12811 return this; // Allow cascading 12812 } 12813 }); 12814 12815 window.finesse = window.finesse || {}; 12816 window.finesse.restservices = window.finesse.restservices || {}; 12817 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 12818 12819 return TeamWrapUpReasons; 12820 }); 12821 12822 /** 12823 * JavaScript representation of a TeamSignOutReasonCode. 12824 * 12825 * @requires finesse.clientservices.ClientServices 12826 * @requires Class 12827 * @requires finesse.FinesseBase 12828 * @requires finesse.restservices.RestBase 12829 */ 12830 12831 /** @private */ 12832 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 12833 var TeamSignOutReasonCode = RestBase.extend({ 12834 12835 /** 12836 * @class 12837 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 12838 * methods to operate on the object against the server. 12839 * 12840 * @param {Object} options 12841 * An object with the following properties:<ul> 12842 * <li><b>id:</b> The id of the object being constructed</li> 12843 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12844 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12845 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12846 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12847 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12848 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12849 * <li><b>content:</b> {String} Raw string of response</li> 12850 * <li><b>object:</b> {Object} Parsed object of response</li> 12851 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12852 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12853 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12854 * </ul></li> 12855 * </ul></li> 12856 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12857 * @constructs 12858 * @ignore 12859 **/ 12860 init: function (options) { 12861 this._super(options); 12862 }, 12863 12864 /** 12865 * @private 12866 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 12867 * @returns {Object} The TeamSignOutReasonCode class. 12868 */ 12869 getRestClass: function () { 12870 return TeamSignOutReasonCode; 12871 }, 12872 12873 /** 12874 * @private 12875 * Gets the REST type for the current object - this is a "ReasonCode". 12876 * @returns {String} The ReasonCode string. 12877 */ 12878 getRestType: function () { 12879 return "ReasonCode"; 12880 }, 12881 12882 /** 12883 * @private 12884 * Override default to indicate that this object doesn't support making 12885 * requests. 12886 */ 12887 supportsRequests: false, 12888 12889 /** 12890 * @private 12891 * Override default to indicate that this object doesn't support subscriptions. 12892 */ 12893 supportsSubscriptions: false, 12894 12895 /** 12896 * Getter for the category. 12897 * @returns {String} The category. 12898 */ 12899 getCategory: function () { 12900 this.isLoaded(); 12901 return this.getData().category; 12902 }, 12903 12904 /** 12905 * Getter for the code. 12906 * @returns {String} The code. 12907 */ 12908 getCode: function () { 12909 this.isLoaded(); 12910 return this.getData().code; 12911 }, 12912 12913 /** 12914 * Getter for the label. 12915 * @returns {String} The label. 12916 */ 12917 getLabel: function () { 12918 this.isLoaded(); 12919 return this.getData().label; 12920 }, 12921 12922 /** 12923 * Getter for the forAll value. 12924 * @returns {String} The forAll. 12925 */ 12926 getForAll: function () { 12927 this.isLoaded(); 12928 return this.getData().forAll; 12929 }, 12930 12931 /** 12932 * Getter for the Uri value. 12933 * @returns {String} The Uri. 12934 */ 12935 getUri: function () { 12936 this.isLoaded(); 12937 return this.getData().uri; 12938 } 12939 12940 }); 12941 12942 window.finesse = window.finesse || {}; 12943 window.finesse.restservices = window.finesse.restservices || {}; 12944 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 12945 12946 return TeamSignOutReasonCode; 12947 }); 12948 12949 /** 12950 * JavaScript representation of the TeamSignOutReasonCodes collection 12951 * object which contains a list of TeamSignOutReasonCode objects. 12952 * 12953 * @requires finesse.clientservices.ClientServices 12954 * @requires Class 12955 * @requires finesse.FinesseBase 12956 * @requires finesse.restservices.RestBase 12957 * @requires finesse.restservices.Dialog 12958 * @requires finesse.restservices.RestCollectionBase 12959 */ 12960 12961 /** @private */ 12962 define('restservices/TeamSignOutReasonCodes',[ 12963 'restservices/RestCollectionBase', 12964 'restservices/RestBase', 12965 'restservices/TeamSignOutReasonCode' 12966 ], 12967 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 12968 12969 var TeamSignOutReasonCodes = RestCollectionBase.extend({ 12970 /** 12971 * @class 12972 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 12973 * methods to operate on the object against the server. 12974 * 12975 * @param {Object} options 12976 * An object with the following properties:<ul> 12977 * <li><b>id:</b> The id of the object being constructed</li> 12978 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12979 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12980 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12981 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12982 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12983 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12984 * <li><b>content:</b> {String} Raw string of response</li> 12985 * <li><b>object:</b> {Object} Parsed object of response</li> 12986 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12987 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12988 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12989 * </ul></li> 12990 * </ul></li> 12991 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12992 * @constructs 12993 **/ 12994 init: function (options) { 12995 this._super(options); 12996 }, 12997 12998 /** 12999 * @private 13000 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 13001 */ 13002 getRestClass: function () { 13003 return TeamSignOutReasonCodes; 13004 }, 13005 13006 /** 13007 * @private 13008 * Gets the REST class for the objects that make up the collection. - this 13009 * is the TeamSignOutReasonCode class. 13010 */ 13011 getRestItemClass: function () { 13012 return TeamSignOutReasonCode; 13013 }, 13014 13015 /** 13016 * @private 13017 * Gets the REST type for the current object - this is a "ReasonCodes". 13018 */ 13019 getRestType: function () { 13020 return "ReasonCodes"; 13021 }, 13022 13023 /** 13024 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 13025 */ 13026 getRestUrl: function () { 13027 var restObj = this._restObj, restUrl = ""; 13028 13029 //Prepend the base REST object if one was provided. 13030 //Otherwise prepend with the default webapp name. 13031 if (restObj instanceof RestBase) { 13032 restUrl += restObj.getRestUrl(); 13033 } else { 13034 restUrl += "/finesse/api"; 13035 } 13036 //Append the REST type. 13037 restUrl += "/ReasonCodes?category=LOGOUT"; 13038 //Append ID if it is not undefined, null, or empty. 13039 if (this._id) { 13040 restUrl += "/" + this._id; 13041 } 13042 return restUrl; 13043 }, 13044 13045 /** 13046 * @private 13047 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 13048 */ 13049 getRestItemType: function () { 13050 return "ReasonCode"; 13051 }, 13052 13053 /** 13054 * @private 13055 * Override default to indicates that the collection supports making requests. 13056 */ 13057 supportsRequests: true, 13058 13059 /** 13060 * @private 13061 * Override default to indicates that the collection does not subscribe to its objects. 13062 */ 13063 supportsRestItemSubscriptions: false, 13064 13065 /** 13066 * Retrieve the Sign Out Reason Codes. 13067 * 13068 * @returns {finesse.restservices.TeamSignOutReasonCodes} 13069 * This TeamSignOutReasonCodes object to allow cascading. 13070 */ 13071 get: function () { 13072 // set loaded to false so it will rebuild the collection after the get 13073 this._loaded = false; 13074 // reset collection 13075 this._collection = {}; 13076 // perform get 13077 this._synchronize(); 13078 return this; 13079 }, 13080 13081 /* We only use PUT and GET on Reason Code team assignments 13082 * @param {Object} contact 13083 * @param {Object} contentBody 13084 * @param {Function} successHandler 13085 */ 13086 createPutSuccessHandler: function (contact, contentBody, successHandler) { 13087 return function (rsp) { 13088 // Update internal structure based on response. Here we 13089 // inject the contentBody from the PUT request into the 13090 // rsp.object element to mimic a GET as a way to take 13091 // advantage of the existing _processResponse method. 13092 rsp.object = contentBody; 13093 contact._processResponse(rsp); 13094 13095 //Remove the injected contentBody object before cascading response 13096 rsp.object = {}; 13097 13098 //cascade response back to consumer's response handler 13099 successHandler(rsp); 13100 }; 13101 }, 13102 13103 /** 13104 * Update - This should be all that is needed. 13105 * @param {Object} newValues 13106 * @param {Object} handlers 13107 * @returns {finesse.restservices.TeamSignOutReasonCodes} 13108 * This TeamSignOutReasonCodes object to allow cascading. 13109 */ 13110 update: function (newValues, handlers) { 13111 this.isLoaded(); 13112 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 13113 13114 contentBody[this.getRestType()] = { 13115 }; 13116 13117 for (i in newValues) { 13118 if (newValues.hasOwnProperty(i)) { 13119 innerObject = { 13120 "uri": newValues[i] 13121 }; 13122 contentBodyInner.push(innerObject); 13123 } 13124 } 13125 13126 contentBody[this.getRestType()] = { 13127 "ReasonCode" : contentBodyInner 13128 }; 13129 13130 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13131 handlers = handlers || {}; 13132 13133 this.restRequest(this.getRestUrl(), { 13134 method: 'PUT', 13135 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 13136 error: handlers.error, 13137 content: contentBody 13138 }); 13139 13140 return this; // Allow cascading 13141 } 13142 13143 }); 13144 13145 window.finesse = window.finesse || {}; 13146 window.finesse.restservices = window.finesse.restservices || {}; 13147 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 13148 13149 return TeamSignOutReasonCodes; 13150 }); 13151 13152 /** 13153 * JavaScript representation of the Finesse PhoneBook Assignment object. 13154 * 13155 * @requires finesse.clientservices.ClientServices 13156 * @requires Class 13157 * @requires finesse.FinesseBase 13158 * @requires finesse.restservices.RestBase 13159 */ 13160 13161 /** 13162 * The following comment prevents JSLint errors concerning undefined global variables. 13163 * It tells JSLint that these identifiers are defined elsewhere. 13164 */ 13165 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 13166 13167 /** The following comment is to prevent jslint errors about 13168 * using variables before they are defined. 13169 */ 13170 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 13171 13172 /** @private */ 13173 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 13174 var TeamPhoneBook = RestBase.extend({ 13175 13176 /** 13177 * @class 13178 * JavaScript representation of a PhoneBook object. Also exposes 13179 * methods to operate on the object against the server. 13180 * 13181 * @param {Object} options 13182 * An object with the following properties:<ul> 13183 * <li><b>id:</b> The id of the object being constructed</li> 13184 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13185 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13186 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13187 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13188 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13189 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13190 * <li><b>content:</b> {String} Raw string of response</li> 13191 * <li><b>object:</b> {Object} Parsed object of response</li> 13192 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13193 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13194 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13195 * </ul></li> 13196 * </ul></li> 13197 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13198 * @constructs 13199 **/ 13200 init: function (options) { 13201 this._super(options); 13202 }, 13203 13204 /** 13205 * @private 13206 * Gets the REST class for the current object - this is the PhoneBooks class. 13207 * @returns {Object} The PhoneBooks class. 13208 */ 13209 getRestClass: function () { 13210 return TeamPhoneBook; 13211 }, 13212 13213 /** 13214 * @private 13215 * Gets the REST type for the current object - this is a "PhoneBook". 13216 * @returns {String} The PhoneBook string. 13217 */ 13218 getRestType: function () { 13219 return "PhoneBook"; 13220 }, 13221 13222 /** 13223 * @private 13224 * Override default to indicate that this object doesn't support making 13225 * requests. 13226 */ 13227 supportsRequests: false, 13228 13229 /** 13230 * @private 13231 * Override default to indicate that this object doesn't support subscriptions. 13232 */ 13233 supportsSubscriptions: false, 13234 13235 /** 13236 * Getter for the name. 13237 * @returns {String} The name. 13238 */ 13239 getName: function () { 13240 this.isLoaded(); 13241 return this.getData().name; 13242 }, 13243 13244 /** 13245 * Getter for the Uri value. 13246 * @returns {String} The Uri. 13247 */ 13248 getUri: function () { 13249 this.isLoaded(); 13250 return this.getData().uri; 13251 } 13252 13253 }); 13254 13255 window.finesse = window.finesse || {}; 13256 window.finesse.restservices = window.finesse.restservices || {}; 13257 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 13258 13259 return TeamPhoneBook; 13260 }); 13261 13262 /** 13263 * JavaScript representation of the Finesse PhoneBook Assignments collection 13264 * object which contains a list of Not Ready Reason Codes objects. 13265 * 13266 * @requires finesse.clientservices.ClientServices 13267 * @requires Class 13268 * @requires finesse.FinesseBase 13269 * @requires finesse.restservices.RestBase 13270 * @requires finesse.restservices.Dialog 13271 * @requires finesse.restservices.RestCollectionBase 13272 */ 13273 13274 /** 13275 * The following comment prevents JSLint errors concerning undefined global variables. 13276 * It tells JSLint that these identifiers are defined elsewhere. 13277 */ 13278 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 13279 13280 /** The following comment is to prevent jslint errors about 13281 * using variables before they are defined. 13282 */ 13283 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 13284 13285 /** @private */ 13286 define('restservices/TeamPhoneBooks',[ 13287 'restservices/RestCollectionBase', 13288 'restservices/RestBase', 13289 'restservices/TeamPhoneBook' 13290 ], 13291 function (RestCollectionBase, RestBase, TeamPhoneBook) { 13292 var TeamPhoneBooks = RestCollectionBase.extend({ 13293 13294 /** 13295 * @class 13296 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 13297 * methods to operate on the object against the server. 13298 * 13299 * @param {Object} options 13300 * An object with the following properties:<ul> 13301 * <li><b>id:</b> The id of the object being constructed</li> 13302 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13303 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13304 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13305 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13306 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13307 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13308 * <li><b>content:</b> {String} Raw string of response</li> 13309 * <li><b>object:</b> {Object} Parsed object of response</li> 13310 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13311 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13312 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13313 * </ul></li> 13314 * </ul></li> 13315 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13316 * @constructs 13317 **/ 13318 init: function (options) { 13319 this._super(options); 13320 }, 13321 13322 /** 13323 * @private 13324 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 13325 */ 13326 getRestClass: function () { 13327 return TeamPhoneBooks; 13328 }, 13329 13330 /** 13331 * @private 13332 * Gets the REST class for the objects that make up the collection. - this 13333 * is the TeamPhoneBooks class. 13334 */ 13335 getRestItemClass: function () { 13336 return TeamPhoneBook; 13337 }, 13338 13339 /** 13340 * @private 13341 * Gets the REST type for the current object - this is a "ReasonCodes". 13342 */ 13343 getRestType: function () { 13344 return "PhoneBooks"; 13345 }, 13346 13347 /** 13348 * Overrides the parent class. Returns the url for the PhoneBooks resource 13349 */ 13350 getRestUrl: function () { 13351 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 13352 var restObj = this._restObj, 13353 restUrl = ""; 13354 //Prepend the base REST object if one was provided. 13355 if (restObj instanceof RestBase) { 13356 restUrl += restObj.getRestUrl(); 13357 } 13358 //Otherwise prepend with the default webapp name. 13359 else { 13360 restUrl += "/finesse/api"; 13361 } 13362 //Append the REST type. 13363 restUrl += "/PhoneBooks"; 13364 //Append ID if it is not undefined, null, or empty. 13365 if (this._id) { 13366 restUrl += "/" + this._id; 13367 } 13368 return restUrl; 13369 }, 13370 13371 /** 13372 * @private 13373 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 13374 */ 13375 getRestItemType: function () { 13376 return "PhoneBook"; 13377 }, 13378 13379 /** 13380 * @private 13381 * Override default to indicates that the collection supports making 13382 * requests. 13383 */ 13384 supportsRequests: true, 13385 13386 /** 13387 * @private 13388 * Override default to indicates that the collection subscribes to its objects. 13389 */ 13390 supportsRestItemSubscriptions: false, 13391 13392 /** 13393 * Retrieve the Not Ready Reason Codes. 13394 * 13395 * @returns {finesse.restservices.TeamPhoneBooks} 13396 * This TeamPhoneBooks object to allow cascading. 13397 */ 13398 get: function () { 13399 // set loaded to false so it will rebuild the collection after the get 13400 /** @private */ 13401 this._loaded = false; 13402 // reset collection 13403 /** @private */ 13404 this._collection = {}; 13405 // perform get 13406 this._synchronize(); 13407 return this; 13408 }, 13409 13410 /* We only use PUT and GET on Reason Code team assignments 13411 */ 13412 createPutSuccessHandler: function(contact, contentBody, successHandler){ 13413 return function (rsp) { 13414 // Update internal structure based on response. Here we 13415 // inject the contentBody from the PUT request into the 13416 // rsp.object element to mimic a GET as a way to take 13417 // advantage of the existing _processResponse method. 13418 rsp.object = contentBody; 13419 contact._processResponse(rsp); 13420 13421 //Remove the injected Contact object before cascading response 13422 rsp.object = {}; 13423 13424 //cascade response back to consumer's response handler 13425 successHandler(rsp); 13426 }; 13427 }, 13428 13429 /** 13430 * Update - This should be all that is needed. 13431 */ 13432 update: function (newValues, handlers) { 13433 this.isLoaded(); 13434 var contentBody = {}, contentBodyInner = [], i, innerObject; 13435 13436 contentBody[this.getRestType()] = { 13437 }; 13438 13439 for (i in newValues) { 13440 if (newValues.hasOwnProperty(i)) { 13441 innerObject = {}; 13442 innerObject = { 13443 "uri": newValues[i] 13444 }; 13445 contentBodyInner.push(innerObject); 13446 } 13447 } 13448 13449 contentBody[this.getRestType()] = { 13450 "PhoneBook" : contentBodyInner 13451 }; 13452 13453 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13454 handlers = handlers || {}; 13455 13456 this.restRequest(this.getRestUrl(), { 13457 method: 'PUT', 13458 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 13459 error: handlers.error, 13460 content: contentBody 13461 }); 13462 13463 return this; // Allow cascading 13464 } 13465 13466 }); 13467 13468 window.finesse = window.finesse || {}; 13469 window.finesse.restservices = window.finesse.restservices || {}; 13470 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 13471 13472 return TeamPhoneBooks; 13473 }); 13474 13475 /** 13476 * JavaScript representation of the Finesse LayoutConfig object 13477 * @requires ClientServices 13478 * @requires finesse.FinesseBase 13479 * @requires finesse.restservices.RestBase 13480 */ 13481 13482 /** @private */ 13483 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 13484 /** @private */ 13485 var LayoutConfig = RestBase.extend({ 13486 13487 /** 13488 * @class 13489 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 13490 * on the object against the server. 13491 * 13492 * @param {String} id 13493 * Not required... 13494 * @param {Object} callbacks 13495 * An object containing callbacks for instantiation and runtime 13496 * @param {Function} callbacks.onLoad(this) 13497 * Callback to invoke upon successful instantiation 13498 * @param {Function} callbacks.onLoadError(rsp) 13499 * Callback to invoke on instantiation REST request error 13500 * as passed by finesse.clientservices.ClientServices.ajax() 13501 * { 13502 * status: {Number} The HTTP status code returned 13503 * content: {String} Raw string of response 13504 * object: {Object} Parsed object of response 13505 * error: {Object} Wrapped exception that was caught 13506 * error.errorType: {String} Type of error that was caught 13507 * error.errorMessage: {String} Message associated with error 13508 * } 13509 * @param {Function} callbacks.onChange(this) 13510 * Callback to invoke upon successful update 13511 * @param {Function} callbacks.onError(rsp) 13512 * Callback to invoke on update error (refresh or event) 13513 * as passed by finesse.clientservices.ClientServices.ajax() 13514 * { 13515 * status: {Number} The HTTP status code returned 13516 * content: {String} Raw string of response 13517 * object: {Object} Parsed object of response 13518 * error: {Object} Wrapped exception that was caught 13519 * error.errorType: {String} Type of error that was caught 13520 * error.errorMessage: {String} Message associated with error 13521 * } 13522 * 13523 * @constructs 13524 */ 13525 init: function (callbacks) { 13526 this._super("", callbacks); 13527 //when post is performed and id is empty 13528 /*if (id === "") { 13529 this._loaded = true; 13530 }*/ 13531 this._layoutxml = {}; 13532 }, 13533 13534 /** 13535 * Returns REST class of LayoutConfig object 13536 */ 13537 getRestClass: function () { 13538 return LayoutConfig; 13539 }, 13540 13541 /** 13542 * The type of this REST object is LayoutConfig 13543 */ 13544 getRestType: function () { 13545 return "LayoutConfig"; 13546 }, 13547 13548 /** 13549 * Gets the REST URL of this object. 13550 * 13551 * If the parent has an id, the id is appended. 13552 * On occasions of POST, it will not have an id. 13553 */ 13554 getRestUrl: function () { 13555 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 13556 /*if (this._id) { 13557 layoutUri = layoutUri + "/" + this._id; 13558 }*/ 13559 return layoutUri; 13560 }, 13561 13562 /** 13563 * This API does not support subscription 13564 */ 13565 supportsSubscriptions: false, 13566 13567 keepRestResponse: true, 13568 13569 13570 /** 13571 * Gets finesselayout.xml retrieved from the API call 13572 */ 13573 getLayoutxml: function () { 13574 this.isLoaded(); 13575 var layoutxml = this.getData().layoutxml; 13576 13577 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 13578 layoutxml = layoutxml.replace(/&/g,"&"); 13579 13580 return layoutxml; 13581 }, 13582 13583 /** 13584 * Gets the type of this LayoutConfig object 13585 */ 13586 /* 13587 getType: function () { 13588 this.isLoaded(); 13589 return this.getData().type; 13590 },*/ 13591 13592 /** 13593 * Retrieve the LayoutConfig settings. 13594 * If the id is not provided the API call will fail. 13595 * @returns {LayoutConfig} 13596 * This LayoutConfig object to allow cascading. 13597 */ 13598 get: function () { 13599 this._synchronize(); 13600 return this; 13601 }, 13602 13603 /** 13604 * Closure handle updating of the internal data for the LayoutConfig object 13605 * upon a successful update (PUT) request before calling the intended 13606 * success handler provided by the consumer 13607 * 13608 * @param {Object} 13609 * layoutconfig Reference to this LayoutConfig object 13610 * @param {Object} 13611 * LayoutConfig Object that contains the settings to be 13612 * submitted in the api request 13613 * @param {Function} 13614 * successHandler The success handler specified by the consumer 13615 * of this object 13616 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 13617 */ 13618 13619 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 13620 return function (rsp) { 13621 // Update internal structure based on response. Here we 13622 // inject the contentBody from the PUT request into the 13623 // rsp.object element to mimic a GET as a way to take 13624 // advantage of the existing _processResponse method. 13625 rsp.content = contentBody; 13626 rsp.object.LayoutConfig = {}; 13627 rsp.object.LayoutConfig.finesseLayout = contentBody; 13628 layoutconfig._processResponse(rsp); 13629 13630 //Remove the injected layoutConfig object before cascading response 13631 rsp.object.LayoutConfig = {}; 13632 13633 //cascade response back to consumer's response handler 13634 successHandler(rsp); 13635 }; 13636 }, 13637 13638 /** 13639 * Update LayoutConfig 13640 * @param {Object} finesselayout 13641 * The XML for FinesseLayout being stored 13642 * 13643 * @param {Object} handlers 13644 * An object containing callback handlers for the request. Optional. 13645 * @param {Function} options.success(rsp) 13646 * A callback function to be invoked for a successful request. 13647 * { 13648 * status: {Number} The HTTP status code returned 13649 * content: {String} Raw string of response 13650 * object: {Object} Parsed object of response 13651 * } 13652 * @param {Function} options.error(rsp) 13653 * A callback function to be invoked for an unsuccessful request. 13654 * { 13655 * status: {Number} The HTTP status code returned 13656 * content: {String} Raw string of response 13657 * object: {Object} Parsed object of response (HTTP errors) 13658 * error: {Object} Wrapped exception that was caught 13659 * error.errorType: {String} Type of error that was caught 13660 * error.errorMessage: {String} Message associated with error 13661 * } 13662 * @returns {finesse.restservices.LayoutConfig} 13663 * This LayoutConfig object to allow cascading 13664 */ 13665 13666 update: function (layoutxml, handlers) { 13667 this.isLoaded(); 13668 var contentBody = {}; 13669 13670 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 13671 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 13672 13673 contentBody[this.getRestType()] = { 13674 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 13675 }; 13676 13677 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13678 handlers = handlers || {}; 13679 13680 this.restRequest(this.getRestUrl(), { 13681 method: 'PUT', 13682 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 13683 error: handlers.error, 13684 content: contentBody 13685 }); 13686 13687 return this; // Allow cascading 13688 } 13689 13690 /** 13691 *TODO createPostSuccessHandler needs to be debugged to make it working 13692 * Closure handle creating new LayoutConfig object 13693 * upon a successful create (POST) request before calling the intended 13694 * success handler provided by the consumer 13695 * 13696 * @param {Object} 13697 * layoutconfig Reference to this LayoutConfig object 13698 * @param {Object} 13699 * LayoutConfig Object that contains the settings to be 13700 * submitted in the api request 13701 * @param {Function} 13702 * successHandler The success handler specified by the consumer 13703 * of this object 13704 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 13705 */ 13706 /* 13707 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 13708 return function (rsp) { 13709 13710 rsp.object = contentBody; 13711 layoutconfig._processResponse(rsp); 13712 13713 //Remove the injected layoutConfig object before cascading response 13714 rsp.object = {}; 13715 13716 //cascade response back to consumer's response handler 13717 successHandler(rsp); 13718 }; 13719 }, */ 13720 13721 /** 13722 * TODO Method needs to be debugged to make POST working 13723 * Add LayoutConfig 13724 * @param {Object} finesselayout 13725 * The XML for FinesseLayout being stored 13726 * 13727 * @param {Object} handlers 13728 * An object containing callback handlers for the request. Optional. 13729 * @param {Function} options.success(rsp) 13730 * A callback function to be invoked for a successful request. 13731 * { 13732 * status: {Number} The HTTP status code returned 13733 * content: {String} Raw string of response 13734 * object: {Object} Parsed object of response 13735 * } 13736 * @param {Function} options.error(rsp) 13737 * A callback function to be invoked for an unsuccessful request. 13738 * { 13739 * status: {Number} The HTTP status code returned 13740 * content: {String} Raw string of response 13741 * object: {Object} Parsed object of response (HTTP errors) 13742 * error: {Object} Wrapped exception that was caught 13743 * error.errorType: {String} Type of error that was caught 13744 * error.errorMessage: {String} Message associated with error 13745 * } 13746 * @returns {finesse.restservices.LayoutConfig} 13747 * This LayoutConfig object to allow cascading 13748 */ 13749 /* 13750 add: function (layoutxml, handlers) { 13751 this.isLoaded(); 13752 var contentBody = {}; 13753 13754 13755 contentBody[this.getRestType()] = { 13756 "layoutxml": layoutxml, 13757 "type": "current" 13758 }; 13759 13760 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13761 handlers = handlers || {}; 13762 13763 this.restRequest(this.getRestUrl(), { 13764 method: 'POST', 13765 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 13766 error: handlers.error, 13767 content: contentBody 13768 }); 13769 13770 return this; // Allow cascading 13771 } */ 13772 }); 13773 13774 window.finesse = window.finesse || {}; 13775 window.finesse.restservices = window.finesse.restservices || {}; 13776 window.finesse.restservices.LayoutConfig = LayoutConfig; 13777 13778 return LayoutConfig; 13779 13780 }); 13781 13782 /** 13783 * JavaScript representation of the Finesse LayoutConfig object for a Team. 13784 * 13785 * @requires finesse.clientservices.ClientServices 13786 * @requires Class 13787 * @requires finesse.FinesseBase 13788 * @requires finesse.restservices.RestBase 13789 * @requires finesse.utilities.Utilities 13790 * @requires finesse.restservices.LayoutConfig 13791 */ 13792 13793 /** The following comment is to prevent jslint errors about 13794 * using variables before they are defined. 13795 */ 13796 /*global Exception */ 13797 13798 /** @private */ 13799 define('restservices/TeamLayoutConfig',[ 13800 'restservices/RestBase', 13801 'utilities/Utilities', 13802 'restservices/LayoutConfig' 13803 ], 13804 function (RestBase, Utilities, LayoutConfig) { 13805 13806 var TeamLayoutConfig = RestBase.extend({ 13807 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 13808 keepRestResponse: true, 13809 13810 /** 13811 * @class 13812 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 13813 * methods to operate on the object against the server. 13814 * 13815 * @param {Object} options 13816 * An object with the following properties:<ul> 13817 * <li><b>id:</b> The id of the object being constructed</li> 13818 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13819 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13820 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13821 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13822 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13823 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13824 * <li><b>content:</b> {String} Raw string of response</li> 13825 * <li><b>object:</b> {Object} Parsed object of response</li> 13826 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13827 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13828 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13829 * </ul></li> 13830 * </ul></li> 13831 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13832 * @constructs 13833 **/ 13834 init: function (options) { 13835 this._super(options); 13836 }, 13837 13838 /** 13839 * @private 13840 * Gets the REST class for the current object - this is the LayoutConfigs class. 13841 * @returns {Object} The LayoutConfigs class. 13842 */ 13843 getRestClass: function () { 13844 return TeamLayoutConfig; 13845 }, 13846 13847 /** 13848 * @private 13849 * Gets the REST type for the current object - this is a "LayoutConfig". 13850 * @returns {String} The LayoutConfig string. 13851 */ 13852 getRestType: function () { 13853 return "TeamLayoutConfig"; 13854 }, 13855 13856 /** 13857 * @private 13858 * Override default to indicate that this object doesn't support making 13859 * requests. 13860 */ 13861 supportsRequests: false, 13862 13863 /** 13864 * @private 13865 * Override default to indicate that this object doesn't support subscriptions. 13866 */ 13867 supportsSubscriptions: false, 13868 13869 /** 13870 * Getter for the category. 13871 * @returns {String} The category. 13872 */ 13873 getLayoutXML: function () { 13874 this.isLoaded(); 13875 var layoutxml = this.getData().layoutxml; 13876 13877 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 13878 layoutxml = layoutxml.replace(/&/g,"&"); 13879 13880 return layoutxml; 13881 }, 13882 13883 /** 13884 * Getter for the code. 13885 * @returns {String} The code. 13886 */ 13887 getUseDefault: function () { 13888 this.isLoaded(); 13889 return this.getData().useDefault; 13890 }, 13891 13892 /** 13893 * Retrieve the TeamLayoutConfig. 13894 * 13895 * @returns {finesse.restservices.TeamLayoutConfig} 13896 */ 13897 get: function () { 13898 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 13899 this._id = "0"; 13900 // set loaded to false so it will rebuild the collection after the get 13901 this._loaded = false; 13902 // reset collection 13903 this._collection = {}; 13904 // perform get 13905 this._synchronize(); 13906 return this; 13907 }, 13908 13909 createPutSuccessHandler: function(contact, contentBody, successHandler){ 13910 return function (rsp) { 13911 // Update internal structure based on response. Here we 13912 // inject the contentBody from the PUT request into the 13913 // rsp.object element to mimic a GET as a way to take 13914 // advantage of the existing _processResponse method. 13915 rsp.object = contentBody; 13916 contact._processResponse(rsp); 13917 13918 //Remove the injected Contact object before cascading response 13919 rsp.object = {}; 13920 13921 //cascade response back to consumer's response handler 13922 successHandler(rsp); 13923 }; 13924 }, 13925 13926 put: function (newValues, handlers) { 13927 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 13928 this._id = "0"; 13929 this.isLoaded(); 13930 13931 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 13932 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 13933 contentBody = {}; 13934 13935 contentBody[this.getRestType()] = { 13936 "useDefault": newValues.useDefault, 13937 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 13938 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 13939 }; 13940 13941 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13942 handlers = handlers || {}; 13943 13944 this.restRequest(this.getRestUrl(), { 13945 method: 'PUT', 13946 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 13947 error: handlers.error, 13948 content: contentBody 13949 }); 13950 13951 return this; // Allow cascading 13952 }, 13953 13954 getRestUrl: function(){ 13955 // return team's url + /LayoutConfig 13956 // eg: /api/Team/1/LayoutConfig 13957 if(this._restObj === undefined){ 13958 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 13959 } 13960 return this._restObj.getRestUrl() + '/LayoutConfig'; 13961 } 13962 13963 }); 13964 13965 window.finesse = window.finesse || {}; 13966 window.finesse.restservices = window.finesse.restservices || {}; 13967 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 13968 13969 return TeamLayoutConfig; 13970 }); 13971 13972 /** 13973 * JavaScript representation of a TeamWorkflow. 13974 * 13975 * @requires finesse.clientservices.ClientServices 13976 * @requires Class 13977 * @requires finesse.FinesseBase 13978 * @requires finesse.restservices.RestBase 13979 */ 13980 /** @private */ 13981 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 13982 13983 var TeamWorkflow = RestBase.extend({ 13984 13985 /** 13986 * @class 13987 * JavaScript representation of a TeamWorkflow object. Also exposes 13988 * methods to operate on the object against the server. 13989 * 13990 * @param {Object} options 13991 * An object with the following properties:<ul> 13992 * <li><b>id:</b> The id of the object being constructed</li> 13993 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13994 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13995 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13996 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13997 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13998 * <li><b>status:</b> {Number} The HTTP status description returned</li> 13999 * <li><b>content:</b> {String} Raw string of response</li> 14000 * <li><b>object:</b> {Object} Parsed object of response</li> 14001 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14002 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14003 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14004 * </ul></li> 14005 * </ul></li> 14006 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14007 * @constructs 14008 **/ 14009 init: function (options) { 14010 this._super(options); 14011 }, 14012 14013 /** 14014 * @private 14015 * Gets the REST class for the current object - this is the TeamWorkflow class. 14016 * @returns {Object} The TeamWorkflow class. 14017 */ 14018 getRestClass: function () { 14019 return TeamWorkflow; 14020 }, 14021 14022 /** 14023 * @private 14024 * Gets the REST type for the current object - this is a "Workflow". 14025 * @returns {String} The Workflow string. 14026 */ 14027 getRestType: function () { 14028 return "Workflow"; 14029 }, 14030 14031 /** 14032 * @private 14033 * Override default to indicate that this object doesn't support making 14034 * requests. 14035 */ 14036 supportsRequests: false, 14037 14038 /** 14039 * @private 14040 * Override default to indicate that this object doesn't support subscriptions. 14041 */ 14042 supportsSubscriptions: false, 14043 14044 /** 14045 * Getter for the name. 14046 * @returns {String} The name. 14047 */ 14048 getName: function () { 14049 this.isLoaded(); 14050 return this.getData().name; 14051 }, 14052 14053 /** 14054 * Getter for the description. 14055 * @returns {String} The description. 14056 */ 14057 getDescription: function () { 14058 this.isLoaded(); 14059 return this.getData().description; 14060 }, 14061 14062 /** 14063 * Getter for the Uri value. 14064 * @returns {String} The Uri. 14065 */ 14066 getUri: function () { 14067 this.isLoaded(); 14068 return this.getData().uri; 14069 } 14070 14071 }); 14072 14073 window.finesse = window.finesse || {}; 14074 window.finesse.restservices = window.finesse.restservices || {}; 14075 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 14076 14077 return TeamWorkflow; 14078 }); 14079 14080 /** 14081 * JavaScript representation of the TeamWorkflows collection 14082 * object which contains a list of TeamWorkflow objects. 14083 * 14084 * @requires finesse.clientservices.ClientServices 14085 * @requires Class 14086 * @requires finesse.FinesseBase 14087 * @requires finesse.restservices.RestBase 14088 * @requires finesse.restservices.Dialog 14089 * @requires finesse.restservices.RestCollectionBase 14090 */ 14091 /** @private */ 14092 define('restservices/TeamWorkflows',[ 14093 'restservices/RestCollectionBase', 14094 'restservices/TeamWorkflow', 14095 'restservices/RestBase' 14096 ], 14097 function (RestCollectionBase, TeamWorkflow, RestBase) { 14098 14099 var TeamWorkflows = RestCollectionBase.extend({ 14100 14101 /** 14102 * @class 14103 * JavaScript representation of a TeamWorkflows collection object. Also exposes 14104 * methods to operate on the object against the server. 14105 * 14106 * @param {Object} options 14107 * An object with the following properties:<ul> 14108 * <li><b>id:</b> The id of the object being constructed</li> 14109 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14110 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14111 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14112 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14113 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14114 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14115 * <li><b>content:</b> {String} Raw string of response</li> 14116 * <li><b>object:</b> {Object} Parsed object of response</li> 14117 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14118 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14119 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14120 * </ul></li> 14121 * </ul></li> 14122 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14123 * @constructs 14124 **/ 14125 init: function (options) { 14126 this._super(options); 14127 }, 14128 14129 /** 14130 * @private 14131 * Gets the REST class for the current object - this is the TeamWorkflows class. 14132 */ 14133 getRestClass: function () { 14134 return TeamWorkflows; 14135 }, 14136 14137 /** 14138 * @private 14139 * Gets the REST class for the objects that make up the collection. - this 14140 * is the TeamWorkflow class. 14141 */ 14142 getRestItemClass: function () { 14143 return TeamWorkflow; 14144 }, 14145 14146 /** 14147 * @private 14148 * Gets the REST type for the current object - this is a "Workflows". 14149 */ 14150 getRestType: function () { 14151 return "Workflows"; 14152 }, 14153 14154 /** 14155 * Overrides the parent class. Returns the url for the Workflows resource 14156 */ 14157 getRestUrl: function () { 14158 var restObj = this._restObj, restUrl = ""; 14159 14160 //Prepend the base REST object if one was provided. 14161 //Otherwise prepend with the default webapp name. 14162 if (restObj instanceof RestBase) { 14163 restUrl += restObj.getRestUrl(); 14164 } else { 14165 restUrl += "/finesse/api/Team"; 14166 } 14167 //Append ID if it is not undefined, null, or empty. 14168 if (this._id) { 14169 restUrl += "/" + this._id; 14170 } 14171 //Append the REST type. 14172 restUrl += "/Workflows"; 14173 14174 return restUrl; 14175 }, 14176 14177 /** 14178 * @private 14179 * Gets the REST type for the objects that make up the collection - this is "Workflow". 14180 */ 14181 getRestItemType: function () { 14182 return "Workflow"; 14183 }, 14184 14185 /** 14186 * @private 14187 * Override default to indicates that the collection supports making requests. 14188 */ 14189 supportsRequests: true, 14190 14191 /** 14192 * @private 14193 * Override default to indicates that the collection does not subscribe to its objects. 14194 */ 14195 supportsRestItemSubscriptions: false, 14196 14197 /** 14198 * Retrieve the Sign Out Reason Codes. 14199 * 14200 * @returns {finesse.restservices.TeamWorkflows} 14201 * This TeamWorkflows object to allow cascading. 14202 */ 14203 get: function () { 14204 // set loaded to false so it will rebuild the collection after the get 14205 this._loaded = false; 14206 // reset collection 14207 this._collection = {}; 14208 // perform get 14209 this._synchronize(); 14210 return this; 14211 }, 14212 14213 /* We only use PUT and GET on Reason Code team assignments 14214 * @param {Object} contact 14215 * @param {Object} contentBody 14216 * @param {Function} successHandler 14217 */ 14218 createPutSuccessHandler: function (contact, contentBody, successHandler) { 14219 return function (rsp) { 14220 // Update internal structure based on response. Here we 14221 // inject the contentBody from the PUT request into the 14222 // rsp.object element to mimic a GET as a way to take 14223 // advantage of the existing _processResponse method. 14224 rsp.object = contentBody; 14225 contact._processResponse(rsp); 14226 14227 //Remove the injected contentBody object before cascading response 14228 rsp.object = {}; 14229 14230 //cascade response back to consumer's response handler 14231 successHandler(rsp); 14232 }; 14233 }, 14234 14235 /** 14236 * Update - This should be all that is needed. 14237 * @param {Object} newValues 14238 * @param {Object} handlers 14239 * @returns {finesse.restservices.TeamWorkflows} 14240 * This TeamWorkflows object to allow cascading. 14241 */ 14242 update: function (newValues, handlers) { 14243 this.isLoaded(); 14244 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 14245 14246 contentBody[this.getRestType()] = { 14247 }; 14248 14249 for (i in newValues) { 14250 if (newValues.hasOwnProperty(i)) { 14251 innerObject = { 14252 "uri": newValues[i] 14253 }; 14254 contentBodyInner.push(innerObject); 14255 } 14256 } 14257 14258 contentBody[this.getRestType()] = { 14259 "Workflow" : contentBodyInner 14260 }; 14261 14262 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 14263 handlers = handlers || {}; 14264 14265 this.restRequest(this.getRestUrl(), { 14266 method: 'PUT', 14267 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 14268 error: handlers.error, 14269 content: contentBody 14270 }); 14271 14272 return this; // Allow cascading 14273 } 14274 14275 }); 14276 14277 window.finesse = window.finesse || {}; 14278 window.finesse.restservices = window.finesse.restservices || {}; 14279 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 14280 14281 return TeamWorkflows; 14282 }); 14283 14284 /** 14285 * JavaScript representation of the Finesse Team REST object. 14286 * 14287 * @requires finesse.clientservices.ClientServices 14288 * @requires Class 14289 * @requires finesse.FinesseBase 14290 * @requires finesse.restservices.RestBase 14291 * @requires finesse.restservices.RestCollectionBase 14292 * @requires finesse.restservices.User 14293 * @requires finesse.restservices.Users 14294 */ 14295 14296 /** 14297 * The following comment prevents JSLint errors concerning undefined global variables. 14298 * It tells JSLint that these identifiers are defined elsewhere. 14299 */ 14300 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 14301 14302 /** The following comment is to prevent jslint errors about 14303 * using variables before they are defined. 14304 */ 14305 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 14306 14307 /** @private */ 14308 define('restservices/Team',[ 14309 'restservices/RestBase', 14310 'utilities/Utilities', 14311 'restservices/Users', 14312 'restservices/TeamNotReadyReasonCodes', 14313 'restservices/TeamWrapUpReasons', 14314 'restservices/TeamSignOutReasonCodes', 14315 'restservices/TeamPhoneBooks', 14316 'restservices/TeamLayoutConfig', 14317 'restservices/TeamWorkflows' 14318 ], 14319 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows) { 14320 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 14321 14322 _teamLayoutConfig: null, 14323 14324 /** 14325 * @class 14326 * A Team is a set of Agent Users, typically supervised by one or more Supervisor Users. 14327 * 14328 * @augments finesse.restservices.RestBase 14329 * @see finesse.restservices.User#getSupervisedTeams 14330 * @see finesse.restservices.Users 14331 * @constructs 14332 */ 14333 _fakeConstuctor: function () { 14334 /* This is here to hide the real init constructor from the public docs */ 14335 }, 14336 14337 /** 14338 * @private 14339 * @class 14340 * JavaScript representation of a Team object. Also exposes methods to operate 14341 * on the object against the server. 14342 * 14343 * @param {Object} options 14344 * An object with the following properties:<ul> 14345 * <li><b>id:</b> The id of the object being constructed</li> 14346 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14347 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14348 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14349 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14350 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14351 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14352 * <li><b>content:</b> {String} Raw string of response</li> 14353 * <li><b>object:</b> {Object} Parsed object of response</li> 14354 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14355 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14356 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14357 * </ul></li> 14358 * </ul></li> 14359 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14360 **/ 14361 init: function (options) { 14362 this._super(options); 14363 }, 14364 14365 /** 14366 * @private 14367 * Gets the REST class for the current object - this is the Team class. 14368 * @returns {Object} The Team constructor. 14369 */ 14370 getRestClass: function () { 14371 return finesse.restesrvices.Team; 14372 }, 14373 14374 /** 14375 * @private 14376 * Gets the REST type for the current object - this is a "Team". 14377 * @returns {String} The Team string. 14378 */ 14379 getRestType: function () { 14380 return "Team"; 14381 }, 14382 14383 /** 14384 * @private 14385 * Override default to indicate that this object doesn't support making 14386 * requests. 14387 */ 14388 supportsSubscriptions: false, 14389 14390 /** 14391 * Getter for the team id. 14392 * @returns {String} The team id. 14393 */ 14394 getId: function () { 14395 this.isLoaded(); 14396 return this.getData().id; 14397 }, 14398 14399 /** 14400 * Getter for the team name. 14401 * @returns {String} The team name 14402 */ 14403 getName: function () { 14404 this.isLoaded(); 14405 return this.getData().name; 14406 }, 14407 14408 /** 14409 * @private 14410 * Getter for the team uri. 14411 * @returns {String} The team uri 14412 */ 14413 getUri: function () { 14414 this.isLoaded(); 14415 return this.getData().uri; 14416 }, 14417 14418 /** 14419 * Constructs and returns a collection of Users. 14420 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers. 14421 * @returns {finesse.restservices.Users} Users collection of User objects. 14422 */ 14423 getUsers: function (options) { 14424 this.isLoaded(); 14425 options = options || {}; 14426 14427 options.parentObj = this; 14428 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 14429 // for Users needs the "Users" key to validate the provided payload matches the class type. 14430 options.data = this.getData(); 14431 14432 return new Users(options); 14433 }, 14434 14435 /** 14436 * @private 14437 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 14438 * @param callbacks 14439 * @returns {teamNotReadyReasonCodes} 14440 * A teamNotReadyReasonCodes collection object. 14441 */ 14442 getTeamNotReadyReasonCodes: function (callbacks) { 14443 var options = callbacks || {}; 14444 options.parentObj = this; 14445 this.isLoaded(); 14446 14447 if (!this._teamNotReadyReasonCodes) { 14448 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 14449 } 14450 14451 return this._teamNotReadyReasonCodes; 14452 }, 14453 14454 /** 14455 * @private 14456 * Getter for a teamWrapUpReasons collection object that is associated with Team. 14457 * @param callbacks 14458 * @returns {teamWrapUpReasons} 14459 * A teamWrapUpReasons collection object. 14460 */ 14461 getTeamWrapUpReasons: function (callbacks) { 14462 var options = callbacks || {}; 14463 options.parentObj = this; 14464 this.isLoaded(); 14465 14466 if (!this._teamWrapUpReasons) { 14467 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 14468 } 14469 14470 return this._teamWrapUpReasons; 14471 }, 14472 14473 /** 14474 * @private 14475 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 14476 * @param callbacks 14477 * @returns {teamSignOutReasonCodes} 14478 * A teamSignOutReasonCodes collection object. 14479 */ 14480 14481 getTeamSignOutReasonCodes: function (callbacks) { 14482 var options = callbacks || {}; 14483 options.parentObj = this; 14484 this.isLoaded(); 14485 14486 if (!this._teamSignOutReasonCodes) { 14487 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 14488 } 14489 14490 return this._teamSignOutReasonCodes; 14491 }, 14492 14493 /** 14494 * @private 14495 * Getter for a teamPhoneBooks collection object that is associated with Team. 14496 * @param callbacks 14497 * @returns {teamPhoneBooks} 14498 * A teamPhoneBooks collection object. 14499 */ 14500 getTeamPhoneBooks: function (callbacks) { 14501 var options = callbacks || {}; 14502 options.parentObj = this; 14503 this.isLoaded(); 14504 14505 if (!this._phonebooks) { 14506 this._phonebooks = new TeamPhoneBooks(options); 14507 } 14508 14509 return this._phonebooks; 14510 }, 14511 14512 /** 14513 * @private 14514 * Getter for a teamWorkflows collection object that is associated with Team. 14515 * @param callbacks 14516 * @returns {teamWorkflows} 14517 * A teamWorkflows collection object. 14518 */ 14519 getTeamWorkflows: function (callbacks) { 14520 var options = callbacks || {}; 14521 options.parentObj = this; 14522 this.isLoaded(); 14523 14524 if (!this._workflows) { 14525 this._workflows = new TeamWorkflows(options); 14526 } 14527 14528 return this._workflows; 14529 }, 14530 14531 /** 14532 * @private 14533 * Getter for a teamLayoutConfig object that is associated with Team. 14534 * @param callbacks 14535 * @returns {teamLayoutConfig} 14536 */ 14537 getTeamLayoutConfig: function (callbacks) { 14538 var options = callbacks || {}; 14539 options.parentObj = this; 14540 this.isLoaded(); 14541 14542 if (this._teamLayoutConfig === null) { 14543 this._teamLayoutConfig = new TeamLayoutConfig(options); 14544 } 14545 14546 return this._teamLayoutConfig; 14547 } 14548 14549 }); 14550 14551 window.finesse = window.finesse || {}; 14552 window.finesse.restservices = window.finesse.restservices || {}; 14553 window.finesse.restservices.Team = Team; 14554 14555 return Team; 14556 }); 14557 14558 /** 14559 * JavaScript representation of the Finesse Teams collection. 14560 * object which contains a list of Team objects 14561 * @requires finesse.clientservices.ClientServices 14562 * @requires Class 14563 * @requires finesse.FinesseBase 14564 * @requires finesse.restservices.RestBase 14565 * @requires finesse.restservices.RestCollectionBase 14566 */ 14567 14568 /** @private */ 14569 define('restservices/Teams',[ 14570 'restservices/RestCollectionBase', 14571 'restservices/Team' 14572 ], 14573 function (RestCollectionBase, Team) { 14574 /** @private */ 14575 var Teams = RestCollectionBase.extend({ 14576 14577 /** 14578 * @class 14579 * JavaScript representation of a Teams collection object. Also exposes methods to operate 14580 * on the object against the server. 14581 * 14582 * @param {Object} options 14583 * An object with the following properties:<ul> 14584 * <li><b>id:</b> The id of the object being constructed</li> 14585 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14586 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14587 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14588 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14589 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14590 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14591 * <li><b>content:</b> {String} Raw string of response</li> 14592 * <li><b>object:</b> {Object} Parsed object of response</li> 14593 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14594 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14595 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14596 * </ul></li> 14597 * </ul></li> 14598 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14599 * @constructs 14600 **/ 14601 init: function (options) { 14602 this._super(options); 14603 }, 14604 14605 /** 14606 * @private 14607 * Gets the REST class for the current object - this is the Teams class. 14608 * @returns {Object} The Teams constructor. 14609 */ 14610 getRestClass: function () { 14611 return Teams; 14612 }, 14613 14614 /** 14615 * @private 14616 * Gets the REST class for the objects that make up the collection. - this 14617 * is the Team class. 14618 */ 14619 getRestItemClass: function () { 14620 return Team; 14621 }, 14622 14623 /** 14624 * @private 14625 * Gets the REST type for the current object - this is a "Teams". 14626 * @returns {String} The Teams string. 14627 */ 14628 getRestType: function () { 14629 return "Teams"; 14630 }, 14631 14632 /** 14633 * @private 14634 * Gets the REST type for the objects that make up the collection - this is "Team". 14635 */ 14636 getRestItemType: function () { 14637 return "Team"; 14638 }, 14639 14640 /** 14641 * @private 14642 * Override default to indicates that the collection supports making 14643 * requests. 14644 */ 14645 supportsRequests: true, 14646 14647 /** 14648 * @private 14649 * Override default to indicate that this object doesn't support subscriptions. 14650 */ 14651 supportsRestItemSubscriptions: false, 14652 14653 /** 14654 * @private 14655 * Retrieve the Teams. This call will re-query the server and refresh the collection. 14656 * 14657 * @returns {finesse.restservices.Teams} 14658 * This Teams object to allow cascading. 14659 */ 14660 get: function () { 14661 // set loaded to false so it will rebuild the collection after the get 14662 this._loaded = false; 14663 // reset collection 14664 this._collection = {}; 14665 // perform get 14666 this._synchronize(); 14667 return this; 14668 } 14669 14670 }); 14671 14672 window.finesse = window.finesse || {}; 14673 window.finesse.restservices = window.finesse.restservices || {}; 14674 window.finesse.restservices.Teams = Teams; 14675 14676 return Teams; 14677 }); 14678 14679 /** 14680 * JavaScript representation of the Finesse SystemInfo object 14681 * 14682 * @requires finesse.clientservices.ClientServices 14683 * @requires Class 14684 * @requires finesse.FinesseBase 14685 * @requires finesse.restservices.RestBase 14686 */ 14687 14688 /** @private */ 14689 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 14690 14691 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 14692 /** 14693 * @private 14694 * Returns whether this object supports subscriptions 14695 */ 14696 supportsSubscriptions: false, 14697 14698 doNotRefresh: true, 14699 14700 /** 14701 * @class 14702 * JavaScript representation of a SystemInfo object. 14703 * 14704 * @augments finesse.restservices.RestBase 14705 * @see finesse.restservices.SystemInfo.Statuses 14706 * @constructs 14707 */ 14708 _fakeConstuctor: function () { 14709 /* This is here to hide the real init constructor from the public docs */ 14710 }, 14711 14712 /** 14713 * @private 14714 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 14715 * on the object against the server. 14716 * 14717 * @param {Object} options 14718 * An object with the following properties:<ul> 14719 * <li><b>id:</b> The id of the object being constructed</li> 14720 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14721 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14722 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14723 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14724 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14725 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14726 * <li><b>content:</b> {String} Raw string of response</li> 14727 * <li><b>object:</b> {Object} Parsed object of response</li> 14728 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14729 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14730 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14731 * </ul></li> 14732 * </ul></li> 14733 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14734 **/ 14735 init: function (id, callbacks, restObj) 14736 { 14737 this._super(id, callbacks, restObj); 14738 }, 14739 14740 /** 14741 * @private 14742 * Gets the REST class for the current object - this is the SystemInfo object. 14743 */ 14744 getRestClass: function () { 14745 return SystemInfo; 14746 }, 14747 14748 /** 14749 * @private 14750 * Gets the REST type for the current object - this is a "SystemInfo". 14751 */ 14752 getRestType: function () 14753 { 14754 return "SystemInfo"; 14755 }, 14756 14757 _validate: function (obj) 14758 { 14759 return true; 14760 }, 14761 14762 /** 14763 * Returns the status of the Finesse system. 14764 * IN_SERVICE if the Finesse API reports that it is in service, 14765 * OUT_OF_SERVICE otherwise. 14766 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 14767 */ 14768 getStatus: function () { 14769 this.isLoaded(); 14770 return this.getData().status; 14771 }, 14772 14773 /** 14774 * Returns the current timestamp from this SystemInfo object. 14775 * This is used to calculate time drift delta between server and client. 14776 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 14777 */ 14778 getCurrentTimestamp: function () { 14779 this.isLoaded(); 14780 return this.getData().currentTimestamp; 14781 }, 14782 14783 /** 14784 * Getter for the xmpp domain of the system. 14785 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 14786 */ 14787 getXmppDomain: function () { 14788 this.isLoaded(); 14789 return this.getData().xmppDomain; 14790 }, 14791 14792 /** 14793 * Getter for the xmpp pubsub domain of the system. 14794 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 14795 */ 14796 getXmppPubSubDomain: function () { 14797 this.isLoaded(); 14798 return this.getData().xmppPubSubDomain; 14799 }, 14800 14801 /** 14802 * Getter for the deployment type (UCCE or UCCX). 14803 * @returns {String} "UCCE" or "UCCX" 14804 */ 14805 getDeploymentType: function () { 14806 this.isLoaded(); 14807 return this.getData().deploymentType; 14808 }, 14809 14810 /** 14811 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 14812 * @returns {Boolean} True for single node deployments, false otherwise. 14813 */ 14814 isSingleNode: function () { 14815 var secondary = this.getData().secondaryNode; 14816 if (secondary && secondary.host) { 14817 return false; 14818 } 14819 return true; 14820 }, 14821 14822 /** 14823 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 14824 * This is useful for getting the FQDN of the current Finesse server. 14825 * @param {String} ...arguments[]... - any number of arguments to match against 14826 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 14827 */ 14828 getThisHost: function () { 14829 var i, 14830 primary = this.getData().primaryNode, 14831 secondary = this.getData().secondaryNode; 14832 14833 for (i = 0; (i < arguments.length); i = i + 1) { 14834 if (primary && arguments[i] === primary.host) { 14835 return primary.host; 14836 } else if (secondary && arguments[i] === secondary.host) { 14837 return secondary.host; 14838 } 14839 } 14840 }, 14841 14842 /** 14843 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 14844 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 14845 * @param {String} arguments - any number of arguments to match against 14846 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 14847 */ 14848 getAlternateHost: function () { 14849 var i, 14850 isPrimary = false, 14851 primary = this.getData().primaryNode, 14852 secondary = this.getData().secondaryNode, 14853 xmppDomain = this.getData().xmppDomain, 14854 alternateHost; 14855 14856 if (primary && primary.host) { 14857 if (xmppDomain === primary.host) { 14858 isPrimary = true; 14859 } 14860 if (secondary && secondary.host) { 14861 if (isPrimary) { 14862 return secondary.host; 14863 } 14864 return primary.host; 14865 } 14866 } 14867 } 14868 }); 14869 14870 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 14871 /** 14872 * Finesse is in service. 14873 */ 14874 IN_SERVICE: "IN_SERVICE", 14875 /** 14876 * Finesse is not in service. 14877 */ 14878 OUT_OF_SERVICE: "OUT_OF_SERVICE", 14879 /** 14880 * @class SystemInfo status values. 14881 * @constructs 14882 */ 14883 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 14884 14885 }; 14886 14887 window.finesse = window.finesse || {}; 14888 window.finesse.restservices = window.finesse.restservices || {}; 14889 window.finesse.restservices.SystemInfo = SystemInfo; 14890 14891 return SystemInfo; 14892 }); 14893 14894 /** 14895 * Provides standard way resolve message keys with substitution 14896 * 14897 * @requires finesse.container.I18n or gadgets.Prefs 14898 */ 14899 14900 // Add Utilities to the finesse.utilities namespace 14901 define('utilities/I18n',[], function () { 14902 var I18n = (function () { 14903 14904 /** 14905 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 14906 * @private 14907 */ 14908 var _getMsg; 14909 14910 return { 14911 /** 14912 * Provides a message resolver for this utility singleton. 14913 * @param {Function} getMsg 14914 * A function that returns a string given a message key. 14915 * If the key is not found, this function must return 14916 * something that tests false (i.e. undefined or ""). 14917 */ 14918 setGetter : function (getMsg) { 14919 _getMsg = getMsg; 14920 }, 14921 14922 /** 14923 * Resolves the given message key, also performing substitution. 14924 * This generic utility will use a custom function to resolve the key 14925 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 14926 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 14927 * upon the first invocation and store that reference for efficiency. 14928 * 14929 * Since this will construct a new gadgets.Prefs object, it is recommended 14930 * for gadgets to explicitly provide the setter to prevent duplicate 14931 * gadgets.Prefs objects. This does not apply if your gadget does not need 14932 * access to gadgets.Prefs other than getMsg. 14933 * 14934 * @param {String} key 14935 * The key to lookup 14936 * @param {String} arguments 14937 * Arguments for substitution 14938 * @returns {String/Function} 14939 * The resolved string if successful, otherwise a function that returns 14940 * a '???' string that can also be casted into a string. 14941 */ 14942 getString : function (key) { 14943 var prefs, i, retStr, noMsg, getFailed = ""; 14944 if (!_getMsg) { 14945 if (finesse.container && finesse.container.I18n) { 14946 _getMsg = finesse.container.I18n.getMsg; 14947 } else if (gadgets) { 14948 prefs = new gadgets.Prefs(); 14949 _getMsg = prefs.getMsg; 14950 } 14951 } 14952 14953 try { 14954 retStr = _getMsg(key); 14955 } catch (e) { 14956 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 14957 } 14958 14959 if (retStr) { // Lookup was successful, perform substitution (if any) 14960 for (i = 1; i < arguments.length; i += 1) { 14961 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 14962 } 14963 //in order to fix French text with single quotes in it, we need to replace \' with ' 14964 return retStr.replace(/\\'/g, "'"); 14965 } 14966 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 14967 /** @private */ 14968 noMsg = function () { 14969 return "???" + key + "???" + getFailed; 14970 }; 14971 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 14972 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 14973 /** @private */ 14974 noMsg.toString = function () { 14975 return "???" + key + "???" + getFailed; 14976 }; 14977 return noMsg; 14978 14979 } 14980 }; 14981 }()); 14982 14983 window.finesse = window.finesse || {}; 14984 window.finesse.utilities = window.finesse.utilities || {}; 14985 window.finesse.utilities.I18n = I18n; 14986 14987 return I18n; 14988 }); 14989 14990 /** 14991 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 14992 * 14993 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 14994 */ 14995 // Add Utilities to the finesse.utilities namespace 14996 define('utilities/Logger',[], function () { 14997 var Logger = (function () { 14998 14999 var 15000 15001 /** @private **/ 15002 debugOn, 15003 15004 /** 15005 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 15006 * @param num is the number to pad to 2 digits 15007 * @returns a two digit padded string 15008 * @private 15009 */ 15010 padTwoDigits = function (num) { 15011 return (num < 10) ? '0' + num : num; 15012 }, 15013 15014 /** 15015 * Checks to see if we have a console - this allows us to support Firefox or IE. 15016 * @returns {Boolean} True for Firefox, False for IE 15017 * @private 15018 */ 15019 hasConsole = function () { 15020 var retval = false; 15021 try 15022 { 15023 if (window.console !== undefined) 15024 { 15025 retval = true; 15026 } 15027 } 15028 catch (err) 15029 { 15030 retval = false; 15031 } 15032 15033 return retval; 15034 }, 15035 15036 /** 15037 * Gets a timestamp. 15038 * @returns {String} is a timestamp in the following format: HH:MM:SS 15039 * @private 15040 */ 15041 getTimeStamp = function () { 15042 var date = new Date(), timeStr; 15043 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 15044 15045 return timeStr; 15046 }; 15047 15048 return { 15049 /** 15050 * Enable debug mode. Debug mode may impact performance on the UI. 15051 * 15052 * @param {Boolean} enable 15053 * True to enable debug logging. 15054 * @private 15055 */ 15056 setDebug : function (enable) { 15057 debugOn = enable; 15058 }, 15059 15060 /** 15061 * Logs a string as DEBUG. 15062 * 15063 * @param str is the string to log. 15064 * @private 15065 */ 15066 log : function (str) { 15067 var timeStr = getTimeStamp(); 15068 15069 if (debugOn) { 15070 if (hasConsole()) 15071 { 15072 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 15073 } 15074 } 15075 }, 15076 15077 /** 15078 * Logs a string as INFO. 15079 * 15080 * @param str is the string to log. 15081 * @private 15082 */ 15083 info : function (str) { 15084 var timeStr = getTimeStamp(); 15085 15086 if (hasConsole()) 15087 { 15088 window.console.info(timeStr + ": " + "INFO" + " - " + str); 15089 } 15090 }, 15091 15092 /** 15093 * Logs a string as WARN. 15094 * 15095 * @param str is the string to log. 15096 * @private 15097 */ 15098 warn : function (str) { 15099 var timeStr = getTimeStamp(); 15100 15101 if (hasConsole()) 15102 { 15103 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 15104 } 15105 }, 15106 /** 15107 * Logs a string as ERROR. 15108 * 15109 * @param str is the string to log. 15110 * @private 15111 */ 15112 error : function (str) { 15113 var timeStr = getTimeStamp(); 15114 15115 if (hasConsole()) 15116 { 15117 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 15118 } 15119 } 15120 }; 15121 }()); 15122 15123 return Logger; 15124 }); 15125 15126 /** 15127 * Allows gadgets to call the log function to publish client logging messages over the hub. 15128 * 15129 * @requires OpenAjax 15130 */ 15131 /** @private */ 15132 define('cslogger/ClientLogger',[], function () { 15133 15134 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 15135 var _hub, _logTopic, _originId, _sessId, _host, 15136 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 15137 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 15138 15139 /** 15140 * Gets timestamp drift stored in sessionStorage 15141 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 15142 * @private 15143 */ 15144 getTsDrift = function() { 15145 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 15146 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 15147 } 15148 else { 15149 return undefined; 15150 } 15151 }, 15152 15153 /** 15154 * Sets timestamp drift in sessionStorage 15155 * @param delta is the timestamp drift between server.and client. 15156 * @private 15157 */ 15158 setTsDrift = function(delta) { 15159 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 15160 }, 15161 15162 /** 15163 * Gets Finesse server timezone offset from GMT in seconds 15164 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 15165 * @private 15166 */ 15167 getServerOffset = function() { 15168 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 15169 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 15170 } 15171 else { 15172 return undefined; 15173 } 15174 }, 15175 15176 /** 15177 * Sets server timezone offset 15178 * @param sec is the server timezone GMT offset in seconds. 15179 * @private 15180 */ 15181 setServerOffset = function(sec) { 15182 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 15183 }, 15184 15185 /** 15186 * Checks to see if we have a console. 15187 * @returns Whether the console object exists. 15188 * @private 15189 */ 15190 hasConsole = function () { 15191 try { 15192 if (window.console !== undefined) { 15193 return true; 15194 } 15195 } 15196 catch (err) { 15197 // ignore and return false 15198 } 15199 15200 return false; 15201 }, 15202 15203 /** 15204 * Gets a short form (6 character) session ID from sessionStorage 15205 * @private 15206 */ 15207 getSessId = function() { 15208 if (!_sessId) { 15209 //when _sessId not defined yet, initiate it 15210 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 15211 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 15212 } 15213 else { 15214 _sessId=" "; 15215 } 15216 } 15217 return _sessId; 15218 }, 15219 15220 /** 15221 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 15222 * @param num is the number to pad to 2 digits 15223 * @returns a two digit padded string 15224 * @private 15225 */ 15226 padTwoDigits = function (num) 15227 { 15228 return (num < 10) ? '0' + num : num; 15229 }, 15230 15231 /** 15232 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 15233 * @param num is the number to pad to 3 digits 15234 * @returns a three digit padded string 15235 * @private 15236 */ 15237 padThreeDigits = function (num) 15238 { 15239 if (num < 10) 15240 { 15241 return '00'+num; 15242 } 15243 else if (num < 100) 15244 { 15245 return '0'+num; 15246 } 15247 else 15248 { 15249 return num; 15250 } 15251 }, 15252 15253 /** 15254 * Compute the "hour" 15255 * 15256 * @param s is time in seconds 15257 * @returns {String} which is the hour 15258 * @private 15259 */ 15260 ho = function (s) { 15261 return ((s/60).toString()).split(".")[0]; 15262 }, 15263 15264 /** 15265 * Gets local timezone offset string. 15266 * 15267 * @param t is the time in seconds 15268 * @param s is the separator character between hours and minutes, e.g. ':' 15269 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 15270 * @private 15271 */ 15272 getGmtOffString = function (min,s) { 15273 var t, sign; 15274 if (min<0) { 15275 t = -min; 15276 sign = "-"; 15277 } 15278 else { 15279 t = min; 15280 sign = "+"; 15281 } 15282 15283 if (s===':') { 15284 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 15285 } 15286 else { 15287 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 15288 } 15289 }, 15290 15291 /** 15292 * Gets short form of a month name in English 15293 * 15294 * @param monthNum is zero-based month number 15295 * @returns {String} is short form of month name in English 15296 * @private 15297 */ 15298 getMonthShortStr = function (monthNum) { 15299 var result; 15300 try { 15301 result = MONTH[monthNum]; 15302 } 15303 catch (err) { 15304 if (hasConsole()) { 15305 window.console.log("Month must be between 0 and 11"); 15306 } 15307 } 15308 return result; 15309 }, 15310 15311 /** 15312 * Gets a timestamp. 15313 * @param aDate is a javascript Date object 15314 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 15315 * @private 15316 */ 15317 getDateTimeStamp = function (aDate) 15318 { 15319 var date, off, timeStr; 15320 if (aDate === null) { 15321 date = new Date(); 15322 } 15323 else { 15324 date = aDate; 15325 } 15326 off = -1*date.getTimezoneOffset(); 15327 timeStr = date.getFullYear().toString() + "-" + 15328 padTwoDigits(date.getMonth()+1) + "-" + 15329 padTwoDigits (date.getDate()) + "T"+ 15330 padTwoDigits(date.getHours()) + ":" + 15331 padTwoDigits(date.getMinutes()) + ":" + 15332 padTwoDigits(date.getSeconds())+"." + 15333 padThreeDigits(date.getMilliseconds()) + " "+ 15334 getGmtOffString(off, ':'); 15335 15336 return timeStr; 15337 }, 15338 15339 /** 15340 * Gets drift-adjusted timestamp. 15341 * @param aTimestamp is a timestamp in milliseconds 15342 * @param drift is a timestamp drift in milliseconds 15343 * @param serverOffset is a timezone GMT offset in minutes 15344 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 15345 * @private 15346 */ 15347 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 15348 { 15349 var date, timeStr, localOffset; 15350 if (aTimestamp === null) { 15351 return "--- -- ---- --:--:--.--- -----"; 15352 } 15353 else if (drift === undefined || serverOffset === undefined) { 15354 if (hasConsole()) { 15355 window.console.log("drift or serverOffset must be a number"); 15356 } 15357 return "--- -- ---- --:--:--.--- -----"; 15358 } 15359 else { 15360 //need to get a zone diff in minutes 15361 localOffset = (new Date()).getTimezoneOffset(); 15362 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 15363 timeStr = getMonthShortStr(date.getMonth()) + " "+ 15364 padTwoDigits (date.getDate())+ " "+ 15365 date.getFullYear().toString() + " "+ 15366 padTwoDigits(date.getHours()) + ":" + 15367 padTwoDigits(date.getMinutes()) + ":" + 15368 padTwoDigits(date.getSeconds())+"." + 15369 padThreeDigits(date.getMilliseconds())+" "+ 15370 getGmtOffString(serverOffset, ''); 15371 return timeStr; 15372 } 15373 }, 15374 15375 /** 15376 * Logs a message to a hidden textarea element on the page 15377 * 15378 * @param msg is the string to log. 15379 * @private 15380 */ 15381 writeToLogOutput = function (msg) { 15382 var logOutput = document.getElementById("finesseLogOutput"); 15383 15384 if (logOutput === null) 15385 { 15386 logOutput = document.createElement("textarea"); 15387 logOutput.id = "finesseLogOutput"; 15388 logOutput.style.display = "none"; 15389 document.body.appendChild(logOutput); 15390 } 15391 15392 if (logOutput.value === "") 15393 { 15394 logOutput.value = msg; 15395 } 15396 else 15397 { 15398 logOutput.value = logOutput.value + "\n" + msg; 15399 } 15400 }, 15401 15402 /* 15403 * Logs a message to console 15404 * @param str is the string to log. * @private 15405 */ 15406 logToConsole = function (str) 15407 { 15408 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 15409 now = new Date(); 15410 timeStr = getDateTimeStamp(now); 15411 if (getTsDrift() !== undefined) { 15412 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 15413 } 15414 else { 15415 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 15416 } 15417 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 15418 // Log to console 15419 if (hasConsole()) { 15420 window.console.log(msg); 15421 } 15422 15423 //Uncomment to print logs to hidden textarea. 15424 //writeToLogOutput(msg); 15425 15426 return msg; 15427 }; 15428 return { 15429 15430 /** 15431 * Publishes a Log Message over the hub. 15432 * 15433 * @param {String} message 15434 * The string to log. 15435 * @example 15436 * _clientLogger.log("This is some important message for MyGadget"); 15437 * 15438 */ 15439 log : function (message) { 15440 if(_hub) { 15441 _hub.publish(_logTopic, logToConsole(_originId + message)); 15442 } 15443 }, 15444 15445 /** 15446 * @class 15447 * Allows gadgets to call the log function to publish client logging messages over the hub. 15448 * 15449 * @constructs 15450 */ 15451 _fakeConstuctor: function () { 15452 /* This is here so we can document init() as a method rather than as a constructor. */ 15453 }, 15454 15455 /** 15456 * Initiates the client logger with a hub a gadgetId and gadget's config object. 15457 * @param {Object} hub 15458 * The hub to communicate with. 15459 * @param {String} gadgetId 15460 * A unique string to identify which gadget is doing the logging. 15461 * @param {Object} config 15462 * The config object used to get host name for that thirdparty gadget 15463 * @example 15464 * var _clientLogger = finesse.cslogger.ClientLogger; 15465 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 15466 * 15467 */ 15468 init: function (hub, gadgetId, config) { 15469 _hub = hub; 15470 _logTopic = "finesse.clientLogging." + gadgetId; 15471 _originId = gadgetId + " : "; 15472 if ((config === undefined) || (config === "undefined")) 15473 { 15474 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 15475 } 15476 else 15477 { 15478 _host = ((config && config.host)?config.host : "?.?.?.?"); 15479 } 15480 } 15481 }; 15482 }()); 15483 15484 window.finesse = window.finesse || {}; 15485 window.finesse.cslogger = window.finesse.cslogger || {}; 15486 window.finesse.cslogger.ClientLogger = ClientLogger; 15487 15488 finesse = finesse || {}; 15489 /** @namespace Supports writing messages to a central log. */ 15490 finesse.cslogger = finesse.cslogger || {}; 15491 15492 return ClientLogger; 15493 }); 15494 15495 /* using variables before they are defined. 15496 */ 15497 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 15498 15499 /** 15500 * Allows each gadget to communicate with the server to send logs. 15501 */ 15502 15503 /** 15504 * @class 15505 * @private 15506 * Allows each product to initialize its method of storage 15507 */ 15508 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 15509 15510 var FinesseLogger = (function () { 15511 15512 var 15513 15514 /** 15515 * Array use to collect ongoing logs in memory 15516 * @private 15517 */ 15518 _logArray = [], 15519 15520 /** 15521 * The final data string sent to the server, =_logArray.join 15522 * @private 15523 */ 15524 _logStr = "", 15525 15526 /** 15527 * Keep track of size of log 15528 * @private 15529 */ 15530 _logSize = 0, 15531 15532 /** 15533 * Flag to keep track show/hide of send log link 15534 * @private 15535 */ 15536 _sendLogShown = false, 15537 15538 /** 15539 * Flag to keep track if local log initialized 15540 * @private 15541 */ 15542 _loggingInitialized = false, 15543 15544 15545 /** 15546 * local log size limit 15547 * @private 15548 */ 15549 _maxLocalStorageSize = 5000000, 15550 15551 /** 15552 * half local log size limit 15553 * @private 15554 */ 15555 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 15556 15557 15558 /** 15559 * threshold for purge 15560 * @private 15561 */ 15562 _purgeStartPercent = 0.75, 15563 15564 /** 15565 * log item prefix 15566 * @private 15567 */ 15568 _linePrefix = null, 15569 15570 /** 15571 * locallog session 15572 * @private 15573 */ 15574 _session = null, 15575 15576 /** 15577 * Flag to keep track show/hide of send log link 15578 * @private 15579 */ 15580 _sessionKey = null, 15581 /** 15582 * Log session metadata 15583 * @private 15584 */ 15585 _logInfo = {}, 15586 15587 /** 15588 * Flag to find sessions 15589 * @private 15590 */ 15591 _findSessionsObj = null, 15592 15593 /** 15594 * Wrap up console.log esp. for IE9 15595 * @private 15596 */ 15597 _myConsoleLog = function (str) { 15598 if (window.console !== undefined) { 15599 window.console.log(str); 15600 } 15601 }, 15602 /** 15603 * Initialize the Local Logging 15604 * @private 15605 */ 15606 _initLogging = function () { 15607 if (_loggingInitialized) { 15608 return; 15609 } 15610 //Build a new store 15611 _session = sessionStorage.getItem("finSessKey"); 15612 //if the _session is null or empty, skip the init 15613 if (!_session) { 15614 return; 15615 } 15616 _sessionKey = "Fi"+_session; 15617 _linePrefix = _sessionKey + "_"; 15618 _logInfo = {}; 15619 _logInfo.name = _session; 15620 _logInfo.size = 0; 15621 _logInfo.head = 0; 15622 _logInfo.tail = 0; 15623 _logInfo.startTime = new Date().getTime(); 15624 _loggingInitialized = true; 15625 _initSessionList(); 15626 }, 15627 15628 /** 15629 * get total data size 15630 * 15631 * @return {Integer} which is the amount of data stored in local storage. 15632 * @private 15633 */ 15634 _getTotalData = function () 15635 { 15636 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 15637 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15638 if (!sessionsInfoStr) { 15639 return 0; 15640 } 15641 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15642 15643 for (sessName in sessionsInfoObj.sessions) 15644 { 15645 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 15646 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 15647 if (!sessLogInfoStr) { 15648 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 15649 } 15650 else { 15651 sessLogInfoObj = JSON.parse(sessLogInfoStr); 15652 totalData = totalData + sessLogInfoObj.size; 15653 } 15654 } 15655 } 15656 15657 return totalData; 15658 }, 15659 15660 /** 15661 * Remove lines from tail up until store size decreases to half of max size limit. 15662 * 15663 * @private 15664 */ 15665 _purgeCurrentSession = function() { 15666 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 15667 curStoreSize = _getTotalData(); 15668 if (curStoreSize < _halfMaxLocalStorageSize) { 15669 return; 15670 } 15671 logInfoStr = localStorage.getItem(_sessionKey); 15672 if (!logInfoStr) { 15673 return; 15674 } 15675 theLogInfo = JSON.parse(logInfoStr); 15676 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 15677 while(curStoreSize > _halfMaxLocalStorageSize) { 15678 try { 15679 tailKey = _sessionKey+"_"+theLogInfo.tail; 15680 line = localStorage.getItem(tailKey); 15681 if (line) { 15682 purgedSize = purgedSize +line.length; 15683 localStorage.removeItem(tailKey); 15684 curStoreSize = curStoreSize - line.length; 15685 theLogInfo.size = theLogInfo.size - line.length; 15686 } 15687 } 15688 catch (err) { 15689 _myConsoleLog("purgeCurrentSession encountered err="+err); 15690 } 15691 if (theLogInfo.tail < theLogInfo.head) { 15692 theLogInfo.tail = theLogInfo.tail + 1; 15693 } 15694 else { 15695 break; 15696 } 15697 } 15698 //purge stops here, we need to update session's meta data in storage 15699 secLogInfoStr = localStorage.getItem(_sessionKey); 15700 if (!secLogInfoStr) { 15701 //somebody cleared the localStorage 15702 return; 15703 } 15704 15705 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 15706 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 15707 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 15708 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 15709 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 15710 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 15711 }, 15712 15713 /** 15714 * Purge a session 15715 * 15716 * @param sessionName is the name of the session 15717 * @return {Integer} which is the current amount of data purged 15718 * @private 15719 */ 15720 _purgeSession = function (sessionName) { 15721 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 15722 //Get the session logInfo 15723 logInfoStr = localStorage.getItem("Fi" + sessionName); 15724 if (!logInfoStr) { 15725 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 15726 return 0; 15727 } 15728 theLogInfo = JSON.parse(logInfoStr); 15729 15730 //Note: This assumes that we don't crash in the middle of purging 15731 //=> if we do then it should get deleted next time 15732 //Purge tail->head 15733 while (theLogInfo.tail <= theLogInfo.head) 15734 { 15735 try { 15736 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 15737 theLogInfo.tail = theLogInfo.tail + 1; 15738 } 15739 catch (err) { 15740 _myConsoleLog("In _purgeSession err="+err); 15741 break; 15742 } 15743 } 15744 15745 //Remove the entire session 15746 localStorage.removeItem("Fi" + sessionName); 15747 15748 //Update FinesseSessionsInfo 15749 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15750 if (!sessionsInfoStr) { 15751 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 15752 return 0; 15753 } 15754 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15755 if (sessionsInfoObj.sessions !== null) 15756 { 15757 delete sessionsInfoObj.sessions[sessionName]; 15758 15759 sessionsInfoObj.total = sessionsInfoObj.total - 1; 15760 sessionsInfoObj.lastWrittenBy = _session; 15761 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 15762 } 15763 15764 return theLogInfo.size; 15765 }, 15766 15767 /** 15768 * purge old sessions 15769 * 15770 * @param storeSize 15771 * @return {Boolean} whether purging reaches its target 15772 * @private 15773 */ 15774 _purgeOldSessions = function (storeSize) { 15775 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 15776 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15777 if (!sessionsInfoStr) { 15778 _myConsoleLog("Could not get FinesseSessionsInfo"); 15779 return true; 15780 } 15781 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15782 curStoreSize = _getTotalData(); 15783 15784 activeSession = _session; 15785 sessions = sessionsInfoObj.sessions; 15786 for (sessName in sessions) { 15787 if (sessions.hasOwnProperty(sessName)) { 15788 if (sessName !== activeSession) { 15789 purgedSize = purgedSize + _purgeSession(sessName); 15790 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 15791 return true; 15792 } 15793 } 15794 } 15795 } 15796 //purge is not done, so return false 15797 return false; 15798 }, 15799 15800 /** 15801 * handle insert error 15802 * 15803 * @param error 15804 * @private 15805 */ 15806 _insertLineHandleError = function (error) { 15807 _myConsoleLog(error); 15808 }, 15809 15810 /** 15811 * check storage data size and if need purge 15812 * @private 15813 */ 15814 _checkSizeAndPurge = function () { 15815 var purgeIsDone=false, totalSize = _getTotalData(); 15816 if (totalSize > 0.75*_maxLocalStorageSize) { 15817 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 15818 purgeIsDone = _purgeOldSessions(totalSize); 15819 if (purgeIsDone) { 15820 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 15821 } 15822 else { 15823 //after all old sessions purged, still need purge 15824 totalSize = _getTotalData(); 15825 if (totalSize > 0.75*_maxLocalStorageSize) { 15826 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 15827 _purgeCurrentSession(); 15828 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 15829 } 15830 } 15831 } 15832 }, 15833 15834 /** 15835 * check if the session is already in meta data 15836 * 15837 * @param metaData 15838 * @param sessionName 15839 * @return {Boolean} true if session has metaData (false otherwise) 15840 * @private 15841 */ 15842 _sessionsInfoContains = function (metaData, sessionName) { 15843 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 15844 return true; 15845 } 15846 return false; 15847 }, 15848 15849 15850 /** 15851 * setup sessions in local storage 15852 * 15853 * @param logInfo 15854 * @private 15855 */ 15856 _getAndSetNumberOfSessions = function (logInfo) { 15857 var numOfSessionsPass1, numOfSessionsPass2, l; 15858 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 15859 if (numOfSessionsPass1 === null) { 15860 //Init first time 15861 numOfSessionsPass1 = {}; 15862 numOfSessionsPass1.total = 1; 15863 numOfSessionsPass1.sessions = {}; 15864 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 15865 numOfSessionsPass1.lastWrittenBy = logInfo.name; 15866 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 15867 } 15868 else { 15869 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 15870 //check if the session is already in the FinesseSessionSInfo 15871 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 15872 return; 15873 } 15874 //Save numOfSessionsPass1 15875 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 15876 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 15877 numOfSessionsPass1.lastWrittenBy = logInfo.name; 15878 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 15879 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 15880 if (!numOfSessionsPass2) { 15881 _myConsoleLog("Could not get FinesseSessionsInfo"); 15882 return; 15883 } 15884 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 15885 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 15886 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 15887 //// _myConsoleLog("Rebuild sessions"); 15888 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 15889 ////} 15890 ////else { 15891 //// _sessionTimerId = null; 15892 ////callback(numOfSessionsPass2.sessions); 15893 ////} 15894 } 15895 if (!localStorage.getItem(_sessionKey)) { 15896 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 15897 } 15898 }, 15899 15900 15901 /** 15902 * init session list 15903 * @private 15904 */ 15905 _initSessionList = function () { 15906 _getAndSetNumberOfSessions(_logInfo); 15907 }, 15908 15909 /** 15910 * do the real store of log line 15911 * 15912 * @param line 15913 * @private 15914 */ 15915 _persistLine = function (line) { 15916 var key, logInfoStr; 15917 logInfoStr = localStorage.getItem(_sessionKey); 15918 if (logInfoStr === null) { 15919 return; 15920 } 15921 _logInfo = JSON.parse(logInfoStr); 15922 _logInfo.head = _logInfo.head + 1; 15923 key = _linePrefix + _logInfo.head; 15924 localStorage.setItem(key, line); 15925 //Save the size 15926 _logInfo.size = _logInfo.size + line.length; 15927 if (_logInfo.tail === 0) { 15928 _logInfo.tail = _logInfo.head; 15929 } 15930 15931 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 15932 _checkSizeAndPurge(); 15933 }, 15934 15935 /** 15936 * Insert a line into the localStorage. 15937 * 15938 * @param line line to be inserted 15939 * @private 15940 */ 15941 _insertLine = function (line) { 15942 //_myConsoleLog("_insertLine: [" + line + "]"); 15943 //Write the next line to localStorage 15944 try { 15945 //Persist the line 15946 _persistLine(line); 15947 } 15948 catch (err) { 15949 _myConsoleLog("error in _insertLine(), err="+err); 15950 //_insertLineHandleError(err); 15951 } 15952 }, 15953 15954 15955 /** 15956 * Clear the local storage 15957 * @private 15958 */ 15959 _clearLocalStorage = function() { 15960 localStorage.clear(); 15961 15962 }, 15963 15964 /** 15965 * Collect logs when onCollect called 15966 * 15967 * @param data 15968 * @private 15969 */ 15970 _collectMethod = function(data) { 15971 //Size of log should not exceed 1.5MB 15972 var info, maxLength = 1572864; 15973 15974 //add size buffer equal to the size of info to be added when publish 15975 info = Utilities.getSanitizedUserAgentString() + " "; 15976 info = escape(info); 15977 15978 //If log was empty previously, fade in buttons 15979 if (!_sendLogShown) { 15980 //call the fadeInSendLog() in Footer 15981 finesse.modules.Footer.sendLogAppear(); 15982 _sendLogShown = true; 15983 _logSize = info.length; 15984 } 15985 15986 //if local storage logging is enabled, then insert the log into local storage 15987 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 15988 if (data) { 15989 if (data.length>0 && data.substring(0,1) === '\n') { 15990 _insertLine(data.substring(1)); 15991 } 15992 else { 15993 _insertLine(data); 15994 } 15995 } 15996 } 15997 15998 //escape all data to get accurate size (shindig will escape when it builds request) 15999 //escape 6 special chars for XML: &<>"'\n 16000 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 16001 data = escape(data+"\n"); 16002 16003 if (data.length < maxLength){ 16004 //make room for new data if log is exceeding max length 16005 while (_logSize + data.length > maxLength) { 16006 _logSize -= (_logArray.shift()).length; 16007 } 16008 } 16009 16010 //Else push the log into memory, increment the log size 16011 _logArray.push(data); 16012 16013 //inc the size accordingly 16014 _logSize+=data.length; 16015 16016 }; 16017 16018 return { 16019 16020 /** 16021 * @private 16022 * Initiate FinesseLogger. 16023 */ 16024 init: function () { 16025 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 16026 _initLogging(); 16027 }, 16028 16029 /** 16030 * @private 16031 * Clear all items stored in localStorage. 16032 */ 16033 clear : function () { 16034 _clearLocalStorage(); 16035 }, 16036 16037 /** 16038 * @private 16039 * Initialize the local storage logging. 16040 */ 16041 initLocalLog: function () { 16042 _initLogging(); 16043 }, 16044 16045 /** 16046 * @private 16047 * Inserts a line into the localStorage. 16048 * @param line to insert 16049 */ 16050 localLog : function (line) { 16051 _insertLine(line); 16052 }, 16053 16054 /** 16055 * @ignore 16056 * Publish logs to server and clear the memory 16057 * 16058 * @param userObj 16059 * @param options 16060 * @param callBack 16061 */ 16062 publish: function(userObj, options, callBack) { 16063 // Avoid null references. 16064 options = options || {}; 16065 callBack = callBack || {}; 16066 16067 if (callBack.sending === "function") { 16068 callBack.sending(); 16069 } 16070 16071 //logs the basic version and machine info and escaped new line 16072 _logStr = Utilities.getSanitizedUserAgentString() + " "; 16073 16074 //join the logs to correct string format 16075 _logStr += unescape(_logArray.join("")); 16076 16077 //turning log string to JSON obj 16078 var logObj = { 16079 ClientLog: { 16080 logData : _logStr //_logStr 16081 } 16082 }, 16083 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 16084 /** @private */ 16085 options.onAdd = function(){ 16086 tmpOnAdd(); 16087 _logArray.length = 0; _logSize =0; 16088 _sendLogShown = false; 16089 }; 16090 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 16091 /** @private */ 16092 options.onLoad = function (clientLogObj) { 16093 clientLogObj.sendLogs(logObj,{ 16094 error: callBack.error 16095 }); 16096 }; 16097 16098 userObj.getClientLog(options); 16099 } 16100 }; 16101 }()); 16102 16103 window.finesse = window.finesse || {}; 16104 window.finesse.cslogger = window.finesse.cslogger || {}; 16105 /** @private */ 16106 window.finesse.cslogger.FinesseLogger = FinesseLogger; 16107 16108 return FinesseLogger; 16109 }); 16110 16111 /** 16112 * Contains a list of topics used for containerservices pubsub. 16113 * 16114 */ 16115 16116 /** 16117 * @class 16118 * Contains a list of topics with some utility functions. 16119 */ 16120 /** @private */ 16121 define('containerservices/Topics',[], function () { 16122 16123 var Topics = (function () { 16124 16125 /** 16126 * The namespace prepended to all Finesse topics. 16127 */ 16128 this.namespace = "finesse.containerservices"; 16129 16130 /** 16131 * @private 16132 * Gets the full topic name with the ContainerServices namespace prepended. 16133 * @param {String} topic 16134 * The topic category. 16135 * @returns {String} 16136 * The full topic name with prepended namespace. 16137 */ 16138 var _getNSTopic = function (topic) { 16139 return this.namespace + "." + topic; 16140 }; 16141 16142 16143 16144 /** @scope finesse.containerservices.Topics */ 16145 return { 16146 /** 16147 * @private 16148 * request channel. */ 16149 REQUESTS: _getNSTopic("requests"), 16150 16151 /** 16152 * @private 16153 * reload gadget channel. */ 16154 RELOAD_GADGET: _getNSTopic("reloadGadget"), 16155 16156 /** 16157 * @private 16158 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 16159 */ 16160 getTopic: function (restUri) { 16161 //The topic should not start with '/' else it will get replaced with 16162 //'.' which is invalid. 16163 //Thus, remove '/' if it is at the beginning of the string 16164 if (restUri.indexOf('/') === 0) { 16165 restUri = restUri.substr(1); 16166 } 16167 16168 //Replace every instance of "/" with ".". This is done to follow the 16169 //OpenAjaxHub topic name convention. 16170 return restUri.replace(/\//g, "."); 16171 } 16172 }; 16173 }()); 16174 16175 window.finesse = window.finesse || {}; 16176 window.finesse.containerservices = window.finesse.containerservices || {}; 16177 window.finesse.containerservices.Topics = Topics; 16178 16179 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 16180 finesse.containerservices = finesse.containerservices || {}; 16181 16182 return Topics; 16183 }); 16184 16185 /** The following comment is to prevent jslint errors about 16186 * using variables before they are defined. 16187 */ 16188 /*global finesse*/ 16189 16190 /** 16191 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 16192 * 16193 * @requires OpenAjax, finesse.containerservices.Topics 16194 */ 16195 16196 /** @private */ 16197 define('containerservices/MasterPublisher',[ 16198 "utilities/Utilities", 16199 "containerservices/Topics" 16200 ], 16201 function (Utilities, Topics) { 16202 16203 var MasterPublisher = function () { 16204 16205 var 16206 16207 /** 16208 * Reference to the gadget pubsub Hub instance. 16209 * @private 16210 */ 16211 _hub = gadgets.Hub, 16212 16213 /** 16214 * Reference to the Topics class. 16215 * @private 16216 */ 16217 _topics = Topics, 16218 16219 /** 16220 * Reference to conversion utilities class. 16221 * @private 16222 */ 16223 _utils = Utilities, 16224 16225 /** 16226 * References to ClientServices logger methods 16227 * @private 16228 */ 16229 _logger = { 16230 log: finesse.clientservices.ClientServices.log 16231 }, 16232 16233 /** 16234 * The types of possible request types supported when listening to the 16235 * requests channel. Each request type could result in different operations. 16236 * @private 16237 */ 16238 _REQTYPES = { 16239 ACTIVETAB: "ActiveTabReq", 16240 SET_ACTIVETAB: "SetActiveTabReq", 16241 RELOAD_GADGET: "ReloadGadgetReq" 16242 }, 16243 16244 /** 16245 * Handles client requests made to the request topic. The type of the 16246 * request is described in the "type" property within the data payload. Each 16247 * type can result in a different operation. 16248 * @param {String} topic 16249 * The topic which data was published to. 16250 * @param {Object} data 16251 * The data containing requests information published by clients. 16252 * @param {String} data.type 16253 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 16254 * @param {Object} data.data 16255 * May contain data relevant for the particular requests. 16256 * @param {String} [data.invokeID] 16257 * The ID used to identify the request with the response. The invoke ID 16258 * will be included in the data in the publish to the topic. It is the 16259 * responsibility of the client to correlate the published data to the 16260 * request made by using the invoke ID. 16261 * @private 16262 */ 16263 _clientRequestHandler = function (topic, data) { 16264 16265 //Ensure a valid data object with "type" and "data" properties. 16266 if (typeof data === "object" && 16267 typeof data.type === "string" && 16268 typeof data.data === "object") { 16269 switch (data.type) { 16270 case _REQTYPES.ACTIVETAB: 16271 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 16272 break; 16273 case _REQTYPES.SET_ACTIVETAB: 16274 if (typeof data.data.id === "string") { 16275 _logger.log("Handling request to activate tab: " + data.data.id); 16276 if (!finesse.container.Tabs.activateTab(data.data.id)) { 16277 _logger.log("No tab found with id: " + data.data.id); 16278 } 16279 } 16280 break; 16281 case _REQTYPES.RELOAD_GADGET: 16282 _hub.publish("finesse.containerservices.reloadGadget", data.data); 16283 break; 16284 default: 16285 break; 16286 } 16287 } 16288 }; 16289 16290 (function () { 16291 16292 //Listen to a request channel to respond to any requests made by other 16293 //clients because the Master may have access to useful information. 16294 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 16295 }()); 16296 16297 //BEGIN TEST CODE// 16298 /** 16299 * Test code added to expose private functions that are used by unit test 16300 * framework. This section of code is removed during the build process 16301 * before packaging production code. The [begin|end]TestSection are used 16302 * by the build to identify the section to strip. 16303 * @ignore 16304 */ 16305 this.beginTestSection = 0; 16306 16307 /** 16308 * @ignore 16309 */ 16310 this.getTestObject = function () { 16311 //Load mock dependencies. 16312 var _mock = new MockControl(); 16313 _hub = _mock.createMock(gadgets.Hub); 16314 16315 return { 16316 //Expose mock dependencies 16317 mock: _mock, 16318 hub: _hub, 16319 16320 //Expose internal private functions 16321 reqtypes: _REQTYPES, 16322 16323 clientRequestHandler: _clientRequestHandler 16324 16325 }; 16326 }; 16327 16328 16329 /** 16330 * @ignore 16331 */ 16332 this.endTestSection = 0; 16333 //END TEST CODE// 16334 }; 16335 16336 window.finesse = window.finesse || {}; 16337 window.finesse.containerservices = window.finesse.containerservices || {}; 16338 window.finesse.containerservices.MasterPublisher = MasterPublisher; 16339 16340 return MasterPublisher; 16341 }); 16342 16343 /** 16344 * JavaScript representation of the Finesse WorkflowActionEvent object. 16345 * 16346 * @requires finesse.FinesseBase 16347 */ 16348 16349 /** The following comment is to prevent jslint errors about 16350 * using variables before they are defined. 16351 */ 16352 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 16353 /** @private */ 16354 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 16355 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 16356 /** 16357 * Reference to the WorkflowActionEvent name 16358 * This will be set by setWorkflowActionEvent 16359 * @private 16360 */ 16361 _name: null, 16362 16363 /** 16364 * Reference to the WorkflowActionEvent type 16365 * This will be set by setWorkflowActionEvent 16366 * @private 16367 */ 16368 _type: null, 16369 16370 /** 16371 * Reference to the WorkflowActionEvent handledBy value 16372 * This will be set by setWorkflowActionEvent 16373 * @private 16374 */ 16375 _handledBy: null, 16376 16377 /** 16378 * Reference to the WorkflowActionEvent params array 16379 * This will be set by setWorkflowActionEvent 16380 * @private 16381 */ 16382 _params: [], 16383 16384 /** 16385 * Reference to the WorkflowActionEvent actionVariables array 16386 * This will be set by setWorkflowActionEvent 16387 * @private 16388 */ 16389 _actionVariables: [], 16390 16391 /** 16392 * @class 16393 * JavaScript representation of a WorkflowActionEvent object. 16394 * The WorkflowActionEvent object is delivered as the payload of 16395 * a WorkflowAction callback. This can be subscribed to by using 16396 * {@link finesse.containerservices.ContainerServices#addHandler} with a 16397 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 16398 * Gadgets should key on events with a handleBy value of "OTHER". 16399 * 16400 * @constructs 16401 **/ 16402 init: function () { 16403 this._super(); 16404 }, 16405 16406 /** 16407 * Validate that the passed in object is a WorkflowActionEvent object 16408 * and sets the variables if it is 16409 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 16410 * it validates successfully. 16411 * @returns {Boolean} Whether it is valid or not. 16412 * @private 16413 */ 16414 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 16415 var returnValue; 16416 16417 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 16418 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 16419 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 16420 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 16421 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 16422 this._name = maybeWorkflowActionEvent.name; 16423 this._type = maybeWorkflowActionEvent.type; 16424 this._handledBy = maybeWorkflowActionEvent.handledBy; 16425 this._params = maybeWorkflowActionEvent.params; 16426 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 16427 returnValue = true; 16428 } else { 16429 returnValue = false; 16430 } 16431 16432 return returnValue; 16433 }, 16434 16435 /** 16436 * Getter for the WorkflowActionEvent name. 16437 * @returns {String} The name of the WorkflowAction. 16438 */ 16439 getName: function () { 16440 // escape nulls to empty string 16441 return this._name || ""; 16442 }, 16443 16444 /** 16445 * Getter for the WorkflowActionEvent type. 16446 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 16447 */ 16448 getType: function () { 16449 // escape nulls to empty string 16450 return this._type || ""; 16451 }, 16452 16453 /** 16454 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 16455 * events with a handleBy of "OTHER". 16456 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 16457 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 16458 */ 16459 getHandledBy: function () { 16460 // escape nulls to empty string 16461 return this._handledBy || ""; 16462 }, 16463 16464 16465 /** 16466 * Getter for the WorkflowActionEvent Params map. 16467 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 16468 * BROWSER_POP<ul> 16469 * <li>windowName : Name of window to pop into, or blank to always open new window. 16470 * <li>path : URL to open.</ul> 16471 * HTTP_REQUEST<ul> 16472 * <li>method : "PUT" or "POST". 16473 * <li>location : "FINESSE" or "OTHER". 16474 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 16475 * <li>path : Request URL. 16476 * <li>body : Request content for POST requests.</ul> 16477 */ 16478 getParams: function () { 16479 var map = {}, 16480 params = this._params, 16481 i, 16482 param; 16483 16484 if (params === null || params.length === 0) { 16485 return map; 16486 } 16487 16488 for (i = 0; i < params.length; i += 1) { 16489 param = params[i]; 16490 // escape nulls to empty string 16491 param.name = param.name || ""; 16492 param.value = param.value || ""; 16493 param.expandedValue = param.expandedValue || ""; 16494 map[param.name] = param; 16495 } 16496 16497 return map; 16498 }, 16499 16500 /** 16501 * Getter for the WorkflowActionEvent ActionVariables map 16502 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 16503 */ 16504 getActionVariables: function() { 16505 var map = {}, 16506 actionVariables = this._actionVariables, 16507 i, 16508 actionVariable; 16509 16510 if (actionVariables === null || actionVariables.length === 0) { 16511 return map; 16512 } 16513 16514 for (i = 0; i < actionVariables.length; i += 1) { 16515 actionVariable = actionVariables[i]; 16516 // escape nulls to empty string 16517 actionVariable.name = actionVariable.name || ""; 16518 actionVariable.type = actionVariable.type || ""; 16519 actionVariable.node = actionVariable.node || ""; 16520 actionVariable.testValue = actionVariable.testValue || ""; 16521 actionVariable.actualValue = actionVariable.actualValue || ""; 16522 map[actionVariable.name] = actionVariable; 16523 } 16524 16525 return map; 16526 } 16527 }); 16528 16529 16530 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 16531 /** 16532 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 16533 * with the action, but first and foremost Finesse will handle this WorkflowAction. 16534 */ 16535 FINESSE: "FINESSE", 16536 16537 /** 16538 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 16539 * ignore this action and expects Gadget Developers to take action. 16540 */ 16541 OTHER: "OTHER", 16542 16543 /** 16544 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 16545 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 16546 * @constructs 16547 */ 16548 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16549 }; 16550 16551 window.finesse = window.finesse || {}; 16552 window.finesse.containerservices = window.finesse.containerservices || {}; 16553 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 16554 16555 return WorkflowActionEvent; 16556 }); 16557 16558 /** 16559 * JavaScript representation of the Finesse TimerTickEvent 16560 * 16561 * @requires finesse.FinesseBase 16562 */ 16563 16564 /** The following comment is to prevent jslint errors about 16565 * using variables before they are defined. 16566 */ 16567 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 16568 /** @private */ 16569 define('containerservices/TimerTickEvent',[ 16570 "FinesseBase" 16571 ], 16572 function (FinesseBase) { 16573 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 16574 /** 16575 * date the TimerTickEvent was queued 16576 * @private 16577 */ 16578 _dateQueued: null, 16579 16580 /** 16581 * the frequency of the timer tick (in miiliseconds) 16582 * @private 16583 */ 16584 _tickFrequency: 1000, 16585 16586 /** 16587 * @class 16588 * JavaScript representation of a TimerTickEvent object. 16589 * The TimerTickEvent object is delivered as the payload of 16590 * a TimerTickEvent callback. This can be subscribed to by using 16591 * {@link finesse.containerservices.ContainerServices#addHandler} with a 16592 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 16593 * 16594 * @constructs 16595 **/ 16596 init: function (tickFrequency, dateQueued) { 16597 this._super(); 16598 16599 this._tickFrequency = tickFrequency; 16600 this._dateQueued = dateQueued; 16601 }, 16602 16603 /** 16604 * Get the "tickFrequency" field 16605 * @param {int} which is the "TickFrequency" field 16606 * @private 16607 */ 16608 getTickFrequency: function () { 16609 return this._tickFrequency; 16610 }, 16611 16612 /** 16613 * Getter for the TimerTickEvent "DateQueued" field. 16614 * @returns {Date} which is a Date object when the TimerTickEvent was queued 16615 */ 16616 getDateQueued: function () { 16617 return this._dateQueued; 16618 } 16619 16620 }); 16621 16622 window.finesse = window.finesse || {}; 16623 window.finesse.containerservices = window.finesse.containerservices || {}; 16624 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 16625 16626 return TimerTickEvent; 16627 }); 16628 16629 /** 16630 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 16631 * 16632 * @requires finesse.FinesseBase 16633 */ 16634 16635 /** The following comment is to prevent jslint errors about 16636 * using variables before they are defined. 16637 */ 16638 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 16639 /** @private */ 16640 define('containerservices/GadgetViewChangedEvent',[ 16641 "FinesseBase" 16642 ], 16643 function (FinesseBase) { 16644 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 16645 /** 16646 * Reference to the gadget id 16647 * @private 16648 */ 16649 _gadgetId: null, 16650 16651 /** 16652 * Reference to the tab id 16653 * @private 16654 */ 16655 _tabId: null, 16656 16657 /** 16658 * Reference to the maxAvailableHeight 16659 * @private 16660 */ 16661 _maxAvailableHeight: null, 16662 16663 /** 16664 * Reference to the view 16665 * E.g. 'default' or 'canvas' 16666 * @private 16667 */ 16668 _view: null, 16669 16670 /** 16671 * @class 16672 * JavaScript representation of a GadgetViewChangedEvent object. 16673 * The GadgetViewChangedEvent object is delivered as the payload of 16674 * a GadgetViewChangedEvent callback. This can be subscribed to by using 16675 * {@link finesse.containerservices.ContainerServices#addHandler} with a 16676 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 16677 * 16678 * @constructs 16679 **/ 16680 init: function (gadgetId, tabId, maxAvailableHeight, view) { 16681 this._super(); 16682 16683 this._gadgetId = gadgetId; 16684 this._tabId = tabId; 16685 this._maxAvailableHeight = maxAvailableHeight; 16686 this._view = view; 16687 }, 16688 16689 /** 16690 * Getter for the gadget id. 16691 * @returns {String} The identifier for the gadget changing view. 16692 */ 16693 getGadgetId: function () { 16694 // escape nulls to empty string 16695 return this._gadgetId || ""; 16696 }, 16697 16698 /** 16699 * Getter for the maximum available height. 16700 * @returns {String} The maximum available height for the gadget's view. 16701 */ 16702 getMaxAvailableHeight: function () { 16703 // escape nulls to empty string 16704 return this._maxAvailableHeight || ""; 16705 }, 16706 16707 /** 16708 * Getter for the tab id. 16709 * @returns {String} The identifier for the tab where the gadget changing view resides. 16710 */ 16711 getTabId: function () { 16712 // escape nulls to empty string 16713 return this._tabId || ""; 16714 }, 16715 16716 /** 16717 * Getter for the view. 16718 * @returns {String} The view type the gadget is changing to. 16719 */ 16720 getView: function () { 16721 // escape nulls to empty string 16722 return this._view || ""; 16723 } 16724 }); 16725 16726 window.finesse = window.finesse || {}; 16727 window.finesse.containerservices = window.finesse.containerservices || {}; 16728 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 16729 16730 return GadgetViewChangedEvent; 16731 }); 16732 16733 /** 16734 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 16735 * 16736 * @requires finesse.FinesseBase 16737 */ 16738 16739 /** The following comment is to prevent jslint errors about 16740 * using variables before they are defined. 16741 */ 16742 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 16743 /** @private */ 16744 define('containerservices/MaxAvailableHeightChangedEvent',[ 16745 "FinesseBase" 16746 ], 16747 function (FinesseBase) { 16748 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 16749 16750 /** 16751 * Reference to the maxAvailableHeight 16752 * @private 16753 */ 16754 _maxAvailableHeight: null, 16755 16756 /** 16757 * @class 16758 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 16759 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 16760 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 16761 * {@link finesse.containerservices.ContainerServices#addHandler} with a 16762 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 16763 * 16764 * @constructs 16765 **/ 16766 init: function (maxAvailableHeight) { 16767 this._super(); 16768 16769 this._maxAvailableHeight = maxAvailableHeight; 16770 }, 16771 16772 /** 16773 * Getter for the maximum available height. 16774 * @returns {String} The maximum available height for a gadget in canvas view 16775 */ 16776 getMaxAvailableHeight: function () { 16777 // escape nulls to empty string 16778 return this._maxAvailableHeight || ""; 16779 } 16780 }); 16781 16782 window.finesse = window.finesse || {}; 16783 window.finesse.containerservices = window.finesse.containerservices || {}; 16784 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 16785 16786 return MaxAvailableHeightChangedEvent; 16787 }); 16788 16789 /** 16790 * Exposes a set of API wrappers that will hide the dirty work of 16791 * constructing Finesse API requests and consuming Finesse events. 16792 * 16793 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 16794 */ 16795 16796 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 16797 /*global window:true, gadgets:true, publisher:true, define:true, finesse:true, _tabTracker:true, _workflowActionEventTracker:true, _masterReloader:true, frameElement:true, $:true, parent:true, MockControl:true, _getNotifierReference:true, _gadgetViewChanged:true, _maxAvailableHeightChanged:true */ 16798 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 16799 /** @private */ 16800 define('containerservices/ContainerServices',[ 16801 "utilities/Utilities", 16802 "restservices/Notifier", 16803 "containerservices/Topics", 16804 "containerservices/MasterPublisher", 16805 "containerservices/WorkflowActionEvent", 16806 "containerservices/TimerTickEvent", 16807 "containerservices/GadgetViewChangedEvent", 16808 "containerservices/MaxAvailableHeightChangedEvent" 16809 ], 16810 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 16811 16812 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 16813 16814 var 16815 16816 /** 16817 * Shortcut reference to the Utilities singleton 16818 * This will be set by init() 16819 * @private 16820 */ 16821 _util, 16822 16823 /** 16824 * Shortcut reference to the gadget pubsub Hub instance. 16825 * This will be set by init() 16826 * @private 16827 */ 16828 _hub, 16829 16830 /** 16831 * Boolean whether this instance is master or not 16832 * @private 16833 */ 16834 _master = false, 16835 16836 /** 16837 * Whether the Client Services have been initiated yet. 16838 * @private 16839 */ 16840 _inited = false, 16841 16842 /** 16843 * References to ClientServices logger methods 16844 * @private 16845 */ 16846 _logger = { 16847 log: finesse.clientservices.ClientServices.log 16848 }, 16849 16850 /** 16851 * Stores the list of subscription IDs for all subscriptions so that it 16852 * could be retrieve for unsubscriptions. 16853 * @private 16854 */ 16855 _subscriptionID = {}, 16856 16857 /** 16858 * Reference to the gadget's parent container 16859 * @private 16860 */ 16861 _container, 16862 16863 /** 16864 * Reference to the MasterPublisher 16865 * @private 16866 */ 16867 _publisher, 16868 16869 /** 16870 * Object that will contain the Notifiers 16871 * @private 16872 */ 16873 _notifiers = {}, 16874 16875 /** 16876 * Reference to the tabId that is associated with the gadget 16877 * @private 16878 */ 16879 _myTab = null, 16880 16881 /** 16882 * Reference to the visibility of current gadget 16883 * @private 16884 */ 16885 _visible = false, 16886 16887 /** 16888 * Shortcut reference to the Topics class. 16889 * This will be set by init() 16890 * @private 16891 */ 16892 _topics, 16893 16894 /** 16895 * Associates a topic name with the private handler function. 16896 * Adding a new topic requires that you add this association here 16897 * in to keep addHandler generic. 16898 * @param {String} topic : Specifies the callback to retrieve 16899 * @return {Function} The callback function associated with the topic param. 16900 * @private 16901 */ 16902 _topicCallback = function (topic) { 16903 var callback, notifier; 16904 switch (topic) 16905 { 16906 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 16907 callback = _tabTracker; 16908 break; 16909 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 16910 callback = _workflowActionEventTracker; 16911 break; 16912 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 16913 callback = _masterReloader; 16914 break; 16915 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 16916 callback = _gadgetViewChanged; 16917 break; 16918 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 16919 callback = _maxAvailableHeightChanged; 16920 break; 16921 default: 16922 callback = function (param) { 16923 var data = null; 16924 16925 notifier = _getNotifierReference(topic); 16926 16927 if (arguments.length === 1) { 16928 data = param; 16929 } else { 16930 data = arguments; 16931 } 16932 notifier.notifyListeners(data); 16933 }; 16934 } 16935 return callback; 16936 }, 16937 16938 /** 16939 * Ensure that ClientServices have been inited. 16940 * @private 16941 */ 16942 _isInited = function () { 16943 if (!_inited) { 16944 throw new Error("ContainerServices needs to be inited."); 16945 } 16946 }, 16947 16948 /** 16949 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 16950 * @param {String} topic : Specifies the notifier to retrieve 16951 * @return {Notifier} The notifier object. 16952 * @private 16953 */ 16954 _getNotifierReference = function (topic) { 16955 if (!_notifiers.hasOwnProperty(topic)) 16956 { 16957 _notifiers[topic] = new Notifier(); 16958 } 16959 16960 return _notifiers[topic]; 16961 }, 16962 16963 /** 16964 * Utility function to make a subscription to a particular topic. Only one 16965 * callback function is registered to a particular topic at any time. 16966 * @param {String} topic 16967 * The full topic name. The topic name should follow the OpenAjax 16968 * convention using dot notation (ex: finesse.api.User.1000). 16969 * @param {Function} callback 16970 * The function that should be invoked with the data when an event 16971 * is delivered to the specific topic. 16972 * @returns {Boolean} 16973 * True if the subscription was made successfully and the callback was 16974 * been registered. False if the subscription already exist, the 16975 * callback was not overwritten. 16976 * @private 16977 */ 16978 _subscribe = function (topic, callback) { 16979 _isInited(); 16980 16981 //Ensure that the same subscription isn't made twice. 16982 if (!_subscriptionID[topic]) { 16983 //Store the subscription ID using the topic name as the key. 16984 _subscriptionID[topic] = _hub.subscribe(topic, 16985 //Invoke the callback just with the data object. 16986 function (topic, data) { 16987 callback(data); 16988 }); 16989 return true; 16990 } 16991 return false; 16992 }, 16993 16994 /** 16995 * Unsubscribe from a particular topic. 16996 * @param {String} topic : The full topic name. 16997 * @private 16998 */ 16999 _unsubscribe = function (topic) { 17000 _isInited(); 17001 17002 //Unsubscribe from the topic using the subscription ID recorded when 17003 //the subscription was made, then delete the ID from data structure. 17004 _hub.unsubscribe(_subscriptionID[topic]); 17005 delete _subscriptionID[topic]; 17006 }, 17007 17008 /** 17009 * Get my tab id. 17010 * @returns {String} tabid : The tabid of this container/gadget. 17011 * @private 17012 */ 17013 _getMyTab = function () { 17014 if (_myTab === null) 17015 { 17016 try { 17017 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 17018 } catch (err) { 17019 _logger.log("Error accessing current tab: " + err.message); 17020 _myTab = null; 17021 } 17022 } 17023 return _myTab; 17024 }, 17025 17026 /** 17027 * Callback function that is called when an activeTab message is posted to the Hub. 17028 * Notifies listener functions if this tab is the one that was just made active. 17029 * @param {String} tabId : The tabId which was just made visible. 17030 * @private 17031 */ 17032 _tabTracker = function(tabId) { 17033 if (tabId === _getMyTab()) { 17034 if(!_visible) { 17035 _visible = true; 17036 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 17037 } 17038 } else { 17039 _visible = false; 17040 } 17041 }, 17042 17043 /** 17044 * Make a request to set a particular tab active. This 17045 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 17046 * to ensure the gadget gets properly initialized. 17047 * @param {String} tabId 17048 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 17049 * @private 17050 */ 17051 _activateTab = function ( tabId ) { 17052 _logger.log("Sending request to activate tab: " + tabId); 17053 if(_hub){ 17054 var data = { 17055 type: "SetActiveTabReq", 17056 data: { id: tabId }, 17057 invokeID: (new Date()).getTime() 17058 }; 17059 _hub.publish(_topics.REQUESTS, data); 17060 } else { 17061 throw new Error("Hub is not defined."); 17062 } 17063 17064 }, 17065 17066 /** 17067 * Callback function that is called when a gadget view changed message is posted to the Hub. 17068 * @private 17069 */ 17070 _gadgetViewChanged = function (data) { 17071 if (data) { 17072 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 17073 data.gadgetId, 17074 data.tabId, 17075 data.maxAvailableHeight, 17076 data.view); 17077 17078 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 17079 } 17080 }, 17081 17082 /** 17083 * Callback function that is called when a max available height changed message is posted to the Hub. 17084 * @private 17085 */ 17086 _maxAvailableHeightChanged = function (data) { 17087 if (data) { 17088 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 17089 data.maxAvailableHeight); 17090 17091 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 17092 } 17093 }, 17094 17095 /** 17096 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 17097 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 17098 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 17099 * @private 17100 */ 17101 _workflowActionEventTracker = function(workflowActionEvent) { 17102 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 17103 17104 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 17105 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 17106 } 17107 // else 17108 // { 17109 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 17110 // } 17111 17112 }, 17113 17114 /** 17115 * Callback function that is called when a reloadGadget event message is posted to the Hub. 17116 * 17117 * Grabs the id of the gadget we want to reload from the data and reload it! 17118 * 17119 * @param {String} topic 17120 * which topic the event came on (unused) 17121 * @param {Object} data 17122 * the data published with the event 17123 * @private 17124 */ 17125 _masterReloader = function (topic, data) { 17126 var gadgetId = data.gadgetId; 17127 if (gadgetId) { 17128 _container.reloadGadget(gadgetId); 17129 } 17130 }, 17131 17132 /** 17133 * Pulls the gadget id from the url parameters 17134 * @return {String} id of the gadget 17135 * @private 17136 */ 17137 _findMyGadgetId = function () { 17138 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 17139 return gadgets.util.getUrlParameters().mid; 17140 } 17141 }; 17142 17143 return { 17144 /** 17145 * @class 17146 * This class provides container-level services for gadget developers, exposing container events by 17147 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 17148 * event handling (add/remove). 17149 * @example 17150 * containerServices = finesse.containerservices.ContainerServices.init(); 17151 * containerServices.addHandler( 17152 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 17153 * function() { 17154 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 17155 * // automatically adjust the height of the gadget to show the html 17156 * gadgets.window.adjustHeight(); 17157 * }); 17158 * containerServices.makeActiveTabReq(); 17159 * 17160 * @constructs 17161 */ 17162 _fakeConstuctor: function () { 17163 /* This is here so we can document init() as a method rather than as a constructor. */ 17164 }, 17165 17166 /** 17167 * Initialize ContainerServices for use in gadget. 17168 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 17169 * @returns ContainerServices instance. 17170 */ 17171 init: function (master) { 17172 if (!_inited) { 17173 _inited = true; 17174 // Set shortcuts 17175 _util = Utilities; 17176 17177 //init the hub only when it's available 17178 if(gadgets.Hub) { 17179 _hub = gadgets.Hub; 17180 } 17181 17182 if(Topics) { 17183 _topics = Topics; 17184 } 17185 17186 if (master) { 17187 _master = true; 17188 _container = finesse.container.Container; 17189 _publisher = new MasterPublisher(); 17190 17191 // subscribe for reloading gadget events 17192 // we only want the master ContainerServices handling these events 17193 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 17194 } else { 17195 _container = parent.finesse.container.Container; 17196 } 17197 } 17198 17199 this.makeActiveTabReq(); 17200 17201 //Return the CS object for object chaining. 17202 return this; 17203 }, 17204 17205 /** 17206 * Shows the jQuery UI Dialog with the specified parameters. The following are the 17207 * default parameters: <ul> 17208 * <li> Title of "Cisco Finesse".</li> 17209 * <li>Message of "A generic error has occured".</li> 17210 * <li>The only button, "Ok", closes the dialog.</li> 17211 * <li>Modal (blocks other dialogs).</li> 17212 * <li>Not draggable.</li> 17213 * <li>Fixed size.</li></ul> 17214 * @param {Object} options 17215 * An object containing additional options for the dialog. 17216 * @param {String/Boolean} options.title 17217 * Title to use. undefined defaults to "Cisco Finesse". false to hide 17218 * @param {Function} options.close 17219 * A function to invoke when the dialog is closed. 17220 * @param {String} options.message 17221 * The message to display in the dialog. 17222 * Defaults to "A generic error has occurred." 17223 * @param {Boolean} options.isBlocking 17224 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 17225 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 17226 * @see finesse.containerservices.ContainerServices#hideDialog 17227 */ 17228 showDialog: function(options) { 17229 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 17230 return _container.showDialog(options); 17231 } 17232 }, 17233 17234 /** 17235 * Hides the jQuery UI Dialog. 17236 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 17237 * @see finesse.containerservices.ContainerServices#showDialog 17238 */ 17239 hideDialog: function() { 17240 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 17241 return _container.hideDialog(); 17242 } 17243 }, 17244 17245 /** 17246 * Reloads the current gadget. 17247 * For use from within a gadget only. 17248 */ 17249 reloadMyGadget: function () { 17250 var topic, gadgetId, data; 17251 17252 if (!_master) { 17253 // first unsubscribe this gadget from all topics on the hub 17254 for (topic in _notifiers) { 17255 if (_notifiers.hasOwnProperty(topic)) { 17256 _unsubscribe(topic); 17257 delete _notifiers[topic]; 17258 } 17259 } 17260 17261 // send an asynch request to the hub to tell the master container 17262 // services that we want to refresh this gadget 17263 gadgetId = _findMyGadgetId(); 17264 data = { 17265 type: "ReloadGadgetReq", 17266 data: {gadgetId: gadgetId}, 17267 invokeID: (new Date()).getTime() 17268 }; 17269 _hub.publish(_topics.REQUESTS, data); 17270 } 17271 }, 17272 17273 /** 17274 * Updates the url for this gadget and then reload it. 17275 * 17276 * This allows the gadget to be reloaded from a different location 17277 * than what is uploaded to the current server. For example, this 17278 * would be useful for 3rd party gadgets to implement their own failover 17279 * mechanisms. 17280 * 17281 * For use from within a gadget only. 17282 * 17283 * @param {String} url 17284 * url from which to reload gadget 17285 */ 17286 reloadMyGadgetFromUrl: function (url) { 17287 if (!_master) { 17288 var gadgetId = _findMyGadgetId(); 17289 17290 // update the url in the container 17291 _container.modifyGadgetUrl(gadgetId, url); 17292 17293 // reload it 17294 this.reloadMyGadget(); 17295 } 17296 }, 17297 17298 /** 17299 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 17300 * will be invoked when that topic is notified. 17301 * @param {String} topic 17302 * The Hub topic to which we are listening. 17303 * @param {Function} callback 17304 * The callback function to invoke. 17305 * @see finesse.containerservices.ContainerServices.Topics 17306 * @see finesse.containerservices.ContainerServices#removeHandler 17307 */ 17308 addHandler: function (topic, callback) { 17309 _isInited(); 17310 var notifier = null; 17311 17312 try { 17313 // For backwards compatibility... 17314 if (topic === "tabVisible") { 17315 if (window.console && typeof window.console.log === "function") { 17316 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 17317 } 17318 17319 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 17320 } 17321 17322 // Add the callback to the notifier. 17323 _util.validateHandler(callback); 17324 17325 notifier = _getNotifierReference(topic); 17326 17327 notifier.addListener(callback); 17328 17329 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 17330 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 17331 // to only when necessary. 17332 _subscribe(topic, _topicCallback(topic)); 17333 17334 } catch (err) { 17335 throw new Error("addHandler(): " + err); 17336 } 17337 }, 17338 17339 /** 17340 * Removes a previously-added handler for one of the supported topics. 17341 * @param {String} topic 17342 * The Hub topic from which we are removing the callback. 17343 * @param {Function} callback 17344 * The name of the callback function to remove. 17345 * @see finesse.containerservices.ContainerServices.Topics 17346 * @see finesse.containerservices.ContainerServices#addHandler 17347 */ 17348 removeHandler: function(topic, callback) { 17349 var notifier = null; 17350 17351 try { 17352 _util.validateHandler(callback); 17353 17354 notifier = _getNotifierReference(topic); 17355 17356 notifier.removeListener(callback); 17357 } catch (err) { 17358 throw new Error("removeHandler(): " + err); 17359 } 17360 }, 17361 17362 /** 17363 * Returns the visibility of current gadget. Note that this 17364 * will not be set until after the initialization of the gadget. 17365 * @return {Boolean} The visibility of current gadget. 17366 */ 17367 tabVisible: function(){ 17368 return _visible; 17369 }, 17370 17371 /** 17372 * Make a request to check the current tab. The 17373 * activeTab event will be invoked if on the active tab. This 17374 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 17375 * to ensure the gadget gets properly initialized. 17376 */ 17377 makeActiveTabReq : function () { 17378 if(_hub){ 17379 var data = { 17380 type: "ActiveTabReq", 17381 data: {}, 17382 invokeID: (new Date()).getTime() 17383 }; 17384 _hub.publish(_topics.REQUESTS, data); 17385 } else { 17386 throw new Error("Hub is not defined."); 17387 } 17388 17389 }, 17390 17391 /** 17392 * Make a request to set a particular tab active. This 17393 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 17394 * to ensure the gadget gets properly initialized. 17395 * @param {String} tabId 17396 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 17397 */ 17398 activateTab : function (tabId) { 17399 _activateTab(tabId); 17400 }, 17401 17402 /** 17403 * Make a request to set this container's tab active. This 17404 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 17405 * to ensure the gadget gets properly initialized. 17406 */ 17407 activateMyTab : function () { 17408 _activateTab( _getMyTab() ); 17409 }, 17410 17411 /** 17412 * Get the tabId of my container/gadget. 17413 * @returns {String} tabid : The tabid of this container/gadget. 17414 */ 17415 getMyTabId : function () { 17416 return _getMyTab(); 17417 }, 17418 17419 /** 17420 * Gets the id of the gadget. 17421 * @returns {number} the id of the gadget 17422 */ 17423 getMyGadgetId : function () { 17424 return _findMyGadgetId(); 17425 }, 17426 17427 //BEGIN TEST CODE// 17428 /** 17429 * Test code added to expose private functions that are used by unit test 17430 * framework. This section of code is removed during the build process 17431 * before packaging production code. The [begin|end]TestSection are used 17432 * by the build to identify the section to strip. 17433 * @ignore 17434 */ 17435 beginTestSection : 0, 17436 17437 /** 17438 * @ignore 17439 */ 17440 getTestObject: function () { 17441 //Load mock dependencies. 17442 var _mock = new MockControl(); 17443 _util = _mock.createMock(Utilities); 17444 _hub = _mock.createMock(gadgets.Hub); 17445 _inited = true; 17446 return { 17447 //Expose mock dependencies 17448 mock: _mock, 17449 hub: _hub, 17450 util: _util, 17451 addHandler: this.addHandler, 17452 removeHandler: this.removeHandler 17453 }; 17454 }, 17455 17456 /** 17457 * @ignore 17458 */ 17459 endTestSection: 0 17460 //END TEST CODE// 17461 }; 17462 }()); 17463 17464 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 17465 /** 17466 * Topic for subscribing to be notified when the active tab changes. 17467 * The provided callback will be invoked when the tab that the gadget 17468 * that subscribes with this becomes active. To ensure code is called 17469 * when the gadget is already on the active tab use the 17470 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 17471 * method. 17472 */ 17473 ACTIVE_TAB: "finesse.containerservices.activeTab", 17474 17475 /** 17476 * Topic for WorkflowAction events traffic. 17477 * The provided callback will be invoked when a WorkflowAction needs 17478 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 17479 * that can be used to interrogate the WorkflowAction and determine to use or not. 17480 */ 17481 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 17482 17483 /** 17484 * Topic for Timer Tick event. 17485 * The provided callback will be invoked when this event is fired. 17486 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 17487 */ 17488 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 17489 17490 /** 17491 * Topic for Reload Gadget events traffic. 17492 * Only the master ContainerServices instance will handle this event. 17493 */ 17494 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 17495 17496 /** 17497 * Topic for listening to gadget view changed events. 17498 * The provided callback will be invoked when a gadget changes view. 17499 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 17500 */ 17501 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 17502 17503 /** 17504 * Topic for listening to max available height changed events. 17505 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 17506 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 17507 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 17508 */ 17509 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 17510 17511 /** 17512 * @class This is the set of Topics used for subscribing for events from ContainerServices. 17513 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 17514 * 17515 * @constructs 17516 */ 17517 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 17518 }; 17519 17520 window.finesse = window.finesse || {}; 17521 window.finesse.containerservices = window.finesse.containerservices || {}; 17522 window.finesse.containerservices.ContainerServices = ContainerServices; 17523 17524 return ContainerServices; 17525 }); 17526 17527 /** 17528 * This "interface" is just a way to easily jsdoc the Object callback handlers. 17529 * 17530 * @requires finesse.clientservices.ClientServices 17531 * @requires Class 17532 */ 17533 /** @private */ 17534 define('interfaces/RestObjectHandlers',[ 17535 "FinesseBase", 17536 "utilities/Utilities", 17537 "restservices/Notifier", 17538 "clientservices/ClientServices", 17539 "clientservices/Topics" 17540 ], 17541 function () { 17542 17543 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 17544 17545 return { 17546 17547 /** 17548 * @class 17549 * This "interface" defines REST Object callback handlers, passed as an argument to 17550 * Object getter methods in cases where the Object is going to be created. 17551 * 17552 * @param {Object} [handlers] 17553 * An object containing callback handlers for instantiation and runtime 17554 * Callback to invoke upon successful instantiation, passes in REST object. 17555 * @param {Function} [handlers.onLoad(this)] 17556 * Callback to invoke upon loading the data for the first time. 17557 * @param {Function} [handlers.onChange(this)] 17558 * Callback to invoke upon successful update object (PUT) 17559 * @param {Function} [handlers.onAdd(this)] 17560 * Callback to invoke upon successful update to add object (POST) 17561 * @param {Function} [handlers.onDelete(this)] 17562 * Callback to invoke upon successful update to delete object (DELETE) 17563 * @param {Function} [handlers.onError(rsp)] 17564 * Callback to invoke on update error (refresh or event) 17565 * as passed by finesse.restservices.RestBase.restRequest()<br> 17566 * {<br> 17567 * status: {Number} The HTTP status code returned<br> 17568 * content: {String} Raw string of response<br> 17569 * object: {Object} Parsed object of response<br> 17570 * error: {Object} Wrapped exception that was caught<br> 17571 * error.errorType: {String} Type of error that was caught<br> 17572 * error.errorMessage: {String} Message associated with error<br> 17573 * }<br> 17574 * <br> 17575 * Note that RestCollections have two additional callback handlers:<br> 17576 * <br> 17577 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 17578 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 17579 17580 * @constructs 17581 */ 17582 _fakeConstuctor: function () { 17583 /* This is here to enable jsdoc to document this as a class. */ 17584 } 17585 }; 17586 }()); 17587 17588 window.finesse = window.finesse || {}; 17589 window.finesse.interfaces = window.finesse.interfaces || {}; 17590 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 17591 17592 return RestObjectHandlers; 17593 17594 }); 17595 17596 17597 /** 17598 * This "interface" is just a way to easily jsdoc the REST request handlers. 17599 * 17600 * @requires finesse.clientservices.ClientServices 17601 * @requires Class 17602 */ 17603 /** @private */ 17604 define('interfaces/RequestHandlers',[ 17605 "FinesseBase", 17606 "utilities/Utilities", 17607 "restservices/Notifier", 17608 "clientservices/ClientServices", 17609 "clientservices/Topics" 17610 ], 17611 function () { 17612 17613 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 17614 17615 return { 17616 17617 /** 17618 * @class 17619 * This "interface" defines REST Object callback handlers, passed as an argument to 17620 * Object getter methods in cases where the Object is going to be created. 17621 * 17622 * @param {Object} handlers 17623 * An object containing the following (optional) handlers for the request:<ul> 17624 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 17625 * response object as its only parameter:<ul> 17626 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17627 * <li><b>content:</b> {String} Raw string of response</li> 17628 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 17629 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 17630 * error response object as its only parameter:<ul> 17631 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17632 * <li><b>content:</b> {String} Raw string of response</li> 17633 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 17634 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17635 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17636 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17637 * </ul></li> 17638 * </ul> 17639 17640 * @constructs 17641 */ 17642 _fakeConstuctor: function () { 17643 /* This is here to enable jsdoc to document this as a class. */ 17644 } 17645 }; 17646 }()); 17647 17648 window.finesse = window.finesse || {}; 17649 window.finesse.interfaces = window.finesse.interfaces || {}; 17650 window.finesse.interfaces.RequestHandlers = RequestHandlers; 17651 17652 finesse = finesse || {}; 17653 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 17654 finesse.interfaces = finesse.interfaces || {}; 17655 17656 return RequestHandlers; 17657 17658 }); 17659 17660 17661 define('finesse',[ 17662 'restservices/Users', 17663 'restservices/Teams', 17664 'restservices/SystemInfo', 17665 'utilities/I18n', 17666 'utilities/Logger', 17667 'utilities/SaxParser', 17668 'cslogger/ClientLogger', 17669 'cslogger/FinesseLogger', 17670 'containerservices/ContainerServices', 17671 'interfaces/RestObjectHandlers', 17672 'interfaces/RequestHandlers' 17673 ], 17674 function () { 17675 17676 // If being used in a gadget, stuff the auth string into the gadget prefs 17677 if (gadgets.Prefs) { 17678 var _prefs = new gadgets.Prefs(), 17679 authString = finesse.utilities.Utilities.getUserAuthString(); 17680 _prefs.set("authorization", authString); 17681 } 17682 17683 // window.finesse = finesse; 17684 //TODO: For now, we are preserving the dependency tree order to maintain structural integrity 17685 // Once verified, we can clean up all window object references and only assign finesse from here 17686 17687 return window.finesse; 17688 17689 }); 17690 17691 require(["finesse"]); 17692 return require('finesse'); })); 17693 17694 // Prevent other JS files from wiping out window.finesse from the namespace 17695 var finesse = window.finesse;