1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 10.0(1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2013 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 30 /* Simple JavaScript Inheritance 31 * By John Resig http://ejohn.org/ 32 * MIT Licensed. 33 */ 34 // Inspired by base2 and Prototype 35 (function (factory) { 36 37 38 // Define as an AMD module if possible 39 if ( typeof define === 'function' && define.amd ) 40 { 41 define('../thirdparty/Class',[], factory ); 42 } 43 44 /* Define using browser globals otherwise 45 * Prevent multiple instantiations if the script is loaded twice 46 */ 47 else 48 { 49 factory(); 50 } 51 }(function () { 52 (function(){ 53 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 54 // The base Class implementation (does nothing) 55 /** @private */ 56 this.Class = function(){}; 57 58 // Create a new Class that inherits from this class 59 /** @private */ 60 Class.extend = function(prop) { 61 var _super = this.prototype; 62 63 // Instantiate a base class (but only create the instance, 64 // don't run the init constructor) 65 initializing = true; 66 var prototype = new this(); 67 initializing = false; 68 69 // Copy the properties over onto the new prototype 70 for (var name in prop) { 71 // Check if we're overwriting an existing function 72 prototype[name] = typeof prop[name] == "function" && 73 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 74 (function(name, fn){ 75 return function() { 76 var tmp = this._super; 77 78 // Add a new ._super() method that is the same method 79 // but on the super-class 80 this._super = _super[name]; 81 82 // The method only need to be bound temporarily, so we 83 // remove it when we're done executing 84 var ret = fn.apply(this, arguments); 85 this._super = tmp; 86 87 return ret; 88 }; 89 })(name, prop[name]) : 90 prop[name]; 91 } 92 93 // The dummy class constructor 94 /** @private */ 95 function Class() { 96 // All construction is actually done in the init method 97 if ( !initializing && this.init ) 98 this.init.apply(this, arguments); 99 } 100 101 // Populate our constructed prototype object 102 Class.prototype = prototype; 103 104 // Enforce the constructor to be what we expect 105 Class.prototype.constructor = Class; 106 107 // And make this class extendable 108 Class.extend = arguments.callee; 109 110 return Class; 111 }; 112 })(); 113 })); 114 115 /** 116 * JavaScript base object that all finesse objects should inherit 117 * from because it encapsulates and provides the common functionality. 118 * 119 * Note: This javascript class requires the "inhert.js" to be included 120 * (which simplifies the class inheritance). 121 * 122 * 123 * @requires finesse.utilities.Logger 124 */ 125 126 /** The following comment is to prevent jslint errors about 127 * using variables before they are defined. 128 */ 129 /*global Class */ 130 (function (factory) { 131 132 133 // Define as an AMD module if possible 134 if ( typeof define === 'function' && define.amd ) 135 { 136 define('FinesseBase', ["../thirdparty/Class"], factory ); 137 } 138 /* Define using browser globals otherwise 139 * Prevent multiple instantiations if the script is loaded twice 140 */ 141 else 142 { 143 factory(Class); 144 } 145 }(function (Class) { 146 var FinesseBase = Class.extend({ 147 init: function () { 148 } 149 }); 150 151 window.finesse = window.finesse || {}; 152 window.finesse.FinesseBase = FinesseBase; 153 154 return FinesseBase; 155 })); 156 157 /** 158 * A collection of conversion utilities. 159 * Last modified 07-06-2011, Cisco Systems 160 * 161 */ 162 /** @private */ 163 (function (factory) { 164 165 166 // Define as an AMD module if possible 167 if ( typeof define === 'function' && define.amd ) 168 { 169 define('utilities/../../thirdparty/util/converter',[], factory); 170 } 171 /* Define using browser globals otherwise 172 * Prevent multiple instantiations if the script is loaded twice 173 */ 174 else 175 { 176 factory(); 177 } 178 }(function () { 179 window.finesse = window.finesse || {}; 180 181 /** 182 * @class 183 * Contains a collection of utility functions. 184 * @private 185 */ 186 window.finesse.Converter = (function () { 187 return { 188 /* This work is licensed under Creative Commons GNU LGPL License. 189 190 License: http://creativecommons.org/licenses/LGPL/2.1/ 191 Version: 0.9 192 Author: Stefan Goessner/2006 193 Web: http://goessner.net/ 194 195 2013-09-16 Modified to remove use of XmlNode.innerHTML in the innerXml function by Cisco Systems, Inc. 196 */ 197 xml2json: function (xml, tab) { 198 var X = { 199 toObj: function (xml) { 200 var o = {}; 201 if (xml.nodeType === 1) { 202 // element node .. 203 if (xml.attributes.length) 204 // element with attributes .. 205 for (var i = 0; i < xml.attributes.length; i++) 206 o["@" + xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue || "").toString(); 207 if (xml.firstChild) { 208 // element has child nodes .. 209 var textChild = 0, 210 cdataChild = 0, 211 hasElementChild = false; 212 for (var n = xml.firstChild; n; n = n.nextSibling) { 213 if (n.nodeType == 1) hasElementChild = true; 214 else if (n.nodeType == 3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; 215 // non-whitespace text 216 else if (n.nodeType == 4) cdataChild++; 217 // cdata section node 218 } 219 if (hasElementChild) { 220 if (textChild < 2 && cdataChild < 2) { 221 // structured element with evtl. a single text or/and cdata node .. 222 X.removeWhite(xml); 223 for (var n = xml.firstChild; n; n = n.nextSibling) { 224 if (n.nodeType == 3) 225 // text node 226 o["#text"] = X.escape(n.nodeValue); 227 else if (n.nodeType == 4) 228 // cdata node 229 o["#cdata"] = X.escape(n.nodeValue); 230 else if (o[n.nodeName]) { 231 // multiple occurence of element .. 232 if (o[n.nodeName] instanceof Array) 233 o[n.nodeName][o[n.nodeName].length] = X.toObj(n); 234 else 235 o[n.nodeName] = [o[n.nodeName], X.toObj(n)]; 236 } 237 else 238 // first occurence of element.. 239 o[n.nodeName] = X.toObj(n); 240 } 241 } 242 else { 243 // mixed content 244 if (!xml.attributes.length) 245 o = X.escape(X.innerXml(xml)); 246 else 247 o["#text"] = X.escape(X.innerXml(xml)); 248 } 249 } 250 else if (textChild) { 251 // pure text 252 if (!xml.attributes.length) 253 o = X.escape(X.innerXml(xml)); 254 else 255 o["#text"] = X.escape(X.innerXml(xml)); 256 } 257 else if (cdataChild) { 258 // cdata 259 if (cdataChild > 1) 260 o = X.escape(X.innerXml(xml)); 261 else 262 for (var n = xml.firstChild; n; n = n.nextSibling) 263 o["#cdata"] = X.escape(n.nodeValue); 264 } 265 } 266 if (!xml.attributes.length && !xml.firstChild) o = null; 267 } 268 else if (xml.nodeType == 9) { 269 // document.node 270 o = X.toObj(xml.documentElement); 271 } 272 else 273 throw ("unhandled node type: " + xml.nodeType); 274 return o; 275 }, 276 toJson: function(o, name, ind) { 277 var json = name ? ("\"" + name + "\"") : ""; 278 if (o instanceof Array) { 279 for (var i = 0, n = o.length; i < n; i++) 280 o[i] = X.toJson(o[i], "", ind + "\t"); 281 json += (name ? ":[": "[") + (o.length > 1 ? ("\n" + ind + "\t" + o.join(",\n" + ind + "\t") + "\n" + ind) : o.join("")) + "]"; 282 } 283 else if (o == null) 284 json += (name && ":") + "null"; 285 else if (typeof(o) == "object") { 286 var arr = []; 287 for (var m in o) 288 arr[arr.length] = X.toJson(o[m], m, ind + "\t"); 289 json += (name ? ":{": "{") + (arr.length > 1 ? ("\n" + ind + "\t" + arr.join(",\n" + ind + "\t") + "\n" + ind) : arr.join("")) + "}"; 290 } 291 else if (typeof(o) == "string") 292 json += (name && ":") + "\"" + o.toString() + "\""; 293 else 294 json += (name && ":") + o.toString(); 295 return json; 296 }, 297 innerXml: function(node) { 298 var s = ""; 299 var asXml = function(n) { 300 var s = ""; 301 if (n.nodeType == 1) { 302 s += "<" + n.nodeName; 303 for (var i = 0; i < n.attributes.length; i++) 304 s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue || "").toString() + "\""; 305 if (n.firstChild) { 306 s += ">"; 307 for (var c = n.firstChild; c; c = c.nextSibling) 308 s += asXml(c); 309 s += "</" + n.nodeName + ">"; 310 } 311 else 312 s += "/>"; 313 } 314 else if (n.nodeType == 3) 315 s += n.nodeValue; 316 else if (n.nodeType == 4) 317 s += "<![CDATA[" + n.nodeValue + "]]>"; 318 return s; 319 }; 320 for (var c = node.firstChild; c; c = c.nextSibling) 321 s += asXml(c); 322 return s; 323 }, 324 escape: function(txt) { 325 return txt.replace(/[\\]/g, "\\\\") 326 .replace(/[\"]/g, '\\"') 327 .replace(/[\n]/g, '\\n') 328 .replace(/[\r]/g, '\\r'); 329 }, 330 removeWhite: function(e) { 331 e.normalize(); 332 for (var n = e.firstChild; n;) { 333 if (n.nodeType == 3) { 334 // text node 335 if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { 336 // pure whitespace text node 337 var nxt = n.nextSibling; 338 e.removeChild(n); 339 n = nxt; 340 } 341 else 342 n = n.nextSibling; 343 } 344 else if (n.nodeType == 1) { 345 // element node 346 X.removeWhite(n); 347 n = n.nextSibling; 348 } 349 else 350 // any other node 351 n = n.nextSibling; 352 } 353 return e; 354 } 355 }; 356 if (xml.nodeType == 9) 357 // document node 358 xml = xml.documentElement; 359 var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 360 return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 361 }, 362 363 /* This work is licensed under Creative Commons GNU LGPL License. 364 365 License: http://creativecommons.org/licenses/LGPL/2.1/ 366 Version: 0.9 367 Author: Stefan Goessner/2006 368 Web: http://goessner.net/ 369 */ 370 json2xml: function(o, tab) { 371 var toXml = function(v, name, ind) { 372 var xml = ""; 373 if (v instanceof Array) { 374 for (var i = 0, n = v.length; i < n; i++) 375 xml += ind + toXml(v[i], name, ind + "\t") + "\n"; 376 } 377 else if (typeof(v) == "object") { 378 var hasChild = false; 379 xml += ind + "<" + name; 380 for (var m in v) { 381 if (m.charAt(0) == "@") 382 xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\""; 383 else 384 hasChild = true; 385 } 386 xml += hasChild ? ">": "/>"; 387 if (hasChild) { 388 for (var m in v) { 389 if (m == "#text") 390 xml += v[m]; 391 else if (m == "#cdata") 392 xml += "<![CDATA[" + v[m] + "]]>"; 393 else if (m.charAt(0) != "@") 394 xml += toXml(v[m], m, ind + "\t"); 395 } 396 xml += (xml.charAt(xml.length - 1) == "\n" ? ind: "") + "</" + name + ">"; 397 } 398 } 399 else { 400 xml += ind + "<" + name + ">" + v.toString() + "</" + name + ">"; 401 } 402 return xml; 403 }, 404 xml = ""; 405 for (var m in o) 406 xml += toXml(o[m], m, ""); 407 return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 408 } 409 }; 410 })(); 411 })); 412 413 /** 414 * SaxParser.js: provides a simple SAX parser 415 * 416 * NONVALIDATING - this will not validate whether you have valid XML or not. It will simply report what it finds. 417 * Only supports elements, attributes, and text. No comments, cdata, processing instructions, etc. 418 */ 419 420 /** 421 * @requires 422 * @ignore 423 */ 424 // Add SaxParser to the finesse.utilities namespace 425 var finesse = finesse || {}; 426 finesse.utilities = finesse.utilities || {}; 427 428 (function (factory) { 429 430 431 // Define as an AMD module if possible 432 if ( typeof define === 'function' && define.amd ) 433 { 434 define('utilities/SaxParser', [], factory ); 435 } 436 /* Define using browser globals otherwise 437 * Prevent multiple instantiations if the script is loaded twice 438 */ 439 else 440 { 441 finesse.utilities.SaxParser = factory(); 442 } 443 }(function () { 444 return { 445 parse: function(xml, callback) { 446 // Event callbacks 447 /** @private */ 448 var triggerEvent = function (type, data) { 449 callback.call(null, type, data); 450 }, 451 /** @private */ 452 triggerStartElement = function (name) { 453 triggerEvent("StartElement", name); 454 }, 455 /** @private */ 456 triggerEndElement = function (name) { 457 triggerEvent("EndElement", name); 458 }, 459 /** @private */ 460 triggerAttribute = function (name, value) { 461 triggerEvent("Attribute", { "name": name, "value": value }); 462 }, 463 /** @private */ 464 triggerText = function (text) { 465 triggerEvent("Text", text); 466 }, 467 468 // Parsing 469 cursor = 0, 470 xmlLength = xml.length, 471 whitespaceRegex = /^[ \t\r\n]*$/, 472 /** @private */ 473 isWhitespace = function (text) { 474 return whitespaceRegex.test(text); 475 }, 476 /** @private */ 477 moveToNonWhitespace = function () { 478 while (isWhitespace(xml.charAt(cursor))) { 479 cursor += 1; 480 } 481 }, 482 /** @private */ 483 parseAttribute = function () { 484 var nameBuffer = [], 485 valueBuffer = [], 486 valueIsQuoted = false, 487 cursorChar = ""; 488 489 nameBuffer.push(xml.charAt(cursor)); 490 491 // Get the name 492 cursor += 1; 493 while (cursor < xmlLength) { 494 cursorChar = xml.charAt(cursor); 495 if (isWhitespace(cursorChar) || cursorChar === "=") { 496 // Move on to gathering value 497 break; 498 } 499 else { 500 nameBuffer.push(cursorChar); 501 } 502 cursor += 1; 503 } 504 505 // Skip the equals sign and any whitespace 506 moveToNonWhitespace(); 507 if (cursorChar === "=") { 508 cursor += 1; 509 } else { 510 throw new Error("Did not find = following attribute name at " + cursor); 511 } 512 moveToNonWhitespace(); 513 514 // Get the value 515 valueIsQuoted = cursor !== xmlLength - 1 ? xml.charAt(cursor) === "\"": false; 516 if (valueIsQuoted) { 517 cursor += 1; 518 while (cursor < xmlLength) { 519 cursorChar = xml.charAt(cursor); 520 if (cursorChar === "\"") { 521 // Found the closing quote, so end value 522 triggerAttribute(nameBuffer.join(""), valueBuffer.join("")); 523 break; 524 } 525 else { 526 valueBuffer.push(cursorChar); 527 } 528 cursor += 1; 529 } 530 } 531 else { 532 throw new Error("Found unquoted attribute value at " + cursor); 533 } 534 }, 535 /** @private */ 536 parseEndElement = function () { 537 var elementNameBuffer = [], 538 cursorChar = ""; 539 cursor += 2; 540 while (cursor < xmlLength) { 541 cursorChar = xml.charAt(cursor); 542 if (cursorChar === ">") { 543 triggerEndElement(elementNameBuffer.join("")); 544 break; 545 } 546 else { 547 elementNameBuffer.push(cursorChar); 548 } 549 cursor += 1; 550 } 551 }, 552 /** @private */ 553 parseReference = function() { 554 var type, 555 TYPE_DEC_CHAR_REF = 1, 556 TYPE_HEX_CHAR_REF = 2, 557 TYPE_ENTITY_REF = 3, 558 buffer = ""; 559 cursor += 1; 560 // Determine the type of reference. 561 if (xml.charAt(cursor) === "#") { 562 cursor += 1; 563 if (xml.charAt(cursor) === "x") { 564 type = TYPE_HEX_CHAR_REF; 565 cursor += 1; 566 } else { 567 type = TYPE_DEC_CHAR_REF; 568 } 569 } else { 570 type = TYPE_ENTITY_REF; 571 } 572 // Read the reference into a buffer. 573 while (xml.charAt(cursor) !== ";") { 574 buffer += xml.charAt(cursor); 575 cursor += 1; 576 if (cursor >= xmlLength) { 577 throw new Error("Unterminated XML reference: " + buffer); 578 } 579 } 580 // Convert the reference to the appropriate character. 581 switch (type) { 582 case TYPE_DEC_CHAR_REF: 583 return String.fromCharCode(parseInt(buffer, 10)); 584 case TYPE_HEX_CHAR_REF: 585 return String.fromCharCode(parseInt(buffer, 16)); 586 case TYPE_ENTITY_REF: 587 switch (buffer) { 588 case "amp": 589 return "&"; 590 case "lt": 591 return "<"; 592 case "gt": 593 return ">"; 594 case "apos": 595 return "'"; 596 case "quot": 597 return "\""; 598 default: 599 throw new Error("Invalid XML entity reference: " + buffer); 600 } 601 // break; (currently unreachable) 602 } 603 }, 604 /** @private */ 605 parseElement = function () { 606 var elementNameBuffer = [], 607 textBuffer = [], 608 cursorChar = "", 609 whitespace = false; 610 611 // Get element name 612 cursor += 1; 613 while (cursor < xmlLength) { 614 cursorChar = xml.charAt(cursor); 615 whitespace = isWhitespace(cursorChar); 616 if (!whitespace && cursorChar !== "/" && cursorChar !== ">") { 617 elementNameBuffer.push(cursorChar); 618 } 619 else { 620 elementNameBuffer = elementNameBuffer.join(""); 621 triggerStartElement(elementNameBuffer); 622 break; 623 } 624 cursor += 1; 625 } 626 627 // Get attributes 628 if (whitespace) { 629 while (cursor < xmlLength) { 630 moveToNonWhitespace(); 631 cursorChar = xml.charAt(cursor); 632 if (cursorChar !== "/" && cursorChar !== ">") { 633 // Start of attribute 634 parseAttribute(); 635 } 636 cursorChar = xml.charAt(cursor); 637 if (cursorChar === "/" || cursorChar === ">") { 638 break; 639 } 640 else { 641 cursor += 1; 642 } 643 } 644 } 645 646 // End tag if "/>" was found, 647 // otherwise we're at the end of the start tag and have to parse into it 648 if (cursorChar === "/") { 649 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === ">") { 650 cursor += 1; 651 triggerEndElement(elementNameBuffer); 652 } 653 } 654 else { 655 // cursor is on ">", so parse into element content. Assume text until we find a "<", 656 // which could be a child element or the current element's end tag. We do not support 657 // mixed content of text and elements as siblings unless the text is only whitespace. 658 // Text cannot contain <, >, ", or &. They should be <, >, ", & respectively. 659 cursor += 1; 660 while (cursor < xmlLength) { 661 cursorChar = xml.charAt(cursor); 662 if (cursorChar === "<") { 663 // Determine if end tag or element 664 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === "/") { 665 // At end tag 666 textBuffer = textBuffer.join(""); 667 if (!isWhitespace(textBuffer)) { 668 triggerText(textBuffer); 669 } 670 parseEndElement(); 671 break; 672 } 673 else { 674 // At start tag 675 textBuffer = textBuffer.join(""); 676 if (!isWhitespace(textBuffer)) { 677 triggerText(textBuffer); 678 } 679 parseElement(); 680 textBuffer = []; 681 } 682 } else if (cursorChar === "&") { 683 textBuffer.push(parseReference()); 684 } 685 else { 686 textBuffer.push(cursorChar); 687 } 688 cursor += 1; 689 } 690 } 691 }, 692 /** @private */ 693 skipXmlDeclaration = function() { 694 if (xml.substr(0, 5) === "<?xml" && isWhitespace(xml.charAt(5))) { 695 cursor = xml.indexOf(">") + 1; 696 } 697 moveToNonWhitespace(); 698 }; 699 700 // Launch. 701 skipXmlDeclaration(); 702 parseElement(); 703 } 704 }; 705 })); 706 707 /** 708 * Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> 709 * ?? 2011 Colin Snover <http://zetafleet.com> 710 * Released under MIT license. 711 */ 712 (function (factory) { 713 714 715 // Define as an AMD module if possible 716 if ( typeof define === 'function' && define.amd ) 717 { 718 define('iso8601', [], factory ); 719 } 720 /* Define using browser globals otherwise 721 * Prevent multiple instantiations if the script is loaded twice 722 */ 723 else 724 { 725 factory(); 726 } 727 }(function () { 728 (function (Date, undefined) { 729 var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 730 /** @private **/ 731 Date.parse = function (date) { 732 var timestamp, struct, minutesOffset = 0; 733 734 // ES5 ??15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 735 // before falling back to any implementation-specific date parsing, so that???s what we do, even if native 736 // implementations could be faster 737 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ?? 10 tzHH 11 tzmm 738 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))) { 739 // avoid NaN timestamps caused by ???undefined??? values being passed to Date.UTC 740 for (var i = 0, k; (k = numericKeys[i]); ++i) { 741 struct[k] = +struct[k] || 0; 742 } 743 744 // allow undefined days and months 745 struct[2] = (+struct[2] || 1) - 1; 746 struct[3] = +struct[3] || 1; 747 748 if (struct[8] !== 'Z' && struct[9] !== undefined) { 749 minutesOffset = struct[10] * 60 + struct[11]; 750 751 if (struct[9] === '+') { 752 minutesOffset = 0 - minutesOffset; 753 } 754 } 755 756 timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); 757 } 758 else { 759 timestamp = origParse ? origParse(date) : NaN; 760 } 761 762 return timestamp; 763 }; 764 }(Date)); 765 })); 766 767 /*! 768 Math.uuid.js (v1.4) 769 http://www.broofa.com 770 mailto:robert@broofa.com 771 772 Copyright (c) 2010 Robert Kieffer 773 Dual licensed under the MIT and GPL licenses. 774 */ 775 776 /* 777 * Generate a random uuid. 778 * 779 * USAGE: Math.uuid(length, radix) 780 * length - the desired number of characters 781 * radix - the number of allowable values for each character. 782 * 783 * EXAMPLES: 784 * // No arguments - returns RFC4122, version 4 ID 785 * >>> Math.uuid() 786 * "92329D39-6F5C-4520-ABFC-AAB64544E172" 787 * 788 * // One argument - returns ID of the specified length 789 * >>> Math.uuid(15) // 15 character ID (default base=62) 790 * "VcydxgltxrVZSTV" 791 * 792 * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) 793 * >>> Math.uuid(8, 2) // 8 character ID (base=2) 794 * "01001010" 795 * >>> Math.uuid(8, 10) // 8 character ID (base=10) 796 * "47473046" 797 * >>> Math.uuid(8, 16) // 8 character ID (base=16) 798 * "098F4D35" 799 */ 800 (function (factory) { 801 802 803 // Define as an AMD module if possible 804 if ( typeof define === 'function' && define.amd ) 805 { 806 define('Math.uuid', [], factory ); 807 } 808 /* Define using browser globals otherwise 809 * Prevent multiple instantiations if the script is loaded twice 810 */ 811 else 812 { 813 factory(); 814 } 815 }(function () { 816 (function() { 817 // Private array of chars to use 818 var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 819 820 /** @private **/ 821 Math.uuid = function (len, radix) { 822 var chars = CHARS, uuid = [], i; 823 radix = radix || chars.length; 824 825 if (len) { 826 // Compact form 827 for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 828 } else { 829 // rfc4122, version 4 form 830 var r; 831 832 // rfc4122 requires these characters 833 uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 834 uuid[14] = '4'; 835 836 // Fill in random data. At i==19 set the high bits of clock sequence as 837 // per rfc4122, sec. 4.1.5 838 for (i = 0; i < 36; i++) { 839 if (!uuid[i]) { 840 r = 0 | Math.random()*16; 841 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 842 } 843 } 844 } 845 846 return uuid.join(''); 847 }; 848 849 // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 850 // by minimizing calls to random() 851 /** @private **/ 852 Math.uuidFast = function() { 853 var chars = CHARS, uuid = new Array(36), rnd=0, r; 854 for (var i = 0; i < 36; i++) { 855 if (i==8 || i==13 || i==18 || i==23) { 856 uuid[i] = '-'; 857 } else if (i==14) { 858 uuid[i] = '4'; 859 } else { 860 if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 861 r = rnd & 0xf; 862 rnd = rnd >> 4; 863 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 864 } 865 } 866 return uuid.join(''); 867 }; 868 869 // A more compact, but less performant, RFC4122v4 solution: 870 /** @private **/ 871 Math.uuidCompact = function() { 872 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 873 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 874 return v.toString(16); 875 }); 876 }; 877 })(); 878 })); 879 880 /** 881 * The following comment prevents JSLint errors concerning undefined global variables. 882 * It tells JSLint that these identifiers are defined elsewhere. 883 */ 884 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true, plusplus: true, unparam: true, forin: true */ 885 886 /** The following comment is to prevent jslint errors about 887 * using variables before they are defined. 888 */ 889 /*global $, _prefs,_uiMsg,ciscowidgets,dojo,finesse,gadgets,hostUrl, Handlebars */ 890 891 /** 892 * A collection of utility functions. 893 * 894 * @requires finesse.Converter 895 */ 896 var finesse = finesse || {}; 897 /** @namespace Useful javascript utility methods. */ 898 finesse.utilities = finesse.utilities || {}; 899 900 (function (factory) { 901 902 903 // Define as an AMD module if possible 904 if ( typeof define === 'function' && define.amd ) 905 { 906 define('utilities/Utilities', ["../../thirdparty/util/converter", 907 "utilities/SaxParser", 908 "iso8601", 909 "Math.uuid"], factory ); 910 } 911 /* Define using browser globals otherwise 912 * Prevent multiple instantiations if the script is loaded twice 913 */ 914 else 915 { 916 finesse.utilities.Utilities = factory(finesse.Converter, finesse.utilities.SaxParser); 917 } 918 }(function (Converter, SaxParser) { 919 var Utilities = /** @lends finesse.utilities.Utilities */ { 920 921 /** 922 * @class 923 * A PhoneBook is a list of Contacts available to a User for quick dial. 924 * 925 * @augments finesse.restservices.RestBase 926 * @see finesse.restservices.Contacts 927 * @constructs 928 */ 929 _fakeConstuctor: function () { 930 /* This is here for jsdocs. */ 931 }, 932 933 /** 934 * @private 935 * Retrieves the specified item from window.localStorage 936 * @param {String} key 937 * The key of the item to retrieve 938 * @returns {String} 939 * The string with the value of the retrieved item; returns 940 * what the browser would return if not found (typically null or undefined) 941 * Returns false if window.localStorage feature is not even found. 942 */ 943 getDOMStoreItem: function (key) { 944 var store = window.localStorage; 945 if (store) { 946 return store.getItem(key); 947 } 948 }, 949 950 /** 951 * @private 952 * Sets an item into window.localStorage 953 * @param {String} key 954 * The key for the item to set 955 * @param {String} value 956 * The value to set 957 * @returns {Boolean} 958 * True if successful, false if window.localStorage is 959 * not even found. 960 */ 961 setDOMStoreItem: function (key, value) { 962 var store = window.localStorage; 963 if (store) { 964 store.setItem(key, value); 965 return true; 966 } 967 return false; 968 }, 969 970 /** 971 * @private 972 * Removes a particular item from window.localStorage 973 * @param {String} key 974 * The key of the item to remove 975 * @returns {Boolean} 976 * True if successful, false if not 977 * Returns false if window.localStorage feature is not even found. 978 */ 979 removeDOMStoreItem: function (key) { 980 var store = window.localStorage; 981 if (store) { 982 store.removeItem(key); 983 return true; 984 } 985 return false; 986 }, 987 988 /** 989 * @private 990 * Dumps all the contents of window.localStorage 991 * @returns {Boolean} 992 * True if successful, false if not. 993 * Returns false if window.localStorage feature is not even found. 994 */ 995 clearDOMStore: function () { 996 var store = window.localStorage; 997 if (store) { 998 store.clear(); 999 return true; 1000 } 1001 return false; 1002 }, 1003 1004 /** 1005 * @private 1006 * Creates a message listener for window.postMessage messages. 1007 * @param {Function} callback 1008 * The callback that will be invoked with the message. The callback 1009 * is responsible for any security checks. 1010 * @param {String} [origin] 1011 * The origin to check against for security. Allows all messages 1012 * if no origin is provided. 1013 * @returns {Function} 1014 * The callback function used to register with the message listener. 1015 * This is different than the one provided as a parameter because 1016 * the function is overloaded with origin checks. 1017 * @throws {Error} If the callback provided is not a function. 1018 */ 1019 receiveMessage: function (callback, origin) { 1020 if (typeof callback !== "function") { 1021 throw new Error("Callback is not a function."); 1022 } 1023 1024 //Create a function closure to perform origin check. 1025 /** @private */ 1026 var cb = function (e) { 1027 // If an origin check is requested (provided), we'll only invoke the callback if it passes 1028 if (typeof origin !== "string" || (typeof origin === "string" && typeof e.origin === "string" && e.origin.toLowerCase() === origin.toLowerCase())) { 1029 callback(e); 1030 } 1031 }; 1032 1033 if (window.addEventListener) { //Firefox, Opera, Chrome, Safari 1034 window.addEventListener("message", cb, false); 1035 } else { //Internet Explorer 1036 window.attachEvent("onmessage", cb); 1037 } 1038 1039 //Return callback used to register with listener so that invoker 1040 //could use it to remove. 1041 return cb; 1042 }, 1043 1044 /** 1045 * @private 1046 * Sends a message to a target frame using window.postMessage. 1047 * @param {Function} message 1048 * Message to be sent to target frame. 1049 * @param {Object} [target="parent"] 1050 * An object reference to the target frame. Default us the parent. 1051 * @param {String} [targetOrigin="*"] 1052 * The URL of the frame this frame is sending the message to. 1053 */ 1054 sendMessage: function (message, target, targetOrigin) { 1055 //Default to any target URL if none is specified. 1056 targetOrigin = targetOrigin || "*"; 1057 1058 //Default to parent target if none is specified. 1059 target = target || parent; 1060 1061 //Ensure postMessage is supported by browser before invoking. 1062 if (window.postMessage) { 1063 target.postMessage(message, targetOrigin); 1064 } 1065 }, 1066 1067 /** 1068 * Returns the passed in handler, if it is a function. 1069 * @param {Function} handler 1070 * The handler to validate 1071 * @returns {Function} 1072 * The provided handler if it is valid 1073 * @throws Error 1074 * If the handler provided is invalid 1075 */ 1076 validateHandler: function (handler) { 1077 if (handler === undefined || typeof handler === "function") { 1078 return handler; 1079 } else { 1080 throw new Error("handler must be a function"); 1081 } 1082 }, 1083 1084 /** 1085 * @private 1086 * Tries to get extract the AWS error code from a 1087 * finesse.clientservices.ClientServices parsed error response object. 1088 * @param {Object} rsp 1089 * The handler to validate 1090 * @returns {String} 1091 * The error code, HTTP status code, or undefined 1092 */ 1093 getErrCode: function (rsp) { 1094 try { // Best effort to get the error code 1095 return rsp.object.ApiErrors.ApiError.ErrorType; 1096 } catch (e) { // Second best effort to get the HTTP Status code 1097 if (rsp && rsp.status) { 1098 return "HTTP " + rsp.status; 1099 } 1100 } // Otherwise, don't return anything (undefined) 1101 }, 1102 1103 /** 1104 * @private 1105 * Tries to get extract the AWS error data from a 1106 * finesse.clientservices.ClientServices parsed error response object. 1107 * @param {Object} rsp 1108 * The handler to validate 1109 * @returns {String} 1110 * The error data, HTTP status code, or undefined 1111 */ 1112 getErrData: function (rsp) { 1113 try { // Best effort to get the error data 1114 return rsp.object.ApiErrors.ApiError.ErrorData; 1115 } catch (e) { // Second best effort to get the HTTP Status code 1116 if (rsp && rsp.status) { 1117 return "HTTP " + rsp.status; 1118 } 1119 } // Otherwise, don't return anything (undefined) 1120 }, 1121 1122 /** 1123 * @private 1124 * Tries to get extract the AWS overrideable boolean from a 1125 * finesse.clientservices.ClientServices parsed error response object. 1126 * @param {Object} rsp 1127 * The handler to validate 1128 * @returns {String} 1129 * The overrideable boolean, HTTP status code, or undefined 1130 */ 1131 getErrOverrideable: function (rsp) { 1132 try { // Best effort to get the override boolean 1133 return rsp.object.ApiErrors.ApiError.Overrideable; 1134 } catch (e) { // Second best effort to get the HTTP Status code 1135 if (rsp && rsp.status) { 1136 return "HTTP " + rsp.status; 1137 } 1138 } // Otherwise, don't return anything (undefined) 1139 }, 1140 1141 /** 1142 * Trims leading and trailing whitespace from a string. 1143 * @param {String} str 1144 * The string to trim 1145 * @returns {String} 1146 * The trimmed string 1147 */ 1148 trim: function (str) { 1149 return str.replace(/^\s*/, "").replace(/\s*$/, ""); 1150 }, 1151 1152 /** 1153 * Utility method for getting the current time in milliseconds. 1154 * @returns {String} 1155 * The current time in milliseconds 1156 */ 1157 currentTimeMillis : function () { 1158 return (new Date()).getTime(); 1159 }, 1160 1161 /** 1162 * Utility method for getting the current time, 1163 * adjusted by the calculated "drift" to closely 1164 * approximate the server time. This is used 1165 * when calculating durations based on a server 1166 * timestamp, which otherwise can produce unexpected 1167 * results if the times on client and server are 1168 * off. 1169 * 1170 * @returns {String} 1171 * The current server time in milliseconds 1172 */ 1173 currentServerTimeMillis : function () { 1174 if (!finesse.container.Config.clientDriftInMillis) { 1175 finesse.container.Config.clientDriftInMillis = 0; 1176 } 1177 return (new Date()).getTime() + finesse.container.Config.clientDriftInMillis; 1178 }, 1179 1180 /** 1181 * @private 1182 * Generates an RFC1422v4-compliant UUID using pesudorandom numbers. 1183 * @returns {String} 1184 * An RFC1422v4-compliant UUID using pesudorandom numbers. 1185 **/ 1186 generateUUID: function () { 1187 return Math.uuidCompact(); 1188 }, 1189 1190 /** @private */ 1191 xml2json: Converter.xml2json, 1192 1193 1194 /** 1195 * @private 1196 * Utility method to get the JSON parser either from gadgets.json 1197 * or from window.JSON (which will be initialized by CUIC if 1198 * browser doesn't support 1199 */ 1200 getJSONParser: function() { 1201 var _container = window.gadgets || {}, 1202 parser = _container.json || window.JSON; 1203 return parser; 1204 }, 1205 1206 /** 1207 * @private 1208 * Utility method to convert a javascript object to XML. 1209 * @param {Object} object 1210 * The object to convert to XML. 1211 * @param {Boolean} escapeFlag 1212 * If escapeFlag evaluates to true: 1213 * - XML escaping is done on the element values. 1214 * - Attributes, #cdata, and #text is not supported. 1215 * - The XML is unformatted (no whitespace between elements). 1216 * If escapeFlag evaluates to false: 1217 * - Element values are written 'as is' (no escaping). 1218 * - Attributes, #cdata, and #text is supported. 1219 * - The XML is formatted. 1220 * @returns The XML string. 1221 */ 1222 json2xml: function (object, escapeFlag) { 1223 var xml; 1224 if (escapeFlag) { 1225 xml = this._json2xmlWithEscape(object); 1226 } 1227 else { 1228 xml = Converter.json2xml(object, '\t'); 1229 } 1230 return xml; 1231 }, 1232 1233 /** 1234 * @private 1235 * Utility method to convert XML string into javascript object. 1236 */ 1237 xml2JsObj : function (event) { 1238 var parser = this.getJSONParser(); 1239 return parser.parse(Converter.xml2json(jQuery.parseXML(event), "")); 1240 }, 1241 1242 /** 1243 * @private 1244 * Utility method to convert an XML string to a javascript object. 1245 * @desc This function calls to the SAX parser and responds to callbacks 1246 * received from the parser. Entity translation is not handled here. 1247 * @param {String} xml 1248 * The XML to parse. 1249 * @returns The javascript object. 1250 */ 1251 xml2js: function (xml) { 1252 var STATES = { 1253 INVALID: 0, 1254 NEW_NODE: 1, 1255 ATTRIBUTE_NODE: 2, 1256 TEXT_NODE: 3, 1257 END_NODE: 4 1258 }, 1259 state = STATES.INVALID, 1260 rootObj = {}, 1261 newObj, 1262 objStack = [rootObj], 1263 nodeName = "", 1264 1265 /** 1266 * @private 1267 * Adds a property to the current top JSO. 1268 * @desc This is also where we make considerations for arrays. 1269 * @param {String} name 1270 * The name of the property to add. 1271 * @param (String) value 1272 * The value of the property to add. 1273 */ 1274 addProperty = function (name, value) { 1275 var current = objStack[objStack.length - 1]; 1276 if(current.hasOwnProperty(name) && current[name] instanceof Array){ 1277 current[name].push(value); 1278 }else if(current.hasOwnProperty(name)){ 1279 current[name] = [current[name], value]; 1280 }else{ 1281 current[name] = value; 1282 } 1283 }, 1284 1285 /** 1286 * @private 1287 * The callback passed to the SAX parser which processes events from 1288 * the SAX parser in order to construct the resulting JSO. 1289 * @param (String) type 1290 * The type of event received. 1291 * @param (String) data 1292 * The data received from the SAX parser. The contents of this 1293 * parameter vary based on the type of event. 1294 */ 1295 xmlFound = function (type, data) { 1296 switch (type) { 1297 case "StartElement": 1298 // Because different node types have different expectations 1299 // of parenting, we don't push another JSO until we know 1300 // what content we're getting 1301 1302 // If we're already in the new node state, we're running 1303 // into a child node. There won't be any text here, so 1304 // create another JSO 1305 if(state === STATES.NEW_NODE){ 1306 newObj = {}; 1307 addProperty(nodeName, newObj); 1308 objStack.push(newObj); 1309 } 1310 state = STATES.NEW_NODE; 1311 nodeName = data; 1312 break; 1313 case "EndElement": 1314 // If we're in the new node state, we've found no content 1315 // set the tag property to null 1316 if(state === STATES.NEW_NODE){ 1317 addProperty(nodeName, null); 1318 }else if(state === STATES.END_NODE){ 1319 objStack.pop(); 1320 } 1321 state = STATES.END_NODE; 1322 break; 1323 case "Attribute": 1324 // If were in the new node state, no JSO has yet been created 1325 // for this node, create one 1326 if(state === STATES.NEW_NODE){ 1327 newObj = {}; 1328 addProperty(nodeName, newObj); 1329 objStack.push(newObj); 1330 } 1331 // Attributes are differentiated from child elements by a 1332 // preceding "@" in the property name 1333 addProperty("@" + data.name, data.value); 1334 state = STATES.ATTRIBUTE_NODE; 1335 break; 1336 case "Text": 1337 // In order to maintain backwards compatibility, when no 1338 // attributes are assigned to a tag, its text contents are 1339 // assigned directly to the tag property instead of a JSO. 1340 1341 // If we're in the attribute node state, then the JSO for 1342 // this tag was already created when the attribute was 1343 // assigned, differentiate this property from a child 1344 // element by naming it "#text" 1345 if(state === STATES.ATTRIBUTE_NODE){ 1346 addProperty("#text", data); 1347 }else{ 1348 addProperty(nodeName, data); 1349 } 1350 state = STATES.TEXT_NODE; 1351 break; 1352 } 1353 }; 1354 SaxParser.parse(xml, xmlFound); 1355 return rootObj; 1356 }, 1357 1358 /** 1359 * @private 1360 * Traverses a plain-old-javascript-object recursively and outputs its XML representation. 1361 * @param {Object} obj 1362 * The javascript object to be converted into XML. 1363 * @returns {String} The XML representation of the object. 1364 */ 1365 js2xml: function (obj) { 1366 var xml = "", i, elem; 1367 1368 if (obj !== null) { 1369 if (obj.constructor === Object) { 1370 for (elem in obj) { 1371 if (obj[elem] === null || typeof(obj[elem]) === 'undefined') { 1372 xml += '<' + elem + '/>'; 1373 } else if (obj[elem].constructor === Array) { 1374 for (i = 0; i < obj[elem].length; i++) { 1375 xml += '<' + elem + '>' + this.js2xml(obj[elem][i]) + '</' + elem + '>'; 1376 } 1377 } else if (elem[0] !== '@') { 1378 if (this.js2xmlObjIsEmpty(obj[elem])) { 1379 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '/>'; 1380 } else if (elem === "#text") { 1381 xml += obj[elem]; 1382 } else { 1383 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '>' + this.js2xml(obj[elem]) + '</' + elem + '>'; 1384 } 1385 } 1386 } 1387 } else { 1388 xml = obj; 1389 } 1390 } 1391 1392 return xml; 1393 }, 1394 1395 /** 1396 * @private 1397 * Utility method called exclusively by js2xml() to find xml attributes. 1398 * @desc Traverses children one layer deep of a javascript object to "look ahead" 1399 * for properties flagged as such (with '@'). 1400 * @param {Object} obj 1401 * The obj to traverse. 1402 * @returns {String} Any attributes formatted for xml, if any. 1403 */ 1404 js2xmlAtt: function (obj) { 1405 var elem; 1406 1407 if (obj !== null) { 1408 if (obj.constructor === Object) { 1409 for (elem in obj) { 1410 if (obj[elem] !== null && typeof(obj[elem]) !== "undefined" && obj[elem].constructor !== Array) { 1411 if (elem[0] === '@'){ 1412 return ' ' + elem.substring(1) + '="' + obj[elem] + '"'; 1413 } 1414 } 1415 } 1416 } 1417 } 1418 1419 return ''; 1420 }, 1421 1422 /** 1423 * @private 1424 * Utility method called exclusively by js2xml() to determine if 1425 * a node has any children, with special logic for ignoring attributes. 1426 * @desc Attempts to traverse the elements in the object while ignoring attributes. 1427 * @param {Object} obj 1428 * The obj to traverse. 1429 * @returns {Boolean} whether or not the JS object is "empty" 1430 */ 1431 js2xmlObjIsEmpty: function (obj) { 1432 var elem; 1433 1434 if (obj !== null) { 1435 if (obj.constructor === Object) { 1436 for (elem in obj) { 1437 if (obj[elem] !== null) { 1438 if (obj[elem].constructor === Array){ 1439 return false; 1440 } 1441 1442 if (elem[0] !== '@'){ 1443 return false; 1444 } 1445 } else { 1446 return false; 1447 } 1448 } 1449 } else { 1450 return false; 1451 } 1452 } 1453 1454 return true; 1455 }, 1456 1457 /** 1458 * Encodes the given string into base64. 1459 *<br> 1460 * <b>NOTE:</b> {input} is assumed to be UTF-8; only the first 1461 * 8 bits of each input element are significant. 1462 * 1463 * @param {String} input 1464 * The string to convert to base64. 1465 * @returns {String} 1466 * The converted string. 1467 */ 1468 b64Encode: function (input) { 1469 var output = "", idx, data, 1470 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 1471 1472 for (idx = 0; idx < input.length; idx += 3) { 1473 data = input.charCodeAt(idx) << 16 | 1474 input.charCodeAt(idx + 1) << 8 | 1475 input.charCodeAt(idx + 2); 1476 1477 //assume the first 12 bits are valid 1478 output += table.charAt((data >>> 18) & 0x003f) + 1479 table.charAt((data >>> 12) & 0x003f); 1480 output += ((idx + 1) < input.length) ? 1481 table.charAt((data >>> 6) & 0x003f) : 1482 "="; 1483 output += ((idx + 2) < input.length) ? 1484 table.charAt(data & 0x003f) : 1485 "="; 1486 } 1487 1488 return output; 1489 }, 1490 1491 /** 1492 * Decodes the given base64 string. 1493 * <br> 1494 * <b>NOTE:</b> output is assumed to be UTF-8; only the first 1495 * 8 bits of each output element are significant. 1496 * 1497 * @param {String} input 1498 * The base64 encoded string 1499 * @returns {String} 1500 * Decoded string 1501 */ 1502 b64Decode: function (input) { 1503 var output = "", idx, h, data, 1504 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 1505 1506 for (idx = 0; idx < input.length; idx += 4) { 1507 h = [ 1508 table.indexOf(input.charAt(idx)), 1509 table.indexOf(input.charAt(idx + 1)), 1510 table.indexOf(input.charAt(idx + 2)), 1511 table.indexOf(input.charAt(idx + 3)) 1512 ]; 1513 1514 data = (h[0] << 18) | (h[1] << 12) | (h[2] << 6) | h[3]; 1515 if (input.charAt(idx + 2) === '=') { 1516 data = String.fromCharCode( 1517 (data >>> 16) & 0x00ff 1518 ); 1519 } else if (input.charAt(idx + 3) === '=') { 1520 data = String.fromCharCode( 1521 (data >>> 16) & 0x00ff, 1522 (data >>> 8) & 0x00ff 1523 ); 1524 } else { 1525 data = String.fromCharCode( 1526 (data >>> 16) & 0x00ff, 1527 (data >>> 8) & 0x00ff, 1528 data & 0x00ff 1529 ); 1530 } 1531 output += data; 1532 } 1533 1534 return output; 1535 }, 1536 1537 /** 1538 * @private 1539 * Extracts the username and the password from the Base64 encoded string. 1540 * @params {String} 1541 * A base64 encoded string containing credentials that (when decoded) 1542 * are colon delimited. 1543 * @returns {Object} 1544 * An object with the following structure: 1545 * {id:string, password:string} 1546 */ 1547 getCredentials: function (authorization) { 1548 var credObj = {}, 1549 credStr = this.b64Decode(authorization), 1550 colonIndx = credStr.indexOf(":"); 1551 1552 //Check to ensure that string is colon delimited. 1553 if (colonIndx === -1) { 1554 throw new Error("String is not colon delimited."); 1555 } 1556 1557 //Extract ID and password. 1558 credObj.id = credStr.substring(0, colonIndx); 1559 credObj.password = credStr.substring(colonIndx + 1); 1560 return credObj; 1561 }, 1562 1563 /** 1564 * Takes a string and removes any spaces within the string. 1565 * @param {String} string 1566 * The string to remove spaces from 1567 * @returns {String} 1568 * The string without spaces 1569 */ 1570 removeSpaces: function (string) { 1571 return string.split(' ').join(''); 1572 }, 1573 1574 /** 1575 * Escapes spaces as encoded " " characters so they can 1576 * be safely rendered by jQuery.text(string) in all browsers. 1577 * 1578 * (Although IE behaves as expected, Firefox collapses spaces if this function is not used.) 1579 * 1580 * @param text 1581 * The string whose spaces should be escaped 1582 * 1583 * @returns 1584 * The string with spaces escaped 1585 */ 1586 escapeSpaces: function (string) { 1587 return string.replace(/\s/g, '\u00a0'); 1588 }, 1589 1590 /** 1591 * Adds a span styled to line break at word edges around the string passed in. 1592 * @param str String to be wrapped in word-breaking style. 1593 * @private 1594 */ 1595 addWordWrapping : function (str) { 1596 return '<span style="word-wrap: break-word;">' + str + '</span>'; 1597 }, 1598 1599 /** 1600 * Takes an Object and determines whether it is an Array or not. 1601 * @param {Object} obj 1602 * The Object in question 1603 * @returns {Boolean} 1604 * true if the object is an Array, else false. 1605 */ 1606 isArray: function (obj) { 1607 return obj.constructor.toString().indexOf("Array") !== -1; 1608 }, 1609 1610 /** 1611 * @private 1612 * Takes a data object and returns an array extracted 1613 * @param {Object} data 1614 * JSON payload 1615 * 1616 * @returns {array} 1617 * extracted array 1618 */ 1619 getArray: function (data) { 1620 if (this.isArray(data)) { 1621 //Return if already an array. 1622 return data; 1623 } else { 1624 //Create an array, iterate through object, and push to array. This 1625 //should only occur with one object, and therefore one obj in array. 1626 var arr = []; 1627 arr.push(data); 1628 return arr; 1629 } 1630 }, 1631 1632 /** 1633 * @private 1634 * Extracts the ID for an entity given the Finesse REST URI. The ID is 1635 * assumed to be the last element in the URI (after the last "/"). 1636 * @param {String} uri 1637 * The Finesse REST URI to extract the ID from. 1638 * @returns {String} 1639 * The ID extracted from the REST URI. 1640 */ 1641 getId: function (uri) { 1642 if (!uri) { 1643 return ""; 1644 } 1645 var strLoc = uri.lastIndexOf("/"); 1646 return uri.slice(strLoc + 1); 1647 }, 1648 1649 /** 1650 * Compares two objects for equality. 1651 * @param {Object} obj1 1652 * First of two objects to compare. 1653 * @param {Object} obj2 1654 * Second of two objects to compare. 1655 */ 1656 getEquals: function (objA, objB) { 1657 var key; 1658 1659 for (key in objA) { 1660 if (objA.hasOwnProperty(key)) { 1661 if (!objA[key]) { 1662 objA[key] = ""; 1663 } 1664 1665 if (typeof objB[key] === 'undefined') { 1666 return false; 1667 } 1668 if (typeof objB[key] === 'object') { 1669 if (!objB[key].equals(objA[key])) { 1670 return false; 1671 } 1672 } 1673 if (objB[key] !== objA[key]) { 1674 return false; 1675 } 1676 } 1677 } 1678 return true; 1679 }, 1680 1681 /** 1682 * Regular expressions used in translating HTML and XML entities 1683 */ 1684 ampRegEx : new RegExp('&', 'gi'), 1685 ampEntityRefRegEx : new RegExp('&', 'gi'), 1686 ltRegEx : new RegExp('<', 'gi'), 1687 ltEntityRefRegEx : new RegExp('<', 'gi'), 1688 gtRegEx : new RegExp('>', 'gi'), 1689 gtEntityRefRegEx : new RegExp('>', 'gi'), 1690 xmlSpecialCharRegEx: new RegExp('[&<>"\']', 'g'), 1691 entityRefRegEx: new RegExp('&[^;]+(?:;|$)', 'g'), 1692 1693 /** 1694 * Translates between special characters and HTML entities 1695 * 1696 * @param text 1697 * The text to translate 1698 * 1699 * @param makeEntityRefs 1700 * If true, encode special characters as HTML entities; if 1701 * false, decode HTML entities back to special characters 1702 * 1703 * @private 1704 */ 1705 translateHTMLEntities: function (text, makeEntityRefs) { 1706 if (typeof(text) !== "undefined" && text !== null && text !== "") { 1707 if (makeEntityRefs) { 1708 text = text.replace(this.ampRegEx, '&'); 1709 text = text.replace(this.ltRegEx, '<'); 1710 text = text.replace(this.gtRegEx, '>'); 1711 } else { 1712 text = text.replace(this.gtEntityRefRegEx, '>'); 1713 text = text.replace(this.ltEntityRefRegEx, '<'); 1714 text = text.replace(this.ampEntityRefRegEx, '&'); 1715 } 1716 } 1717 1718 return text; 1719 }, 1720 1721 /** 1722 * Translates between special characters and XML entities 1723 * 1724 * @param text 1725 * The text to translate 1726 * 1727 * @param makeEntityRefs 1728 * If true, encode special characters as XML entities; if 1729 * false, decode XML entities back to special characters 1730 * 1731 * @private 1732 */ 1733 translateXMLEntities: function (text, makeEntityRefs) { 1734 /** @private */ 1735 var escape = function (character) { 1736 switch (character) { 1737 case "&": 1738 return "&"; 1739 case "<": 1740 return "<"; 1741 case ">": 1742 return ">"; 1743 case "'": 1744 return "'"; 1745 case "\"": 1746 return """; 1747 default: 1748 return character; 1749 } 1750 }, 1751 /** @private */ 1752 unescape = function (entity) { 1753 switch (entity) { 1754 case "&": 1755 return "&"; 1756 case "<": 1757 return "<"; 1758 case ">": 1759 return ">"; 1760 case "'": 1761 return "'"; 1762 case """: 1763 return "\""; 1764 default: 1765 if (entity.charAt(1) === "#" && entity.charAt(entity.length - 1) === ";") { 1766 if (entity.charAt(2) === "x") { 1767 return String.fromCharCode(parseInt(entity.slice(3, -1), 16)); 1768 } else { 1769 return String.fromCharCode(parseInt(entity.slice(2, -1), 10)); 1770 } 1771 } else { 1772 throw new Error("Invalid XML entity: " + entity); 1773 } 1774 } 1775 }; 1776 1777 if (typeof(text) !== "undefined" && text !== null && text !== "") { 1778 if (makeEntityRefs) { 1779 text = text.replace(this.xmlSpecialCharRegEx, escape); 1780 } else { 1781 text = text.replace(this.entityRefRegEx, unescape); 1782 } 1783 } 1784 1785 return text; 1786 }, 1787 1788 /** 1789 * @private 1790 * Utility method to pad the number with a leading 0 for single digits 1791 * @param (Number) num 1792 * the number to pad 1793 */ 1794 pad : function (num) { 1795 if (num < 10) { 1796 return "0" + num; 1797 } 1798 1799 return String(num); 1800 }, 1801 1802 /** 1803 * Utility method to render a timestamp value (in seconds) into HH:MM:SS format. 1804 * @param {Number} time 1805 * The timestamp in ms to render 1806 * @returns {String} 1807 * Time string in HH:MM:SS format. 1808 */ 1809 getDisplayTime : function (time) { 1810 var hour, min, sec, timeStr = "00:00:00"; 1811 1812 if (time && time !== "-1") { 1813 // calculate hours, minutes, and seconds 1814 hour = this.pad(Math.floor(time / 3600)); 1815 min = this.pad(Math.floor((time % 3600) / 60)); 1816 sec = this.pad(Math.floor((time % 3600) % 60)); 1817 // construct HH:MM:SS time string 1818 timeStr = hour + ":" + min + ":" + sec; 1819 } 1820 1821 return timeStr; 1822 }, 1823 1824 /** 1825 * Checks if the string is null. If it is, return empty string; else return 1826 * the string itself. 1827 * 1828 * @param {String} str 1829 * The string to check 1830 * @return {String} 1831 * Empty string or string itself 1832 */ 1833 convertNullToEmptyString : function (str) { 1834 return str || ""; 1835 }, 1836 1837 /** 1838 * Utility method to render a timestamp string (of format 1839 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format. 1840 * 1841 * @param {String} timestamp 1842 * The timestamp to render 1843 * @param {Date} [now] 1844 * Optional argument to provide the time from which to 1845 * calculate the duration instead of using the current time 1846 * @returns {String} 1847 * Duration string in HH:MM:SS format. 1848 */ 1849 convertTsToDuration : function (timestamp, now) { 1850 return this.convertTsToDurationWithFormat(timestamp, false, now); 1851 }, 1852 1853 /** 1854 * Utility method to render a timestamp string (of format 1855 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format, 1856 * with optional -1 for null or negative times. 1857 * 1858 * @param {String} timestamp 1859 * The timestamp to render 1860 * @param {Boolean} forFormat 1861 * If True, if duration is null or negative, return -1 so that the duration can be formated 1862 * as needed in the Gadget. 1863 * @param {Date} [now] 1864 * Optional argument to provide the time from which to 1865 * calculate the duration instead of using the current time 1866 * @returns {String} 1867 * Duration string in HH:MM:SS format. 1868 */ 1869 convertTsToDurationWithFormat : function (timestamp, forFormat, now) { 1870 var startTimeInMs, nowInMs, durationInSec = "-1"; 1871 1872 // Calculate duration 1873 if (timestamp && typeof timestamp === "string") { 1874 // first check it '--' for a msg in grid 1875 if (timestamp === '--' || timestamp ==="" || timestamp === "-1") { 1876 return "-1"; 1877 } 1878 // else try convert string into a time 1879 startTimeInMs = Date.parse(timestamp); 1880 if (!isNaN(startTimeInMs)) { 1881 if (!now || !(now instanceof Date)) { 1882 nowInMs = this.currentServerTimeMillis(); 1883 } else { 1884 nowInMs = now.getTime(); 1885 } 1886 durationInSec = Math.floor((nowInMs - startTimeInMs) / 1000); 1887 // Since currentServerTime is not exact (lag between sending and receiving 1888 // messages will differ slightly), treat a slightly negative (less than 1 sec) 1889 // value as 0, to avoid "--" showing up when a state first changes. 1890 if (durationInSec === -1) { 1891 durationInSec = 0; 1892 } 1893 1894 if (durationInSec < 0) { 1895 if (forFormat) { 1896 return "-1"; 1897 } else { 1898 return this.getDisplayTime("-1"); 1899 } 1900 } 1901 } 1902 }else { 1903 if(forFormat){ 1904 return "-1"; 1905 } 1906 } 1907 return this.getDisplayTime(durationInSec); 1908 }, 1909 /** 1910 * @private 1911 * Adds a new cookie to the page with a default domain. 1912 * @param {String} key 1913 * the key to assign a value to 1914 * @param {String} value 1915 * the value to assign to the key 1916 * @param {Number} days 1917 * number of days (from current) until the cookie should expire 1918 */ 1919 addCookie : function (key, value, days) { 1920 var date, expires = "", 1921 cookie = key + "=" + escape(value); 1922 if (typeof days === "number") { 1923 date = new Date(); 1924 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 1925 cookie += "; expires=" + date.toGMTString(); 1926 } 1927 document.cookie = cookie + "; path=/"; 1928 }, 1929 1930 /** 1931 * @private 1932 * Get the value of a cookie given a key. 1933 * @param {String} key 1934 * a key to lookup 1935 * @returns {String} 1936 * the value mapped to a key, null if key doesn't exist 1937 */ 1938 getCookie : function (key) { 1939 var i, pairs, pair; 1940 if (document.cookie) { 1941 pairs = document.cookie.split(";"); 1942 for (i = 0; i < pairs.length; i += 1) { 1943 pair = this.trim(pairs[i]).split("="); 1944 if (pair[0] === key) { 1945 return unescape(pair[1]); 1946 } 1947 } 1948 } 1949 return null; 1950 }, 1951 1952 /** 1953 * @private 1954 * Deletes the cookie mapped to specified key. 1955 * @param {String} key 1956 * the key to delete 1957 */ 1958 deleteCookie : function (key) { 1959 this.addCookie(key, "", -1); 1960 }, 1961 1962 /** 1963 * @private 1964 * Case insensitive sort for use with arrays or Dojox stores 1965 * @param {String} a 1966 * first value 1967 * @param {String} b 1968 * second value 1969 */ 1970 caseInsensitiveSort: function (a, b) { 1971 var ret = 0, emptyString = ""; 1972 a = a + emptyString; 1973 b = b + emptyString; 1974 a = a.toLowerCase(); 1975 b = b.toLowerCase(); 1976 if (a > b) { 1977 ret = 1; 1978 } 1979 if (a < b) { 1980 ret = -1; 1981 } 1982 return ret; 1983 }, 1984 1985 /** 1986 * @private 1987 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 1988 * 1989 * The displayWjitFunc function will be called once and only once when the div for our wijit 1990 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 1991 * throw exceptions and do not render properly if they are created in a display:none div. 1992 * If our gadget is visisble the function will be called immediately. 1993 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 1994 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 1995 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 1996 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 1997 * end up waiting for the node to become visisble anyway. 1998 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 1999 */ 2000 onGadgetFirstVisible: function (displayWjitFunc) { 2001 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2002 try { 2003 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2004 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2005 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2006 2007 // Loop through all of the tab panels to find one that has our gadget id 2008 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2009 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2010 if (q.length > 0) { // You found it 2011 panelNode = node; 2012 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2013 active = dojo.hasClass(panelNode, "active"); 2014 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2015 return; 2016 } 2017 }); 2018 // If panel is already active - execute the function - we're done 2019 if (active) { 2020 //?console.log(frameId + " is visible display it"); 2021 setTimeout(displayWjitFunc); 2022 } 2023 // If its not visible - wait for the active class to show up. 2024 else { 2025 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2026 iterval = setInterval(dojo.hitch(this, function () { 2027 if (dojo.hasClass(panelNode, "active")) { 2028 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2029 clearInterval(iterval); 2030 setTimeout(displayWjitFunc); 2031 } 2032 }), 250); 2033 } 2034 } catch (err) { 2035 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2036 } 2037 }, 2038 2039 /** 2040 * @private 2041 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2042 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2043 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2044 */ 2045 downloadFile : function (url) { 2046 var iframe = document.getElementById("download_iframe"); 2047 2048 if (!iframe) 2049 { 2050 iframe = document.createElement("iframe"); 2051 $(document.body).append(iframe); 2052 $(iframe).css("display", "none"); 2053 } 2054 2055 iframe.src = url; 2056 }, 2057 2058 /** 2059 * @private 2060 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2061 */ 2062 bitMask: { 2063 /** @private */ 2064 isSet: function (value, mask) { 2065 return (value & mask) === mask; 2066 }, 2067 /** 2068 * Returns true if all flags in the intArray are set on the specified value 2069 * @private 2070 */ 2071 all: function (value, intArray) { 2072 var i = intArray.length; 2073 if (typeof(i) === "undefined") 2074 { 2075 intArray = [intArray]; 2076 i = 1; 2077 } 2078 while ((i = i - 1) !== -1) 2079 { 2080 if (!this.isSet(value, intArray[i])) 2081 { 2082 return false; 2083 } 2084 } 2085 return true; 2086 }, 2087 /** 2088 * @private 2089 * Returns true if any flags in the intArray are set on the specified value 2090 */ 2091 any: function (value, intArray) { 2092 var i = intArray.length; 2093 if (typeof(i) === "undefined") 2094 { 2095 intArray = [intArray]; 2096 i = 1; 2097 } 2098 while ((i = i - 1) !== -1) 2099 { 2100 if (this.isSet(value, intArray[i])) 2101 { 2102 return true; 2103 } 2104 } 2105 return false; 2106 } 2107 }, 2108 2109 /** @private */ 2110 renderDojoGridOffScreen: function (grid) { 2111 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2112 $(document.body).append(offscreenDiv); 2113 grid.placeAt(offscreenDiv); 2114 grid.startup(); 2115 document.body.removeChild(offscreenDiv); 2116 return grid; 2117 }, 2118 2119 /** @private */ 2120 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2121 var timerId = null, 2122 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2123 theInputControl = theControl.find("input"), 2124 theClearButton = theControl.find("a"), 2125 inputControlWidthWithClear = 204, 2126 inputControlWidthNoClear = 230, 2127 sPreviousInput = theInputControl.val(), 2128 /** @private **/ 2129 toggleClearButton = function(){ 2130 if (theInputControl.val() === "") { 2131 theClearButton.hide(); 2132 theControl.removeClass("input-append"); 2133 theInputControl.width(inputControlWidthNoClear); 2134 } else { 2135 theInputControl.width(inputControlWidthWithClear); 2136 theClearButton.show(); 2137 theControl.addClass("input-append"); 2138 } 2139 }; 2140 2141 // set placeholder text 2142 theInputControl.attr('placeholder', placeholderText); 2143 2144 theInputControl.unbind('keyup').bind('keyup', function() { 2145 if (sPreviousInput !== theInputControl.val()) { 2146 window.clearTimeout(timerId); 2147 sPreviousInput = theInputControl.val(); 2148 timerId = window.setTimeout(function() { 2149 changeCallback.call((callbackScope || window), theInputControl.val()); 2150 theInputControl[0].focus(); 2151 }, callbackDelay); 2152 } 2153 2154 toggleClearButton(); 2155 }); 2156 2157 theClearButton.bind('click', function() { 2158 theInputControl.val(''); 2159 changeCallback.call((callbackScope || window), ''); 2160 2161 toggleClearButton(); 2162 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2163 }); 2164 2165 theInputControl.val(""); 2166 toggleClearButton(); 2167 }, 2168 2169 DataTables: { 2170 /** @private */ 2171 createDataTable: function (options, dataTableOptions) { 2172 var grid, 2173 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 2174 headerRow = table.find("tr"), 2175 defaultOptions = { 2176 "aaData": [], 2177 "bPaginate": false, 2178 "bLengthChange": false, 2179 "bFilter": false, 2180 "bInfo": false, 2181 "sScrollY": "176", 2182 "oLanguage": { 2183 "sEmptyTable": "", 2184 "sZeroRecords": "" 2185 } 2186 }, 2187 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 2188 columnDefs = [], 2189 columnFormatter; 2190 2191 // Create a header cell for each column, and set up the datatable definition for the column 2192 $(options.columns).each(function (index, column) { 2193 headerRow.append($("<th></th>")); 2194 columnDefs[index] = { 2195 "mData": column.propertyName, 2196 "sTitle": column.columnHeader, 2197 "sWidth": column.width, 2198 "aTargets": [index], 2199 "bSortable": column.sortable, 2200 "bVisible": column.visible, 2201 "mRender": column.render 2202 }; 2203 if (typeof(column.renderFunction) === "function") 2204 { 2205 /** @ignore **/ 2206 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 2207 var returnValue; 2208 2209 //Apply column render logic to value before applying extra render function 2210 if (typeof(column.render) === "function") 2211 { 2212 value = column.render.call(value, value, value); 2213 } 2214 2215 if (typeof(type) === "string") 2216 { 2217 switch (type) 2218 { 2219 case "undefined": 2220 case "sort": 2221 returnValue = value; 2222 break; 2223 case "set": 2224 throw new Error("Unsupported set data in Finesse Grid"); 2225 case "filter": 2226 case "display": 2227 case "type": 2228 returnValue = column.renderFunction.call(dataObject, value, dataObject); 2229 break; 2230 default: 2231 break; 2232 } 2233 } 2234 else 2235 { 2236 throw new Error("type param not specified in Finesse DataTable mData"); 2237 } 2238 2239 return returnValue; 2240 }; 2241 } 2242 }); 2243 gridOptions.aoColumnDefs = columnDefs; 2244 2245 // Set the height 2246 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 2247 { 2248 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 2249 } 2250 2251 // Place it into the DOM 2252 if (typeof(options.container) !== "undefined" && options.container !== null) 2253 { 2254 $(options.container).append(table); 2255 } 2256 2257 // Create the DataTable 2258 table.dataTable(gridOptions); 2259 2260 return table; 2261 } 2262 }, 2263 2264 /** 2265 * @private 2266 * Sets a dojo button to the specified disable state, removing it from 2267 * the tab order if disabling, and restoring it to the tab order if enabling. 2268 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 2269 * @param {bool} disabled 2270 */ 2271 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 2272 var labelNode, 2273 tabIndex; 2274 2275 dojoButton.set("disabled", disabled); 2276 2277 // Remove the tabindex attribute on disabled buttons, store it, 2278 // and replace it when it becomes enabled again 2279 labelNode = $("#" + dojoButton.id + "_label"); 2280 if (disabled) 2281 { 2282 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 2283 labelNode.removeAttr("tabindex"); 2284 } 2285 else 2286 { 2287 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 2288 if (typeof(tabIndex) === "string") 2289 { 2290 labelNode.attr("tabindex", Number(tabIndex)); 2291 } 2292 } 2293 }, 2294 2295 /** 2296 * @private 2297 * Measures the given text using the supplied fontFamily and fontSize 2298 * @param {string} text text to measure 2299 * @param {string} fontFamily 2300 * @param {string} fontSize 2301 * @return {number} pixel width 2302 */ 2303 measureText: function (text, fontFamily, fontSize) { 2304 var width, 2305 element = $("<div></div>").text(text).css({ 2306 "fontSize": fontSize, 2307 "fontFamily": fontFamily 2308 }).addClass("offscreen").appendTo(document.body); 2309 2310 width = element.width(); 2311 element.remove(); 2312 2313 return width; 2314 }, 2315 2316 /** 2317 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 2318 * needing to resize down in IE. This gets around that by calculating the height 2319 * manually and passing it in. 2320 * @return {undefined} 2321 */ 2322 "adjustGadgetHeight": function () { 2323 var bScrollHeight = $("body").height() + 20; 2324 gadgets.window.adjustHeight(bScrollHeight); 2325 }, 2326 2327 /** 2328 * Private helper method for converting a javascript object to xml, where the values of the elements are 2329 * appropriately escaped for XML. 2330 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 2331 * there is no whitespace between elements. 2332 * @param object The javascript object to convert to XML. 2333 * @returns The XML string. 2334 * @private 2335 */ 2336 _json2xmlWithEscape: function(object) { 2337 var that = this, 2338 xml = "", 2339 m, 2340 /** @private **/ 2341 toXmlHelper = function(value, name) { 2342 var xml = "", 2343 i, 2344 m; 2345 if (value instanceof Array) { 2346 for (i = 0; i < value.length; ++i) { 2347 xml += toXmlHelper(value[i], name); 2348 } 2349 } 2350 else if (typeof value === "object") { 2351 xml += "<" + name + ">"; 2352 for (m in value) { 2353 if (value.hasOwnProperty(m)) { 2354 xml += toXmlHelper(value[m], m); 2355 } 2356 } 2357 xml += "</" + name + ">"; 2358 } 2359 else { 2360 // is a leaf node 2361 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 2362 "</" + name + ">"; 2363 } 2364 return xml; 2365 }; 2366 for (m in object) { 2367 if (object.hasOwnProperty(m)) { 2368 xml += toXmlHelper(object[m], m); 2369 } 2370 } 2371 return xml; 2372 }, 2373 2374 /** 2375 * Utility to detect the browser version of IE, whether it is IE9 or IE8 or in compatibility mode. 2376 * If IE is earlier than IE8, version returns undefined. 2377 * 2378 * @return {Object} browser object has the following form: 2379 * { 2380 * isIE: {Boolean}, 2381 * version: {String}, // 8 for IE8, 9 or IE9 2382 * isCompatibilityMode: {Boolean} 2383 * } 2384 */ 2385 detectIEBrowserVersion: function () { 2386 var browser = { 2387 isIE: false, 2388 isCompatibilityMode: false 2389 }, 2390 2391 useragent = navigator.userAgent, 2392 2393 appName = navigator.appName; 2394 2395 // Check to see if app is IE 2396 if (appName.indexOf("Microsoft Internet Explorer") !== -1) { 2397 browser.isIE = true; 2398 // Check for Trident version 2399 if (useragent.indexOf("Trident/5.0") !== -1) { // IE9 2400 browser.version = "9"; 2401 if (useragent.indexOf("MSIE 9.0") === -1) { 2402 browser.isCompatibilityMode = true; 2403 } 2404 } else if (useragent.indexOf("Trident/4.0") !== -1) { // IE8 2405 browser.version = "8"; 2406 if (useragent.indexOf("MSIE 8.0") === -1) { 2407 browser.isCompatibilityMode = true; 2408 } 2409 } // Else unsupported IE browser version, return undefined for version 2410 } 2411 2412 return browser; 2413 } 2414 }; 2415 2416 return Utilities; 2417 })); 2418 2419 /** The following comment is to prevent jslint errors about 2420 * using variables before they are defined. 2421 */ 2422 /*global finesse*/ 2423 2424 /** 2425 * Initiated by the Master to create a shared BOSH connection. 2426 * 2427 * @requires Utilities 2428 */ 2429 2430 /** 2431 * @class 2432 * Establishes a shared event connection by creating a communication tunnel 2433 * with the notification server and consume events which could be published. 2434 * Public functions are exposed to register to the connection status information 2435 * and events. 2436 * @constructor 2437 * @param {String} host 2438 * The host name/ip of the Finesse server. 2439 * @throws {Error} If required constructor parameter is missing. 2440 */ 2441 /** @private */ 2442 (function (factory) { 2443 2444 2445 // Define as an AMD module if possible 2446 if ( typeof define === 'function' && define.amd ) 2447 { 2448 define('clientservices/MasterTunnel',["utilities/Utilities"], factory ); 2449 } 2450 2451 /* Define using browser globals otherwise 2452 * Prevent multiple instantiations if the script is loaded twice 2453 */ 2454 else 2455 { 2456 factory(finesse.utilities.Utilities); 2457 } 2458 2459 }(function (Utilities) { 2460 var MasterTunnel = function (host, scheme) { 2461 if (typeof host !== "string" || host.length === 0) { 2462 throw new Error("Required host parameter missing."); 2463 } 2464 2465 var 2466 2467 /** 2468 * Flag to indicate whether the tunnel frame is loaded. 2469 * @private 2470 */ 2471 _isTunnelLoaded = false, 2472 2473 /** 2474 * Short reference to the Finesse utility. 2475 * @private 2476 */ 2477 _util = Utilities, 2478 2479 /** 2480 * The URL with host and port to the Finesse server. 2481 * @private 2482 */ 2483 _tunnelOrigin, 2484 2485 /** 2486 * Location of the tunnel HTML URL. 2487 * @private 2488 */ 2489 _tunnelURL, 2490 2491 /** 2492 * The port on which to connect to the Finesse server to load the eventing resources. 2493 * @private 2494 */ 2495 _tunnelOriginPort, 2496 2497 /** 2498 * Flag to indicate whether we have processed the tunnel config yet. 2499 * @private 2500 */ 2501 _isTunnelConfigInit = false, 2502 2503 /** 2504 * The tunnel frame window object. 2505 * @private 2506 */ 2507 _tunnelFrame, 2508 2509 /** 2510 * The handler registered with the object to be invoked when an event is 2511 * delivered by the notification server. 2512 * @private 2513 */ 2514 _eventHandler, 2515 2516 /** 2517 * The handler registered with the object to be invoked when presence is 2518 * delivered by the notification server. 2519 * @private 2520 */ 2521 _presenceHandler, 2522 2523 /** 2524 * The handler registered with the object to be invoked when the BOSH 2525 * connection has changed states. The object will contain the "status" 2526 * property and a "resourceID" property only if "status" is "connected". 2527 * @private 2528 */ 2529 _connInfoHandler, 2530 2531 /** 2532 * The last connection status published by the JabberWerx library. 2533 * @private 2534 */ 2535 _statusCache, 2536 2537 /** 2538 * The last event sent by notification server. 2539 * @private 2540 */ 2541 _eventCache, 2542 2543 /** 2544 * The ID of the user logged into notification server. 2545 * @private 2546 */ 2547 _id, 2548 2549 /** 2550 * The domain of the XMPP server, representing the portion of the JID 2551 * following '@': userid@domain.com 2552 * @private 2553 */ 2554 _xmppDomain, 2555 2556 /** 2557 * The password of the user logged into notification server. 2558 * @private 2559 */ 2560 _password, 2561 2562 /** 2563 * The jid of the pubsub service on the XMPP server 2564 * @private 2565 */ 2566 _pubsubDomain, 2567 2568 /** 2569 * The resource to use for the BOSH connection. 2570 * @private 2571 */ 2572 _resource, 2573 2574 /** 2575 * The resource ID identifying the client device (that we receive from the server). 2576 * @private 2577 */ 2578 _resourceID, 2579 2580 /** 2581 * The different types of messages that could be sent to the parent frame. 2582 * The types here should be understood by the parent frame and used to 2583 * identify how the message is formatted. 2584 * @private 2585 */ 2586 _TYPES = { 2587 EVENT: 0, 2588 ID: 1, 2589 PASSWORD: 2, 2590 RESOURCEID: 3, 2591 STATUS: 4, 2592 XMPPDOMAIN: 5, 2593 PUBSUBDOMAIN: 6, 2594 SUBSCRIBE: 7, 2595 UNSUBSCRIBE: 8, 2596 PRESENCE: 9, 2597 CONNECT_REQ: 10 2598 }, 2599 2600 _handlers = { 2601 subscribe: {}, 2602 unsubscribe: {} 2603 }, 2604 2605 2606 /** 2607 * Create a connection info object. 2608 * @returns {Object} 2609 * A connection info object containing a "status" and "resourceID". 2610 * @private 2611 */ 2612 _createConnInfoObj = function () { 2613 return { 2614 status: _statusCache, 2615 resourceID: _resourceID 2616 }; 2617 }, 2618 2619 /** 2620 * Utility function which sends a message to the dynamic tunnel frame 2621 * event frame formatted as follows: "type|message". 2622 * @param {Number} type 2623 * The category type of the message. 2624 * @param {String} message 2625 * The message to be sent to the tunnel frame. 2626 * @private 2627 */ 2628 _sendMessage = function (type, message) { 2629 message = type + "|" + message; 2630 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 2631 }, 2632 2633 /** 2634 * Utility to process the response of a subscribe request from 2635 * the tunnel frame, then invoking the stored callback handler 2636 * with the respective data (error, when applicable) 2637 * @param {String} data 2638 * The response in the format of "node[|error]" 2639 * @private 2640 */ 2641 _processSubscribeResponse = function (data) { 2642 var dataArray = data.split("|"), 2643 node = dataArray[0], 2644 err; 2645 2646 //Error is optionally the second item in the array 2647 if (dataArray.length) { 2648 err = dataArray[1]; 2649 } 2650 2651 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 2652 if (_handlers.subscribe[node]) { 2653 _handlers.subscribe[node](err); 2654 delete _handlers.subscribe[node]; 2655 } 2656 }, 2657 2658 /** 2659 * Utility to process the response of an unsubscribe request from 2660 * the tunnel frame, then invoking the stored callback handler 2661 * with the respective data (error, when applicable) 2662 * @param {String} data 2663 * The response in the format of "node[|error]" 2664 * @private 2665 */ 2666 _processUnsubscribeResponse = function (data) { 2667 var dataArray = data.split("|"), 2668 node = dataArray[0], 2669 err; 2670 2671 //Error is optionally the second item in the array 2672 if (dataArray.length) { 2673 err = dataArray[1]; 2674 } 2675 2676 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 2677 if (_handlers.unsubscribe[node]) { 2678 _handlers.unsubscribe[node](err); 2679 delete _handlers.unsubscribe[node]; 2680 } 2681 }, 2682 2683 /** 2684 * Handler for messages delivered by window.postMessage. Listens for events 2685 * published by the notification server, connection status published by 2686 * the JabberWerx library, and the resource ID created when the BOSH 2687 * connection has been established. 2688 * @param {Object} e 2689 * The message object as provided by the window.postMessage feature. 2690 * @private 2691 */ 2692 _messageHandler = function (e) { 2693 var 2694 2695 //Extract the message type and message data. The expected format is 2696 //"type|data" where type is a number represented by the TYPES object. 2697 delimPos = e.data.indexOf("|"), 2698 type = Number(e.data.substr(0, delimPos)), 2699 data = e.data.substr(delimPos + 1); 2700 2701 //Accepts messages and invoke the correct registered handlers. 2702 switch (type) { 2703 case _TYPES.EVENT: 2704 _eventCache = data; 2705 if (typeof _eventHandler === "function") { 2706 _eventHandler(data); 2707 } 2708 break; 2709 case _TYPES.STATUS: 2710 _statusCache = data; 2711 2712 //A "loaded" status means that the frame is ready to accept 2713 //credentials for establishing a BOSH connection. 2714 if (data === "loaded") { 2715 _isTunnelLoaded = true; 2716 if(_resource) { 2717 _sendMessage(_TYPES.RESOURCEID, _resource); 2718 } 2719 _sendMessage(_TYPES.ID, _id); 2720 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 2721 _sendMessage(_TYPES.PASSWORD, _password); 2722 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 2723 } else if (typeof _connInfoHandler === "function") { 2724 _connInfoHandler(_createConnInfoObj()); 2725 } 2726 break; 2727 case _TYPES.RESOURCEID: 2728 _resourceID = data; 2729 break; 2730 case _TYPES.SUBSCRIBE: 2731 _processSubscribeResponse(data); 2732 break; 2733 case _TYPES.UNSUBSCRIBE: 2734 _processUnsubscribeResponse(data); 2735 break; 2736 case _TYPES.PRESENCE: 2737 if (typeof _presenceHandler === "function") { 2738 _presenceHandler(data); 2739 } 2740 break; 2741 default: 2742 break; 2743 } 2744 }, 2745 2746 /** 2747 * Initialize the tunnel config so that the url can be http or https with the appropriate port 2748 * @private 2749 */ 2750 _initTunnelConfig = function () { 2751 if (_isTunnelConfigInit === true) { 2752 return; 2753 } 2754 2755 //Initialize tunnel origin 2756 //Determine tunnel origin based on host and scheme 2757 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 2758 if (scheme) { 2759 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 2760 } else { 2761 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 2762 } 2763 _tunnelURL = _tunnelOrigin + "/tunnel/"; 2764 2765 _isTunnelConfigInit = true; 2766 }, 2767 2768 /** 2769 * Create the tunnel iframe which establishes the shared BOSH connection. 2770 * Messages are sent across frames using window.postMessage. 2771 * @private 2772 */ 2773 _createTunnel = function () { 2774 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 2775 iframe = document.createElement("iframe"); 2776 iframe.style.display = "none"; 2777 iframe.setAttribute("id", tunnelID); 2778 iframe.setAttribute("name", tunnelID); 2779 iframe.setAttribute("src", _tunnelURL); 2780 document.body.appendChild(iframe); 2781 _tunnelFrame = window.frames[tunnelID]; 2782 }; 2783 2784 /** 2785 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 2786 * @private 2787 */ 2788 this.makeConnectReq = function () { 2789 _sendMessage(_TYPES.PASSWORD, _password); 2790 }; 2791 2792 /** 2793 * @private 2794 * Returns the host of the Finesse server. 2795 * @returns {String} 2796 * The host specified during the creation of the object. 2797 */ 2798 this.getHost = function () { 2799 return host; 2800 }; 2801 2802 /** 2803 * @private 2804 * The resource ID of the user who is logged into the notification server. 2805 * @returns {String} 2806 * The resource ID generated by the notification server. 2807 */ 2808 this.getResourceID = function () { 2809 return _resourceID; 2810 }; 2811 2812 /** 2813 * @private 2814 * Indicates whether the tunnel frame is loaded. 2815 * @returns {Boolean} 2816 * True if the tunnel frame is loaded, false otherwise. 2817 */ 2818 this.isTunnelLoaded = function () { 2819 return _isTunnelLoaded; 2820 }; 2821 2822 /** 2823 * @private 2824 * The location of the tunnel HTML URL. 2825 * @returns {String} 2826 * The location of the tunnel HTML URL. 2827 */ 2828 this.getTunnelURL = function () { 2829 return _tunnelURL; 2830 }; 2831 2832 /** 2833 * @private 2834 * Tunnels a subscribe request to the eventing iframe. 2835 * @param {String} node 2836 * The node to subscribe to 2837 * @param {Function} handler 2838 * Handler to invoke upon success or failure 2839 */ 2840 this.subscribe = function (node, handler) { 2841 if (handler && typeof handler !== "function") { 2842 throw new Error("Parameter is not a function."); 2843 } 2844 _handlers.subscribe[node] = handler; 2845 _sendMessage(_TYPES.SUBSCRIBE, node); 2846 }; 2847 2848 /** 2849 * @private 2850 * Tunnels an unsubscribe request to the eventing iframe. 2851 * @param {String} node 2852 * The node to unsubscribe from 2853 * @param {Function} handler 2854 * Handler to invoke upon success or failure 2855 */ 2856 this.unsubscribe = function (node, handler) { 2857 if (handler && typeof handler !== "function") { 2858 throw new Error("Parameter is not a function."); 2859 } 2860 _handlers.unsubscribe[node] = handler; 2861 _sendMessage(_TYPES.UNSUBSCRIBE, node); 2862 }; 2863 2864 /** 2865 * @private 2866 * Registers a handler to be invoked when an event is delivered. Only one 2867 * is registered at a time. If there has already been an event that was 2868 * delivered, the handler will be invoked immediately. 2869 * @param {Function} handler 2870 * Invoked when an event is delivered through the event connection. 2871 */ 2872 this.registerEventHandler = function (handler) { 2873 if (typeof handler !== "function") { 2874 throw new Error("Parameter is not a function."); 2875 } 2876 _eventHandler = handler; 2877 if (_eventCache) { 2878 handler(_eventCache); 2879 } 2880 }; 2881 2882 /** 2883 * @private 2884 * Unregisters the event handler completely. 2885 */ 2886 this.unregisterEventHandler = function () { 2887 _eventHandler = undefined; 2888 }; 2889 2890 /** 2891 * @private 2892 * Registers a handler to be invoked when a presence event is delivered. Only one 2893 * is registered at a time. 2894 * @param {Function} handler 2895 * Invoked when a presence event is delivered through the event connection. 2896 */ 2897 this.registerPresenceHandler = function (handler) { 2898 if (typeof handler !== "function") { 2899 throw new Error("Parameter is not a function."); 2900 } 2901 _presenceHandler = handler; 2902 }; 2903 2904 /** 2905 * @private 2906 * Unregisters the presence event handler completely. 2907 */ 2908 this.unregisterPresenceHandler = function () { 2909 _presenceHandler = undefined; 2910 }; 2911 2912 /** 2913 * @private 2914 * Registers a handler to be invoked when a connection status changes. The 2915 * object passed will contain a "status" property, and a "resourceID" 2916 * property, which will contain the most current resource ID assigned to 2917 * the client. If there has already been an event that was delivered, the 2918 * handler will be invoked immediately. 2919 * @param {Function} handler 2920 * Invoked when a connection status changes. 2921 */ 2922 this.registerConnectionInfoHandler = function (handler) { 2923 if (typeof handler !== "function") { 2924 throw new Error("Parameter is not a function."); 2925 } 2926 _connInfoHandler = handler; 2927 if (_statusCache) { 2928 handler(_createConnInfoObj()); 2929 } 2930 }; 2931 2932 /** 2933 * @private 2934 * Unregisters the connection information handler. 2935 */ 2936 this.unregisterConnectionInfoHandler = function () { 2937 _connInfoHandler = undefined; 2938 }; 2939 2940 /** 2941 * @private 2942 * Start listening for events and create a event tunnel for the shared BOSH 2943 * connection. 2944 * @param {String} id 2945 * The ID of the user for the notification server. 2946 * @param {String} password 2947 * The password of the user for the notification server. 2948 * @param {String} xmppDomain 2949 * The XMPP domain of the notification server 2950 * @param {String} pubsubDomain 2951 * The location (JID) of the XEP-0060 PubSub service 2952 * @param {String} resource 2953 * The resource to connect to the notification servier with. 2954 */ 2955 this.init = function (id, password, xmppDomain, pubsubDomain, resource) { 2956 2957 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string") { 2958 throw new Error("Invalid or missing required parameters."); 2959 } 2960 2961 _initTunnelConfig(); 2962 2963 _id = id; 2964 _password = password; 2965 _xmppDomain = xmppDomain; 2966 _pubsubDomain = pubsubDomain; 2967 _resource = resource; 2968 2969 //Attach a listener for messages sent from tunnel frame. 2970 _util.receiveMessage(_messageHandler, _tunnelOrigin); 2971 2972 //Create the tunnel iframe which will establish the shared connection. 2973 _createTunnel(); 2974 }; 2975 2976 //BEGIN TEST CODE// 2977 // /** 2978 // * Test code added to expose private functions that are used by unit test 2979 // * framework. This section of code is removed during the build process 2980 // * before packaging production code. The [begin|end]TestSection are used 2981 // * by the build to identify the section to strip. 2982 // * @ignore 2983 // */ 2984 // this.beginTestSection = 0; 2985 // 2986 // /** 2987 // * @ignore 2988 // */ 2989 // this.getTestObject = function () { 2990 // //Load mock dependencies. 2991 // var _mock = new MockControl(); 2992 // _util = _mock.createMock(finesse.utilities.Utilities); 2993 // 2994 // return { 2995 // //Expose mock dependencies 2996 // mock: _mock, 2997 // util: _util, 2998 // 2999 // //Expose internal private functions 3000 // types: _TYPES, 3001 // createConnInfoObj: _createConnInfoObj, 3002 // sendMessage: _sendMessage, 3003 // messageHandler: _messageHandler, 3004 // createTunnel: _createTunnel, 3005 // handlers: _handlers, 3006 // initTunnelConfig : _initTunnelConfig 3007 // }; 3008 // }; 3009 // 3010 // /** 3011 // * @ignore 3012 // */ 3013 // this.endTestSection = 0; 3014 // //END TEST CODE// 3015 }; 3016 3017 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 3018 finesse.clientservices = finesse.clientservices || {}; 3019 3020 window.finesse = window.finesse || {}; 3021 window.finesse.clientservices = window.finesse.clientservices || {}; 3022 window.finesse.clientservices.MasterTunnel = MasterTunnel; 3023 3024 })); 3025 3026 /** 3027 * Contains a list of topics used for client side pubsub. 3028 * 3029 */ 3030 3031 /** @private */ 3032 (function (factory) { 3033 3034 3035 // Define as an AMD module if possible 3036 if ( typeof define === 'function' && define.amd ) 3037 { 3038 define('clientservices/Topics',[], factory ); 3039 } 3040 3041 /* Define using browser globals otherwise 3042 * Prevent multiple instantiations if the script is loaded twice 3043 */ 3044 else 3045 { 3046 factory(); 3047 } 3048 3049 }(function () { 3050 3051 var Topics = (function () { 3052 3053 /** 3054 * @private 3055 * The namespace prepended to all Finesse topics. 3056 */ 3057 this.namespace = "finesse.info"; 3058 3059 /** 3060 * @private 3061 * Gets the full topic name with the Finesse namespace prepended. 3062 * @param {String} topic 3063 * The topic category. 3064 * @returns {String} 3065 * The full topic name with prepended namespace. 3066 */ 3067 var _getNSTopic = function (topic) { 3068 return this.namespace + "." + topic; 3069 }; 3070 3071 /** @scope finesse.clientservices.Topics */ 3072 return { 3073 /** 3074 * @private 3075 * Client side request channel. 3076 */ 3077 REQUESTS: _getNSTopic("requests"), 3078 3079 /** 3080 * @private 3081 * Client side response channel. 3082 */ 3083 RESPONSES: _getNSTopic("responses"), 3084 3085 /** 3086 * @private 3087 * Connection status. 3088 */ 3089 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 3090 3091 /** 3092 * @private 3093 * Presence channel 3094 */ 3095 PRESENCE: _getNSTopic("presence"), 3096 3097 /** 3098 * @private 3099 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 3100 */ 3101 getTopic: function (restUri) { 3102 //The topic should not start with '/' else it will get replaced with 3103 //'.' which is invalid. 3104 //Thus, remove '/' if it is at the beginning of the string 3105 if (restUri.indexOf('/') === 0) { 3106 restUri = restUri.substr(1); 3107 } 3108 3109 //Replace every instance of "/" with ".". This is done to follow the 3110 //OpenAjaxHub topic name convention. 3111 return restUri.replace(/\//g, "."); 3112 } 3113 }; 3114 }()); 3115 window.finesse = window.finesse || {}; 3116 window.finesse.clientservices = window.finesse.clientservices || {}; 3117 /** @private */ 3118 window.finesse.clientservices.Topics = Topics; 3119 3120 return Topics; 3121 })); 3122 3123 /** The following comment is to prevent jslint errors about 3124 * using variables before they are defined. 3125 */ 3126 /*global finesse*/ 3127 3128 /** 3129 * Registers with the MasterTunnel to receive events, which it 3130 * could publish to the OpenAjax gadget pubsub infrastructure. 3131 * 3132 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 3133 */ 3134 3135 /** @private */ 3136 (function (factory) { 3137 3138 3139 // Define as an AMD module if possible 3140 if ( typeof define === 'function' && define.amd ) 3141 { 3142 define('clientservices/MasterPublisher',["clientservices/MasterTunnel", 3143 "clientservices/Topics", 3144 "utilities/Utilities"], factory ); 3145 } 3146 3147 /* Define using browser globals otherwise 3148 * Prevent multiple instantiations if the script is loaded twice 3149 */ 3150 else 3151 { 3152 factory(finesse.clientservices.MasterTunnel, finesse.clientservices.Topics, finesse.utilities.Utilities); 3153 } 3154 3155 }(function (MasterTunnel, Topics, Utilities) { 3156 3157 var MasterPublisher = function (tunnel, hub) { 3158 if (!(tunnel instanceof MasterTunnel)) { 3159 throw new Error("Required tunnel object missing or invalid."); 3160 } 3161 3162 var 3163 3164 ClientServices = finesse.clientservices.ClientServices, 3165 3166 /** 3167 * Reference to the gadget pubsub Hub instance. 3168 * @private 3169 */ 3170 _hub = hub, 3171 3172 /** 3173 * Reference to the Topics class. 3174 * @private 3175 */ 3176 _topics = Topics, 3177 3178 /** 3179 * Reference to conversion utilities class. 3180 * @private 3181 */ 3182 _utils = Utilities, 3183 3184 /** 3185 * References to ClientServices logger methods 3186 * @private 3187 */ 3188 _logger = { 3189 log: ClientServices.log 3190 }, 3191 3192 /** 3193 * Store the passed in tunnel. 3194 * @private 3195 */ 3196 _tunnel = tunnel, 3197 3198 /** 3199 * Caches the connection info event so that it could be published if there 3200 * is a request for it. 3201 * @private 3202 */ 3203 _connInfoCache = {}, 3204 3205 /** 3206 * The types of possible request types supported when listening to the 3207 * requests channel. Each request type could result in different operations. 3208 * @private 3209 */ 3210 _REQTYPES = { 3211 CONNECTIONINFO: "ConnectionInfoReq", 3212 SUBSCRIBE: "SubscribeNodeReq", 3213 UNSUBSCRIBE: "UnsubscribeNodeReq", 3214 CONNECT: "ConnectionReq" 3215 }, 3216 3217 /** 3218 * Will store list of nodes that have OF subscriptions created 3219 * _nodesList[node][subscribing].reqIds[subid] 3220 * _nodesList[node][active].reqIds[subid] 3221 * _nodesList[node][unsubscribing].reqIds[subid] 3222 * _nodesList[node][holding].reqIds[subid] 3223 * @private 3224 */ 3225 _nodesList = {}, 3226 3227 /** 3228 * The states that a subscription can be in 3229 * @private 3230 */ 3231 _CHANNELSTATES = { 3232 UNINITIALIZED: "Uninitialized", 3233 PENDING: "Pending", 3234 OPERATIONAL: "Operational" 3235 }, 3236 3237 /** 3238 * Checks if the payload is JSON 3239 * @returns {Boolean} 3240 * @private 3241 */ 3242 _isJsonPayload = function(event) { 3243 var delimStart, delimEnd, retval = false; 3244 3245 try { 3246 delimStart = event.indexOf('{'); 3247 delimEnd = event.lastIndexOf('}'); 3248 3249 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 3250 retval = true; //event contains JSON payload 3251 } 3252 } catch (err) { 3253 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 3254 } 3255 return retval; 3256 }, 3257 3258 /** 3259 * Parses a JSON event and then publishes. 3260 * 3261 * @param {String} event 3262 * The full event payload. 3263 * @throws {Error} If the payload object is malformed. 3264 * @private 3265 */ 3266 _parseAndPublishJSONEvent = function(event) { 3267 var topic, eventObj, publishEvent, 3268 delimPos = event.indexOf("{"), 3269 node, parser, 3270 eventJson = event, 3271 returnObj = {node: null, data: null}; 3272 3273 try { 3274 //Extract and strip the node path from the message 3275 if (delimPos > 0) 3276 { 3277 //We need to decode the URI encoded node path 3278 //TODO: make sure this is kosher with OpenAjax topic naming 3279 node = decodeURI(event.substr(0, delimPos)); 3280 eventJson = event.substr(delimPos); 3281 3282 //Converting the node path to openAjaxhub topic 3283 topic = _topics.getTopic(node); 3284 3285 returnObj.node = node; 3286 returnObj.payload = eventJson; 3287 } 3288 else 3289 { 3290 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 3291 throw new Error("node is not given in postMessage: " + eventJson); 3292 } 3293 3294 parser = _utils.getJSONParser(); 3295 3296 eventObj = parser.parse(eventJson); 3297 returnObj.data = eventObj; 3298 3299 } catch (err) { 3300 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 3301 throw new Error("Malformed event payload : " + err); 3302 } 3303 3304 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 3305 3306 publishEvent = {content : event, object : eventObj }; 3307 3308 //Publish event to proper event topic. 3309 if (topic && eventObj) { 3310 _hub.publish(topic, publishEvent); 3311 } 3312 }, 3313 3314 /** 3315 * Parses an XML event and then publishes. 3316 * 3317 * @param {String} event 3318 * The full event payload. 3319 * @throws {Error} If the payload object is malformed. 3320 * @private 3321 */ 3322 _parseAndPublishXMLEvent = function(event) { 3323 var topic, eventObj, publishEvent, restTopic, 3324 delimPos = event.indexOf("<"), 3325 node, 3326 eventXml = event; 3327 3328 try { 3329 //Extract and strip the node path from the message 3330 if (delimPos > 0) { 3331 //We need to decode the URI encoded node path 3332 //TODO: make sure this is kosher with OpenAjax topic naming 3333 node = decodeURI(event.substr(0, delimPos)); 3334 eventXml = event.substr(delimPos); 3335 //Converting the node path to openAjaxhub topic 3336 topic = _topics.getTopic(node); 3337 } else { 3338 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 3339 throw new Error("node is not given in postMessage: " + eventXml); 3340 } 3341 3342 eventObj = _utils.xml2JsObj(eventXml); 3343 3344 } catch (err) { 3345 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 3346 throw new Error("Malformed event payload : " + err); 3347 } 3348 3349 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 3350 3351 publishEvent = {content : event, object : eventObj }; 3352 3353 //Publish event to proper event topic. 3354 if (topic && eventObj) { 3355 _hub.publish(topic, publishEvent); 3356 } 3357 }, 3358 3359 /** 3360 * Publishes events to the appropriate topic. The topic name is determined 3361 * by fetching the source value from the event. 3362 * @param {String} event 3363 * The full event payload. 3364 * @throws {Error} If the payload object is malformed. 3365 * @private 3366 */ 3367 _eventHandler = function (event) { 3368 3369 //Handle JSON or XML events 3370 if (!_isJsonPayload(event)) 3371 { 3372 //XML 3373 _parseAndPublishXMLEvent(event); 3374 } 3375 else 3376 { 3377 //JSON 3378 _parseAndPublishJSONEvent(event); 3379 } 3380 }, 3381 3382 3383 /** 3384 * Handler for when presence events are sent through the MasterTunnel. 3385 * @returns {Object} 3386 * A presence xml event. 3387 * @private 3388 */ 3389 _presenceHandler = function (event) { 3390 var eventObj = _utils.xml2JsObj(event), publishEvent; 3391 3392 publishEvent = {content : event, object : eventObj}; 3393 3394 if (eventObj) { 3395 _hub.publish(_topics.PRESENCE, publishEvent); 3396 } 3397 }, 3398 3399 /** 3400 * Clone the connection info object from cache. 3401 * @returns {Object} 3402 * A connection info object containing a "status" and "resourceID". 3403 * @private 3404 */ 3405 _cloneConnInfoObj = function () { 3406 if (_connInfoCache) { 3407 return { 3408 status: _connInfoCache.status, 3409 resourceID: _connInfoCache.resourceID 3410 }; 3411 } else { 3412 return null; 3413 } 3414 }, 3415 3416 _cleanupPendingRequests = function () { 3417 var node, curSubid, errObj = { 3418 error: { 3419 errorType: "Disconnected", 3420 errorMessage: "Outstanding request will never complete." 3421 } 3422 }; 3423 3424 // Iterate through all outstanding subscribe requests to notify them that it will never return 3425 for (node in _nodesList) { 3426 if (_nodesList.hasOwnProperty(node)) { 3427 for (curSubid in _nodesList[node].subscribing.reqIds) { 3428 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 3429 // Notify this outstanding subscribe request to give up and error out 3430 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 3431 } 3432 } 3433 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 3434 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 3435 // Notify this outstanding unsubscribe request to give up and error out 3436 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 3437 } 3438 } 3439 } 3440 } 3441 }, 3442 3443 /** 3444 * Publishes the connection info to the connection info topic. 3445 * @param {Object} connInfo 3446 * The connection info object containing the status and resource ID. 3447 * @private 3448 */ 3449 _connInfoHandler = function (connInfo) { 3450 var before = _connInfoCache.status; 3451 _connInfoCache = connInfo; 3452 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 3453 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 3454 if (before === "connected" && connInfo.status !== "connected") { 3455 // Fail all pending requests when we transition to disconnected 3456 _cleanupPendingRequests(); 3457 } 3458 }, 3459 3460 3461 /** 3462 * Utility method to bookkeep node subscription requests and determine 3463 * whehter it is necessary to tunnel the request to JabberWerx. 3464 * @param {String} node 3465 * The node of interest 3466 * @param {String} reqId 3467 * A unique string identifying the request/subscription 3468 * @private 3469 */ 3470 _subscribeNode = function (node, subid) { 3471 if (_connInfoCache.status !== "connected") { 3472 _hub.publish(_topics.RESPONSES + "." + subid, { 3473 error: { 3474 errorType: "Not connected", 3475 errorMessage: "Cannot subscribe without connection." 3476 } 3477 }); 3478 return; 3479 } 3480 // NODE DOES NOT YET EXIST 3481 if (!_nodesList[node]) { 3482 _nodesList[node] = { 3483 "subscribing": { 3484 "reqIds": {}, 3485 "length": 0 3486 }, 3487 "active": { 3488 "reqIds": {}, 3489 "length": 0 3490 }, 3491 "unsubscribing": { 3492 "reqIds": {}, 3493 "length": 0 3494 }, 3495 "holding": { 3496 "reqIds": {}, 3497 "length": 0 3498 } 3499 }; 3500 } 3501 if (_nodesList[node].active.length === 0) { 3502 if (_nodesList[node].unsubscribing.length === 0) { 3503 if (_nodesList[node].subscribing.length === 0) { 3504 _nodesList[node].subscribing.reqIds[subid] = true; 3505 _nodesList[node].subscribing.length += 1; 3506 3507 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 3508 _tunnel.subscribe(node, function (err) { 3509 var errObj, curSubid; 3510 if (err) { 3511 errObj = { 3512 subscribe: { 3513 content: err 3514 } 3515 }; 3516 3517 try { 3518 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 3519 } catch (e) { 3520 errObj.error = { 3521 errorType: "parseError", 3522 errorMessage: "Could not serialize XML: " + e 3523 }; 3524 } 3525 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 3526 } else { 3527 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 3528 } 3529 3530 for (curSubid in _nodesList[node].subscribing.reqIds) { 3531 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 3532 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 3533 if (!err) { 3534 _nodesList[node].active.reqIds[curSubid] = true; 3535 _nodesList[node].active.length += 1; 3536 } 3537 delete _nodesList[node].subscribing.reqIds[curSubid]; 3538 _nodesList[node].subscribing.length -= 1; 3539 } 3540 } 3541 }); 3542 3543 } else { //other ids are subscribing 3544 _nodesList[node].subscribing.reqIds[subid] = true; 3545 _nodesList[node].subscribing.length += 1; 3546 } 3547 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 3548 _nodesList[node].holding.reqIds[subid] = true; 3549 _nodesList[node].holding.length += 1; 3550 } 3551 } else { // The node has active subscriptions; add this subid and return successful response 3552 _nodesList[node].active.reqIds[subid] = true; 3553 _nodesList[node].active.length += 1; 3554 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 3555 } 3556 }, 3557 3558 /** 3559 * Utility method to bookkeep node unsubscribe requests and determine 3560 * whehter it is necessary to tunnel the request to JabberWerx. 3561 * @param {String} node 3562 * The node to unsubscribe from 3563 * @param {String} reqId 3564 * A unique string identifying the subscription to remove 3565 * @private 3566 */ 3567 _unsubscribeNode = function (node, subid) { 3568 if (!_nodesList[node]) { //node DNE, publish success response 3569 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 3570 } else { 3571 if (_connInfoCache.status !== "connected") { 3572 _hub.publish(_topics.RESPONSES + "." + subid, { 3573 error: { 3574 errorType: "Not connected", 3575 errorMessage: "Cannot unsubscribe without connection." 3576 } 3577 }); 3578 return; 3579 } 3580 if (_nodesList[node].active.length > 1) { 3581 delete _nodesList[node].active.reqIds[subid]; 3582 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 3583 _nodesList[node].active.length -= 1; 3584 } else if (_nodesList[node].active.length === 1) { // transition subid from active category to unsubscribing category 3585 _nodesList[node].unsubscribing.reqIds[subid] = true; 3586 _nodesList[node].unsubscribing.length += 1; 3587 delete _nodesList[node].active.reqIds[subid]; 3588 _nodesList[node].active.length -= 1; 3589 3590 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 3591 _tunnel.unsubscribe(node, function (err) { 3592 var errObj, curSubid; 3593 if (err) { 3594 errObj = { 3595 subscribe: { 3596 content: err 3597 } 3598 }; 3599 3600 try { 3601 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 3602 } catch (e) { 3603 errObj.error = { 3604 errorType: "parseError", 3605 errorMessage: "Could not serialize XML: " + e 3606 }; 3607 } 3608 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 3609 } else { 3610 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 3611 } 3612 3613 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 3614 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 3615 // publish to all subids whether unsubscribe failed or succeeded 3616 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 3617 if (!err) { 3618 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 3619 _nodesList[node].unsubscribing.length -= 1; 3620 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 3621 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 3622 _nodesList[node].unsubscribing.length -= 1; 3623 } 3624 } 3625 } 3626 3627 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 3628 for (curSubid in _nodesList[node].holding.reqIds) { 3629 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 3630 delete _nodesList[node].holding.reqIds[curSubid]; 3631 _nodesList[node].holding.length -= 1; 3632 _subscribeNode(node, curSubid); 3633 } 3634 } 3635 } 3636 }); 3637 } else { // length <= 0? 3638 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 3639 } 3640 } 3641 }, 3642 3643 /** 3644 * Handles client requests to establish a BOSH connection. 3645 * @param {String} id 3646 * id of the xmpp user 3647 * @param {String} password 3648 * password of the xmpp user 3649 * @param {String} xmppDomain 3650 * xmppDomain of the xmpp user account 3651 * @private 3652 */ 3653 _connect = function (id, password, xmppDomain) { 3654 _tunnel.makeConnectReq(id, password, xmppDomain); 3655 }, 3656 3657 /** 3658 * Handles client requests made to the request topic. The type of the 3659 * request is described in the "type" property within the data payload. Each 3660 * type can result in a different operation. 3661 * @param {String} topic 3662 * The topic which data was published to. 3663 * @param {Object} data 3664 * The data containing requests information published by clients. 3665 * @param {String} data.type 3666 * The type of the request. Supported: "ConnectionInfoReq" 3667 * @param {Object} data.data 3668 * May contain data relevant for the particular requests. 3669 * @param {String} [data.invokeID] 3670 * The ID used to identify the request with the response. The invoke ID 3671 * will be included in the data in the publish to the topic. It is the 3672 * responsibility of the client to correlate the published data to the 3673 * request made by using the invoke ID. 3674 * @private 3675 */ 3676 _clientRequestHandler = function (topic, data) { 3677 var dataCopy; 3678 3679 //Ensure a valid data object with "type" and "data" properties. 3680 if (typeof data === "object" && 3681 typeof data.type === "string" && 3682 typeof data.data === "object") { 3683 switch (data.type) { 3684 case _REQTYPES.CONNECTIONINFO: 3685 //It is possible that Slave clients come up before the Master 3686 //client. If that is the case, the Slaves will need to make a 3687 //request for the Master to send the latest connection info to the 3688 //connectionInfo topic. 3689 dataCopy = _cloneConnInfoObj(); 3690 if (dataCopy) { 3691 if (data.invokeID !== undefined) { 3692 dataCopy.invokeID = data.invokeID; 3693 } 3694 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 3695 } 3696 break; 3697 case _REQTYPES.SUBSCRIBE: 3698 if (typeof data.data.node === "string") { 3699 _subscribeNode(data.data.node, data.invokeID); 3700 } 3701 break; 3702 case _REQTYPES.UNSUBSCRIBE: 3703 if (typeof data.data.node === "string") { 3704 _unsubscribeNode(data.data.node, data.invokeID); 3705 } 3706 break; 3707 case _REQTYPES.CONNECT: 3708 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 3709 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 3710 break; 3711 default: 3712 break; 3713 } 3714 } 3715 }; 3716 3717 (function () { 3718 //Register to receive events and connection status from tunnel. 3719 _tunnel.registerEventHandler(_eventHandler); 3720 _tunnel.registerPresenceHandler(_presenceHandler); 3721 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 3722 3723 //Listen to a request channel to respond to any requests made by other 3724 //clients because the Master may have access to useful information. 3725 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 3726 }()); 3727 3728 /** 3729 * @private 3730 * Handles client requests to establish a BOSH connection. 3731 * @param {String} id 3732 * id of the xmpp user 3733 * @param {String} password 3734 * password of the xmpp user 3735 * @param {String} xmppDomain 3736 * xmppDomain of the xmpp user account 3737 */ 3738 this.connect = function (id, password, xmppDomain) { 3739 _connect(id, password, xmppDomain); 3740 }; 3741 3742 /** 3743 * @private 3744 * Resets the list of explicit subscriptions 3745 */ 3746 this.wipeout = function () { 3747 _cleanupPendingRequests(); 3748 _nodesList = {}; 3749 }; 3750 3751 //BEGIN TEST CODE// 3752 /** 3753 * Test code added to expose private functions that are used by unit test 3754 * framework. This section of code is removed during the build process 3755 * before packaging production code. The [begin|end]TestSection are used 3756 * by the build to identify the section to strip. 3757 * @ignore 3758 */ 3759 this.beginTestSection = 0; 3760 3761 /** 3762 * @ignore 3763 */ 3764 this.getTestObject = function () { 3765 //Load mock dependencies. 3766 var _mock = new MockControl(); 3767 _hub = _mock.createMock(gadgets.Hub); 3768 _tunnel = _mock.createMock(); 3769 3770 return { 3771 //Expose mock dependencies 3772 mock: _mock, 3773 hub: _hub, 3774 tunnel: _tunnel, 3775 setTunnel: function (tunnel) { 3776 _tunnel = tunnel; 3777 }, 3778 getTunnel: function () { 3779 return _tunnel; 3780 }, 3781 3782 //Expose internal private functions 3783 reqtypes: _REQTYPES, 3784 eventHandler: _eventHandler, 3785 presenceHandler: _presenceHandler, 3786 3787 subscribeNode: _subscribeNode, 3788 unsubscribeNode: _unsubscribeNode, 3789 3790 getNodeList: function () { 3791 return _nodesList; 3792 }, 3793 setNodeList: function (nodelist) { 3794 _nodesList = nodelist; 3795 }, 3796 3797 cloneConnInfoObj: _cloneConnInfoObj, 3798 connInfoHandler: _connInfoHandler, 3799 clientRequestHandler: _clientRequestHandler 3800 3801 }; 3802 }; 3803 3804 3805 /** 3806 * @ignore 3807 */ 3808 this.endTestSection = 0; 3809 //END TEST CODE// 3810 3811 }; 3812 3813 window.finesse = window.finesse || {}; 3814 window.finesse.clientservices = window.finesse.clientservices || {}; 3815 window.finesse.clientservices.MasterPublisher = MasterPublisher; 3816 3817 })); 3818 3819 /** The following comment is to prevent jslint errors about 3820 * using variables before they are defined. 3821 */ 3822 /*global publisher:true */ 3823 3824 /** 3825 * Exposes a set of API wrappers that will hide the dirty work of 3826 * constructing Finesse API requests and consuming Finesse events. 3827 * 3828 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 3829 */ 3830 3831 3832 /** 3833 * Allow clients to make Finesse API requests and consume Finesse events by 3834 * calling a set of exposed functions. The Services layer will do the dirty 3835 * work of establishing a shared BOSH connection (for designated Master 3836 * modules), consuming events for client subscriptions, and constructing API 3837 * requests. 3838 */ 3839 /** @private */ 3840 (function (factory) { 3841 3842 3843 // Define as an AMD module if possible 3844 if ( typeof define === 'function' && define.amd ) 3845 { 3846 define('clientservices/ClientServices',["clientservices/MasterTunnel", 3847 "clientservices/MasterPublisher", 3848 "clientservices/Topics", 3849 "utilities/Utilities"], factory ); 3850 } 3851 3852 /* Define using browser globals otherwise 3853 * Prevent multiple instantiations if the script is loaded twice 3854 */ 3855 else 3856 { 3857 factory(finesse.clientservices.MasterTunnel, finesse.clientservices.MasterPublisher, finesse.clientservices.Topics, finesse.utilities.Utilities); 3858 } 3859 3860 }(function (MasterTunnel, MasterPublisher, Topics, Utilities) { 3861 3862 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 3863 var 3864 3865 /** 3866 * Shortcut reference to the master tunnel 3867 * @private 3868 */ 3869 _tunnel, 3870 3871 _publisher, 3872 3873 /** 3874 * Shortcut reference to the finesse.utilities.Utilities singleton 3875 * This will be set by init() 3876 * @private 3877 */ 3878 _util, 3879 3880 /** 3881 * Shortcut reference to the gadgets.io object. 3882 * This will be set by init() 3883 * @private 3884 */ 3885 _io, 3886 3887 /** 3888 * Shortcut reference to the gadget pubsub Hub instance. 3889 * This will be set by init() 3890 * @private 3891 */ 3892 _hub, 3893 3894 /** 3895 * Logger object set externally by setLogger, defaults to nothing. 3896 * @private 3897 */ 3898 _logger = {}, 3899 3900 /** 3901 * Shortcut reference to the Topics class. 3902 * This will be set by init() 3903 * @private 3904 */ 3905 _topics, 3906 3907 /** 3908 * Config object needed to initialize this library 3909 * This must be set by init() 3910 * @private 3911 */ 3912 _config, 3913 3914 /** 3915 * @private 3916 * Whether or not this ClientService instance is a Master. 3917 */ 3918 _isMaster = false, 3919 3920 /** 3921 * @private 3922 * Whether the Client Services have been initiated yet. 3923 */ 3924 _inited = false, 3925 3926 /** 3927 * Stores the list of subscription IDs for all subscriptions so that it 3928 * could be retrieve for unsubscriptions. 3929 * @private 3930 */ 3931 _subscriptionID = {}, 3932 3933 /** 3934 * The possible states of the JabberWerx BOSH connection. 3935 * @private 3936 */ 3937 _STATUS = { 3938 CONNECTING: "connecting", 3939 CONNECTED: "connected", 3940 DISCONNECTED: "disconnected", 3941 DISCONNECTED_CONFLICT: "conflict", 3942 DISCONNECTED_UNAUTHORIZED: "unauthorized", 3943 DISCONNECTING: "disconnecting", 3944 RECONNECTING: "reconnecting", 3945 UNLOADING: "unloading", 3946 FAILING: "failing", 3947 RECOVERED: "recovered" 3948 }, 3949 3950 _failoverMode = false, 3951 3952 /** 3953 * Handler function to be invoked when BOSH connection is connecting. 3954 * @private 3955 */ 3956 _onConnectingHandler, 3957 3958 /** 3959 * Handler function to be invoked when BOSH connection is connected 3960 * @private 3961 */ 3962 _onConnectHandler, 3963 3964 /** 3965 * Handler function to be invoked when BOSH connection is disconnecting. 3966 * @private 3967 */ 3968 _onDisconnectingHandler, 3969 3970 /** 3971 * Handler function to be invoked when the BOSH is disconnected. 3972 * @private 3973 */ 3974 _onDisconnectHandler, 3975 3976 /** 3977 * Handler function to be invoked when the BOSH is reconnecting. 3978 * @private 3979 */ 3980 _onReconnectingHandler, 3981 3982 /** 3983 * Handler function to be invoked when the BOSH is unloading. 3984 * @private 3985 */ 3986 _onUnloadingHandler, 3987 3988 /** 3989 * Contains a cache of the latest connection info containing the current 3990 * state of the BOSH connection and the resource ID. 3991 * @private 3992 */ 3993 _connInfo, 3994 3995 /** 3996 * Keeps track of all the objects that need to be refreshed when we recover 3997 * due to our resilient connection. Only objects that we subscribe to will 3998 * be added to this list. 3999 * @private 4000 */ 4001 _refreshList = [], 4002 4003 /** 4004 * @private 4005 * Centralized logger.log method for external logger 4006 * @param {String} msg 4007 * Message to log 4008 */ 4009 _log = function (msg) { 4010 // If the external logger throws up, it stops here. 4011 try { 4012 if (_logger.log) { 4013 _logger.log("[ClientServices] " + msg); 4014 } 4015 } catch (e) { } 4016 }, 4017 4018 /** 4019 * Go through each object in the _refreshList and call its refresh() function 4020 * @private 4021 */ 4022 _refreshObjects = function () { 4023 var i; 4024 4025 // wipe out the explicit subscription list before we refresh objects 4026 if (_publisher) { 4027 _publisher.wipeout(); 4028 } 4029 4030 // refresh each item in the refresh list 4031 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4032 _log("Refreshing " + _refreshList[i].getRestUrl()); 4033 _refreshList[i].refresh(10); 4034 } 4035 }, 4036 4037 /** 4038 * Handler to process connection info publishes. 4039 * @param {Object} data 4040 * The connection info data object. 4041 * @param {String} data.status 4042 * The BOSH connection status. 4043 * @param {String} data.resourceID 4044 * The resource ID for the connection. 4045 * @private 4046 */ 4047 _connInfoHandler = function (data) { 4048 4049 //Invoke registered handler depending on status received. Due to the 4050 //request topic where clients can make request for the Master to publish 4051 //the connection info, there is a chance that duplicate connection info 4052 //events may be sent, so ensure that there has been a state change 4053 //before invoking the handlers. 4054 if (_connInfo === undefined || _connInfo.status !== data.status) { 4055 _connInfo = data; 4056 switch (data.status) { 4057 case _STATUS.CONNECTING: 4058 if (_isMaster && _onConnectingHandler) { 4059 _onConnectingHandler(); 4060 } 4061 break; 4062 case _STATUS.CONNECTED: 4063 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 4064 _onConnectHandler(); 4065 } 4066 break; 4067 case _STATUS.DISCONNECTED: 4068 if (_isMaster && _onDisconnectHandler) { 4069 _onDisconnectHandler(); 4070 } 4071 break; 4072 case _STATUS.DISCONNECTED_CONFLICT: 4073 if (_isMaster && _onDisconnectHandler) { 4074 _onDisconnectHandler("conflict"); 4075 } 4076 break; 4077 case _STATUS.DISCONNECTED_UNAUTHORIZED: 4078 if (_isMaster && _onDisconnectHandler) { 4079 _onDisconnectHandler("unauthorized"); 4080 } 4081 break; 4082 case _STATUS.DISCONNECTING: 4083 if (_isMaster && _onDisconnectingHandler) { 4084 _onDisconnectingHandler(); 4085 } 4086 break; 4087 case _STATUS.RECONNECTING: 4088 if (_isMaster && _onReconnectingHandler) { 4089 _onReconnectingHandler(); 4090 } 4091 break; 4092 case _STATUS.UNLOADING: 4093 if (_isMaster && _onUnloadingHandler) { 4094 _onUnloadingHandler(); 4095 } 4096 break; 4097 case _STATUS.FAILING: 4098 if (!_isMaster) { 4099 // Stop 4100 _failoverMode = true; 4101 if (_onDisconnectHandler) { 4102 _onDisconnectHandler(); 4103 } 4104 } 4105 break; 4106 case _STATUS.RECOVERED: 4107 if (!_isMaster) { 4108 _failoverMode = false; 4109 if (_onConnectHandler) { 4110 _onConnectHandler(); 4111 } 4112 } 4113 // Whenever we are recovered, we need to refresh any objects 4114 // that are stored. 4115 _refreshObjects(); 4116 break; 4117 } 4118 } 4119 }, 4120 4121 /** 4122 * Ensure that ClientServices have been inited. 4123 * @private 4124 */ 4125 _isInited = function () { 4126 if (!_inited) { 4127 throw new Error("ClientServices needs to be inited."); 4128 } 4129 }, 4130 4131 /** 4132 * Have the client become the Master by initiating a tunnel to a shared 4133 * event BOSH connection. The Master is responsible for publishing all 4134 * events to the pubsub infrastructure. 4135 * @private 4136 */ 4137 _becomeMaster = function () { 4138 _tunnel = new MasterTunnel(_config.host, _config.scheme); 4139 _publisher = new MasterPublisher(_tunnel, _hub); 4140 _tunnel.init(_config.id, _config.password, _config.xmppDomain, _config.pubsubDomain, _config.resource); 4141 _isMaster = true; 4142 }, 4143 4144 /** 4145 * Make a request to the request channel to have the Master publish the 4146 * connection info object. 4147 * @private 4148 */ 4149 _makeConnectionInfoReq = function () { 4150 var data = { 4151 type: "ConnectionInfoReq", 4152 data: {}, 4153 invokeID: (new Date()).getTime() 4154 }; 4155 _hub.publish(_topics.REQUESTS, data); 4156 }, 4157 4158 /** 4159 * Utility method to register a handler which is associated with a 4160 * particular connection status. 4161 * @param {String} status 4162 * The connection status string. 4163 * @param {Function} handler 4164 * The handler to associate with a particular connection status. 4165 * @throws {Error} 4166 * If the handler provided is not a function. 4167 * @private 4168 */ 4169 _registerHandler = function (status, handler) { 4170 if (typeof handler === "function") { 4171 if (_connInfo && _connInfo.status === status) { 4172 handler(); 4173 } 4174 switch (status) { 4175 case _STATUS.CONNECTING: 4176 _onConnectingHandler = handler; 4177 break; 4178 case _STATUS.CONNECTED: 4179 _onConnectHandler = handler; 4180 break; 4181 case _STATUS.DISCONNECTED: 4182 _onDisconnectHandler = handler; 4183 break; 4184 case _STATUS.DISCONNECTING: 4185 _onDisconnectingHandler = handler; 4186 break; 4187 case _STATUS.RECONNECTING: 4188 _onReconnectingHandler = handler; 4189 break; 4190 case _STATUS.UNLOADING: 4191 _onUnloadingHandler = handler; 4192 break; 4193 } 4194 4195 } else { 4196 throw new Error("Callback is not a function"); 4197 } 4198 }; 4199 4200 return { 4201 4202 /** 4203 * @private 4204 * Adds an item to the list to be refreshed upon reconnect 4205 * @param {RestBase} object - rest object to be refreshed 4206 */ 4207 addToRefreshList: function (object) { 4208 _refreshList.push(object); 4209 }, 4210 4211 /** 4212 * @private 4213 * Removes the given item from the refresh list 4214 * @param {RestBase} object - rest object to be removed 4215 */ 4216 removeFromRefreshList: function (object) { 4217 var i; 4218 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4219 if (_refreshList[i] === object) { 4220 _refreshList.splice(i, 1); 4221 break; 4222 } 4223 } 4224 }, 4225 4226 /** 4227 * @private 4228 * The location of the tunnel HTML URL. 4229 * @returns {String} 4230 * The location of the tunnel HTML URL. 4231 */ 4232 getTunnelURL: function () { 4233 return _tunnel.getTunnelURL(); 4234 }, 4235 4236 /** 4237 * @private 4238 * Indicates whether the tunnel frame is loaded. 4239 * @returns {Boolean} 4240 * True if the tunnel frame is loaded, false otherwise. 4241 */ 4242 isTunnelLoaded: function () { 4243 return _tunnel.isTunnelLoaded(); 4244 }, 4245 4246 /** 4247 * @private 4248 * Indicates whether the ClientServices instance is a Master. 4249 * @returns {Boolean} 4250 * True if this instance of ClientServices is a Master, false otherwise. 4251 */ 4252 isMaster: function () { 4253 return _isMaster; 4254 }, 4255 4256 /** 4257 * @private 4258 * Get the resource ID. An ID is only available if the BOSH connection has 4259 * been able to connect successfully. 4260 * @returns {String} 4261 * The resource ID string. Null if the BOSH connection was never 4262 * successfully created and/or the resource ID has not been associated. 4263 */ 4264 getResourceID: function () { 4265 if (_connInfo !== undefined) { 4266 return _connInfo.resourceID; 4267 } 4268 return null; 4269 }, 4270 4271 /* 4272 getHub: function () { 4273 return _hub; 4274 }, 4275 */ 4276 /** 4277 * @private 4278 * Add a callback to be invoked when the BOSH connection is attempting 4279 * to connect. If the connection is already trying to connect, the 4280 * callback will be invoked immediately. 4281 * @param {Function} handler 4282 * An empty param function to be invoked on connecting. Only one 4283 * handler can be registered at a time. Handlers already registered 4284 * will be overwritten. 4285 */ 4286 registerOnConnectingHandler: function (handler) { 4287 _registerHandler(_STATUS.CONNECTING, handler); 4288 }, 4289 4290 /** 4291 * @private 4292 * Removes the on connecting callback that was registered. 4293 */ 4294 unregisterOnConnectingHandler: function () { 4295 _onConnectingHandler = undefined; 4296 }, 4297 4298 /** 4299 * @private 4300 * Add a callback to be invoked when the BOSH connection has been 4301 * established. If the connection has already been established, the 4302 * callback will be invoked immediately. 4303 * @param {Function} handler 4304 * An empty param function to be invoked on connect. Only one handler 4305 * can be registered at a time. Handlers already registered will be 4306 * overwritten. 4307 */ 4308 registerOnConnectHandler: function (handler) { 4309 _registerHandler(_STATUS.CONNECTED, handler); 4310 }, 4311 4312 /** 4313 * @private 4314 * Removes the on connect callback that was registered. 4315 */ 4316 unregisterOnConnectHandler: function () { 4317 _onConnectHandler = undefined; 4318 }, 4319 4320 /** 4321 * @private 4322 * Add a callback to be invoked when the BOSH connection goes down. If 4323 * the connection is already down, invoke the callback immediately. 4324 * @param {Function} handler 4325 * An empty param function to be invoked on disconnected. Only one 4326 * handler can be registered at a time. Handlers already registered 4327 * will be overwritten. 4328 */ 4329 registerOnDisconnectHandler: function (handler) { 4330 _registerHandler(_STATUS.DISCONNECTED, handler); 4331 }, 4332 4333 /** 4334 * @private 4335 * Removes the on disconnect callback that was registered. 4336 */ 4337 unregisterOnDisconnectHandler: function () { 4338 _onDisconnectHandler = undefined; 4339 }, 4340 4341 /** 4342 * @private 4343 * Add a callback to be invoked when the BOSH is currently disconnecting. If 4344 * the connection is already disconnecting, invoke the callback immediately. 4345 * @param {Function} handler 4346 * An empty param function to be invoked on disconnected. Only one 4347 * handler can be registered at a time. Handlers already registered 4348 * will be overwritten. 4349 */ 4350 registerOnDisconnectingHandler: function (handler) { 4351 _registerHandler(_STATUS.DISCONNECTING, handler); 4352 }, 4353 4354 /** 4355 * @private 4356 * Removes the on disconnecting callback that was registered. 4357 */ 4358 unregisterOnDisconnectingHandler: function () { 4359 _onDisconnectingHandler = undefined; 4360 }, 4361 4362 /** 4363 * @private 4364 * Add a callback to be invoked when the BOSH connection is attempting 4365 * to connect. If the connection is already trying to connect, the 4366 * callback will be invoked immediately. 4367 * @param {Function} handler 4368 * An empty param function to be invoked on connecting. Only one 4369 * handler can be registered at a time. Handlers already registered 4370 * will be overwritten. 4371 */ 4372 registerOnReconnectingHandler: function (handler) { 4373 _registerHandler(_STATUS.RECONNECTING, handler); 4374 }, 4375 4376 /** 4377 * @private 4378 * Removes the on reconnecting callback that was registered. 4379 */ 4380 unregisterOnReconnectingHandler: function () { 4381 _onReconnectingHandler = undefined; 4382 }, 4383 4384 /** 4385 * @private 4386 * Add a callback to be invoked when the BOSH connection is unloading 4387 * 4388 * @param {Function} handler 4389 * An empty param function to be invoked on connecting. Only one 4390 * handler can be registered at a time. Handlers already registered 4391 * will be overwritten. 4392 */ 4393 registerOnUnloadingHandler: function (handler) { 4394 _registerHandler(_STATUS.UNLOADING, handler); 4395 }, 4396 4397 /** 4398 * @private 4399 * Removes the on unloading callback that was registered. 4400 */ 4401 unregisterOnUnloadingHandler: function () { 4402 _onUnloadingHandler = undefined; 4403 }, 4404 4405 /** 4406 * @private 4407 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 4408 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 4409 * configuration is encapsulated in here anyways. 4410 * This removes the dependency 4411 * @param {String} url 4412 * The relative url to make the request to (the host from the passed in config will be 4413 * appended). It is expected that any encoding to the URL is already done. 4414 * @param {Function} handler 4415 * Callback handler for makeRequest to invoke when the response returns. 4416 * Completely passed through to gadgets.io.makeRequest 4417 * @param {Object} params 4418 * The params object that gadgets.io.makeRequest expects. Authorization and locale 4419 * headers are mixed in. 4420 */ 4421 makeRequest: function (url, handler, params) { 4422 var requestedScheme, scheme = "http"; 4423 4424 // ClientServices needs to be initialized with a config for restHost, auth, and locale 4425 _isInited(); 4426 4427 // Allow mixin of auth and locale headers 4428 params = params || {}; 4429 4430 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 4431 // using GET http method because then the params are added to the url as query params, which 4432 // exposes the authorization string in the url. This is a placeholder until oauth comes in 4433 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 4434 4435 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 4436 4437 // Add Basic auth to request header 4438 params[gadgets.io.RequestParameters.HEADERS].Authorization = "Basic " + _config.authorization; 4439 //Locale 4440 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 4441 4442 //Allow clients to override the scheme: 4443 // - If not specified => we use HTTP 4444 // - If null specified => we use _config.scheme 4445 // - Otherwise => we use whatever they provide 4446 requestedScheme = params.SCHEME; 4447 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 4448 if (requestedScheme === null) { 4449 scheme = _config.scheme; 4450 } else { 4451 scheme = requestedScheme; 4452 } 4453 } 4454 4455 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 4456 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 4457 }, 4458 4459 /** 4460 * @private 4461 * Utility function to make a subscription to a particular topic. Only one 4462 * callback function is registered to a particular topic at any time. 4463 * @param {String} topic 4464 * The full topic name. The topic name should follow the OpenAjax 4465 * convention using dot notation (ex: finesse.api.User.1000). 4466 * @param {Function} callback 4467 * The function that should be invoked with the data when an event 4468 * is delivered to the specific topic. 4469 * @returns {Boolean} 4470 * True if the subscription was made successfully and the callback was 4471 * been registered. False if the subscription already exist, the 4472 * callback was not overwritten. 4473 */ 4474 subscribe: function (topic, callback, disableDuringFailover) { 4475 _isInited(); 4476 4477 //Ensure that the same subscription isn't made twice. 4478 if (!_subscriptionID[topic]) { 4479 //Store the subscription ID using the topic name as the key. 4480 _subscriptionID[topic] = _hub.subscribe(topic, 4481 //Invoke the callback just with the data object. 4482 function (topic, data) { 4483 if (!disableDuringFailover || _isMaster || !_failoverMode) { 4484 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 4485 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 4486 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 4487 // - Master instance will get all events regardless, because it is responsible for managing failover 4488 // - If we are not in a failover mode, everything goes 4489 // _refreshObjects will reconcile anything that was missed once we are back in action 4490 callback(data); 4491 } 4492 }); 4493 return true; 4494 } 4495 return false; 4496 }, 4497 4498 /** 4499 * @private 4500 * Unsubscribe from a particular topic. 4501 * @param {String} topic 4502 * The full topic name. 4503 */ 4504 unsubscribe: function (topic) { 4505 _isInited(); 4506 4507 //Unsubscribe from the topic using the subscription ID recorded when 4508 //the subscription was made, then delete the ID from data structure. 4509 if (_subscriptionID[topic]) { 4510 _hub.unsubscribe(_subscriptionID[topic]); 4511 delete _subscriptionID[topic]; 4512 } 4513 }, 4514 4515 /** 4516 * @private 4517 * Make a request to the request channel to have the Master subscribe 4518 * to a node. 4519 * @param {String} node 4520 * The node to subscribe to. 4521 */ 4522 subscribeNode: function (node, handler) { 4523 if (handler && typeof handler !== "function") { 4524 throw new Error("ClientServices.subscribeNode: handler is not a function"); 4525 } 4526 4527 // Construct the request to send to MasterPublisher through the OpenAjax Hub 4528 var data = { 4529 type: "SubscribeNodeReq", 4530 data: {node: node}, 4531 invokeID: _util.generateUUID() 4532 }, 4533 responseTopic = _topics.RESPONSES + "." + data.invokeID, 4534 _this = this; 4535 4536 // We need to first subscribe to the response channel 4537 this.subscribe(responseTopic, function (rsp) { 4538 // Since this channel is only used for this singular request, 4539 // we are not interested anymore. 4540 // This is also critical to not leaking memory by having OpenAjax 4541 // store a bunch of orphaned callback handlers that enclose on 4542 // our entire ClientServices singleton 4543 _this.unsubscribe(responseTopic); 4544 if (handler) { 4545 handler(data.invokeID, rsp); 4546 } 4547 }); 4548 // Then publish the request on the request channel 4549 _hub.publish(_topics.REQUESTS, data); 4550 }, 4551 4552 /** 4553 * @private 4554 * Make a request to the request channel to have the Master unsubscribe 4555 * from a node. 4556 * @param {String} node 4557 * The node to unsubscribe from. 4558 */ 4559 unsubscribeNode: function (node, subid, handler) { 4560 if (handler && typeof handler !== "function") { 4561 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 4562 } 4563 4564 // Construct the request to send to MasterPublisher through the OpenAjax Hub 4565 var data = { 4566 type: "UnsubscribeNodeReq", 4567 data: { 4568 node: node, 4569 subid: subid 4570 }, 4571 invokeID: _util.generateUUID() 4572 }, 4573 responseTopic = _topics.RESPONSES + "." + data.invokeID, 4574 _this = this; 4575 4576 // We need to first subscribe to the response channel 4577 this.subscribe(responseTopic, function (rsp) { 4578 // Since this channel is only used for this singular request, 4579 // we are not interested anymore. 4580 // This is also critical to not leaking memory by having OpenAjax 4581 // store a bunch of orphaned callback handlers that enclose on 4582 // our entire ClientServices singleton 4583 _this.unsubscribe(responseTopic); 4584 if (handler) { 4585 handler(rsp); 4586 } 4587 }); 4588 // Then publish the request on the request channel 4589 _hub.publish(_topics.REQUESTS, data); 4590 }, 4591 4592 /** 4593 * @private 4594 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 4595 */ 4596 makeConnectionReq : function () { 4597 // Disallow others (non-masters) from administering BOSH connections that are not theirs 4598 if (_isMaster && _publisher) { 4599 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 4600 } else { 4601 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 4602 } 4603 }, 4604 4605 /** 4606 * @private 4607 * Set's the global logger for this Client Services instance. 4608 * @param {Object} logger 4609 * Logger object with the following attributes defined:<ul> 4610 * <li><b>log:</b> function (msg) to simply log a message 4611 * </ul> 4612 */ 4613 setLogger: function (logger) { 4614 // We want to check the logger coming in so we don't have to check every time it is called. 4615 if (logger && typeof logger === "object" && typeof logger.log === "function") { 4616 _logger = logger; 4617 } else { 4618 // We are resetting it to an empty object so that _logger.log in .log is falsy. 4619 _logger = {}; 4620 } 4621 }, 4622 4623 /** 4624 * @private 4625 * Centralized logger.log method for external logger 4626 * @param {String} msg 4627 * Message to log 4628 */ 4629 log: _log, 4630 4631 /** 4632 * @class 4633 * Allow clients to make Finesse API requests and consume Finesse events by 4634 * calling a set of exposed functions. The Services layer will do the dirty 4635 * work of establishing a shared BOSH connection (for designated Master 4636 * modules), consuming events for client subscriptions, and constructing API 4637 * requests. 4638 * 4639 * @constructs 4640 */ 4641 _fakeConstuctor: function () { 4642 /* This is here so we can document init() as a method rather than as a constructor. */ 4643 }, 4644 4645 /** 4646 * Initiates the Client Services with the specified config parameters. 4647 * Enabling the Client Services as Master will trigger the establishment 4648 * of a BOSH event connection. 4649 * @param {Object} config 4650 * Configuration object containing properties used for making REST requests:<ul> 4651 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 4652 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 4653 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 4654 * appropriate authorization string is provided, otherwise it is 4655 * required.</li> 4656 * <li><b>password:</b> The password belonging to the user. This is an optional param as 4657 * long as the appropriate authorization string is provided, 4658 * otherwise it is required.</li> 4659 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 4660 * param is provided to allow the ability to hide the password 4661 * param. If provided, the id and the password extracted from this 4662 * string will be used over the config.id and config.password.</li> 4663 * </ul> 4664 * @throws {Error} If required constructor parameter is missing. 4665 * @example 4666 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 4667 */ 4668 init: function (config) { 4669 if (!_inited) { 4670 //Validate the properties within the config object if one is provided. 4671 if (!(typeof config === "object" && 4672 typeof config.host === "string" && config.host.length > 0 && config.restHost && 4673 (typeof config.authorization === "string" || 4674 (typeof config.id === "string" && 4675 typeof config.password === "string")))) { 4676 throw new Error("Config object contains invalid properties."); 4677 } 4678 4679 // Initialize configuration 4680 _config = config; 4681 4682 // Set shortcuts 4683 _util = Utilities; 4684 _topics = Topics; 4685 4686 //TODO: document when this is properly supported 4687 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 4688 _hub = config.hub || gadgets.Hub; 4689 _io = config.io || gadgets.io; 4690 4691 //If the authorization string is provided, then use that to 4692 //extract the ID and the password. Otherwise use the ID and 4693 //password from the respective ID and password params. 4694 if (_config.authorization) { 4695 var creds = _util.getCredentials(_config.authorization); 4696 _config.id = creds.id; 4697 _config.password = creds.password; 4698 } 4699 else { 4700 _config.authorization = _util.b64Encode( 4701 _config.id + ":" + _config.password); 4702 } 4703 4704 _inited = true; 4705 4706 if (_hub) { 4707 //Subscribe to receive connection information. Since it is possible that 4708 //the client comes up after the Master comes up, the client will need 4709 //to make a request to have the Master send the latest connection info. 4710 //It would be possible that all clients get connection info again. 4711 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 4712 _makeConnectionInfoReq(); 4713 } 4714 } 4715 4716 //Return the CS object for object chaining. 4717 return this; 4718 }, 4719 4720 /** 4721 * @private 4722 * Initializes the BOSH component of this ClientServices instance. This establishes 4723 * the BOSH connection and will trigger the registered handlers as the connection 4724 * status changes respectively:<ul> 4725 * <li>registerOnConnectingHandler</li> 4726 * <li>registerOnConnectHandler</li> 4727 * <li>registerOnDisconnectHandler</li> 4728 * <li>registerOnDisconnectingHandler</li> 4729 * <li>registerOnReconnectingHandler</li> 4730 * <li>registerOnUnloadingHandler</li> 4731 * <ul> 4732 * 4733 * @param {Object} config 4734 * An object containing the following (optional) handlers for the request:<ul> 4735 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 4736 * This is used to construct the JID: user@domain.com</li> 4737 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 4738 * Available from the SystemInfo object. 4739 * This is used for creating or removing subscriptions.</li> 4740 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 4741 * </ul> 4742 */ 4743 initBosh: function (config) { 4744 //Validate the properties within the config object if one is provided. 4745 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 4746 throw new Error("Config object contains invalid properties."); 4747 } 4748 4749 // Mixin the required information for establishing the BOSH connection 4750 _config.xmppDomain = config.xmppDomain; 4751 _config.pubsubDomain = config.pubsubDomain; 4752 _config.resource = config.resource; 4753 4754 //Initiate Master launch sequence 4755 _becomeMaster(); 4756 }, 4757 4758 /** 4759 * @private 4760 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 4761 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 4762 * @param {Object} failoverMode 4763 * true if failing, false or something falsy when recovered 4764 */ 4765 setFailoverMode: function (failoverMode) { 4766 if (_isMaster) { 4767 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 4768 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 4769 }); 4770 } 4771 }, 4772 4773 /** 4774 * @private 4775 * Private accessor used to inject mocked private dependencies for unit testing 4776 */ 4777 _getTestObj: function () { 4778 return { 4779 setPublisher: function (publisher) { 4780 _publisher = publisher; 4781 } 4782 }; 4783 } 4784 }; 4785 }()); 4786 4787 window.finesse = window.finesse || {}; 4788 window.finesse.clientservices = window.finesse.clientservices || {}; 4789 window.finesse.clientservices.ClientServices = ClientServices; 4790 4791 return ClientServices; 4792 4793 })); 4794 4795 /** 4796 * The following comment prevents JSLint errors concerning undefined global variables. 4797 * It tells JSLint that these identifiers are defined elsewhere. 4798 */ 4799 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 4800 4801 /** The following comment is to prevent jslint errors about 4802 * using variables before they are defined. 4803 */ 4804 /*global Handlebars */ 4805 4806 /** 4807 * JavaScript class to implement common notification 4808 * functionality. 4809 * 4810 * @requires Class 4811 * @requires finesse.FinesseBase 4812 */ 4813 /** @private */ 4814 (function (factory) { 4815 4816 4817 // Define as an AMD module if possible 4818 if ( typeof define === 'function' && define.amd ) 4819 { 4820 define('restservices/Notifier',['FinesseBase', 4821 'clientservices/ClientServices'], factory ); 4822 } 4823 4824 /* Define using browser globals otherwise 4825 * Prevent multiple instantiations if the script is loaded twice 4826 */ 4827 else 4828 { 4829 factory(finesse.FinesseBase, finesse.clientservices.ClientServices); 4830 } 4831 }(function (FinesseBase, ClientServices) { 4832 var Notifier = FinesseBase.extend({ 4833 /** 4834 * Initializes the notifier object. 4835 */ 4836 init : function () { 4837 this._super(); 4838 this._listenerCallback = []; 4839 }, 4840 4841 /** 4842 * Add a listener. 4843 * 4844 * @param callback_function 4845 * @param scope 4846 * is the callback function to add 4847 */ 4848 addListener : function (callback_function, scope) { 4849 var len = this._listenerCallback.length, i, cb, add = true; 4850 for (i = 0; i < len; i += 1) { 4851 cb = this._listenerCallback[i].callback; 4852 if (cb === callback_function) { 4853 // this callback already exists 4854 add = false; 4855 break; 4856 } 4857 } 4858 if (add) { 4859 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 4860 } 4861 }, 4862 4863 /** 4864 * Remove a listener. 4865 * 4866 * @param callback_function 4867 * is the callback function to remove 4868 * @return {Boolean} true if removed 4869 */ 4870 removeListener : function (callback_function) { 4871 4872 var result = false, len = this._listenerCallback.length, i, cb; 4873 for (i = len - 1; i >= 0; i -=1) { 4874 cb = this._listenerCallback[i].callback; 4875 if (cb === callback_function) { 4876 this._listenerCallback[i] = undefined; 4877 this._listenerCallback.splice(i, 1); 4878 result = true; 4879 break; 4880 } 4881 } 4882 4883 return result; 4884 }, 4885 4886 /** 4887 * Removes all listeners 4888 * @return {undefined} 4889 */ 4890 reset: function () { 4891 this._listenerCallback = []; 4892 }, 4893 4894 /** 4895 * Notify all listeners. 4896 * 4897 * @param obj 4898 * is the object that has changed 4899 */ 4900 notifyListeners : function (obj) { 4901 var len = this._listenerCallback.length, i, callbackFunction, scope; 4902 4903 for (i = 0; i < len; i += 1) { 4904 // Be sure that one bad callback does not prevent other listeners 4905 // from receiving. 4906 try { 4907 callbackFunction = this._listenerCallback[i].callback; 4908 scope = this._listenerCallback[i].scope; 4909 if (typeof callbackFunction === 'function') { 4910 callbackFunction.call(scope, obj); 4911 } 4912 } catch (err) { 4913 ClientServices.log("Exception caught: " + err); 4914 } 4915 } 4916 }, 4917 4918 /** 4919 * Gets a copy of the listeners. 4920 * @return changeListenerCopy (array of callbacks) 4921 */ 4922 getListeners : function () { 4923 var changeListenerCopy = [], len = this._listenerCallback.length, i; 4924 4925 for (i = 0; i < len; i += 1) { 4926 changeListenerCopy.push(this._listenerCallback[i].callback); 4927 } 4928 4929 return changeListenerCopy; 4930 }, 4931 4932 /** 4933 * Verifies that the handler is function. 4934 * @param handler to verify 4935 * @return the handler 4936 * @throws Error if not a function 4937 */ 4938 _isAFunction : function (handler) { 4939 if (handler === undefined || typeof handler === "function") { 4940 return handler; 4941 } else { 4942 throw new Error("handler must be a function"); 4943 } 4944 } 4945 }); 4946 4947 window.finesse = window.finesse || {}; 4948 window.finesse.restservices = window.finesse.restservices || {}; 4949 window.finesse.restservices.Notifier = Notifier; 4950 4951 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 4952 finesse.restservices = finesse.restservices || {}; 4953 4954 return Notifier; 4955 })); 4956 4957 /** 4958 * JavaScript base object that all REST objects should inherit 4959 * from because it encapsulates and provides the common functionality that 4960 * all REST objects need. 4961 * 4962 * @requires finesse.clientservices.ClientServices 4963 * @requires Class 4964 */ 4965 4966 /** @private */ 4967 (function (factory) { 4968 4969 4970 // Define as an AMD module if possible 4971 if ( typeof define === 'function' && define.amd ) 4972 { 4973 define('restservices/RestBase', ["FinesseBase", 4974 "utilities/Utilities", 4975 "restservices/Notifier", 4976 "clientservices/ClientServices", 4977 "clientservices/Topics"], factory ); 4978 } 4979 /* Define using browser globals otherwise 4980 * Prevent multiple instantiations if the script is loaded twice 4981 */ 4982 else 4983 { 4984 factory(finesse.FinesseBase, finesse.utilities.Utilities, 4985 finesse.restservices.Notifier, 4986 finesse.clientservices.ClientServices, 4987 finesse.clientservices.Topics); 4988 } 4989 }(function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 4990 4991 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 4992 4993 doNotLog: false, 4994 4995 /** 4996 * Used by _processUpdate() and restRequest(). 4997 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 4998 * so that one of the callbacks can be fired when a corresponding event is 4999 * received inside _processUpdate(). 5000 * @private 5001 */ 5002 _pendingCallbacks: {}, 5003 5004 /** 5005 * Gets the REST class for the current object. This object throws an 5006 * exception because subtype must implement. 5007 * @throws {Error} because subtype must implement 5008 * @private 5009 */ 5010 getRestClass: function () { 5011 throw new Error("getRestClass(): Not implemented in subtype."); 5012 }, 5013 5014 /** 5015 * Gets the REST type for the current object. This object throws an 5016 * exception because subtype must implement. 5017 * @throws {Error} because subtype must implement. 5018 * @private 5019 */ 5020 getRestType: function () { 5021 throw new Error("getRestType(): Not implemented in subtype."); 5022 }, 5023 5024 /** 5025 * Gets the node path for the current object. This object throws an 5026 * exception because subtype must implement. 5027 * @throws {Error} because subtype must implement. 5028 * @private 5029 */ 5030 getXMPPNodePath: function () { 5031 throw new Error("getXMPPNodePath(): Not implemented in subtype."); 5032 }, 5033 5034 /** 5035 * Boolean function that specifies whether the REST object supports 5036 * requests. True by default. Subclasses should override if false. 5037 * @private 5038 */ 5039 supportsRequests: true, 5040 5041 /** 5042 * Boolean function that specifies whether the REST object supports 5043 * subscriptions. True by default. Subclasses should override if false. 5044 * @private 5045 */ 5046 supportsSubscriptions: true, 5047 5048 /** 5049 * Boolean function that specifies whether the REST object should retain 5050 * a copy of the REST response. False by default. Subclasses should override if true. 5051 * @private 5052 */ 5053 keepRestResponse: false, 5054 5055 /** 5056 * Boolean function that specifies whether the REST object explicitly 5057 * subscribes. False by default. Subclasses should override if true. 5058 * @private 5059 */ 5060 explicitSubscription: false, 5061 5062 /** 5063 * Boolean function that specifies whether subscribing should be 5064 * automatically done at construction. Defaults to true. 5065 * This be overridden at object construction, not by implementing subclasses 5066 * @private 5067 */ 5068 autoSubscribe: true, 5069 5070 /** 5071 * Private reference to default logger 5072 * @private 5073 */ 5074 _logger: { 5075 log: ClientServices.log, 5076 error: ClientServices.log 5077 }, 5078 5079 /** 5080 * @class 5081 * JavaScript representation of a REST object. Also exposes methods to operate 5082 * on the object against the server. This object is typically extended into individual 5083 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 5084 * 5085 * @constructor 5086 * @param {String} id 5087 * The ID that uniquely identifies the REST object. 5088 * @param {Object} callbacks 5089 * An object containing callbacks for instantiation and runtime 5090 * Callback to invoke upon successful instantiation, passes in REST object. 5091 * @param {Function} callbacks.onLoad(this) 5092 * Callback to invoke upon loading the data for the first time. 5093 * @param {Function} callbacks.onChange(this) 5094 * Callback to invoke upon successful update object (PUT) 5095 * @param {Function} callbacks.onAdd(this) 5096 * Callback to invoke upon successful update to add object (POST) 5097 * @param {Function} callbacks.onDelete(this) 5098 * Callback to invoke upon successful update to delete object (DELETE) 5099 * @param {Function} callbacks.onError(rsp) 5100 * Callback to invoke on update error (refresh or event) 5101 * as passed by finesse.restservices.RestBase.restRequest() 5102 * { 5103 * status: {Number} The HTTP status code returned 5104 * content: {String} Raw string of response 5105 * object: {Object} Parsed object of response 5106 * error: {Object} Wrapped exception that was caught 5107 * error.errorType: {String} Type of error that was caught 5108 * error.errorMessage: {String} Message associated with error 5109 * } 5110 * @param {RestBase} [restObj] 5111 * A RestBase parent object which this object has an association with. 5112 * @constructs 5113 */ 5114 init: function (options, callbacks, restObj) { 5115 /** 5116 * Initialize the base class 5117 */ 5118 var _this = this; 5119 5120 this._super(); 5121 5122 if (typeof options === "object") { 5123 this._id = options.id; 5124 this._restObj = options.parentObj; 5125 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 5126 this.doNotSubscribe = options.doNotSubscribe; 5127 callbacks = { 5128 onLoad: options.onLoad, 5129 onChange: options.onChange, 5130 onAdd: options.onAdd, 5131 onDelete: options.onDelete, 5132 onError: options.onError 5133 }; 5134 } else { 5135 this._id = options; 5136 this._restObj = restObj; 5137 } 5138 5139 // Common stuff 5140 5141 this._data = {}; 5142 5143 //Contains the full rest response to be processed by upper layers if needed 5144 this._restResponse = undefined; 5145 5146 this._lastUpdate = {}; 5147 5148 this._util = Utilities; 5149 5150 //Should be correctly initialized in either a window OR gadget context 5151 this._config = finesse.container.Config; 5152 5153 // Setup all the notifiers - change, load and error. 5154 this._changeNotifier = new Notifier(); 5155 this._loadNotifier = new Notifier(); 5156 this._addNotifier = new Notifier(); 5157 this._deleteNotifier = new Notifier(); 5158 this._errorNotifier = new Notifier(); 5159 5160 this._loaded = false; 5161 5162 // Protect against null dereferencing of options allowing its 5163 // (nonexistent) keys to be read as undefined 5164 callbacks = callbacks || {}; 5165 5166 this.addHandler('load', callbacks.onLoad); 5167 this.addHandler('change', callbacks.onChange); 5168 this.addHandler('add', callbacks.onAdd); 5169 this.addHandler('delete', callbacks.onDelete); 5170 this.addHandler('error', callbacks.onError); 5171 5172 // Attempt to get the RestType then synchronize 5173 try { 5174 this.getRestType(); 5175 5176 // Only subscribe if this REST object supports subscriptions 5177 // and autoSubscribe was not requested to be disabled as a construction option 5178 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 5179 this.subscribe({ 5180 success: function () { 5181 //TODO: figure out how to use Function.call() or Function.apply() here... 5182 //this is exactly the same as the below else case other than the scope of "this" 5183 if (typeof options === "object" && options.data) { 5184 if (!_this._processObject(_this._normalize(options.data))) { 5185 // notify of error if we fail to construct 5186 _this._errorNotifier.notifyListeners(_this); 5187 } 5188 } else { 5189 // Only subscribe if this REST object supports requests 5190 if (_this.supportsRequests) { 5191 _this._synchronize(); 5192 } 5193 } 5194 }, 5195 error: function (err) { 5196 _this._errorNotifier.notifyListeners(err); 5197 } 5198 }); 5199 } else { 5200 if (typeof options === "object" && options.data) { 5201 if (!this._processObject(this._normalize(options.data))) { 5202 // notify of error if we fail to construct 5203 this._errorNotifier.notifyListeners(this); 5204 } 5205 } else { 5206 // Only subscribe if this REST object supports requests 5207 if (this.supportsRequests) { 5208 this._synchronize(); 5209 } 5210 } 5211 } 5212 5213 } catch (err) { 5214 this._logger.error('id=' + this._id + ': ' + err); 5215 } 5216 }, 5217 5218 /** 5219 * Determines if the object has a particular property. 5220 * @param obj is the object to examine 5221 * @param property is the property to check for 5222 * @returns {Boolean} 5223 */ 5224 hasProperty: function (obj, prop) { 5225 return (obj !== null) && (obj.hasOwnProperty(prop)); 5226 }, 5227 5228 /** 5229 * Gets a property from the object. 5230 * @param obj is the object to examine 5231 * @param property is the property to get 5232 * @returns {Property Value} or {Null} if not found 5233 */ 5234 getProperty: function (obj, property) { 5235 var result = null; 5236 5237 if (this.hasProperty(obj, property) === false) { 5238 result = null; 5239 } else { 5240 result = obj[property]; 5241 } 5242 return result; 5243 }, 5244 5245 /** 5246 * Utility to extracts the ID from the specified REST URI. This is with the 5247 * assumption that the ID is always the last element in the URI after the 5248 * "/" delimiter. 5249 * @param {String} restUri 5250 * The REST uri (i.e. /finesse/api/User/1000). 5251 * @private 5252 */ 5253 _extractId: function (restObj) { 5254 var obj, restUri = "", strLoc; 5255 for (obj in restObj) { 5256 if (restObj.hasOwnProperty(obj)) { 5257 restUri = restObj[obj].uri; 5258 break; 5259 } 5260 } 5261 return Utilities.getId(restUri); 5262 }, 5263 5264 /** 5265 * Gets the data for this object. 5266 * @returns {Object} which is contained in data 5267 */ 5268 getData: function () { 5269 return this._data; 5270 }, 5271 5272 /** 5273 * Gets the complete REST response to the request made 5274 * @returns {Object} which is contained in data 5275 * @private 5276 */ 5277 getRestResponse: function () { 5278 return this._restResponse; 5279 }, 5280 5281 /** 5282 * The REST URL in which this object can be referenced. 5283 * @return {String} 5284 * The REST URI for this object. 5285 * @private 5286 */ 5287 getRestUrl: function () { 5288 var 5289 restObj = this._restObj, 5290 restUrl = ""; 5291 5292 //Prepend the base REST object if one was provided. 5293 if (restObj instanceof RestBase) { 5294 restUrl += restObj.getRestUrl(); 5295 } 5296 //Otherwise prepend with the default webapp name. 5297 else { 5298 restUrl += "/finesse/api"; 5299 } 5300 5301 //Append the REST type. 5302 restUrl += "/" + this.getRestType(); 5303 5304 //Append ID if it is not undefined, null, or empty. 5305 if (this._id) { 5306 restUrl += "/" + this._id; 5307 } 5308 return restUrl; 5309 }, 5310 5311 /** 5312 * Getter for the id of this RestBase 5313 * @returns {String} 5314 * The id of this RestBase 5315 */ 5316 getId: function () { 5317 return this._id; 5318 }, 5319 5320 /** 5321 * Synchronize this object with the server using REST GET request. 5322 * @private 5323 */ 5324 _synchronize: function (retries) { 5325 // Fetch this REST object 5326 if (typeof this._id === "string") { 5327 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 5328 5329 this._doGET( 5330 { 5331 success: function (rsp) { 5332 if (!_this._processResponse(rsp)) { 5333 if (retries > 0) { 5334 setTimeout(function () { 5335 _this._synchronize(retries - 1); 5336 }, _RETRY_INTERVAL); 5337 } else { 5338 _this._errorNotifier.notifyListeners(_this); 5339 } 5340 } else { 5341 // If this object was already "loaded" prior to 5342 // the _doGET request, then call the 5343 // changeNotifier 5344 if (isLoaded) { 5345 _this._changeNotifier.notifyListeners(_this); 5346 } 5347 } 5348 }, 5349 error: function (rsp) { 5350 if (retries > 0) { 5351 setTimeout(function () { 5352 _this._synchronize(retries - 1); 5353 }, _RETRY_INTERVAL); 5354 5355 } else { 5356 _this._errorNotifier.notifyListeners(rsp); 5357 } 5358 } 5359 } 5360 ); 5361 5362 } else { 5363 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 5364 } 5365 }, 5366 5367 /** 5368 * Adds an handler to this object. 5369 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 5370 * @param {String} notifierType 5371 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 5372 * @param {Function} callback 5373 * The function callback to invoke. 5374 * @example 5375 * // Handler for additions to the Dialogs collection object. 5376 * // When Dialog (a RestBase object) is created, add a change handler. 5377 * _handleDialogAdd = function(dialog) { 5378 * dialog.addHandler('change', _handleDialogChange); 5379 * } 5380 */ 5381 addHandler: function (notifierType, callback, scope) { 5382 var notifier = null; 5383 try { 5384 Utilities.validateHandler(callback); 5385 5386 notifier = this._getNotifierReference(notifierType); 5387 5388 notifier.addListener(callback, scope); 5389 5390 // If load handler is added and object has 5391 // already been loaded, invoke callback 5392 // immediately 5393 if (notifierType === 'load' && this._loaded) { 5394 callback.call((scope || window), this); 5395 } 5396 } catch (err) { 5397 this._logger.error('id=' + this._id + ': ' + err); 5398 } 5399 }, 5400 5401 /** 5402 * Removes a handler from this object. 5403 * @param {String} notifierType 5404 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 5405 * @param {Function} callback 5406 * The function to remove. 5407 */ 5408 removeHandler: function (notifierType, callback) { 5409 var notifier = null; 5410 try { 5411 Utilities.validateHandler(callback); 5412 5413 notifier = this._getNotifierReference(notifierType); 5414 5415 if (typeof(callback) === "undefined") 5416 { 5417 // Remove all listeners for the type 5418 notifier.reset(); 5419 } 5420 else 5421 { 5422 // Remove the specified listener 5423 finesse.utilities.Utilities.validateHandler(callback); 5424 notifier.removeListener(callback); 5425 } 5426 } catch (err) { 5427 this._logger.error('id=' + this._id + ': ' + err); 5428 } 5429 }, 5430 5431 /** 5432 * Utility method gating any operations that require complete instantiation 5433 * @throws Error 5434 * If this object was not fully instantiated yet 5435 * @returns {finesse.restservices.RestBase} 5436 * This RestBase object to allow cascading 5437 */ 5438 isLoaded: function () { 5439 if (!this._loaded) { 5440 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 5441 } 5442 return this; // Allow cascading 5443 }, 5444 5445 5446 /** 5447 * Force an update on this object. Since an asynchronous GET is performed, 5448 * it is necessary to have an onChange handler registered in order to be 5449 * notified when the response of this returns. 5450 * @param {Integer} retries 5451 * The number or retry attempts to make. 5452 * @returns {finesse.restservices.RestBase} 5453 * This RestBase object to allow cascading 5454 */ 5455 refresh: function (retries) { 5456 //Disallow GETs if object doesn't support it. 5457 if (!this.supportsRequests) { 5458 throw new Error("Object doesn't support request operations."); 5459 } 5460 5461 this._synchronize(retries); 5462 5463 return this; // Allow cascading 5464 }, 5465 5466 /** 5467 * Utility method to validate against the known schema of this RestBase 5468 * @param {Object} obj 5469 * The object to validate 5470 * @returns {Boolean} 5471 * True if the object fits the schema of this object. This usually 5472 * means all required keys or nested objects are present. 5473 * False otherwise. 5474 * @private 5475 */ 5476 _validate: function (obj) { 5477 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 5478 if (!valid) 5479 { 5480 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 5481 } 5482 return valid; 5483 }, 5484 5485 /** 5486 * Utility method to fetch this RestBase from the server 5487 * @param {finesse.interfaces.RequestHandlers} handlers 5488 * An object containing the handlers for the request 5489 * @returns {finesse.restservices.RestBase} 5490 * This RestBase object to allow cascading 5491 * @private 5492 */ 5493 _doGET: function (handlers) { 5494 this.restRequest(this.getRestUrl(), handlers); 5495 return this; // Allow cascading 5496 }, 5497 5498 /** 5499 * Common update event handler used by the pubsub callback closure. 5500 * Processes the update event then notifies listeners. 5501 * @param {Object} scope 5502 * An object containing callbacks to handle the asynchronous get 5503 * @param {Object} update 5504 * An object containing callbacks to handle the asynchronous get 5505 * @private 5506 */ 5507 _updateEventHandler: function (scope, update) { 5508 if (scope._processUpdate(update)) { 5509 switch (update.object.Update.event) { 5510 case "POST": 5511 scope._addNotifier.notifyListeners(scope); 5512 break; 5513 case "PUT": 5514 scope._changeNotifier.notifyListeners(scope); 5515 break; 5516 case "DELETE": 5517 scope._deleteNotifier.notifyListeners(scope); 5518 break; 5519 } 5520 } 5521 }, 5522 5523 /** 5524 * Utility method to create a callback to be given to OpenAjax to invoke when a message 5525 * is published on the topic of our REST URL (also XEP-0060 node). 5526 * This needs to be its own defined method so that subclasses can have their own implementation. 5527 * @returns {Function} callback(update) 5528 * The callback to be invoked when an update event is received. This callback will 5529 * process the update and notify listeners. 5530 * @private 5531 */ 5532 _createPubsubCallback: function () { 5533 var _this = this; 5534 return function (update) { 5535 _this._updateEventHandler(_this, update); 5536 }; 5537 }, 5538 5539 /** 5540 * Subscribe to pubsub infra using the REST URL as the topic name. 5541 * @param {finesse.interfaces.RequestHandlers} handlers 5542 * An object containing the handlers for the request 5543 * @private 5544 */ 5545 subscribe: function (callbacks) { 5546 // Only need to do a subscription to client pubsub. No need to trigger 5547 // a subscription on the Finesse server due to implicit subscribe (at 5548 // least for now). 5549 var _this = this, 5550 topic = Topics.getTopic(this.getRestUrl()), 5551 handlers, 5552 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 5553 5554 callbacks = callbacks || {}; 5555 5556 handlers = { 5557 /** @private */ 5558 success: function () { 5559 // Add item to the refresh list in ClientServices to refresh if 5560 // we recover due to our resilient connection. However, do 5561 // not add if doNotRefresh flag is set. 5562 if (!_this.doNotRefresh) { 5563 ClientServices.addToRefreshList(_this); 5564 } 5565 5566 if (typeof callbacks.success === "function") { 5567 callbacks.success(); 5568 } 5569 }, 5570 /** @private */ 5571 error: function (err) { 5572 if (successful) { 5573 ClientServices.unsubscribe(topic); 5574 } 5575 5576 if (typeof callbacks.error === "function") { 5577 callbacks.error(err); 5578 } 5579 } 5580 }; 5581 5582 // Request a node subscription only if this object requires explicit subscriptions 5583 if (this.explicitSubscription === true) { 5584 this._subscribeNode(handlers); 5585 } else { 5586 if (successful) { 5587 this._subid = "OpenAjaxOnly"; 5588 handlers.success(); 5589 } else { 5590 handlers.error(); 5591 } 5592 } 5593 5594 return this; 5595 }, 5596 5597 /** 5598 * Unsubscribe to pubsub infra using the REST URL as the topic name. 5599 * @param {finesse.interfaces.RequestHandlers} handlers 5600 * An object containing the handlers for the request 5601 * @private 5602 */ 5603 unsubscribe: function (callbacks) { 5604 // Only need to do a subscription to client pubsub. No need to trigger 5605 // a subscription on the Finesse server due to implicit subscribe (at 5606 // least for now). 5607 var _this = this, 5608 topic = Topics.getTopic(this.getRestUrl()), 5609 handlers; 5610 5611 // no longer keep track of object to refresh on reconnect 5612 ClientServices.removeFromRefreshList(_this); 5613 5614 callbacks = callbacks || {}; 5615 5616 handlers = { 5617 /** @private */ 5618 success: function () { 5619 if (typeof callbacks.success === "function") { 5620 callbacks.success(); 5621 } 5622 }, 5623 /** @private */ 5624 error: function (err) { 5625 if (typeof callbacks.error === "function") { 5626 callbacks.error(err); 5627 } 5628 } 5629 }; 5630 5631 if (this._subid) { 5632 ClientServices.unsubscribe(topic); 5633 // Request a node unsubscribe only if this object requires explicit subscriptions 5634 if (this.explicitSubscription === true) { 5635 this._unsubscribeNode(handlers); 5636 } else { 5637 this._subid = undefined; 5638 handlers.success(); 5639 } 5640 } else { 5641 handlers.success(); 5642 } 5643 5644 return this; 5645 }, 5646 5647 /** 5648 * Private utility to perform node subscribe requests for explicit subscriptions 5649 * @param {finesse.interfaces.RequestHandlers} handlers 5650 * An object containing the handlers for the request 5651 * @private 5652 */ 5653 _subscribeNode: function (callbacks) { 5654 var _this = this; 5655 5656 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 5657 callbacks = callbacks || {}; 5658 5659 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 5660 if (err) { 5661 if (typeof callbacks.error === "function") { 5662 callbacks.error(err); 5663 } 5664 } else { 5665 // Store the subid on a successful subscribe 5666 _this._subid = subid; 5667 if (typeof callbacks.success === "function") { 5668 callbacks.success(); 5669 } 5670 } 5671 }); 5672 }, 5673 5674 /** 5675 * Private utility to perform node unsubscribe requests for explicit subscriptions 5676 * @param {finesse.interfaces.RequestHandlers} handlers 5677 * An object containing the handlers for the request 5678 * @private 5679 */ 5680 _unsubscribeNode: function (callbacks) { 5681 var _this = this; 5682 5683 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 5684 callbacks = callbacks || {}; 5685 5686 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 5687 _this._subid = undefined; 5688 if (err) { 5689 if (typeof callbacks.error === "function") { 5690 callbacks.error(err); 5691 } 5692 } else { 5693 if (typeof callbacks.success === "function") { 5694 callbacks.success(); 5695 } 5696 } 5697 }); 5698 }, 5699 5700 /** 5701 * Validate and store the object into the internal data store. 5702 * @param {Object} object 5703 * The JavaScript object that should match of schema of this REST object. 5704 * @returns {Boolean} 5705 * True if the object was validated and stored successfully. 5706 * @private 5707 */ 5708 _processObject: function (object) { 5709 if (this._validate(object)) { 5710 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 5711 5712 // If loaded for the first time, call the load notifiers. 5713 if (!this._loaded) { 5714 this._loaded = true; 5715 this._loadNotifier.notifyListeners(this); 5716 } 5717 5718 return true; 5719 } 5720 return false; 5721 }, 5722 5723 /** 5724 * Normalize the object to mitigate the differences between the backend 5725 * and what this REST object should hold. For example, the backend sends 5726 * send an event with the root property name being lower case. In order to 5727 * match the GET, the property should be normalized to an upper case. 5728 * @param {Object} object 5729 * The object which should be normalized. 5730 * @returns {Object} 5731 * Return the normalized object. 5732 * @private 5733 */ 5734 _normalize: function (object) { 5735 var 5736 restType = this.getRestType(), 5737 // Get the REST object name with first character being lower case. 5738 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 5739 5740 // Normalize payload to match REST object. The payload for an update 5741 // use a lower case object name as oppose to upper case. Only normalize 5742 // if necessary. 5743 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 5744 //Since the object is going to be modified, clone the object so that 5745 //it doesn't affect others (due to OpenAjax publishing to other 5746 //subscriber. 5747 object = jQuery.extend(true, {}, object); 5748 5749 object[restType] = object[objRestType]; 5750 delete(object[objRestType]); 5751 } 5752 return object; 5753 }, 5754 5755 /** 5756 * Utility method to process the response of a successful get 5757 * @param {Object} rsp 5758 * The response of a successful get 5759 * @returns {Boolean} 5760 * True if the update was successfully processed (the response object 5761 * passed the schema validation) and updated the internal data cache, 5762 * false otherwise. 5763 * @private 5764 */ 5765 _processResponse: function (rsp) { 5766 try { 5767 if (this.keepRestResponse) { 5768 this._restResponse = rsp.content; 5769 } 5770 return this._processObject(rsp.object); 5771 } 5772 catch (err) { 5773 this._logger.error(this.getRestType() + ': ' + err); 5774 } 5775 return false; 5776 }, 5777 5778 /** 5779 * Utility method to process the update notification. 5780 * @param {Object} update 5781 * The payload of an update notification. 5782 * @returns {Boolean} 5783 * True if the update was successfully processed (the update object 5784 * passed the schema validation) and updated the internal data cache, 5785 * false otherwise. 5786 * @private 5787 */ 5788 _processUpdate: function (update) { 5789 try { 5790 var updateObj, requestId, fakeResponse, receivedError; 5791 5792 // The backend will send the data object with a lower case. To be 5793 // consistent with what should be represented in this object, the 5794 // object name should be upper case. This will normalize the object. 5795 updateObj = this._normalize(update.object.Update.data); 5796 5797 // Store the last event. 5798 this._lastUpdate = update.object; 5799 5800 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 5801 5802 if (requestId && this._pendingCallbacks[requestId]) { 5803 5804 /* 5805 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 5806 * a simulated/"fake" AJAX response object from the information in the received event. 5807 * The constructed object should conform to the contract for response objects specified 5808 * in _createAjaxHandler(). 5809 */ 5810 fakeResponse = {}; 5811 5812 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 5813 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 5814 //doing a parse(stringify(update)). 5815 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 5816 5817 fakeResponse.object = {}; 5818 5819 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 5820 5821 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 5822 receivedError = updateObj.apiErrors.apiError; 5823 fakeResponse.object.ApiErrors = {}; 5824 fakeResponse.object.ApiErrors.ApiError = {}; 5825 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 5826 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 5827 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 5828 5829 /* 5830 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 5831 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 5832 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 5833 **/ 5834 fakeResponse.status = 400; 5835 5836 } else { //Success case 5837 5838 fakeResponse.object = this._lastUpdate; 5839 5840 /* 5841 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 5842 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 5843 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 5844 **/ 5845 fakeResponse.status = 200; 5846 } 5847 5848 try { 5849 5850 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 5851 this._pendingCallbacks[requestId].error(fakeResponse); 5852 } 5853 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 5854 /*else if (this._pendingCallbacks[requestId].success) { 5855 this._pendingCallbacks[requestId].success(fakeResponse); 5856 }*/ 5857 5858 } catch (callbackErr) { 5859 5860 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 5861 5862 } 5863 5864 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 5865 delete this._pendingCallbacks[requestId]; 5866 5867 } else { 5868 this._logger.log(this.getRestType() + ": Received the following event with an invalid or unknown requestId:"); 5869 this._logger.log(gadgets.json.stringify(update)); 5870 } 5871 5872 return this._processObject(updateObj); 5873 } 5874 catch (err) { 5875 this._logger.error(this.getRestType() + ': ' + err); 5876 } 5877 return false; 5878 }, 5879 5880 /** 5881 * Utility method to create ajax response handler closures around the 5882 * provided callbacks. Callbacks should be passed through from .ajax(). 5883 * makeRequest is responsible for garbage collecting these closures. 5884 * @param {finesse.interfaces.RequestHandlers} handlers 5885 * An object containing the handlers for the request 5886 * @private 5887 */ 5888 _createAjaxHandler: function (options) { 5889 //We should not need to check this again since it has already been done in .restRequest() 5890 //options = options || {}; 5891 5892 //Get a reference to the parent User object 5893 var _this = this; 5894 5895 return function (rsp) { 5896 5897 var requestId, error = false, rspObj; 5898 5899 if (options.success || options.error) { 5900 rspObj = { 5901 status: rsp.rc, 5902 content: rsp.text 5903 }; 5904 5905 if (!_this.doNotLog) { 5906 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "'"); 5907 } 5908 5909 //Some responses may not have a body. 5910 if (rsp.text && rsp.text.length > 0) { 5911 try { 5912 rspObj.object = _this._util.xml2js(rsp.text); 5913 } catch (e) { 5914 error = true; 5915 rspObj.error = { 5916 errorType: "parseError", 5917 errorMessage: "Could not serialize XML: " + e 5918 }; 5919 } 5920 } else { 5921 rspObj.object = {}; 5922 } 5923 5924 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 5925 if (options.success) { 5926 options.success(rspObj); 5927 } 5928 } else { 5929 if (options.error) { 5930 options.error(rspObj); 5931 } 5932 } 5933 5934 /* 5935 * If a synchronous error happened after a non-GET request (usually a validation error), we 5936 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 5937 * will arrive later. The corresponding requestId should be present in the response headers. 5938 * 5939 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 5940 * 'requestId' below. 5941 **/ 5942 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 5943 requestId = rsp.headers.requestid[0]; 5944 if (_this._pendingCallbacks[requestId]) { 5945 delete _this._pendingCallbacks[requestId]; 5946 } 5947 } 5948 } 5949 }; 5950 }, 5951 5952 /** 5953 * Utility method to make an asynchronous request 5954 * @param {String} url 5955 * The unencoded URL to which the request is sent (will be encoded) 5956 * @param {Object} options 5957 * An object containing additional options for the request. 5958 * @param {Object} options.content 5959 * An object to send in the content body of the request. Will be 5960 * serialized into XML before sending. 5961 * @param {String} options.method 5962 * The type of request. Defaults to "GET" when none is specified. 5963 * @param {Function} options.success(rsp) 5964 * A callback function to be invoked for a successful request. 5965 * { 5966 * status: {Number} The HTTP status code returned 5967 * content: {String} Raw string of response 5968 * object: {Object} Parsed object of response 5969 * } 5970 * @param {Function} options.error(rsp) 5971 * A callback function to be invoked for an unsuccessful request. 5972 * { 5973 * status: {Number} The HTTP status code returned 5974 * content: {String} Raw string of response 5975 * object: {Object} Parsed object of response 5976 * error: {Object} Wrapped exception that was caught 5977 * error.errorType: {String} Type of error that was caught 5978 * error.errorMessage: {String} Message associated with error 5979 * } 5980 * @private 5981 */ 5982 restRequest: function (url, options) { 5983 5984 var params, encodedUrl; 5985 5986 params = {}; 5987 5988 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 5989 options = options || {}; 5990 options.success = this._util.validateHandler(options.success); 5991 options.error = this._util.validateHandler(options.error); 5992 5993 // Request Headers 5994 params[gadgets.io.RequestParameters.HEADERS] = {}; 5995 5996 // HTTP method is a passthrough to gadgets.io.makeRequest, makeRequest defaults to GET 5997 params[gadgets.io.RequestParameters.METHOD] = options.method; 5998 5999 //true if this should be a GET request, false otherwise 6000 if (!options.method || options.method === "GET") { 6001 //Disable caching for GETs 6002 if (url.indexOf("?") > -1) { 6003 url += "&"; 6004 } else { 6005 url += "?"; 6006 } 6007 url += "nocache=" + this._util.currentTimeMillis(); 6008 } else { 6009 /** 6010 * If not GET, generate a requestID and add it to the headers, then wrap 6011 * callbacks into an object and store it in _pendingCallbacks. 6012 * If we receive a synchronous error response instead of a 202 as expected, 6013 * the AJAX handler will clean up _pendingCallbacks. 6014 **/ 6015 /* 6016 * TODO: Clean up _pendingCallbacks if an entry persists after a certain amount of time has passed. 6017 * In the block below, can store the current time (new Date().getTime()) alongside the 6018 * callbacks in the new _pendingCallbacks entry. Then iterate through a copty of _pendingCallbacks, 6019 * deleting all entries inside _pendingCallbacks that are older than a certain threshold (2 minutes for example.) 6020 * This solves a potential memory leak issue if we never receive an event for a given stored requestId; 6021 * we don't want to store unfired callbacks forever. 6022 */ 6023 /** @private */ 6024 options.uuid = this._util.generateUUID(); 6025 params[gadgets.io.RequestParameters.HEADERS].requestId = options.uuid; 6026 //By default, Shindig strips nearly all of the response headers, but this parameter tells Shindig 6027 //to send the headers through unmodified; we need to be able to read the 'requestId' header if we 6028 //get a synchronous error as a result of a non-GET request. (See the bottom of _createAjaxHandler().) 6029 params[gadgets.io.RequestParameters.GET_FULL_HEADERS] = "true"; 6030 this._pendingCallbacks[options.uuid] = {}; 6031 this._pendingCallbacks[options.uuid].success = options.success; 6032 this._pendingCallbacks[options.uuid].error = options.error; 6033 } 6034 6035 //debugger; 6036 // NAT: IGNORE this until you hit save after changing assignments, then 6037 // pause here and set window.errorOnRequest to true, step past the next line, 6038 // and then set it to false. True value will throw an error when saving assignments. 6039 encodedUrl = encodeURI(url) + (window.errorOnRestRequest ? "ERROR" : ""); 6040 6041 if (!this.doNotLog) { 6042 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', Making REST request: method=" + (options.method || "GET") + ", url='" + encodedUrl + "'"); 6043 } 6044 6045 // Content Body 6046 if (typeof options.content === "object") { 6047 // Content Type 6048 params[gadgets.io.RequestParameters.HEADERS]["Content-Type"] = "application/xml"; 6049 // Content 6050 params[gadgets.io.RequestParameters.POST_DATA] = this._util.js2xml(options.content); 6051 6052 if (!this.doNotLog) { 6053 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', POST_DATA='" + params[gadgets.io.RequestParameters.POST_DATA] + "'"); 6054 } 6055 } 6056 6057 ClientServices.makeRequest(encodedUrl, this._createAjaxHandler(options), params); 6058 }, 6059 6060 /** 6061 * Retrieves a reference to a particular notifierType. 6062 * @param notifierType is a string which indicates the notifier to retrieve 6063 * ('load', 'change', 'add', 'delete', 'error') 6064 * @return {Notifier} 6065 * @private 6066 */ 6067 _getNotifierReference: function (notifierType) { 6068 var notifierReference = null; 6069 if (notifierType === 'load') { 6070 notifierReference = this._loadNotifier; 6071 } else if (notifierType === 'change') { 6072 notifierReference = this._changeNotifier; 6073 } else if (notifierType === 'add') { 6074 notifierReference = this._addNotifier; 6075 } else if (notifierType === 'delete') { 6076 notifierReference = this._deleteNotifier; 6077 } else if (notifierType === 'error') { 6078 notifierReference = this._errorNotifier; 6079 } else { 6080 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 6081 } 6082 6083 return notifierReference; 6084 } 6085 }); 6086 6087 window.finesse = window.finesse || {}; 6088 window.finesse.restservices = window.finesse.restservices || {}; 6089 window.finesse.restservices.RestBase = RestBase; 6090 6091 return RestBase; 6092 })); 6093 6094 /** The following comment is to prevent jslint errors about 6095 * using variables before they are defined. 6096 */ 6097 /*global finesse*/ 6098 6099 /** 6100 * JavaScript base object that all REST collection objects should 6101 * inherit from because it encapsulates and provides the common functionality 6102 * that all REST objects need. 6103 * 6104 * @requires finesse.clientservices.ClientServices 6105 * @requires Class 6106 * @requires finesse.FinesseBase 6107 * @requires finesse.restservices.RestBase 6108 */ 6109 6110 /** 6111 * @class 6112 * JavaScript representation of a REST collection object. 6113 * 6114 * @constructor 6115 * @param {Function} callbacks.onCollectionAdd(this) 6116 * Callback to invoke upon successful item addition to the collection. 6117 * @param {Function} callbacks.onCollectionDelete(this) 6118 * Callback to invoke upon successful item deletion from the collection. 6119 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 6120 */ 6121 /** @private */ 6122 (function (factory) { 6123 6124 6125 // Define as an AMD module if possible 6126 if ( typeof define === 'function' && define.amd ) 6127 { 6128 define('restservices/RestCollectionBase', ['restservices/RestBase', 6129 'utilities/Utilities', 6130 'restservices/Notifier'], factory ); 6131 } 6132 /* Define using browser globals otherwise 6133 * Prevent multiple instantiations if the script is loaded twice 6134 */ 6135 else 6136 { 6137 factory(finesse.restservices.RestBase, finesse.utilities.Utilities, finesse.restservices.Notifier); 6138 } 6139 }(function (RestBase, Utilities, Notifier) { 6140 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 6141 6142 /** 6143 * Boolean function that specifies whether the collection handles subscribing 6144 * and propagation of events for the individual REST object items the 6145 * collection holds. False by default. Subclasses should override if true. 6146 * @private 6147 */ 6148 supportsRestItemSubscriptions: false, 6149 6150 /** 6151 * Gets the constructor the individual items that make of the collection. 6152 * For example, a Dialogs collection object will hold a list of Dialog items. 6153 * @throws Error because subtype must implement. 6154 * @private 6155 */ 6156 getRestItemClass: function () { 6157 throw new Error("getRestItemClass(): Not implemented in subtype."); 6158 }, 6159 6160 /** 6161 * Gets the REST type of the individual items that make of the collection. 6162 * For example, a Dialogs collection object will hold a list of Dialog items. 6163 * @throws Error because subtype must implement. 6164 * @private 6165 */ 6166 getRestItemType: function () { 6167 throw new Error("getRestItemType(): Not implemented in subtype."); 6168 }, 6169 6170 /** 6171 * The base REST URL in which items this object contains can be referenced. 6172 * @return {String} 6173 * The REST URI for items this object contains. 6174 * @private 6175 */ 6176 getRestItemBaseUrl: function () { 6177 var 6178 restUrl = "/finesse/api"; 6179 6180 //Append the REST type. 6181 restUrl += "/" + this.getRestItemType(); 6182 6183 return restUrl; 6184 }, 6185 6186 /* 6187 * Creates a new object from the given data 6188 * @param data - data object 6189 * @private 6190 */ 6191 _objectCreator: function (data) { 6192 var objectId = this._extractId(data), 6193 newRestObj = this._collection[objectId], 6194 _this = this; 6195 6196 //Prevent duplicate entries into collection. 6197 if (!newRestObj) { 6198 //Create a new REST object using the subtype defined by the 6199 //overridden method. 6200 newRestObj = new (this.getRestItemClass())({ 6201 doNotSubscribe: this.handlesItemSubscription, 6202 id: objectId, 6203 data: data, 6204 onLoad: function (newObj) { 6205 //Normalize and add REST object to collection datastore. 6206 _this._collection[objectId] = newObj; 6207 _this._collectionAddNotifier.notifyListeners(newObj); 6208 _this.length += 1; 6209 } 6210 }); 6211 } 6212 else { 6213 //If entry already exist in collection, process the new event, 6214 //and notify all change listeners since an existing object has 6215 //change. This could happen in the case when the Finesse server 6216 //cycles, and sends a snapshot of the user's calls. 6217 newRestObj._processObject(data); 6218 newRestObj._changeNotifier.notifyListeners(newRestObj); 6219 } 6220 }, 6221 6222 /* 6223 * Deletes and object and notifies its handlers 6224 * @param data - data object 6225 * @private 6226 */ 6227 _objectDeleter: function (data) { 6228 var objectId = this._extractId(data), 6229 object = this._collection[objectId]; 6230 if (object) { 6231 //Even though this is a delete, let's make sure the object we are passing has got good data 6232 object._processObject(data); 6233 //Notify listeners and delete from internal datastore. 6234 this._collectionDeleteNotifier.notifyListeners(object); 6235 delete this._collection[objectId]; 6236 this.length -= 1; 6237 } 6238 }, 6239 6240 /** 6241 * Replaces the collection with a refreshed list using the passed in 6242 * data. 6243 * @param data - data object (usually this._data) 6244 * @private 6245 */ 6246 _buildRefreshedCollection: function (data) { 6247 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 6248 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 6249 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 6250 } else { 6251 dataArray = []; 6252 } 6253 6254 // iterate through each item in the new data and add to or update collection 6255 for (i = 0; i < dataArray.length; i += 1) { 6256 dataObject = {}; 6257 dataObject[this.getRestItemType()] = dataArray[i]; 6258 objectId = this._extractId(dataObject); 6259 6260 this._objectCreator(dataObject); 6261 newIds.push(objectId); 6262 6263 // resubscribe if the object requires an explicit subscription 6264 object = this._collection[objectId]; 6265 if (object.explicitSubscription) { 6266 object._subscribeNode(); 6267 } 6268 } 6269 6270 // now clean up items (if any) that were removed 6271 for (objectId in this._collection) { 6272 if (this._collection.hasOwnProperty(objectId)) { 6273 foundFlag = false; 6274 for (i = newIds.length - 1; i >= 0; i -= 1) { 6275 if (newIds[i] === objectId) { 6276 foundFlag = true; 6277 break; 6278 } 6279 } 6280 // did not find in updated list, so delete it 6281 if (!foundFlag) { 6282 this._objectDeleter({'data': this._collection[objectId]._data}); 6283 } 6284 } 6285 } 6286 }, 6287 6288 /** 6289 * Force an update on this object. Since an asynchronous GET is performed, 6290 * it is necessary to have an onChange handler registered in order to be 6291 * notified when the response of this returns. 6292 * @returns {finesse.restservices.RestBaseCollection} 6293 * This RestBaseCollection object to allow cascading 6294 */ 6295 refresh: function() { 6296 var _this = this, isLoaded = this._loaded; 6297 6298 this._doGET({ 6299 success: function(rsp) { 6300 if (_this._processResponse(rsp)) { 6301 _this._buildRefreshedCollection(_this._data); 6302 } else { 6303 _this._errorNotifier.notifyListeners(_this); 6304 } 6305 6306 // resubscribe if the collection requires an explicit subscription 6307 if (_this.explicitSubscription) { 6308 _this._subscribeNode(); 6309 } 6310 }, 6311 error: function(rsp) { 6312 _this._errorNotifier.notifyListeners(rsp); 6313 } 6314 }); 6315 6316 return this; 6317 }, 6318 6319 /** 6320 * @private 6321 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 6322 * format of an array of {(Object Type): object} objects. For example, a 6323 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 6324 * @param skipOuterObject If {true} is passed in for this param, then the "data" 6325 * property is returned instead of an object with the 6326 * data appended. 6327 * @return {Array} 6328 */ 6329 extractCollectionData: function (skipOuterObject) { 6330 var restObjs, 6331 obj, 6332 result = [], 6333 _this = this; 6334 6335 if (this._data) 6336 { 6337 restObjs = this._data[this.getRestItemType()]; 6338 6339 if (restObjs) 6340 { 6341 // check if there are multiple objects to pass 6342 if (!$.isArray(restObjs)) 6343 { 6344 restObjs = [restObjs]; 6345 } 6346 6347 // if so, create an object for each and add to result array 6348 $.each(restObjs, function (id, object) { 6349 if (skipOuterObject === true) 6350 { 6351 obj = object; 6352 } 6353 else 6354 { 6355 obj = {}; 6356 obj[_this.getRestItemType()] = object; 6357 } 6358 result.push(obj); 6359 }); 6360 } 6361 6362 } 6363 6364 return result; 6365 }, 6366 6367 /** 6368 * For Finesse, collections are handled uniquely on a POST and 6369 * doesn't necessary follow REST conventions. A POST on a collection 6370 * doesn't mean that the collection has been created, it means that an 6371 * item has been added to the collection. This function will generate 6372 * a closure which will handle this logic appropriately. 6373 * @param {Object} scope 6374 * The scope of where the callback should be invoked. 6375 * @private 6376 */ 6377 _addHandlerCb: function (scope) { 6378 return function (restItem) { 6379 var data = restItem.extractCollectionData(); 6380 6381 $.each(data, function (id, object) { 6382 scope._objectCreator(object); 6383 }); 6384 }; 6385 }, 6386 6387 /** 6388 * For Finesse, collections are handled uniquely on a DELETE and 6389 * doesn't necessary follow REST conventions. A DELETE on a collection 6390 * doesn't mean that the collection has been deleted, it means that an 6391 * item has been deleted from the collection. This function will generate 6392 * a closure which will handle this logic appropriately. 6393 * @param {Object} scope 6394 * The scope of where the callback should be invoked. 6395 * @private 6396 */ 6397 _deleteHandlerCb: function (scope) { 6398 return function (restItem) { 6399 var data = restItem.extractCollectionData(); 6400 6401 $.each(data, function (id, obj) { 6402 scope._objectDeleter(obj); 6403 }); 6404 }; 6405 }, 6406 6407 /** 6408 * Utility method to process the update notification for Rest Items 6409 * that are children of the collection whose events are published to 6410 * the collection's node. 6411 * @param {Object} update 6412 * The payload of an update notification. 6413 * @returns {Boolean} 6414 * True if the update was successfully processed (the update object 6415 * passed the schema validation) and updated the internal data cache, 6416 * false otherwise. 6417 * @private 6418 */ 6419 _processRestItemUpdate: function (update) { 6420 var object, objectId, updateObj = update.object.Update; 6421 6422 //Extract the ID from the source if the Update was an error. 6423 if (updateObj.data.apiErrors) { 6424 objectId = Utilities.getId(updateObj.source); 6425 } 6426 //Otherwise extract from the data object itself. 6427 else { 6428 objectId = this._extractId(updateObj.data); 6429 } 6430 6431 object = this._collection[objectId]; 6432 if (object) { 6433 if (object._processUpdate(update)) { 6434 switch (updateObj.event) { 6435 case "POST": 6436 object._addNotifier.notifyListeners(object); 6437 break; 6438 case "PUT": 6439 object._changeNotifier.notifyListeners(object); 6440 break; 6441 case "DELETE": 6442 object._deleteNotifier.notifyListeners(object); 6443 break; 6444 } 6445 } 6446 } 6447 }, 6448 6449 /** 6450 * SUBCLASS IMPLEMENTATION (override): 6451 * For collections, this callback has the additional responsibility of passing events 6452 * of collection item updates to the item objects themselves. The collection needs to 6453 * do this because item updates are published to the collection's node. 6454 * @returns {Function} 6455 * The callback to be invoked when an update event is received 6456 * @private 6457 */ 6458 _createPubsubCallback: function () { 6459 var _this = this; 6460 return function (update) { 6461 //If the source of the update is our REST URL, this means the collection itself is modified 6462 if (update.object.Update.source === _this.getRestUrl()) { 6463 _this._updateEventHandler(_this, update); 6464 } else { 6465 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 6466 //rest item update of one of our children that was published on our node (OpenAjax topic) 6467 _this._processRestItemUpdate(update); 6468 } 6469 }; 6470 }, 6471 6472 /** 6473 * @class 6474 * This is the base collection object. 6475 * 6476 * @constructs 6477 * @augments finesse.restservices.RestBase 6478 * @see finesse.restservices.Contacts 6479 * @see finesse.restservices.Dialogs 6480 * @see finesse.restservices.PhoneBooks 6481 * @see finesse.restservices.Queues 6482 * @see finesse.restservices.WorkflowActions 6483 * @see finesse.restservices.Workflows 6484 * @see finesse.restservices.WrapUpReasons 6485 */ 6486 _fakeConstuctor: function () { 6487 /* This is here to hide the real init constructor from the public docs */ 6488 }, 6489 6490 /** 6491 * @private 6492 * @param {Object} options 6493 * An object with the following properties:<ul> 6494 * <li><b>id:</b> The id of the object being constructed</li> 6495 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 6496 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 6497 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 6498 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 6499 * This does not include adding and deleting members of the collection</li> 6500 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 6501 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 6502 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 6503 * <li><b>status:</b> {Number} The HTTP status code returned</li> 6504 * <li><b>content:</b> {String} Raw string of response</li> 6505 * <li><b>object:</b> {Object} Parsed object of response</li> 6506 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 6507 * <li><b>errorType:</b> {String} Type of error that was caught</li> 6508 * <li><b>errorMessage:</b> {String} Message associated with error</li> 6509 * </ul></li> 6510 * </ul></li> 6511 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 6512 **/ 6513 init: function (options) { 6514 6515 options = options || {}; 6516 options.id = ""; 6517 6518 //Make internal datastore collection to hold a list of objects. 6519 this._collection = {}; 6520 this.length = 0; 6521 6522 //Collections will have additional callbacks that will be invoked when 6523 //an item has been added/deleted. 6524 this._collectionAddNotifier = new Notifier(); 6525 this._collectionDeleteNotifier = new Notifier(); 6526 6527 //Initialize the base class. 6528 this._super(options); 6529 6530 this.addHandler('collectionAdd', options.onCollectionAdd); 6531 this.addHandler('collectionDelete', options.onCollectionDelete); 6532 6533 //For Finesse, collections are handled uniquely on a POST/DELETE and 6534 //doesn't necessary follow REST conventions. A POST on a collection 6535 //doesn't mean that the collection has been created, it means that an 6536 //item has been added to the collection. A DELETE means that an item has 6537 //been removed from the collection. Due to this, we are attaching 6538 //special callbacks to the add/delete that will handle this logic. 6539 this.addHandler("add", this._addHandlerCb(this)); 6540 this.addHandler("delete", this._deleteHandlerCb(this)); 6541 }, 6542 6543 /** 6544 * Returns the collection. 6545 * @returns {Object} 6546 * The collection as an object 6547 */ 6548 getCollection: function () { 6549 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 6550 return this._collection; 6551 }, 6552 6553 /** 6554 * Utility method to build the internal collection data structure (object) based on provided data 6555 * @param {Object} data 6556 * The data to build the internal collection from 6557 * @private 6558 */ 6559 _buildCollection: function (data) { 6560 var i, object, objectId, dataArray; 6561 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 6562 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 6563 for (i = 0; i < dataArray.length; i += 1) { 6564 6565 object = {}; 6566 object[this.getRestItemType()] = dataArray[i]; 6567 objectId = this._extractId(object); 6568 this._collection[objectId] = new (this.getRestItemClass())({ 6569 doNotSubscribe: this.handlesItemSubscription, 6570 id: objectId, 6571 data: object 6572 }); 6573 this.length += 1; 6574 } 6575 } 6576 }, 6577 6578 /** 6579 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 6580 * Override this in subclasses if you need only object with certain attribute values. 6581 * @param {Object} item Item to test. 6582 * @return {Boolean} False to keep, true to filter out (discard); 6583 */ 6584 _filterOutItem: function (item) { 6585 return false; 6586 }, 6587 6588 /** 6589 * Validate and store the object into the internal data store. 6590 * SUBCLASS IMPLEMENTATION (override): 6591 * Performs collection specific logic to _buildCollection internally based on provided data 6592 * @param {Object} object 6593 * The JavaScript object that should match of schema of this REST object. 6594 * @returns {Boolean} 6595 * True if the object was validated and stored successfully. 6596 * @private 6597 */ 6598 _processObject: function (object) { 6599 var i, 6600 restItemType = this.getRestItemType(), 6601 items; 6602 if (this._validate(object)) { 6603 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 6604 6605 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 6606 if (this._data) 6607 { 6608 items = this._data[restItemType]; 6609 6610 if (typeof(items) !== "undefined") 6611 { 6612 if (typeof(items.length) === "undefined") 6613 { 6614 // Single object 6615 if (this._filterOutItem(items)) 6616 { 6617 this._data[restItemType] = items = []; 6618 } 6619 6620 } 6621 else 6622 { 6623 // filter out objects 6624 for (i = items.length - 1; i !== -1; i = i - 1) 6625 { 6626 if (this._filterOutItem(items[i])) 6627 { 6628 items.splice(i, 1); 6629 } 6630 } 6631 } 6632 } 6633 } 6634 6635 // If loaded for the first time, call the load notifiers. 6636 if (!this._loaded) { 6637 this._buildCollection(this._data); 6638 this._loaded = true; 6639 this._loadNotifier.notifyListeners(this); 6640 } 6641 6642 return true; 6643 6644 } 6645 return false; 6646 }, 6647 6648 /** 6649 * Retrieves a reference to a particular notifierType. 6650 * @param {String} notifierType 6651 * Specifies the notifier to retrieve (load, change, error, add, delete) 6652 * @return {Notifier} The notifier object. 6653 */ 6654 _getNotifierReference: function (notifierType) { 6655 var notifierReference; 6656 6657 try { 6658 //Use the base method to get references for load/change/error. 6659 notifierReference = this._super(notifierType); 6660 } catch (err) { 6661 //Check for add/delete 6662 if (notifierType === "collectionAdd") { 6663 notifierReference = this._collectionAddNotifier; 6664 } else if (notifierType === "collectionDelete") { 6665 notifierReference = this._collectionDeleteNotifier; 6666 } else { 6667 //Rethrow exception from base class. 6668 throw err; 6669 } 6670 } 6671 return notifierReference; 6672 } 6673 }); 6674 6675 window.finesse = window.finesse || {}; 6676 window.finesse.restservices = window.finesse.restservices || {}; 6677 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 6678 6679 return RestCollectionBase; 6680 })); 6681 6682 /** 6683 * JavaScript representation of the Finesse Dialog object. 6684 * 6685 * @requires finesse.clientservices.ClientServices 6686 * @requires Class 6687 * @requires finesse.FinesseBase 6688 * @requires finesse.restservices.RestBase 6689 */ 6690 6691 /** @private */ 6692 (function (factory) { 6693 6694 6695 // Define as an AMD module if possible 6696 if ( typeof define === 'function' && define.amd ) 6697 { 6698 define('restservices/Dialog', ['restservices/RestBase', 6699 'utilities/Utilities'], factory ); 6700 } 6701 /* Define using browser globals otherwise 6702 * Prevent multiple instantiations if the script is loaded twice 6703 */ 6704 else 6705 { 6706 factory(finesse.restservices.RestBase, finesse.utilities.Utilities); 6707 } 6708 }(function (RestBase, Utilities) { 6709 var Dialog = RestBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 6710 6711 /** 6712 * @class 6713 * A Dialog is an attempted connection between or among multiple participants, 6714 * for example, a regular phone call, a conference, or a silent monitor session. 6715 * 6716 * @augments finesse.restservices.RestBase 6717 * @constructs 6718 */ 6719 _fakeConstuctor: function () { 6720 /* This is here to hide the real init constructor from the public docs */ 6721 }, 6722 6723 /** 6724 * @private 6725 * 6726 * @param {Object} options 6727 * An object with the following properties:<ul> 6728 * <li><b>id:</b> The id of the object being constructed</li> 6729 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 6730 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 6731 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 6732 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 6733 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 6734 * <li><b>status:</b> {Number} The HTTP status code returned</li> 6735 * <li><b>content:</b> {String} Raw string of response</li> 6736 * <li><b>object:</b> {Object} Parsed object of response</li> 6737 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 6738 * <li><b>errorType:</b> {String} Type of error that was caught</li> 6739 * <li><b>errorMessage:</b> {String} Message associated with error</li> 6740 * </ul></li> 6741 * </ul></li> 6742 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 6743 **/ 6744 init: function (options) { 6745 this._super(options); 6746 }, 6747 6748 /** 6749 * @private 6750 * Gets the REST class for the current object - this is the Dialog class. 6751 * @returns {Object} The Dialog class. 6752 */ 6753 getRestClass: function () { 6754 return Dialog; 6755 }, 6756 6757 /** 6758 * @private 6759 * The constant for agent device. 6760 */ 6761 _agentDeviceType: "AGENT_DEVICE", 6762 6763 /** 6764 * @private 6765 * Gets the REST type for the current object - this is a "Dialog". 6766 * @returns {String} The Dialog string. 6767 */ 6768 getRestType: function () { 6769 return "Dialog"; 6770 }, 6771 6772 /** 6773 * @private 6774 * Override default to indicate that this object doesn't support making 6775 * requests. 6776 */ 6777 supportsRequests: false, 6778 6779 /** 6780 * @private 6781 * Override default to indicate that this object doesn't support subscriptions. 6782 */ 6783 supportsSubscriptions: false, 6784 6785 /** 6786 * Getter for the from address. 6787 * @returns {String} The from address. 6788 */ 6789 getFromAddress: function () { 6790 this.isLoaded(); 6791 return this.getData().fromAddress; 6792 }, 6793 6794 /** 6795 * Getter for the to address. 6796 * @returns {String} The to address. 6797 */ 6798 getToAddress: function () { 6799 this.isLoaded(); 6800 return this.getData().toAddress; 6801 }, 6802 /** 6803 * Getter for the media type. 6804 * @returns {String} The media type. 6805 */ 6806 getMediaType: function () { 6807 this.isLoaded(); 6808 return this.getData().mediaType; 6809 }, 6810 /** 6811 * @private 6812 * Getter for the uri. 6813 * @returns {String} The uri. 6814 */ 6815 getDialogUri: function () { 6816 this.isLoaded(); 6817 return this.getData().uri; 6818 }, 6819 6820 /** 6821 * Getter for the callType. 6822 * @deprecated Use getMediaProperties().callType instead. 6823 * @returns {String} The callType. 6824 */ 6825 getCallType: function () { 6826 this.isLoaded(); 6827 return this.getData().mediaProperties.callType; 6828 }, 6829 6830 /** 6831 * Getter for the DNIS. This is usually the actual number dialed. 6832 * @deprecated Use getMediaProperties().DNIS instead. 6833 * @returns {String} The callType. 6834 */ 6835 getDNIS: function () { 6836 this.isLoaded(); 6837 return this.getData().mediaProperties.DNIS; 6838 }, 6839 6840 /** 6841 * Getter for the Dialog state. 6842 * @returns {String} The Dialog state. 6843 */ 6844 getState: function () { 6845 this.isLoaded(); 6846 return this.getData().state; 6847 }, 6848 6849 /** 6850 * Retrieves a list of participants within the Dialog object. 6851 * @returns {Object} Array list of participants. 6852 */ 6853 getParticipants: function () { 6854 this.isLoaded(); 6855 var participants = this.getData().participants.Participant; 6856 //Due to the nature of the XML->JSO converter library, a single 6857 //element in the XML array will be considered to an object instead of 6858 //a real array. This will handle those cases to ensure that an array is 6859 //always returned. 6860 6861 return Utilities.getArray(participants); 6862 }, 6863 6864 /** 6865 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 6866 * (It is not a CTI Route Point, IVR Port, or the caller) 6867 * 6868 * @param {String} filterExtension used to remove a single extension from the list 6869 * @returns {Array} Participants which is an array of all participants which can be dropped. 6870 */ 6871 getDroppableParticipants: function (filterExtension) { 6872 this.isLoaded(); 6873 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 6874 6875 participants = this.getParticipants(); 6876 6877 if (filterExtension) 6878 { 6879 filterExtensionToRemove = filterExtension; 6880 } 6881 6882 //Loop through all the participants to remove non-agents & remove filterExtension 6883 //We could have removed filterExtension using splice, but we have to iterate through 6884 //the list anyway. 6885 for(idx=0;idx<participants.length;idx=idx+1) 6886 { 6887 part = participants[idx]; 6888 6889 //Skip the filterExtension 6890 if (part.mediaAddress !== filterExtensionToRemove) 6891 { 6892 callStateOk = this._isParticipantStateDroppable(part); 6893 6894 //Remove non-agents & make sure callstate 6895 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 6896 { 6897 droppableParticipants.push(part); 6898 } 6899 } 6900 } 6901 6902 return Utilities.getArray(droppableParticipants); 6903 }, 6904 6905 _isParticipantStateDroppable : function (part) 6906 { 6907 var isParticipantStateDroppable = false; 6908 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 6909 { 6910 isParticipantStateDroppable = true; 6911 } 6912 6913 return isParticipantStateDroppable; 6914 }, 6915 6916 /** 6917 * Is the participant droppable 6918 * 6919 * @param {String} participantExt Extension of participant. 6920 * @returns {Boolean} True is droppable. 6921 */ 6922 isParticipantDroppable : function (participantExt) { 6923 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 6924 6925 droppableParticipants = this.getDroppableParticipants(); 6926 6927 if (droppableParticipants) 6928 { 6929 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 6930 { 6931 part = droppableParticipants[idx]; 6932 6933 if (part.mediaAddress === participantExt) 6934 { 6935 callStateOk = this._isParticipantStateDroppable(part); 6936 6937 //Remove non-agents & make sure callstate 6938 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 6939 { 6940 isDroppable = true; 6941 break; 6942 } 6943 } 6944 } 6945 } 6946 6947 return isDroppable; 6948 }, 6949 6950 /** 6951 * Retrieves a list of media properties from the dialog object. 6952 * @returns {Object} Map of call variables; names mapped to values. 6953 * Variables may include the following:<ul> 6954 * <li>dialedNumber: The number dialed. 6955 * <li>callType: The type of call. Call types include:<ul> 6956 * <li>ACD_IN 6957 * <li>PREROUTE_ACD_IN 6958 * <li>PREROUTE_DIRECT_AGENT 6959 * <li>TRANSFER 6960 * <li>OTHER_IN 6961 * <li>OUT 6962 * <li>AGENT_INSIDE 6963 * <li>CONSULT 6964 * <li>CONFERENCE 6965 * <li>SUPERVISOR_MONITOR 6966 * <li>OUTBOUND 6967 * <li>OUTBOUND_PREVIEW</ul> 6968 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 6969 * <li>wrapUpReason: A description of the call. 6970 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 6971 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 6972 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 6973 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 6974 * <li>BACampaign 6975 * <li>BAAccountNumber 6976 * <li>BAResponse 6977 * <li>BAStatus<ul> 6978 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 6979 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 6980 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 6981 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 6982 * <li>BADialedListID 6983 * <li>BATimeZone 6984 * <li>BABuddyName</ul></ul> 6985 * 6986 */ 6987 getMediaProperties: function () { 6988 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 6989 6990 this.isLoaded(); 6991 6992 // We have to convert to jQuery object to do a proper compare 6993 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 6994 6995 if ((this._lastMediaPropertiesJQuery !== undefined) 6996 && (this._lastMediaPropertiesMap !== undefined) 6997 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 6998 6999 return this._lastMediaPropertiesMap; 7000 } 7001 7002 currentMediaPropertiesMap = {}; 7003 7004 mpData = this.getData().mediaProperties; 7005 7006 if (mpData) { 7007 if (mpData.callvariables && mpData.callvariables.CallVariable) { 7008 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 7009 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 7010 }); 7011 } 7012 7013 jQuery.each(mpData, function (key, value) { 7014 if (key !== 'callvariables') { 7015 currentMediaPropertiesMap[key] = value; 7016 } 7017 }); 7018 } 7019 7020 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 7021 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 7022 7023 return this._lastMediaPropertiesMap; 7024 }, 7025 7026 /** 7027 * @private 7028 * Invoke a request to the server given a content body and handlers. 7029 * 7030 * @param {Object} contentBody 7031 * A JS object containing the body of the action request. 7032 * @param {finesse.interfaces.RequestHandlers} handlers 7033 * An object containing the handlers for the request 7034 */ 7035 _makeRequest: function (contentBody, handlers) { 7036 // Protect against null dereferencing of options allowing its 7037 // (nonexistent) keys to be read as undefined 7038 handlers = handlers || {}; 7039 7040 this.restRequest(this.getRestUrl(), { 7041 method: 'PUT', 7042 success: handlers.success, 7043 error: handlers.error, 7044 content: contentBody 7045 }); 7046 }, 7047 7048 /** 7049 * Invoke a consult call out to a destination. 7050 * 7051 * @param {String} mediaAddress 7052 * The media address of the user performing the consult call. 7053 * @param {String} toAddress 7054 * The destination address of the consult call. 7055 * @param {finesse.interfaces.RequestHandlers} handlers 7056 * An object containing the handlers for the request 7057 */ 7058 makeConsultCall: function (mediaAddress, toAddress, handlers) { 7059 this.isLoaded(); 7060 var contentBody = {}; 7061 contentBody[this.getRestType()] = { 7062 "targetMediaAddress": mediaAddress, 7063 "toAddress": toAddress, 7064 "requestedAction": Dialog.Actions.CONSULT_CALL 7065 }; 7066 this._makeRequest(contentBody, handlers); 7067 return this; // Allow cascading 7068 }, 7069 7070 /** 7071 * Invoke a single step transfer request. 7072 * 7073 * @param {String} mediaAddress 7074 * The media address of the user performing the single step transfer. 7075 * @param {String} toAddress 7076 * The destination address of the single step transfer. 7077 * @param {finesse.interfaces.RequestHandlers} handlers 7078 * An object containing the handlers for the request 7079 */ 7080 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 7081 this.isLoaded(); 7082 var contentBody = {}; 7083 contentBody[this.getRestType()] = { 7084 "targetMediaAddress": mediaAddress, 7085 "toAddress": toAddress, 7086 "requestedAction": Dialog.Actions.TRANSFER_SST 7087 }; 7088 this._makeRequest(contentBody, handlers); 7089 return this; // Allow cascading 7090 }, 7091 7092 /** 7093 * Update this dialog's wrap-up reason. 7094 * 7095 * @param {String} wrapUpReason 7096 * The new wrap-up reason for this dialog 7097 * @param {finesse.interfaces.RequestHandlers} handlers 7098 * An object containing the handlers for the request 7099 */ 7100 updateWrapUpReason: function (wrapUpReason, options) 7101 { 7102 this.isLoaded(); 7103 var mediaProperties = 7104 { 7105 "wrapUpReason": wrapUpReason 7106 }; 7107 7108 options = options || {}; 7109 options.content = {}; 7110 options.content[this.getRestType()] = 7111 { 7112 "mediaProperties": mediaProperties, 7113 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 7114 }; 7115 options.method = "PUT"; 7116 this.restRequest(this.getRestUrl(), options); 7117 7118 return this; 7119 }, 7120 7121 /** 7122 * Invoke a request to server based on the action given. 7123 * @param {String} mediaAddress 7124 * The media address of the user performing the action. 7125 * @param {finesse.restservices.Dialog.Actions} action 7126 * The action string indicating the action to invoke on dialog. 7127 * @param {finesse.interfaces.RequestHandlers} handlers 7128 * An object containing the handlers for the request 7129 */ 7130 requestAction: function (mediaAddress, action, handlers) { 7131 this.isLoaded(); 7132 var contentBody = {}; 7133 contentBody[this.getRestType()] = { 7134 "targetMediaAddress": mediaAddress, 7135 "requestedAction": action 7136 }; 7137 this._makeRequest(contentBody, handlers); 7138 return this; // Allow cascading 7139 }, 7140 7141 /** 7142 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 7143 * 7144 * @param targetMediaAddress is the address to drop 7145 * @param {finesse.interfaces.RequestHandlers} handlers 7146 * An object containing the handlers for the request 7147 */ 7148 dropParticipant: function (targetMediaAddress, handlers) { 7149 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 7150 }, 7151 7152 /** 7153 * Invoke a request to server to send DTMF digit tones. 7154 * @param {String} mediaAddress 7155 * @param {String} action 7156 * The action string indicating the action to invoke on dialog. 7157 * @param {finesse.interfaces.RequestHandlers} handlers 7158 * An object containing the handlers for the request 7159 */ 7160 sendDTMFRequest: function (mediaAddress, handlers, digit) { 7161 this.isLoaded(); 7162 var contentBody = {}; 7163 contentBody[this.getRestType()] = { 7164 "targetMediaAddress": mediaAddress, 7165 "requestedAction": "SEND_DTMF", 7166 "actionParams": { 7167 "ActionParam": { 7168 "name": "dtmfString", 7169 "value": digit 7170 } 7171 } 7172 }; 7173 this._makeRequest(contentBody, handlers); 7174 return this; // Allow cascading 7175 } 7176 }); 7177 7178 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 7179 /** 7180 * Drops the Participant from the Dialog. 7181 */ 7182 DROP: "DROP", 7183 /** 7184 * Answers a Dialog. 7185 */ 7186 ANSWER: "ANSWER", 7187 /** 7188 * Holds the Dialog. 7189 */ 7190 HOLD: "HOLD", 7191 /** 7192 * Barges into a Call Dialog. 7193 */ 7194 BARGE_CALL: "BARGE_CALL", 7195 /** 7196 * Allow as Supervisor to Drop a Participant from the Dialog. 7197 */ 7198 PARTICIPANT_DROP: "PARTICIPANT_DROP", 7199 /** 7200 * Makes a new Call Dialog. 7201 */ 7202 MAKE_CALL: "MAKE_CALL", 7203 /** 7204 * Retrieves a Dialog that is on Hold. 7205 */ 7206 RETRIEVE: "RETRIEVE", 7207 /** 7208 * Initiates a Consult Call. 7209 */ 7210 CONSULT_CALL: "CONSULT_CALL", 7211 /** 7212 * Initiates a Transfer of a Dialog. 7213 */ 7214 TRANSFER: "TRANSFER", 7215 /** 7216 * Initiates a Single-Step Transfer of a Dialog. 7217 */ 7218 TRANSFER_SST: "TRANSFER_SST", 7219 /** 7220 * Initiates a Conference of a Dialog. 7221 */ 7222 CONFERENCE: "CONFERENCE", 7223 /** 7224 * Updates data on a Call Dialog. 7225 */ 7226 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 7227 /** 7228 * Initiates a Recording on a Call Dialog. 7229 */ 7230 START_RECORDING : "START_RECORDING", 7231 /** 7232 * Sends DTMF (dialed digits) to a Call Dialog. 7233 */ 7234 DTMF : "SEND_DTMF", 7235 /** 7236 * Accepts a Dialog that is being Previewed. 7237 */ 7238 ACCEPT: "ACCEPT", 7239 /** 7240 * Rejects a Dialog. 7241 */ 7242 REJECT: "REJECT", 7243 /** 7244 * Closes a Dialog. 7245 */ 7246 CLOSE : "CLOSE", 7247 /** 7248 * @class Set of action constants for a Dialog. These should be used for 7249 * {@link finesse.restservices.Dialog#requestAction}. 7250 * @constructs 7251 */ 7252 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 7253 }; 7254 7255 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 7256 /** 7257 * Indicates that the call is ringing at a device. 7258 */ 7259 ALERTING: "ALERTING", 7260 /** 7261 * Indicates that the phone is off the hook at a device. 7262 */ 7263 INITIATING: "INITIATING", 7264 /** 7265 * Indicates that the dialog has a least one active participant. 7266 */ 7267 ACTIVE: "ACTIVE", 7268 /** 7269 * Indicates that the dialog has no active participants. 7270 */ 7271 DROPPED: "DROPPED", 7272 /** 7273 * Indicates that the phone is dialing at the device. 7274 */ 7275 INITIATED: "INITIATED", 7276 /** 7277 * Indicates that the dialog has failed. 7278 * @see Dialog.ReasonStates 7279 */ 7280 FAILED: "FAILED", 7281 /** 7282 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 7283 */ 7284 ACCEPTED: "ACCEPTED", 7285 /** 7286 * @class Possible Dialog State constants. 7287 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 7288 * @constructs 7289 */ 7290 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 7291 }; 7292 7293 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 7294 /** 7295 * Indicates that an incoming call is ringing on the device. 7296 */ 7297 ALERTING: "ALERTING", 7298 /** 7299 * Indicates that an outgoing call, not yet active, exists on the device. 7300 */ 7301 INITIATING: "INITIATING", 7302 /** 7303 * Indicates that the participant is active on the call. 7304 */ 7305 ACTIVE: "ACTIVE", 7306 /** 7307 * Indicates that the participant has dropped from the call. 7308 */ 7309 DROPPED: "DROPPED", 7310 /** 7311 * Indicates that the participant has held their connection to the call. 7312 */ 7313 HELD: "HELD", 7314 /** 7315 * Indicates that the phone is dialing at a device. 7316 */ 7317 INITIATED: "INITIATED", 7318 /** 7319 * Indicates that the call failed. 7320 * @see Dialog.ReasonStates 7321 */ 7322 FAILED: "FAILED", 7323 /** 7324 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 7325 */ 7326 WRAP_UP: "WRAP_UP", 7327 /** 7328 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 7329 */ 7330 ACCEPTED: "ACCEPTED", 7331 /** 7332 * @class Possible Dialog Participant State constants. 7333 * @constructs 7334 */ 7335 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 7336 }; 7337 7338 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 7339 /** 7340 * Dialog was Busy. This will typically be for a Failed Dialog. 7341 */ 7342 BUSY: "BUSY", 7343 /** 7344 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 7345 */ 7346 BAD_DESTINATION: "BAD_DESTINATION", 7347 /** 7348 * All Other Reasons. This will typically be for a Failed Dialog. 7349 */ 7350 OTHER: "OTHER", 7351 /** 7352 * The Device Resource for the Dialog was not available. 7353 */ 7354 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 7355 /** 7356 * @class Possible dialog state reasons code constants. 7357 * @constructs 7358 */ 7359 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 7360 }; 7361 7362 window.finesse = window.finesse || {}; 7363 window.finesse.restservices = window.finesse.restservices || {}; 7364 window.finesse.restservices.Dialog = Dialog; 7365 7366 7367 return Dialog; 7368 })); 7369 7370 /** 7371 * JavaScript representation of the Finesse Dialogs collection 7372 * object which contains a list of Dialog objects. 7373 * 7374 * @requires finesse.clientservices.ClientServices 7375 * @requires Class 7376 * @requires finesse.FinesseBase 7377 * @requires finesse.restservices.RestBase 7378 * @requires finesse.restservices.Dialog 7379 */ 7380 /** @private */ 7381 (function (factory) { 7382 7383 7384 // Define as an AMD module if possible 7385 if ( typeof define === 'function' && define.amd ) 7386 { 7387 define('restservices/Dialogs', ['restservices/RestCollectionBase', 7388 'restservices/Dialog'], factory ); 7389 } 7390 /* Define using browser globals otherwise 7391 * Prevent multiple instantiations if the script is loaded twice 7392 */ 7393 else 7394 { 7395 factory(finesse.restservices.RestCollectionBase, finesse.restservices.Dialog); 7396 } 7397 }(function (RestCollectionBase, Dialog) { 7398 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 7399 7400 /** 7401 * @class 7402 * JavaScript representation of a Dialogs collection object. Also exposes 7403 * methods to operate on the object against the server. 7404 * @augments finesse.restservices.RestCollectionBase 7405 * @constructs 7406 * @see finesse.restservices.Dialog 7407 * @example 7408 * _dialogs = _user.getDialogs( { 7409 * onCollectionAdd : _handleDialogAdd, 7410 * onCollectionDelete : _handleDialogDelete, 7411 * onLoad : _handleDialogsLoaded 7412 * }); 7413 * 7414 * _dialogCollection = _dialogs.getCollection(); 7415 * for (var dialogId in _dialogCollection) { 7416 * if (_dialogCollection.hasOwnProperty(dialogId)) { 7417 * _dialog = _dialogCollection[dialogId]; 7418 * etc... 7419 * } 7420 * } 7421 */ 7422 _fakeConstuctor: function () { 7423 /* This is here to hide the real init constructor from the public docs */ 7424 }, 7425 7426 /** 7427 * @private 7428 * @param {Object} options 7429 * An object with the following properties:<ul> 7430 * <li><b>id:</b> The id of the object being constructed</li> 7431 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7432 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7433 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7434 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7435 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7436 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7437 * <li><b>content:</b> {String} Raw string of response</li> 7438 * <li><b>object:</b> {Object} Parsed object of response</li> 7439 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7440 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7441 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7442 * </ul></li> 7443 * </ul></li> 7444 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7445 **/ 7446 init: function (options) { 7447 this._super(options); 7448 }, 7449 7450 /** 7451 * @private 7452 * Gets the REST class for the current object - this is the Dialogs class. 7453 */ 7454 getRestClass: function () { 7455 return Dialogs; 7456 }, 7457 7458 /** 7459 * @private 7460 * Gets the REST class for the objects that make up the collection. - this 7461 * is the Dialog class. 7462 */ 7463 getRestItemClass: function () { 7464 return Dialog; 7465 }, 7466 7467 /** 7468 * @private 7469 * Gets the REST type for the current object - this is a "Dialogs". 7470 */ 7471 getRestType: function () { 7472 return "Dialogs"; 7473 }, 7474 7475 /** 7476 * @private 7477 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 7478 */ 7479 getRestItemType: function () { 7480 return "Dialog"; 7481 }, 7482 7483 /** 7484 * @private 7485 * Override default to indicates that the collection doesn't support making 7486 * requests. 7487 */ 7488 supportsRequests: true, 7489 7490 /** 7491 * @private 7492 * Override default to indicates that the collection subscribes to its objects. 7493 */ 7494 supportsRestItemSubscriptions: true, 7495 7496 /** 7497 * @private 7498 * Create a new Dialog in this collection 7499 * 7500 * @param {String} toAddress 7501 * The to address of the new Dialog 7502 * @param {String} fromAddress 7503 * The from address of the new Dialog 7504 * @param {finesse.interfaces.RequestHandlers} handlers 7505 * An object containing the (optional) handlers for the request. 7506 * @return {finesse.restservices.Dialogs} 7507 * This Dialogs object, to allow cascading. 7508 */ 7509 createNewCallDialog: function (toAddress, fromAddress, handlers) 7510 { 7511 var contentBody = {}; 7512 contentBody[this.getRestItemType()] = { 7513 "requestedAction": "MAKE_CALL", 7514 "toAddress": toAddress, 7515 "fromAddress": fromAddress 7516 }; 7517 7518 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 7519 handlers = handlers || {}; 7520 7521 this.restRequest(this.getRestUrl(), { 7522 method: 'POST', 7523 success: handlers.success, 7524 error: handlers.error, 7525 content: contentBody 7526 }); 7527 return this; // Allow cascading 7528 }, 7529 7530 /** 7531 * @private 7532 * Create a new Dialog in this collection as a result of a requested action 7533 * 7534 * @param {String} toAddress 7535 * The to address of the new Dialog 7536 * @param {String} fromAddress 7537 * The from address of the new Dialog 7538 * @param {finesse.restservices.Dialog.Actions} actionType 7539 * The associated action to request for creating this new dialog 7540 * @param {finesse.interfaces.RequestHandlers} handlers 7541 * An object containing the (optional) handlers for the request. 7542 * @return {finesse.restservices.Dialogs} 7543 * This Dialogs object, to allow cascading. 7544 */ 7545 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 7546 { 7547 var contentBody = {}; 7548 this._isLoaded = true; 7549 7550 contentBody[this.getRestItemType()] = { 7551 "requestedAction": actionType, 7552 "toAddress": toAddress, 7553 "fromAddress": fromAddress 7554 }; 7555 7556 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 7557 handlers = handlers || {}; 7558 7559 this.restRequest(this.getRestUrl(), { 7560 method: 'POST', 7561 success: handlers.success, 7562 error: handlers.error, 7563 content: contentBody 7564 }); 7565 return this; // Allow cascading 7566 }, 7567 7568 /** 7569 * @private 7570 * Create a new Dialog in this collection as a result of a requested action 7571 * @param {String} fromAddress 7572 * The from address of the new Dialog 7573 * @param {String} toAddress 7574 * The to address of the new Dialog 7575 * @param {finesse.restservices.Dialog.Actions} actionType 7576 * The associated action to request for creating this new dialog 7577 * @param {String} dialogUri 7578 * The associated uri of SUPERVISOR_MONITOR call 7579 * @param {finesse.interfaces.RequestHandlers} handlers 7580 * An object containing the (optional) handlers for the request. 7581 * @return {finesse.restservices.Dialogs} 7582 * This Dialogs object, to allow cascading. 7583 */ 7584 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 7585 this.isLoaded(); 7586 7587 var contentBody = {}; 7588 contentBody[this.getRestItemType()] = { 7589 "fromAddress": fromAddress, 7590 "toAddress": toAddress, 7591 "requestedAction": actionType, 7592 "associatedDialogUri": dialogURI 7593 7594 }; 7595 // (nonexistent) keys to be read as undefined 7596 handlers = handlers || {}; 7597 this.restRequest(this.getRestUrl(), { 7598 method: 'POST', 7599 success: handlers.success, 7600 error: handlers.error, 7601 content: contentBody 7602 }); 7603 return this; // Allow cascading 7604 }, 7605 7606 /** 7607 * Utility method to get the number of dialogs in this collection. 7608 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 7609 * 'SUPERVISOR_MONITOR' from the count. 7610 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 7611 * @return {Number} The number of dialogs in this collection. 7612 */ 7613 getDialogCount: function (excludeSilentMonitor) { 7614 this.isLoaded(); 7615 7616 var dialogId, count = 0; 7617 if (excludeSilentMonitor) { 7618 for (dialogId in this._collection) { 7619 if (this._collection.hasOwnProperty(dialogId)) { 7620 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 7621 count += 1; 7622 } 7623 } 7624 } 7625 7626 return count; 7627 } else { 7628 return this.length; 7629 } 7630 } 7631 7632 }); 7633 7634 window.finesse = window.finesse || {}; 7635 window.finesse.restservices = window.finesse.restservices || {}; 7636 window.finesse.restservices.Dialogs = Dialogs; 7637 7638 return Dialogs; 7639 })); 7640 7641 /** 7642 * JavaScript representation of the Finesse ClientLog object 7643 * 7644 * @requires finesse.clientservices.ClientServices 7645 * @requires Class 7646 * @requires finesse.FinesseBase 7647 * @requires finesse.restservices.RestBase 7648 */ 7649 7650 /** The following comment is to prevent jslint errors about 7651 * using variables before they are defined. 7652 */ 7653 /** @private */ 7654 /*global finesse*/ 7655 7656 (function (factory) { 7657 7658 7659 // Define as an AMD module if possible 7660 if ( typeof define === 'function' && define.amd ) 7661 { 7662 define('restservices/ClientLog',["restservices/RestBase"], factory ); 7663 } 7664 7665 /* Define using browser globals otherwise 7666 * Prevent multiple instantiations if the script is loaded twice 7667 */ 7668 else 7669 { 7670 factory(finesse.restservices.RestBase); 7671 } 7672 7673 }(function (RestBase) { 7674 7675 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 7676 /** 7677 * @private 7678 * Returns whether this object supports transport logs 7679 */ 7680 doNotLog : true, 7681 7682 doNotRefresh: true, 7683 7684 explicitSubscription : true, 7685 7686 /** 7687 * @class 7688 * @private 7689 * JavaScript representation of a ClientLog object. Also exposes methods to operate 7690 * on the object against the server. 7691 * 7692 * @param {Object} options 7693 * An object with the following properties:<ul> 7694 * <li><b>id:</b> The id of the object being constructed</li> 7695 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7696 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7697 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7698 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7699 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7700 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7701 * <li><b>content:</b> {String} Raw string of response</li> 7702 * <li><b>object:</b> {Object} Parsed object of response</li> 7703 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7704 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7705 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7706 * </ul></li> 7707 * </ul></li> 7708 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7709 * @constructs 7710 * @augments finesse.restservices.RestBase 7711 **/ 7712 init: function (options) { 7713 this._super({ 7714 id: "", 7715 data: {clientLog : null}, 7716 onAdd: options.onAdd, 7717 onChange: options.onChange, 7718 onLoad: options.onLoad, 7719 onError: options.onError, 7720 parentObj: options.parentObj 7721 }); 7722 }, 7723 7724 /** 7725 * @private 7726 * Gets the REST class for the current object - this is the ClientLog object. 7727 */ 7728 getRestClass: function () { 7729 return ClientLog; 7730 }, 7731 7732 /** 7733 * @private 7734 * Gets the REST type for the current object - this is a "ClientLog". 7735 */ 7736 getRestType: function () 7737 { 7738 return "ClientLog"; 7739 }, 7740 7741 /** 7742 * @private 7743 * Gets the node path for the current object 7744 * @returns {String} The node path 7745 */ 7746 getXMPPNodePath: function () { 7747 return this.getRestUrl(); 7748 }, 7749 7750 /** 7751 * @private 7752 * Invoke a request to the server given a content body and handlers. 7753 * 7754 * @param {Object} contentBody 7755 * A JS object containing the body of the action request. 7756 * @param {Object} handlers 7757 * An object containing the following (optional) handlers for the request:<ul> 7758 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 7759 * response object as its only parameter:<ul> 7760 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7761 * <li><b>content:</b> {String} Raw string of response</li> 7762 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 7763 * <li>A error callback function for an unsuccessful request to be invoked with the 7764 * error response object as its only parameter:<ul> 7765 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7766 * <li><b>content:</b> {String} Raw string of response</li> 7767 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 7768 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7769 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7770 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7771 * </ul></li> 7772 * </ul> 7773 */ 7774 sendLogs: function (contentBody, handlers) { 7775 // Protect against null dereferencing of options allowing its 7776 // (nonexistent) keys to be read as undefined 7777 handlers = handlers || {}; 7778 7779 this.restRequest(this.getRestUrl(), { 7780 method: 'POST', 7781 //success: handlers.success, 7782 error: handlers.error, 7783 content: contentBody 7784 }); 7785 } 7786 }); 7787 7788 window.finesse = window.finesse || {}; 7789 window.finesse.restservices = window.finesse.restservices || {}; 7790 window.finesse.restservices.ClientLog = ClientLog; 7791 7792 return ClientLog; 7793 })); 7794 7795 /** 7796 * JavaScript representation of the Finesse Queue object 7797 * @requires finesse.clientservices.ClientServices 7798 * @requires Class 7799 * @requires finesse.FinesseBase 7800 * @requires finesse.restservices.RestBase 7801 */ 7802 7803 /** @private */ 7804 (function (factory) { 7805 7806 7807 // Define as an AMD module if possible 7808 if ( typeof define === 'function' && define.amd ) 7809 { 7810 define('restservices/Queue', ['restservices/RestBase', 7811 'utilities/Utilities'], factory ); 7812 } 7813 /* Define using browser globals otherwise 7814 * Prevent multiple instantiations if the script is loaded twice 7815 */ 7816 else 7817 { 7818 factory(finesse.restservices.RestBase, finesse.utilities.Utilities); 7819 } 7820 }(function (RestBase, Utilities) { 7821 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 7822 7823 /** 7824 * @class 7825 * A Queue is a list of Contacts available to a User for quick dial. 7826 * 7827 * @augments finesse.restservices.RestBase 7828 * @constructs 7829 */ 7830 _fakeConstuctor: function () { 7831 /* This is here to hide the real init constructor from the public docs */ 7832 }, 7833 7834 /** 7835 * @private 7836 * JavaScript representation of a Queue object. Also exposes methods to operate 7837 * on the object against the server. 7838 * 7839 * @constructor 7840 * @param {String} id 7841 * Not required... 7842 * @param {Object} callbacks 7843 * An object containing callbacks for instantiation and runtime 7844 * @param {Function} callbacks.onLoad(this) 7845 * Callback to invoke upon successful instantiation 7846 * @param {Function} callbacks.onLoadError(rsp) 7847 * Callback to invoke on instantiation REST request error 7848 * as passed by finesse.clientservices.ClientServices.ajax() 7849 * { 7850 * status: {Number} The HTTP status code returned 7851 * content: {String} Raw string of response 7852 * object: {Object} Parsed object of response 7853 * error: {Object} Wrapped exception that was caught 7854 * error.errorType: {String} Type of error that was caught 7855 * error.errorMessage: {String} Message associated with error 7856 * } 7857 * @param {Function} callbacks.onChange(this) 7858 * Callback to invoke upon successful update 7859 * @param {Function} callbacks.onError(rsp) 7860 * Callback to invoke on update error (refresh or event) 7861 * as passed by finesse.clientservices.ClientServices.ajax() 7862 * { 7863 * status: {Number} The HTTP status code returned 7864 * content: {String} Raw string of response 7865 * object: {Object} Parsed object of response 7866 * error: {Object} Wrapped exception that was caught 7867 * error.errorType: {String} Type of error that was caught 7868 * error.errorMessage: {String} Message associated with error 7869 * } 7870 * 7871 */ 7872 init: function (id, callbacks, restObj) { 7873 this._super(id, callbacks, restObj); 7874 }, 7875 7876 /** 7877 * @private 7878 * Gets the REST class for the current object - this is the Queue object. 7879 */ 7880 getRestClass: function () { 7881 return Queue; 7882 }, 7883 7884 /** 7885 * @private 7886 * Gets the REST type for the current object - this is a "Queue". 7887 */ 7888 getRestType: function () { 7889 return "Queue"; 7890 }, 7891 7892 /** 7893 * @private 7894 * Returns whether this object supports subscriptions 7895 */ 7896 supportsSubscriptions: function () { 7897 return true; 7898 }, 7899 7900 /** 7901 * @private 7902 * Specifies whether this object's subscriptions need to be explicitly requested 7903 */ 7904 explicitSubscription: true, 7905 7906 /** 7907 * @private 7908 * Specifies that this object should not be refreshed; possible reasons are that 7909 * it's encapsulating collection already does it 7910 */ 7911 doNotRefresh: true, 7912 7913 /** 7914 * @private 7915 * Gets the node path for the current object - this is the team Users node 7916 * @returns {String} The node path 7917 */ 7918 getXMPPNodePath: function () { 7919 return this.getRestUrl(); 7920 }, 7921 7922 /** 7923 * Getter for the queue id 7924 * @returns {String} 7925 * The id of the Queue 7926 */ 7927 getId: function () { 7928 this.isLoaded(); 7929 return this._id; 7930 }, 7931 7932 /** 7933 * Getter for the queue name 7934 * @returns {String} 7935 * The name of the Queue 7936 */ 7937 getName: function () { 7938 this.isLoaded(); 7939 return this.getData().name; 7940 }, 7941 7942 /** 7943 * Getter for the queue statistics. 7944 * Supported statistics include:<br> 7945 * - callsInQueue<br> 7946 * - startTimeOfLongestCallInQueue<br> 7947 * <br> 7948 * These statistics can be accessed via dot notation:<br> 7949 * i.e.: getStatistics().callsInQueue 7950 * @returns {Object} 7951 * The Object with different statistics as properties. 7952 */ 7953 getStatistics: function () { 7954 this.isLoaded(); 7955 return this.getData().statistics; 7956 }, 7957 7958 /** 7959 * Parses a uriString to retrieve the id portion 7960 * @param {String} uriString 7961 * @return {String} id 7962 */ 7963 _parseIdFromUriString : function (uriString) { 7964 return Utilities.getId(uriString); 7965 } 7966 7967 }); 7968 7969 window.finesse = window.finesse || {}; 7970 window.finesse.restservices = window.finesse.restservices || {}; 7971 window.finesse.restservices.Queue = Queue; 7972 7973 return Queue; 7974 })); 7975 7976 /** 7977 * JavaScript representation of the Finesse Queues collection 7978 * object which contains a list of Queue objects. 7979 * @requires finesse.clientservices.ClientServices 7980 * @requires Class 7981 * @requires finesse.FinesseBase 7982 * @requires finesse.restservices.RestBase 7983 * @requires finesse.restservices.RestCollectionBase 7984 */ 7985 7986 /** 7987 * @class 7988 * JavaScript representation of a Queues collection object. 7989 * 7990 * @constructor 7991 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 7992 */ 7993 7994 /** @private */ 7995 (function (factory) { 7996 7997 7998 // Define as an AMD module if possible 7999 if ( typeof define === 'function' && define.amd ) 8000 { 8001 define('restservices/Queues', ['restservices/RestCollectionBase', 8002 'restservices/Queue'], factory ); 8003 } 8004 /* Define using browser globals otherwise 8005 * Prevent multiple instantiations if the script is loaded twice 8006 */ 8007 else 8008 { 8009 factory(finesse.restservices.RestCollectionBase, finesse.restservices.Queue); 8010 } 8011 }(function (RestCollectionBase, Queue) { 8012 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 8013 8014 /** 8015 * @class 8016 * JavaScript representation of a Queues collection object. 8017 * @augments finesse.restservices.RestCollectionBase 8018 * @constructs 8019 * @see finesse.restservices.Queue 8020 * @example 8021 * _queues = _user.getQueues( { 8022 * onCollectionAdd : _handleQueueAdd, 8023 * onCollectionDelete : _handleQueueDelete, 8024 * onLoad : _handleQueuesLoaded 8025 * }); 8026 * 8027 * _queueCollection = _queues.getCollection(); 8028 * for (var queueId in _queueCollection) { 8029 * if (_queueCollection.hasOwnProperty(queueId)) { 8030 * _queue = _queueCollection[queueId]; 8031 * etc... 8032 * } 8033 * } 8034 */ 8035 _fakeConstuctor: function () { 8036 /* This is here to hide the real init constructor from the public docs */ 8037 }, 8038 8039 /** 8040 * @private 8041 * JavaScript representation of a Queues object. Also exposes 8042 * methods to operate on the object against the server. 8043 * 8044 * @param {Object} options 8045 * An object with the following properties:<ul> 8046 * <li><b>id:</b> The id of the object being constructed</li> 8047 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8048 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8049 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8050 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8051 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8052 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8053 * <li><b>content:</b> {String} Raw string of response</li> 8054 * <li><b>object:</b> {Object} Parsed object of response</li> 8055 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8056 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8057 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8058 * </ul></li> 8059 * </ul></li> 8060 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8061 **/ 8062 init: function (options) { 8063 this._super(options); 8064 }, 8065 8066 /** 8067 * @private 8068 * Gets xmpp node path. 8069 */ 8070 getXMPPNodePath: function () { 8071 return this.getRestUrl(); 8072 }, 8073 8074 /** 8075 * @private 8076 * Gets the REST class for the current object - this is the Queues class. 8077 */ 8078 getRestClass: function () { 8079 return Queues; 8080 }, 8081 8082 /** 8083 * @private 8084 * Gets the REST class for the objects that make up the collection. - this 8085 * is the Queue class. 8086 */ 8087 getRestItemClass: function () { 8088 return Queue; 8089 }, 8090 8091 /** 8092 * @private 8093 * Gets the REST type for the current object - this is a "Queues". 8094 */ 8095 getRestType: function () { 8096 return "Queues"; 8097 }, 8098 8099 /** 8100 * @private 8101 * Gets the REST type for the objects that make up the collection - this is "Queue". 8102 */ 8103 getRestItemType: function () { 8104 return "Queue"; 8105 }, 8106 8107 explicitSubscription: true 8108 }); 8109 8110 window.finesse = window.finesse || {}; 8111 window.finesse.restservices = window.finesse.restservices || {}; 8112 window.finesse.restservices.Queues = Queues; 8113 8114 return Queues; 8115 })); 8116 8117 /** 8118 * JavaScript representation of the Finesse WrapUpReason object. 8119 * 8120 * @requires finesse.clientservices.ClientServices 8121 * @requires Class 8122 * @requires finesse.FinesseBase 8123 * @requires finesse.restservices.RestBase 8124 */ 8125 8126 /** @private */ 8127 (function (factory) { 8128 8129 8130 // Define as an AMD module if possible 8131 if ( typeof define === 'function' && define.amd ) 8132 { 8133 define('restservices/WrapUpReason',['restservices/RestBase'], factory); 8134 } 8135 /* Define using browser globals otherwise 8136 * Prevent multiple instantiations if the script is loaded twice 8137 */ 8138 else 8139 { 8140 factory(finesse.restservices.RestBase); 8141 } 8142 }(function (RestBase) { 8143 8144 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 8145 8146 /** 8147 * @class 8148 * A WrapUpReason is a code and description identifying a particular reason that a 8149 * User is in WORK (WrapUp) mode. 8150 * 8151 * @augments finesse.restservices.RestBase 8152 * @see finesse.restservices.User 8153 * @see finesse.restservices.User.States#WORK 8154 * @constructs 8155 */ 8156 _fakeConstuctor: function () { 8157 /* This is here to hide the real init constructor from the public docs */ 8158 }, 8159 8160 /** 8161 * @private 8162 * JavaScript representation of a WrapUpReason object. Also exposes 8163 * methods to operate on the object against the server. 8164 * 8165 * @param {Object} options 8166 * An object with the following properties:<ul> 8167 * <li><b>id:</b> The id of the object being constructed</li> 8168 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8169 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8170 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8171 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8172 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8173 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8174 * <li><b>content:</b> {String} Raw string of response</li> 8175 * <li><b>object:</b> {Object} Parsed object of response</li> 8176 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8177 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8178 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8179 * </ul></li> 8180 * </ul></li> 8181 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8182 **/ 8183 init: function (options) { 8184 this._super(options); 8185 }, 8186 8187 /** 8188 * @private 8189 * Gets the REST class for the current object - this is the WrapUpReason class. 8190 * @returns {Object} The WrapUpReason class. 8191 */ 8192 getRestClass: function () { 8193 return WrapUpReason; 8194 }, 8195 8196 /** 8197 * @private 8198 * Gets the REST type for the current object - this is a "WrapUpReason". 8199 * @returns {String} The WrapUpReason string. 8200 */ 8201 getRestType: function () { 8202 return "WrapUpReason"; 8203 }, 8204 8205 /** 8206 * @private 8207 * Gets the REST type for the current object - this is a "WrapUpReasons". 8208 * @returns {String} The WrapUpReasons string. 8209 */ 8210 getParentRestType: function () { 8211 return "WrapUpReasons"; 8212 }, 8213 8214 /** 8215 * @private 8216 * Override default to indicate that this object doesn't support making 8217 * requests. 8218 */ 8219 supportsRequests: false, 8220 8221 /** 8222 * @private 8223 * Override default to indicate that this object doesn't support subscriptions. 8224 */ 8225 supportsSubscriptions: false, 8226 8227 /** 8228 * Getter for the label. 8229 * @returns {String} The label. 8230 */ 8231 getLabel: function () { 8232 this.isLoaded(); 8233 return this.getData().label; 8234 }, 8235 8236 /** 8237 * @private 8238 * Getter for the forAll flag. 8239 * @returns {Boolean} True if global. 8240 */ 8241 getForAll: function () { 8242 this.isLoaded(); 8243 return this.getData().forAll; 8244 }, 8245 8246 /** 8247 * @private 8248 * Getter for the Uri value. 8249 * @returns {String} The Uri. 8250 */ 8251 getUri: function () { 8252 this.isLoaded(); 8253 return this.getData().uri; 8254 } 8255 }); 8256 8257 window.finesse = window.finesse || {}; 8258 window.finesse.restservices = window.finesse.restservices || {}; 8259 window.finesse.restservices.WrapUpReason = WrapUpReason; 8260 8261 return WrapUpReason; 8262 })); 8263 8264 /** 8265 * JavaScript representation of the Finesse WrapUpReasons collection 8266 * object which contains a list of WrapUpReason objects. 8267 * 8268 * @requires finesse.clientservices.ClientServices 8269 * @requires Class 8270 * @requires finesse.FinesseBase 8271 * @requires finesse.restservices.RestBase 8272 * @requires finesse.restservices.Dialog 8273 * @requires finesse.restservices.RestCollectionBase 8274 */ 8275 8276 /** @private */ 8277 (function (factory) { 8278 8279 8280 // Define as an AMD module if possible 8281 if ( typeof define === 'function' && define.amd ) 8282 { 8283 define('restservices/WrapUpReasons', ['restservices/RestCollectionBase', 8284 'restservices/WrapUpReason'], factory ); 8285 } 8286 /* Define using browser globals otherwise 8287 * Prevent multiple instantiations if the script is loaded twice 8288 */ 8289 else 8290 { 8291 factory(finesse.restservices.RestCollectionBase, finesse.restservices.WrapUpReason); 8292 } 8293 }(function (RestCollectionBase, WrapUpReason) { 8294 8295 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 8296 8297 /** 8298 * @class 8299 * JavaScript representation of a WrapUpReasons collection object. 8300 * @augments finesse.restservices.RestCollectionBase 8301 * @constructs 8302 * @see finesse.restservices.WrapUpReason 8303 * @example 8304 * _wrapUpReasons = _user.getWrapUpReasons ( { 8305 * onCollectionAdd : _handleWrapUpReasonAdd, 8306 * onCollectionDelete : _handleWrapUpReasonDelete, 8307 * onLoad : _handleWrapUpReasonsLoaded 8308 * }); 8309 * 8310 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 8311 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 8312 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 8313 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 8314 * etc... 8315 * } 8316 * } 8317 */ 8318 _fakeConstuctor: function () { 8319 /* This is here to hide the real init constructor from the public docs */ 8320 }, 8321 8322 /** 8323 * @private 8324 * JavaScript representation of a WrapUpReasons collection object. Also exposes 8325 * methods to operate on the object against the server. 8326 * 8327 * @param {Object} options 8328 * An object with the following properties:<ul> 8329 * <li><b>id:</b> The id of the object being constructed</li> 8330 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8331 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8332 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8333 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8334 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8335 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8336 * <li><b>content:</b> {String} Raw string of response</li> 8337 * <li><b>object:</b> {Object} Parsed object of response</li> 8338 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8339 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8340 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8341 * </ul></li> 8342 * </ul></li> 8343 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8344 **/ 8345 init: function (options) { 8346 this._super(options); 8347 }, 8348 8349 /** 8350 * @private 8351 * Gets the REST class for the current object - this is the WrapUpReasons class. 8352 */ 8353 getRestClass: function () { 8354 return WrapUpReasons; 8355 }, 8356 8357 /** 8358 * @private 8359 * Gets the REST class for the objects that make up the collection. - this 8360 * is the WrapUpReason class. 8361 */ 8362 getRestItemClass: function () { 8363 return WrapUpReason; 8364 }, 8365 8366 /** 8367 * @private 8368 * Gets the REST type for the current object - this is a "WrapUpReasons". 8369 */ 8370 getRestType: function () { 8371 return "WrapUpReasons"; 8372 }, 8373 8374 /** 8375 * @private 8376 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 8377 */ 8378 getRestItemType: function () { 8379 return "WrapUpReason"; 8380 }, 8381 8382 /** 8383 * @private 8384 * Override default to indicates that the collection supports making 8385 * requests. 8386 */ 8387 supportsRequests: true, 8388 8389 /** 8390 * @private 8391 * Override default to indicate that this object doesn't support subscriptions. 8392 */ 8393 supportsRestItemSubscriptions: false, 8394 8395 /** 8396 * @private 8397 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 8398 * 8399 * @returns {finesse.restservices.WrapUpReasons} 8400 * This ReadyReasonCodes object to allow cascading. 8401 */ 8402 get: function () { 8403 // set loaded to false so it will rebuild the collection after the get 8404 this._loaded = false; 8405 // reset collection 8406 this._collection = {}; 8407 // perform get 8408 this._synchronize(); 8409 return this; 8410 } 8411 8412 }); 8413 8414 window.finesse = window.finesse || {}; 8415 window.finesse.restservices = window.finesse.restservices || {}; 8416 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 8417 8418 return WrapUpReasons; 8419 })); 8420 8421 /** 8422 * JavaScript representation of the Finesse Contact object. 8423 * @requires finesse.clientservices.ClientServices 8424 * @requires Class 8425 * @requires finesse.FinesseBase 8426 * @requires finesse.restservices.RestBase 8427 */ 8428 /** @private */ 8429 (function (factory) { 8430 8431 8432 // Define as an AMD module if possible 8433 if ( typeof define === 'function' && define.amd ) 8434 { 8435 define('restservices/Contact',['restservices/RestBase'], factory); 8436 } 8437 /* Define using browser globals otherwise 8438 * Prevent multiple instantiations if the script is loaded twice 8439 */ 8440 else 8441 { 8442 factory(finesse.restservices.RestBase); 8443 } 8444 }(function (RestBase) { 8445 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 8446 8447 /** 8448 * @class 8449 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 8450 * a Phone Number, and a Description. 8451 * 8452 * @augments finesse.restservices.RestBase 8453 * @see finesse.restservices.PhoneBook 8454 * @constructs 8455 */ 8456 _fakeConstuctor: function () { 8457 /* This is here to hide the real init constructor from the public docs */ 8458 }, 8459 8460 /** 8461 * @private 8462 * @param {Object} options 8463 * An object with the following properties:<ul> 8464 * <li><b>id:</b> The id of the object being constructed</li> 8465 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8466 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8467 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8468 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8469 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8470 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8471 * <li><b>content:</b> {String} Raw string of response</li> 8472 * <li><b>object:</b> {Object} Parsed object of response</li> 8473 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8474 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8475 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8476 * </ul></li> 8477 * </ul></li> 8478 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8479 **/ 8480 init: function (options) { 8481 this._super(options); 8482 }, 8483 8484 /** 8485 * @private 8486 * Gets the REST class for the current object - this is the Contact class. 8487 * @returns {Object} The Contact class. 8488 */ 8489 getRestClass: function () { 8490 return Contact; 8491 }, 8492 8493 /** 8494 * @private 8495 * Gets the REST type for the current object - this is a "Contact". 8496 * @returns {String} The Contact string. 8497 */ 8498 getRestType: function () { 8499 return "Contact"; 8500 }, 8501 8502 /** 8503 * @private 8504 * Override default to indicate that this object doesn't support making 8505 * requests. 8506 */ 8507 supportsRequests: false, 8508 8509 /** 8510 * @private 8511 * Override default to indicate that this object doesn't support subscriptions. 8512 */ 8513 supportsSubscriptions: false, 8514 8515 /** 8516 * Getter for the firstName. 8517 * @returns {String} The firstName. 8518 */ 8519 getFirstName: function () { 8520 this.isLoaded(); 8521 return this.getData().firstName; 8522 }, 8523 8524 /** 8525 * Getter for the lastName. 8526 * @returns {String} The lastName. 8527 */ 8528 getLastName: function () { 8529 this.isLoaded(); 8530 return this.getData().lastName; 8531 }, 8532 8533 /** 8534 * Getter for the phoneNumber. 8535 * @returns {String} The phoneNumber. 8536 */ 8537 getPhoneNumber: function () { 8538 this.isLoaded(); 8539 return this.getData().phoneNumber; 8540 }, 8541 8542 /** 8543 * Getter for the description. 8544 * @returns {String} The description. 8545 */ 8546 getDescription: function () { 8547 this.isLoaded(); 8548 return this.getData().description; 8549 }, 8550 8551 /** @private */ 8552 createPutSuccessHandler: function(contact, contentBody, successHandler){ 8553 return function (rsp) { 8554 // Update internal structure based on response. Here we 8555 // inject the contentBody from the PUT request into the 8556 // rsp.object element to mimic a GET as a way to take 8557 // advantage of the existing _processResponse method. 8558 rsp.object = contentBody; 8559 contact._processResponse(rsp); 8560 8561 //Remove the injected Contact object before cascading response 8562 rsp.object = {}; 8563 8564 //cascade response back to consumer's response handler 8565 successHandler(rsp); 8566 }; 8567 }, 8568 8569 /** @private */ 8570 createPostSuccessHandler: function (contact, contentBody, successHandler) { 8571 return function (rsp) { 8572 rsp.object = contentBody; 8573 contact._processResponse(rsp); 8574 8575 //Remove the injected Contact object before cascading response 8576 rsp.object = {}; 8577 8578 //cascade response back to consumer's response handler 8579 successHandler(rsp); 8580 }; 8581 }, 8582 8583 /** 8584 * Add 8585 * @private 8586 */ 8587 add: function (newValues, handlers) { 8588 // this.isLoaded(); 8589 var contentBody = {}; 8590 8591 contentBody[this.getRestType()] = { 8592 "firstName": newValues.firstName, 8593 "lastName": newValues.lastName, 8594 "phoneNumber": newValues.phoneNumber, 8595 "description": newValues.description 8596 }; 8597 8598 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8599 handlers = handlers || {}; 8600 8601 this.restRequest(this.getRestUrl(), { 8602 method: 'POST', 8603 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 8604 error: handlers.error, 8605 content: contentBody 8606 }); 8607 8608 return this; // Allow cascading 8609 }, 8610 8611 /** 8612 * Update 8613 * @private 8614 */ 8615 update: function (newValues, handlers) { 8616 this.isLoaded(); 8617 var contentBody = {}; 8618 8619 contentBody[this.getRestType()] = { 8620 "uri": this.getId(), 8621 "firstName": newValues.firstName, 8622 "lastName": newValues.lastName, 8623 "phoneNumber": newValues.phoneNumber, 8624 "description": newValues.description 8625 }; 8626 8627 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8628 handlers = handlers || {}; 8629 8630 this.restRequest(this.getRestUrl(), { 8631 method: 'PUT', 8632 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 8633 error: handlers.error, 8634 content: contentBody 8635 }); 8636 8637 return this; // Allow cascading 8638 }, 8639 8640 8641 /** 8642 * Delete 8643 * @private 8644 */ 8645 "delete": function ( handlers) { 8646 this.isLoaded(); 8647 8648 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8649 handlers = handlers || {}; 8650 8651 this.restRequest(this.getRestUrl(), { 8652 method: 'DELETE', 8653 success: this.createPutSuccessHandler(this, {}, handlers.success), 8654 error: handlers.error, 8655 content: undefined 8656 }); 8657 8658 return this; // Allow cascading 8659 } 8660 }); 8661 8662 window.finesse = window.finesse || {}; 8663 window.finesse.restservices = window.finesse.restservices || {}; 8664 window.finesse.restservices.Contact = Contact; 8665 8666 return Contact; 8667 })); 8668 8669 /** 8670 * JavaScript representation of the Finesse Contacts collection 8671 * object which contains a list of Contact objects. 8672 * 8673 * @requires finesse.clientservices.ClientServices 8674 * @requires Class 8675 * @requires finesse.FinesseBase 8676 * @requires finesse.restservices.RestBase 8677 * @requires finesse.restservices.Dialog 8678 * @requires finesse.restservices.RestCollectionBase 8679 */ 8680 /** @private */ 8681 (function (factory) { 8682 8683 8684 // Define as an AMD module if possible 8685 if ( typeof define === 'function' && define.amd ) 8686 { 8687 define('restservices/Contacts', ['restservices/RestCollectionBase', 8688 'restservices/Contact'], factory ); 8689 } 8690 /* Define using browser globals otherwise 8691 * Prevent multiple instantiations if the script is loaded twice 8692 */ 8693 else 8694 { 8695 factory(finesse.restservices.RestCollectionBase, finesse.restservices.Contact); 8696 } 8697 }(function (RestCollectionBase, Contact) { 8698 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 8699 8700 /** 8701 * @class 8702 * JavaScript representation of a Contacts collection object. Also exposes 8703 * methods to operate on the object against the server. 8704 * @augments finesse.restservices.RestCollectionBase 8705 * @constructs 8706 * @see finesse.restservices.Contact 8707 * @see finesse.restservices.PhoneBook 8708 * @example 8709 * _contacts = _phonebook.getContacts( { 8710 * onCollectionAdd : _handleContactAdd, 8711 * onCollectionDelete : _handleContactDelete, 8712 * onLoad : _handleContactsLoaded 8713 * }); 8714 * 8715 * _contactCollection = _contacts.getCollection(); 8716 * for (var contactId in _contactCollection) { 8717 * if (_contactCollection.hasOwnProperty(contactId)) { 8718 * _contact = _contactCollection[contactId]; 8719 * etc... 8720 * } 8721 * } 8722 */ 8723 _fakeConstuctor: function () { 8724 /* This is here to hide the real init constructor from the public docs */ 8725 }, 8726 8727 /** 8728 * @private 8729 * JavaScript representation of a Contacts collection object. Also exposes 8730 * methods to operate on the object against the server. 8731 * 8732 * @param {Object} options 8733 * An object with the following properties:<ul> 8734 * <li><b>id:</b> The id of the object being constructed</li> 8735 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8736 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8737 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8738 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8739 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8740 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8741 * <li><b>content:</b> {String} Raw string of response</li> 8742 * <li><b>object:</b> {Object} Parsed object of response</li> 8743 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8744 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8745 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8746 * </ul></li> 8747 * </ul></li> 8748 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8749 **/ 8750 init: function (options) { 8751 this._super(options); 8752 }, 8753 8754 /** 8755 * @private 8756 * Gets the REST class for the current object - this is the Contacts class. 8757 */ 8758 getRestClass: function () { 8759 return Contacts; 8760 }, 8761 8762 /** 8763 * @private 8764 * Gets the REST class for the objects that make up the collection. - this 8765 * is the Contact class. 8766 */ 8767 getRestItemClass: function () { 8768 return Contact; 8769 }, 8770 8771 /** 8772 * @private 8773 * Gets the REST type for the current object - this is a "Contacts". 8774 */ 8775 getRestType: function () { 8776 return "Contacts"; 8777 }, 8778 8779 /** 8780 * @private 8781 * Gets the REST type for the objects that make up the collection - this is "Contacts". 8782 */ 8783 getRestItemType: function () { 8784 return "Contact"; 8785 }, 8786 8787 /** 8788 * @private 8789 * Override default to indicates that the collection supports making 8790 * requests. 8791 */ 8792 supportsRequests: true, 8793 8794 /** 8795 * @private 8796 * Override default to indicates that the collection subscribes to its objects. 8797 */ 8798 supportsRestItemSubscriptions: false, 8799 8800 /** 8801 * @private 8802 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 8803 * 8804 * @returns {finesse.restservices.Contacts} 8805 * This Contacts object, to allow cascading. 8806 */ 8807 get: function () { 8808 // set loaded to false so it will rebuild the collection after the get 8809 this._loaded = false; 8810 // reset collection 8811 this._collection = {}; 8812 // perform get 8813 this._synchronize(); 8814 return this; 8815 } 8816 8817 }); 8818 8819 window.finesse = window.finesse || {}; 8820 window.finesse.restservices = window.finesse.restservices || {}; 8821 window.finesse.restservices.Contacts = Contacts; 8822 8823 8824 return Contacts; 8825 })); 8826 8827 /** 8828 * JavaScript representation of the Finesse PhoneBook object. 8829 * 8830 * @requires finesse.clientservices.ClientServices 8831 * @requires Class 8832 * @requires finesse.FinesseBase 8833 * @requires finesse.restservices.RestBase 8834 */ 8835 8836 /** @private */ 8837 (function (factory) { 8838 8839 8840 // Define as an AMD module if possible 8841 if ( typeof define === 'function' && define.amd ) 8842 { 8843 define('restservices/PhoneBook',['restservices/RestBase', 8844 'restservices/Contacts'], factory); 8845 } 8846 /* Define using browser globals otherwise 8847 * Prevent multiple instantiations if the script is loaded twice 8848 */ 8849 else 8850 { 8851 factory(finesse.restservices.RestBase, 8852 finesse.restservices.Contacts); 8853 } 8854 }(function (RestBase, Contacts) { 8855 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 8856 8857 _contacts: null, 8858 8859 /** 8860 * @class 8861 * A PhoneBook is a list of Contacts available to a User for quick dial. 8862 * 8863 * @augments finesse.restservices.RestBase 8864 * @see finesse.restservices.Contacts 8865 * @constructs 8866 */ 8867 _fakeConstuctor: function () { 8868 /* This is here to hide the real init constructor from the public docs */ 8869 }, 8870 8871 /** 8872 * @private 8873 * JavaScript representation of a PhoneBook object. Also exposes 8874 * methods to operate on the object against the server. 8875 * 8876 * @param {Object} options 8877 * An object with the following properties:<ul> 8878 * <li><b>id:</b> The id of the object being constructed</li> 8879 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8880 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8881 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8882 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8883 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8884 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8885 * <li><b>content:</b> {String} Raw string of response</li> 8886 * <li><b>object:</b> {Object} Parsed object of response</li> 8887 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8888 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8889 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8890 * </ul></li> 8891 * </ul></li> 8892 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8893 **/ 8894 init: function (options) { 8895 this._super(options); 8896 }, 8897 8898 /** 8899 * @private 8900 * Gets the REST class for the current object - this is the PhoneBook class. 8901 * @returns {Object} The PhoneBook class. 8902 */ 8903 getRestClass: function () { 8904 return PhoneBook; 8905 }, 8906 8907 /** 8908 * @private 8909 * Gets the REST type for the current object - this is a "PhoneBook". 8910 * @returns {String} The PhoneBook string. 8911 */ 8912 getRestType: function () { 8913 return "PhoneBook"; 8914 }, 8915 8916 /** 8917 * @private 8918 * Override default to indicate that this object doesn't support making 8919 * requests. 8920 */ 8921 supportsRequests: false, 8922 8923 /** 8924 * @private 8925 * Override default to indicate that this object doesn't support subscriptions. 8926 */ 8927 supportsSubscriptions: false, 8928 8929 /** 8930 * Getter for the name of the Phone Book. 8931 * @returns {String} The name. 8932 */ 8933 getName: function () { 8934 this.isLoaded(); 8935 return this.getData().name; 8936 }, 8937 8938 /** 8939 * Getter for the type flag. 8940 * @returns {String} The type. 8941 */ 8942 getType: function () { 8943 this.isLoaded(); 8944 return this.getData().type; 8945 }, 8946 8947 /** 8948 * @private 8949 * Getter for the Uri value. 8950 * @returns {String} The Uri. 8951 */ 8952 getUri: function () { 8953 this.isLoaded(); 8954 return this.getData().uri; 8955 }, 8956 8957 /** 8958 * Getter for a Contacts collection object that is associated with PhoneBook. 8959 * @param {finesse.interfaces.RequestHandlers} handlers 8960 * An object containing the handlers for the request 8961 * @returns {finesse.restservices.Contacts} 8962 * A Contacts collection object. 8963 */ 8964 getContacts: function (callbacks) { 8965 var options = callbacks || {}; 8966 options.parentObj = this; 8967 this.isLoaded(); 8968 8969 if (this._contacts === null) { 8970 this._contacts = new Contacts(options); 8971 } 8972 8973 return this._contacts; 8974 }, 8975 8976 /** 8977 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 8978 * @returns {String} uri to contacts 8979 * or {finesse.restservices.Contacts} collection 8980 */ 8981 getEmbeddedContacts: function(){ 8982 this.isLoaded(); 8983 return this.getData().contacts; 8984 }, 8985 8986 /** @private */ 8987 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 8988 return function (rsp) { 8989 // Update internal structure based on response. Here we 8990 // inject the contentBody from the PUT request into the 8991 // rsp.object element to mimic a GET as a way to take 8992 // advantage of the existing _processResponse method. 8993 rsp.object = contentBody; 8994 phonebook._processResponse(rsp); 8995 8996 //Remove the injected PhoneBook object before cascading response 8997 rsp.object = {}; 8998 8999 //cascade response back to consumer's response handler 9000 successHandler(rsp); 9001 }; 9002 }, 9003 9004 /** @private */ 9005 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 9006 return function (rsp) { 9007 rsp.object = contentBody; 9008 phonebook._processResponse(rsp); 9009 9010 //Remove the injected PhoneBook object before cascading response 9011 rsp.object = {}; 9012 9013 //cascade response back to consumer's response handler 9014 successHandler(rsp); 9015 }; 9016 }, 9017 9018 /** 9019 * @private 9020 * Add a PhoneBook. 9021 * @param {Object} newValues 9022 * @param {String} newValues.name Name of PhoneBook 9023 * @param {String} newValues.type Type of PhoneBook 9024 * @param {finesse.interfaces.RequestHandlers} handlers 9025 * An object containing the handlers for the request 9026 * @returns {finesse.restservices.PhoneBook} 9027 * This PhoneBook object, to allow cascading 9028 */ 9029 add: function (newValues, handlers) { 9030 // this.isLoaded(); 9031 var contentBody = {}; 9032 9033 contentBody[this.getRestType()] = { 9034 "name": newValues.name, 9035 "type": newValues.type 9036 }; 9037 9038 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9039 handlers = handlers || {}; 9040 9041 this.restRequest(this.getRestUrl(), { 9042 method: 'POST', 9043 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9044 error: handlers.error, 9045 content: contentBody 9046 }); 9047 9048 return this; // Allow cascading 9049 }, 9050 9051 /** 9052 * @private 9053 * Update a PhoneBook. 9054 * @param {Object} newValues 9055 * @param {String} newValues.name Name of PhoneBook 9056 * @param {String} newValues.type Type of PhoneBook 9057 * @param {finesse.interfaces.RequestHandlers} handlers 9058 * An object containing the handlers for the request 9059 * @returns {finesse.restservices.PhoneBook} 9060 * This PhoneBook object, to allow cascading 9061 */ 9062 update: function (newValues, handlers) { 9063 this.isLoaded(); 9064 var contentBody = {}; 9065 9066 contentBody[this.getRestType()] = { 9067 "uri": this.getId(), 9068 "name": newValues.name, 9069 "type": newValues.type 9070 }; 9071 9072 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9073 handlers = handlers || {}; 9074 9075 this.restRequest(this.getRestUrl(), { 9076 method: 'PUT', 9077 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9078 error: handlers.error, 9079 content: contentBody 9080 }); 9081 9082 return this; // Allow cascading 9083 }, 9084 9085 9086 /** 9087 * Delete a PhoneBook. 9088 * @param {finesse.interfaces.RequestHandlers} handlers 9089 * An object containing the handlers for the request 9090 * @returns {finesse.restservices.PhoneBook} 9091 * This PhoneBook object, to allow cascading 9092 */ 9093 "delete": function ( handlers) { 9094 this.isLoaded(); 9095 9096 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9097 handlers = handlers || {}; 9098 9099 this.restRequest(this.getRestUrl(), { 9100 method: 'DELETE', 9101 success: this.createPutSuccessHandler(this, {}, handlers.success), 9102 error: handlers.error, 9103 content: undefined 9104 }); 9105 9106 return this; // Allow cascading 9107 } 9108 9109 9110 9111 }); 9112 9113 window.finesse = window.finesse || {}; 9114 window.finesse.restservices = window.finesse.restservices || {}; 9115 window.finesse.restservices.PhoneBook = PhoneBook; 9116 9117 return PhoneBook; 9118 })); 9119 9120 /** 9121 * JavaScript representation of the Finesse PhoneBooks collection 9122 * object which contains a list of PhoneBook objects. 9123 * 9124 * @requires finesse.clientservices.ClientServices 9125 * @requires Class 9126 * @requires finesse.FinesseBase 9127 * @requires finesse.restservices.RestBase 9128 * @requires finesse.restservices.Dialog 9129 * @requires finesse.restservices.RestCollectionBase 9130 */ 9131 /** @private */ 9132 (function (factory) { 9133 9134 9135 // Define as an AMD module if possible 9136 if ( typeof define === 'function' && define.amd ) 9137 { 9138 define('restservices/PhoneBooks', ['restservices/RestCollectionBase', 9139 'restservices/PhoneBook'], factory ); 9140 } 9141 /* Define using browser globals otherwise 9142 * Prevent multiple instantiations if the script is loaded twice 9143 */ 9144 else 9145 { 9146 factory(finesse.restservices.RestCollectionBase, finesse.restservices.PhoneBook); 9147 } 9148 }(function (RestCollectionBase, PhoneBook) { 9149 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 9150 9151 /** 9152 * @class 9153 * JavaScript representation of a PhoneBooks collection object. 9154 * @augments finesse.restservices.RestCollectionBase 9155 * @constructs 9156 * @see finesse.restservices.PhoneBook 9157 * @see finesse.restservices.Contacts 9158 * @see finesse.restservices.Contact 9159 * @example 9160 * _phoneBooks = _user.getPhoneBooks( { 9161 * onCollectionAdd : _handlePhoneBookAdd, 9162 * onCollectionDelete : _handlePhoneBookDelete, 9163 * onLoad : _handlePhoneBooksLoaded 9164 * }); 9165 * 9166 * _phoneBookCollection = _phoneBooks.getCollection(); 9167 * for (var phoneBookId in _phoneBookCollection) { 9168 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 9169 * _phoneBook = _phoneBookCollection[phoneBookId]; 9170 * etc... 9171 * } 9172 * } 9173 */ 9174 _fakeConstuctor: function () { 9175 /* This is here to hide the real init constructor from the public docs */ 9176 }, 9177 9178 /** 9179 * @private 9180 * 9181 * @param {Object} options 9182 * An object with the following properties:<ul> 9183 * <li><b>id:</b> The id of the object being constructed</li> 9184 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9185 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9186 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9187 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9188 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9189 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9190 * <li><b>content:</b> {String} Raw string of response</li> 9191 * <li><b>object:</b> {Object} Parsed object of response</li> 9192 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9193 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9194 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9195 * </ul></li> 9196 * </ul></li> 9197 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9198 **/ 9199 init: function (options) { 9200 this._super(options); 9201 }, 9202 9203 /** 9204 * @private 9205 * Gets the REST class for the current object - this is the PhoneBooks class. 9206 */ 9207 getRestClass: function () { 9208 return PhoneBooks; 9209 }, 9210 9211 /** 9212 * @private 9213 * Gets the REST class for the objects that make up the collection. - this 9214 * is the PhoneBook class. 9215 */ 9216 getRestItemClass: function () { 9217 return PhoneBook; 9218 }, 9219 9220 /** 9221 * @private 9222 * Gets the REST type for the current object - this is a "PhoneBooks". 9223 */ 9224 getRestType: function () { 9225 return "PhoneBooks"; 9226 }, 9227 9228 /** 9229 * @private 9230 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 9231 */ 9232 getRestItemType: function () { 9233 return "PhoneBook"; 9234 }, 9235 9236 /** 9237 * @private 9238 * Override default to indicates that the collection supports making 9239 * requests. 9240 */ 9241 supportsRequests: true, 9242 9243 /** 9244 * @private 9245 * Override default to indicates that the collection subscribes to its objects. 9246 */ 9247 supportsRestItemSubscriptions: false, 9248 9249 /** 9250 * @private 9251 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 9252 * 9253 * @returns {finesse.restservices.PhoneBooks} 9254 * This PhoneBooks object, to allow cascading. 9255 */ 9256 get: function () { 9257 // set loaded to false so it will rebuild the collection after the get 9258 this._loaded = false; 9259 // reset collection 9260 this._collection = {}; 9261 // perform get 9262 this._synchronize(); 9263 return this; 9264 } 9265 9266 }); 9267 9268 window.finesse = window.finesse || {}; 9269 window.finesse.restservices = window.finesse.restservices || {}; 9270 window.finesse.restservices.PhoneBooks = PhoneBooks; 9271 9272 return PhoneBooks; 9273 })); 9274 9275 /** 9276 * JavaScript representation of the Finesse WorkflowAction object. 9277 * 9278 * @requires finesse.clientservices.ClientServices 9279 * @requires Class 9280 * @requires finesse.FinesseBase 9281 * @requires finesse.restservices.RestBase 9282 */ 9283 9284 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 9285 /*global define,finesse */ 9286 9287 /** @private */ 9288 (function (factory) { 9289 9290 9291 // Define as an AMD module if possible 9292 if ( typeof define === 'function' && define.amd ) 9293 { 9294 define('restservices/WorkflowAction', ['restservices/RestBase'], factory ); 9295 } 9296 /* Define using browser globals otherwise 9297 * Prevent multiple instantiations if the script is loaded twice 9298 */ 9299 else 9300 { 9301 factory(finesse.restservices.RestBase); 9302 } 9303 }(function (RestBase) { 9304 9305 var WorkflowAction = RestBase.extend({ 9306 9307 _contacts: null, 9308 9309 actionTypes: [ 9310 { 9311 name: 'BROWSER_POP', 9312 params: [ 9313 { 9314 name: 'windowName', 9315 type: 'text' 9316 }, 9317 { 9318 name: 'path', 9319 type: 'systemVariableSingleLineEditor' 9320 } 9321 ] 9322 }, 9323 { 9324 name: 'HTTP_REQUEST', 9325 params: [ 9326 { 9327 name: 'method', 9328 type: 'dropdown', 9329 values: ['POST', 'PUT'] 9330 }, 9331 { 9332 name: 'location', 9333 type: 'dropdown', 9334 values: ['FINESSE', 'OTHER'] 9335 }, 9336 { 9337 name: 'contentType', 9338 type: 'text' 9339 }, 9340 { 9341 name: 'path', 9342 type: 'systemVariableSingleLineEditor' 9343 }, 9344 { 9345 name: 'body', 9346 type: 'systemVariableMultiLineEditor' 9347 } 9348 ] 9349 } 9350 // more action type definitions here 9351 ], 9352 9353 /** 9354 * @class 9355 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 9356 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 9357 * 9358 * @augments finesse.restservices.RestBase 9359 * @see finesse.restservices.Workflow 9360 * @constructs 9361 */ 9362 _fakeConstuctor: function () { 9363 /* This is here to hide the real init constructor from the public docs */ 9364 }, 9365 9366 /** 9367 * @private 9368 * JavaScript representation of a WorkflowAction object. Also exposes 9369 * methods to operate on the object against the server. 9370 * 9371 * @param {Object} options 9372 * An object with the following properties:<ul> 9373 * <li><b>id:</b> The id of the object being constructed</li> 9374 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9375 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9376 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9377 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9378 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9379 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9380 * <li><b>content:</b> {String} Raw string of response</li> 9381 * <li><b>object:</b> {Object} Parsed object of response</li> 9382 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9383 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9384 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9385 * </ul></li> 9386 * </ul></li> 9387 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9388 **/ 9389 init: function (options) { 9390 this._super(options); 9391 }, 9392 9393 /** 9394 * @private 9395 * Gets the REST class for the current object - this is the WorkflowAction class. 9396 * @returns {Object} The WorkflowAction class. 9397 */ 9398 getRestClass: function () { 9399 return finesse.restservices.WorkflowAction; 9400 }, 9401 9402 /** 9403 * @private 9404 * Gets the REST type for the current object - this is a "WorkflowAction". 9405 * @returns {String} The WorkflowAction string. 9406 */ 9407 getRestType: function () { 9408 return "WorkflowAction"; 9409 }, 9410 9411 /** 9412 * @private 9413 * Override default to indicate that this object doesn't support making 9414 * requests. 9415 */ 9416 supportsRequests: false, 9417 9418 /** 9419 * @private 9420 * Override default to indicate that this object doesn't support subscriptions. 9421 */ 9422 supportsSubscriptions: false, 9423 9424 /** 9425 * Getter for the name. 9426 * @returns {String} The name. 9427 */ 9428 getName: function () { 9429 this.isLoaded(); 9430 return this.getData().name; 9431 }, 9432 9433 /** 9434 * Getter for the type flag. 9435 * @returns {String} The type. 9436 */ 9437 getType: function () { 9438 this.isLoaded(); 9439 return this.getData().type; 9440 }, 9441 9442 /** 9443 * @private 9444 * Getter for the Uri value. 9445 * @returns {String} The Uri. 9446 */ 9447 getUri: function () { 9448 this.isLoaded(); 9449 return this.getData().uri; 9450 }, 9451 9452 /** 9453 * @private 9454 * Getter for the handledBy value. 9455 * @returns {String} handledBy. 9456 */ 9457 getHandledBy: function () { 9458 this.isLoaded(); 9459 return this.getData().handledBy; 9460 }, 9461 9462 /** 9463 * Getter for the parameters. 9464 * @returns {Object} key = param name, value = param value 9465 */ 9466 getParams: function () { 9467 var map = {}, 9468 params = this.getData().params.Param, 9469 i, 9470 param; 9471 9472 for(i=0; i<params.length; i+=1){ 9473 param = params[i]; 9474 map[param.name] = param.value || ""; 9475 } 9476 9477 return map; 9478 }, 9479 9480 /** 9481 * Getter for the ActionVariables 9482 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 9483 */ 9484 getActionVariables: function() { 9485 var map = {}, 9486 actionVariablesParent = this.getData().actionVariables, 9487 actionVariables, 9488 i, 9489 actionVariable; 9490 9491 if(actionVariablesParent === null || actionVariablesParent.length === 0){ 9492 return map; 9493 } 9494 actionVariables = actionVariablesParent.ActionVariable; 9495 9496 if(actionVariables.length > 0){ 9497 for(i=0; i<actionVariables.length; i+=1){ 9498 actionVariable = actionVariables[i]; 9499 // escape nulls to empty string 9500 actionVariable.name = actionVariable.name || ""; 9501 actionVariable.type = actionVariable.type || ""; 9502 actionVariable.node = actionVariable.node || ""; 9503 actionVariable.testValue = actionVariable.testValue || ""; 9504 map[actionVariable.name] = actionVariable; 9505 } 9506 } else { 9507 map[actionVariables.name] = actionVariables; 9508 } 9509 9510 return map; 9511 }, 9512 9513 /** @private */ 9514 createPutSuccessHandler: function(action, contentBody, successHandler){ 9515 return function (rsp) { 9516 // Update internal structure based on response. Here we 9517 // inject the contentBody from the PUT request into the 9518 // rsp.object element to mimic a GET as a way to take 9519 // advantage of the existing _processResponse method. 9520 rsp.object = contentBody; 9521 action._processResponse(rsp); 9522 9523 //Remove the injected WorkflowAction object before cascading response 9524 rsp.object = {}; 9525 9526 //cascade response back to consumer's response handler 9527 successHandler(rsp); 9528 }; 9529 }, 9530 9531 /** @private */ 9532 createPostSuccessHandler: function (action, contentBody, successHandler) { 9533 return function (rsp) { 9534 rsp.object = contentBody; 9535 action._processResponse(rsp); 9536 9537 //Remove the injected WorkflowAction object before cascading response 9538 rsp.object = {}; 9539 9540 //cascade response back to consumer's response handler 9541 successHandler(rsp); 9542 }; 9543 }, 9544 9545 /** 9546 * @private 9547 * Build params array out of all the values coming into add or update methods 9548 * paramMap is a map of params.. we need to translate it into an array of Param objects 9549 * where path and windowName are params for the BROWSER_POP type 9550 */ 9551 buildParamsForRest: function(paramMap){ 9552 var params = {"Param": []}, 9553 i; 9554 for(i in paramMap){ 9555 if(paramMap.hasOwnProperty(i)){ 9556 params.Param.push({name: i, value: paramMap[i]}); 9557 } 9558 } 9559 return params; 9560 }, 9561 9562 /** 9563 * @private 9564 * Build actionVariables array out of all the values coming into add or update methods 9565 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 9566 * where path and windowName are params for the BROWSER_POP type 9567 */ 9568 buildActionVariablesForRest: function(actionVariableMap){ 9569 var actionVariables = {"ActionVariable": []}, 9570 i, 9571 actionVariable; 9572 for(i in actionVariableMap){ 9573 if(actionVariableMap.hasOwnProperty(i)){ 9574 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 9575 actionVariable = { 9576 "name": actionVariableMap[i].name, 9577 "type": actionVariableMap[i].type, 9578 "node": actionVariableMap[i].node, 9579 "testValue": actionVariableMap[i].testValue 9580 }; 9581 actionVariables.ActionVariable.push(actionVariable); 9582 } 9583 } 9584 return actionVariables; 9585 }, 9586 9587 /** 9588 * Add 9589 */ 9590 add: function (newValues, handlers) { 9591 var contentBody = {}; 9592 9593 contentBody[this.getRestType()] = { 9594 "name": newValues.name, 9595 "type": newValues.type, 9596 "handledBy": newValues.handledBy, 9597 "params": this.buildParamsForRest(newValues.params), 9598 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 9599 }; 9600 9601 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9602 handlers = handlers || {}; 9603 9604 this.restRequest(this.getRestUrl(), { 9605 method: 'POST', 9606 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9607 error: handlers.error, 9608 content: contentBody 9609 }); 9610 9611 return this; // Allow cascading 9612 }, 9613 9614 /** 9615 * @private 9616 * Update 9617 */ 9618 update: function (newValues, handlers) { 9619 this.isLoaded(); 9620 var contentBody = {}; 9621 9622 contentBody[this.getRestType()] = { 9623 "uri": this.getId(), 9624 "name": newValues.name, 9625 "type": newValues.type, 9626 "handledBy": newValues.handledBy, 9627 "params": this.buildParamsForRest(newValues.params), 9628 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 9629 }; 9630 9631 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9632 handlers = handlers || {}; 9633 9634 this.restRequest(this.getRestUrl(), { 9635 method: 'PUT', 9636 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9637 error: handlers.error, 9638 content: contentBody 9639 }); 9640 9641 return this; // Allow cascading 9642 }, 9643 9644 9645 /** 9646 * @private 9647 * Delete 9648 */ 9649 "delete": function ( handlers) { 9650 this.isLoaded(); 9651 9652 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9653 handlers = handlers || {}; 9654 9655 this.restRequest(this.getRestUrl(), { 9656 method: 'DELETE', 9657 success: this.createPutSuccessHandler(this, {}, handlers.success), 9658 error: handlers.error, 9659 content: undefined 9660 }); 9661 9662 return this; // Allow cascading 9663 } 9664 9665 9666 9667 }); 9668 9669 window.finesse = window.finesse || {}; 9670 window.finesse.restservices = window.finesse.restservices || {}; 9671 window.finesse.restservices.WorkflowAction = WorkflowAction; 9672 9673 return WorkflowAction; 9674 })); 9675 9676 /** 9677 * JavaScript representation of the Finesse WorkflowActions collection 9678 * object which contains a list of WorkflowAction objects. 9679 * 9680 * @requires finesse.clientservices.ClientServices 9681 * @requires Class 9682 * @requires finesse.FinesseBase 9683 * @requires finesse.restservices.RestBase 9684 * @requires finesse.restservices.Dialog 9685 * @requires finesse.restservices.RestCollectionBase 9686 */ 9687 9688 /** @private */ 9689 (function (factory) { 9690 9691 9692 // Define as an AMD module if possible 9693 if ( typeof define === 'function' && define.amd ) 9694 { 9695 define('restservices/WorkflowActions', ['restservices/RestCollectionBase', 9696 'restservices/RestBase', 9697 'restservices/WorkflowAction'], factory ); 9698 } 9699 /* Define using browser globals otherwise 9700 * Prevent multiple instantiations if the script is loaded twice 9701 */ 9702 else 9703 { 9704 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.WorkflowAction); 9705 } 9706 }(function (RestCollectionBase, RestBase, WorkflowAction) { 9707 9708 var WorkflowActions = RestCollectionBase.extend({ 9709 9710 /** 9711 * @class 9712 * JavaScript representation of a WorkflowActions collection object. 9713 * @augments finesse.restservices.RestCollectionBase 9714 * @constructs 9715 * @see finesse.restservices.WorkflowAction 9716 * @see finesse.restservices.Workflow 9717 * @see finesse.restservices.Workflows 9718 * @example 9719 * _workflowActions = _user.getWorkflowActions( { 9720 * onCollectionAdd : _handleWorkflowActionAdd, 9721 * onCollectionDelete : _handleWorkflowActionDelete, 9722 * onLoad : _handleWorkflowActionsLoaded 9723 * }); 9724 */ 9725 _fakeConstuctor: function () { 9726 /* This is here to hide the real init constructor from the public docs */ 9727 }, 9728 9729 /** 9730 * @private 9731 * JavaScript representation of a WorkflowActions collection object. Also exposes 9732 * methods to operate on the object against the server. 9733 * 9734 * @param {Object} options 9735 * An object with the following properties:<ul> 9736 * <li><b>id:</b> The id of the object being constructed</li> 9737 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9738 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9739 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9740 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9741 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9742 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9743 * <li><b>content:</b> {String} Raw string of response</li> 9744 * <li><b>object:</b> {Object} Parsed object of response</li> 9745 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9746 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9747 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9748 * </ul></li> 9749 * </ul></li> 9750 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9751 **/ 9752 init: function (options) { 9753 this._super(options); 9754 }, 9755 9756 /** 9757 * @private 9758 * Gets the REST class for the current object - this is the WorkflowActions class. 9759 */ 9760 getRestClass: function () { 9761 return WorkflowActions; 9762 }, 9763 9764 /** 9765 * @private 9766 * Gets the REST class for the objects that make up the collection. - this 9767 * is the WorkflowAction class. 9768 */ 9769 getRestItemClass: function () { 9770 return WorkflowAction; 9771 }, 9772 9773 /** 9774 * @private 9775 * Gets the REST type for the current object - this is a "WorkflowActions". 9776 */ 9777 getRestType: function () { 9778 return "WorkflowActions"; 9779 }, 9780 9781 /** 9782 * @private 9783 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 9784 */ 9785 getRestItemType: function () { 9786 return "WorkflowAction"; 9787 }, 9788 9789 /** 9790 * @private 9791 * Override default to indicates that the collection supports making 9792 * requests. 9793 */ 9794 supportsRequests: true, 9795 9796 /** 9797 * @private 9798 * Override default to indicates that the collection subscribes to its objects. 9799 */ 9800 supportsRestItemSubscriptions: false, 9801 9802 /** 9803 * @private 9804 * Retrieve the WorkflowActions. 9805 * 9806 * @returns {finesse.restservices.WorkflowActions} 9807 * This WorkflowActions object to allow cascading. 9808 */ 9809 get: function () { 9810 // set loaded to false so it will rebuild the collection after the get 9811 this._loaded = false; 9812 // reset collection 9813 this._collection = {}; 9814 // perform get 9815 this._synchronize(); 9816 return this; 9817 } 9818 }); 9819 9820 window.finesse = window.finesse || {}; 9821 window.finesse.restservices = window.finesse.restservices || {}; 9822 window.finesse.restservices.WorkflowActions = WorkflowActions; 9823 9824 return WorkflowActions; 9825 })); 9826 9827 /** 9828 * JavaScript representation of the Finesse Workflow object. 9829 * 9830 * @requires finesse.clientservices.ClientServices 9831 * @requires Class 9832 * @requires finesse.FinesseBase 9833 * @requires finesse.restservices.RestBase 9834 */ 9835 9836 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 9837 /*global define,finesse */ 9838 9839 /** @private */ 9840 (function (factory) { 9841 9842 9843 if (typeof define === 'function' && define.amd) { 9844 // Define as an AMD module if possible 9845 define('restservices/Workflow',['restservices/RestBase', 9846 'restservices/WorkflowActions'], factory); 9847 } else { 9848 /* Define using browser globals otherwise 9849 * Prevent multiple instantiations if the script is loaded twice 9850 */ 9851 factory(finesse.restservices.RestBase, 9852 finesse.restservices.WorkflowActions); 9853 } 9854 }(function (RestBase, WorkflowActions) { 9855 9856 var Workflow = RestBase.extend({ 9857 9858 /** 9859 * @class 9860 * JavaScript representation of a Workflow object. Also exposes 9861 * methods to operate on the object against the server. 9862 * 9863 * @param {Object} options 9864 * An object with the following properties:<ul> 9865 * <li><b>id:</b> The id of the object being constructed</li> 9866 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9867 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9868 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9869 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9870 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9871 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9872 * <li><b>content:</b> {String} Raw string of response</li> 9873 * <li><b>object:</b> {Object} Parsed object of response</li> 9874 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9875 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9876 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9877 * </ul></li> 9878 * </ul></li> 9879 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9880 * @constructs 9881 **/ 9882 init: function (options) { 9883 this._super(options); 9884 }, 9885 9886 /** 9887 * @private 9888 * Gets the REST class for the current object - this is the Workflow class. 9889 * @returns {Object} The Workflow class. 9890 */ 9891 getRestClass: function () { 9892 return Workflow; 9893 }, 9894 9895 /** 9896 * @private 9897 * Gets the REST type for the current object - this is a "Workflow". 9898 * @returns {String} The Workflow string. 9899 */ 9900 getRestType: function () { 9901 return "Workflow"; 9902 }, 9903 9904 /** 9905 * @private 9906 * Override default to indicate that this object doesn't support making 9907 * requests. 9908 */ 9909 supportsRequests: false, 9910 9911 /** 9912 * @private 9913 * Override default to indicate that this object doesn't support subscriptions. 9914 */ 9915 supportsSubscriptions: false, 9916 9917 /** 9918 * @private 9919 * Getter for the Uri value. 9920 * @returns {String} The Uri. 9921 */ 9922 getUri: function () { 9923 this.isLoaded(); 9924 return this.getData().uri; 9925 }, 9926 9927 /** 9928 * Getter for the name. 9929 * @returns {String} The name. 9930 */ 9931 getName: function () { 9932 this.isLoaded(); 9933 return this.getData().name; 9934 }, 9935 9936 /** 9937 * Getter for the description. 9938 * @returns {String} The description. 9939 */ 9940 getDescription: function () { 9941 this.isLoaded(); 9942 return this.getData().description; 9943 }, 9944 9945 /** 9946 * Getter for the trigger set. 9947 * @returns {String} The trigger set. 9948 */ 9949 getTriggerSet: function () { 9950 this.isLoaded(); 9951 return this.getData().TriggerSet; 9952 }, 9953 9954 /** 9955 * Getter for the condition set. 9956 * @returns {String} The condition set. 9957 */ 9958 getConditionSet: function () { 9959 this.isLoaded(); 9960 return this.getData().ConditionSet; 9961 }, 9962 9963 /** 9964 * Getter for the assigned workflowActions. 9965 * @returns {String} The workflowActions object. 9966 */ 9967 getWorkflowActions: function () { 9968 this.isLoaded(); 9969 var workflowActions = this.getData().workflowActions; 9970 if (workflowActions === null) { 9971 workflowActions = ""; 9972 } 9973 return workflowActions; 9974 }, 9975 9976 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 9977 return function (rsp) { 9978 // Update internal structure based on response. Here we 9979 // inject the contentBody from the PUT request into the 9980 // rsp.object element to mimic a GET as a way to take 9981 // advantage of the existing _processResponse method. 9982 rsp.object = contentBody; 9983 workflow._processResponse(rsp); 9984 9985 //Remove the injected Workflow object before cascading response 9986 rsp.object = {}; 9987 9988 //cascade response back to consumer's response handler 9989 successHandler(rsp); 9990 }; 9991 }, 9992 9993 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 9994 return function (rsp) { 9995 rsp.object = contentBody; 9996 workflow._processResponse(rsp); 9997 9998 //Remove the injected Workflow object before cascading response 9999 rsp.object = {}; 10000 10001 //cascade response back to consumer's response handler 10002 successHandler(rsp); 10003 }; 10004 }, 10005 10006 /** 10007 * @private 10008 * Add 10009 */ 10010 add: function (newValues, handlers) { 10011 // this.isLoaded(); 10012 var contentBody = {}; 10013 10014 contentBody[this.getRestType()] = { 10015 "name": newValues.name, 10016 "description": newValues.description, 10017 "TriggerSet" : newValues.TriggerSet, 10018 "ConditionSet" : newValues.ConditionSet, 10019 "workflowActions" : newValues.workflowActions 10020 }; 10021 10022 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10023 handlers = handlers || {}; 10024 10025 this.restRequest(this.getRestUrl(), { 10026 method: 'POST', 10027 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10028 error: handlers.error, 10029 content: contentBody 10030 }); 10031 10032 return this; // Allow cascading 10033 }, 10034 10035 /** 10036 * @private 10037 * Update 10038 */ 10039 update: function (newValues, handlers) { 10040 this.isLoaded(); 10041 var contentBody = {}; 10042 10043 contentBody[this.getRestType()] = { 10044 "uri": this.getId(), 10045 "name": newValues.name, 10046 "description": newValues.description, 10047 "TriggerSet" : newValues.TriggerSet, 10048 "ConditionSet" : newValues.ConditionSet, 10049 "workflowActions" : newValues.workflowActions 10050 }; 10051 10052 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10053 handlers = handlers || {}; 10054 10055 this.restRequest(this.getRestUrl(), { 10056 method: 'PUT', 10057 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10058 error: handlers.error, 10059 content: contentBody 10060 }); 10061 10062 return this; // Allow cascading 10063 }, 10064 10065 10066 /** 10067 * @private 10068 * Delete 10069 */ 10070 "delete": function (handlers) { 10071 this.isLoaded(); 10072 10073 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10074 handlers = handlers || {}; 10075 10076 this.restRequest(this.getRestUrl(), { 10077 method: 'DELETE', 10078 success: this.createPutSuccessHandler(this, {}, handlers.success), 10079 error: handlers.error, 10080 content: undefined 10081 }); 10082 10083 return this; // Allow cascading 10084 } 10085 10086 10087 10088 }); 10089 10090 window.finesse = window.finesse || {}; 10091 window.finesse.restservices = window.finesse.restservices || {}; 10092 window.finesse.restservices.Workflow = Workflow; 10093 10094 return Workflow; 10095 })); 10096 10097 /** 10098 * JavaScript representation of the Finesse workflows collection 10099 * object which contains a list of workflow objects. 10100 * 10101 * @requires finesse.clientservices.ClientServices 10102 * @requires Class 10103 * @requires finesse.FinesseBase 10104 * @requires finesse.restservices.RestBase 10105 * @requires finesse.restservices.Dialog 10106 * @requires finesse.restservices.RestCollectionBase 10107 */ 10108 10109 /** @private */ 10110 (function (factory) { 10111 10112 10113 // Define as an AMD module if possible 10114 if ( typeof define === 'function' && define.amd ) 10115 { 10116 define('restservices/Workflows', ['restservices/RestCollectionBase', 10117 'restservices/RestBase', 10118 'restservices/Workflow'], factory ); 10119 } 10120 /* Define using browser globals otherwise 10121 * Prevent multiple instantiations if the script is loaded twice 10122 */ 10123 else 10124 { 10125 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.Workflow); 10126 } 10127 }(function (RestCollectionBase, RestBase, Workflow) { 10128 10129 var Workflows = RestCollectionBase.extend({ 10130 10131 /** 10132 * @class 10133 * JavaScript representation of a workflows collection object. Also exposes 10134 * methods to operate on the object against the server. 10135 * 10136 * @param {Object} options 10137 * An object with the following properties:<ul> 10138 * <li><b>id:</b> The id of the object being constructed</li> 10139 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10140 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10141 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10142 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10143 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10144 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10145 * <li><b>content:</b> {String} Raw string of response</li> 10146 * <li><b>object:</b> {Object} Parsed object of response</li> 10147 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10148 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10149 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10150 * </ul></li> 10151 * </ul></li> 10152 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10153 * @constructs 10154 **/ 10155 init: function (options) { 10156 this._super(options); 10157 }, 10158 10159 /** 10160 * @private 10161 * Gets the REST class for the current object - this is the workflows class. 10162 */ 10163 getRestClass: function () { 10164 return Workflows; 10165 }, 10166 10167 /** 10168 * @private 10169 * Gets the REST class for the objects that make up the collection. - this 10170 * is the workflow class. 10171 */ 10172 getRestItemClass: function () { 10173 return Workflow; 10174 }, 10175 10176 /** 10177 * @private 10178 * Gets the REST type for the current object - this is a "workflows". 10179 */ 10180 getRestType: function () { 10181 return "Workflows"; 10182 }, 10183 10184 /** 10185 * @private 10186 * Gets the REST type for the objects that make up the collection - this is "workflows". 10187 */ 10188 getRestItemType: function () { 10189 return "Workflow"; 10190 }, 10191 10192 /** 10193 * @private 10194 * Override default to indicates that the collection supports making requests. 10195 */ 10196 supportsRequests: true, 10197 10198 /** 10199 * @private 10200 * Override default to indicates that the collection does not subscribe to its objects. 10201 */ 10202 supportsRestItemSubscriptions: false, 10203 10204 /** 10205 * @private 10206 * Retrieve the workflows. This call will re-query the server and refresh the collection. 10207 * 10208 * @returns {finesse.restservices.workflows} 10209 * This workflows object to allow cascading. 10210 */ 10211 get: function () { 10212 // set loaded to false so it will rebuild the collection after the get 10213 this._loaded = false; 10214 // reset collection 10215 this._collection = {}; 10216 // perform get 10217 this._synchronize(); 10218 return this; 10219 } 10220 }); 10221 10222 window.finesse = window.finesse || {}; 10223 window.finesse.restservices = window.finesse.restservices || {}; 10224 window.finesse.restservices.Workflows = Workflows; 10225 10226 return Workflows; 10227 })); 10228 10229 /** 10230 * JavaScript representation of the Finesse MediaPropertiesLayout object 10231 * 10232 * @requires finesse.clientservices.ClientServices 10233 * @requires Class 10234 * @requires finesse.FinesseBase 10235 * @requires finesse.restservices.RestBase 10236 */ 10237 10238 /** The following comment is to prevent jslint errors about 10239 * using variables before they are defined. 10240 */ 10241 /*global finesse*/ 10242 10243 /** @private */ 10244 (function (factory) { 10245 10246 10247 // Define as an AMD module if possible 10248 if ( typeof define === 'function' && define.amd ) 10249 { 10250 define('restservices/MediaPropertiesLayout', ['restservices/RestBase'], factory ); 10251 } 10252 /* Define using browser globals otherwise 10253 * Prevent multiple instantiations if the script is loaded twice 10254 */ 10255 else 10256 { 10257 factory(finesse.restservices.RestBase); 10258 } 10259 }(function (RestBase) { 10260 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 10261 10262 /** 10263 * @class 10264 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 10265 * 10266 * @augments finesse.restservices.RestBase 10267 * @see finesse.restservices.Dialog#getMediaProperties 10268 * @see finesse.restservices.User#getMediaPropertiesLayout 10269 * @constructs 10270 */ 10271 _fakeConstuctor: function () { 10272 /* This is here to hide the real init constructor from the public docs */ 10273 }, 10274 10275 /** 10276 * @private 10277 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 10278 * methods to operate on the object against the server. 10279 * 10280 * @param {Object} options 10281 * An object with the following properties:<ul> 10282 * <li><b>id:</b> The id of the object being constructed</li> 10283 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10284 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10285 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10286 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10287 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10288 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10289 * <li><b>content:</b> {String} Raw string of response</li> 10290 * <li><b>object:</b> {Object} Parsed object of response</li> 10291 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10292 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10293 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10294 * </ul></li> 10295 * </ul></li> 10296 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10297 **/ 10298 init: function (options) { 10299 this._super(options); 10300 }, 10301 10302 /** 10303 * @private 10304 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 10305 */ 10306 getRestClass: function () { 10307 return MediaPropertiesLayout; 10308 }, 10309 10310 /** 10311 * @private 10312 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 10313 */ 10314 getRestType: function () { 10315 return "MediaPropertiesLayout"; 10316 }, 10317 10318 /** 10319 * @private 10320 * Overrides the parent class. Returns the url for the MediaPropertiesLayout resource 10321 */ 10322 getRestUrl: function () { 10323 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 10324 }, 10325 10326 /** 10327 * @private 10328 * Returns whether this object supports subscriptions 10329 */ 10330 supportsSubscriptions: false, 10331 10332 /** 10333 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 10334 * @returns {finesse.restservices.MediaPropertiesLayout} 10335 * This MediaPropertiesLayout object to allow cascading 10336 */ 10337 get: function () { 10338 this._synchronize(); 10339 10340 return this; //Allow cascading 10341 }, 10342 10343 /** 10344 * Gets the data for this object. 10345 * 10346 * Performs safe conversion from raw API data to ensure that the returned layout object 10347 * always has a header with correct entry fields, and exactly two columns with lists of entries. 10348 * 10349 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 10350 */ 10351 getData: function () { 10352 10353 var layout = this._data, result, _addColumnData; 10354 10355 result = this.getEmptyData(); 10356 10357 /** 10358 * @private 10359 */ 10360 _addColumnData = function (entryData, colIndex) { 10361 10362 if (!entryData) { 10363 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 10364 entryData = {}; 10365 } else if (entryData.mediaProperty) { 10366 //If entryData contains the keys for a single entry rather than being a collection of entries, 10367 //rewrite it to be a collection containing a single entry 10368 entryData = { "": entryData }; 10369 } 10370 10371 //Add each of the entries in the list to the column 10372 jQuery.each(entryData, function (i, entryData) { 10373 10374 //If the entry has no displayName specified, explicitly set it to the empty string 10375 if (!entryData.displayName) { 10376 entryData.displayName = ""; 10377 } 10378 10379 result.columns[colIndex].push(entryData); 10380 10381 }); 10382 10383 }; 10384 10385 //The header should only contain a single entry 10386 if (layout.header && layout.header.entry) { 10387 10388 //If the entry has no displayName specified, explicitly set it to the empty string 10389 if (!layout.header.entry.displayName) { 10390 layout.header.entry.displayName = ""; 10391 } 10392 10393 result.header = layout.header.entry; 10394 10395 } else { 10396 10397 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 10398 10399 } 10400 10401 //If the column object contains an entry object that wasn't part of a list of entries, 10402 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 10403 //Force the entry object to be the 2nd element in an otherwise-empty list. 10404 if (layout.column && layout.column.entry) { 10405 layout.column = [ 10406 null, 10407 { "entry": layout.column.entry } 10408 ]; 10409 } 10410 10411 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 10412 10413 //Render left column entries 10414 if (layout.column[0] && layout.column[0].entry) { 10415 _addColumnData(layout.column[0].entry, 0); 10416 } 10417 10418 //Render right column entries 10419 if (layout.column[1] && layout.column[1].entry) { 10420 _addColumnData(layout.column[1].entry, 1); 10421 } 10422 10423 } 10424 10425 return result; 10426 10427 }, 10428 10429 /** 10430 * @private 10431 * Empty/template version of getData(). 10432 * 10433 * Used by getData(), and by callers of getData() in error cases. 10434 */ 10435 getEmptyData: function () { 10436 10437 return { 10438 header : { 10439 displayName: null, 10440 mediaProperty: null 10441 }, 10442 columns : [[], []] 10443 }; 10444 10445 }, 10446 10447 /** 10448 * @private 10449 * Set the layout of this MediaPropertiesLayout. 10450 * @param {String} layout 10451 * The layout you are setting 10452 * @param {finesse.interfaces.RequestHandlers} handlers 10453 * An object containing the handlers for the request 10454 * @returns {finesse.restservices.MediaPropertiesLayout} 10455 * This MediaPropertiesLayout object to allow cascading 10456 */ 10457 setLayout: function (layout, handlers) { 10458 10459 var contentBody = {}; 10460 10461 contentBody[this.getRestType()] = layout; 10462 10463 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10464 handlers = handlers || {}; 10465 10466 this.restRequest(this.getRestUrl(), { 10467 method: 'PUT', 10468 success: handlers.success, 10469 error: handlers.error, 10470 content: contentBody 10471 }); 10472 10473 return this; // Allow cascading 10474 } 10475 10476 }); 10477 10478 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 10479 /** 10480 * @class Format of MediaPropertiesLayout Object.<br> 10481 * Object { <ul> 10482 * <li>header : { <ul> 10483 * <li>dispayName {String} 10484 * <li>mediaProperty {String}</ul>} 10485 * <li>columns : { <ul> 10486 * <li>[ [] , [] ] 10487 * </ul> 10488 * where column arrays consists of the same Object format as header.<br> 10489 * }</ul> 10490 * }<br> 10491 * @constructs 10492 */ 10493 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 10494 10495 }; 10496 10497 window.finesse = window.finesse || {}; 10498 window.finesse.restservices = window.finesse.restservices || {}; 10499 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 10500 10501 return MediaPropertiesLayout; 10502 })); 10503 10504 /** 10505 * JavaScript representation of the Finesse User object 10506 * 10507 * @requires finesse.clientservices.ClientServices 10508 * @requires Class 10509 * @requires finesse.FinesseBase 10510 * @requires finesse.restservices.RestBase 10511 */ 10512 10513 /** @private */ 10514 (function (factory) { 10515 10516 10517 // Define as an AMD module if possible 10518 if ( typeof define === 'function' && define.amd ) 10519 { 10520 define('restservices/User', ['restservices/RestBase', 10521 'restservices/Dialogs', 10522 'restservices/ClientLog', 10523 'restservices/Queues', 10524 'restservices/WrapUpReasons', 10525 'restservices/PhoneBooks', 10526 'restservices/Workflows', 10527 'restservices/MediaPropertiesLayout', 10528 'utilities/Utilities'], factory ); 10529 } 10530 /* Define using browser globals otherwise 10531 * Prevent multiple instantiations if the script is loaded twice 10532 */ 10533 else 10534 { 10535 factory(finesse.restservices.RestBase, 10536 finesse.restservices.Dialogs, 10537 finesse.restservices.ClientLog, 10538 finesse.restservices.Queues, 10539 finesse.restservices.WrapUpReasons, 10540 finesse.restservices.PhoneBooks, 10541 finesse.restservices.Workflows, 10542 finesse.restservices.MediaPropertiesLayout, 10543 finesse.utilities.Utilities); 10544 } 10545 }(function (RestBase, 10546 Dialogs, 10547 ClientLog, 10548 Queues, 10549 WrapUpReasons, 10550 PhoneBooks, 10551 Workflows, 10552 MediaPropertiesLayout, 10553 Utilities) { 10554 10555 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 10556 10557 _dialogs : null, 10558 _clientLogObj : null, 10559 _wrapUpReasons : null, 10560 _phoneBooks : null, 10561 _workflows : null, 10562 _mediaPropertiesLayout : null, 10563 _queues : null, 10564 10565 /** 10566 * @class 10567 * The User represents a Finesse Agent or Supervisor. 10568 * 10569 * @param {Object} options 10570 * An object with the following properties:<ul> 10571 * <li><b>id:</b> The id of the object being constructed</li> 10572 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 10573 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 10574 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 10575 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 10576 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 10577 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10578 * <li><b>content:</b> {String} Raw string of response</li> 10579 * <li><b>object:</b> {Object} Parsed object of response</li> 10580 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10581 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10582 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10583 * </ul></li> 10584 * </ul></li> 10585 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10586 * @augments finesse.restservices.RestBase 10587 * @constructs 10588 * @example 10589 * _user = new finesse.restservices.User({ 10590 * id: _id, 10591 * onLoad : _handleUserLoad, 10592 * onChange : _handleUserChange 10593 * }); 10594 **/ 10595 init: function (options) { 10596 this._super(options); 10597 }, 10598 10599 Callbacks: {}, 10600 10601 /** 10602 * @private 10603 * Gets the REST class for the current object - this is the User object. 10604 */ 10605 getRestClass: function () { 10606 return User; 10607 }, 10608 10609 /** 10610 * @private 10611 * Gets the REST type for the current object - this is a "User". 10612 */ 10613 getRestType: function () { 10614 return "User"; 10615 }, 10616 /** 10617 * @private 10618 * overloading this to return URI 10619 */ 10620 getXMPPNodePath: function () { 10621 return this.getRestUrl(); 10622 }, 10623 /** 10624 * @private 10625 * Returns whether this object supports subscriptions 10626 */ 10627 supportsSubscriptions: function () { 10628 return true; 10629 }, 10630 10631 /** 10632 * Getter for the firstName of this User. 10633 * @returns {String} 10634 * The firstName for this User 10635 */ 10636 getFirstName: function () { 10637 this.isLoaded(); 10638 return Utilities.convertNullToEmptyString(this.getData().firstName); 10639 }, 10640 10641 /** 10642 * Getter for the lastName of this User. 10643 * @returns {String} 10644 * The lastName for this User 10645 */ 10646 getLastName: function () { 10647 this.isLoaded(); 10648 return Utilities.convertNullToEmptyString(this.getData().lastName); 10649 }, 10650 10651 /** 10652 * Getter for the extension of this User. 10653 * @returns {String} 10654 * The extension, if any, of this User 10655 */ 10656 getExtension: function () { 10657 this.isLoaded(); 10658 return Utilities.convertNullToEmptyString(this.getData().extension); 10659 }, 10660 10661 /** 10662 * Getter for the id of the Team of this User 10663 * @returns {String} 10664 * The current (or last fetched) id of the Team of this User 10665 */ 10666 getTeamId: function () { 10667 this.isLoaded(); 10668 return this.getData().teamId; 10669 }, 10670 10671 /** 10672 * Getter for the name of the Team of this User 10673 * @returns {String} 10674 * The current (or last fetched) name of the Team of this User 10675 */ 10676 getTeamName: function () { 10677 this.isLoaded(); 10678 return this.getData().teamName; 10679 }, 10680 10681 /** 10682 * Is user an agent? 10683 * @returns {Boolean} True if user has role of agent, else false. 10684 */ 10685 hasAgentRole: function () { 10686 this.isLoaded(); 10687 return this.hasRole("Agent"); 10688 }, 10689 10690 /** 10691 * Is user a supervisor? 10692 * @returns {Boolean} True if user has role of supervisor, else false. 10693 */ 10694 hasSupervisorRole: function () { 10695 this.isLoaded(); 10696 return this.hasRole("Supervisor"); 10697 }, 10698 10699 /** 10700 * @private 10701 * Checks to see if user has "theRole" 10702 * @returns {Boolean} 10703 */ 10704 hasRole: function (theRole) { 10705 this.isLoaded(); 10706 var result = false, i, roles, len; 10707 10708 roles = this.getData().roles.role; 10709 len = roles.length; 10710 if (typeof roles === 'string') { 10711 if (roles === theRole) { 10712 result = true; 10713 } 10714 } else { 10715 for (i = 0; i < len ; i = i + 1) { 10716 if (roles[i] === theRole) { 10717 result = true; 10718 break; 10719 } 10720 } 10721 } 10722 10723 return result; 10724 }, 10725 10726 /** 10727 * Getter for the pending state of this User. 10728 * @returns {String} 10729 * The pending state of this User 10730 * @see finesse.restservices.User.States 10731 */ 10732 getPendingState: function () { 10733 this.isLoaded(); 10734 return Utilities.convertNullToEmptyString(this.getData().pendingState); 10735 }, 10736 10737 /** 10738 * Getter for the state of this User. 10739 * @returns {String} 10740 * The current (or last fetched) state of this User 10741 * @see finesse.restservices.User.States 10742 */ 10743 getState: function () { 10744 this.isLoaded(); 10745 return this.getData().state; 10746 }, 10747 10748 /** 10749 * Getter for the state change time of this User. 10750 * @returns {String} 10751 * The state change time of this User 10752 */ 10753 getStateChangeTime: function () { 10754 this.isLoaded(); 10755 return this.getData().stateChangeTime; 10756 }, 10757 10758 /** 10759 * Checks to see if the user is considered a mobile agent by checking for 10760 * the existence of the mobileAgent node. 10761 * @returns {Boolean} 10762 * True if this agent is a mobile agent. 10763 */ 10764 isMobileAgent: function () { 10765 this.isLoaded(); 10766 var ma = this.getData().mobileAgent; 10767 return ma !== null && typeof ma === "object"; 10768 }, 10769 10770 /** 10771 * Getter for the mobile agent work mode. 10772 * @returns {finesse.restservices.User.WorkMode} 10773 * If available, return the mobile agent work mode, otherwise null. 10774 */ 10775 getMobileAgentMode: function () { 10776 this.isLoaded(); 10777 if (this.isMobileAgent()) { 10778 return this.getData().mobileAgent.mode; 10779 } 10780 return null; 10781 }, 10782 10783 /** 10784 * Getter for the mobile agent dial number. 10785 * @returns {String} 10786 * If available, return the mobile agent dial number, otherwise null. 10787 */ 10788 getMobileAgentDialNumber: function () { 10789 this.isLoaded(); 10790 if (this.isMobileAgent()) { 10791 return this.getData().mobileAgent.dialNumber; 10792 } 10793 return null; 10794 }, 10795 10796 /** 10797 * Getter for a Dialogs collection object that is associated with User. 10798 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10799 * applicable when Object has not been previously created). 10800 * @returns {finesse.restservices.Dialogs} 10801 * A Dialogs collection object. 10802 */ 10803 getDialogs: function (callbacks) { 10804 var options = callbacks || {}; 10805 options.parentObj = this; 10806 this.isLoaded(); 10807 10808 if (this._dialogs === null) { 10809 this._dialogs = new Dialogs(options); 10810 } 10811 10812 return this._dialogs; 10813 }, 10814 10815 /** 10816 * @private 10817 * Getter for a ClientLog object that is associated with User. 10818 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10819 * applicable when Object has not been previously created). 10820 * @returns {finesse.restservices.ClientLog} 10821 * A ClientLog collection object. 10822 */ 10823 getClientLog: function (callbacks) { 10824 var options = callbacks || {}; 10825 options.parentObj = this; 10826 this.isLoaded(); 10827 10828 if (this._clientLogObj === null) { 10829 this._clientLogObj = new ClientLog(options); 10830 } 10831 else { 10832 if(options.onLoad && typeof options.onLoad === "function") { 10833 options.onLoad(this._clientLogObj); 10834 } 10835 } 10836 return this._clientLogObj; 10837 }, 10838 10839 /** 10840 * Getter for a Queues collection object that is associated with User. 10841 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10842 * applicable when Object has not been previously created). 10843 * @returns {finesse.restservices.Queues} 10844 * A Queues collection object. 10845 */ 10846 getQueues: function (callbacks) { 10847 var options = callbacks || {}; 10848 options.parentObj = this; 10849 this.isLoaded(); 10850 10851 if (this._queues === null) { 10852 this._queues = new Queues(options); 10853 } 10854 10855 return this._queues; 10856 }, 10857 10858 /** 10859 * Getter for a WrapUpReasons collection object that is associated with User. 10860 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10861 * applicable when Object has not been previously created). 10862 * @returns {finesse.restservices.WrapUpReasons} 10863 * A WrapUpReasons collection object. 10864 */ 10865 getWrapUpReasons: function (callbacks) { 10866 var options = callbacks || {}; 10867 options.parentObj = this; 10868 this.isLoaded(); 10869 10870 if (this._wrapUpReasons === null) { 10871 this._wrapUpReasons = new WrapUpReasons(options); 10872 } 10873 10874 return this._wrapUpReasons; 10875 }, 10876 10877 /** 10878 * Getter for a PhoneBooks collection object that is associated with User. 10879 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10880 * applicable when Object has not been previously created). 10881 * @returns {finesse.restservices.PhoneBooks} 10882 * A PhoneBooks collection object. 10883 */ 10884 getPhoneBooks: function (callbacks) { 10885 var options = callbacks || {}; 10886 options.parentObj = this; 10887 this.isLoaded(); 10888 10889 if (this._phoneBooks === null) { 10890 this._phoneBooks = new PhoneBooks(options); 10891 } 10892 10893 return this._phoneBooks; 10894 }, 10895 10896 /** 10897 * @private 10898 * Loads the Workflows collection object that is associated with User and 10899 * 'returns' them to the caller via the handlers. 10900 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10901 * applicable when Object has not been previously created). 10902 * @see finesse.restservices.Workflow 10903 * @see finesse.restservices.Workflows 10904 * @see finesse.restservices.RestCollectionBase 10905 */ 10906 loadWorkflows: function (callbacks) { 10907 var options = callbacks || {}; 10908 options.parentObj = this; 10909 this.isLoaded(); 10910 10911 if (this._workflows === null) { 10912 this._workflows = new Workflows(options); 10913 } else { 10914 this._workflows.refresh(); 10915 } 10916 10917 }, 10918 10919 /** 10920 * Getter for a MediaPropertiesLayout object that is associated with User. 10921 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 10922 * applicable when Object has not been previously created). 10923 * @returns {finesse.restservices.MediaPropertiesLayout} 10924 * The MediaPropertiesLayout object associated with this user 10925 */ 10926 getMediaPropertiesLayout: function (callbacks) { 10927 var options = callbacks || {}; 10928 options.parentObj = this; 10929 options.id = this._id; 10930 10931 this.isLoaded(); 10932 if (this._mediaPropertiesLayout === null) { 10933 this._mediaPropertiesLayout = new MediaPropertiesLayout(options); 10934 } 10935 return this._mediaPropertiesLayout; 10936 }, 10937 10938 /** 10939 * Getter for the supervised Teams this User (Supervisor) supervises, if any. 10940 * @see finesse.restservices.Team 10941 * @returns {Array} 10942 * An array of Teams supervised by this User (Supervisor) 10943 */ 10944 getSupervisedTeams: function () { 10945 this.isLoaded(); 10946 10947 try { 10948 return Utilities.getArray(this.getData().teams.Team); 10949 } catch (e) { 10950 return []; 10951 } 10952 10953 }, 10954 10955 /** 10956 * Perform an agent login for this user, associating him with the 10957 * specified extension. 10958 * @param {Object} params 10959 * An object containing properties for agent login. 10960 * @param {String} params.extension 10961 * The extension to associate with this user 10962 * @param {Object} [params.mobileAgent] 10963 * A mobile agent object containing the mode and dial number properties. 10964 * @param {finesse.interfaces.RequestHandlers} params.handlers 10965 * @see finesse.interfaces.RequestHandlers 10966 * @returns {finesse.restservices.User} 10967 * This User object, to allow cascading 10968 * @private 10969 */ 10970 _login: function (params) { 10971 var handlers, contentBody = {}, 10972 restType = this.getRestType(); 10973 10974 // Protect against null dereferencing. 10975 params = params || {}; 10976 10977 contentBody[restType] = { 10978 "state": User.States.LOGIN, 10979 "extension": params.extension 10980 }; 10981 10982 // Create mobile agent node if available. 10983 if (typeof params.mobileAgent === "object") { 10984 contentBody[restType].mobileAgent = { 10985 "mode": params.mobileAgent.mode, 10986 "dialNumber": params.mobileAgent.dialNumber 10987 }; 10988 } 10989 10990 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10991 handlers = params.handlers || {}; 10992 10993 this.restRequest(this.getRestUrl(), { 10994 method: 'PUT', 10995 success: handlers.success, 10996 error: handlers.error, 10997 content: contentBody 10998 }); 10999 11000 return this; // Allow cascading 11001 }, 11002 11003 /** 11004 * Perform an agent login for this user, associating him with the 11005 * specified extension. 11006 * @param {String} extension 11007 * The extension to associate with this user 11008 * @param {finesse.interfaces.RequestHandlers} handlers 11009 * An object containing the handlers for the request 11010 * @returns {finesse.restservices.User} 11011 * This User object, to allow cascading 11012 */ 11013 login: function (extension, handlers) { 11014 this.isLoaded(); 11015 var params = { 11016 "extension": extension, 11017 "handlers": handlers 11018 }; 11019 return this._login(params); 11020 }, 11021 11022 /** 11023 * Perform an agent login for this user, associating him with the 11024 * specified extension. 11025 * @param {String} extension 11026 * The extension to associate with this user 11027 * @param {String} mode 11028 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 11029 * @param {String} extension 11030 * The external dial number desired to be used by the mobile agent. 11031 * @param {finesse.interfaces.RequestHandlers} handlers 11032 * An object containing the handlers for the request 11033 * @returns {finesse.restservices.User} 11034 * This User object, to allow cascading 11035 */ 11036 loginMobileAgent: function (extension, mode, dialNumber, handlers) { 11037 this.isLoaded(); 11038 var params = { 11039 "extension": extension, 11040 "mobileAgent": { 11041 "mode": mode, 11042 "dialNumber": dialNumber 11043 }, 11044 "handlers": handlers 11045 }; 11046 return this._login(params); 11047 }, 11048 11049 /** 11050 * Perform an agent logout for this user. 11051 * @param {String} reasonCode 11052 * The reason this user is logging out. Pass null for no reason. 11053 * @param {finesse.interfaces.RequestHandlers} handlers 11054 * An object containing the handlers for the request 11055 * @returns {finesse.restservices.User} 11056 * This User object, to allow cascading 11057 */ 11058 logout: function (reasonCode, handlers) { 11059 return this.setState("LOGOUT", reasonCode, handlers); 11060 }, 11061 11062 /** 11063 * Set the state of the user. 11064 * @param {String} newState 11065 * The state you are setting 11066 * @param {ReasonCode} reasonCode 11067 * The reason this user is logging out. Pass null for no reason. 11068 * @param {finesse.interfaces.RequestHandlers} handlers 11069 * An object containing the handlers for the request 11070 * @see finesse.restservices.User.States 11071 * @returns {finesse.restservices.User} 11072 * This User object, to allow cascading 11073 */ 11074 setState: function (newState, reasonCode, handlers) { 11075 this.isLoaded(); 11076 11077 var options, contentBody = {}; 11078 11079 if (!reasonCode) { 11080 contentBody[this.getRestType()] = { 11081 "state": newState 11082 }; 11083 } else { 11084 contentBody[this.getRestType()] = { 11085 "state": newState, 11086 "reasonCodeId": reasonCode.id 11087 }; 11088 } 11089 11090 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11091 handlers = handlers || {}; 11092 11093 options = { 11094 method: 'PUT', 11095 success: handlers.success, 11096 error: handlers.error, 11097 content: contentBody 11098 }; 11099 11100 // After removing the selective 202 handling, we should be able to just use restRequest 11101 this.restRequest(this.getRestUrl(), options); 11102 11103 return this; // Allow cascading 11104 }, 11105 11106 /** 11107 * Make call to a particular phone number. 11108 * 11109 * @param {String} 11110 * The number to call 11111 * @param {finesse.interfaces.RequestHandlers} handlers 11112 * An object containing the handlers for the request 11113 * @returns {finesse.restservices.User} 11114 * This User object, to allow cascading 11115 */ 11116 makeCall: function (number, handlers) { 11117 this.isLoaded(); 11118 11119 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 11120 11121 return this; // Allow cascading 11122 }, 11123 11124 /** 11125 * Make a silent monitor call to a particular agent's phone number. 11126 * 11127 * @param {String} 11128 * The number to call 11129 * @param {finesse.interfaces.RequestHandlers} handlers 11130 * An object containing the handlers for the request 11131 * @returns {finesse.restservices.User} 11132 * This User object, to allow cascading 11133 */ 11134 makeSMCall: function (number, handlers) { 11135 this.isLoaded(); 11136 11137 var actionType = "SILENT_MONITOR"; 11138 11139 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 11140 11141 return this; // Allow cascading 11142 }, 11143 11144 11145 /** 11146 * Make a silent monitor call to a particular agent's phone number. 11147 * 11148 * @param {String} 11149 * The number to call 11150 * @param {String} dialogUri 11151 * The associated dialog uri of SUPERVISOR_MONITOR call 11152 * @param {finesse.interfaces.RequestHandlers} handlers 11153 * An object containing the handlers for the request 11154 * @see finesse.restservices.dialog 11155 * @returns {finesse.restservices.User} 11156 * This User object, to allow cascading 11157 */ 11158 makeBargeCall:function (number, dialogURI, handlers) { 11159 this.isLoaded(); 11160 var actionType = "BARGE_CALL"; 11161 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 11162 11163 return this; // Allow cascading 11164 }, 11165 11166 /** 11167 * Returns true if the user's current state will result in a pending state change. A pending state 11168 * change is a request to change state that does not result in an immediate state change. For 11169 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 11170 * agent will not change state until the call ends. 11171 * 11172 * The current set of states that result in pending state changes is as follows: 11173 * TALKING 11174 * HOLD 11175 * RESERVED_OUTBOUND_PREVIEW 11176 * @returns {Boolean} True if there is a pending state change. 11177 * @see finesse.restservices.User.States 11178 */ 11179 isPendingStateChange: function () { 11180 var state = this.getState(); 11181 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 11182 }, 11183 11184 /** 11185 * Returns true if the user's current state is WORK or WORK_READY. This is used so 11186 * that a pending state is not cleared when moving into wrap up (work) mode. 11187 * Note that we don't add this as a pending state, since changes while in wrap up 11188 * occur immediately (and we don't want any "pending state" to flash on screen. 11189 * 11190 * @see finesse.restservices.User.States 11191 * @returns {Boolean} True if user is in wrap-up mode. 11192 */ 11193 isWrapUp: function () { 11194 var state = this.getState(); 11195 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 11196 }, 11197 11198 /** 11199 * @private 11200 * Parses a uriString to retrieve the id portion 11201 * @param {String} uriString 11202 * @return {String} id 11203 */ 11204 _parseIdFromUriString : function (uriString) { 11205 return Utilities.getId(uriString); 11206 }, 11207 11208 /** 11209 * Gets the user's Not Ready reason code. 11210 * @return {String} Reason Code Id, or undefined if not set or indeterminate 11211 */ 11212 getNotReadyReasonCodeId : function () { 11213 this.isLoaded(); 11214 11215 var reasoncodeIdResult, finesseServerReasonCodeId; 11216 finesseServerReasonCodeId = this.getData().reasonCodeId; 11217 11218 //FinesseServer will give "-l" => we will set to undefined (for convenience) 11219 if (finesseServerReasonCodeId !== "-1") { 11220 reasoncodeIdResult = finesseServerReasonCodeId; 11221 } 11222 11223 return reasoncodeIdResult; 11224 }, 11225 11226 /** 11227 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 11228 * Note that there is no return value; use the success handler to process a 11229 * valid return. 11230 * @param {finesse.interfaces.RequestHandlers} handlers 11231 * An object containing the handlers for the request 11232 * @param {String} reasonCodeId The id for the reason code to lookup 11233 * 11234 */ 11235 getReasonCodeById : function (handlers, reasonCodeId) 11236 { 11237 var self = this, contentBody, reasonCode, url; 11238 contentBody = {}; 11239 11240 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 11241 this.restRequest(url, { 11242 method: 'GET', 11243 success: function (rsp) { 11244 reasonCode = { 11245 uri: rsp.object.ReasonCode.uri, 11246 label: rsp.object.ReasonCode.label, 11247 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 11248 }; 11249 handlers.success(reasonCode); 11250 }, 11251 error: function (rsp) { 11252 handlers.error(rsp); 11253 }, 11254 content: contentBody 11255 }); 11256 }, 11257 11258 /** 11259 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 11260 * @param {String} type (LOGOUT or NOT_READY) 11261 * @param {finesse.interfaces.RequestHandlers} handlers 11262 * An object containing the handlers for the request 11263 */ 11264 _getReasonCodesByType : function (type, handlers) 11265 { 11266 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 11267 11268 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 11269 this.restRequest(url, { 11270 method: 'GET', 11271 success: function (rsp) { 11272 reasonCodes = []; 11273 11274 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 11275 if (reasonCodeArray === undefined) { 11276 reasonCodes = undefined; 11277 } else if (reasonCodeArray[0] !== undefined) { 11278 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 11279 reasonCodes[i] = { 11280 label: rsp.object.ReasonCodes.ReasonCode[i].label, 11281 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 11282 }; 11283 } 11284 } else { 11285 reasonCodes[0] = { 11286 label: rsp.object.ReasonCodes.ReasonCode.label, 11287 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 11288 }; 11289 } 11290 handlers.success(reasonCodes); 11291 }, 11292 error: function (rsp) { 11293 handlers.error(rsp); 11294 }, 11295 content: contentBody 11296 }); 11297 }, 11298 11299 /** 11300 * Performs a GET against Finesse server retrieving all the Signout reason codes. 11301 * Note that there is no return value; use the success handler to process a 11302 * valid return. 11303 * @param {finesse.interfaces.RequestHandlers} handlers 11304 * An object containing the handlers for the request 11305 */ 11306 getSignoutReasonCodes : function (handlers) 11307 { 11308 this._getReasonCodesByType("LOGOUT", handlers); 11309 }, 11310 11311 /** 11312 * Performs a GET against Finesse server retrieving all the Not Ready reason codes. 11313 * Note that there is no return value; use the success handler to process a 11314 * valid return. 11315 * @param {finesse.interfaces.RequestHandlers} handlers 11316 * An object containing the handlers for the request 11317 */ 11318 getNotReadyReasonCodes : function (handlers) 11319 { 11320 this._getReasonCodesByType("NOT_READY", handlers); 11321 } 11322 }); 11323 11324 User.States = /** @lends finesse.restservices.User.States.prototype */ { 11325 /** 11326 * User Login. Note that while this is an action, is not technically a state, since a 11327 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 11328 */ 11329 LOGIN: "LOGIN", 11330 /** 11331 * User is logged out. 11332 */ 11333 LOGOUT: "LOGOUT", 11334 /** 11335 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 11336 */ 11337 NOT_READY: "NOT_READY", 11338 /** 11339 * User is ready for calls. 11340 */ 11341 READY: "READY", 11342 /** 11343 * User has a call coming in, but has not answered it. 11344 */ 11345 RESERVED: "RESERVED", 11346 /** 11347 * User has an outbound call being made, but has not been connected to it. 11348 */ 11349 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 11350 /** 11351 * User has an outbound call's preview information being displayed, but has not acted on it. 11352 */ 11353 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 11354 /** 11355 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 11356 */ 11357 TALKING: "TALKING", 11358 /** 11359 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 11360 */ 11361 HOLD: "HOLD", 11362 /** 11363 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 11364 */ 11365 WORK: "WORK", 11366 /** 11367 * This is the same as WORK, except that after time out user becomes READY. 11368 */ 11369 WORK_READY: "WORK_READY", 11370 /** 11371 * @class Possible User state values. 11372 * @constructs 11373 */ 11374 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11375 11376 }; 11377 11378 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 11379 /** 11380 * Mobile agent is connected (dialed) for each incoming call received. 11381 */ 11382 CALL_BY_CALL: "CALL_BY_CALL", 11383 /** 11384 * Mobile agent is connected (dialed) at login. 11385 */ 11386 NAILED_CONNECTION: "NAILED_CONNECTION", 11387 /** 11388 * @class Possible Mobile Agent Work Mode Types. 11389 * @constructs 11390 */ 11391 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11392 11393 }; 11394 11395 window.finesse = window.finesse || {}; 11396 window.finesse.restservices = window.finesse.restservices || {}; 11397 window.finesse.restservices.User = User; 11398 11399 return User; 11400 })); 11401 11402 /** 11403 * JavaScript representation of the Finesse Users collection 11404 * object which contains a list of Users objects. 11405 * 11406 * @requires finesse.clientservices.ClientServices 11407 * @requires Class 11408 * @requires finesse.FinesseBase 11409 * @requires finesse.restservices.RestBase 11410 * @requires finesse.restservices.RestCollectionBase 11411 * @requires finesse.restservices.User 11412 */ 11413 11414 /** @private */ 11415 (function (factory) { 11416 11417 11418 // Define as an AMD module if possible 11419 if ( typeof define === 'function' && define.amd ) 11420 { 11421 define('restservices/Users', ['restservices/RestCollectionBase', 11422 'restservices/RestBase', 11423 'restservices/User'], factory ); 11424 } 11425 /* Define using browser globals otherwise 11426 * Prevent multiple instantiations if the script is loaded twice 11427 */ 11428 else 11429 { 11430 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.User); 11431 } 11432 }(function (RestCollectionBase, RestBase, User) { 11433 11434 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 11435 11436 /** 11437 * @class 11438 * JavaScript representation of a Users collection object. 11439 * While there is no method provided to retrieve all Users, this collection is 11440 * used to return the Users in a supervised Team. 11441 * @augments finesse.restservices.RestCollectionBase 11442 * @constructs 11443 * @see finesse.restservices.Team 11444 * @see finesse.restservices.User 11445 * @see finesse.restservices.User#getSupervisedTeams 11446 * @example 11447 * // Note: The following method gets an Array of Teams, not a Collection. 11448 * _teams = _user.getSupervisedTeams(); 11449 * if (_teams.length > 0) { 11450 * _team0Users = _teams[0].getUsers(); 11451 * } 11452 */ 11453 _fakeConstuctor: function () { 11454 /* This is here to hide the real init constructor from the public docs */ 11455 }, 11456 11457 /** 11458 * @private 11459 * JavaScript representation of the Finesse Users collection 11460 * object which contains a list of Users objects. 11461 * 11462 * @param {Object} options 11463 * An object with the following properties:<ul> 11464 * <li><b>id:</b> The id of the object being constructed</li> 11465 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11466 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11467 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11468 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11469 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11470 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11471 * <li><b>content:</b> {String} Raw string of response</li> 11472 * <li><b>object:</b> {Object} Parsed object of response</li> 11473 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11474 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11475 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11476 * </ul></li> 11477 * </ul></li> 11478 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11479 **/ 11480 init: function (options) { 11481 this._super(options); 11482 }, 11483 11484 /** 11485 * @private 11486 * Gets the REST class for the current object - this is the Users class. 11487 */ 11488 getRestClass: function () { 11489 return Users; 11490 }, 11491 11492 /** 11493 * @private 11494 * Gets the REST class for the objects that make up the collection. - this 11495 * is the User class. 11496 */ 11497 getRestItemClass: function () { 11498 return User; 11499 }, 11500 11501 /** 11502 * @private 11503 * Gets the REST type for the current object - this is a "Users". 11504 */ 11505 getRestType: function () { 11506 return "Users"; 11507 }, 11508 11509 /** 11510 * @private 11511 * Gets the REST type for the objects that make up the collection - this is "User". 11512 */ 11513 getRestItemType: function () { 11514 return "User"; 11515 }, 11516 11517 /** 11518 * @private 11519 * Gets the node path for the current object - this is the team Users node 11520 * @returns {String} The node path 11521 */ 11522 getXMPPNodePath: function () { 11523 return this.getRestUrl(); 11524 }, 11525 11526 /** 11527 * @private 11528 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 11529 * This needs to be done because the GET /Team/id/Users API is missing 11530 * @returns {Users} This Users (collection) object to allow cascading 11531 */ 11532 _doGET: function (handlers) { 11533 var _this = this; 11534 handlers = handlers || {}; 11535 // Only do this for /Team/id/Users 11536 if (this._restObj && this._restObj.getRestType() === "Team") { 11537 this._restObj._doGET({ 11538 success: function (rspObj) { 11539 // Making sure the response was a valid Team 11540 if (_this._restObj._validate(rspObj.object)) { 11541 // Shimmying the response to look like a Users collection by extracting it from the Team response 11542 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 11543 handlers.success(rspObj); 11544 } else { 11545 handlers.error(rspObj); 11546 } 11547 }, 11548 error: handlers.error 11549 }); 11550 return this; // Allow cascading 11551 } else { 11552 return this._super(handlers); 11553 } 11554 }, 11555 11556 /** 11557 * @private 11558 * Override default to indicates that the collection doesn't support making 11559 * requests. 11560 */ 11561 supportsRequests: false, 11562 11563 /** 11564 * @private 11565 * Indicates that this collection handles the subscription for its items 11566 */ 11567 handlesItemSubscription: true, 11568 11569 /** 11570 * @private 11571 * Override default to indicate that we need to subscribe explicitly 11572 */ 11573 explicitSubscription: true 11574 11575 }); 11576 11577 window.finesse = window.finesse || {}; 11578 window.finesse.restservices = window.finesse.restservices || {}; 11579 window.finesse.restservices.Users = Users; 11580 11581 return Users; 11582 })); 11583 11584 /** 11585 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 11586 * 11587 * @requires finesse.clientservices.ClientServices 11588 * @requires Class 11589 * @requires finesse.FinesseBase 11590 * @requires finesse.restservices.RestBase 11591 */ 11592 11593 /** @private */ 11594 (function (factory) { 11595 11596 11597 // Define as an AMD module if possible 11598 if ( typeof define === 'function' && define.amd ) 11599 { 11600 define('restservices/TeamNotReadyReasonCode', ['restservices/RestBase'], factory ); 11601 } 11602 /* Define using browser globals otherwise 11603 * Prevent multiple instantiations if the script is loaded twice 11604 */ 11605 else 11606 { 11607 factory(finesse.restservices.RestBase); 11608 } 11609 }(function (RestBase) { 11610 11611 var TeamNotReadyReasonCode = RestBase.extend( { 11612 11613 /** 11614 * @class 11615 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 11616 * methods to operate on the object against the server. 11617 * 11618 * @param {Object} options 11619 * An object with the following properties:<ul> 11620 * <li><b>id:</b> The id of the object being constructed</li> 11621 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11622 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11623 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11624 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11625 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11626 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11627 * <li><b>content:</b> {String} Raw string of response</li> 11628 * <li><b>object:</b> {Object} Parsed object of response</li> 11629 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11630 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11631 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11632 * </ul></li> 11633 * </ul></li> 11634 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11635 * @constructs 11636 **/ 11637 init: function (options) { 11638 this._super(options); 11639 }, 11640 11641 /** 11642 * @private 11643 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 11644 * @returns {Object} The TeamNotReadyReasonCode class. 11645 */ 11646 getRestClass: function () { 11647 return TeamNotReadyReasonCode; 11648 }, 11649 11650 /** 11651 * @private 11652 * Gets the REST type for the current object - this is a "ReasonCode". 11653 * @returns {String} The ReasonCode string. 11654 */ 11655 getRestType: function () { 11656 return "ReasonCode"; 11657 }, 11658 11659 /** 11660 * @private 11661 * Override default to indicate that this object doesn't support making 11662 * requests. 11663 */ 11664 supportsRequests: false, 11665 11666 /** 11667 * @private 11668 * Override default to indicate that this object doesn't support subscriptions. 11669 */ 11670 supportsSubscriptions: false, 11671 11672 /** 11673 * Getter for the category. 11674 * @returns {String} The category. 11675 */ 11676 getCategory: function () { 11677 this.isLoaded(); 11678 return this.getData().category; 11679 }, 11680 11681 /** 11682 * Getter for the code. 11683 * @returns {String} The code. 11684 */ 11685 getCode: function () { 11686 this.isLoaded(); 11687 return this.getData().code; 11688 }, 11689 11690 /** 11691 * Getter for the label. 11692 * @returns {String} The label. 11693 */ 11694 getLabel: function () { 11695 this.isLoaded(); 11696 return this.getData().label; 11697 }, 11698 11699 /** 11700 * Getter for the forAll value. 11701 * @returns {String} The forAll. 11702 */ 11703 getForAll: function () { 11704 this.isLoaded(); 11705 return this.getData().forAll; 11706 }, 11707 11708 /** 11709 * Getter for the Uri value. 11710 * @returns {String} The Uri. 11711 */ 11712 getUri: function () { 11713 this.isLoaded(); 11714 return this.getData().uri; 11715 } 11716 11717 }); 11718 11719 window.finesse = window.finesse || {}; 11720 window.finesse.restservices = window.finesse.restservices || {}; 11721 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 11722 11723 return TeamNotReadyReasonCode; 11724 })); 11725 11726 /** 11727 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 11728 * object which contains a list of TeamNotReadyReasonCode objects. 11729 * 11730 * @requires finesse.clientservices.ClientServices 11731 * @requires Class 11732 * @requires finesse.FinesseBase 11733 * @requires finesse.restservices.RestBase 11734 * @requires finesse.restservices.Dialog 11735 * @requires finesse.restservices.RestCollectionBase 11736 */ 11737 11738 /** @private */ 11739 (function (factory) { 11740 11741 11742 // Define as an AMD module if possible 11743 if ( typeof define === 'function' && define.amd ) 11744 { 11745 define('restservices/TeamNotReadyReasonCodes', ['restservices/RestCollectionBase', 11746 'restservices/RestBase', 11747 'restservices/TeamNotReadyReasonCode'], factory ); 11748 } 11749 /* Define using browser globals otherwise 11750 * Prevent multiple instantiations if the script is loaded twice 11751 */ 11752 else 11753 { 11754 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.TeamNotReadyReasonCode); 11755 } 11756 }(function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 11757 11758 var TeamNotReadyReasonCodes = RestCollectionBase.extend( { 11759 11760 /** 11761 * @class 11762 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 11763 * methods to operate on the object against the server. 11764 * 11765 * @param {Object} options 11766 * An object with the following properties:<ul> 11767 * <li><b>id:</b> The id of the object being constructed</li> 11768 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11769 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11770 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11771 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11772 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11773 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11774 * <li><b>content:</b> {String} Raw string of response</li> 11775 * <li><b>object:</b> {Object} Parsed object of response</li> 11776 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11777 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11778 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11779 * </ul></li> 11780 * </ul></li> 11781 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11782 * @augments finesse.restservices.RestCollectionBase 11783 * @constructs 11784 **/ 11785 init: function (options) { 11786 this._super(options); 11787 }, 11788 11789 /** 11790 * @private 11791 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 11792 */ 11793 getRestClass: function () { 11794 return TeamNotReadyReasonCodes; 11795 }, 11796 11797 /** 11798 * @private 11799 * Gets the REST class for the objects that make up the collection. - this 11800 * is the TeamNotReadyReasonCode class. 11801 */ 11802 getRestItemClass: function () { 11803 return TeamNotReadyReasonCode; 11804 }, 11805 11806 /** 11807 * @private 11808 * Gets the REST type for the current object - this is a "ReasonCodes". 11809 */ 11810 getRestType: function () { 11811 return "ReasonCodes"; 11812 }, 11813 11814 /** 11815 * @private 11816 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 11817 */ 11818 getRestUrl: function () { 11819 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 11820 var restObj = this._restObj, 11821 restUrl = ""; 11822 //Prepend the base REST object if one was provided. 11823 //Otherwise prepend with the default webapp name. 11824 if (restObj instanceof RestBase) { 11825 restUrl += restObj.getRestUrl(); 11826 } 11827 else { 11828 restUrl += "/finesse/api"; 11829 } 11830 //Append the REST type. 11831 restUrl += "/ReasonCodes?category=NOT_READY"; 11832 //Append ID if it is not undefined, null, or empty. 11833 if (this._id) { 11834 restUrl += "/" + this._id; 11835 } 11836 return restUrl; 11837 }, 11838 11839 /** 11840 * @private 11841 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 11842 */ 11843 getRestItemType: function () { 11844 return "ReasonCode"; 11845 }, 11846 11847 /** 11848 * @private 11849 * Override default to indicates that the collection supports making 11850 * requests. 11851 */ 11852 supportsRequests: true, 11853 11854 /** 11855 * @private 11856 * Override default to indicate that this object doesn't support subscriptions. 11857 */ 11858 supportsRestItemSubscriptions: false, 11859 11860 /** 11861 * @private 11862 * Retrieve the Not Ready Reason Codes. 11863 * 11864 * @returns {TeamNotReadyReasonCodes} 11865 * This TeamNotReadyReasonCodes object to allow cascading. 11866 */ 11867 get: function () { 11868 // set loaded to false so it will rebuild the collection after the get 11869 this._loaded = false; 11870 // reset collection 11871 this._collection = {}; 11872 // perform get 11873 this._synchronize(); 11874 return this; 11875 }, 11876 11877 /** 11878 * @private 11879 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 11880 * @param {Object} reasonCodes 11881 * @param {String} contentBody 11882 * @param successHandler 11883 * @return {function} 11884 */ 11885 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 11886 return function (rsp) { 11887 // Update internal structure based on response. Here we 11888 // inject the contentBody from the PUT request into the 11889 // rsp.object element to mimic a GET as a way to take 11890 // advantage of the existing _processResponse method. 11891 rsp.object = contentBody; 11892 reasonCodes._processResponse(rsp); 11893 11894 //Remove the injected contentBody object before cascading response 11895 rsp.object = {}; 11896 11897 //cascade response back to consumer's response handler 11898 successHandler(rsp); 11899 }; 11900 }, 11901 11902 /** 11903 * @private 11904 * Perform the REST API PUT call to update the reason code assignments for the team 11905 * @param {string[]} newValues 11906 * @param handlers 11907 */ 11908 update: function (newValues, handlers) { 11909 this.isLoaded(); 11910 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 11911 11912 contentBody[this.getRestType()] = { 11913 }; 11914 11915 for (i in newValues) { 11916 if (newValues.hasOwnProperty(i)) { 11917 innerObject = { 11918 "uri": newValues[i] 11919 }; 11920 contentBodyInner.push(innerObject); 11921 } 11922 } 11923 11924 contentBody[this.getRestType()] = { 11925 "ReasonCode" : contentBodyInner 11926 }; 11927 11928 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11929 handlers = handlers || {}; 11930 11931 this.restRequest(this.getRestUrl(), { 11932 method: 'PUT', 11933 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11934 error: handlers.error, 11935 content: contentBody 11936 }); 11937 11938 return this; // Allow cascading 11939 } 11940 }); 11941 11942 window.finesse = window.finesse || {}; 11943 window.finesse.restservices = window.finesse.restservices || {}; 11944 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 11945 11946 return TeamNotReadyReasonCodes; 11947 })); 11948 11949 /** 11950 * JavaScript representation of the Finesse Team Wrap Up Reason object. 11951 * 11952 * @requires finesse.clientservices.ClientServices 11953 * @requires Class 11954 * @requires finesse.FinesseBase 11955 * @requires finesse.restservices.RestBase 11956 */ 11957 /** @private */ 11958 (function (factory) { 11959 11960 11961 // Define as an AMD module if possible 11962 if ( typeof define === 'function' && define.amd ) 11963 { 11964 define('restservices/TeamWrapUpReason',['restservices/RestBase'], factory); 11965 } 11966 /* Define using browser globals otherwise 11967 * Prevent multiple instantiations if the script is loaded twice 11968 */ 11969 else 11970 { 11971 factory(finesse.restservices.RestBase); 11972 } 11973 }(function (RestBase) { 11974 11975 var TeamWrapUpReason = RestBase.extend({ 11976 11977 /** 11978 * @class 11979 * JavaScript representation of a TeamWrapUpReason object. Also exposes 11980 * methods to operate on the object against the server. 11981 * 11982 * @param {Object} options 11983 * An object with the following properties:<ul> 11984 * <li><b>id:</b> The id of the object being constructed</li> 11985 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11986 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11987 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11988 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11989 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11990 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11991 * <li><b>content:</b> {String} Raw string of response</li> 11992 * <li><b>object:</b> {Object} Parsed object of response</li> 11993 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11994 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11995 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11996 * </ul></li> 11997 * </ul></li> 11998 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11999 * @constructs 12000 **/ 12001 init: function (options) { 12002 this._super(options); 12003 }, 12004 12005 /** 12006 * @private 12007 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 12008 * @returns {Object} The TeamWrapUpReason class. 12009 */ 12010 getRestClass: function () { 12011 return TeamWrapUpReason; 12012 }, 12013 12014 /** 12015 * @private 12016 * Gets the REST type for the current object - this is a "WrapUpReason". 12017 * @returns {String} The WrapUpReason string. 12018 */ 12019 getRestType: function () { 12020 return "WrapUpReason"; 12021 }, 12022 12023 /** 12024 * @private 12025 * Override default to indicate that this object doesn't support making 12026 * requests. 12027 */ 12028 supportsRequests: false, 12029 12030 /** 12031 * @private 12032 * Override default to indicate that this object doesn't support subscriptions. 12033 */ 12034 supportsSubscriptions: false, 12035 12036 /** 12037 * Getter for the label. 12038 * @returns {String} The label. 12039 */ 12040 getLabel: function () { 12041 this.isLoaded(); 12042 return this.getData().label; 12043 }, 12044 12045 /** 12046 * @private 12047 * Getter for the forAll value. 12048 * @returns {Boolean} True if global 12049 */ 12050 getForAll: function () { 12051 this.isLoaded(); 12052 return this.getData().forAll; 12053 }, 12054 12055 /** 12056 * @private 12057 * Getter for the Uri value. 12058 * @returns {String} The Uri. 12059 */ 12060 getUri: function () { 12061 this.isLoaded(); 12062 return this.getData().uri; 12063 } 12064 }); 12065 12066 window.finesse = window.finesse || {}; 12067 window.finesse.restservices = window.finesse.restservices || {}; 12068 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 12069 12070 return TeamWrapUpReason; 12071 })); 12072 12073 /** 12074 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 12075 * object which contains a list of Wrap-Up Reasons objects. 12076 * 12077 * @requires finesse.clientservices.ClientServices 12078 * @requires Class 12079 * @requires finesse.FinesseBase 12080 * @requires finesse.restservices.RestBase 12081 * @requires finesse.restservices.Dialog 12082 * @requires finesse.restservices.RestCollectionBase 12083 */ 12084 /** @private */ 12085 (function (factory) { 12086 12087 12088 // Define as an AMD module if possible 12089 if ( typeof define === 'function' && define.amd ) 12090 { 12091 define('restservices/TeamWrapUpReasons', ['restservices/RestCollectionBase', 12092 'restservices/RestBase', 12093 'restservices/TeamWrapUpReason'], factory ); 12094 } 12095 /* Define using browser globals otherwise 12096 * Prevent multiple instantiations if the script is loaded twice 12097 */ 12098 else 12099 { 12100 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.TeamWrapUpReason); 12101 } 12102 }(function (RestCollectionBase, RestBase, TeamWrapUpReason) { 12103 12104 var TeamWrapUpReasons = RestCollectionBase.extend({ 12105 12106 /** 12107 * @class 12108 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 12109 * methods to operate on the object against the server. 12110 * 12111 * @param {Object} options 12112 * An object with the following properties:<ul> 12113 * <li><b>id:</b> The id of the object being constructed</li> 12114 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12115 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12116 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12117 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12118 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12119 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12120 * <li><b>content:</b> {String} Raw string of response</li> 12121 * <li><b>object:</b> {Object} Parsed object of response</li> 12122 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12123 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12124 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12125 * </ul></li> 12126 * </ul></li> 12127 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12128 * @constructs 12129 **/ 12130 init: function (options) { 12131 this._super(options); 12132 }, 12133 12134 /** 12135 * @private 12136 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 12137 */ 12138 getRestClass: function () { 12139 return TeamWrapUpReasons; 12140 }, 12141 12142 /** 12143 * @private 12144 * Gets the REST class for the objects that make up the collection. - this 12145 * is the TeamWrapUpReason class. 12146 */ 12147 getRestItemClass: function () { 12148 return TeamWrapUpReason; 12149 }, 12150 12151 /** 12152 * @private 12153 * Gets the REST type for the current object - this is a "WrapUpReasons". 12154 */ 12155 getRestType: function () { 12156 return "WrapUpReasons"; 12157 }, 12158 12159 /** 12160 * @private 12161 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 12162 */ 12163 getRestItemType: function () { 12164 return "WrapUpReason"; 12165 }, 12166 12167 /** 12168 * @private 12169 * Override default to indicates that the collection supports making 12170 * requests. 12171 */ 12172 supportsRequests: true, 12173 12174 /** 12175 * @private 12176 * Override default to indicate that this object doesn't support subscriptions. 12177 */ 12178 supportsRestItemSubscriptions: false, 12179 12180 /** 12181 * Retrieve the Team Wrap Up Reasons. 12182 * 12183 * @returns {finesse.restservices.TeamWrapUpReasons} 12184 * This TeamWrapUpReasons object to allow cascading. 12185 */ 12186 get: function () { 12187 // set loaded to false so it will rebuild the collection after the get 12188 this._loaded = false; 12189 // reset collection 12190 this._collection = {}; 12191 // perform get 12192 this._synchronize(); 12193 return this; 12194 }, 12195 12196 /** 12197 * Set up the PutSuccessHandler for TeamWrapUpReasons 12198 * @param {Object} wrapUpReasons 12199 * @param {Object} contentBody 12200 * @param successHandler 12201 * @returns response 12202 */ 12203 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 12204 return function (rsp) { 12205 // Update internal structure based on response. Here we 12206 // inject the contentBody from the PUT request into the 12207 // rsp.object element to mimic a GET as a way to take 12208 // advantage of the existing _processResponse method. 12209 rsp.object = contentBody; 12210 12211 wrapUpReasons._processResponse(rsp); 12212 12213 //Remove the injected contentBody object before cascading response 12214 rsp.object = {}; 12215 12216 //cascade response back to consumer's response handler 12217 successHandler(rsp); 12218 }; 12219 }, 12220 12221 /** 12222 * Perform the REST API PUT call to update the reason code assignments for the team 12223 * @param {String Array} newValues 12224 * @param handlers 12225 * @returns {Object} this 12226 */ 12227 update: function (newValues, handlers) { 12228 this.isLoaded(); 12229 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 12230 12231 contentBody[this.getRestType()] = { 12232 }; 12233 12234 for (i in newValues) { 12235 if (newValues.hasOwnProperty(i)) { 12236 innerObject = { 12237 "uri": newValues[i] 12238 }; 12239 contentBodyInner.push(innerObject); 12240 } 12241 } 12242 12243 contentBody[this.getRestType()] = { 12244 "WrapUpReason" : contentBodyInner 12245 }; 12246 12247 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12248 handlers = handlers || {}; 12249 12250 this.restRequest(this.getRestUrl(), { 12251 method: 'PUT', 12252 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12253 error: handlers.error, 12254 content: contentBody 12255 }); 12256 12257 return this; // Allow cascading 12258 } 12259 }); 12260 12261 window.finesse = window.finesse || {}; 12262 window.finesse.restservices = window.finesse.restservices || {}; 12263 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 12264 12265 return TeamWrapUpReasons; 12266 })); 12267 12268 /** 12269 * JavaScript representation of a TeamSignOutReasonCode. 12270 * 12271 * @requires finesse.clientservices.ClientServices 12272 * @requires Class 12273 * @requires finesse.FinesseBase 12274 * @requires finesse.restservices.RestBase 12275 */ 12276 12277 /** @private */ 12278 (function (factory) { 12279 12280 12281 // Define as an AMD module if possible 12282 if ( typeof define === 'function' && define.amd ) 12283 { 12284 define('restservices/TeamSignOutReasonCode', ['restservices/RestBase'], factory ); 12285 } 12286 /* Define using browser globals otherwise 12287 * Prevent multiple instantiations if the script is loaded twice 12288 */ 12289 else 12290 { 12291 factory(finesse.restservices.RestBase); 12292 } 12293 }(function (RestBase) { 12294 var TeamSignOutReasonCode = RestBase.extend({ 12295 12296 /** 12297 * @class 12298 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 12299 * methods to operate on the object against the server. 12300 * 12301 * @param {Object} options 12302 * An object with the following properties:<ul> 12303 * <li><b>id:</b> The id of the object being constructed</li> 12304 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12305 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12306 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12307 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12308 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12309 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12310 * <li><b>content:</b> {String} Raw string of response</li> 12311 * <li><b>object:</b> {Object} Parsed object of response</li> 12312 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12313 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12314 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12315 * </ul></li> 12316 * </ul></li> 12317 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12318 * @constructs 12319 * @ignore 12320 **/ 12321 init: function (options) { 12322 this._super(options); 12323 }, 12324 12325 /** 12326 * @private 12327 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 12328 * @returns {Object} The TeamSignOutReasonCode class. 12329 */ 12330 getRestClass: function () { 12331 return TeamSignOutReasonCode; 12332 }, 12333 12334 /** 12335 * @private 12336 * Gets the REST type for the current object - this is a "ReasonCode". 12337 * @returns {String} The ReasonCode string. 12338 */ 12339 getRestType: function () { 12340 return "ReasonCode"; 12341 }, 12342 12343 /** 12344 * @private 12345 * Override default to indicate that this object doesn't support making 12346 * requests. 12347 */ 12348 supportsRequests: false, 12349 12350 /** 12351 * @private 12352 * Override default to indicate that this object doesn't support subscriptions. 12353 */ 12354 supportsSubscriptions: false, 12355 12356 /** 12357 * Getter for the category. 12358 * @returns {String} The category. 12359 */ 12360 getCategory: function () { 12361 this.isLoaded(); 12362 return this.getData().category; 12363 }, 12364 12365 /** 12366 * Getter for the code. 12367 * @returns {String} The code. 12368 */ 12369 getCode: function () { 12370 this.isLoaded(); 12371 return this.getData().code; 12372 }, 12373 12374 /** 12375 * Getter for the label. 12376 * @returns {String} The label. 12377 */ 12378 getLabel: function () { 12379 this.isLoaded(); 12380 return this.getData().label; 12381 }, 12382 12383 /** 12384 * Getter for the forAll value. 12385 * @returns {String} The forAll. 12386 */ 12387 getForAll: function () { 12388 this.isLoaded(); 12389 return this.getData().forAll; 12390 }, 12391 12392 /** 12393 * Getter for the Uri value. 12394 * @returns {String} The Uri. 12395 */ 12396 getUri: function () { 12397 this.isLoaded(); 12398 return this.getData().uri; 12399 } 12400 12401 }); 12402 12403 window.finesse = window.finesse || {}; 12404 window.finesse.restservices = window.finesse.restservices || {}; 12405 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 12406 12407 return TeamSignOutReasonCode; 12408 })); 12409 12410 /** 12411 * JavaScript representation of the TeamSignOutReasonCodes collection 12412 * object which contains a list of TeamSignOutReasonCode objects. 12413 * 12414 * @requires finesse.clientservices.ClientServices 12415 * @requires Class 12416 * @requires finesse.FinesseBase 12417 * @requires finesse.restservices.RestBase 12418 * @requires finesse.restservices.Dialog 12419 * @requires finesse.restservices.RestCollectionBase 12420 */ 12421 12422 /** @private */ 12423 (function (factory) { 12424 12425 12426 // Define as an AMD module if possible 12427 if ( typeof define === 'function' && define.amd ) 12428 { 12429 define('restservices/TeamSignOutReasonCodes', ['restservices/RestCollectionBase', 12430 'restservices/RestBase', 12431 'restservices/TeamSignOutReasonCode'], factory ); 12432 } 12433 /* Define using browser globals otherwise 12434 * Prevent multiple instantiations if the script is loaded twice 12435 */ 12436 else 12437 { 12438 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.TeamSignOutReasonCode); 12439 } 12440 }(function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 12441 12442 var TeamSignOutReasonCodes = RestCollectionBase.extend({ 12443 /** 12444 * @class 12445 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 12446 * methods to operate on the object against the server. 12447 * 12448 * @param {Object} options 12449 * An object with the following properties:<ul> 12450 * <li><b>id:</b> The id of the object being constructed</li> 12451 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12452 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12453 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12454 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12455 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12456 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12457 * <li><b>content:</b> {String} Raw string of response</li> 12458 * <li><b>object:</b> {Object} Parsed object of response</li> 12459 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12460 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12461 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12462 * </ul></li> 12463 * </ul></li> 12464 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12465 * @constructs 12466 **/ 12467 init: function (options) { 12468 this._super(options); 12469 }, 12470 12471 /** 12472 * @private 12473 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 12474 */ 12475 getRestClass: function () { 12476 return TeamSignOutReasonCodes; 12477 }, 12478 12479 /** 12480 * @private 12481 * Gets the REST class for the objects that make up the collection. - this 12482 * is the TeamSignOutReasonCode class. 12483 */ 12484 getRestItemClass: function () { 12485 return TeamSignOutReasonCode; 12486 }, 12487 12488 /** 12489 * @private 12490 * Gets the REST type for the current object - this is a "ReasonCodes". 12491 */ 12492 getRestType: function () { 12493 return "ReasonCodes"; 12494 }, 12495 12496 /** 12497 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 12498 */ 12499 getRestUrl: function () { 12500 var restObj = this._restObj, restUrl = ""; 12501 12502 //Prepend the base REST object if one was provided. 12503 //Otherwise prepend with the default webapp name. 12504 if (restObj instanceof RestBase) { 12505 restUrl += restObj.getRestUrl(); 12506 } else { 12507 restUrl += "/finesse/api"; 12508 } 12509 //Append the REST type. 12510 restUrl += "/ReasonCodes?category=LOGOUT"; 12511 //Append ID if it is not undefined, null, or empty. 12512 if (this._id) { 12513 restUrl += "/" + this._id; 12514 } 12515 return restUrl; 12516 }, 12517 12518 /** 12519 * @private 12520 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 12521 */ 12522 getRestItemType: function () { 12523 return "ReasonCode"; 12524 }, 12525 12526 /** 12527 * @private 12528 * Override default to indicates that the collection supports making requests. 12529 */ 12530 supportsRequests: true, 12531 12532 /** 12533 * @private 12534 * Override default to indicates that the collection does not subscribe to its objects. 12535 */ 12536 supportsRestItemSubscriptions: false, 12537 12538 /** 12539 * Retrieve the Sign Out Reason Codes. 12540 * 12541 * @returns {finesse.restservices.TeamSignOutReasonCodes} 12542 * This TeamSignOutReasonCodes object to allow cascading. 12543 */ 12544 get: function () { 12545 // set loaded to false so it will rebuild the collection after the get 12546 this._loaded = false; 12547 // reset collection 12548 this._collection = {}; 12549 // perform get 12550 this._synchronize(); 12551 return this; 12552 }, 12553 12554 /* We only use PUT and GET on Reason Code team assignments 12555 * @param {Object} contact 12556 * @param {Object} contentBody 12557 * @param {Function} successHandler 12558 */ 12559 createPutSuccessHandler: function (contact, contentBody, successHandler) { 12560 return function (rsp) { 12561 // Update internal structure based on response. Here we 12562 // inject the contentBody from the PUT request into the 12563 // rsp.object element to mimic a GET as a way to take 12564 // advantage of the existing _processResponse method. 12565 rsp.object = contentBody; 12566 contact._processResponse(rsp); 12567 12568 //Remove the injected contentBody object before cascading response 12569 rsp.object = {}; 12570 12571 //cascade response back to consumer's response handler 12572 successHandler(rsp); 12573 }; 12574 }, 12575 12576 /** 12577 * Update - This should be all that is needed. 12578 * @param {Object} newValues 12579 * @param {Object} handlers 12580 * @returns {finesse.restservices.TeamSignOutReasonCodes} 12581 * This TeamSignOutReasonCodes object to allow cascading. 12582 */ 12583 update: function (newValues, handlers) { 12584 this.isLoaded(); 12585 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 12586 12587 contentBody[this.getRestType()] = { 12588 }; 12589 12590 for (i in newValues) { 12591 if (newValues.hasOwnProperty(i)) { 12592 innerObject = { 12593 "uri": newValues[i] 12594 }; 12595 contentBodyInner.push(innerObject); 12596 } 12597 } 12598 12599 contentBody[this.getRestType()] = { 12600 "ReasonCode" : contentBodyInner 12601 }; 12602 12603 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12604 handlers = handlers || {}; 12605 12606 this.restRequest(this.getRestUrl(), { 12607 method: 'PUT', 12608 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12609 error: handlers.error, 12610 content: contentBody 12611 }); 12612 12613 return this; // Allow cascading 12614 } 12615 12616 }); 12617 12618 window.finesse = window.finesse || {}; 12619 window.finesse.restservices = window.finesse.restservices || {}; 12620 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 12621 12622 return TeamSignOutReasonCodes; 12623 })); 12624 12625 /** 12626 * JavaScript representation of the Finesse PhoneBook Assignment object. 12627 * 12628 * @requires finesse.clientservices.ClientServices 12629 * @requires Class 12630 * @requires finesse.FinesseBase 12631 * @requires finesse.restservices.RestBase 12632 */ 12633 12634 /** 12635 * The following comment prevents JSLint errors concerning undefined global variables. 12636 * It tells JSLint that these identifiers are defined elsewhere. 12637 */ 12638 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 12639 12640 /** The following comment is to prevent jslint errors about 12641 * using variables before they are defined. 12642 */ 12643 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 12644 12645 /** @private */ 12646 (function (factory) { 12647 12648 12649 // Define as an AMD module if possible 12650 if ( typeof define === 'function' && define.amd ) 12651 { 12652 define('restservices/TeamPhoneBook', ['restservices/RestBase'], factory ); 12653 } 12654 /* Define using browser globals otherwise 12655 * Prevent multiple instantiations if the script is loaded twice 12656 */ 12657 else 12658 { 12659 factory(finesse.restservices.RestBase); 12660 } 12661 }(function (RestBase) { 12662 var TeamPhoneBook = RestBase.extend({ 12663 12664 /** 12665 * @class 12666 * JavaScript representation of a PhoneBook object. Also exposes 12667 * methods to operate on the object against the server. 12668 * 12669 * @param {Object} options 12670 * An object with the following properties:<ul> 12671 * <li><b>id:</b> The id of the object being constructed</li> 12672 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12673 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12674 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12675 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12676 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12677 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12678 * <li><b>content:</b> {String} Raw string of response</li> 12679 * <li><b>object:</b> {Object} Parsed object of response</li> 12680 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12681 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12682 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12683 * </ul></li> 12684 * </ul></li> 12685 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12686 * @constructs 12687 **/ 12688 init: function (options) { 12689 this._super(options); 12690 }, 12691 12692 /** 12693 * @private 12694 * Gets the REST class for the current object - this is the PhoneBooks class. 12695 * @returns {Object} The PhoneBooks class. 12696 */ 12697 getRestClass: function () { 12698 return TeamPhoneBook; 12699 }, 12700 12701 /** 12702 * @private 12703 * Gets the REST type for the current object - this is a "PhoneBook". 12704 * @returns {String} The PhoneBook string. 12705 */ 12706 getRestType: function () { 12707 return "PhoneBook"; 12708 }, 12709 12710 /** 12711 * @private 12712 * Override default to indicate that this object doesn't support making 12713 * requests. 12714 */ 12715 supportsRequests: false, 12716 12717 /** 12718 * @private 12719 * Override default to indicate that this object doesn't support subscriptions. 12720 */ 12721 supportsSubscriptions: false, 12722 12723 /** 12724 * Getter for the name. 12725 * @returns {String} The name. 12726 */ 12727 getName: function () { 12728 this.isLoaded(); 12729 return this.getData().name; 12730 }, 12731 12732 /** 12733 * Getter for the Uri value. 12734 * @returns {String} The Uri. 12735 */ 12736 getUri: function () { 12737 this.isLoaded(); 12738 return this.getData().uri; 12739 } 12740 12741 }); 12742 12743 window.finesse = window.finesse || {}; 12744 window.finesse.restservices = window.finesse.restservices || {}; 12745 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 12746 12747 return TeamPhoneBook; 12748 })); 12749 12750 /** 12751 * JavaScript representation of the Finesse PhoneBook Assignments collection 12752 * object which contains a list of Not Ready Reason Codes objects. 12753 * 12754 * @requires finesse.clientservices.ClientServices 12755 * @requires Class 12756 * @requires finesse.FinesseBase 12757 * @requires finesse.restservices.RestBase 12758 * @requires finesse.restservices.Dialog 12759 * @requires finesse.restservices.RestCollectionBase 12760 */ 12761 12762 /** 12763 * The following comment prevents JSLint errors concerning undefined global variables. 12764 * It tells JSLint that these identifiers are defined elsewhere. 12765 */ 12766 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 12767 12768 /** The following comment is to prevent jslint errors about 12769 * using variables before they are defined. 12770 */ 12771 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 12772 12773 /** @private */ 12774 (function (factory) { 12775 12776 12777 // Define as an AMD module if possible 12778 if ( typeof define === 'function' && define.amd ) 12779 { 12780 define('restservices/TeamPhoneBooks', ['restservices/RestCollectionBase', 12781 'restservices/RestBase', 12782 'restservices/TeamPhoneBook'], factory ); 12783 } 12784 /* Define using browser globals otherwise 12785 * Prevent multiple instantiations if the script is loaded twice 12786 */ 12787 else 12788 { 12789 factory(finesse.restservices.RestCollectionBase, finesse.restservices.RestBase, finesse.restservices.TeamPhoneBook); 12790 } 12791 }(function (RestCollectionBase, RestBase, TeamPhoneBook) { 12792 var TeamPhoneBooks = RestCollectionBase.extend({ 12793 12794 /** 12795 * @class 12796 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 12797 * methods to operate on the object against the server. 12798 * 12799 * @param {Object} options 12800 * An object with the following properties:<ul> 12801 * <li><b>id:</b> The id of the object being constructed</li> 12802 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12803 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12804 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12805 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12806 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12807 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12808 * <li><b>content:</b> {String} Raw string of response</li> 12809 * <li><b>object:</b> {Object} Parsed object of response</li> 12810 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12811 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12812 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12813 * </ul></li> 12814 * </ul></li> 12815 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12816 * @constructs 12817 **/ 12818 init: function (options) { 12819 this._super(options); 12820 }, 12821 12822 /** 12823 * @private 12824 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 12825 */ 12826 getRestClass: function () { 12827 return TeamPhoneBooks; 12828 }, 12829 12830 /** 12831 * @private 12832 * Gets the REST class for the objects that make up the collection. - this 12833 * is the TeamPhoneBooks class. 12834 */ 12835 getRestItemClass: function () { 12836 return TeamPhoneBook; 12837 }, 12838 12839 /** 12840 * @private 12841 * Gets the REST type for the current object - this is a "ReasonCodes". 12842 */ 12843 getRestType: function () { 12844 return "PhoneBooks"; 12845 }, 12846 12847 /** 12848 * Overrides the parent class. Returns the url for the PhoneBooks resource 12849 */ 12850 getRestUrl: function () { 12851 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 12852 var restObj = this._restObj, 12853 restUrl = ""; 12854 //Prepend the base REST object if one was provided. 12855 if (restObj instanceof RestBase) { 12856 restUrl += restObj.getRestUrl(); 12857 } 12858 //Otherwise prepend with the default webapp name. 12859 else { 12860 restUrl += "/finesse/api"; 12861 } 12862 //Append the REST type. 12863 restUrl += "/PhoneBooks"; 12864 //Append ID if it is not undefined, null, or empty. 12865 if (this._id) { 12866 restUrl += "/" + this._id; 12867 } 12868 return restUrl; 12869 }, 12870 12871 /** 12872 * @private 12873 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 12874 */ 12875 getRestItemType: function () { 12876 return "PhoneBook"; 12877 }, 12878 12879 /** 12880 * @private 12881 * Override default to indicates that the collection supports making 12882 * requests. 12883 */ 12884 supportsRequests: true, 12885 12886 /** 12887 * @private 12888 * Override default to indicates that the collection subscribes to its objects. 12889 */ 12890 supportsRestItemSubscriptions: false, 12891 12892 /** 12893 * Retrieve the Not Ready Reason Codes. 12894 * 12895 * @returns {finesse.restservices.TeamPhoneBooks} 12896 * This TeamPhoneBooks object to allow cascading. 12897 */ 12898 get: function () { 12899 // set loaded to false so it will rebuild the collection after the get 12900 /** @private */ 12901 this._loaded = false; 12902 // reset collection 12903 /** @private */ 12904 this._collection = {}; 12905 // perform get 12906 this._synchronize(); 12907 return this; 12908 }, 12909 12910 /* We only use PUT and GET on Reason Code team assignments 12911 */ 12912 createPutSuccessHandler: function(contact, contentBody, successHandler){ 12913 return function (rsp) { 12914 // Update internal structure based on response. Here we 12915 // inject the contentBody from the PUT request into the 12916 // rsp.object element to mimic a GET as a way to take 12917 // advantage of the existing _processResponse method. 12918 rsp.object = contentBody; 12919 contact._processResponse(rsp); 12920 12921 //Remove the injected Contact object before cascading response 12922 rsp.object = {}; 12923 12924 //cascade response back to consumer's response handler 12925 successHandler(rsp); 12926 }; 12927 }, 12928 12929 /** 12930 * Update - This should be all that is needed. 12931 */ 12932 update: function (newValues, handlers) { 12933 this.isLoaded(); 12934 var contentBody = {}, contentBodyInner = [], i, innerObject; 12935 12936 contentBody[this.getRestType()] = { 12937 }; 12938 12939 for (i in newValues) { 12940 if (newValues.hasOwnProperty(i)) { 12941 innerObject = {}; 12942 innerObject = { 12943 "uri": newValues[i] 12944 }; 12945 contentBodyInner.push(innerObject); 12946 } 12947 } 12948 12949 contentBody[this.getRestType()] = { 12950 "PhoneBook" : contentBodyInner 12951 }; 12952 12953 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12954 handlers = handlers || {}; 12955 12956 this.restRequest(this.getRestUrl(), { 12957 method: 'PUT', 12958 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12959 error: handlers.error, 12960 content: contentBody 12961 }); 12962 12963 return this; // Allow cascading 12964 } 12965 12966 }); 12967 12968 window.finesse = window.finesse || {}; 12969 window.finesse.restservices = window.finesse.restservices || {}; 12970 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 12971 12972 return TeamPhoneBooks; 12973 })); 12974 12975 /** 12976 * JavaScript representation of the Finesse LayoutConfig object 12977 * @requires ClientServices 12978 * @requires finesse.FinesseBase 12979 * @requires finesse.restservices.RestBase 12980 */ 12981 12982 /** @private */ 12983 (function (factory) { 12984 12985 12986 // Define as an AMD module if possible 12987 if ( typeof define === 'function' && define.amd ) 12988 { 12989 define('restservices/LayoutConfig', ['restservices/RestBase'], factory ); 12990 } 12991 /* Define using browser globals otherwise 12992 * Prevent multiple instantiations if the script is loaded twice 12993 */ 12994 else 12995 { 12996 factory(finesse.restservices.RestBase); 12997 } 12998 }(function (RestBase) { 12999 /** @private */ 13000 var LayoutConfig = RestBase.extend({ 13001 13002 /** 13003 * @class 13004 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 13005 * on the object against the server. 13006 * 13007 * @param {String} id 13008 * Not required... 13009 * @param {Object} callbacks 13010 * An object containing callbacks for instantiation and runtime 13011 * @param {Function} callbacks.onLoad(this) 13012 * Callback to invoke upon successful instantiation 13013 * @param {Function} callbacks.onLoadError(rsp) 13014 * Callback to invoke on instantiation REST request error 13015 * as passed by finesse.clientservices.ClientServices.ajax() 13016 * { 13017 * status: {Number} The HTTP status code returned 13018 * content: {String} Raw string of response 13019 * object: {Object} Parsed object of response 13020 * error: {Object} Wrapped exception that was caught 13021 * error.errorType: {String} Type of error that was caught 13022 * error.errorMessage: {String} Message associated with error 13023 * } 13024 * @param {Function} callbacks.onChange(this) 13025 * Callback to invoke upon successful update 13026 * @param {Function} callbacks.onError(rsp) 13027 * Callback to invoke on update error (refresh or event) 13028 * as passed by finesse.clientservices.ClientServices.ajax() 13029 * { 13030 * status: {Number} The HTTP status code returned 13031 * content: {String} Raw string of response 13032 * object: {Object} Parsed object of response 13033 * error: {Object} Wrapped exception that was caught 13034 * error.errorType: {String} Type of error that was caught 13035 * error.errorMessage: {String} Message associated with error 13036 * } 13037 * 13038 * @constructs 13039 */ 13040 init: function (callbacks) { 13041 this._super("", callbacks); 13042 //when post is performed and id is empty 13043 /*if (id === "") { 13044 this._loaded = true; 13045 }*/ 13046 this._layoutxml = {}; 13047 }, 13048 13049 /** 13050 * Returns REST class of LayoutConfig object 13051 */ 13052 getRestClass: function () { 13053 return LayoutConfig; 13054 }, 13055 13056 /** 13057 * The type of this REST object is LayoutConfig 13058 */ 13059 getRestType: function () { 13060 return "LayoutConfig"; 13061 }, 13062 13063 /** 13064 * Gets the REST URL of this object. 13065 * 13066 * If the parent has an id, the id is appended. 13067 * On occasions of POST, it will not have an id. 13068 */ 13069 getRestUrl: function () { 13070 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 13071 /*if (this._id) { 13072 layoutUri = layoutUri + "/" + this._id; 13073 }*/ 13074 return layoutUri; 13075 }, 13076 13077 /** 13078 * This API does not support subscription 13079 */ 13080 supportsSubscriptions: false, 13081 13082 keepRestResponse: true, 13083 13084 13085 /** 13086 * Gets finesselayout.xml retrieved from the API call 13087 */ 13088 getLayoutxml: function () { 13089 this.isLoaded(); 13090 var layoutxml = this.getData().layoutxml; 13091 13092 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 13093 layoutxml = layoutxml.replace(/&/g,"&"); 13094 13095 return layoutxml; 13096 }, 13097 13098 /** 13099 * Gets the type of this LayoutConfig object 13100 */ 13101 /* 13102 getType: function () { 13103 this.isLoaded(); 13104 return this.getData().type; 13105 },*/ 13106 13107 /** 13108 * Retrieve the LayoutConfig settings. 13109 * If the id is not provided the API call will fail. 13110 * @returns {LayoutConfig} 13111 * This LayoutConfig object to allow cascading. 13112 */ 13113 get: function () { 13114 this._synchronize(); 13115 return this; 13116 }, 13117 13118 /** 13119 * Closure handle updating of the internal data for the LayoutConfig object 13120 * upon a successful update (PUT) request before calling the intended 13121 * success handler provided by the consumer 13122 * 13123 * @param {Object} 13124 * layoutconfig Reference to this LayoutConfig object 13125 * @param {Object} 13126 * LayoutConfig Object that contains the settings to be 13127 * submitted in the api request 13128 * @param {Function} 13129 * successHandler The success handler specified by the consumer 13130 * of this object 13131 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 13132 */ 13133 13134 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 13135 return function (rsp) { 13136 // Update internal structure based on response. Here we 13137 // inject the contentBody from the PUT request into the 13138 // rsp.object element to mimic a GET as a way to take 13139 // advantage of the existing _processResponse method. 13140 rsp.content = contentBody; 13141 rsp.object.LayoutConfig = {}; 13142 rsp.object.LayoutConfig.finesseLayout = contentBody; 13143 layoutconfig._processResponse(rsp); 13144 13145 //Remove the injected layoutConfig object before cascading response 13146 rsp.object.LayoutConfig = {}; 13147 13148 //cascade response back to consumer's response handler 13149 successHandler(rsp); 13150 }; 13151 }, 13152 13153 /** 13154 * Update LayoutConfig 13155 * @param {Object} finesselayout 13156 * The XML for FinesseLayout being stored 13157 * 13158 * @param {Object} handlers 13159 * An object containing callback handlers for the request. Optional. 13160 * @param {Function} options.success(rsp) 13161 * A callback function to be invoked for a successful request. 13162 * { 13163 * status: {Number} The HTTP status code returned 13164 * content: {String} Raw string of response 13165 * object: {Object} Parsed object of response 13166 * } 13167 * @param {Function} options.error(rsp) 13168 * A callback function to be invoked for an unsuccessful request. 13169 * { 13170 * status: {Number} The HTTP status code returned 13171 * content: {String} Raw string of response 13172 * object: {Object} Parsed object of response (HTTP errors) 13173 * error: {Object} Wrapped exception that was caught 13174 * error.errorType: {String} Type of error that was caught 13175 * error.errorMessage: {String} Message associated with error 13176 * } 13177 * @returns {finesse.restservices.LayoutConfig} 13178 * This LayoutConfig object to allow cascading 13179 */ 13180 13181 update: function (layoutxml, handlers) { 13182 this.isLoaded(); 13183 var contentBody = {}; 13184 13185 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 13186 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 13187 13188 contentBody[this.getRestType()] = { 13189 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 13190 }; 13191 13192 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13193 handlers = handlers || {}; 13194 13195 this.restRequest(this.getRestUrl(), { 13196 method: 'PUT', 13197 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 13198 error: handlers.error, 13199 content: contentBody 13200 }); 13201 13202 return this; // Allow cascading 13203 } 13204 13205 /** 13206 *TODO createPostSuccessHandler needs to be debugged to make it working 13207 * Closure handle creating new LayoutConfig object 13208 * upon a successful create (POST) request before calling the intended 13209 * success handler provided by the consumer 13210 * 13211 * @param {Object} 13212 * layoutconfig Reference to this LayoutConfig object 13213 * @param {Object} 13214 * LayoutConfig Object that contains the settings to be 13215 * submitted in the api request 13216 * @param {Function} 13217 * successHandler The success handler specified by the consumer 13218 * of this object 13219 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 13220 */ 13221 /* 13222 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 13223 return function (rsp) { 13224 13225 rsp.object = contentBody; 13226 layoutconfig._processResponse(rsp); 13227 13228 //Remove the injected layoutConfig object before cascading response 13229 rsp.object = {}; 13230 13231 //cascade response back to consumer's response handler 13232 successHandler(rsp); 13233 }; 13234 }, */ 13235 13236 /** 13237 * TODO Method needs to be debugged to make POST working 13238 * Add LayoutConfig 13239 * @param {Object} finesselayout 13240 * The XML for FinesseLayout being stored 13241 * 13242 * @param {Object} handlers 13243 * An object containing callback handlers for the request. Optional. 13244 * @param {Function} options.success(rsp) 13245 * A callback function to be invoked for a successful request. 13246 * { 13247 * status: {Number} The HTTP status code returned 13248 * content: {String} Raw string of response 13249 * object: {Object} Parsed object of response 13250 * } 13251 * @param {Function} options.error(rsp) 13252 * A callback function to be invoked for an unsuccessful request. 13253 * { 13254 * status: {Number} The HTTP status code returned 13255 * content: {String} Raw string of response 13256 * object: {Object} Parsed object of response (HTTP errors) 13257 * error: {Object} Wrapped exception that was caught 13258 * error.errorType: {String} Type of error that was caught 13259 * error.errorMessage: {String} Message associated with error 13260 * } 13261 * @returns {finesse.restservices.LayoutConfig} 13262 * This LayoutConfig object to allow cascading 13263 */ 13264 /* 13265 add: function (layoutxml, handlers) { 13266 this.isLoaded(); 13267 var contentBody = {}; 13268 13269 13270 contentBody[this.getRestType()] = { 13271 "layoutxml": layoutxml, 13272 "type": "current" 13273 }; 13274 13275 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13276 handlers = handlers || {}; 13277 13278 this.restRequest(this.getRestUrl(), { 13279 method: 'POST', 13280 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 13281 error: handlers.error, 13282 content: contentBody 13283 }); 13284 13285 return this; // Allow cascading 13286 } */ 13287 }); 13288 13289 window.finesse = window.finesse || {}; 13290 window.finesse.restservices = window.finesse.restservices || {}; 13291 window.finesse.restservices.LayoutConfig = LayoutConfig; 13292 13293 return LayoutConfig; 13294 13295 })); 13296 13297 /** 13298 * JavaScript representation of the Finesse LayoutConfig object for a Team. 13299 * 13300 * @requires finesse.clientservices.ClientServices 13301 * @requires Class 13302 * @requires finesse.FinesseBase 13303 * @requires finesse.restservices.RestBase 13304 * @requires finesse.utilities.Utilities 13305 * @requires finesse.restservices.LayoutConfig 13306 */ 13307 13308 /** The following comment is to prevent jslint errors about 13309 * using variables before they are defined. 13310 */ 13311 /*global Exception */ 13312 13313 /** @private */ 13314 (function (factory) { 13315 13316 13317 // Define as an AMD module if possible 13318 if ( typeof define === 'function' && define.amd ) 13319 { 13320 define('restservices/TeamLayoutConfig', ['restservices/RestBase', 13321 'utilities/Utilities', 13322 'restservices/LayoutConfig'], factory ); 13323 } 13324 /* Define using browser globals otherwise 13325 * Prevent multiple instantiations if the script is loaded twice 13326 */ 13327 else 13328 { 13329 factory(finesse.restservices.RestBase, finesse.utilities.Utilities, finesse.restservices.LayoutConfig); 13330 } 13331 }(function (RestBase, Utilities, LayoutConfig) { 13332 13333 var TeamLayoutConfig = RestBase.extend({ 13334 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 13335 keepRestResponse: true, 13336 13337 /** 13338 * @class 13339 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 13340 * methods to operate on the object against the server. 13341 * 13342 * @param {Object} options 13343 * An object with the following properties:<ul> 13344 * <li><b>id:</b> The id of the object being constructed</li> 13345 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13346 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13347 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13348 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13349 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13350 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13351 * <li><b>content:</b> {String} Raw string of response</li> 13352 * <li><b>object:</b> {Object} Parsed object of response</li> 13353 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13354 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13355 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13356 * </ul></li> 13357 * </ul></li> 13358 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13359 * @constructs 13360 **/ 13361 init: function (options) { 13362 this._super(options); 13363 }, 13364 13365 /** 13366 * @private 13367 * Gets the REST class for the current object - this is the LayoutConfigs class. 13368 * @returns {Object} The LayoutConfigs class. 13369 */ 13370 getRestClass: function () { 13371 return TeamLayoutConfig; 13372 }, 13373 13374 /** 13375 * @private 13376 * Gets the REST type for the current object - this is a "LayoutConfig". 13377 * @returns {String} The LayoutConfig string. 13378 */ 13379 getRestType: function () { 13380 return "TeamLayoutConfig"; 13381 }, 13382 13383 /** 13384 * @private 13385 * Override default to indicate that this object doesn't support making 13386 * requests. 13387 */ 13388 supportsRequests: false, 13389 13390 /** 13391 * @private 13392 * Override default to indicate that this object doesn't support subscriptions. 13393 */ 13394 supportsSubscriptions: false, 13395 13396 /** 13397 * Getter for the category. 13398 * @returns {String} The category. 13399 */ 13400 getLayoutXML: function () { 13401 this.isLoaded(); 13402 var layoutxml = this.getData().layoutxml; 13403 13404 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 13405 layoutxml = layoutxml.replace(/&/g,"&"); 13406 13407 return layoutxml; 13408 }, 13409 13410 /** 13411 * Getter for the code. 13412 * @returns {String} The code. 13413 */ 13414 getUseDefault: function () { 13415 this.isLoaded(); 13416 return this.getData().useDefault; 13417 }, 13418 13419 /** 13420 * Retrieve the TeamLayoutConfig. 13421 * 13422 * @returns {finesse.restservices.TeamLayoutConfig} 13423 */ 13424 get: function () { 13425 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 13426 this._id = "0"; 13427 // set loaded to false so it will rebuild the collection after the get 13428 this._loaded = false; 13429 // reset collection 13430 this._collection = {}; 13431 // perform get 13432 this._synchronize(); 13433 return this; 13434 }, 13435 13436 createPutSuccessHandler: function(contact, contentBody, successHandler){ 13437 return function (rsp) { 13438 // Update internal structure based on response. Here we 13439 // inject the contentBody from the PUT request into the 13440 // rsp.object element to mimic a GET as a way to take 13441 // advantage of the existing _processResponse method. 13442 rsp.object = contentBody; 13443 contact._processResponse(rsp); 13444 13445 //Remove the injected Contact object before cascading response 13446 rsp.object = {}; 13447 13448 //cascade response back to consumer's response handler 13449 successHandler(rsp); 13450 }; 13451 }, 13452 13453 put: function (newValues, handlers) { 13454 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 13455 this._id = "0"; 13456 this.isLoaded(); 13457 13458 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 13459 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 13460 contentBody = {}; 13461 13462 contentBody[this.getRestType()] = { 13463 "useDefault": newValues.useDefault, 13464 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 13465 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 13466 }; 13467 13468 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13469 handlers = handlers || {}; 13470 13471 this.restRequest(this.getRestUrl(), { 13472 method: 'PUT', 13473 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 13474 error: handlers.error, 13475 content: contentBody 13476 }); 13477 13478 return this; // Allow cascading 13479 }, 13480 13481 getRestUrl: function(){ 13482 // return team's url + /LayoutConfig 13483 // eg: /api/Team/1/LayoutConfig 13484 if(this._restObj === undefined){ 13485 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 13486 } 13487 return this._restObj.getRestUrl() + '/LayoutConfig'; 13488 } 13489 13490 }); 13491 13492 window.finesse = window.finesse || {}; 13493 window.finesse.restservices = window.finesse.restservices || {}; 13494 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 13495 13496 return TeamLayoutConfig; 13497 })); 13498 13499 /** 13500 * JavaScript representation of a TeamWorkflow. 13501 * 13502 * @requires finesse.clientservices.ClientServices 13503 * @requires Class 13504 * @requires finesse.FinesseBase 13505 * @requires finesse.restservices.RestBase 13506 */ 13507 /** @private */ 13508 (function (factory) { 13509 13510 13511 // Define as an AMD module if possible 13512 if ( typeof define === 'function' && define.amd ) 13513 { 13514 define('restservices/TeamWorkflow', ['restservices/RestBase'], factory ); 13515 } 13516 /* Define using browser globals otherwise 13517 * Prevent multiple instantiations if the script is loaded twice 13518 */ 13519 else 13520 { 13521 factory(finesse.restservices.RestBase); 13522 } 13523 }(function (RestBase) { 13524 13525 var TeamWorkflow = RestBase.extend({ 13526 13527 /** 13528 * @class 13529 * JavaScript representation of a TeamWorkflow object. Also exposes 13530 * methods to operate on the object against the server. 13531 * 13532 * @param {Object} options 13533 * An object with the following properties:<ul> 13534 * <li><b>id:</b> The id of the object being constructed</li> 13535 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13536 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13537 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13538 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13539 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13540 * <li><b>status:</b> {Number} The HTTP status description returned</li> 13541 * <li><b>content:</b> {String} Raw string of response</li> 13542 * <li><b>object:</b> {Object} Parsed object of response</li> 13543 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13544 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13545 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13546 * </ul></li> 13547 * </ul></li> 13548 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13549 * @constructs 13550 **/ 13551 init: function (options) { 13552 this._super(options); 13553 }, 13554 13555 /** 13556 * @private 13557 * Gets the REST class for the current object - this is the TeamWorkflow class. 13558 * @returns {Object} The TeamWorkflow class. 13559 */ 13560 getRestClass: function () { 13561 return TeamWorkflow; 13562 }, 13563 13564 /** 13565 * @private 13566 * Gets the REST type for the current object - this is a "Workflow". 13567 * @returns {String} The Workflow string. 13568 */ 13569 getRestType: function () { 13570 return "Workflow"; 13571 }, 13572 13573 /** 13574 * @private 13575 * Override default to indicate that this object doesn't support making 13576 * requests. 13577 */ 13578 supportsRequests: false, 13579 13580 /** 13581 * @private 13582 * Override default to indicate that this object doesn't support subscriptions. 13583 */ 13584 supportsSubscriptions: false, 13585 13586 /** 13587 * Getter for the name. 13588 * @returns {String} The name. 13589 */ 13590 getName: function () { 13591 this.isLoaded(); 13592 return this.getData().name; 13593 }, 13594 13595 /** 13596 * Getter for the description. 13597 * @returns {String} The description. 13598 */ 13599 getDescription: function () { 13600 this.isLoaded(); 13601 return this.getData().description; 13602 }, 13603 13604 /** 13605 * Getter for the Uri value. 13606 * @returns {String} The Uri. 13607 */ 13608 getUri: function () { 13609 this.isLoaded(); 13610 return this.getData().uri; 13611 } 13612 13613 }); 13614 13615 window.finesse = window.finesse || {}; 13616 window.finesse.restservices = window.finesse.restservices || {}; 13617 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 13618 13619 return TeamWorkflow; 13620 })); 13621 13622 /** 13623 * JavaScript representation of the TeamWorkflows collection 13624 * object which contains a list of TeamWorkflow objects. 13625 * 13626 * @requires finesse.clientservices.ClientServices 13627 * @requires Class 13628 * @requires finesse.FinesseBase 13629 * @requires finesse.restservices.RestBase 13630 * @requires finesse.restservices.Dialog 13631 * @requires finesse.restservices.RestCollectionBase 13632 */ 13633 /** @private */ 13634 (function (factory) { 13635 13636 13637 // Define as an AMD module if possible 13638 if ( typeof define === 'function' && define.amd ) 13639 { 13640 define('restservices/TeamWorkflows', ['restservices/RestCollectionBase', 13641 'restservices/TeamWorkflow', 13642 'restservices/RestBase'], factory ); 13643 } 13644 /* Define using browser globals otherwise 13645 * Prevent multiple instantiations if the script is loaded twice 13646 */ 13647 else 13648 { 13649 factory(finesse.restservices.RestCollectionBase, finesse.restservices.TeamWorkflow, finesse.restservices.RestBase); 13650 } 13651 }(function (RestCollectionBase, TeamWorkflow, RestBase) { 13652 13653 var TeamWorkflows = RestCollectionBase.extend({ 13654 13655 /** 13656 * @class 13657 * JavaScript representation of a TeamWorkflows collection object. Also exposes 13658 * methods to operate on the object against the server. 13659 * 13660 * @param {Object} options 13661 * An object with the following properties:<ul> 13662 * <li><b>id:</b> The id of the object being constructed</li> 13663 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13664 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13665 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13666 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13667 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13668 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13669 * <li><b>content:</b> {String} Raw string of response</li> 13670 * <li><b>object:</b> {Object} Parsed object of response</li> 13671 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13672 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13673 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13674 * </ul></li> 13675 * </ul></li> 13676 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13677 * @constructs 13678 **/ 13679 init: function (options) { 13680 this._super(options); 13681 }, 13682 13683 /** 13684 * @private 13685 * Gets the REST class for the current object - this is the TeamWorkflows class. 13686 */ 13687 getRestClass: function () { 13688 return TeamWorkflows; 13689 }, 13690 13691 /** 13692 * @private 13693 * Gets the REST class for the objects that make up the collection. - this 13694 * is the TeamWorkflow class. 13695 */ 13696 getRestItemClass: function () { 13697 return TeamWorkflow; 13698 }, 13699 13700 /** 13701 * @private 13702 * Gets the REST type for the current object - this is a "Workflows". 13703 */ 13704 getRestType: function () { 13705 return "Workflows"; 13706 }, 13707 13708 /** 13709 * Overrides the parent class. Returns the url for the Workflows resource 13710 */ 13711 getRestUrl: function () { 13712 var restObj = this._restObj, restUrl = ""; 13713 13714 //Prepend the base REST object if one was provided. 13715 //Otherwise prepend with the default webapp name. 13716 if (restObj instanceof RestBase) { 13717 restUrl += restObj.getRestUrl(); 13718 } else { 13719 restUrl += "/finesse/api/Team"; 13720 } 13721 //Append ID if it is not undefined, null, or empty. 13722 if (this._id) { 13723 restUrl += "/" + this._id; 13724 } 13725 //Append the REST type. 13726 restUrl += "/Workflows"; 13727 13728 return restUrl; 13729 }, 13730 13731 /** 13732 * @private 13733 * Gets the REST type for the objects that make up the collection - this is "Workflow". 13734 */ 13735 getRestItemType: function () { 13736 return "Workflow"; 13737 }, 13738 13739 /** 13740 * @private 13741 * Override default to indicates that the collection supports making requests. 13742 */ 13743 supportsRequests: true, 13744 13745 /** 13746 * @private 13747 * Override default to indicates that the collection does not subscribe to its objects. 13748 */ 13749 supportsRestItemSubscriptions: false, 13750 13751 /** 13752 * Retrieve the Sign Out Reason Codes. 13753 * 13754 * @returns {finesse.restservices.TeamWorkflows} 13755 * This TeamWorkflows object to allow cascading. 13756 */ 13757 get: function () { 13758 // set loaded to false so it will rebuild the collection after the get 13759 this._loaded = false; 13760 // reset collection 13761 this._collection = {}; 13762 // perform get 13763 this._synchronize(); 13764 return this; 13765 }, 13766 13767 /* We only use PUT and GET on Reason Code team assignments 13768 * @param {Object} contact 13769 * @param {Object} contentBody 13770 * @param {Function} successHandler 13771 */ 13772 createPutSuccessHandler: function (contact, contentBody, successHandler) { 13773 return function (rsp) { 13774 // Update internal structure based on response. Here we 13775 // inject the contentBody from the PUT request into the 13776 // rsp.object element to mimic a GET as a way to take 13777 // advantage of the existing _processResponse method. 13778 rsp.object = contentBody; 13779 contact._processResponse(rsp); 13780 13781 //Remove the injected contentBody object before cascading response 13782 rsp.object = {}; 13783 13784 //cascade response back to consumer's response handler 13785 successHandler(rsp); 13786 }; 13787 }, 13788 13789 /** 13790 * Update - This should be all that is needed. 13791 * @param {Object} newValues 13792 * @param {Object} handlers 13793 * @returns {finesse.restservices.TeamWorkflows} 13794 * This TeamWorkflows object to allow cascading. 13795 */ 13796 update: function (newValues, handlers) { 13797 this.isLoaded(); 13798 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 13799 13800 contentBody[this.getRestType()] = { 13801 }; 13802 13803 for (i in newValues) { 13804 if (newValues.hasOwnProperty(i)) { 13805 innerObject = { 13806 "uri": newValues[i] 13807 }; 13808 contentBodyInner.push(innerObject); 13809 } 13810 } 13811 13812 contentBody[this.getRestType()] = { 13813 "Workflow" : contentBodyInner 13814 }; 13815 13816 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13817 handlers = handlers || {}; 13818 13819 this.restRequest(this.getRestUrl(), { 13820 method: 'PUT', 13821 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 13822 error: handlers.error, 13823 content: contentBody 13824 }); 13825 13826 return this; // Allow cascading 13827 } 13828 13829 }); 13830 13831 window.finesse = window.finesse || {}; 13832 window.finesse.restservices = window.finesse.restservices || {}; 13833 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 13834 13835 return TeamWorkflows; 13836 })); 13837 13838 /** 13839 * JavaScript representation of the Finesse Team REST object. 13840 * 13841 * @requires finesse.clientservices.ClientServices 13842 * @requires Class 13843 * @requires finesse.FinesseBase 13844 * @requires finesse.restservices.RestBase 13845 * @requires finesse.restservices.RestCollectionBase 13846 * @requires finesse.restservices.User 13847 * @requires finesse.restservices.Users 13848 */ 13849 13850 /** 13851 * The following comment prevents JSLint errors concerning undefined global variables. 13852 * It tells JSLint that these identifiers are defined elsewhere. 13853 */ 13854 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 13855 13856 /** The following comment is to prevent jslint errors about 13857 * using variables before they are defined. 13858 */ 13859 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 13860 13861 /** @private */ 13862 (function (factory) { 13863 13864 13865 // Define as an AMD module if possible 13866 if ( typeof define === 'function' && define.amd ) 13867 { 13868 define('restservices/Team', ['restservices/RestBase', 13869 'utilities/Utilities', 13870 'restservices/Users', 13871 'restservices/TeamNotReadyReasonCodes', 13872 'restservices/TeamWrapUpReasons', 13873 'restservices/TeamSignOutReasonCodes', 13874 'restservices/TeamPhoneBooks', 13875 'restservices/TeamLayoutConfig', 13876 'restservices/TeamWorkflows'], factory ); 13877 } 13878 /* Define using browser globals otherwise 13879 * Prevent multiple instantiations if the script is loaded twice 13880 */ 13881 else 13882 { 13883 factory(finesse.restservices.RestBase, 13884 finesse.utilities.Utilities, 13885 finesse.restservices.Users, 13886 finesse.restservices.TeamNotReadyReasonCodes, 13887 finesse.restservices.TeamWrapUpReasons, 13888 finesse.restservices.TeamSignOutReasonCodes, 13889 finesse.restservices.TeamPhoneBooks, 13890 finesse.restservices.TeamLayoutConfig, 13891 finesse.restservices.TeamWorkflows); 13892 } 13893 }(function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows) { 13894 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 13895 13896 _teamLayoutConfig: null, 13897 13898 /** 13899 * @class 13900 * A Team is a set of Agent Users, typically supervised by one or more Supervisor Users. 13901 * 13902 * @augments finesse.restservices.RestBase 13903 * @see finesse.restservices.User#getSupervisedTeams 13904 * @see finesse.restservices.Users 13905 * @constructs 13906 */ 13907 _fakeConstuctor: function () { 13908 /* This is here to hide the real init constructor from the public docs */ 13909 }, 13910 13911 /** 13912 * @private 13913 * @class 13914 * JavaScript representation of a Team object. Also exposes methods to operate 13915 * on the object against the server. 13916 * 13917 * @param {Object} options 13918 * An object with the following properties:<ul> 13919 * <li><b>id:</b> The id of the object being constructed</li> 13920 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13921 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13922 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13923 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13924 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13925 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13926 * <li><b>content:</b> {String} Raw string of response</li> 13927 * <li><b>object:</b> {Object} Parsed object of response</li> 13928 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13929 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13930 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13931 * </ul></li> 13932 * </ul></li> 13933 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13934 **/ 13935 init: function (options) { 13936 this._super(options); 13937 }, 13938 13939 /** 13940 * @private 13941 * Gets the REST class for the current object - this is the Team class. 13942 * @returns {Object} The Team constructor. 13943 */ 13944 getRestClass: function () { 13945 return finesse.restesrvices.Team; 13946 }, 13947 13948 /** 13949 * @private 13950 * Gets the REST type for the current object - this is a "Team". 13951 * @returns {String} The Team string. 13952 */ 13953 getRestType: function () { 13954 return "Team"; 13955 }, 13956 13957 /** 13958 * @private 13959 * Override default to indicate that this object doesn't support making 13960 * requests. 13961 */ 13962 supportsSubscriptions: false, 13963 13964 /** 13965 * Getter for the team id. 13966 * @returns {String} The team id. 13967 */ 13968 getId: function () { 13969 this.isLoaded(); 13970 return this.getData().id; 13971 }, 13972 13973 /** 13974 * Getter for the team name. 13975 * @returns {String} The team name 13976 */ 13977 getName: function () { 13978 this.isLoaded(); 13979 return this.getData().name; 13980 }, 13981 13982 /** 13983 * @private 13984 * Getter for the team uri. 13985 * @returns {String} The team uri 13986 */ 13987 getUri: function () { 13988 this.isLoaded(); 13989 return this.getData().uri; 13990 }, 13991 13992 /** 13993 * Constructs and returns a collection of Users. 13994 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers. 13995 * @returns {finesse.restservices.Users} Users collection of User objects. 13996 */ 13997 getUsers: function (options) { 13998 this.isLoaded(); 13999 options = options || {}; 14000 14001 options.parentObj = this; 14002 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 14003 // for Users needs the "Users" key to validate the provided payload matches the class type. 14004 options.data = this.getData(); 14005 14006 return new Users(options); 14007 }, 14008 14009 /** 14010 * @private 14011 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 14012 * @param callbacks 14013 * @returns {teamNotReadyReasonCodes} 14014 * A teamNotReadyReasonCodes collection object. 14015 */ 14016 getTeamNotReadyReasonCodes: function (callbacks) { 14017 var options = callbacks || {}; 14018 options.parentObj = this; 14019 this.isLoaded(); 14020 14021 if (!this._teamNotReadyReasonCodes) { 14022 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 14023 } 14024 14025 return this._teamNotReadyReasonCodes; 14026 }, 14027 14028 /** 14029 * @private 14030 * Getter for a teamWrapUpReasons collection object that is associated with Team. 14031 * @param callbacks 14032 * @returns {teamWrapUpReasons} 14033 * A teamWrapUpReasons collection object. 14034 */ 14035 getTeamWrapUpReasons: function (callbacks) { 14036 var options = callbacks || {}; 14037 options.parentObj = this; 14038 this.isLoaded(); 14039 14040 if (!this._teamWrapUpReasons) { 14041 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 14042 } 14043 14044 return this._teamWrapUpReasons; 14045 }, 14046 14047 /** 14048 * @private 14049 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 14050 * @param callbacks 14051 * @returns {teamSignOutReasonCodes} 14052 * A teamSignOutReasonCodes collection object. 14053 */ 14054 14055 getTeamSignOutReasonCodes: function (callbacks) { 14056 var options = callbacks || {}; 14057 options.parentObj = this; 14058 this.isLoaded(); 14059 14060 if (!this._teamSignOutReasonCodes) { 14061 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 14062 } 14063 14064 return this._teamSignOutReasonCodes; 14065 }, 14066 14067 /** 14068 * @private 14069 * Getter for a teamPhoneBooks collection object that is associated with Team. 14070 * @param callbacks 14071 * @returns {teamPhoneBooks} 14072 * A teamPhoneBooks collection object. 14073 */ 14074 getTeamPhoneBooks: function (callbacks) { 14075 var options = callbacks || {}; 14076 options.parentObj = this; 14077 this.isLoaded(); 14078 14079 if (!this._phonebooks) { 14080 this._phonebooks = new TeamPhoneBooks(options); 14081 } 14082 14083 return this._phonebooks; 14084 }, 14085 14086 /** 14087 * @private 14088 * Getter for a teamWorkflows collection object that is associated with Team. 14089 * @param callbacks 14090 * @returns {teamWorkflows} 14091 * A teamWorkflows collection object. 14092 */ 14093 getTeamWorkflows: function (callbacks) { 14094 var options = callbacks || {}; 14095 options.parentObj = this; 14096 this.isLoaded(); 14097 14098 if (!this._workflows) { 14099 this._workflows = new TeamWorkflows(options); 14100 } 14101 14102 return this._workflows; 14103 }, 14104 14105 /** 14106 * @private 14107 * Getter for a teamLayoutConfig object that is associated with Team. 14108 * @param callbacks 14109 * @returns {teamLayoutConfig} 14110 */ 14111 getTeamLayoutConfig: function (callbacks) { 14112 var options = callbacks || {}; 14113 options.parentObj = this; 14114 this.isLoaded(); 14115 14116 if (this._teamLayoutConfig === null) { 14117 this._teamLayoutConfig = new TeamLayoutConfig(options); 14118 } 14119 14120 return this._teamLayoutConfig; 14121 } 14122 14123 }); 14124 14125 window.finesse = window.finesse || {}; 14126 window.finesse.restservices = window.finesse.restservices || {}; 14127 window.finesse.restservices.Team = Team; 14128 14129 return Team; 14130 })); 14131 14132 /** 14133 * JavaScript representation of the Finesse Teams collection. 14134 * object which contains a list of Team objects 14135 * @requires finesse.clientservices.ClientServices 14136 * @requires Class 14137 * @requires finesse.FinesseBase 14138 * @requires finesse.restservices.RestBase 14139 * @requires finesse.restservices.RestCollectionBase 14140 */ 14141 14142 /** @private */ 14143 (function (factory) { 14144 14145 14146 // Define as an AMD module if possible 14147 if ( typeof define === 'function' && define.amd ) 14148 { 14149 define('restservices/Teams', ['restservices/RestCollectionBase', 14150 'restservices/Team'], factory ); 14151 } 14152 /* Define using browser globals otherwise 14153 * Prevent multiple instantiations if the script is loaded twice 14154 */ 14155 else 14156 { 14157 factory(finesse.restservices.RestCollectionBase, finesse.restservices.Team); 14158 } 14159 }(function (RestCollectionBase, Team) { 14160 /** @private */ 14161 var Teams = RestCollectionBase.extend({ 14162 14163 /** 14164 * @class 14165 * JavaScript representation of a Teams collection object. Also exposes methods to operate 14166 * on the object against the server. 14167 * 14168 * @param {Object} options 14169 * An object with the following properties:<ul> 14170 * <li><b>id:</b> The id of the object being constructed</li> 14171 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14172 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14173 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14174 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14175 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14176 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14177 * <li><b>content:</b> {String} Raw string of response</li> 14178 * <li><b>object:</b> {Object} Parsed object of response</li> 14179 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14180 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14181 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14182 * </ul></li> 14183 * </ul></li> 14184 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14185 * @constructs 14186 **/ 14187 init: function (options) { 14188 this._super(options); 14189 }, 14190 14191 /** 14192 * @private 14193 * Gets the REST class for the current object - this is the Teams class. 14194 * @returns {Object} The Teams constructor. 14195 */ 14196 getRestClass: function () { 14197 return Teams; 14198 }, 14199 14200 /** 14201 * @private 14202 * Gets the REST class for the objects that make up the collection. - this 14203 * is the Team class. 14204 */ 14205 getRestItemClass: function () { 14206 return Team; 14207 }, 14208 14209 /** 14210 * @private 14211 * Gets the REST type for the current object - this is a "Teams". 14212 * @returns {String} The Teams string. 14213 */ 14214 getRestType: function () { 14215 return "Teams"; 14216 }, 14217 14218 /** 14219 * @private 14220 * Gets the REST type for the objects that make up the collection - this is "Team". 14221 */ 14222 getRestItemType: function () { 14223 return "Team"; 14224 }, 14225 14226 /** 14227 * @private 14228 * Override default to indicates that the collection supports making 14229 * requests. 14230 */ 14231 supportsRequests: true, 14232 14233 /** 14234 * @private 14235 * Override default to indicate that this object doesn't support subscriptions. 14236 */ 14237 supportsRestItemSubscriptions: false, 14238 14239 /** 14240 * @private 14241 * Retrieve the Teams. This call will re-query the server and refresh the collection. 14242 * 14243 * @returns {finesse.restservices.Teams} 14244 * This Teams object to allow cascading. 14245 */ 14246 get: function () { 14247 // set loaded to false so it will rebuild the collection after the get 14248 this._loaded = false; 14249 // reset collection 14250 this._collection = {}; 14251 // perform get 14252 this._synchronize(); 14253 return this; 14254 } 14255 14256 }); 14257 14258 window.finesse = window.finesse || {}; 14259 window.finesse.restservices = window.finesse.restservices || {}; 14260 window.finesse.restservices.Teams = Teams; 14261 14262 return Teams; 14263 })); 14264 14265 /** 14266 * JavaScript representation of the Finesse SystemInfo object 14267 * 14268 * @requires finesse.clientservices.ClientServices 14269 * @requires Class 14270 * @requires finesse.FinesseBase 14271 * @requires finesse.restservices.RestBase 14272 */ 14273 14274 /** @private */ 14275 (function (factory) { 14276 14277 14278 // Define as an AMD module if possible 14279 if ( typeof define === 'function' && define.amd ) 14280 { 14281 define('restservices/SystemInfo', ['restservices/RestBase'], factory ); 14282 } 14283 /* Define using browser globals otherwise 14284 * Prevent multiple instantiations if the script is loaded twice 14285 */ 14286 else 14287 { 14288 factory(finesse.restservices.RestBase); 14289 } 14290 }(function (RestBase) { 14291 14292 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 14293 /** 14294 * @private 14295 * Returns whether this object supports subscriptions 14296 */ 14297 supportsSubscriptions: false, 14298 14299 doNotRefresh: true, 14300 14301 /** 14302 * @class 14303 * JavaScript representation of a SystemInfo object. 14304 * 14305 * @augments finesse.restservices.RestBase 14306 * @see finesse.restservices.SystemInfo.Statuses 14307 * @constructs 14308 */ 14309 _fakeConstuctor: function () { 14310 /* This is here to hide the real init constructor from the public docs */ 14311 }, 14312 14313 /** 14314 * @private 14315 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 14316 * on the object against the server. 14317 * 14318 * @param {Object} options 14319 * An object with the following properties:<ul> 14320 * <li><b>id:</b> The id of the object being constructed</li> 14321 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14322 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14323 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14324 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14325 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14326 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14327 * <li><b>content:</b> {String} Raw string of response</li> 14328 * <li><b>object:</b> {Object} Parsed object of response</li> 14329 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14330 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14331 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14332 * </ul></li> 14333 * </ul></li> 14334 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14335 **/ 14336 init: function (id, callbacks, restObj) 14337 { 14338 this._super(id, callbacks, restObj); 14339 }, 14340 14341 /** 14342 * @private 14343 * Gets the REST class for the current object - this is the SystemInfo object. 14344 */ 14345 getRestClass: function () { 14346 return SystemInfo; 14347 }, 14348 14349 /** 14350 * @private 14351 * Gets the REST type for the current object - this is a "SystemInfo". 14352 */ 14353 getRestType: function () 14354 { 14355 return "SystemInfo"; 14356 }, 14357 14358 _validate: function (obj) 14359 { 14360 return true; 14361 }, 14362 14363 /** 14364 * Returns the status of the Finesse system. 14365 * IN_SERVICE if the Finesse API reports that it is in service, 14366 * OUT_OF_SERVICE otherwise. 14367 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 14368 */ 14369 getStatus: function () { 14370 this.isLoaded(); 14371 return this.getData().status; 14372 }, 14373 14374 /** 14375 * Returns the current timestamp from this SystemInfo object. 14376 * This is used to calculate time drift delta between server and client. 14377 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 14378 */ 14379 getCurrentTimestamp: function () { 14380 this.isLoaded(); 14381 return this.getData().currentTimestamp; 14382 }, 14383 14384 /** 14385 * Getter for the xmpp domain of the system. 14386 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 14387 */ 14388 getXmppDomain: function () { 14389 this.isLoaded(); 14390 return this.getData().xmppDomain; 14391 }, 14392 14393 /** 14394 * Getter for the xmpp pubsub domain of the system. 14395 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 14396 */ 14397 getXmppPubSubDomain: function () { 14398 this.isLoaded(); 14399 return this.getData().xmppPubSubDomain; 14400 }, 14401 14402 /** 14403 * Getter for the deployment type (UCCE or UCCX). 14404 * @returns {String} "UCCE" or "UCCX" 14405 */ 14406 getDeploymentType: function () { 14407 this.isLoaded(); 14408 return this.getData().deploymentType; 14409 }, 14410 14411 /** 14412 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 14413 * @returns {Boolean} True for single node deployments, false otherwise. 14414 */ 14415 isSingleNode: function () { 14416 var secondary = this.getData().secondaryNode; 14417 if (secondary && secondary.host) { 14418 return false; 14419 } 14420 return true; 14421 }, 14422 14423 /** 14424 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 14425 * This is useful for getting the FQDN of the current Finesse server. 14426 * @param {String} ...arguments[]... - any number of arguments to match against 14427 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 14428 */ 14429 getThisHost: function () { 14430 var i, 14431 primary = this.getData().primaryNode, 14432 secondary = this.getData().secondaryNode; 14433 14434 for (i = 0; (i < arguments.length); i = i + 1) { 14435 if (primary && arguments[i] === primary.host) { 14436 return primary.host; 14437 } else if (secondary && arguments[i] === secondary.host) { 14438 return secondary.host; 14439 } 14440 } 14441 }, 14442 14443 /** 14444 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 14445 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 14446 * @param {String} arguments - any number of arguments to match against 14447 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 14448 */ 14449 getAlternateHost: function () { 14450 var i, 14451 isPrimary = false, 14452 primary = this.getData().primaryNode, 14453 secondary = this.getData().secondaryNode, 14454 alternateHost; 14455 14456 if (primary && primary.host) { 14457 for (i = 0; (i < arguments.length); i = i + 1) { 14458 if (arguments[i] === primary.host) { 14459 isPrimary = true; 14460 break; 14461 } 14462 } 14463 if (secondary && secondary.host) { 14464 if (isPrimary) { 14465 return secondary.host; 14466 } 14467 return primary.host; 14468 } 14469 } 14470 } 14471 }); 14472 14473 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 14474 /** 14475 * Finesse is in service. 14476 */ 14477 IN_SERVICE: "IN_SERVICE", 14478 /** 14479 * Finesse is not in service. 14480 */ 14481 OUT_OF_SERVICE: "OUT_OF_SERVICE", 14482 /** 14483 * @class SystemInfo status values. 14484 * @constructs 14485 */ 14486 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 14487 14488 }; 14489 14490 window.finesse = window.finesse || {}; 14491 window.finesse.restservices = window.finesse.restservices || {}; 14492 window.finesse.restservices.SystemInfo = SystemInfo; 14493 14494 return SystemInfo; 14495 })); 14496 14497 /** 14498 * Provides standard way resolve message keys with substitution 14499 * 14500 * @requires finesse.container.I18n or gadgets.Prefs 14501 */ 14502 14503 // Add Utilities to the finesse.utilities namespace 14504 var finesse = finesse || {}; 14505 finesse.utilities = finesse.utilities || {}; 14506 14507 (function (factory) { 14508 14509 14510 // Define as an AMD module if possible 14511 if ( typeof define === 'function' && define.amd ) 14512 { 14513 define('utilities/I18n', [], factory ); 14514 } 14515 /* Define using browser globals otherwise 14516 * Prevent multiple instantiations if the script is loaded twice 14517 */ 14518 else 14519 { 14520 finesse.utilities.I18n = factory(); 14521 } 14522 }(function () { 14523 var I18n = (function () { 14524 14525 /** 14526 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 14527 * @private 14528 */ 14529 var _getMsg; 14530 14531 return { 14532 /** 14533 * Provides a message resolver for this utility singleton. 14534 * @param {Function} getMsg 14535 * A function that returns a string given a message key. 14536 * If the key is not found, this function must return 14537 * something that tests false (i.e. undefined or ""). 14538 */ 14539 setGetter : function (getMsg) { 14540 _getMsg = getMsg; 14541 }, 14542 14543 /** 14544 * Resolves the given message key, also performing substitution. 14545 * This generic utility will use a custom function to resolve the key 14546 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 14547 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 14548 * upon the first invocation and store that reference for efficiency. 14549 * 14550 * Since this will construct a new gadgets.Prefs object, it is recommended 14551 * for gadgets to explicitly provide the setter to prevent duplicate 14552 * gadgets.Prefs objects. This does not apply if your gadget does not need 14553 * access to gadgets.Prefs other than getMsg. 14554 * 14555 * @param {String} key 14556 * The key to lookup 14557 * @param {String} arguments 14558 * Arguments for substitution 14559 * @returns {String/Function} 14560 * The resolved string if successful, otherwise a function that returns 14561 * a '???' string that can also be casted into a string. 14562 */ 14563 getString : function (key) { 14564 var prefs, i, retStr, noMsg, getFailed = ""; 14565 if (!_getMsg) { 14566 if (finesse.container && finesse.container.I18n) { 14567 _getMsg = finesse.container.I18n.getMsg; 14568 } else if (gadgets) { 14569 prefs = new gadgets.Prefs(); 14570 _getMsg = prefs.getMsg; 14571 } 14572 } 14573 14574 try { 14575 retStr = _getMsg(key); 14576 } catch (e) { 14577 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 14578 } 14579 14580 if (retStr) { // Lookup was successful, perform substitution (if any) 14581 for (i = 1; i < arguments.length; i += 1) { 14582 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 14583 } 14584 //in order to fix French text with single quotes in it, we need to replace \' with ' 14585 return retStr.replace(/\\'/g, "'"); 14586 } 14587 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 14588 /** @private */ 14589 noMsg = function () { 14590 return "???" + key + "???" + getFailed; 14591 }; 14592 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 14593 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 14594 /** @private */ 14595 noMsg.toString = function () { 14596 return "???" + key + "???" + getFailed; 14597 }; 14598 return noMsg; 14599 14600 } 14601 }; 14602 }()); 14603 14604 return I18n; 14605 })); 14606 14607 /** 14608 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 14609 * 14610 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 14611 */ 14612 // Add Utilities to the finesse.utilities namespace 14613 var finesse = finesse || {}; 14614 finesse.utilities = finesse.utilities || {}; 14615 14616 (function (factory) { 14617 14618 14619 // Define as an AMD module if possible 14620 if ( typeof define === 'function' && define.amd ) 14621 { 14622 define('utilities/Logger', [], factory ); 14623 } 14624 /* Define using browser globals otherwise 14625 * Prevent multiple instantiations if the script is loaded twice 14626 */ 14627 else 14628 { 14629 finesse.utilities.Logger = factory(); 14630 } 14631 }(function () { 14632 var Logger = (function () { 14633 14634 var 14635 14636 /** @private **/ 14637 debugOn, 14638 14639 /** 14640 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 14641 * @param num is the number to pad to 2 digits 14642 * @returns a two digit padded string 14643 * @private 14644 */ 14645 padTwoDigits = function (num) { 14646 return (num < 10) ? '0' + num : num; 14647 }, 14648 14649 /** 14650 * Checks to see if we have a console - this allows us to support Firefox or IE. 14651 * @returns {Boolean} True for Firefox, False for IE 14652 * @private 14653 */ 14654 hasConsole = function () { 14655 var retval = false; 14656 try 14657 { 14658 if (window.console !== undefined) 14659 { 14660 retval = true; 14661 } 14662 } 14663 catch (err) 14664 { 14665 retval = false; 14666 } 14667 14668 return retval; 14669 }, 14670 14671 /** 14672 * Gets a timestamp. 14673 * @returns {String} is a timestamp in the following format: HH:MM:SS 14674 * @private 14675 */ 14676 getTimeStamp = function () { 14677 var date = new Date(), timeStr; 14678 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 14679 14680 return timeStr; 14681 }; 14682 14683 return { 14684 /** 14685 * Enable debug mode. Debug mode may impact performance on the UI. 14686 * 14687 * @param {Boolean} enable 14688 * True to enable debug logging. 14689 * @private 14690 */ 14691 setDebug : function (enable) { 14692 debugOn = enable; 14693 }, 14694 14695 /** 14696 * Logs a string as DEBUG. 14697 * 14698 * @param str is the string to log. 14699 * @private 14700 */ 14701 log : function (str) { 14702 var timeStr = getTimeStamp(); 14703 14704 if (debugOn) { 14705 if (hasConsole()) 14706 { 14707 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 14708 } 14709 } 14710 }, 14711 14712 /** 14713 * Logs a string as INFO. 14714 * 14715 * @param str is the string to log. 14716 * @private 14717 */ 14718 info : function (str) { 14719 var timeStr = getTimeStamp(); 14720 14721 if (hasConsole()) 14722 { 14723 window.console.info(timeStr + ": " + "INFO" + " - " + str); 14724 } 14725 }, 14726 14727 /** 14728 * Logs a string as WARN. 14729 * 14730 * @param str is the string to log. 14731 * @private 14732 */ 14733 warn : function (str) { 14734 var timeStr = getTimeStamp(); 14735 14736 if (hasConsole()) 14737 { 14738 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 14739 } 14740 }, 14741 /** 14742 * Logs a string as ERROR. 14743 * 14744 * @param str is the string to log. 14745 * @private 14746 */ 14747 error : function (str) { 14748 var timeStr = getTimeStamp(); 14749 14750 if (hasConsole()) 14751 { 14752 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 14753 } 14754 } 14755 }; 14756 }()); 14757 14758 return Logger; 14759 })); 14760 14761 /** 14762 * Allows gadgets to call the log function to publish client logging messages over the hub. 14763 * 14764 * @requires OpenAjax 14765 */ 14766 /** @private */ 14767 (function (factory) { 14768 14769 14770 // Define as an AMD module if possible 14771 if ( typeof define === 'function' && define.amd ) 14772 { 14773 define('cslogger/ClientLogger',[], factory ); 14774 } 14775 14776 /* Define using browser globals otherwise 14777 * Prevent multiple instantiations if the script is loaded twice 14778 */ 14779 else 14780 { 14781 factory(); 14782 } 14783 14784 }(function () { 14785 14786 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 14787 var _hub, _logTopic, _originId, _sessId, 14788 14789 /** 14790 * Get a short form (6 character) session ID from sessionStorage 14791 * @private 14792 */ 14793 getSessId = function() { 14794 if (!_sessId) { 14795 //when _sessId not defined yet, initiate it 14796 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 14797 _sessId= " "+window.sessionStorage.getItem('finSessKey')+" "; 14798 } 14799 else { 14800 _sessId=" "; 14801 } 14802 } 14803 return _sessId; 14804 }, 14805 14806 /** 14807 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 14808 * @param num is the number to pad to 2 digits 14809 * @returns a two digit padded string 14810 * @private 14811 */ 14812 padTwoDigits = function (num) 14813 { 14814 return (num < 10) ? '0' + num : num; 14815 }, 14816 14817 /** 14818 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 14819 * @param num is the number to pad to 3 digits 14820 * @returns a three digit padded string 14821 * @private 14822 */ 14823 padThreeDigits = function (num) 14824 { 14825 if (num < 10) 14826 { 14827 return '00'+num; 14828 } 14829 else if (num < 100) 14830 { 14831 return '0'+num; 14832 } 14833 else 14834 { 14835 return num; 14836 } 14837 }, 14838 14839 /** 14840 * Gets a timestamp. 14841 * @returns {String} is a timestamp in the following format: HH:MM:SS 14842 * @private 14843 */ 14844 getDateTimeStamp = function () 14845 { 14846 var date = new Date(), timeStr; 14847 timeStr = date.getFullYear().toString().substring(2) + 14848 padTwoDigits(date.getMonth()+1) + 14849 padTwoDigits (date.getDate()) + " "+ 14850 padTwoDigits(date.getHours()) + ":" + 14851 padTwoDigits(date.getMinutes()) + ":" + 14852 padTwoDigits(date.getSeconds()+"." + 14853 padThreeDigits(date.getMilliseconds())); 14854 14855 return timeStr; 14856 }, 14857 14858 /** 14859 * Checks to see if we have a console. 14860 * @returns Whether the console object exists. 14861 * @private 14862 */ 14863 hasConsole = function () { 14864 try { 14865 if (window.console !== undefined) { 14866 return true; 14867 } 14868 } catch (err) { 14869 // ignore and return false 14870 } 14871 14872 return false; 14873 }, 14874 14875 /** 14876 * Logs a message to a hidden textarea element on the page 14877 * 14878 * @param str is the string to log. 14879 * @param logType is the optional type which is prefixed (default is "LOG") 14880 * @private 14881 */ 14882 writeToLogOutput = function (msg) { 14883 var logOutput = document.getElementById("finesseLogOutput"); 14884 14885 if (logOutput === null) 14886 { 14887 logOutput = document.createElement("textarea"); 14888 logOutput.id = "finesseLogOutput"; 14889 logOutput.style.display = "none"; 14890 document.body.appendChild(logOutput); 14891 } 14892 14893 if (logOutput.value === "") 14894 { 14895 logOutput.value = msg; 14896 } 14897 else 14898 { 14899 logOutput.value = logOutput.value + "\n" + msg; 14900 } 14901 }, 14902 14903 /** 14904 * @private 14905 */ 14906 logToConsole = function (str) 14907 { 14908 var msg, timeStr = getDateTimeStamp(), sessKey=getSessId(); 14909 msg = "\n" + timeStr + sessKey + ": " + ((finesse.container && finesse.container.Config && finesse.container.Config.host) ? finesse.container.Config.host + ": " : "") + str; 14910 14911 // Log to console 14912 if (hasConsole()) { 14913 window.console.log(msg); 14914 } 14915 14916 //Uncomment to print logs to hidden textarea. 14917 //writeToLogOutput(msg); 14918 14919 return msg; 14920 }; 14921 14922 return { 14923 14924 /** 14925 * Publishes a Log Message over the hub. 14926 * 14927 * @param {String} message 14928 * The string to log. 14929 * @example 14930 * _clientLogger.log("This is some important message for MyGadget"); 14931 * 14932 */ 14933 log : function (message) { 14934 if(_hub) { 14935 _hub.publish(_logTopic, logToConsole(_originId + message)); 14936 } 14937 }, 14938 14939 /** 14940 * @class 14941 * Allows gadgets to call the log function to publish client logging messages over the hub. 14942 * 14943 * @constructs 14944 */ 14945 _fakeConstuctor: function () { 14946 /* This is here so we can document init() as a method rather than as a constructor. */ 14947 }, 14948 14949 /** 14950 * Initiates the client logger with a hub and a gadgetId. 14951 * @param {Object} hub 14952 * The hub to communicate with. 14953 * @param {String} gadgetId 14954 * A unique string to identify which gadget is doing the logging. 14955 * @example 14956 * var _clientLogger = finesse.cslogger.ClientLogger; 14957 * _clientLogger.init(gadgets.Hub, "MyGadgetId"); 14958 * 14959 */ 14960 init: function (hub, gadgetId) { 14961 _hub = hub; 14962 _logTopic = "finesse.clientLogging." + gadgetId; 14963 _originId = gadgetId + " : "; 14964 } 14965 }; 14966 }()); 14967 14968 window.finesse = window.finesse || {}; 14969 window.finesse.cslogger = window.finesse.cslogger || {}; 14970 window.finesse.cslogger.ClientLogger = ClientLogger; 14971 14972 finesse = finesse || {}; 14973 /** @namespace Supports writing messages to a central log. */ 14974 finesse.cslogger = finesse.cslogger || {}; 14975 14976 return ClientLogger; 14977 })); 14978 14979 /* using variables before they are defined. 14980 */ 14981 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 14982 14983 /** 14984 * Allows each gadget to communicate with the server to send logs. 14985 */ 14986 14987 /** 14988 * @class 14989 * @private 14990 * Allows each product to initialize its method of storage 14991 */ 14992 (function (factory) { 14993 14994 14995 // Define as an AMD module if possible 14996 if ( typeof define === 'function' && define.amd ) 14997 { 14998 define('cslogger/FinesseLogger',["clientservices/ClientServices"], factory ); 14999 } 15000 15001 /* Define using browser globals otherwise 15002 * Prevent multiple instantiations if the script is loaded twice 15003 */ 15004 else 15005 { 15006 factory(finesse.clientservices.ClientServices); 15007 } 15008 15009 }(function (ClientServices) { 15010 window.finesse = window.finesse || {}; 15011 window.finesse.cslogger = window.finesse.cslogger || {}; 15012 /** @private */ 15013 window.finesse.cslogger.FinesseLogger = (function () { 15014 15015 var 15016 15017 /** 15018 * Array use to collect ongoing logs in memory 15019 * @private 15020 */ 15021 _logArray = [], 15022 15023 /** 15024 * The final data string sent to the server, =_logArray.join 15025 * @private 15026 */ 15027 _logStr = "", 15028 15029 /** 15030 * Keep track of size of log 15031 * @private 15032 */ 15033 _logSize = 0, 15034 15035 /** 15036 * Flag to keep track show/hide of send log link 15037 * @private 15038 */ 15039 _sendLogShown = false, 15040 15041 /** 15042 * Flag to keep track if local log initialized 15043 * @private 15044 */ 15045 _loggingInitialized = false, 15046 15047 15048 /** 15049 * local log size limit 15050 * @private 15051 */ 15052 _maxLocalStorageSize = 5000000, 15053 15054 /** 15055 * half local log size limit 15056 * @private 15057 */ 15058 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 15059 15060 15061 /** 15062 * threshold for purge 15063 * @private 15064 */ 15065 _purgeStartPercent = 0.75, 15066 15067 /** 15068 * log item prefix 15069 * @private 15070 */ 15071 _linePrefix = null, 15072 15073 /** 15074 * locallog session 15075 * @private 15076 */ 15077 _session = null, 15078 15079 /** 15080 * Flag to keep track show/hide of send log link 15081 * @private 15082 */ 15083 _sessionKey = null, 15084 /** 15085 * Log session metadata 15086 * @private 15087 */ 15088 _logInfo = {}, 15089 15090 /** 15091 * Flag to find sessions 15092 * @private 15093 */ 15094 _findSessionsObj = null, 15095 15096 /** 15097 * Wrap up console.log esp. for IE9 15098 * @private 15099 */ 15100 _myConsoleLog = function (str) { 15101 if (window.console !== undefined) { 15102 window.console.log(str); 15103 } 15104 }, 15105 /** 15106 * Initialize the Local Logging 15107 * @private 15108 */ 15109 _initLogging = function () { 15110 if (_loggingInitialized) { 15111 return; 15112 } 15113 //Build a new store 15114 _session = sessionStorage.getItem("finSessKey"); 15115 //if the _session is null or empty, skip the init 15116 if (!_session) { 15117 return; 15118 } 15119 _sessionKey = "Fi"+_session; 15120 _linePrefix = _sessionKey + "_"; 15121 _logInfo = {}; 15122 _logInfo.name = _session; 15123 _logInfo.size = 0; 15124 _logInfo.head = 0; 15125 _logInfo.tail = 0; 15126 _logInfo.startTime = new Date().getTime(); 15127 _loggingInitialized = true; 15128 _initSessionList(); 15129 }, 15130 15131 /** 15132 * get total data size 15133 * 15134 * @return {Integer} which is the amount of data stored in local storage. 15135 * @private 15136 */ 15137 _getTotalData = function () 15138 { 15139 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 15140 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15141 if (!sessionsInfoStr) { 15142 return 0; 15143 } 15144 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15145 15146 for (sessName in sessionsInfoObj.sessions) 15147 { 15148 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 15149 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 15150 if (!sessLogInfoStr) { 15151 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 15152 } 15153 else { 15154 sessLogInfoObj = JSON.parse(sessLogInfoStr); 15155 totalData = totalData + sessLogInfoObj.size; 15156 } 15157 } 15158 } 15159 15160 return totalData; 15161 }, 15162 15163 /** 15164 * Remove lines from tail up until store size decreases to half of max size limit. 15165 * 15166 * @private 15167 */ 15168 _purgeCurrentSession = function() { 15169 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 15170 curStoreSize = _getTotalData(); 15171 if (curStoreSize < _halfMaxLocalStorageSize) { 15172 return; 15173 } 15174 logInfoStr = localStorage.getItem(_sessionKey); 15175 if (!logInfoStr) { 15176 return; 15177 } 15178 theLogInfo = JSON.parse(logInfoStr); 15179 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 15180 while(curStoreSize > _halfMaxLocalStorageSize) { 15181 try { 15182 tailKey = _sessionKey+"_"+theLogInfo.tail; 15183 line = localStorage.getItem(tailKey); 15184 if (line) { 15185 purgedSize = purgedSize +line.length; 15186 localStorage.removeItem(tailKey); 15187 curStoreSize = curStoreSize - line.length; 15188 theLogInfo.size = theLogInfo.size - line.length; 15189 } 15190 } 15191 catch (err) { 15192 _myConsoleLog("purgeCurrentSession encountered err="+err); 15193 } 15194 if (theLogInfo.tail < theLogInfo.head) { 15195 theLogInfo.tail = theLogInfo.tail + 1; 15196 } 15197 else { 15198 break; 15199 } 15200 } 15201 //purge stops here, we need to update session's meta data in storage 15202 secLogInfoStr = localStorage.getItem(_sessionKey); 15203 if (!secLogInfoStr) { 15204 //somebody cleared the localStorage 15205 return; 15206 } 15207 15208 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 15209 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 15210 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 15211 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 15212 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 15213 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 15214 }, 15215 15216 /** 15217 * Purge a session 15218 * 15219 * @param sessionName is the name of the session 15220 * @return {Integer} which is the current amount of data purged 15221 * @private 15222 */ 15223 _purgeSession = function (sessionName) { 15224 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 15225 //Get the session logInfo 15226 logInfoStr = localStorage.getItem("Fi" + sessionName); 15227 if (!logInfoStr) { 15228 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 15229 return 0; 15230 } 15231 theLogInfo = JSON.parse(logInfoStr); 15232 15233 //Note: This assumes that we don't crash in the middle of purging 15234 //=> if we do then it should get deleted next time 15235 //Purge tail->head 15236 while (theLogInfo.tail <= theLogInfo.head) 15237 { 15238 try { 15239 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 15240 theLogInfo.tail = theLogInfo.tail + 1; 15241 } 15242 catch (err) { 15243 _myConsoleLog("In _purgeSession err="+err); 15244 break; 15245 } 15246 } 15247 15248 //Remove the entire session 15249 localStorage.removeItem("Fi" + sessionName); 15250 15251 //Update FinesseSessionsInfo 15252 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15253 if (!sessionsInfoStr) { 15254 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 15255 return 0; 15256 } 15257 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15258 if (sessionsInfoObj.sessions !== null) 15259 { 15260 delete sessionsInfoObj.sessions[sessionName]; 15261 15262 sessionsInfoObj.total = sessionsInfoObj.total - 1; 15263 sessionsInfoObj.lastWrittenBy = _session; 15264 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 15265 } 15266 15267 return theLogInfo.size; 15268 }, 15269 15270 /** 15271 * purge old sessions 15272 * 15273 * @param storeSize 15274 * @return {Boolean} whether purging reaches its target 15275 * @private 15276 */ 15277 _purgeOldSessions = function (storeSize) { 15278 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 15279 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 15280 if (!sessionsInfoStr) { 15281 _myConsoleLog("Could not get FinesseSessionsInfo"); 15282 return true; 15283 } 15284 sessionsInfoObj = JSON.parse(sessionsInfoStr); 15285 curStoreSize = _getTotalData(); 15286 15287 activeSession = _session; 15288 sessions = sessionsInfoObj.sessions; 15289 for (sessName in sessions) { 15290 if (sessions.hasOwnProperty(sessName)) { 15291 if (sessName !== activeSession) { 15292 purgedSize = purgedSize + _purgeSession(sessName); 15293 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 15294 return true; 15295 } 15296 } 15297 } 15298 } 15299 //purge is not done, so return false 15300 return false; 15301 }, 15302 15303 /** 15304 * handle insert error 15305 * 15306 * @param error 15307 * @private 15308 */ 15309 _insertLineHandleError = function (error) { 15310 _myConsoleLog(error); 15311 }, 15312 15313 /** 15314 * check storage data size and if need purge 15315 * @private 15316 */ 15317 _checkSizeAndPurge = function () { 15318 var purgeIsDone=false, totalSize = _getTotalData(); 15319 if (totalSize > 0.75*_maxLocalStorageSize) { 15320 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 15321 purgeIsDone = _purgeOldSessions(totalSize); 15322 if (purgeIsDone) { 15323 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 15324 } 15325 else { 15326 //after all old sessions purged, still need purge 15327 totalSize = _getTotalData(); 15328 if (totalSize > 0.75*_maxLocalStorageSize) { 15329 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 15330 _purgeCurrentSession(); 15331 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 15332 } 15333 } 15334 } 15335 }, 15336 15337 /** 15338 * check if the session is already in meta data 15339 * 15340 * @param metaData 15341 * @param sessionName 15342 * @return {Boolean} true if session has metaData (false otherwise) 15343 * @private 15344 */ 15345 _sessionsInfoContains = function (metaData, sessionName) { 15346 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 15347 return true; 15348 } 15349 return false; 15350 }, 15351 15352 15353 /** 15354 * setup sessions in local storage 15355 * 15356 * @param logInfo 15357 * @private 15358 */ 15359 _getAndSetNumberOfSessions = function (logInfo) { 15360 var numOfSessionsPass1, numOfSessionsPass2, l; 15361 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 15362 if (numOfSessionsPass1 === null) { 15363 //Init first time 15364 numOfSessionsPass1 = {}; 15365 numOfSessionsPass1.total = 1; 15366 numOfSessionsPass1.sessions = {}; 15367 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 15368 numOfSessionsPass1.lastWrittenBy = logInfo.name; 15369 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 15370 } 15371 else { 15372 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 15373 //check if the session is already in the FinesseSessionSInfo 15374 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 15375 return; 15376 } 15377 //Save numOfSessionsPass1 15378 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 15379 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 15380 numOfSessionsPass1.lastWrittenBy = logInfo.name; 15381 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 15382 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 15383 if (!numOfSessionsPass2) { 15384 _myConsoleLog("Could not get FinesseSessionsInfo"); 15385 return; 15386 } 15387 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 15388 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 15389 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 15390 //// _myConsoleLog("Rebuild sessions"); 15391 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 15392 ////} 15393 ////else { 15394 //// _sessionTimerId = null; 15395 ////callback(numOfSessionsPass2.sessions); 15396 ////} 15397 } 15398 if (!localStorage.getItem(_sessionKey)) { 15399 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 15400 } 15401 }, 15402 15403 15404 /** 15405 * init session list 15406 * @private 15407 */ 15408 _initSessionList = function () { 15409 _getAndSetNumberOfSessions(_logInfo); 15410 }, 15411 15412 /** 15413 * do the real store of log line 15414 * 15415 * @param line 15416 * @private 15417 */ 15418 _persistLine = function (line) { 15419 var key, logInfoStr; 15420 logInfoStr = localStorage.getItem(_sessionKey); 15421 if (logInfoStr === null) { 15422 return; 15423 } 15424 _logInfo = JSON.parse(logInfoStr); 15425 _logInfo.head = _logInfo.head + 1; 15426 key = _linePrefix + _logInfo.head; 15427 localStorage.setItem(key, line); 15428 //Save the size 15429 _logInfo.size = _logInfo.size + line.length; 15430 if (_logInfo.tail === 0) { 15431 _logInfo.tail = _logInfo.head; 15432 } 15433 15434 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 15435 _checkSizeAndPurge(); 15436 }, 15437 15438 /** 15439 * Insert a line into the localStorage. 15440 * 15441 * @param line line to be inserted 15442 * @private 15443 */ 15444 _insertLine = function (line) { 15445 //_myConsoleLog("_insertLine: [" + line + "]"); 15446 //Write the next line to localStorage 15447 try { 15448 //Persist the line 15449 _persistLine(line); 15450 } 15451 catch (err) { 15452 _myConsoleLog("error in _insertLine(), err="+err); 15453 //_insertLineHandleError(err); 15454 } 15455 }, 15456 15457 15458 /** 15459 * Clear the local storage 15460 * @private 15461 */ 15462 _clearLocalStorage = function() { 15463 localStorage.clear(); 15464 15465 }, 15466 15467 /** 15468 * Collect logs when onCollect called 15469 * 15470 * @param data 15471 * @private 15472 */ 15473 _collectMethod = function(data) { 15474 //Size of log should not exceed 1.5MB 15475 var info, maxLength = 1572864; 15476 15477 //add size buffer equal to the size of info to be added when publish 15478 info = navigator.userAgent + " "; 15479 info = escape(info); 15480 15481 //If log was empty previously, fade in buttons 15482 if (!_sendLogShown) { 15483 //call the fadeInSendLog() in Footer 15484 finesse.modules.Footer.sendLogAppear(); 15485 _sendLogShown = true; 15486 _logSize = info.length; 15487 } 15488 15489 //if local storage logging is enabled, then insert the log into local storage 15490 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 15491 if (data) { 15492 if (data.length>0 && data.substring(0,1) === '\n') { 15493 _insertLine(data.substring(1)); 15494 } 15495 else { 15496 _insertLine(data); 15497 } 15498 } 15499 } 15500 15501 //escape all data to get accurate size (shindig will escape when it builds request) 15502 //escape 6 special chars for XML: &<>"'\n 15503 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 15504 data = escape(data+"\n"); 15505 15506 if (data.length < maxLength){ 15507 //make room for new data if log is exceeding max length 15508 while (_logSize + data.length > maxLength) { 15509 _logSize -= (_logArray.shift()).length; 15510 } 15511 } 15512 15513 //Else push the log into memory, increment the log size 15514 _logArray.push(data); 15515 15516 //inc the size accordingly 15517 _logSize+=data.length; 15518 15519 }; 15520 15521 return { 15522 15523 /** 15524 * @private 15525 * Initiate FinesseLogger. 15526 */ 15527 init: function () { 15528 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 15529 _initLogging(); 15530 }, 15531 15532 /** 15533 * @private 15534 * Clear all items stored in localStorage. 15535 */ 15536 clear : function () { 15537 _clearLocalStorage(); 15538 }, 15539 15540 /** 15541 * @private 15542 * Initialize the local storage logging. 15543 */ 15544 initLocalLog: function () { 15545 _initLogging(); 15546 }, 15547 15548 /** 15549 * @private 15550 * Inserts a line into the localStorage. 15551 * @param line to insert 15552 */ 15553 localLog : function (line) { 15554 _insertLine(line); 15555 }, 15556 15557 /** 15558 * @ignore 15559 * Publish logs to server and clear the memory 15560 * 15561 * @param userObj 15562 * @param options 15563 * @param callBack 15564 */ 15565 publish: function(userObj, options, callBack) { 15566 // Avoid null references. 15567 options = options || {}; 15568 callBack = callBack || {}; 15569 15570 if (callBack.sending === "function") { 15571 callBack.sending(); 15572 } 15573 15574 //logs the basic version and machine info and escaped new line 15575 _logStr = navigator.userAgent + " "; 15576 15577 //join the logs to correct string format 15578 _logStr += unescape(_logArray.join("")); 15579 15580 //turning log string to JSON obj 15581 var logObj = { 15582 ClientLog: { 15583 logData : _logStr //_logStr 15584 } 15585 }, 15586 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 15587 /** @private */ 15588 options.onAdd = function(){ 15589 tmpOnAdd(); 15590 _logArray.length = 0; _logSize =0; 15591 _sendLogShown = false; 15592 }; 15593 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 15594 /** @private */ 15595 options.onLoad = function (clientLogObj) { 15596 clientLogObj.sendLogs(logObj,{ 15597 error: callBack.error 15598 }); 15599 }; 15600 15601 userObj.getClientLog(options); 15602 } 15603 }; 15604 }()); 15605 })); 15606 15607 /** 15608 * Contains a list of topics used for containerservices pubsub. 15609 * 15610 */ 15611 15612 /** 15613 * @class 15614 * Contains a list of topics with some utility functions. 15615 */ 15616 /** @private */ 15617 (function (factory) { 15618 15619 15620 // Define as an AMD module if possible 15621 if ( typeof define === 'function' && define.amd ) 15622 { 15623 define('containerservices/Topics',[], factory ); 15624 } 15625 15626 /* Define using browser globals otherwise 15627 * Prevent multiple instantiations if the script is loaded twice 15628 */ 15629 else 15630 { 15631 factory(); 15632 } 15633 15634 }(function () { 15635 15636 var Topics = (function () { 15637 15638 /** 15639 * The namespace prepended to all Finesse topics. 15640 */ 15641 this.namespace = "finesse.containerservices"; 15642 15643 /** 15644 * @private 15645 * Gets the full topic name with the ContainerServices namespace prepended. 15646 * @param {String} topic 15647 * The topic category. 15648 * @returns {String} 15649 * The full topic name with prepended namespace. 15650 */ 15651 var _getNSTopic = function (topic) { 15652 return this.namespace + "." + topic; 15653 }; 15654 15655 15656 15657 /** @scope finesse.containerservices.Topics */ 15658 return { 15659 /** 15660 * @private 15661 * request channel. */ 15662 REQUESTS: _getNSTopic("requests"), 15663 15664 /** 15665 * @private 15666 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 15667 */ 15668 getTopic: function (restUri) { 15669 //The topic should not start with '/' else it will get replaced with 15670 //'.' which is invalid. 15671 //Thus, remove '/' if it is at the beginning of the string 15672 if (restUri.indexOf('/') === 0) { 15673 restUri = restUri.substr(1); 15674 } 15675 15676 //Replace every instance of "/" with ".". This is done to follow the 15677 //OpenAjaxHub topic name convention. 15678 return restUri.replace(/\//g, "."); 15679 } 15680 }; 15681 }()); 15682 15683 window.finesse = window.finesse || {}; 15684 window.finesse.containerservices = window.finesse.containerservices || {}; 15685 window.finesse.containerservices.Topics = Topics; 15686 15687 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 15688 finesse.containerservices = finesse.containerservices || {}; 15689 15690 return Topics; 15691 15692 })); 15693 15694 /** The following comment is to prevent jslint errors about 15695 * using variables before they are defined. 15696 */ 15697 /*global finesse*/ 15698 15699 /** 15700 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 15701 * 15702 * @requires OpenAjax, finesse.containerservices.Topics 15703 */ 15704 15705 /** @private */ 15706 (function (factory) { 15707 15708 15709 // Define as an AMD module if possible 15710 if ( typeof define === 'function' && define.amd ) 15711 { 15712 define('containerservices/MasterPublisher',["utilities/Utilities", 15713 "containerservices/Topics"], factory ); 15714 } 15715 15716 /* Define using browser globals otherwise 15717 * Prevent multiple instantiations if the script is loaded twice 15718 */ 15719 else 15720 { 15721 factory(finesse.utilities.Utilities, finesse.containerservices.Topics); 15722 } 15723 15724 }(function (Utilities, Topics) { 15725 15726 var MasterPublisher = function () { 15727 15728 var 15729 15730 /** 15731 * Reference to the gadget pubsub Hub instance. 15732 * @private 15733 */ 15734 _hub = gadgets.Hub, 15735 15736 /** 15737 * Reference to the Topics class. 15738 * @private 15739 */ 15740 _topics = Topics, 15741 15742 /** 15743 * Reference to conversion utilities class. 15744 * @private 15745 */ 15746 _utils = Utilities, 15747 15748 15749 /** 15750 * The types of possible request types supported when listening to the 15751 * requests channel. Each request type could result in different operations. 15752 * @private 15753 */ 15754 _REQTYPES = { 15755 ACTIVETAB: "ActiveTabReq" 15756 }, 15757 15758 /** 15759 * Handles client requests made to the request topic. The type of the 15760 * request is described in the "type" property within the data payload. Each 15761 * type can result in a different operation. 15762 * @param {String} topic 15763 * The topic which data was published to. 15764 * @param {Object} data 15765 * The data containing requests information published by clients. 15766 * @param {String} data.type 15767 * The type of the request. Supported: "ActiveTabReq" 15768 * @param {Object} data.data 15769 * May contain data relevant for the particular requests. 15770 * @param {String} [data.invokeID] 15771 * The ID used to identify the request with the response. The invoke ID 15772 * will be included in the data in the publish to the topic. It is the 15773 * responsibility of the client to correlate the published data to the 15774 * request made by using the invoke ID. 15775 * @private 15776 */ 15777 _clientRequestHandler = function (topic, data) { 15778 15779 //Ensure a valid data object with "type" and "data" properties. 15780 if (typeof data === "object" && 15781 typeof data.type === "string" && 15782 typeof data.data === "object") { 15783 switch (data.type) { 15784 case _REQTYPES.ACTIVETAB: 15785 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 15786 break; 15787 default: 15788 break; 15789 } 15790 } 15791 }; 15792 15793 (function () { 15794 15795 //Listen to a request channel to respond to any requests made by other 15796 //clients because the Master may have access to useful information. 15797 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 15798 }()); 15799 15800 //BEGIN TEST CODE// 15801 /** 15802 * Test code added to expose private functions that are used by unit test 15803 * framework. This section of code is removed during the build process 15804 * before packaging production code. The [begin|end]TestSection are used 15805 * by the build to identify the section to strip. 15806 * @ignore 15807 */ 15808 this.beginTestSection = 0; 15809 15810 /** 15811 * @ignore 15812 */ 15813 this.getTestObject = function () { 15814 //Load mock dependencies. 15815 var _mock = new MockControl(); 15816 _hub = _mock.createMock(gadgets.Hub); 15817 15818 return { 15819 //Expose mock dependencies 15820 mock: _mock, 15821 hub: _hub, 15822 15823 //Expose internal private functions 15824 reqtypes: _REQTYPES, 15825 15826 clientRequestHandler: _clientRequestHandler 15827 15828 }; 15829 }; 15830 15831 15832 /** 15833 * @ignore 15834 */ 15835 this.endTestSection = 0; 15836 //END TEST CODE// 15837 }; 15838 15839 window.finesse = window.finesse || {}; 15840 window.finesse.containerservices = window.finesse.containerservices || {}; 15841 window.finesse.containerservices.MasterPublisher = MasterPublisher; 15842 15843 return MasterPublisher; 15844 })); 15845 15846 /** 15847 * JavaScript representation of the Finesse WorkflowActionEvent object. 15848 * 15849 * @requires finesse.FinesseBase 15850 */ 15851 15852 /** The following comment is to prevent jslint errors about 15853 * using variables before they are defined. 15854 */ 15855 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 15856 /** @private */ 15857 (function (factory) { 15858 15859 15860 // Define as an AMD module if possible 15861 if ( typeof define === 'function' && define.amd ) 15862 { 15863 define('containerservices/WorkflowActionEvent', ["FinesseBase"], factory ); 15864 } 15865 /* Define using browser globals otherwise 15866 * Prevent multiple instantiations if the script is loaded twice 15867 */ 15868 else 15869 { 15870 factory(finesse.FinesseBase); 15871 } 15872 }(function (FinesseBase) { 15873 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 15874 /** 15875 * Reference to the WorkflowActionEvent name 15876 * This will be set by setWorkflowActionEvent 15877 * @private 15878 */ 15879 _name: null, 15880 15881 /** 15882 * Reference to the WorkflowActionEvent type 15883 * This will be set by setWorkflowActionEvent 15884 * @private 15885 */ 15886 _type: null, 15887 15888 /** 15889 * Reference to the WorkflowActionEvent handledBy value 15890 * This will be set by setWorkflowActionEvent 15891 * @private 15892 */ 15893 _handledBy: null, 15894 15895 /** 15896 * Reference to the WorkflowActionEvent params array 15897 * This will be set by setWorkflowActionEvent 15898 * @private 15899 */ 15900 _params: [], 15901 15902 /** 15903 * Reference to the WorkflowActionEvent actionVariables array 15904 * This will be set by setWorkflowActionEvent 15905 * @private 15906 */ 15907 _actionVariables: [], 15908 15909 /** 15910 * @class 15911 * JavaScript representation of a WorkflowActionEvent object. 15912 * The WorkflowActionEvent object is delivered as the payload of 15913 * a WorkflowAction callback. This can be subscribed to by using 15914 * {@link finesse.containerservices.ContainerServices#addHandler} with a 15915 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 15916 * Gadgets should key on events with a handleBy value of "OTHER". 15917 * 15918 * @constructs 15919 **/ 15920 init: function () { 15921 this._super(); 15922 }, 15923 15924 /** 15925 * Validate that the passed in object is a WorkflowActionEvent object 15926 * and sets the variables if it is 15927 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 15928 * it validates successfully. 15929 * @returns {Boolean} Whether it is valid or not. 15930 * @private 15931 */ 15932 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 15933 var returnValue; 15934 15935 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 15936 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 15937 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 15938 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 15939 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 15940 this._name = maybeWorkflowActionEvent.name; 15941 this._type = maybeWorkflowActionEvent.type; 15942 this._handledBy = maybeWorkflowActionEvent.handledBy; 15943 this._params = maybeWorkflowActionEvent.params; 15944 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 15945 returnValue = true; 15946 } else { 15947 returnValue = false; 15948 } 15949 15950 return returnValue; 15951 }, 15952 15953 /** 15954 * Getter for the WorkflowActionEvent name. 15955 * @returns {String} The name of the WorkflowAction. 15956 */ 15957 getName: function () { 15958 // escape nulls to empty string 15959 return this._name || ""; 15960 }, 15961 15962 /** 15963 * Getter for the WorkflowActionEvent type. 15964 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 15965 */ 15966 getType: function () { 15967 // escape nulls to empty string 15968 return this._type || ""; 15969 }, 15970 15971 /** 15972 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 15973 * events with a handleBy of "OTHER". 15974 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 15975 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 15976 */ 15977 getHandledBy: function () { 15978 // escape nulls to empty string 15979 return this._handledBy || ""; 15980 }, 15981 15982 15983 /** 15984 * Getter for the WorkflowActionEvent Params map. 15985 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 15986 * BROWSER_POP<ul> 15987 * <li>windowName : Name of window to pop into, or blank to always open new window. 15988 * <li>path : URL to open.</ul> 15989 * HTTP_REQUEST<ul> 15990 * <li>method : "PUT" or "POST". 15991 * <li>location : "FINESSE" or "OTHER". 15992 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 15993 * <li>path : Request URL. 15994 * <li>body : Request content for POST requests.</ul> 15995 */ 15996 getParams: function () { 15997 var map = {}, 15998 params = this._params, 15999 i, 16000 param; 16001 16002 if (params === null || params.length === 0) { 16003 return map; 16004 } 16005 16006 for (i = 0; i < params.length; i += 1) { 16007 param = params[i]; 16008 // escape nulls to empty string 16009 param.name = param.name || ""; 16010 param.value = param.value || ""; 16011 param.expandedValue = param.expandedValue || ""; 16012 map[param.name] = param; 16013 } 16014 16015 return map; 16016 }, 16017 16018 /** 16019 * Getter for the WorkflowActionEvent ActionVariables map 16020 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 16021 */ 16022 getActionVariables: function() { 16023 var map = {}, 16024 actionVariables = this._actionVariables, 16025 i, 16026 actionVariable; 16027 16028 if (actionVariables === null || actionVariables.length === 0) { 16029 return map; 16030 } 16031 16032 for (i = 0; i < actionVariables.length; i += 1) { 16033 actionVariable = actionVariables[i]; 16034 // escape nulls to empty string 16035 actionVariable.name = actionVariable.name || ""; 16036 actionVariable.type = actionVariable.type || ""; 16037 actionVariable.node = actionVariable.node || ""; 16038 actionVariable.testValue = actionVariable.testValue || ""; 16039 actionVariable.actualValue = actionVariable.actualValue || ""; 16040 map[actionVariable.name] = actionVariable; 16041 } 16042 16043 return map; 16044 } 16045 }); 16046 16047 16048 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 16049 /** 16050 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 16051 * with the action, but first and foremost Finesse will handle this WorkflowAction. 16052 */ 16053 FINESSE: "FINESSE", 16054 16055 /** 16056 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 16057 * ignore this action and expects Gadget Developers to take action. 16058 */ 16059 OTHER: "OTHER", 16060 16061 /** 16062 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 16063 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 16064 * @constructs 16065 */ 16066 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16067 }; 16068 16069 window.finesse = window.finesse || {}; 16070 window.finesse.containerservices = window.finesse.containerservices || {}; 16071 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 16072 16073 return WorkflowActionEvent; 16074 })); 16075 16076 /** 16077 * Exposes a set of API wrappers that will hide the dirty work of 16078 * constructing Finesse API requests and consuming Finesse events. 16079 * 16080 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 16081 */ 16082 16083 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 16084 /*global window:true, gadgets:true, publisher:true, define:true, finesse:true, _tabTracker:true, _workflowActionEventTracker:true, frameElement:true, $:true, parent:true, MockControl:true */ 16085 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 16086 /** @private */ 16087 (function (factory) { 16088 16089 16090 // Define as an AMD module if possible 16091 if ( typeof define === 'function' && define.amd ) 16092 { 16093 define('containerservices/ContainerServices',["utilities/Utilities", 16094 "restservices/Notifier", 16095 "containerservices/Topics", 16096 "containerservices/MasterPublisher", 16097 "containerservices/WorkflowActionEvent"], factory ); 16098 } 16099 16100 /* Define using browser globals otherwise 16101 * Prevent multiple instantiations if the script is loaded twice 16102 */ 16103 else 16104 { 16105 factory(finesse.utilities.Utilities, finesse.restservices.Notifier, finesse.containerservices.Topics, finesse.containerservices.MasterPublisher, finesse.containerservices.WorkflowActionEvent); 16106 } 16107 16108 }(function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 16109 16110 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 16111 16112 var 16113 16114 /** 16115 * Shortcut reference to the Utilities singleton 16116 * This will be set by init() 16117 * @private 16118 */ 16119 _util, 16120 16121 /** 16122 * Shortcut reference to the gadget pubsub Hub instance. 16123 * This will be set by init() 16124 * @private 16125 */ 16126 _hub, 16127 16128 /** 16129 * Boolean whether this instance is master or not 16130 * @private 16131 */ 16132 _master = false, 16133 16134 /** 16135 * Whether the Client Services have been initiated yet. 16136 * @private 16137 */ 16138 _inited = false, 16139 16140 /** 16141 * Stores the list of subscription IDs for all subscriptions so that it 16142 * could be retrieve for unsubscriptions. 16143 * @private 16144 */ 16145 _subscriptionID = {}, 16146 16147 /** 16148 * Reference to the gadget's parent container 16149 * @private 16150 */ 16151 _container, 16152 16153 /** 16154 * Reference to the MasterPublisher 16155 * @private 16156 */ 16157 _publisher, 16158 16159 /** 16160 * Object that will contain the Notifiers 16161 * @private 16162 */ 16163 _notifiers = {}, 16164 16165 /** 16166 * Reference to the tabId that is associated with the gadget 16167 * @private 16168 */ 16169 _myTab = null, 16170 16171 /** 16172 * Reference to the visibility of current gadget 16173 * @private 16174 */ 16175 _visible = false, 16176 16177 /** 16178 * Shortcut reference to the Topics class. 16179 * This will be set by init() 16180 * @private 16181 */ 16182 _topics, 16183 16184 /** 16185 * Associates a topic name with the private handler function. 16186 * Adding a new topic requires that you add this association here 16187 * in to keep addHandler generic. 16188 * @param {String} topic : Specifices the callback to retrieve 16189 * @return {Function} The callback function associated with the topic param. 16190 * @private 16191 */ 16192 _topicCallback = function (topic) { 16193 var callback; 16194 switch (topic) 16195 { 16196 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 16197 callback = _tabTracker; 16198 break; 16199 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 16200 callback = _workflowActionEventTracker; 16201 break; 16202 default: 16203 throw new Error("ContainerServices : _topicCallback(topic) - Topic not recognized."); 16204 } 16205 return callback; 16206 }, 16207 16208 /** 16209 * Ensure that ClientServices have been inited. 16210 * @private 16211 */ 16212 _isInited = function () { 16213 if (!_inited) { 16214 throw new Error("ContainerServices needs to be inited."); 16215 } 16216 }, 16217 16218 /** 16219 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 16220 * @param {String} topic : Specifies the notifier to retrieve 16221 * @return {Notifier} The notifier object. 16222 * @private 16223 */ 16224 _getNotifierReference = function (topic) { 16225 if (!_notifiers.hasOwnProperty(topic)) 16226 { 16227 _notifiers[topic] = new Notifier(); 16228 } 16229 16230 return _notifiers[topic]; 16231 }, 16232 16233 /** 16234 * Utility function to make a subscription to a particular topic. Only one 16235 * callback function is registered to a particular topic at any time. 16236 * @param {String} topic 16237 * The full topic name. The topic name should follow the OpenAjax 16238 * convention using dot notation (ex: finesse.api.User.1000). 16239 * @param {Function} callback 16240 * The function that should be invoked with the data when an event 16241 * is delivered to the specific topic. 16242 * @returns {Boolean} 16243 * True if the subscription was made successfully and the callback was 16244 * been registered. False if the subscription already exist, the 16245 * callback was not overwritten. 16246 * @private 16247 */ 16248 _subscribe = function (topic, callback) { 16249 _isInited(); 16250 16251 //Ensure that the same subscription isn't made twice. 16252 if (!_subscriptionID[topic]) { 16253 //Store the subscription ID using the topic name as the key. 16254 _subscriptionID[topic] = _hub.subscribe(topic, 16255 //Invoke the callback just with the data object. 16256 function (topic, data) { 16257 callback(data); 16258 }); 16259 return true; 16260 } 16261 return false; 16262 }, 16263 16264 /** 16265 * Unsubscribe from a particular topic. 16266 * @param {String} topic : The full topic name. 16267 * @private 16268 */ 16269 _unsubscribe = function (topic) { 16270 _isInited(); 16271 16272 //Unsubscribe from the topic using the subscription ID recorded when 16273 //the subscription was made, then delete the ID from data structure. 16274 _hub.unsubscribe(_subscriptionID[topic]); 16275 delete _subscriptionID[topic]; 16276 }, 16277 16278 /** 16279 * Callback function that is called when an activeTab message is posted to the Hub. 16280 * Notifies listener functions if this tab is the one that was just made active. 16281 * @param {String} tabId : The tabId which was just made visible. 16282 * @private 16283 */ 16284 _tabTracker = function(tabId) { 16285 if (_myTab === null) 16286 { 16287 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 16288 } 16289 16290 if (tabId === _myTab) { 16291 if(!_visible) { 16292 _visible = true; 16293 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 16294 } 16295 } else { 16296 _visible = false; 16297 } 16298 }, 16299 16300 /** 16301 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 16302 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 16303 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 16304 * @private 16305 */ 16306 _workflowActionEventTracker = function(workflowActionEvent) { 16307 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 16308 16309 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 16310 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 16311 } 16312 // else 16313 // { 16314 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 16315 // } 16316 16317 }, 16318 16319 /** 16320 * Utility to be called within a gadget to determine the iframe of the parent container 16321 * corresponds to this gadget so that the gadget id can be parsed from the iframe id. 16322 * @return {String} id of the gadget 16323 * @private 16324 */ 16325 _findMyGadgetId = function () { 16326 var pWindow = parent, myIframe, i, id, iframeId, gadgetFrames; 16327 16328 // First, get the parent window 16329 while (pWindow && pWindow.parent !== pWindow) { 16330 pWindow = pWindow.parent; 16331 } 16332 16333 // Then, get the list of all gadget iframes 16334 gadgetFrames = pWindow.document.getElementsByClassName('gadgets-gadget'); 16335 16336 // Then check to see which iframe matches this window 16337 for (i = 0; i < gadgetFrames.length; i += 1) { 16338 if (gadgetFrames[i].contentWindow === self) { 16339 myIframe = gadgetFrames[i]; 16340 break; 16341 } 16342 } 16343 16344 // Parse the iframe id to get the gadget id 16345 if (myIframe) { 16346 iframeId = myIframe.id; 16347 if (iframeId.indexOf('finesse_gadget_') !== -1) { 16348 id = iframeId.substr(iframeId.indexOf('finesse_gadget_') + 15); 16349 } 16350 } 16351 16352 return id; 16353 }; 16354 16355 return { 16356 /** 16357 * @class 16358 * This class provides container-level services for gadget developers, exposing container events by 16359 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 16360 * event handling (add/remove). 16361 * @example 16362 * containerServices = finesse.containerservices.ContainerServices.init(); 16363 * containerServices.addHandler( 16364 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 16365 * function() { 16366 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 16367 * // automatically adjust the height of the gadget to show the html 16368 * gadgets.window.adjustHeight(); 16369 * }); 16370 * containerServices.makeActiveTabReq(); 16371 * 16372 * @constructs 16373 */ 16374 _fakeConstuctor: function () { 16375 /* This is here so we can document init() as a method rather than as a constructor. */ 16376 }, 16377 16378 /** 16379 * Initialize ContainerServices for use in gadget. 16380 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 16381 * @returns ContainerServices instance. 16382 */ 16383 init: function (master) { 16384 if (!_inited) { 16385 _inited = true; 16386 // Set shortcuts 16387 _util = Utilities; 16388 16389 if (master){ 16390 _master = true; 16391 _container = finesse.container.Container; 16392 _publisher = new MasterPublisher(); 16393 } else { 16394 _container = parent.finesse.container.Container; 16395 } 16396 16397 //init the hub only when it's available 16398 if(gadgets.Hub) { 16399 _hub = gadgets.Hub; 16400 } 16401 16402 if(Topics) { 16403 _topics = Topics; 16404 } 16405 } 16406 16407 this.makeActiveTabReq(); 16408 16409 //Return the CS object for object chaining. 16410 return this; 16411 }, 16412 16413 /** 16414 * Shows the jQuery UI Dialog with the specified parameters. The following are the 16415 * default parameters: <ul> 16416 * <li> Title of "Cisco Finesse".</li> 16417 * <li>Message of "A generic error has occured".</li> 16418 * <li>The only button, "Ok", closes the dialog.</li> 16419 * <li>Modal (blocks other dialogs).</li> 16420 * <li>Not draggable.</li> 16421 * <li>Fixed size.</li></ul> 16422 * @param {Object} options 16423 * An object containing additional options for the dialog. 16424 * @param {String/Boolean} options.title 16425 * Title to use. undefined defaults to "Cisco Finesse". false to hide 16426 * @param {Function} options.close 16427 * A function to invoke when the dialog is closed. 16428 * @param {String} options.message 16429 * The message to display in the dialog. 16430 * Defaults to "A generic error has occurred." 16431 * @param {Boolean} options.isBlocking 16432 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 16433 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 16434 * @see finesse.containerservices.ContainerServices#hideDialog 16435 */ 16436 showDialog: function(options) { 16437 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 16438 return _container.showDialog(options); 16439 } 16440 }, 16441 16442 /** 16443 * Hides the jQuery UI Dialog. 16444 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 16445 * @see finesse.containerservices.ContainerServices#showDialog 16446 */ 16447 hideDialog: function() { 16448 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 16449 return _container.hideDialog(); 16450 } 16451 }, 16452 16453 /** 16454 * Reloads the current gadget. 16455 * For use from within a gadget only. 16456 */ 16457 reloadMyGadget: function () { 16458 if (!_master) { 16459 var gadgetId = _findMyGadgetId(); 16460 16461 if (_container.reloadGadget) { 16462 _container.reloadGadget(gadgetId); 16463 } 16464 } 16465 }, 16466 16467 /** 16468 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 16469 * will be invoked when that topic is notified. 16470 * @param {String} topic 16471 * The Hub topic to which we are listening. 16472 * @param {Function} callback 16473 * The callback function to invoke. 16474 * @see finesse.containerservices.ContainerServices.Topics 16475 * @see finesse.containerservices.ContainerServices#removeHandler 16476 */ 16477 addHandler: function (topic, callback) { 16478 _isInited(); 16479 var notifier = null; 16480 16481 try { 16482 // For backwards compatibility... 16483 if (topic === "tabVisible") { 16484 if (window.console && typeof window.console.log === "function") { 16485 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 16486 } 16487 16488 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 16489 } 16490 16491 // Add the callback to the notifier. 16492 _util.validateHandler(callback); 16493 16494 notifier = _getNotifierReference(topic); 16495 16496 notifier.addListener(callback); 16497 16498 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 16499 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 16500 // to only when necessary. 16501 _subscribe(topic, _topicCallback(topic)); 16502 16503 } catch (err) { 16504 throw new Error("addHandler(): " + err); 16505 } 16506 }, 16507 16508 /** 16509 * Removes a previously-added handler for one of the supported topics. 16510 * @param {String} topic 16511 * The Hub topic from which we are removing the callback. 16512 * @param {Function} callback 16513 * The name of the callback function to remove. 16514 * @see finesse.containerservices.ContainerServices.Topics 16515 * @see finesse.containerservices.ContainerServices#addHandler 16516 */ 16517 removeHandler: function(topic, callback) { 16518 var notifier = null; 16519 16520 try { 16521 _util.validateHandler(callback); 16522 16523 notifier = _getNotifierReference(topic); 16524 16525 notifier.removeListener(callback); 16526 } catch (err) { 16527 throw new Error("removeHandler(): " + err); 16528 } 16529 }, 16530 16531 /** 16532 * Returns the visibility of current gadget. Note that this 16533 * will not be set until after the initialization of the gadget. 16534 * @return {Boolean} The visibility of current gadget. 16535 */ 16536 tabVisible: function(){ 16537 return _visible; 16538 }, 16539 16540 /** 16541 * Make a request to check the current tab. The 16542 * activeTab event will be invoked if on the active tab. This 16543 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 16544 * to ensure the gadget gets properly initialized. 16545 */ 16546 makeActiveTabReq : function () { 16547 if(_hub){ 16548 var data = { 16549 type: "ActiveTabReq", 16550 data: {}, 16551 invokeID: (new Date()).getTime() 16552 }; 16553 _hub.publish(_topics.REQUESTS, data); 16554 } else { 16555 throw new Error("Hub is not defined."); 16556 } 16557 16558 }, 16559 16560 //BEGIN TEST CODE// 16561 /** 16562 * Test code added to expose private functions that are used by unit test 16563 * framework. This section of code is removed during the build process 16564 * before packaging production code. The [begin|end]TestSection are used 16565 * by the build to identify the section to strip. 16566 * @ignore 16567 */ 16568 beginTestSection : 0, 16569 16570 /** 16571 * @ignore 16572 */ 16573 getTestObject: function () { 16574 //Load mock dependencies. 16575 var _mock = new MockControl(); 16576 _util = _mock.createMock(Utilities); 16577 _hub = _mock.createMock(gadgets.Hub); 16578 _inited = true; 16579 return { 16580 //Expose mock dependencies 16581 mock: _mock, 16582 hub: _hub, 16583 util: _util, 16584 addHandler: this.addHandler, 16585 removeHandler: this.removeHandler 16586 }; 16587 }, 16588 16589 /** 16590 * @ignore 16591 */ 16592 endTestSection: 0 16593 //END TEST CODE// 16594 }; 16595 }()); 16596 16597 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 16598 /** 16599 * Topic for subscribing to be notified when the active tab changes. 16600 * The provided callback will be invoked when the tab that the gadget 16601 * that subscribes with this becomes active. To ensure code is called 16602 * when the gadget is already on the active tab use the 16603 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 16604 * method. 16605 */ 16606 ACTIVE_TAB: "finesse.containerservices.activeTab", 16607 16608 /** 16609 * Topic for WorkflowAction events traffic. 16610 * The provided callback will be invoked when a WorkflowAction needs 16611 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 16612 * that can be used to interrogate the WorkflowAction and determine to use or not. 16613 */ 16614 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 16615 16616 /** 16617 * @class This is the set of Topics used for subscribing for events from ContainerServices. 16618 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 16619 * 16620 * @constructs 16621 */ 16622 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16623 }; 16624 16625 window.finesse = window.finesse || {}; 16626 window.finesse.containerservices = window.finesse.containerservices || {}; 16627 window.finesse.containerservices.ContainerServices = ContainerServices; 16628 16629 return ContainerServices; 16630 })); 16631 16632 /** 16633 * This "interface" is just a way to easily jsdoc the Object callback handlers. 16634 * 16635 * @requires finesse.clientservices.ClientServices 16636 * @requires Class 16637 */ 16638 /** @private */ 16639 (function (factory) { 16640 16641 16642 // Define as an AMD module if possible 16643 if ( typeof define === 'function' && define.amd ) 16644 { 16645 define('interfaces/RestObjectHandlers', ["FinesseBase", 16646 "utilities/Utilities", 16647 "restservices/Notifier", 16648 "clientservices/ClientServices", 16649 "clientservices/Topics"], factory ); 16650 } 16651 /* Define using browser globals otherwise 16652 * Prevent multiple instantiations if the script is loaded twice 16653 */ 16654 else 16655 { 16656 factory(finesse.FinesseBase, finesse.utilities.Utilities, 16657 finesse.restservices.Notifier, 16658 finesse.clientservices.ClientServices, 16659 finesse.clientservices.Topics); 16660 } 16661 }(function () { 16662 16663 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 16664 16665 return { 16666 16667 /** 16668 * @class 16669 * This "interface" defines REST Object callback handlers, passed as an argument to 16670 * Object getter methods in cases where the Object is going to be created. 16671 * 16672 * @param {Object} [handlers] 16673 * An object containing callback handlers for instantiation and runtime 16674 * Callback to invoke upon successful instantiation, passes in REST object. 16675 * @param {Function} [handlers.onLoad(this)] 16676 * Callback to invoke upon loading the data for the first time. 16677 * @param {Function} [handlers.onChange(this)] 16678 * Callback to invoke upon successful update object (PUT) 16679 * @param {Function} [handlers.onAdd(this)] 16680 * Callback to invoke upon successful update to add object (POST) 16681 * @param {Function} [handlers.onDelete(this)] 16682 * Callback to invoke upon successful update to delete object (DELETE) 16683 * @param {Function} [handlers.onError(rsp)] 16684 * Callback to invoke on update error (refresh or event) 16685 * as passed by finesse.restservices.RestBase.restRequest()<br> 16686 * {<br> 16687 * status: {Number} The HTTP status code returned<br> 16688 * content: {String} Raw string of response<br> 16689 * object: {Object} Parsed object of response<br> 16690 * error: {Object} Wrapped exception that was caught<br> 16691 * error.errorType: {String} Type of error that was caught<br> 16692 * error.errorMessage: {String} Message associated with error<br> 16693 * }<br> 16694 * <br> 16695 * Note that RestCollections have two additional callback handlers:<br> 16696 * <br> 16697 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 16698 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 16699 16700 * @constructs 16701 */ 16702 _fakeConstuctor: function () { 16703 /* This is here to enable jsdoc to document this as a class. */ 16704 } 16705 }; 16706 }()); 16707 16708 window.finesse = window.finesse || {}; 16709 window.finesse.interfaces = window.finesse.interfaces || {}; 16710 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 16711 16712 return RestObjectHandlers; 16713 16714 })); 16715 16716 16717 /** 16718 * This "interface" is just a way to easily jsdoc the REST request handlers. 16719 * 16720 * @requires finesse.clientservices.ClientServices 16721 * @requires Class 16722 */ 16723 /** @private */ 16724 (function (factory) { 16725 16726 16727 // Define as an AMD module if possible 16728 if ( typeof define === 'function' && define.amd ) 16729 { 16730 define('interfaces/RequestHandlers', ["FinesseBase", 16731 "utilities/Utilities", 16732 "restservices/Notifier", 16733 "clientservices/ClientServices", 16734 "clientservices/Topics"], factory ); 16735 } 16736 /* Define using browser globals otherwise 16737 * Prevent multiple instantiations if the script is loaded twice 16738 */ 16739 else 16740 { 16741 factory(finesse.FinesseBase, finesse.utilities.Utilities, 16742 finesse.restservices.Notifier, 16743 finesse.clientservices.ClientServices, 16744 finesse.clientservices.Topics); 16745 } 16746 }(function () { 16747 16748 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 16749 16750 return { 16751 16752 /** 16753 * @class 16754 * This "interface" defines REST Object callback handlers, passed as an argument to 16755 * Object getter methods in cases where the Object is going to be created. 16756 * 16757 * @param {Object} handlers 16758 * An object containing the following (optional) handlers for the request:<ul> 16759 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 16760 * response object as its only parameter:<ul> 16761 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16762 * <li><b>content:</b> {String} Raw string of response</li> 16763 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 16764 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 16765 * error response object as its only parameter:<ul> 16766 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16767 * <li><b>content:</b> {String} Raw string of response</li> 16768 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 16769 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16770 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16771 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16772 * </ul></li> 16773 * </ul> 16774 16775 * @constructs 16776 */ 16777 _fakeConstuctor: function () { 16778 /* This is here to enable jsdoc to document this as a class. */ 16779 } 16780 }; 16781 }()); 16782 16783 window.finesse = window.finesse || {}; 16784 window.finesse.interfaces = window.finesse.interfaces || {}; 16785 window.finesse.interfaces.RequestHandlers = RequestHandlers; 16786 16787 finesse = finesse || {}; 16788 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 16789 finesse.interfaces = finesse.interfaces || {}; 16790 16791 return RequestHandlers; 16792 16793 })); 16794 16795 16796 (function (factory) { 16797 16798 16799 // Define as an AMD module if possible 16800 if ( typeof define === 'function' && define.amd ) 16801 { 16802 define('finesse-desktop', [], factory ); 16803 } 16804 /* Define using browser globals otherwise 16805 * Prevent multiple instantiations if the script is loaded twice 16806 */ 16807 else 16808 { 16809 factory(); 16810 } 16811 }(function () { 16812 if (typeof require === 'function') { 16813 (function () { 16814 /* 16815 The 'paths' section lets you define keys to refer to libraries 16816 so you don't have to to refer to them by their full paths everywhere. 16817 16818 This removes the need to refer to 3rd-party libraries using the '3rdparty' path, 16819 and the need to refer to patched libaries using the '.patched' suffix. 16820 16821 These keys are usable inside all require() calls inside each gadget module. 16822 */ 16823 require.config({ 16824 paths : { 16825 'Math.uuid' : '../thirdparty/util/Math.uuid', 16826 'iso8601' : '../thirdparty/util/iso8601' 16827 }, 16828 16829 /* 16830 This 'shim' section lets you specify a dependency tree for all libaries that are NOT AMD modules. 16831 (AMD modules always contain a 'define()' call. For more information about AMD: https://github.com/amdjs/amdjs-api/wiki/AMD) 16832 16833 ** DO NOT list any AMD modules in this 'shim' section. ** 16834 16835 The format is 'libraryKey': ['dependencyKey1', 'dependencyKey2' ...]. Both keys can refer to any JS module 16836 or one of the 'path' keys listed above. 16837 16838 FOR EXAMPLE: 16839 dataTables depends on jQuery, but all of the dataTables plugins depend on dataTables. 16840 We can refer to 'dataTables' and 'dataTables.scroller' as keys on the left side 16841 since those are aliases defined in the 'paths' section above. 16842 16843 NOTE: Most jQuery plugins ARE NOT AMD modules, so when adding a new jQuery plugin that is not an AMD module, 16844 make sure it has jquery listed as a dependency here (similar to all of the boostrap-* plugins, jquery.cookie, etc.) 16845 16846 Official documentation on RequireJS shim configuration: http://requirejs.org/docs/api.html#config-shim 16847 */ 16848 shim : { 16849 'Math.uuid' : { 16850 exports : 'Math' 16851 } 16852 } 16853 }); 16854 16855 //All modules in this list (and their dependencies) will be concatenated into one file to be used in the container and gadgets 16856 require(['restservices/Users', 16857 'restservices/Teams', 16858 'restservices/SystemInfo', 16859 'utilities/I18n', 16860 'utilities/Logger', 16861 'utilities/SaxParser', 16862 'cslogger/ClientLogger', 16863 'cslogger/FinesseLogger', 16864 'containerservices/ContainerServices', 16865 'interfaces/RestObjectHandlers', 16866 'interfaces/RequestHandlers'], function() { 16867 // Do nothing 16868 }); 16869 }.bind(this)()); 16870 } 16871 16872 // If being used in a gadget, stuff the auth string into the gadget prefs 16873 if (gadgets.Prefs) { 16874 var _prefs = new gadgets.Prefs(), 16875 authString = finesse.utilities.Utilities.getCookie("finesse_authorization"); 16876 _prefs.set("authorization", authString); 16877 } 16878 })); 16879