1 /** 2 * Copyright (C) 2005-2010 Alfresco Software Limited. 3 * 4 * This file is part of Alfresco 5 * 6 * Alfresco is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * Alfresco is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public License 17 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 /** 21 * YUI Library aliases 22 * Deliberately named differently to the ones various components and modules use, to avoid unexpected behaviour. 23 */ 24 var YUIDom = YAHOO.util.Dom, 25 YUIEvent = YAHOO.util.Event, 26 YUISelector = YAHOO.util.Selector, 27 YUIKeyListener = YAHOO.util.KeyListener; 28 29 /** 30 * Alfresco root namespace. 31 * 32 * @namespace Alfresco 33 */ 34 // Ensure Alfresco root object exists 35 if (typeof Alfresco == "undefined" || !Alfresco) 36 { 37 var Alfresco = {}; 38 } 39 40 /** 41 * Alfresco top-level constants namespace. 42 * 43 * @namespace Alfresco 44 * @class Alfresco.constants 45 */ 46 Alfresco.constants = Alfresco.constants || {}; 47 48 /** 49 * Alfresco top-level template namespace. 50 * 51 * @namespace Alfresco 52 * @class Alfresco.template 53 */ 54 Alfresco.template = Alfresco.template || {}; 55 56 /** 57 * Alfresco top-level component namespace. 58 * 59 * @namespace Alfresco 60 * @class Alfresco.component 61 */ 62 Alfresco.component = Alfresco.component || {}; 63 64 /** 65 * Alfresco top-level dashlet namespace. 66 * 67 * @namespace Alfresco 68 * @class Alfresco.dashlet 69 */ 70 Alfresco.dashlet = Alfresco.dashlet || {}; 71 72 /** 73 * Alfresco top-level module namespace. 74 * 75 * @namespace Alfresco 76 * @class Alfresco.module 77 */ 78 Alfresco.module = Alfresco.module || {}; 79 80 /** 81 * Alfresco top-level util namespace. 82 * 83 * @namespace Alfresco 84 * @class Alfresco.util 85 */ 86 Alfresco.util = Alfresco.util || {}; 87 88 /** 89 * Alfresco top-level logger namespace. 90 * 91 * @namespace Alfresco 92 * @class Alfresco.logger 93 */ 94 Alfresco.logger = Alfresco.logger || {}; 95 96 /** 97 * Alfresco top-level service namespace. 98 * 99 * @namespace Alfresco 100 * @class Alfresco.service 101 */ 102 Alfresco.service = Alfresco.service || {}; 103 104 /** 105 * Alfresco top-level thirdparty namespace. 106 * Used for importing third party javascript functions 107 * 108 * @namespace Alfresco 109 * @class Alfresco.thirdparty 110 */ 111 Alfresco.thirdparty = Alfresco.thirdparty || {}; 112 113 /** 114 * Alfresco top-level widget namespace. 115 * 116 * @namespace Alfresco 117 * @class Alfresco.widget 118 */ 119 Alfresco.widget = Alfresco.widget || {}; 120 121 /** 122 * Alfresco top-level admin namespace. 123 * 124 * @namespace Alfresco 125 * @class Alfresco.Admin 126 */ 127 Alfresco.admin = Alfresco.admin || {}; 128 129 /** 130 * Alfresco top-level action namespace. 131 * Used as a namespace for common functionality reused by multiple components. 132 * 133 * @namespace Alfresco 134 * @class Alfresco.action 135 */ 136 Alfresco.action = Alfresco.action || {}; 137 138 /** 139 * Alfresco top-level doclib namespace. 140 * 141 * @namespace Alfresco 142 * @class Alfresco.doclib 143 */ 144 Alfresco.doclib = Alfresco.doclib || 145 { 146 MODE_SITE: 0, 147 MODE_REPOSITORY: 1 148 }; 149 150 /** 151 * Alfresco top-level messages namespace. 152 * 153 * @namespace Alfresco 154 * @class Alfresco.messages 155 */ 156 Alfresco.messages = Alfresco.messages || 157 { 158 global: null, 159 scope: {} 160 }; 161 162 /** 163 * Checks whether particular plugin for associated mimetype is installed 164 * 165 * @method Alfresco.util.isBrowserPluginInstalled 166 */ 167 Alfresco.util.isBrowserPluginInstalled = function(mimeType) 168 { 169 return navigator.mimeTypes && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin; 170 }; 171 172 /** 173 * Checks whether Mac browser plugin for SharePoint is installed 174 * 175 * @method Alfresco.util.appendArrayToObject 176 */ 177 Alfresco.util.isSharePointPluginInstalled = function() 178 { 179 var webkitPluginInstalled = Alfresco.util.isBrowserPluginInstalled("application/x-sharepoint-webkit"); 180 var npapiPluginInstalled = Alfresco.util.isBrowserPluginInstalled("application/x-sharepoint"); 181 if (YAHOO.env.ua.webkit && webkitPluginInstalled) 182 return true; 183 return npapiPluginInstalled != undefined; 184 }; 185 186 /** 187 * Appends an array onto an object 188 * 189 * @method Alfresco.util.appendArrayToObject 190 * @param obj {object} Object to be appended to 191 * @param arr {array} Array to append/merge onto object 192 * @param p_value {object} Optional: default value for property. 193 * @return {object} The appended object 194 * @static 195 */ 196 Alfresco.util.appendArrayToObject = function(obj, arr, p_value) 197 { 198 var value = (p_value !== undefined ? p_value : true); 199 200 if (YAHOO.lang.isObject(obj) && YAHOO.lang.isArray(arr)) 201 { 202 for (var i = 0, ii = arr.length; i < ii; i++) 203 { 204 if (arr[i] !== undefined) 205 { 206 obj[arr[i]] = value; 207 } 208 } 209 } 210 return obj; 211 }; 212 213 /** 214 * Convert an array into an object 215 * 216 * @method Alfresco.util.arrayToObject 217 * @param arr {array} Array to convert to object 218 * @param p_value {object} Optional: default value for property. 219 * @return {object} Object conversion of array 220 * @static 221 */ 222 Alfresco.util.arrayToObject = function(arr, p_value) 223 { 224 var obj = {}, 225 value = (p_value !== undefined ? p_value : true); 226 227 if (YAHOO.lang.isArray(arr)) 228 { 229 for (var i = 0, ii = arr.length; i < ii; i++) 230 { 231 if (arr[i] !== undefined) 232 { 233 obj[arr[i]] = value; 234 } 235 } 236 } 237 return obj; 238 }; 239 240 /** 241 * Copies the values in an object into a new instance. 242 * 243 * Note 1. This method ONLY copy values of type object, array, date, boolean, string or number. 244 * Note 2. Functions are not copied. 245 * Note 3. Objects with a constructor other than of type Object are still in the result but aren't copied. 246 * This means that objects of HTMLElements or window will be in the result but will not be copied and hence 247 * shared between p_obj and the returned copy og p_obj. 248 * 249 * @method Alfresco.util.deepCopy 250 * @param p_obj {object|array|date|string|number|boolean} The object to copy 251 * @param p_oInstructions {object} (Optional) Contains special non default copy instructions 252 * @param p_oInstructions.copyFunctions {boolean} (Optional) false by default 253 * @return {object|array|date|string|number|boolean} A new instance of the same type as o with the same values 254 * @static 255 */ 256 Alfresco.util.deepCopy = function(p_oObj, p_oInstructions) 257 { 258 if (!p_oObj) 259 { 260 return p_oObj; 261 } 262 if (!p_oInstructions) 263 { 264 p_oInstructions = {}; 265 } 266 267 if (YAHOO.lang.isArray(p_oObj)) 268 { 269 var arr = []; 270 for (var i = 0, il = p_oObj.length, arrVal; i < il; i++) 271 { 272 arrVal = p_oObj[i]; 273 if (!YAHOO.lang.isFunction(arrVal) || p_oInstructions.copyFunctions == true) 274 { 275 arr.push(Alfresco.util.deepCopy(arrVal, p_oInstructions)); 276 } 277 } 278 return arr; 279 } 280 281 if (Alfresco.util.isDate(p_oObj)) 282 { 283 return new Date(p_oObj.getTime()); 284 } 285 286 if (YAHOO.lang.isString(p_oObj) || YAHOO.lang.isNumber(p_oObj) || YAHOO.lang.isBoolean(p_oObj)) 287 { 288 return p_oObj; 289 } 290 291 if (YAHOO.lang.isObject(p_oObj)) 292 { 293 if (p_oObj.toString() == "[object Object]") 294 { 295 var obj = {}, objVal; 296 for (var name in p_oObj) 297 { 298 if (p_oObj.hasOwnProperty(name)) 299 { 300 objVal = p_oObj[name]; 301 if (!YAHOO.lang.isFunction(objVal) || p_oInstructions.copyFunctions == true) 302 { 303 obj[name] = Alfresco.util.deepCopy(objVal, p_oInstructions); 304 } 305 } 306 } 307 return obj; 308 } 309 else 310 { 311 // The object was 312 return p_oObj; 313 } 314 } 315 316 return null; 317 }; 318 319 /** 320 * Tests if o is of type date 321 * 322 * @method Alfresco.util.isDate 323 * @param o {object} The object to test 324 * @return {boolean} True if o is of type date 325 * @static 326 */ 327 Alfresco.util.isDate = function(o) 328 { 329 return o.constructor && o.constructor.toString().indexOf("Date") != -1; 330 }; 331 332 /** 333 * Returns true if obj matches all attributes and their values in pattern. 334 * Attribute values in pattern may contain wildcards ("*"). 335 * 336 * @method objectMatchesPattern 337 * @param obj {object} The object to match pattern against 338 * @param pattern {object} An object with attributes to match against obj 339 * @return {boolean} true if obj matches pattern, false otherwise 340 */ 341 Alfresco.util.objectMatchesPattern = function(obj, pattern) 342 { 343 for (var attrName in pattern) 344 { 345 if (pattern.hasOwnProperty(attrName) && 346 (!pattern.hasOwnProperty(attrName) || (obj[attrName] != pattern[attrName] && pattern[attrName] != "*"))) 347 { 348 return false; 349 } 350 } 351 return true; 352 }; 353 354 /** 355 * Create empty JavaScript object literal from dotted notation string 356 * <pre>e.g. Alfresco.util.dotNotationToObject("org.alfresco.site") returns {"org":{"alfresco":{"site":{}}}}</pre> 357 * 358 * @method Alfresco.util.dotNotationToObject 359 * @param str {string} an dotted notation string 360 * @param value {object|string|number} an optional object to set the "deepest" object to 361 * @return {object} An empty object literal, build from the dotted notation 362 * @static 363 */ 364 Alfresco.util.dotNotationToObject = function(str, value) 365 { 366 var object = {}, obj = object; 367 if (typeof str === "string") 368 { 369 var properties = str.split("."), property, i, ii; 370 for (i = 0, ii = properties.length - 1; i < ii; i++) 371 { 372 property = properties[i]; 373 obj[property] = {}; 374 obj = obj[property]; 375 } 376 obj[properties[i]] = value !== undefined ? value : null; 377 } 378 return object; 379 }; 380 381 /** 382 * Returns an object literal's property value given a dotted notation string representing the property path 383 * 384 * @method Alfresco.util.findValueByDotNotation 385 * @param obj {object} i.e. {org:{alfresco:{site:"share"}}} 386 * @param propertyPath {string} i.e. "org.alfresco.site" 387 * @param defaultValue {object} optional The value to return if there is no value for the propertyPath 388 * @return {object} the value for the property specified by the string, in the example "share" would be returned 389 * @static 390 */ 391 Alfresco.util.findValueByDotNotation = function(obj, propertyPath, defaultValue) 392 { 393 var value = defaultValue ? defaultValue : null; 394 if (propertyPath && obj) 395 { 396 var currObj = obj; 397 var props = propertyPath.split("."); 398 for (var i = 0; i < props.length; i++) 399 { 400 currObj = currObj[props[i]]; 401 if (typeof currObj == "undefined") 402 { 403 return value; 404 } 405 } 406 return currObj; 407 } 408 return value; 409 }; 410 411 /** 412 * Substitutes placeholder dotted notation values in strings given an object containing those properties 413 * 414 * @method Alfresco.util.substituteDotNotation 415 * @param str {string} string containing dot notated property placeholders 416 * @param obj {object} JavaScript object 417 * @return {string} String with populated placeholders 418 */ 419 Alfresco.util.substituteDotNotation = function(str, obj) 420 { 421 return YAHOO.lang.substitute(str, {}, function substituteDotNotation_substitute(p_key, p_value, p_meta) 422 { 423 return Alfresco.util.findValueByDotNotation(obj, p_key); 424 }); 425 } 426 427 /** 428 * Check if an array contains an object 429 * @method Alfresco.util.arrayContains 430 * @param arr {array} Array to convert to object 431 * @param el {object} The element to be searched for in the array 432 * @return {boolean} True if arr contains el 433 * @static 434 */ 435 Alfresco.util.findInArray = function(arr, value, attr) 436 { 437 var index = Alfresco.util.arrayIndex(arr, value, attr); 438 return index !== -1 ? arr[index] : null; 439 }; 440 441 /** 442 * Check if an array contains an object 443 * @method Alfresco.util.arrayContains 444 * @param arr {array} Array to convert to object 445 * @param el {object} The element to be searched for in the array 446 * @return {boolean} True if arr contains el 447 * @static 448 */ 449 Alfresco.util.arrayContains = function(arr, el) 450 { 451 return Alfresco.util.arrayIndex(arr, el) !== -1; 452 }; 453 454 /** 455 * Removes element el from array arr 456 * 457 * @method Alfresco.util.arrayRemove 458 * @param arr {array} Array to remove el from 459 * @param el {object} The element to be removed 460 * @return {boolean} The array now without the element 461 * @static 462 */ 463 Alfresco.util.arrayRemove = function(arr, el) 464 { 465 var i = Alfresco.util.arrayIndex(arr, el); 466 while (i !== -1) 467 { 468 arr.splice(i, 1); 469 i = Alfresco.util.arrayIndex(arr, el); 470 } 471 return arr; 472 }; 473 474 /** 475 * Finds the index of an object in an array 476 * 477 * @method Alfresco.util.arrayIndex 478 * @param arr {array} Array to search in 479 * @param value {object} The element to find the index for in the array 480 * @param attr {string} (Optional) If provided, valu ewill be compared to an attribute inside the object, instead of compared to the object itself. 481 * @return {integer} -1 if not found, other wise the index 482 * @static 483 */ 484 Alfresco.util.arrayIndex = function(arr, value, attr) 485 { 486 if (arr) 487 { 488 for (var i = 0, ii = arr.length; i < ii; i++) 489 { 490 if (attr) 491 { 492 if (arr[i] && arr[i][attr] == value) 493 { 494 return i; 495 } 496 } 497 else if (arr[i] == value) 498 { 499 return i; 500 } 501 } 502 } 503 return -1; 504 }; 505 506 /** 507 * Asserts param contains a proper value 508 * @method Alfresco.util.assertNotEmpty 509 * @param param {object} Parameter to assert valid 510 * @param message {string} Error message to throw on assertion failure 511 * @static 512 * @throws {Error} 513 */ 514 Alfresco.util.assertNotEmpty = function(param, message) 515 { 516 if (typeof param == "undefined" || !param || param === "") 517 { 518 throw new Error(message); 519 } 520 }; 521 522 /** 523 * Check a value is neither undefined nor null (returns false). 524 * An empty string also returns false unless the allowEmptyString flag is set. 525 * @method Alfresco.util.isValueSet 526 * @param value {object} Parameter to check 527 * @param allowEmptyString {boolean} Optional flag to indicate that empty strings are valid inputs. 528 * @static 529 * @return {boolean} Flag indicating whether the value is set or not. 530 */ 531 Alfresco.util.isValueSet = function(value, allowEmptyString) 532 { 533 if (YAHOO.lang.isUndefined(value) || YAHOO.lang.isNull(value)) 534 { 535 return false; 536 } 537 if (YAHOO.lang.isString(value) && value.length === 0 && !!allowEmptyString === false) 538 { 539 return false; 540 } 541 return true; 542 }; 543 544 /** 545 * Append multiple parts of a path, ensuring duplicate path separators are removed. 546 * Leaves "://" patterns intact so URIs and nodeRefs are safe to pass through. 547 * 548 * @method Alfresco.util.combinePaths 549 * @param path1 {string} First path 550 * @param path2 {string} Second path 551 * @param ... 552 * @param pathN {string} Nth path 553 * @return {string} A string containing the combined paths 554 * @static 555 */ 556 Alfresco.util.combinePaths = function() 557 { 558 var path = "", i, ii; 559 for (i = 0, ii = arguments.length; i < ii; i++) 560 { 561 if (arguments[i] !== null) 562 { 563 path += arguments[i] + (arguments[i] !== "/" ? "/" : ""); 564 } 565 } 566 path = path.replace(/(^|[^:])\/{2,}/g, "$1/"); 567 568 // Remove trailing "/" if the last argument didn't end with one 569 if (arguments.length > 0 && !(typeof arguments[arguments.length - 1] === "undefined") && arguments[arguments.length - 1].match(/(.)\/$/) === null) 570 { 571 path = path.replace(/(.)\/$/g, "$1"); 572 } 573 return path; 574 }; 575 576 /** 577 * Constants for conversion between bytes, kilobytes, megabytes and gigabytes 578 */ 579 Alfresco.util.BYTES_KB = 1024; 580 Alfresco.util.BYTES_MB = 1048576; 581 Alfresco.util.BYTES_GB = 1073741824; 582 583 /** 584 * Converts a file size in bytes to human readable form 585 * 586 * @method Alfresco.util.formatFileSize 587 * @param fileSize {number} File size in bytes 588 * @return {string} The file size in a readable form, i.e 1.2mb 589 * @static 590 * @throws {Error} 591 */ 592 Alfresco.util.formatFileSize = function(fileSize) 593 { 594 if (typeof fileSize == "string") 595 { 596 fileSize = parseInt(fileSize, 10); 597 } 598 599 if (fileSize < Alfresco.util.BYTES_KB) 600 { 601 return fileSize + " " + Alfresco.util.message("size.bytes"); 602 } 603 else if (fileSize < Alfresco.util.BYTES_MB) 604 { 605 fileSize = Math.round(fileSize / Alfresco.util.BYTES_KB); 606 return fileSize + " " + Alfresco.util.message("size.kilobytes"); 607 } 608 else if (fileSize < Alfresco.util.BYTES_GB) 609 { 610 fileSize = Math.round(fileSize / Alfresco.util.BYTES_MB); 611 return fileSize + " " + Alfresco.util.message("size.megabytes"); 612 } 613 614 fileSize = Math.round(fileSize / Alfresco.util.BYTES_GB); 615 return fileSize + " " + Alfresco.util.message("size.gigabytes"); 616 }; 617 618 /** 619 * Given a filename, returns either a filetype icon or generic icon file stem 620 * 621 * @method Alfresco.util.getFileIcon 622 * @param p_fileName {string} File to find icon for 623 * @param p_fileType {string} Optional: Filetype to offer further hinting 624 * @param p_iconSize {int} Icon size: 32 625 * @return {string} The icon name, e.g. doc-file-32.png 626 * @static 627 */ 628 Alfresco.util.getFileIcon = function(p_fileName, p_fileType, p_iconSize) 629 { 630 // Mapping from extn to icon name for cm:content 631 var extns = 632 { 633 "aep": "aep", 634 "ai": "ai", 635 "aiff": "aiff", 636 "asf": "video", 637 "asnd": "asnd", 638 "asx": "video", 639 "au": "audio", 640 "avi": "video", 641 "avx": "video", 642 "bmp": "img", 643 "css": "text", 644 "divx": "video", 645 "doc": "doc", 646 "docx": "doc", 647 "eml": "eml", 648 "fla": "fla", 649 "flv": "video", 650 "fxp": "fxp", 651 "gif": "img", 652 "htm": "html", 653 "html": "html", 654 "indd": "indd", 655 "jpeg": "img", 656 "jpg": "img", 657 "key": "key", 658 "mkv": "video", 659 "mov": "video", 660 "movie": "video", 661 "mp3": "mp3", 662 "mp4": "video", 663 "mpeg": "video", 664 "mpeg2": "video", 665 "mpv2": "video", 666 "numbers": "numbers", 667 "odg": "odg", 668 "odp": "odp", 669 "ods": "ods", 670 "odt": "odt", 671 "ogg": "video", 672 "ogv": "video", 673 "pages": "pages", 674 "pdf": "pdf", 675 "png": "img", 676 "ppj": "ppj", 677 "ppt": "ppt", 678 "pptx": "ppt", 679 "psd": "psd", 680 "qt": "video", 681 "rtf": "rtf", 682 "snd": "audio", 683 "spx": "audio", 684 "svg": "img", 685 "swf": "swf", 686 "tiff": "img", 687 "txt": "text", 688 "wav": "audio", 689 "webm": "video", 690 "wmv": "video", 691 "xls": "xls", 692 "xlsx": "xls", 693 "xml": "xml", 694 "xvid": "video", 695 "zip": "zip" 696 }; 697 698 var prefix = "generic", 699 fileType = typeof p_fileType === "string" ? p_fileType : "cm:content", 700 iconSize = typeof p_iconSize === "number" ? p_iconSize : 32; 701 702 // If type = cm:content, then use extn look-up 703 var type = Alfresco.util.getFileIcon.types[fileType]; 704 if (type === "file") 705 { 706 var extn = p_fileName.substring(p_fileName.lastIndexOf(".") + 1).toLowerCase(); 707 if (extn in extns) 708 { 709 prefix = extns[extn]; 710 } 711 } 712 else if (typeof type == "undefined") 713 { 714 type = "file"; 715 } 716 return prefix + "-" + type + "-" + iconSize + ".png"; 717 }; 718 Alfresco.util.getFileIcon.types = 719 { 720 "{http://www.alfresco.org/model/content/1.0}cmobject": "file", 721 "cm:cmobject": "file", 722 "{http://www.alfresco.org/model/content/1.0}content": "file", 723 "cm:content": "file", 724 "{http://www.alfresco.org/model/content/1.0}thumbnail": "file", 725 "cm:thumbnail": "file", 726 "{http://www.alfresco.org/model/content/1.0}folder": "folder", 727 "cm:folder": "folder", 728 "{http://www.alfresco.org/model/content/1.0}category": "category", 729 "cm:category": "category", 730 "{http://www.alfresco.org/model/content/1.0}person": "user", 731 "cm:person": "user", 732 "{http://www.alfresco.org/model/content/1.0}authorityContainer": "group", 733 "cm:authorityContainer": "group", 734 "tag": "tag", 735 "{http://www.alfresco.org/model/site/1.0}sites": "site", 736 "st:sites": "site", 737 "{http://www.alfresco.org/model/site/1.0}site": "site", 738 "st:site": "site", 739 "{http://www.alfresco.org/model/transfer/1.0}transferGroup": "server-group", 740 "trx:transferGroup": "server-group", 741 "{http://www.alfresco.org/model/transfer/1.0}transferTarget": "server", 742 "trx:transferTarget": "server" 743 }; 744 745 /** 746 * Returns the extension from file url or path 747 * 748 * @method Alfresco.util.getFileExtension 749 * @param filePath {string} File path from which to extract file extension 750 * @return {string|null} File extension or null 751 * @static 752 */ 753 Alfresco.util.getFileExtension = function(filePath) 754 { 755 var match = (new String(filePath)).match(/^.*\.([^\.]*)$/); 756 if (YAHOO.lang.isArray(match) && YAHOO.lang.isString(match[1])) 757 { 758 return match[1]; 759 } 760 761 return null; 762 }; 763 764 /** 765 * Returns the windows scroll position that later can be used for i.e. window.scrollTo. 766 * 767 * @method Alfresco.util.getScrollPosition 768 * @return {Array} An array with the x & y position of the scrollbars 769 * @static 770 */ 771 Alfresco.util.getScrollPosition = function() 772 { 773 if (YAHOO.env.ua.ie > 0) 774 { 775 if (document.compatMode && document.compatMode != "BackCompat") 776 { 777 return [ document.documentElement.scrollLeft, document.documentElement.scrollTop ]; 778 } 779 else 780 { 781 return [ document.body.scrollLeft, document.body.scrollTop ]; 782 } 783 } 784 else 785 { 786 return [ window.scrollX, window.scrollY ]; 787 } 788 }; 789 790 /** 791 * Formats a Freemarker datetime into more UI-friendly format 792 * 793 * @method Alfresco.util.formatDate 794 * @param date {string} Optional: Date as returned from data webscript. Today used if missing. 795 * @param mask {string} Optional: Mask to use to override default. 796 * @return {string} Date formatted for UI 797 * @static 798 */ 799 Alfresco.util.formatDate = function(date) 800 { 801 try 802 { 803 return Alfresco.thirdparty.dateFormat.apply(this, arguments); 804 } 805 catch(e) 806 { 807 return date; 808 } 809 }; 810 811 /** 812 * Convert an ISO8601 date string into a JavaScript native Date object 813 * 814 * @method Alfresco.util.fromISO8601 815 * @param date {string} ISO8601 formatted date string 816 * @return {Date|null} JavaScript native Date object 817 * @static 818 */ 819 Alfresco.util.fromISO8601 = function(date) 820 { 821 try 822 { 823 return Alfresco.thirdparty.fromISO8601.apply(this, arguments); 824 } 825 catch(e) 826 { 827 return null; 828 } 829 }; 830 831 /** 832 * Convert a JavaScript native Date object into an ISO8601 date string 833 * 834 * @method Alfresco.util.toISO8601 835 * @param date {Date} JavaScript native Date object 836 * @return {string} ISO8601 formatted date string 837 * @static 838 */ 839 Alfresco.util.toISO8601 = function(date) 840 { 841 try 842 { 843 return Alfresco.thirdparty.toISO8601.apply(this, arguments); 844 } 845 catch(e) 846 { 847 return ""; 848 } 849 }; 850 851 /** 852 * Convert an JSON date exploded into an object literal into a JavaScript native Date object. 853 * NOTE: Passed-in date will have month as zero-based. 854 * 855 * @method Alfresco.util.fromExplodedJSONDate 856 * @param date {object} object literal of the following example format (UTC): 857 * <pre> 858 * date = 859 * { 860 * year: 2009 861 * month: 4 // NOTE: zero-based 862 * date: 22 863 * hours: 14 864 * minutes: 27 865 * seconds: 42 866 * milliseconds: 390 867 * } 868 * </pre> 869 * @return {Date|null} JavaScript native Date object 870 * @static 871 */ 872 Alfresco.util.fromExplodedJSONDate = function(date) 873 { 874 try 875 { 876 var isoDate = YAHOO.lang.substitute("{year 4}-{month 2}-{date 2}T{hours 2}:{minutes 2}:{seconds 2}.{milliseconds 3}Z", date, function(p_key, p_value, p_meta) 877 { 878 if (p_key == "month") 879 { 880 ++p_value; 881 } 882 p_value = String(p_value); 883 var length = parseInt(p_meta, 10) || 2; 884 while (p_value.length < length) 885 { 886 p_value = "0" + p_value; 887 } 888 return p_value; 889 }); 890 return Alfresco.thirdparty.fromISO8601.apply(this, [isoDate, Array.prototype.slice.call(arguments).slice(1)]); 891 } 892 catch(e) 893 { 894 return null; 895 } 896 }; 897 898 /** 899 * Convert an object literal into a JavaScript native Date object into an JSON date exploded. 900 * NOTE: Passed-in date will have month as zero-based. 901 * 902 * @method Alfresco.util.toExplodedJSONDate 903 * @param date {Date} JavaScript Date object 904 * @return {object} 905 * <pre> 906 * date = 907 * { 908 * year: 2009 909 * month: 4 // NOTE: zero-based 910 * date: 22 911 * hours: 14 912 * minutes: 27 913 * seconds: 42 914 * milliseconds: 390 915 * } 916 * </pre> 917 * @static 918 */ 919 Alfresco.util.toExplodedJSONDate = function(date) 920 { 921 return ( 922 { 923 zone: "UTC", 924 year: date.getFullYear(), 925 month: date.getMonth(), 926 date: date.getDate(), 927 hours: date.getHours(), 928 minutes: date.getMinutes(), 929 seconds: date.getSeconds(), 930 milliseconds: date.getMilliseconds() 931 }); 932 }; 933 934 /** 935 * Generate a relative time between two Date objects. 936 * 937 * @method Alfresco.util.relativeTime 938 * @param from {Date|String} JavaScript Date object or ISO8601-formatted date string 939 * @param to {Date|string} (Optional) JavaScript Date object or ISO8601-formatted date string, defaults to now if not supplied 940 * @return {string} Relative time description 941 * @static 942 */ 943 Alfresco.util.relativeTime = function(from, to) 944 { 945 if (YAHOO.lang.isString(from)) 946 { 947 from = Alfresco.util.fromISO8601(from); 948 } 949 else if (!(from instanceof Date)) 950 { 951 return ""; 952 } 953 954 if (YAHOO.lang.isUndefined(to)) 955 { 956 to = new Date(); 957 } 958 else if (YAHOO.lang.isString(to)) 959 { 960 to = Alfresco.util.fromISO8601(to); 961 } 962 963 var $msg = Alfresco.util.message, 964 seconds_ago = ((to - from) / 1000), 965 minutes_ago = Math.floor(seconds_ago / 60), 966 fnTime = function relativeTime_fnTime() 967 { 968 return '<span title="' + Alfresco.util.formatDate(from) + '">' + $msg.apply(this, arguments) + '</span>'; 969 }; 970 971 if (minutes_ago <= 0) 972 { 973 return fnTime("relative.seconds", this, seconds_ago); 974 } 975 if (minutes_ago == 1) 976 { 977 return fnTime("relative.minute", this); 978 } 979 if (minutes_ago < 45) 980 { 981 return fnTime("relative.minutes", this, minutes_ago); 982 } 983 if (minutes_ago < 90) 984 { 985 return fnTime("relative.hour", this); 986 } 987 var hours_ago = Math.round(minutes_ago / 60); 988 if (minutes_ago < 1440) 989 { 990 return fnTime("relative.hours", this, hours_ago); 991 } 992 if (minutes_ago < 2880) 993 { 994 return fnTime("relative.day", this); 995 } 996 var days_ago = Math.round(minutes_ago / 1440); 997 if (minutes_ago < 43200) 998 { 999 return fnTime("relative.days", this, days_ago); 1000 } 1001 if (minutes_ago < 86400) 1002 { 1003 return fnTime("relative.month", this); 1004 } 1005 var months_ago = Math.round(minutes_ago / 43200); 1006 if (minutes_ago < 525960) 1007 { 1008 return fnTime("relative.months", this, months_ago); 1009 } 1010 if (minutes_ago < 1051920) 1011 { 1012 return fnTime("relative.year", this); 1013 } 1014 var years_ago = Math.round(minutes_ago / 525960); 1015 return fnTime("relative.years", this, years_ago); 1016 }; 1017 1018 /** 1019 * Converts a date to a more user friendly date 1020 * 1021 * @method Alfresco.util.friendlyDate 1022 * @param date {Date} - the date being converted 1023 * @param format {string} - the date format 1024 * @return {string} - the user friendly date 1025 */ 1026 Alfresco.util.friendlyDate = function(date, format) 1027 { 1028 var $msg = Alfresco.util.message, 1029 now = new Date(), 1030 dateFormat = Alfresco.thirdparty.dateFormat, 1031 dateMath = YAHOO.widget.DateMath, 1032 isoMask = Alfresco.thirdparty.dateFormat.masks.isoDate, 1033 isoDate = dateFormat(date, isoMask), 1034 isoToday = dateFormat(now, isoMask), 1035 isoYesterday = dateFormat(dateMath.add(now, dateMath.DAY, -1), isoMask), 1036 isoTomorrow = dateFormat(dateMath.add(now, dateMath.DAY, 1), isoMask); 1037 result = ""; 1038 1039 switch(isoDate) 1040 { 1041 case isoToday: 1042 result = $msg("dateFormat.friendly.today"); 1043 break; 1044 case isoYesterday: 1045 result = $msg("dateFormat.friendly.yesterday"); 1046 break; 1047 case isoTomorrow: 1048 result = $msg("dateFormat.friendly.tomorrow"); 1049 break; 1050 default: 1051 result = dateFormat(date, format); 1052 1053 } 1054 return result; 1055 } 1056 1057 /** 1058 * Pad a value with leading zeros to the specified length. 1059 * 1060 * @method Alfresco.util.pad 1061 * @param value {string|number} non null value to pad 1062 * @param value {number} length to pad out with leading zeros 1063 * @return {string} padded value as a string 1064 * @static 1065 */ 1066 Alfresco.util.pad = function(value, length) 1067 { 1068 value = String(value); 1069 length = parseInt(length, 10) || 2; 1070 while (value.length < length) 1071 { 1072 value = "0" + value; 1073 } 1074 return value; 1075 }; 1076 1077 /** 1078 * Inserts the given string into the supplied text element at the current cursor position. 1079 * 1080 * @method Alfresco.util.insertAtCursor 1081 * @param el {object} The Dom text element to insert into 1082 * @param txt {string} The string to insert at current cursor position 1083 * @static 1084 */ 1085 Alfresco.util.insertAtCursor = function(el, txt) 1086 { 1087 if (document.selection) 1088 { 1089 el.focus(); 1090 document.selection.createRange().text = txt; 1091 } 1092 else if (el.selectionStart || el.selectionStart == '0') 1093 { 1094 el.value = el.value.substring(0, el.selectionStart) + txt + el.value.substring(el.selectionEnd, el.value.length); 1095 } 1096 else 1097 { 1098 el.value += txt; 1099 } 1100 el.focus(); 1101 }; 1102 1103 /** 1104 * Selects text in the input field. 1105 * 1106 * @method selectText 1107 * @param elTextbox {HTMLElement} Text input box element in which to select text. 1108 * @param nStart {Number} Starting index of text string to select. 1109 * @param nEnd {Number} Ending index of text selection. 1110 */ 1111 Alfresco.util.selectText = function(elTextbox, nStart, nEnd) 1112 { 1113 if (elTextbox.setSelectionRange) 1114 { 1115 elTextbox.setSelectionRange(nStart, nEnd); 1116 } 1117 else if (elTextbox.createTextRange) 1118 { 1119 // For IE 1120 var oTextRange = elTextbox.createTextRange(); 1121 oTextRange.moveStart("character", nStart); 1122 oTextRange.moveEnd("character", nEnd-elTextbox.value.length); 1123 oTextRange.select(); 1124 } 1125 else 1126 { 1127 elTextbox.select(); 1128 } 1129 }; 1130 1131 /** 1132 * Checks if the element and all its parents are visible and displayed in the ui. 1133 * 1134 * @method Alfresco.util.isVisible 1135 * @param el {object} The Dom element to check visibility for 1136 * @return true if el and all its parents are displayed in ui 1137 * @static 1138 */ 1139 Alfresco.util.isVisible = function (el) 1140 { 1141 try 1142 { 1143 while (el) 1144 { 1145 if (el.tagName) 1146 { 1147 if(el.tagName.toLowerCase() == "body") 1148 { 1149 return true; 1150 } 1151 } 1152 if (YUIDom.getStyle(el, "display") == "none" || YUIDom.getStyle(el, "visibility") == "hidden") 1153 { 1154 return false; 1155 } 1156 el = el.parentNode; 1157 } 1158 } 1159 catch(ex) 1160 { 1161 return false; 1162 } 1163 return true; 1164 }; 1165 1166 /** 1167 * Decodes an HTML-encoded string 1168 * Replaces < > and & entities with their character equivalents 1169 * 1170 * @method Alfresco.util.decodeHTML 1171 * @param html {string} The string containing HTML entities 1172 * @return {string} Decoded string 1173 * @static 1174 */ 1175 Alfresco.util.decodeHTML = function(html) 1176 { 1177 if (html === null) 1178 { 1179 return ""; 1180 } 1181 return html.split("<").join("<").split(">").join(">").split("&").join("&").split(""").join('"'); 1182 }; 1183 1184 /** 1185 * Encodes a potentially unsafe string with HTML entities 1186 * Replaces <pre><, >, &</pre> characters with their entity equivalents. 1187 * Based on the equivalent encodeHTML and unencodeHTML functions in Prototype. 1188 * 1189 * @method Alfresco.util.encodeHTML 1190 * @param text {string} The string to be encoded 1191 * @param justified {boolean} If true, don't render lines 2..n with an indent 1192 * @return {string} Safe HTML string 1193 * @static 1194 */ 1195 Alfresco.util.encodeHTML = function(text, justified) 1196 { 1197 if (text === null || typeof text == "undefined") 1198 { 1199 return ""; 1200 } 1201 1202 var indent = justified === true ? "" : " "; 1203 1204 if (YAHOO.env.ua.ie > 0) 1205 { 1206 text = "" + text; 1207 return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br />" + indent).replace(/"/g, """); 1208 } 1209 var me = arguments.callee; 1210 me.text.data = text; 1211 return me.div.innerHTML.replace(/\n/g, "<br />" + indent).replace(/"/g, """); 1212 }; 1213 Alfresco.util.encodeHTML.div = document.createElement("div"); 1214 Alfresco.util.encodeHTML.text = document.createTextNode(""); 1215 Alfresco.util.encodeHTML.div.appendChild(Alfresco.util.encodeHTML.text); 1216 1217 /** 1218 * Encodes a folder path string for use in a REST URI. 1219 * First performs a encodeURI() pass so that the '/' character is maintained 1220 * as the path must be intact as URI elements. Then encodes further characters 1221 * on the path that would cause problems in URLs, such as '&', '=' and '#'. 1222 * 1223 * @method Alfresco.util.encodeURIPath 1224 * @param text {string} The string to be encoded 1225 * @return {string} Encoded path URI string. 1226 * @static 1227 */ 1228 Alfresco.util.encodeURIPath = function(text) 1229 { 1230 return encodeURIComponent(text).replace(/%2F/g, "/"); 1231 }; 1232 1233 /** 1234 * Scans a text string for links and injects HTML mark-up to activate them. 1235 * NOTE: If used in conjunction with encodeHTML, this function must be called last. 1236 * 1237 * @method Alfresco.util.activateLinks 1238 * @param text {string} The string potentially containing links 1239 * @return {string} String with links marked-up to make them active 1240 * @static 1241 */ 1242 Alfresco.util.activateLinks = function(text) 1243 { 1244 var re = new RegExp(/((http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?\^=%&:\/~\+#]*[\w\-\@?\^=%&\/~\+#])?)/g); 1245 text = text.replace(re, "<a href=\"$1\" target=\"_blank\">$1</a>"); 1246 return text; 1247 }; 1248 1249 /** 1250 * Convert a plaintext Tweet into HTML with detected links parsed and "activated" 1251 * 1252 * @method Alfresco.util.tweetToHTML 1253 * @param text {string} The plaintext Tweet 1254 * @return {string} HTML string 1255 */ 1256 Alfresco.util.tweetToHTML = function(text) 1257 { 1258 // URLs 1259 text = Alfresco.util.activateLinks(text); 1260 1261 // User links 1262 var re = new RegExp(/(^|[^\w])@([\w]{1,})/g); 1263 text = text.replace(re, "$1<a href=\"http://twitter.com/$2\">@$2</a>"); 1264 1265 // Hash tags 1266 re = new RegExp(/#+([\w]{1,})/g); 1267 text = text.replace(re, "<a href=\"http://search.twitter.com/search?q=%23$1\">#$1</a>"); 1268 1269 return text; 1270 }; 1271 1272 /** 1273 * Tests a select element's options against "value" and 1274 * if there is a match that option is set to the selected index. 1275 * 1276 * @method Alfresco.util.setSelectedIndex 1277 * @param value {HTMLSelectElement} The select element to change the selectedIndex for 1278 * @param selectEl {string} The value to match agains the select elements option values 1279 * @return {string} The label/name of the seleceted option OR null if no option was found 1280 * @static 1281 */ 1282 Alfresco.util.setSelectedIndex = function(selectEl, value) 1283 { 1284 for (var i = 0, l = selectEl.options.length; i < l; i++) 1285 { 1286 if (selectEl.options[i].value == value) 1287 { 1288 selectEl.selectedIndex = i; 1289 return selectEl.options[i].text; 1290 } 1291 } 1292 return null; 1293 }; 1294 1295 /** 1296 * Removes selectClass from all of selectEl's parent's child elements but adds it to selectEl. 1297 * 1298 * @method Alfresco.util.setSelectedClass 1299 * @param parentEl {HTMLElement} The elements to deselct 1300 * @param selectEl {HTMLElement} The element to select 1301 * @param selectClass {string} The css class to remove from unselected and add to the selected element 1302 * @static 1303 */ 1304 Alfresco.util.setSelectedClass = function(parentEl, selectEl, selectClass) 1305 { 1306 var children = parentEl.childNodes, 1307 child = null; 1308 1309 selectClass = selectClass ? selectClass : "selected"; 1310 for (var i = 0, l = children.length; i < l; i++) 1311 { 1312 child = children[i]; 1313 if (!selectEl || child.tagName == selectEl.tagName) 1314 { 1315 YUIDom.removeClass(child, selectClass); 1316 if (child === selectEl) 1317 { 1318 YUIDom.addClass(child, selectClass); 1319 } 1320 } 1321 } 1322 }; 1323 1324 /** 1325 * Helper function to listen for mouse click and keyboard enter events on an element. 1326 * Also makes sure that el has a tabindex so it can get focus 1327 * 1328 * @method useAsButton 1329 * @param el {String|HTMLElement} The element to listen to 1330 * @param callbackFn {function} The method to invoke on click or enter will get called with (event, obj) 1331 * @param callbackObj {Object} The object to pass into the callback 1332 * @param callbackScope {Object} (Optional) The scope to execute the callback in (if not supplied el is used) 1333 * @static 1334 */ 1335 Alfresco.util.useAsButton = function(el, callbackFn, callbackObj, callbackScope) 1336 { 1337 YAHOO.util.Event.addListener(el, "click", callbackFn, callbackObj, callbackScope); 1338 if (YAHOO.lang.isString(el)) { 1339 el = YAHOO.util.Dom.get(el); 1340 } 1341 if(!el.getAttribute("tabindex")) { 1342 el.setAttribute("tabindex", "0"); 1343 } 1344 var fn = callbackFn, 1345 obj = callbackObj, 1346 scope = callbackScope || el; 1347 var callback = function (type, arg) 1348 { 1349 fn.call(scope, type, obj || arg); 1350 }; 1351 new YAHOO.util.KeyListener(el, 1352 { 1353 keys: [ YAHOO.util.KeyListener.KEY.ENTER ] 1354 }, callback).enable(); 1355 }; 1356 1357 /** 1358 * Returns a unique DOM ID for dynamically-created content. Optionally applies the new ID to an element. 1359 * 1360 * @method Alfresco.util.generateDomId 1361 * @param p_el {HTMLElement} Applies new ID to element 1362 * @param p_prefix {string} Optional prefix instead of "alf-id" default 1363 * @return {string} Dom Id guaranteed to be unique on the current page 1364 */ 1365 Alfresco.util.generateDomId = function(p_el, p_prefix) 1366 { 1367 var domId, prefix = p_prefix || "alf-id"; 1368 do 1369 { 1370 domId = prefix + Alfresco.util.generateDomId._nId++; 1371 } while (YUIDom.get(domId) !== null); 1372 1373 Alfresco.util.setDomId(p_el, domId); 1374 1375 return domId; 1376 }; 1377 Alfresco.util.generateDomId._nId = 0; 1378 1379 /** 1380 * Sets the domId as the html dom id on el 1381 * 1382 * @method Alfresco.util.setDomId 1383 * @param p_el {HTMLElement} Applies new ID to element 1384 * @param p_domId {string} The dom id to apply 1385 */ 1386 Alfresco.util.setDomId = function(p_el, p_domId) 1387 { 1388 if (p_el) 1389 { 1390 if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) 1391 { 1392 // MSIE 6 & 7-safe method 1393 p_el.attributes["id"].value = p_domId; 1394 p_el.setAttribute("id", p_domId); 1395 } 1396 else 1397 { 1398 p_el.setAttribute("id", p_domId); 1399 } 1400 } 1401 }; 1402 1403 /** 1404 * Converts "rel" attributes on <a> tags to "target" attributes. 1405 * "target" isn't supported in XHTML, so we use "rel" as a placeholder and replace at runtime. 1406 * 1407 * @method relToTarget 1408 * @param rootNode {HTMLElement|String} An id or HTMLElement to start the query from 1409 */ 1410 Alfresco.util.relToTarget = function(p_rootNode) 1411 { 1412 var elements = YUISelector.query("a[rel]", p_rootNode); 1413 for (var i = 0, ii = elements.length; i < ii; i++) 1414 { 1415 elements[i].setAttribute("target", elements[i].getAttribute("rel")); 1416 } 1417 }; 1418 1419 /** 1420 * Sets single or multiple DOM element innerHTML values. 1421 * Ensures target element exists in the DOM before attempting to set the label. 1422 * 1423 * @method populateHTML 1424 * @param p_label, p_label, ... {Array} Array with exactly two members: 1425 * <pre> 1426 * [0] {String | HTMLElement} Accepts a string to use as an ID for getting a DOM reference, or an actual DOM reference.<br /> 1427 * [1] {String} HTML content to populate element if it exists in the DOM. HTML will NOT be escaped. 1428 * </pre> 1429 */ 1430 Alfresco.util.populateHTML = function() 1431 { 1432 for (var i = 0, ii = arguments.length, el = null; i < ii; i++) 1433 { 1434 el = YUIDom.get(arguments[i][0]); 1435 if (el) 1436 { 1437 el.innerHTML = arguments[i][1]; 1438 } 1439 } 1440 }; 1441 1442 /** 1443 * Checks whether the current browser supports a given CSS property. 1444 * The property should be passed in canonical form and without vendor prefix 1445 * e.g. "TextShadow" 1446 * 1447 * @method hasCssProperty 1448 * @param property {string} CSS property to test 1449 * @return {boolean} True if the browser appears to support the property 1450 */ 1451 Alfresco.util.hasCssProperty = function(property) 1452 { 1453 if (Alfresco.util.hasCssProperty.div.style.hasOwnProperty(property)) 1454 { 1455 return true; 1456 } 1457 1458 // Ensure first character is uppercase 1459 property = property.replace(/^[a-z]/, function(val) 1460 { 1461 return val.toUpperCase(); 1462 }); 1463 1464 var len = Alfresco.util.hasCssProperty.vendors.length; 1465 while(len--) 1466 { 1467 if (Alfresco.util.hasCssProperty.div.style.hasOwnProperty(vendors[len] + property)) 1468 { 1469 return true; 1470 } 1471 } 1472 return false; 1473 }; 1474 Alfresco.util.hasCssProperty.div = document.createElement("div"); 1475 Alfresco.util.hasCssProperty.vendors = ["Khtml", "O", "ms", "Moz", "Webkit"]; 1476 1477 /** 1478 * Wrapper to create a YUI Button with common attributes. 1479 * All supplied object parameters are passed to the button constructor 1480 * e.g. Alfresco.util.createYUIButton(this, "OK", this.onOK, {type: "submit"}); 1481 * 1482 * @method Alfresco.util.createYUIButton 1483 * @param p_scope {object} Component containing button; must have "id" parameter 1484 * @param p_name {string} Dom element ID of markup that button is created from {p_scope.id}-{name} 1485 * @param p_onclick {function} If supplied, registered with the button's click event 1486 * @param p_obj {object} Optional extra object parameters to pass to button constructor 1487 * @param p_oElement {string|HTMLElement} Optional and accepts a string to use as an ID for getting a DOM reference or an actual DOM reference 1488 * @return {YAHOO.widget.Button} New Button instance 1489 * @static 1490 */ 1491 Alfresco.util.createYUIButton = function(p_scope, p_name, p_onclick, p_obj, p_oElement) 1492 { 1493 // Default button parameters 1494 var obj = 1495 { 1496 type: "button", 1497 disabled: false 1498 }; 1499 1500 // Any extra parameters? 1501 if (typeof p_obj == "object") 1502 { 1503 obj = YAHOO.lang.merge(obj, p_obj); 1504 } 1505 1506 // Fix-up the menu element ID 1507 if ((obj.type == "menu") && (typeof obj.menu == "string")) 1508 { 1509 obj.menu = p_scope.id + "-" + obj.menu; 1510 } 1511 1512 // Create the button 1513 var oElement = p_oElement ? p_oElement : p_scope.id + "-" + p_name, 1514 button = null; 1515 1516 if (YUIDom.get(oElement) !== null) 1517 { 1518 button = new YAHOO.widget.Button(oElement, obj); 1519 1520 if (typeof button == "object") 1521 { 1522 // Register the click listener if one was supplied 1523 if (typeof p_onclick == "function") 1524 { 1525 if (obj.type == "menu") 1526 { 1527 // Special case for a menu 1528 button.getMenu().subscribe("click", p_onclick, p_scope, true); 1529 button.getMenu().subscribe("keydown", function (p_sType, p_aArgs, p_oObj) 1530 { 1531 if (p_aArgs[0].keyCode == YUIKeyListener.KEY.ENTER) 1532 { 1533 this.hide(); 1534 p_oObj.fn.call(p_oObj.scope, p_sType, p_aArgs); 1535 } 1536 }, 1537 { 1538 scope: p_scope, 1539 fn: p_onclick 1540 }); 1541 } 1542 else if (obj.type == "checkbox") 1543 { 1544 // Special case for a checkbox button 1545 button.on("checkedChange", p_onclick, button, p_scope); 1546 } 1547 else 1548 { 1549 button.on("click", p_onclick, button, p_scope); 1550 } 1551 } 1552 1553 // Special case if htmlName was passed-in as an option 1554 if (typeof obj.htmlName != "undefined") 1555 { 1556 button.get("element").getElementsByTagName("button")[0].name = obj.htmlName; 1557 } 1558 } 1559 } 1560 return button; 1561 }; 1562 1563 /** 1564 * Wrapper to disable a YUI Button, including link buttons. 1565 * Link buttons aren't disabled by YUI; see http://developer.yahoo.com/yui/button/#apiref 1566 * 1567 * @method Alfresco.util.disableYUIButton 1568 * @param p_button {YAHOO.widget.Button} Button instance 1569 * @static 1570 */ 1571 Alfresco.util.disableYUIButton = function(p_button) 1572 { 1573 if (p_button.set && p_button.get) 1574 { 1575 p_button.set("disabled", true); 1576 if (p_button.get("type") == "link") 1577 { 1578 /** 1579 * Note the non-optimal use of a "private" variable, which is why it's tested before use. 1580 */ 1581 p_button.set("href", ""); 1582 if (p_button._button && p_button._button.setAttribute) 1583 { 1584 p_button._button.setAttribute("onclick", "return false;"); 1585 } 1586 p_button.addStateCSSClasses("disabled"); 1587 p_button.removeStateCSSClasses("hover"); 1588 p_button.removeStateCSSClasses("active"); 1589 p_button.removeStateCSSClasses("focus"); 1590 } 1591 } 1592 }; 1593 1594 /** 1595 * Wrapper to (re)enable a YUI Button, including link buttons. 1596 * Link buttons aren't disabled by YUI; see http://developer.yahoo.com/yui/button/#apiref 1597 * 1598 * @method Alfresco.util.enableYUIButton 1599 * @param p_button {YAHOO.widget.Button} Button instance 1600 * @static 1601 */ 1602 Alfresco.util.enableYUIButton = function(p_button) 1603 { 1604 if (p_button.set && p_button.get) 1605 { 1606 p_button.set("disabled", false); 1607 if (p_button.get("type") == "link") 1608 { 1609 /** 1610 * Note the non-optimal use of a "private" variable, which is why it's tested before use. 1611 */ 1612 if (p_button._button && p_button._button.removeAttribute) 1613 { 1614 p_button._button.removeAttribute("onclick"); 1615 } 1616 p_button.removeStateCSSClasses("disabled"); 1617 } 1618 } 1619 }; 1620 1621 /** 1622 * Creates a "disclosure twister" UI control from existing mark-up. 1623 * 1624 * @method Alfresco.util.createTwister 1625 * @param p_controller {Element|string} Element (or DOM ID) of controller node 1626 * <pre>The code will search for the next sibling which will be used as the hideable panel, unless overridden below</pre> 1627 * @param p_filterName {string} Filter's name under which to save it's collapsed state via preferences 1628 * @param p_config {object} Optional additional configuration to override the defaults 1629 * <pre> 1630 * panel {Element|string} Use this panel as the hideable element instead of the controller's first sibling 1631 * </pre> 1632 * @return {boolean} true = success 1633 */ 1634 Alfresco.util.createTwister = function(p_controller, p_filterName, p_config) 1635 { 1636 var defaultConfig = 1637 { 1638 panel: null, 1639 CLASS_BASE: "alfresco-twister", 1640 CLASS_OPEN: "alfresco-twister-open", 1641 CLASS_CLOSED: "alfresco-twister-closed" 1642 }; 1643 1644 var elController, elPanel, 1645 config = YAHOO.lang.merge(defaultConfig, p_config || {}); 1646 1647 // Controller element 1648 elController = YUIDom.get(p_controller); 1649 if (elController === null) 1650 { 1651 return false; 1652 } 1653 1654 // Panel element - next sibling or specified in configuration 1655 if (config.panel && YUIDom.get(config.panel)) 1656 { 1657 elPanel = YUIDom.get(config.panel); 1658 } 1659 else 1660 { 1661 // Find the first sibling node 1662 elPanel = elController.nextSibling; 1663 while (elPanel.nodeType !== 1 && elPanel !== null) 1664 { 1665 elPanel = elPanel.nextSibling; 1666 } 1667 } 1668 if (elPanel === null) 1669 { 1670 return false; 1671 } 1672 1673 // See if panel should be collapsed via value stored in preferences 1674 var collapsedPrefs = Alfresco.util.arrayToObject(Alfresco.util.createTwister.collapsed.split(",")), 1675 isCollapsed = !!collapsedPrefs[p_filterName]; 1676 1677 // Initial State 1678 YUIDom.addClass(elController, config.CLASS_BASE); 1679 YUIDom.addClass(elController, isCollapsed ? config.CLASS_CLOSED : config.CLASS_OPEN); 1680 YUIDom.setStyle(elPanel, "display", isCollapsed ? "none" : "block"); 1681 1682 YUIEvent.addListener(elController, "click", function(p_event, p_obj) 1683 { 1684 // Only expand/collapse if actual twister element is clicked (not for inner elements, i.e. twister actions) 1685 if (p_event.target == p_event.currentTarget) 1686 { 1687 // Update UI to new state 1688 var collapse = YUIDom.hasClass(p_obj.controller, config.CLASS_OPEN); 1689 if (collapse) 1690 { 1691 YUIDom.replaceClass(p_obj.controller, config.CLASS_OPEN, config.CLASS_CLOSED); 1692 } 1693 else 1694 { 1695 YUIDom.replaceClass(p_obj.controller, config.CLASS_CLOSED, config.CLASS_OPEN); 1696 } 1697 YUIDom.setStyle(p_obj.panel, "display", collapse ? "none" : "block"); 1698 1699 if (p_obj.filterName) 1700 { 1701 // Save to preferences 1702 var fnPref = collapse ? "add" : "remove", 1703 preferences = new Alfresco.service.Preferences(); 1704 preferences[fnPref].call(preferences, Alfresco.service.Preferences.COLLAPSED_TWISTERS, p_obj.filterName); 1705 } 1706 } 1707 }, 1708 { 1709 controller: elController, 1710 panel: elPanel, 1711 filterName: p_filterName 1712 }); 1713 }; 1714 Alfresco.util.createTwister.collapsed = ""; 1715 1716 /** 1717 * Wrapper to create a YUI Panel with common attributes, as follows: 1718 * <pre> 1719 * modal: true, 1720 * constraintoviewport: true, 1721 * draggable: true, 1722 * fixedcenter: true, 1723 * close: true, 1724 * visible: false 1725 * </pre> 1726 * All supplied object parameters are passed to the panel constructor 1727 * e.g. Alfresco.util.createYUIPanel("myId", { draggable: false }); 1728 * 1729 * @method Alfresco.util.createYUIPanel 1730 * @param p_el {string|HTMLElement} The element ID representing the Panel or the element representing the Panel 1731 * @param p_params {object} Optional extra/overridden object parameters to pass to Panel constructor 1732 * @param p_custom {object} Optional parameters to customise Panel creation: 1733 * <pre> 1734 * render {boolean} By default the new Panel will be rendered to document.body. Set to false to prevent this. 1735 * type {object} Use to override YAHOO.widget.Panel default type, e.g. YAHOO.widget.Dialog 1736 * </pre> 1737 * @return {YAHOO.widget.Dialog|flags.type} New Panel instance 1738 * @static 1739 */ 1740 Alfresco.util.createYUIPanel = function(p_el, p_params, p_custom) 1741 { 1742 // Default constructor parameters 1743 var panel, 1744 params = 1745 { 1746 modal: true, 1747 constraintoviewport: true, 1748 draggable: true, 1749 fixedcenter: YAHOO.env.ua.mobile === null ? "contained" : false, 1750 close: true, 1751 visible: false, 1752 postmethod: "none" 1753 }, 1754 custom = 1755 { 1756 render: true, 1757 type: YAHOO.widget.Dialog 1758 }; 1759 1760 // Any extra/overridden constructor parameters? 1761 if (typeof p_params == "object") 1762 { 1763 params = YAHOO.lang.merge(params, p_params); 1764 } 1765 // Any customisation? 1766 if (typeof p_custom == "object") 1767 { 1768 custom = YAHOO.lang.merge(custom, p_custom); 1769 } 1770 1771 // Create and return the panel 1772 panel = new (custom.type)(p_el, params); 1773 1774 if (custom.render) 1775 { 1776 panel.render(document.body); 1777 } 1778 1779 // Let other components react to when a panel is shown or hidden 1780 panel.subscribe("show", function (p_event, p_args) 1781 { 1782 if (!params.fixedcenter) 1783 { 1784 panel.center(); 1785 } 1786 YAHOO.Bubbling.fire("showPanel", 1787 { 1788 panel: this 1789 }); 1790 }); 1791 panel.subscribe("hide", function (p_event, p_args) 1792 { 1793 YAHOO.Bubbling.fire("hidePanel", 1794 { 1795 panel: this 1796 }); 1797 }); 1798 1799 return panel; 1800 }; 1801 1802 /** 1803 * Creates a "balloon tooltip" UI control attached to a passed-in element. 1804 * 1805 * @method Alfresco.util.createBalloon 1806 * @param p_context {Element|string} Element (or DOM ID) to align the balloon to 1807 * @param p_params {object} Optional additional configuration to override the defaults 1808 * <pre> 1809 * width {string} CSS width of the tooltip. Defaults to 30em 1810 * </pre> 1811 * @return {object|null} Balloon instance 1812 */ 1813 Alfresco.util.createBalloon = function(p_context, p_params) 1814 { 1815 var elContext = YUIDom.get(p_context); 1816 if (YAHOO.lang.isNull(elContext)) 1817 { 1818 return null; 1819 } 1820 1821 p_params = YAHOO.lang.merge( 1822 { 1823 effectType: YAHOO.widget.ContainerEffect.FADE, 1824 effectDuration: 0.25, 1825 html: "", 1826 text: "", 1827 closeButton: true, 1828 width: "30em" 1829 }, p_params || {}); 1830 1831 return (new Alfresco.widget.Balloon(elContext, p_params)); 1832 }; 1833 1834 (function() 1835 { 1836 /** 1837 * YUI Library aliases 1838 */ 1839 var Dom = YAHOO.util.Dom, 1840 Event = YAHOO.util.Event; 1841 1842 /** 1843 * Alfresco library aliases 1844 */ 1845 var $html = Alfresco.util.encodeHTML, 1846 PREVENT_SCROLLBAR_FIX = (YAHOO.widget.Module.prototype.platform === "mac" && 0 < YAHOO.env.ua.gecko && YAHOO.env.ua.gecko < 2); 1847 1848 /** 1849 * Alfresco.widget.Balloon constructor. 1850 * Should not be created directly, but via the Alfresco.util.createBalloon static function. 1851 * 1852 * @param p_context {Element|string} Element (or DOM ID) to align the balloon to 1853 * @param p_params {object} Optional additional configuration to override the defaults 1854 * @return {Alfresco.widget.Balloon} The new Balloon instance 1855 * @constructor 1856 */ 1857 Alfresco.widget.Balloon = function(p_context, p_params) 1858 { 1859 var balloon = new YAHOO.widget.Overlay(Alfresco.util.generateDomId(), 1860 { 1861 context: 1862 [ 1863 p_context, 1864 "bl", 1865 "tl", 1866 ["beforeShow", "windowResize"] 1867 ], 1868 constraintoviewport: true, 1869 visible: false, 1870 width: p_params.width || "30em", 1871 effect: 1872 { 1873 effect: p_params.effectType, 1874 duration: p_params.effectDuration 1875 } 1876 }); 1877 1878 if (PREVENT_SCROLLBAR_FIX) 1879 { 1880 // Prevent Mac Firefox 3.x scrollbar bugfix from being applied by YUI 1881 balloon.hideEvent.unsubscribe(balloon.hideMacGeckoScrollbars, balloon, true); 1882 balloon.showEvent.unsubscribe(balloon.showMacGeckoScrollbars, balloon, true); 1883 balloon.hideMacGeckoScrollbars = function (){ Dom.replaceClass(this.element, "prevent-scrollbars", "hide-scrollbars"); }, 1884 balloon.showMacGeckoScrollbars = function(){ Dom.replaceClass(this.element, "hide-scrollbars", "prevent-scrollbars"); }; 1885 } 1886 1887 var wrapper = document.createElement("div"), 1888 arrow = document.createElement("div"); 1889 1890 Dom.addClass(wrapper, "balloon"); 1891 Dom.addClass(arrow, "balloon-arrow"); 1892 1893 if (p_params.closeButton) 1894 { 1895 var closeButton = document.createElement("div"); 1896 closeButton.innerHTML = "X"; 1897 Dom.addClass(closeButton, "closeButton"); 1898 Event.addListener(closeButton, "click", this.hide, this, true); 1899 wrapper.appendChild(closeButton); 1900 } 1901 1902 var content = document.createElement("div"); 1903 Dom.addClass(content, "text"); 1904 content.innerHTML = p_params.html || $html(p_params.text); 1905 wrapper.appendChild(content); 1906 wrapper.appendChild(arrow); 1907 1908 balloon.setBody(wrapper); 1909 balloon.render(Dom.get("doc3")); 1910 1911 this.balloon = balloon; 1912 this.content = content; 1913 1914 this.onClose = new YAHOO.util.CustomEvent("close" , this); 1915 this.onShow = new YAHOO.util.CustomEvent("show" , this); 1916 return this; 1917 }; 1918 1919 Alfresco.widget.Balloon.prototype = 1920 { 1921 /** 1922 * YAHOO.widget.Overlay instance 1923 * 1924 * @property balloon 1925 */ 1926 balloon: null, 1927 1928 /** 1929 * Element containing balloon's content 1930 * 1931 * @property content 1932 */ 1933 content: null, 1934 1935 /** 1936 * Hides the balloon 1937 * 1938 * @method hide 1939 */ 1940 hide: function Balloon_hide() 1941 { 1942 this.balloon.hide(); 1943 this.onClose.fire(); 1944 }, 1945 1946 /** 1947 * Shows the balloon 1948 * 1949 * @method show 1950 */ 1951 show: function Balloon_show() 1952 { 1953 this.balloon.show(); 1954 this.balloon.bringToTop(); 1955 this.onShow.fire(); 1956 }, 1957 1958 /** 1959 * Sets the HTML content of the balloon. 1960 * 1961 * @method html 1962 * @param content {String} Contents will be inserted as-is with no escaping. 1963 */ 1964 html: function Balloon_html(content) 1965 { 1966 this.content.innerHTML = content; 1967 this.balloon.align(); 1968 }, 1969 1970 /** 1971 * Sets the text content of the balloon. 1972 * 1973 * @method text 1974 * @param content {String} Contents will be inserted after being safely HTML-encoded. 1975 */ 1976 text: function Balloon_text(content) 1977 { 1978 this.content.innerHTML = $html(content); 1979 this.balloon.align(); 1980 } 1981 }; 1982 })(); 1983 1984 /** 1985 * Create Insitu Editor 1986 * 1987 * @method Alfresco.util.createInsituEditor 1988 * @param p_context {HTMLElement} DOM node controlling editor visibility 1989 * @param p_params {Object} Instance configuration parameters 1990 * @param p_callback {Object} Callback function after successful edit operation 1991 * @return {Object|null} New instance of an insitu editor 1992 * @static 1993 */ 1994 Alfresco.util.createInsituEditor = function(p_context, p_params, p_callback) 1995 { 1996 var elContext = YUIDom.get(p_context); 1997 if (YAHOO.lang.isNull(elContext)) 1998 { 1999 return false; 2000 } 2001 2002 p_params = YAHOO.lang.merge( 2003 { 2004 showDelay: 600, 2005 hideDelay: 600, 2006 autoDismissDelay: 0, 2007 container: null, 2008 context: elContext, 2009 callback: p_callback, 2010 error: null, 2011 disabled: false, 2012 type: "textBox", 2013 nodeRef: null, 2014 name: null, 2015 value: "", 2016 title: null 2017 }, p_params || {}); 2018 2019 if (Alfresco.widget.InsituEditor[p_params.type]) 2020 { 2021 return (new Alfresco.widget.InsituEditor[p_params.type](p_params)); 2022 } 2023 2024 return null; 2025 }; 2026 2027 (function() 2028 { 2029 /** 2030 * YUI Library aliases 2031 */ 2032 var Dom = YAHOO.util.Dom, 2033 Event = YAHOO.util.Event, 2034 Element = YAHOO.util.Element, 2035 KeyListener = YAHOO.util.KeyListener; 2036 2037 Alfresco.widget.InsituEditorIcon = function(p_editor, p_params) 2038 { 2039 this.editor = p_editor; 2040 this.params = YAHOO.lang.merge({}, p_params); 2041 this.disabled = p_params.disabled; 2042 2043 this.editIcon = document.createElement("span"); 2044 this.editIcon.title = Alfresco.util.encodeHTML(p_params.title); 2045 Dom.addClass(this.editIcon, "insitu-edit"); 2046 2047 if (this.params.container !== null) 2048 { 2049 Dom.get(this.params.container).appendChild(this.editIcon); 2050 } 2051 else 2052 { 2053 this.params.context.parentNode.insertBefore(this.editIcon, this.params.context); 2054 } 2055 2056 Event.on(this.params.context, "mouseover", this.onContextMouseOver, this); 2057 Event.on(this.params.context, "mouseout", this.onContextMouseOut, this); 2058 Event.on(this.editIcon, "mouseover", this.onContextMouseOver, this); 2059 Event.on(this.editIcon, "mouseout", this.onContextMouseOut, this); 2060 }; 2061 2062 Alfresco.widget.InsituEditorIcon.prototype = 2063 { 2064 /** 2065 * Flag to disable the editor 2066 * 2067 * @property disabled 2068 */ 2069 disabled: null, 2070 2071 /** 2072 * Fade the editor icon out 2073 * 2074 * @method _fadeOut 2075 * @param p_element {HTMLElement} The icon element 2076 * @protected 2077 */ 2078 _fadeOut: function InsituEditorIcon__fadeOut(p_element) 2079 { 2080 var fade = new YAHOO.util.Anim(p_element, 2081 { 2082 opacity: 2083 { 2084 to: 0 2085 } 2086 }, 0.2); 2087 2088 fade.onComplete.subscribe(function(e, data, obj) 2089 { 2090 Event.removeListener(obj.editIcon, "click"); 2091 Dom.setStyle(p_element, "visibility", "hidden"); 2092 Dom.setStyle(p_element, "opacity", 0); 2093 }, this); 2094 2095 fade.animate(); 2096 }, 2097 2098 /** 2099 * Fade the editor icon in 2100 * 2101 * @method _fadeIn 2102 * @param p_element {HTMLElement} The icon element 2103 * @protected 2104 */ 2105 _fadeIn: function InsituEditorIcon__fadeIn(p_element) 2106 { 2107 var fade = new YAHOO.util.Anim(p_element, 2108 { 2109 opacity: 2110 { 2111 to: 1 2112 } 2113 }, 0.2); 2114 2115 fade.onComplete.subscribe(function(e, data, obj) 2116 { 2117 Dom.setStyle(p_element, "opacity", 1); 2118 Event.removeListener(obj.editIcon, "click"); 2119 Event.on(obj.editIcon, "click", obj.onIconClick, obj); 2120 }, this); 2121 2122 Dom.setStyle(p_element, "visibility", "visible"); 2123 fade.animate(); 2124 }, 2125 2126 /** 2127 * The default event handler fired when the user mouses over the context element. 2128 * 2129 * @method onContextMouseOver 2130 * @param {DOMEvent} e The current DOM event 2131 * @param {Object} obj The object argument 2132 */ 2133 onContextMouseOver: function InsituEditorIcon_onContextMouseOver(e, obj) 2134 { 2135 if (obj.disabled) 2136 { 2137 return; 2138 } 2139 2140 // Stop the icon from being hidden (set on last mouseout) 2141 if (obj.hideProcId) 2142 { 2143 clearTimeout(obj.hideProcId); 2144 obj.hideProcId = null; 2145 } 2146 2147 obj.showProcId = obj.doIconShow(e, this); 2148 }, 2149 2150 /** 2151 * The default event handler fired when the user mouses out of the context element. 2152 * 2153 * @method onContextMouseOut 2154 * @param {DOMEvent} e The current DOM event 2155 * @param {Object} obj The object argument 2156 */ 2157 onContextMouseOut: function InsituEditorIcon_onContextMouseOut(e, obj) 2158 { 2159 if (obj.disabled) 2160 { 2161 return; 2162 } 2163 2164 if (obj.showProcId) 2165 { 2166 clearTimeout(obj.showProcId); 2167 obj.showProcId = null; 2168 } 2169 2170 if (obj.hideProcId) 2171 { 2172 clearTimeout(obj.hideProcId); 2173 obj.hideProcId = null; 2174 } 2175 2176 obj.hideProcId = setTimeout(function() 2177 { 2178 obj._fadeOut(obj.editIcon); 2179 }, obj.params.hideDelay); 2180 }, 2181 2182 /** 2183 * The default event handler fired when the user clicks the icon element. 2184 * 2185 * @method onIconClick 2186 * @param {DOMEvent} e The current DOM event 2187 * @param {Object} obj The object argument 2188 */ 2189 onIconClick: function InsituEditorIcon_onIconClick(e, obj) 2190 { 2191 if (obj.disabled) 2192 { 2193 return; 2194 } 2195 2196 Alfresco.logger.debug("onIconClick", e); 2197 obj.editor.doShow(); 2198 Event.stopEvent(e); 2199 }, 2200 2201 /** 2202 * Processes the showing of the edit icon 2203 * 2204 * @method doIconShow 2205 * @param {DOMEvent} e The current DOM event 2206 * @param {HTMLElement} context The current context element 2207 * @return {Number} The process ID of the timeout function associated with doIconShow 2208 */ 2209 doIconShow: function InsituEditorIcon_doIconShow(e, context) 2210 { 2211 var me = this; 2212 2213 return window.setTimeout(function() 2214 { 2215 me._fadeIn(me.editIcon); 2216 me.hideProcId = me.doHide(); 2217 }, me.params.showDelay); 2218 }, 2219 2220 /** 2221 * Sets the timeout for the auto-dismiss delay 2222 * 2223 * @method doHide 2224 */ 2225 doHide: function InsituEditorIcon_doHide() 2226 { 2227 if (this.params.autoDismissDelay < 1) 2228 { 2229 return null; 2230 } 2231 2232 var me = this; 2233 2234 return window.setTimeout(function() 2235 { 2236 me._fadeOut(me.editIcon); 2237 }, me.params.autoDismissDelay); 2238 } 2239 }; 2240 2241 /** 2242 * Insitu Editor components. 2243 * 2244 * @namespace Alfresco.widget 2245 * @class Alfresco.widget.InsituEditor 2246 */ 2247 Alfresco.widget.InsituEditor = {}; 2248 2249 /** 2250 * Insitu Editor base class, from which editor implementations should be extended. 2251 * 2252 * @namespace Alfresco.widget.InsituEditor 2253 * @class Alfresco.widget.InsituEditor.base 2254 */ 2255 Alfresco.widget.InsituEditor.base = function(p_params) 2256 { 2257 this.params = YAHOO.lang.merge({}, p_params); 2258 2259 var nodeRef = new Alfresco.util.NodeRef(this.params.nodeRef), 2260 elEditForm = new Element(document.createElement("form"), 2261 { 2262 id: Alfresco.util.generateDomId(), 2263 method: "post", 2264 action: Alfresco.constants.PROXY_URI + "api/node/" + nodeRef.uri + "/formprocessor" 2265 }); 2266 2267 this.elEditForm = elEditForm; 2268 this.editForm = elEditForm.get("element"); 2269 2270 // Form definition 2271 this.form = new Alfresco.forms.Form(this.editForm); 2272 this.form.setAJAXSubmit(true, 2273 { 2274 successCallback: 2275 { 2276 fn: this.onPersistSuccess, 2277 scope: this 2278 }, 2279 failureCallback: 2280 { 2281 fn: this.onPersistFailure, 2282 scope: this 2283 } 2284 }); 2285 this.form.setSubmitAsJSON(true); 2286 2287 elEditForm.addClass("insitu-edit"); 2288 elEditForm.on("submit", function(e) 2289 { 2290 Event.stopEvent(e); 2291 }); 2292 2293 // Create editor icon instance 2294 this.editorIcon = new Alfresco.widget.InsituEditorIcon(this, p_params); 2295 2296 this.params.context.parentNode.insertBefore(this.editForm, this.params.context); 2297 2298 return this; 2299 }; 2300 2301 Alfresco.widget.InsituEditor.base.prototype = 2302 { 2303 /** 2304 * Configuration parameters. 2305 * 2306 * @property params 2307 * @type object 2308 */ 2309 params: null, 2310 2311 /** 2312 * YAHOO.util.Element representing <form> node. 2313 * 2314 * @property elEditForm 2315 * @type YAHOO.util.Element 2316 */ 2317 elEditForm: null, 2318 2319 /** 2320 * <form> DOM element. 2321 * 2322 * @property elEditForm 2323 * @type HTMLElement 2324 */ 2325 editForm: null, 2326 2327 /** 2328 * Forms Runtime instance. 2329 * 2330 * @property form 2331 * @type Alfresco.forms.Form 2332 */ 2333 form: null, 2334 2335 /** 2336 * Generic helper method for invoking a Alfresco.util.Ajax.request() from a responseConfig object 2337 * 2338 * @method _jsonCall 2339 * @param method {string} The method for the XMLHttpRequest 2340 * @param url {string} The url for the XMLHttpRequest 2341 * @param dataObj {object} An object that will be transformed to a json string and put in the request body 2342 * @param responseConfig.successCallback {object} A success callback object 2343 * @param responseConfig.successMessage {string} A success message 2344 * @param responseConfig.failureCallback {object} A failure callback object 2345 * @param responseConfig.failureMessage {string} A failure message 2346 * @private 2347 */ 2348 _jsonCall: function InsituEditor_base__jsonCall(method, url, dataObj, responseConfig) 2349 { 2350 responseConfig = responseConfig || {}; 2351 Alfresco.util.Ajax.jsonRequest( 2352 { 2353 method: method, 2354 url: url, 2355 dataObj: dataObj, 2356 successCallback: responseConfig.successCallback, 2357 successMessage: responseConfig.successMessage, 2358 failureCallback: responseConfig.failureCallback, 2359 failureMessage: responseConfig.failureMessage, 2360 noReloadOnAuthFailure: responseConfig.noReloadOnAuthFailure || false 2361 }); 2362 }, 2363 2364 /** 2365 * Abstract function 2366 * 2367 * @method doShow 2368 */ 2369 doShow: function InsituEditor_base_doShow() 2370 { 2371 Alfresco.logger.debug("Alfresco.widget.InsituEditor", "Abstract implementation 'doShow()' not overridden"); 2372 }, 2373 2374 /** 2375 * Abstract function 2376 * 2377 * @method doHide 2378 * @param restoreUI {boolean} Whether to restore the UI or rely on the caller to do it 2379 */ 2380 doHide: function InsituEditor_base_doHide(restoreUI) 2381 { 2382 Alfresco.logger.debug("Alfresco.widget.InsituEditor", "Abstract implementation 'doHide()' not overridden"); 2383 }, 2384 2385 /** 2386 * Successful property persistence handler 2387 * 2388 * @method onPersistSuccess 2389 * @param response {Object} Server response object literal 2390 */ 2391 onPersistSuccess: function InsituEditor_base_onPersistSuccess(response) 2392 { 2393 var restoreUI = true; 2394 if (this.params.callback.fn) 2395 { 2396 restoreUI = this.params.callback.fn.call(this.params.callback.scope || this, response, this.params.callback.obj); 2397 } 2398 this.doHide(restoreUI); 2399 }, 2400 2401 /** 2402 * Failure property persistence handler 2403 * 2404 * @method onPersistFailure 2405 * @param response {Object} Server response object literal 2406 */ 2407 onPersistFailure: function InsituEditor_base_onPersistFailure(response) 2408 { 2409 // Allow the callee to handle the error 2410 if (this.params.error && this.params.error.fn) 2411 { 2412 this.params.error.fn.call(this.params.error.scope || this, response, this.params.error.obj); 2413 } 2414 } 2415 }; 2416 2417 /** 2418 * Alfresco.widget.InsituEditor.textBox constructor. 2419 * 2420 * @param p_params {Object} Instance configuration parameters 2421 * @return {Alfresco.widget.InsituEditor.textBox} The new textBox editor instance 2422 * @constructor 2423 */ 2424 Alfresco.widget.InsituEditor.textBox = function(p_params) 2425 { 2426 Alfresco.widget.InsituEditor.textBox.superclass.constructor.call(this, p_params); 2427 2428 this.balloon = null; 2429 this.suppressInputBoxFocus = false; 2430 this.contextStyle = null; 2431 this.keyListener = null; 2432 this.markupGenerated = false; 2433 2434 return this; 2435 }; 2436 2437 /* 2438 * Alfresco.widget.InsituEditor.textBox 2439 * 2440 * <form> 2441 * <input type="text" value="digital photograph record.jpg"> 2442 * <a href="#" style="font-size: 13px; margin-left: 0.5em; padding-top: 0px; padding-right: 0.5em; padding-bottom: 0px; padding-left: 0.5em; ">Save</a> 2443 * <a href="#" style="font-size: 13px; padding-top: 0px; padding-right: 0.5em; padding-bottom: 0px; padding-left: 0.5em; ">Cancel</a> 2444 * </form> 2445 */ 2446 YAHOO.extend(Alfresco.widget.InsituEditor.textBox, Alfresco.widget.InsituEditor.base, 2447 { 2448 /** 2449 * Balloon UI instance used for error reporting 2450 * 2451 * @property balloon 2452 * @type object 2453 */ 2454 balloon: null, 2455 2456 /** 2457 * Flag to prevent setting focus on the input text box 2458 * 2459 * @property suppressInputBoxFocus 2460 * @type boolean 2461 */ 2462 suppressInputBoxFocus: null, 2463 2464 /** 2465 * Save context elements style CSS property so it can be restored correctly 2466 * 2467 * @property contextStyle 2468 * @type string 2469 */ 2470 contextStyle: null, 2471 2472 /** 2473 * Key Listener for [Escape] to cancel 2474 * 2475 * @property keyListener 2476 * @type YAHOO.util.KeyListener 2477 */ 2478 keyListener: null, 2479 2480 /** 2481 * Flag tracking whether markup has been generated 2482 * 2483 * @property markupGenerated 2484 * @type boolean 2485 */ 2486 markupGenerated: null, 2487 2488 /** 2489 * Show the editor 2490 * 2491 * @method doShow 2492 * @override 2493 */ 2494 doShow: function InsituEditor_textBox_doShow() 2495 { 2496 this._generateMarkup(); 2497 if (this.contextStyle === null) 2498 { 2499 this.contextStyle = Dom.getStyle(this.params.context, "display"); 2500 } 2501 Dom.setStyle(this.params.context, "display", "none"); 2502 Dom.setStyle(this.editForm, "display", "inline"); 2503 this.keyListener.enable(); 2504 if (typeof this.params.fnSelect === "function") 2505 { 2506 this.params.fnSelect(this.inputBox, this.params.value); 2507 } 2508 else 2509 { 2510 this.inputBox.select(); 2511 } 2512 }, 2513 2514 /** 2515 * Hide the editor 2516 * 2517 * @method doHide 2518 * @param restoreUI {boolean} Whether to restore the UI or rely on the caller to do it 2519 * @override 2520 */ 2521 doHide: function InsituEditor_textBox_doHide(restoreUI) 2522 { 2523 this.balloon.hide(); 2524 this.keyListener.disable(); 2525 if (restoreUI) 2526 { 2527 Dom.setStyle(this.editForm, "display", "none"); 2528 Dom.setStyle(this.params.context, "display", this.contextStyle); 2529 } 2530 }, 2531 2532 /** 2533 * Failure property persistence handler 2534 * 2535 * @override 2536 * @method onPersistFailure 2537 * @param response {Object} Server response object literal 2538 */ 2539 onPersistFailure: function InsituEditor_textBox_onPersistFailure(response) 2540 { 2541 Alfresco.widget.InsituEditor.textBox.superclass.onPersistFailure.call(this, response); 2542 this.balloon.text(this.params.errorMessage); 2543 this.balloon.show(); 2544 }, 2545 2546 /** 2547 * Generate mark-up 2548 * 2549 * @method _generateMarkup 2550 * @protected 2551 */ 2552 _generateMarkup: function InsituEditor_textBox__generateMarkup() 2553 { 2554 if (this.markupGenerated) 2555 { 2556 return; 2557 } 2558 2559 // Generate input box markup 2560 var eInput = new Element(document.createElement("input"), 2561 { 2562 type: "text", 2563 name: this.params.name, 2564 value: this.params.value 2565 }), 2566 eSave = new Element(document.createElement("a"), 2567 { 2568 href: "#", 2569 innerHTML: Alfresco.util.message("button.save") 2570 }), 2571 eCancel = new Element(document.createElement("a"), 2572 { 2573 href: "#", 2574 innerHTML: Alfresco.util.message("button.cancel") 2575 }); 2576 2577 this.elEditForm.appendChild(eInput); 2578 this.elEditForm.appendChild(eSave); 2579 this.elEditForm.appendChild(eCancel); 2580 2581 eInput.on("blur", function(e) 2582 { 2583 if (this.balloon) 2584 { 2585 this.suppressInputBoxFocus = true; 2586 this.balloon.hide(); 2587 } 2588 this.suppressInputBoxFocus = false; 2589 }, this, true); 2590 2591 eSave.on("click", function(e) 2592 { 2593 this.form._submitInvoked(e); 2594 }, this, true); 2595 2596 eCancel.on("click", function(e) 2597 { 2598 Event.stopEvent(e); 2599 this.inputBox.value = this.params.value; 2600 this.doHide(true); 2601 }, this, true); 2602 2603 this.inputBox = eInput.get("element"); 2604 2605 // Key Listener for [Escape] to cancel 2606 this.keyListener = new KeyListener(this.inputBox, 2607 { 2608 keys: [KeyListener.KEY.ESCAPE] 2609 }, 2610 { 2611 fn: function(id, keyEvent) 2612 { 2613 Event.stopEvent(keyEvent[1]); 2614 this.inputBox.value = this.params.value; 2615 this.doHide(true); 2616 }, 2617 scope: this, 2618 correctScope: true 2619 }); 2620 2621 // Balloon UI for errors 2622 this.balloon = Alfresco.util.createBalloon(this.inputBox); 2623 this.balloon.onClose.subscribe(function(e) 2624 { 2625 try 2626 { 2627 if (!this.suppressInputBoxFocus) 2628 { 2629 this.inputBox.focus(); 2630 } 2631 } 2632 catch (e) 2633 { 2634 } 2635 }, this, true); 2636 2637 // Register validation handlers 2638 var vals = this.params.validations; 2639 for (var i = 0, ii = vals.length; i < ii; i++) 2640 { 2641 this.form.addValidation(this.inputBox, vals[i].type, vals[i].args, vals[i].when, vals[i].message); 2642 } 2643 2644 // Override Forms Runtime's error handling 2645 var scope = this; 2646 this.form.addError = function InsituEditor_textBox_addError(msg, field) 2647 { 2648 scope.balloon.html(msg); 2649 scope.balloon.show(); 2650 }; 2651 2652 // Initialise the form 2653 this.form.init(); 2654 2655 this.markupGenerated = true; 2656 } 2657 }); 2658 2659 /** 2660 * Alfresco.widget.InsituEditor.tagEditor constructor. 2661 * 2662 * @param p_params {Object} Instance configuration parameters 2663 * @return {Alfresco.widget.InsituEditor.tagEditor} The new tagEditor instance 2664 * @constructor 2665 */ 2666 Alfresco.widget.InsituEditor.tagEditor = function(p_params) 2667 { 2668 Alfresco.widget.InsituEditor.tagEditor.superclass.constructor.call(this, p_params); 2669 2670 this.balloon = null; 2671 this.suppressInputBoxFocus = false; 2672 this.contextStyle = null; 2673 this.keyListener = null; 2674 this.markupGenerated = false; 2675 2676 return this; 2677 }; 2678 2679 /* 2680 * Alfresco.widget.InsituEditor.tagEditor 2681 */ 2682 YAHOO.extend(Alfresco.widget.InsituEditor.tagEditor, Alfresco.widget.InsituEditor.base, 2683 { 2684 /** 2685 * Balloon UI instance used for error reporting 2686 * 2687 * @property balloon 2688 * @type object 2689 */ 2690 balloon: null, 2691 2692 /** 2693 * Flag to prevent setting focus on the input text box 2694 * 2695 * @property suppressInputBoxFocus 2696 * @type boolean 2697 */ 2698 suppressInputBoxFocus: null, 2699 2700 /** 2701 * Save context elements style CSS property so it can be restored correctly 2702 * 2703 * @property contextStyle 2704 * @type string 2705 */ 2706 contextStyle: null, 2707 2708 /** 2709 * Key Listener for [Escape] to cancel 2710 * 2711 * @property keyListener 2712 * @type YAHOO.util.KeyListener 2713 */ 2714 keyListener: null, 2715 2716 /** 2717 * A list of the tag node references. This is the data that will be submitted 2718 * when the Insitu Editor is saved. 2719 * 2720 * @property tagRefs 2721 * @type array 2722 */ 2723 tagRefs: null, 2724 2725 /** 2726 * A hidden input element that contains the only data used by the form when submitted. 2727 * The value is updated from the contents of the tagRefs property. 2728 * 2729 * @property hiddenInput 2730 * @type HTMLElement 2731 */ 2732 hiddenInput: null, 2733 2734 /** 2735 * An input element used for typing in new tags. Also used as part of a YUI auto-complete 2736 * widget to allow selection from previously created tags. 2737 * 2738 * @property newTagInput 2739 * @type HTMLElement 2740 */ 2741 newTagInput: null, 2742 2743 /** 2744 * A span element that contains a span element for each tag already 2745 * applied to the document. 2746 * 2747 * @property currentTags 2748 * @type HTMLElement 2749 */ 2750 currentTags: null, 2751 2752 /** 2753 * Flag tracking whether markup has been generated 2754 * 2755 * @property markupGenerated 2756 * @type boolean 2757 */ 2758 markupGenerated: null, 2759 2760 /** 2761 * The YUI auto-complete widget used for entering new tags. 2762 * 2763 * @property tagAutoComplete 2764 * @type YAHOO.widget.AutoComplete 2765 */ 2766 tagAutoComplete: null, 2767 2768 /** 2769 * The index of the tag that is currently being edited. This will be 2770 * set to -1 if a tag isn't currently being edited. 2771 * 2772 * @property _editingTagIndex 2773 * @type int 2774 */ 2775 _editingTagIndex: -1, 2776 2777 /** 2778 * Indicates that a tag is primted for deletion. This occurs when the backspace key is used 2779 * when there are no characters entered in the newTagInput field. A further use of the backspace 2780 * key will result in the tag being deleted. 2781 * 2782 * @property _tagPrimedForDelete 2783 * @type boolean 2784 */ 2785 _tagPrimedForDelete: false, 2786 2787 /** 2788 * Show the editor 2789 * 2790 * @method doShow 2791 * @override 2792 */ 2793 doShow: function InsituEditor_tagEditor_doShow() 2794 { 2795 this._generateMarkup(); 2796 if (this.contextStyle === null) 2797 { 2798 this.contextStyle = Dom.getStyle(this.params.context, "display"); 2799 } 2800 Dom.setStyle(this.params.context, "display", "none"); 2801 Dom.setStyle(this.editForm, "display", "inline"); 2802 this.keyListener.enable(); 2803 this.inputBox.select(); 2804 }, 2805 2806 /** 2807 * Hide the editor 2808 * 2809 * @method doHide 2810 * @param restoreUI {boolean} Whether to restore the UI or rely on the caller to do it 2811 * @override 2812 */ 2813 doHide: function InsituEditor_tagEditor_doHide(restoreUI) 2814 { 2815 this.balloon.hide(); 2816 this.keyListener.disable(); 2817 if (restoreUI) 2818 { 2819 Dom.setStyle(this.editForm, "display", "none"); 2820 Dom.setStyle(this.params.context, "display", this.contextStyle); 2821 } 2822 }, 2823 2824 /** 2825 * Successful property persistence handler 2826 * 2827 * @method onPersistSuccess 2828 * @param response {Object} Server response object literal 2829 */ 2830 onPersistSuccess: function InsituEditor_base_onPersistSuccess(response) 2831 { 2832 var restoreUI = true; 2833 if (this.params.callback.fn) 2834 { 2835 restoreUI = this.params.callback.fn.call(this.params.callback.scope || this, response, this.params.callback.obj); 2836 } 2837 this.doHide(restoreUI); 2838 2839 // Fire an event to cause the tags to refresh. A short-timeout is used to compensate for requests coming back too 2840 // tag requests being completed before server side persistence is complete. Tests without this timeout have shown 2841 // that tag updates get missed more often than not. 2842 window.setTimeout(function() { YAHOO.Bubbling.fire("tagRefresh") }, 1000); 2843 }, 2844 2845 /** 2846 * Failure property persistence handler 2847 * 2848 * @override 2849 * @method onPersistFailure 2850 * @param response {Object} Server response object literal 2851 */ 2852 onPersistFailure: function InsituEditor_tagEditor_onPersistFailure(response) 2853 { 2854 Alfresco.widget.InsituEditor.textBox.superclass.onPersistFailure.call(this, response); 2855 this.balloon.text(this.params.errorMessage); 2856 this.balloon.show(); 2857 }, 2858 2859 /** 2860 * Adds a new span that represents an applied tag. This span contains an icon that can 2861 * be clicked on to remove the tag. 2862 * 2863 * @method _addTag 2864 * @param value The name of the tag 2865 * @param nodeRef The nodeRef of the tag 2866 */ 2867 _addTag: function InsituEditor_tagEditor__addTag(value, nodeRef) 2868 { 2869 tag = Alfresco.util.encodeHTML(value); 2870 var span = document.createElement("span"); 2871 YUIDom.addClass(span, "inlineTagEditTag"); 2872 var label = document.createElement("span"); 2873 label.innerHTML = tag; 2874 var removeIcon = document.createElement("img"), 2875 removeIconEdit = document.createElement("img"); 2876 YUIDom.setAttribute(removeIcon, "src", Alfresco.constants.URL_RESCONTEXT + "components/images/delete-tag-off.png"); 2877 YUIDom.setAttribute(removeIcon, "width", 16); 2878 YUIDom.setAttribute(removeIconEdit, "src", Alfresco.constants.URL_RESCONTEXT + "components/images/delete-tag-on.png"); 2879 YUIDom.setAttribute(removeIconEdit, "width", 16); 2880 YUIDom.addClass(removeIconEdit, "hidden"); 2881 span.appendChild(label); 2882 span.appendChild(removeIcon); 2883 span.appendChild(removeIconEdit); 2884 2885 // Make sure we add the tag in the right place... 2886 if (YUIDom.isAncestor(this.currentTags, this.newTagInput)) 2887 { 2888 // An existing tag has been edited, so insert it before the input tag... 2889 YUIDom.insertBefore(span, this.newTagInput.parentNode); 2890 } 2891 else 2892 { 2893 // Add the new tag at the end of the list... 2894 this.currentTags.appendChild(span); 2895 } 2896 this._editingTagIndex = -1; // If we've just added a tag then we're not editing one 2897 2898 2899 // Function for deterining the index of a tag... 2900 var findTagIndex = function InsituEditor_tagEditor_findTagIndex(tagSpan) 2901 { 2902 // Get the index of where the span ended up, needed to insert the nodeRef in the correct place... 2903 var spanIndex = 0, 2904 tmp = tagSpan; 2905 while((tmp = tmp.previousSibling) != null) 2906 { 2907 spanIndex++; 2908 } 2909 return spanIndex; 2910 } 2911 2912 var _this = this; 2913 2914 // Function for removing a nodeRef from the array of refs... 2915 var removeNodeRef = function InsituEditor_tagEditor_removeNodeRef(nodeRef) 2916 { 2917 var index = Alfresco.util.arrayIndex(_this.tagRefs, nodeRef); 2918 if (index != -1) 2919 { 2920 _this.tagRefs.splice(index, 1); 2921 } 2922 } 2923 2924 // Handler the user choosing to remove a tag... 2925 Event.addListener(removeIcon, "click", function(e) 2926 { 2927 removeNodeRef(nodeRef); 2928 YUIDom.setAttribute(_this.hiddenInput, "value", _this.tagRefs.toString()); 2929 _this.currentTags.removeChild(span); 2930 }); 2931 2932 // Handle the user choosing to edit a tag... 2933 Event.addListener(label, "click", function(e) 2934 { 2935 // When the tag label is clicked we need to make it editable. The new tag box needs to 2936 // replace the tag span and have it's value set to the tag being edited. 2937 YUIDom.insertBefore(_this.newTagInput.parentNode, span); 2938 _this.currentTags.removeChild(span); 2939 _this.newTagInput.value = value; 2940 removeNodeRef(nodeRef); 2941 _this._editingTagIndex = findTagIndex(span); // Set the index of the span being edited. 2942 }); 2943 2944 return findTagIndex(span); 2945 }, 2946 2947 /** 2948 * Applies a tag to the document being edited. This will add a new span to represent 2949 * the applied tag, update the the overall hidden input field that will be submitted 2950 * and reset the new tag input field. 2951 * 2952 * @method _applyTag 2953 * @param value The name of the tag 2954 * @param nodeRef The nodeRef of the tag 2955 */ 2956 _applyTag: function InsituEditor_tagEditor__applyTag(tagName, nodeRef) 2957 { 2958 var index = this._addTag(tagName, nodeRef); 2959 this.newTagInput.value = ""; 2960 2961 // Add the nodeRef of the tag into the hidden value field... 2962 this.tagRefs.splice(index, 0, nodeRef); 2963 YUIDom.setAttribute(this.hiddenInput, "value", this.tagRefs.toString()); 2964 2965 // Ensure that auto-complete drop-down is hidden... 2966 this.tagAutoComplete.collapseContainer(); 2967 }, 2968 2969 /** 2970 * Generates the markup that displays the currently applied tags. This function 2971 * can be called once the main markup has been created to allow only tag changes 2972 * to be rendered. 2973 * 2974 * @method _generateCurrentTagMarkup 2975 */ 2976 _generateCurrentTagMarkup: function InsituEditor_tagEditor__generateCurrentTagMarkup() 2977 { 2978 // Clear any previously created span tags... 2979 if (this.currentTags.hasChildNodes()) 2980 { 2981 while (this.currentTags.childNodes.length >= 1) 2982 { 2983 this.currentTags.removeChild( this.currentTags.firstChild ); 2984 } 2985 } 2986 2987 // Add any previously applied tags to the edit box, updating the array of applied tag nodeRefs as we go... 2988 if (this.params.value != undefined) 2989 { 2990 // Need to check that the value param has been set because if the node did not have 2991 // a cm:taggable option then it would be undefined. 2992 for (i = 0, j = this.params.value.length; i < j; i++) 2993 { 2994 this._addTag(this.params.value[i].name, this.params.value[i].nodeRef); 2995 this.tagRefs.push(this.params.value[i].nodeRef); 2996 } 2997 } 2998 }, 2999 3000 /** 3001 * Generate mark-up 3002 * 3003 * @method _generateMarkup 3004 * @protected 3005 */ 3006 _generateMarkup: function InsituEditor_tagEditor__generateMarkup() 3007 { 3008 // Reset the array of persisted tag nodeRefs... 3009 this.tagRefs = []; 3010 3011 if (this.markupGenerated) 3012 { 3013 this._generateCurrentTagMarkup(); 3014 return; 3015 } 3016 3017 var eAutoCompleteWrapper = document.createElement("span"), 3018 eAutoComplete = document.createElement("div"), 3019 eSave = new Element(document.createElement("a"), 3020 { 3021 href: "#", 3022 innerHTML: Alfresco.util.message("button.save") 3023 }), 3024 eCancel = new Element(document.createElement("a"), 3025 { 3026 href: "#", 3027 innerHTML: Alfresco.util.message("button.cancel") 3028 }); 3029 3030 // Create a hidden input field - the value of this field is what will be used to update the 3031 // cm:taggable property of the document when the "Save" button is clicked. 3032 this.hiddenInput = document.createElement("input"); 3033 YUIDom.setAttribute(this.hiddenInput, "type", "hidden"); 3034 YUIDom.setAttribute(this.hiddenInput, "name", this.params.name); 3035 3036 // Create a new input field for entering new tags (this will also allow the user to select tags from 3037 // an auto-complete list... 3038 this.newTagInput = document.createElement("input"); 3039 YUIDom.setAttribute(this.newTagInput, "type", "text"); 3040 3041 // Add the new tag input field and the auto-complete drop-down DIV element to the auto-complete wrapper 3042 eAutoCompleteWrapper.appendChild(this.newTagInput); 3043 eAutoCompleteWrapper.appendChild(eAutoComplete); 3044 3045 // Create a new edit box (this contains all tag spans, as well as the auto-complete enabled input field for 3046 // adding new tags)... 3047 var editBox = document.createElement("div"); 3048 YUIDom.addClass(editBox, "inlineTagEdit"); // This class should make the span look like a text input box 3049 this.currentTags = document.createElement("span"); 3050 editBox.appendChild(this.currentTags); 3051 editBox.appendChild(eAutoCompleteWrapper); // Add the auto-complete wrapper (this contains the input field for typing tags) 3052 3053 // Add any previously applied tags to the edit box, updating the array of applied tag nodeRefs as we go... 3054 this._generateCurrentTagMarkup(); 3055 3056 // Set the current tags in the hidden field... 3057 YUIDom.setAttribute(this.hiddenInput, "value", this.tagRefs.toString()); 3058 3059 // Add the main edit box to the form (all the tags go in this box) 3060 this.elEditForm.appendChild(editBox); 3061 3062 YUIDom.addClass(eAutoCompleteWrapper, "inlineTagEditAutoCompleteWrapper"); 3063 YUIDom.addClass(eAutoComplete, "inlineTagEditAutoComplete"); 3064 this.elEditForm.appendChild(this.hiddenInput); 3065 this.elEditForm.appendChild(eSave); 3066 this.elEditForm.appendChild(eCancel); 3067 3068 /* ************************************************************************************ 3069 * 3070 * This section of code deals with setting up the auto-complete widget for the new tag 3071 * input field. We need to set up a data source for retrieving the existing tags and 3072 * which we will need to filter on the client. 3073 * 3074 **************************************************************************************/ 3075 var oDS = new YAHOO.util.XHRDataSource(Alfresco.constants.PROXY_URI + "api/forms/picker/category/workspace/SpacesStore/tag:tag-root/children?selectableType=cm:category&searchTerm=&size=100&aspect=cm:taggable&"); 3076 oDS.responseType = YAHOO.util.XHRDataSource.TYPE_JSON; 3077 // This schema indicates where to find the tag name in the JSON response 3078 oDS.responseSchema = 3079 { 3080 resultsList : "data.items", 3081 fields : ["name", "nodeRef"] 3082 }; 3083 this.tagAutoComplete = new YAHOO.widget.AutoComplete(this.newTagInput, eAutoComplete, oDS); 3084 this.tagAutoComplete.questionMark = false; // Removes the question mark on the query string (this will be ignored anyway) 3085 this.tagAutoComplete.applyLocalFilter = true; // Filter the results on the client 3086 this.tagAutoComplete.queryDelay = 0.1 // Throttle requests sent 3087 this.tagAutoComplete.animSpeed = 0.08; 3088 this.tagAutoComplete.itemSelectEvent.subscribe(function(type, args) 3089 { 3090 // If the user clicks on an entry in the list then apply the selected tag 3091 var tagName = args[2][0], 3092 nodeRef = args[2][1]; 3093 this._applyTag(tagName, nodeRef); 3094 if (YUIDom.isAncestor(this.currentTags, this.newTagInput)) 3095 { 3096 // We must have just finished editing a tag, therefore we need to move 3097 // the auto-complete box out of the current tags... 3098 YUIDom.insertAfter(this.newTagInput.parentNode, this.currentTags); 3099 } 3100 }, this, true); 3101 // Update the result filter to remove any results that have already been used... 3102 this.tagAutoComplete.dataReturnEvent.subscribe(function(type, args) 3103 { 3104 var results = args[2]; 3105 for (i = 0, j = results.length; i < j; i++) 3106 { 3107 var currNodeRef = results[i].nodeRef; 3108 var index = Alfresco.util.arrayIndex(this.tagRefs, currNodeRef); 3109 if (index != -1) 3110 { 3111 results.splice(i, 1); // Remove the result because it's already been used 3112 i--; // Decrement the index because it's about to get incremented (this avoids skipping an entry) 3113 j--; // Decrement the target length, because the arrays got shorter 3114 } 3115 } 3116 }, this, true); 3117 3118 3119 /* ************************************************************************************** 3120 * 3121 * This section of code deals with handling enter keypresses in the new tag input field. 3122 * We need to capture ENTER keypresses and prevent the form being submitted, but instead 3123 * make a request to create the tag provided and then add it to the hidden variable that 3124 * will get submitted when the "Save" link is used. 3125 * 3126 ****************************************************************************************/ 3127 var _this = this; 3128 Event.addListener(this.newTagInput, "keypress", function(e) 3129 { 3130 if (e.keyCode == 13 && this.value.length > 0) 3131 { 3132 Event.stopEvent(e); // Prevent the surrounding form from being submitted 3133 _this._createTag(this.value, false); 3134 } 3135 }); 3136 3137 // This section of code handles deleting configured tags through the use of the backspacce key.... 3138 var _this = this; 3139 Event.addListener(this.newTagInput, "keydown", function(e) 3140 { 3141 if (e.keyCode == 8 && this.newTagInput.value.length == 0) 3142 { 3143 if (this._editingTagIndex != -1) 3144 { 3145 // If a tag is being edited then we just need to remove the tag and reset the input field 3146 this.tagRefs.splice(this._editingTagIndex, 1); // Remove the nodeRef, the tag span has already been removed 3147 YUIDom.insertAfter(this.newTagInput.parentNode, this.currentTags); // Return the auto-complete elements to their correct position 3148 } 3149 else if (!this._tagPrimedForDelete && this.currentTags.children.length > 0) 3150 { 3151 this._tagPrimedForDelete = true; 3152 var lastTag = YUIDom.getLastChild(this.currentTags); 3153 YUIDom.addClass(lastTag, "inlineTagEditTagPrimed"); 3154 YUIDom.addClass(lastTag.children[1], "hidden"); 3155 YUIDom.removeClass(lastTag.children[2], "hidden"); 3156 } 3157 else 3158 { 3159 // The backspace key was used when there are no more characters to delete 3160 // so we need to delete the last tag... 3161 if (this.tagRefs.length > 0) 3162 { 3163 this.tagRefs.pop(); 3164 YUIDom.setAttribute(this.hiddenInput, "value", this.tagRefs.toString()); 3165 this.currentTags.removeChild(YUIDom.getLastChild(this.currentTags)); 3166 } 3167 this._tagPrimedForDelete = false; // If we've deleted a tag then we're no longer primed for deletion... 3168 } 3169 } 3170 else if (this._tagPrimedForDelete == true) 3171 { 3172 // If any key other than backspace is pressed and the last tag has been primed for deletion 3173 // then we should put it back to the normal state... 3174 this._tagPrimedForDelete = false; 3175 if (this.currentTags.children.length > 0) 3176 { 3177 var lastTag = YUIDom.getLastChild(this.currentTags); 3178 YUIDom.removeClass(lastTag, "inlineTagEditTagPrimed"); 3179 YUIDom.addClass(lastTag.children[2], "hidden"); 3180 YUIDom.removeClass(lastTag.children[1], "hidden"); 3181 } 3182 } 3183 }, this, true); 3184 3185 Event.addListener(editBox, "click", function(e) 3186 { 3187 this.newTagInput.select(); 3188 }, this, true); 3189 3190 Event.addListener(this.newTagInput, "blur", function(e) 3191 { 3192 if (this.balloon) 3193 { 3194 this.suppressInputBoxFocus = true; 3195 this.balloon.hide(); 3196 } 3197 this.suppressInputBoxFocus = false; 3198 }, this, true); 3199 3200 eSave.on("click", function(e) 3201 { 3202 // Check to see if any characters need to be converted into a tag... 3203 if (this.newTagInput.value.length > 0) 3204 { 3205 this._createTag(this.newTagInput.value, true, e); 3206 } 3207 else 3208 { 3209 this.form._submitInvoked(e); 3210 } 3211 3212 }, this, true); 3213 3214 eCancel.on("click", function(e) 3215 { 3216 Event.stopEvent(e); 3217 this.inputBox.value = ""; 3218 this.doHide(true); 3219 }, this, true); 3220 3221 this.inputBox = this.newTagInput; 3222 3223 // Key Listener for [Escape] to cancel 3224 this.keyListener = new KeyListener(this.inputBox, 3225 { 3226 keys: [KeyListener.KEY.ESCAPE] 3227 }, 3228 { 3229 fn: function(id, keyEvent) 3230 { 3231 Event.stopEvent(keyEvent[1]); 3232 this.inputBox.value = ""; 3233 this.doHide(true); 3234 }, 3235 scope: this, 3236 correctScope: true 3237 }); 3238 3239 // Balloon UI for errors 3240 this.balloon = Alfresco.util.createBalloon(editBox); 3241 this.balloon.onClose.subscribe(function(e) 3242 { 3243 try 3244 { 3245 if (!this.suppressInputBoxFocus) 3246 { 3247 this.inputBox.focus(); 3248 } 3249 } 3250 catch (e) 3251 { 3252 } 3253 }, this, true); 3254 3255 // Register validation handlers 3256 var vals = this.params.validations; 3257 for (var i = 0, ii = vals.length; i < ii; i++) 3258 { 3259 this.form.addValidation(this.inputBox, vals[i].type, vals[i].args, vals[i].when, vals[i].message); 3260 } 3261 3262 // Override Forms Runtime's error handling 3263 var scope = this; 3264 this.form.addError = function InsituEditor_tagEditor_addError(msg, field) 3265 { 3266 scope.balloon.html(msg); 3267 scope.balloon.show(); 3268 }; 3269 3270 // Initialise the form 3271 this.form.init(); 3272 this.markupGenerated = true; 3273 }, 3274 3275 /** 3276 * Creates and applies a tag. If the tag already exists then it will just be re-used. 3277 * 3278 * @method _createTag 3279 * @param value The value of the tag to create/apply 3280 * @param submitOnSuccess Whether or not to submit the form on a success callback 3281 * @param eventToStop An event to stop if necessary (this will be completed by the form submit action) 3282 */ 3283 _createTag: function InsituEditor_tagEditor__createTag(value, submitOnSuccess, eventToStop) 3284 { 3285 var dataObj = { name : value }, 3286 successCallback = 3287 { 3288 fn: function(response) 3289 { 3290 // The tag was successfully created, add it before the new tag entry field 3291 // and reset the entry field... 3292 this._applyTag(value, response.json.nodeRef); 3293 if (YUIDom.isAncestor(this.currentTags, this)) 3294 { 3295 // We must have just finished editing a tag, therefore we need to move 3296 // the auto-complete box out of the current tags... 3297 YUIDom.insertAfter(this.parentNode, this.currentTags); 3298 } 3299 3300 if (submitOnSuccess) 3301 { 3302 this.form._submitInvoked(eventToStop); 3303 } 3304 3305 }, 3306 scope: this 3307 }, 3308 failureCallback = 3309 { 3310 fn: function(response) 3311 { 3312 // The tag was not created for some reason. There's no need to 3313 // do any additional action because the validation balloon will 3314 // still be shown. 3315 }, 3316 scope: this 3317 }; 3318 3319 // Post a request to create a new tag. This will succeed even if the tag already 3320 // exists, it will just give us a handy reference to the nodeRef for the tag 3321 Alfresco.util.Ajax.jsonRequest( 3322 { 3323 method: "POST", 3324 url: Alfresco.constants.PROXY_URI + "api/tag/workspace/SpacesStore", 3325 dataObj: dataObj, 3326 successCallback: successCallback, 3327 failureCallback: failureCallback 3328 }); 3329 } 3330 }); 3331 })(); 3332 3333 /** 3334 * Find an event target's class name, ignoring YUI classes. 3335 * 3336 * @method Alfresco.util.findEventClass 3337 * @param p_eventTarget {object} Event target from Event class 3338 * @param p_tagName {string} Optional tag if 'span' needs to be overridden 3339 * @return {string|null} Class name or null 3340 * @static 3341 */ 3342 Alfresco.util.findEventClass = function(p_eventTarget, p_tagName) 3343 { 3344 var src = p_eventTarget.element; 3345 var tagName = (p_tagName || "span").toLowerCase(); 3346 3347 // Walk down until specified tag found and not a yui class 3348 while ((src !== null) && ((src.tagName.toLowerCase() != tagName) || (src.className.indexOf("yui") === 0))) 3349 { 3350 src = src.firstChild; 3351 } 3352 3353 // Found the target element? 3354 if (src === null) 3355 { 3356 return null; 3357 } 3358 3359 return src.className; 3360 }; 3361 3362 /** 3363 * Determines whether a Bubbling event should be ignored or not 3364 * 3365 * @method Alfresco.util.hasEventInterest 3366 * @param p_instance {object} Instance checking for event interest 3367 * @param p_args {object} Bubbling event args 3368 * @return {boolean} false to ignore event 3369 */ 3370 Alfresco.util.hasEventInterest = function(p_eventGroup, p_args) 3371 { 3372 var obj = p_args[1], 3373 sourceGroup = "source", 3374 targetGroup = "target", 3375 hasInterest = false; 3376 3377 if (obj) 3378 { 3379 // Was this a defaultAction event? 3380 if (obj.action === "navigate") 3381 { 3382 obj.eventGroup = obj.anchor.rel; 3383 } 3384 3385 if (obj.eventGroup && p_eventGroup) 3386 { 3387 sourceGroup = (typeof obj.eventGroup == "string") ? obj.eventGroup : obj.eventGroup.eventGroup; 3388 targetGroup = (typeof p_eventGroup == "string") ? p_eventGroup : p_eventGroup.eventGroup; 3389 3390 hasInterest = (sourceGroup == targetGroup); 3391 } 3392 } 3393 return hasInterest; 3394 }; 3395 3396 /** 3397 * Check if flash is installed. 3398 * Returns true if a flash player of the required version is installed 3399 * 3400 * @method Alfresco.util.isFlashInstalled 3401 * @param reqMajorVer {int} 3402 * @param reqMinorVer {int} 3403 * @param reqRevision {int} 3404 * @return {boolean} Returns true if a flash player of the required version is installed 3405 * @static 3406 */ 3407 Alfresco.util.hasRequiredFlashPlayer = function(reqMajorVer, reqMinorVer, reqRevision) 3408 { 3409 if (typeof DetectFlashVer == "function") 3410 { 3411 return DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision); 3412 } 3413 return false; 3414 }; 3415 3416 /** 3417 * Add a component's messages to the central message store. 3418 * 3419 * @method Alfresco.util.addMessages 3420 * @param p_obj {object} Object literal containing messages in the correct locale 3421 * @param p_messageScope {string} Message scope to add these to, e.g. componentId 3422 * @return {boolean} true if messages added 3423 * @throws {Error} 3424 * @static 3425 */ 3426 Alfresco.util.addMessages = function(p_obj, p_messageScope) 3427 { 3428 if (p_messageScope === undefined) 3429 { 3430 throw new Error("messageScope must be defined"); 3431 } 3432 else if (p_messageScope == "global") 3433 { 3434 throw new Error("messageScope cannot be 'global'"); 3435 } 3436 else 3437 { 3438 Alfresco.messages.scope[p_messageScope] = YAHOO.lang.merge(Alfresco.messages.scope[p_messageScope] || {}, p_obj); 3439 return true; 3440 } 3441 // for completeness... 3442 return false; 3443 }; 3444 3445 /** 3446 * Copy one namespace's messages to another's. 3447 * 3448 * @method Alfresco.util.copyMessages 3449 * @param p_source {string} Source namespace 3450 * @param p_destination {string} Destination namespace. Will be overwritten with source's messages 3451 * @throws {Error} 3452 * @static 3453 */ 3454 Alfresco.util.copyMessages = function(p_source, p_destination) 3455 { 3456 if (p_source === undefined) 3457 { 3458 throw new Error("Source must be defined"); 3459 } 3460 else if (Alfresco.messages.scope[p_source] === undefined) 3461 { 3462 throw new Error("Source namespace doesn't exist"); 3463 } 3464 else if (p_destination === undefined) 3465 { 3466 throw new Error("Destination must be defined"); 3467 } 3468 else if (p_destination == "global") 3469 { 3470 throw new Error("Destination cannot be 'global'"); 3471 } 3472 else 3473 { 3474 Alfresco.messages.scope[p_destination] = YAHOO.lang.merge({}, Alfresco.messages.scope[p_source]); 3475 } 3476 }; 3477 3478 /** 3479 * Resolve a messageId into a message. 3480 * If a messageScope is supplied, that container will be searched first, followed by the "global" message scope. 3481 * Note: Implementation follows single-quote quirks of server implementations whereby I18N messages containing 3482 * one or more tokens must use two single-quotes in order to display one. 3483 * See: http://download.oracle.com/javase/1.5.0/docs/api/java/text/MessageFormat.html 3484 * 3485 * @method Alfresco.util.message 3486 * @param p_messageId {string} Message id to resolve 3487 * @param p_messageScope {string} Message scope, e.g. componentId 3488 * @param multiple-values {string} Values to replace tokens with 3489 * @return {string} The localized message string or the messageId if not found 3490 * @throws {Error} 3491 * @static 3492 */ 3493 Alfresco.util.message = function(p_messageId, p_messageScope) 3494 { 3495 var msg = p_messageId; 3496 3497 if (typeof p_messageId != "string") 3498 { 3499 throw new Error("Missing or invalid argument: messageId"); 3500 } 3501 3502 var globalMsg = Alfresco.messages.global[p_messageId]; 3503 if (typeof globalMsg == "string") 3504 { 3505 msg = globalMsg; 3506 } 3507 3508 if ((typeof p_messageScope == "string") && (typeof Alfresco.messages.scope[p_messageScope] == "object")) 3509 { 3510 var scopeMsg = Alfresco.messages.scope[p_messageScope][p_messageId]; 3511 if (typeof scopeMsg == "string") 3512 { 3513 msg = scopeMsg; 3514 } 3515 } 3516 3517 // Search/replace tokens 3518 var tokens = []; 3519 if ((arguments.length == 3) && (typeof arguments[2] == "object")) 3520 { 3521 tokens = arguments[2]; 3522 } 3523 else 3524 { 3525 tokens = Array.prototype.slice.call(arguments).slice(2); 3526 } 3527 3528 // Emulate server-side I18NUtils implementation 3529 if (YAHOO.lang.isArray(tokens) && tokens.length > 0) 3530 { 3531 msg = msg.replace(/''/g, "'"); 3532 } 3533 msg = YAHOO.lang.substitute(msg, tokens); 3534 3535 return msg; 3536 }; 3537 3538 /** 3539 * Helper method to set the required i18n properties on a YUI Calendar Widget 3540 * see: http://developer.yahoo.com/yui/docs/YAHOO.widget.Calendar.html#config_MY_LABEL_MONTH_POSITION 3541 * for what each property does 3542 * 3543 * @method Alfresco.util.calI18nParams 3544 * @param oCal {object} a YAHOO.widget.Calendar object 3545 * @static 3546 */ 3547 Alfresco.util.calI18nParams = function(oCal) 3548 { 3549 var $msg = Alfresco.util.message; 3550 3551 oCal.cfg.setProperty("MONTHS_SHORT", $msg("months.short").split(",")); 3552 oCal.cfg.setProperty("MONTHS_LONG", $msg("months.long").split(",")); 3553 oCal.cfg.setProperty("WEEKDAYS_1CHAR", $msg("days.initial").split(",")); 3554 oCal.cfg.setProperty("WEEKDAYS_SHORT", $msg("days.short").split(",")); 3555 oCal.cfg.setProperty("WEEKDAYS_MEDIUM", $msg("days.medium").split(",")); 3556 oCal.cfg.setProperty("WEEKDAYS_LONG", $msg("days.long").split(",")); 3557 oCal.cfg.setProperty("START_WEEKDAY", $msg("calendar.widget_config.start_weekday")) 3558 3559 var monthPos = $msg("calendar.widget_config.my_label_month_position"); 3560 if (monthPos.length !== 0) 3561 { 3562 oCal.cfg.setProperty("MY_LABEL_MONTH_POSITION", parseInt(monthPos)); 3563 } 3564 3565 var monthSuffix = $msg("calendar.widget_config.my_label_month_suffix"); 3566 if (monthSuffix.length !== 0) 3567 { 3568 oCal.cfg.setProperty("MY_LABEL_MONTH_SUFFIX", monthSuffix); 3569 } 3570 3571 var yearPos = $msg("calendar.widget_config.my_label_year_position"); 3572 if (yearPos.length !== 0) 3573 { 3574 oCal.cfg.setProperty("MY_LABEL_YEAR_POSITION", parseInt(yearPos)); 3575 } 3576 3577 oCal.cfg.setProperty("MY_LABEL_YEAR_SUFFIX", $msg("calendar.widget_config.my_label_year_suffix")); 3578 }; 3579 3580 /** 3581 * Fixes the hidden caret problem in Firefox 2.x. 3582 * Assumes <input> or <textarea> elements are wrapped in a <div class="yui-u"></div> 3583 * 3584 * @method Alfresco.util.caretFix 3585 * @param p_formElement {element|string} Form element to fix input boxes within 3586 * @static 3587 */ 3588 Alfresco.util.caretFix = function(p_formElement) 3589 { 3590 if (YAHOO.env.ua.gecko === 1.8) 3591 { 3592 if (typeof p_formElement == "string") 3593 { 3594 p_formElement = YUIDom.get(p_formElement); 3595 } 3596 var nodes = YUISelector.query(".yui-u", p_formElement); 3597 for (var x = 0; x < nodes.length; x++) 3598 { 3599 var elem = nodes[x]; 3600 YUIDom.addClass(elem, "caret-fix"); 3601 } 3602 } 3603 }; 3604 3605 /** 3606 * Remove the fixes for the hidden caret problem in Firefox 2.x. 3607 * Should be called before hiding a form for re-use. 3608 * 3609 * @method Alfresco.util.undoCaretFix 3610 * @param p_formElement {element|string} Form element to undo fixes within 3611 * @static 3612 */ 3613 Alfresco.util.undoCaretFix = function(p_formElement) 3614 { 3615 if (YAHOO.env.ua.gecko === 1.8) 3616 { 3617 if (typeof p_formElement == "string") 3618 { 3619 p_formElement = YUIDom.get(p_formElement); 3620 } 3621 var nodes = YUISelector.query(".caret-fix", p_formElement); 3622 for (var x = 0; x < nodes.length; x++) 3623 { 3624 var elem = nodes[x]; 3625 YUIDom.removeClass(elem, "caret-fix"); 3626 } 3627 } 3628 }; 3629 3630 /** 3631 * Submits a form programatically, handling the 3632 * various browser nuances. 3633 * 3634 * @method Alfresco.util.submitForm 3635 * @param form The form to submit 3636 * @static 3637 */ 3638 Alfresco.util.submitForm = function(form) 3639 { 3640 var UA = YAHOO.env.ua; 3641 var submitTheForm = false; 3642 3643 if (form !== null) 3644 { 3645 if (UA.ie && (UA.ie < 9)) 3646 { 3647 // IE up to v9 3648 submitTheForm = form.fireEvent("onsubmit"); 3649 } 3650 else 3651 { 3652 // Gecko, Opera, and Safari 3653 var event = document.createEvent("HTMLEvents"); 3654 event.initEvent("submit", true, true); 3655 submitTheForm = form.dispatchEvent(event); 3656 } 3657 3658 if ((UA.ie || UA.webkit) && submitTheForm) 3659 { 3660 // for IE and webkit firing the event doesn't submit 3661 // the form so we have to do it manually (if the 3662 // submission was not cancelled) 3663 form.submit(); 3664 } 3665 } 3666 }; 3667 3668 /** 3669 * Parses a string to a json object and returns it. 3670 * If str contains invalid json code that is displayed using displayPrompt(). 3671 * 3672 * @method Alfresco.util.parseJSON 3673 * @param jsonStr {string} The JSON string to be parsed 3674 * @param displayError {boolean} Set true to display a message informing about bad JSON syntax 3675 * @return {object} The object representing the JSON string 3676 * @static 3677 */ 3678 Alfresco.util.parseJSON = function(jsonStr, displayError) 3679 { 3680 try 3681 { 3682 return YAHOO.lang.JSON.parse(jsonStr); 3683 } 3684 catch (error) 3685 { 3686 if (displayError) 3687 { 3688 Alfresco.util.PopupManager.displayPrompt( 3689 { 3690 title: "Failure", 3691 text: "Can't parse response as json: '" + jsonStr + "'" 3692 }); 3693 } 3694 } 3695 return null; 3696 }; 3697 3698 /** 3699 * Returns a populated URI template, given a TemplateId and an object literal 3700 * containing the tokens to be substituted. 3701 * Understands when the application is hosted in a Portal environment. 3702 * 3703 * @method Alfresco.util.uriTemplate 3704 * @param templateId {string} URI TemplateId from web-framework configuration 3705 * @param obj {object} The object literal containing the token values to substitute 3706 * @param absolute {boolean} Whether the URL should include the protocol and host 3707 * @return {string|null} The populated URI or null if templateId not found 3708 * @static 3709 */ 3710 Alfresco.util.uriTemplate = function(templateId, obj, absolute) 3711 { 3712 // Check we know about the templateId 3713 if (!(templateId in Alfresco.constants.URI_TEMPLATES)) 3714 { 3715 return null; 3716 } 3717 3718 return Alfresco.util.renderUriTemplate(Alfresco.constants.URI_TEMPLATES[templateId], obj, absolute); 3719 }; 3720 3721 /** 3722 * Returns a populated URI template, given the URI template and an object literal 3723 * containing the tokens to be substituted. 3724 * Understands when the application is hosted in a Portal environment. 3725 * 3726 * @method Alfresco.util.renderUriTemplate 3727 * @param template {string} URI Template to be populated 3728 * @param obj {object} The object literal containing the token values to substitute 3729 * @param absolute {boolean} Whether the URL should include the protocol and host 3730 * @return {string|null} The populated URI or null if templateId not found 3731 * @static 3732 */ 3733 Alfresco.util.renderUriTemplate = function(template, obj, absolute) 3734 { 3735 // If a site page was requested but no {siteid} given, then use the current site or remove the missing parameter 3736 if (template.indexOf("{site}") !== -1) 3737 { 3738 if (obj.hasOwnProperty("site")) 3739 { 3740 // A site parameter was given - is it valid? 3741 if (!Alfresco.util.isValueSet(obj.site)) 3742 { 3743 // Not valid - remove site part of template 3744 template = template.replace("/site/{site}", ""); 3745 } 3746 } 3747 else 3748 { 3749 if (Alfresco.constants.SITE.length > 0) 3750 { 3751 // We're currently in a Site, so generate an in-Site link 3752 obj.site = Alfresco.constants.SITE; 3753 } 3754 else 3755 { 3756 // No current Site context, so remove from the template 3757 template = template.replace("/site/{site}", ""); 3758 } 3759 } 3760 } 3761 3762 var uri = template, 3763 regExp = /^(http|https):\/\//; 3764 3765 /** 3766 * NOTE: YAHOO.lang.substitute is currently somewhat broken in YUI 2.9.0 3767 * Specifically, strings are no longer recursively substituted, even with the new "recurse" 3768 * flag set to "true". See http://yuilibrary.com/projects/yui2/ticket/2529100 3769 */ 3770 while (uri !== (uri = YAHOO.lang.substitute(uri, obj))){} 3771 3772 if (!regExp.test(uri)) 3773 { 3774 // Page context required 3775 uri = Alfresco.util.combinePaths(Alfresco.constants.URL_PAGECONTEXT, uri); 3776 } 3777 3778 // Portlet scriptUrl mapping required? 3779 if (Alfresco.constants.PORTLET) 3780 { 3781 // Remove the context prefix 3782 if (uri.indexOf(Alfresco.constants.URL_CONTEXT) === 0) 3783 { 3784 uri = Alfresco.util.combinePaths("/", uri.substring(Alfresco.constants.URL_CONTEXT.length)); 3785 } 3786 3787 uri = Alfresco.constants.PORTLET_URL.replace("$$scriptUrl$$", encodeURIComponent(decodeURIComponent(uri.replace(/%25/g, "%2525").replace(/%26/g, "%252526")))); 3788 } 3789 3790 // Absolute URI needs current protocol and host 3791 if (absolute && (uri.indexOf(location.protocol + "//") !== 0)) 3792 { 3793 // Don't use combinePaths in case the PORTLET_URL encoding is fragile 3794 if (uri.substring(0, 1) !== "/") 3795 { 3796 uri = "/" + uri; 3797 } 3798 uri = location.protocol + "//" + location.host + uri; 3799 } 3800 3801 return uri; 3802 }; 3803 3804 /** 3805 * Returns a URL to a site page. 3806 * If no Site ID is supplied, generates a link to the non-site page. 3807 * 3808 * @method Alfresco.util.siteURL 3809 * @param pageURI {string} Page ID and and QueryString parameters the page might need, e.g. 3810 * <pre> 3811 * "folder-details?nodeRef=" + nodeRef 3812 * </pre> 3813 * @param obj {object} The object literal containing the token values to substitute within the template 3814 * @param absolute {boolean} Whether the URL should include the protocol and host 3815 * @return {string} The populated URL 3816 * @static 3817 */ 3818 Alfresco.util.siteURL = function(pageURI, obj, absolute) 3819 { 3820 return Alfresco.util.uriTemplate("sitepage", YAHOO.lang.merge(obj || {}, 3821 { 3822 pageid: pageURI 3823 }), absolute); 3824 }; 3825 3826 /** 3827 * Parses a URL string into an object 3828 * 3829 * @param URL {string} The URL to convert 3830 * @return urlObject {object} object literal containing the URL properties. 3831 */ 3832 Alfresco.util.parseURL = function(url) 3833 { 3834 var a = document.createElement("a"); 3835 a.href = url; 3836 var urlObject = { 3837 // protocol includes trailing colon. 3838 protocol: a.protocol, 3839 hostname: a.hostname, 3840 port: a.port, 3841 // host = hostname:port 3842 host: a.host, 3843 pathname: a.pathname, 3844 // search and hash include question mark and hash symbol respectively 3845 search: a.search, 3846 hash: a.hash, 3847 // url = protocol + "//" + host + pathname + search + hash 3848 url: url, 3849 queryParams: Alfresco.util.getQueryStringParameters(url), 3850 getUrl: function() 3851 { 3852 return this.getDomain() + this.pathname + Alfresco.util.toQueryString(this.queryParams) + this.hash; 3853 }, 3854 getDomain: function() 3855 { 3856 return this.protocol + "//" + this.host; 3857 } 3858 } 3859 return urlObject; 3860 } 3861 3862 /** 3863 * Navigates to a url 3864 * 3865 * @method Alfresco.util.navigateTo 3866 * @param uri {string} THe uri to navigate to 3867 * @param method {string} (Optional) Default is "GET" 3868 * @param parameters {string|object} 3869 * @static 3870 */ 3871 Alfresco.util.navigateTo = function(uri, method, parameters) 3872 { 3873 method = method ? method.toUpperCase() : "GET"; 3874 if (method == "GET") 3875 { 3876 window.location.href = uri; 3877 } 3878 else 3879 { 3880 var form = document.createElement("form"); 3881 form.method = method; 3882 form.action = uri; 3883 if (method == "POST" || method == "PUT") 3884 { 3885 var input; 3886 for (var name in parameters) 3887 { 3888 if (parameters.hasOwnProperty(name)) 3889 { 3890 value = parameters[name]; 3891 if (value) 3892 { 3893 input = document.createElement("input"); 3894 input.setAttribute("name", name); 3895 input.setAttribute("type", "hidden"); 3896 input.value = value; 3897 form.appendChild(input); 3898 } 3899 } 3900 } 3901 } 3902 document.body.appendChild(form); 3903 form.submit(); 3904 } 3905 }; 3906 3907 /** 3908 * Generates a link to a site dashboard page 3909 * 3910 * @method Alfresco.util.siteDashboardLink 3911 * @param siteId {string} Site short name 3912 * @param siteTitle {string} Site display name. "siteId" used if this param is empty or not supplied 3913 * @param linkAttr {string} Optional attributes to add to the <a> tag, e.g. "class" 3914 * @param disableLink {boolean} Optional attribute instructing the link to be disabled 3915 * (ie returning a span element rather than an a href element) 3916 * @return {string} The populated HTML Link 3917 * @static 3918 */ 3919 Alfresco.util.siteDashboardLink = function(siteId, siteTitle, linkAttr, disableLink) 3920 { 3921 if (!YAHOO.lang.isString(siteId) || siteId.length === 0) 3922 { 3923 return ""; 3924 } 3925 3926 var html = Alfresco.util.encodeHTML(YAHOO.lang.isString(siteTitle) && siteTitle.length > 0 ? siteTitle : siteId), 3927 template = Alfresco.constants.URI_TEMPLATES["sitedashboardpage"], 3928 uri = ""; 3929 3930 // If the "sitedashboardpage" template doesn't exist or is empty, or we're in portlet mode we'll just return the site's title || id 3931 if (disableLink || YAHOO.lang.isUndefined(template) || template.length === 0 || Alfresco.constants.PORTLET) 3932 { 3933 return '<span>' + html + '</span>'; 3934 } 3935 3936 // Generate the link 3937 uri = Alfresco.util.uriTemplate("sitedashboardpage", 3938 { 3939 site: siteId 3940 }); 3941 3942 return '<a href="' + uri + '" ' + (linkAttr || "") + '>' + html + '</a>'; 3943 }; 3944 3945 /** 3946 * Generates a link to the user profile page unless the "userprofilepage" uritemplate has 3947 * been removed or emptied in share-config-custom.xml 3948 * If no fullName is supplied, the userName is displayed instead. 3949 * 3950 * @method Alfresco.util.userProfileLink 3951 * @param userName {string} User Name 3952 * @param fullName {string} Full display name. "userName" used if this param is empty or not supplied 3953 * @param linkAttr {string} Optional attributes to add to the <a> tag, e.g. "class" 3954 * @param disableLink {boolean} Optional attribute instructing the link to be disabled 3955 * (ie returning a span element rather than an a href element) 3956 * @return {string} The populated HTML Link 3957 * @static 3958 */ 3959 Alfresco.util.userProfileLink = function(userName, fullName, linkAttr, disableLink) 3960 { 3961 if (!YAHOO.lang.isString(userName) || userName.length === 0) 3962 { 3963 return ""; 3964 } 3965 3966 var html = Alfresco.util.encodeHTML(YAHOO.lang.isString(fullName) && fullName.length > 0 ? fullName : userName), 3967 template = Alfresco.constants.URI_TEMPLATES["userprofilepage"], 3968 uri = ""; 3969 3970 // If the "userprofilepage" template doesn't exist or is empty, or we're in portlet mode we'll just return the user's fullName || userName 3971 if (disableLink || YAHOO.lang.isUndefined(template) || template.length === 0 || Alfresco.constants.PORTLET) 3972 { 3973 return '<span>' + html + '</span>'; 3974 } 3975 3976 // Generate the link 3977 uri = Alfresco.util.uriTemplate("userprofilepage", 3978 { 3979 userid: userName 3980 }); 3981 3982 return '<a href="' + uri + '" ' + (linkAttr || "") + '>' + html + '</a>'; 3983 }; 3984 3985 /** 3986 * Returns a URL to the content represented by the passed-in nodeRef 3987 * 3988 * @method Alfresco.util.contentURL 3989 * @param nodeRef {string} Standard Alfresco nodeRef 3990 * @param name {string} Filename to download 3991 * @param attach {boolean} If true, browser should prompt the user to "Open or Save?", rather than display inline 3992 * @return {string} The URL to the content 3993 * @static 3994 */ 3995 Alfresco.util.contentURL = function(nodeRef, name, attach) 3996 { 3997 return Alfresco.constants.PROXY_URI + "api/node/content/" + nodeRef.replace(":/", "") + "/" + name + (attach ? "?a=true" : ""); 3998 }; 3999 4000 /** 4001 * Returns the value of the specified query string parameter. 4002 * 4003 * @method getQueryStringParameter 4004 * @param {string} paramName Name of the parameter we want to look up. 4005 * @param {string} queryString Optional URL to look at. If not specified, 4006 * this method uses the URL in the address bar. 4007 * @return {string} The value of the specified parameter, or null. 4008 * @static 4009 */ 4010 Alfresco.util.getQueryStringParameter = function(paramName, url) 4011 { 4012 var params = this.getQueryStringParameters(url); 4013 4014 if (paramName in params) 4015 { 4016 return params[paramName]; 4017 } 4018 4019 return null; 4020 }; 4021 4022 /** 4023 * Returns the query string parameters as an object literal. 4024 * Parameters appearing more than once are returned an an array. 4025 * This method has been extracted from the YUI Browser History Manager. 4026 * It can be used here without the overhead of the History JavaScript include. 4027 * 4028 * @method getQueryStringParameters 4029 * @param queryString {string} Optional URL to look at. If not specified, 4030 * this method uses the URL in the address bar. 4031 * @return {object} Object literal containing QueryString parameters as name/value pairs 4032 * @static 4033 */ 4034 Alfresco.util.getQueryStringParameters = function(url) 4035 { 4036 var i, len, idx, queryString, params, tokens, name, value, objParams; 4037 4038 url = url || window.location.href; 4039 4040 idx = url.indexOf("?"); 4041 queryString = idx >= 0 ? url.substr(idx + 1) : url; 4042 4043 // Remove the hash if any 4044 idx = queryString.lastIndexOf("#"); 4045 queryString = idx >= 0 ? queryString.substr(0, idx) : queryString; 4046 4047 params = queryString.split("&"); 4048 4049 objParams = {}; 4050 4051 for (i = 0, len = params.length; i < len; i++) 4052 { 4053 tokens = params[i].split("="); 4054 if (tokens.length >= 2) 4055 { 4056 name = tokens[0]; 4057 value = decodeURIComponent(tokens[1]); 4058 switch (typeof objParams[name]) 4059 { 4060 case "undefined": 4061 objParams[name] = value; 4062 break; 4063 4064 case "string": 4065 objParams[name] = [objParams[name]].concat(value); 4066 break; 4067 4068 case "object": 4069 objParams[name] = objParams[name].concat(value); 4070 break; 4071 } 4072 } 4073 } 4074 4075 return objParams; 4076 }; 4077 4078 /** 4079 * Turns an object literal into a valid queryString. 4080 * Format of the object is as returned from the getQueryStringParameters() function. 4081 * 4082 * @method toQueryString 4083 * @param params {object} Object literal containing QueryString parameters as name/value pairs 4084 * @return {string} QueryString-formatted string 4085 * @static 4086 */ 4087 Alfresco.util.toQueryString = function(p_params) 4088 { 4089 var qs = "?", name, value, val; 4090 for (name in p_params) 4091 { 4092 if (p_params.hasOwnProperty(name)) 4093 { 4094 value = p_params[name]; 4095 if (typeof value == "object") 4096 { 4097 for (val in value) 4098 { 4099 if (value.hasOwnProperty(val)) 4100 { 4101 qs += encodeURIComponent(name) + "=" + encodeURIComponent(value[val]) + "&"; 4102 } 4103 } 4104 } 4105 else if (typeof value == "string") 4106 { 4107 qs += encodeURIComponent(name) + "=" + encodeURIComponent(value) + "&"; 4108 } 4109 } 4110 } 4111 4112 // Return the string after removing the last character 4113 return qs.substring(0, qs.length - 1); 4114 }; 4115 4116 /** 4117 * Retrieves a JavaScript session variable. 4118 * Variables are scoped to the current "location.host" 4119 * 4120 * @method getVar 4121 * @param name {string} Variable name 4122 * @param default {object} Default value to return if not set 4123 * @return {object|null} Variable value or default if provided (null otherwise) 4124 * @static 4125 */ 4126 Alfresco.util.getVar = function(p_name, p_default) 4127 { 4128 var returnValue = typeof p_default != "undefined" ? p_default : null; 4129 4130 try 4131 { 4132 if (window.name !== "" && YAHOO.lang.JSON.isValid(window.name)) 4133 { 4134 var allVars = YAHOO.lang.JSON.parse(window.name), 4135 scopedVars = allVars[location.host], 4136 value = null; 4137 4138 if (typeof scopedVars == "object") 4139 { 4140 value = scopedVars[p_name]; 4141 if (typeof value !== "undefined" && value !== null) 4142 { 4143 returnValue = value; 4144 } 4145 } 4146 } 4147 } 4148 catch (e) 4149 { 4150 Alfresco.logger.error("Alfresco.util.getVar()", p_name, p_default, e); 4151 } 4152 4153 return returnValue; 4154 }; 4155 4156 /** 4157 * Sets a JavaScript session variable. 4158 * The variables are stored in window.name, so live for as long as the browser window does. 4159 * Variables are scoped to the current "location.host" 4160 * 4161 * @method setVar 4162 * @param name {string} Variable name 4163 * @param value {object} Value to set 4164 * @return {boolean} True for successful set 4165 * @static 4166 */ 4167 Alfresco.util.setVar = function(p_name, p_value) 4168 { 4169 var success = true; 4170 4171 try 4172 { 4173 var allVars = {}; 4174 4175 if (window.name !== "" && YAHOO.lang.JSON.isValid(window.name)) 4176 { 4177 allVars = YAHOO.lang.JSON.parse(window.name); 4178 } 4179 4180 if (typeof allVars[location.host] == "undefined") 4181 { 4182 allVars[location.host] = {}; 4183 } 4184 allVars[location.host][p_name] = p_value; 4185 4186 window.name = YAHOO.lang.JSON.stringify(allVars); 4187 } 4188 catch (e) 4189 { 4190 Alfresco.logger.error("Alfresco.util.setVar()", p_name, p_value, e); 4191 success = false; 4192 } 4193 return success; 4194 }; 4195 4196 4197 /** 4198 * Takes a string and splits it up to valid tags by using whitespace as separators. 4199 * Multi-word tags are supported by "quoting the phrase". 4200 * e.g. the string 4201 * <pre>hello*world "this is alfresco"</pre> 4202 * would result in tags: "hello*world" and "this is alfresco". 4203 * 4204 * @method getTags 4205 * @param str {string} a string containing tags 4206 * @return {array} of valid tags 4207 * @static 4208 */ 4209 Alfresco.util.getTags = function(str) 4210 { 4211 var match = null, 4212 tags = [], 4213 found = {}, 4214 regexp = new RegExp(/([^"^\s]+)\s*|"([^"]+)"\s*/g), 4215 tag; 4216 4217 while (match = regexp.exec(str)) 4218 { 4219 tag = match[1] || match[2]; 4220 if (found[tag] === undefined) 4221 { 4222 found[tag] = true; 4223 tags.push(tag); 4224 } 4225 } 4226 return tags; 4227 }; 4228 4229 /** 4230 * The YUI Bubbling Library augments callback objects with its own built-in fields. 4231 * This function strips those out, so the remainder of the object is "clean" 4232 * 4233 * @method cleanBubblingObject 4234 * @param callbackObj {object} Object literal as passed to the event handler 4235 * @return {object} Object stripped of Bubbling Library fields 4236 * @static 4237 */ 4238 Alfresco.util.cleanBubblingObject = function(callbackObj) 4239 { 4240 // See Bubbling Library, fire() function. These fields are correct for v2.1. 4241 var augmented = 4242 { 4243 action: true, 4244 flagged: true, 4245 decrepitate: true, 4246 stop: true 4247 }, 4248 cleanObj = {}; 4249 4250 for (var index in callbackObj) 4251 { 4252 if (callbackObj.hasOwnProperty(index) && augmented[index] !== true) 4253 { 4254 cleanObj[index] = callbackObj[index]; 4255 } 4256 } 4257 return cleanObj; 4258 }; 4259 4260 /** 4261 * Bind a function to a specific context. 4262 * 4263 * @method bind 4264 * @param fn {function} Function to be bound. 4265 * @param context {object} Context to bind function to. 4266 * @param arguments {object} Optional arguments to prepend to arguments passed-in when function is actually called 4267 * @return {function} Function wrapper. 4268 * @static 4269 */ 4270 Alfresco.util.bind = function(fn, context) 4271 { 4272 if (!YAHOO.lang.isObject(context)) 4273 { 4274 return fn; 4275 } 4276 var args = Array.prototype.slice.call(arguments).slice(2); 4277 return (function() 4278 { 4279 return fn.apply(context, args.concat(Array.prototype.slice.call(arguments))); 4280 }); 4281 }; 4282 4283 /** 4284 * Autocomplete key filter 4285 * Whether or not key is functional or should be ignored. Note that the right 4286 * arrow key is NOT an ignored key since it triggers queries for certain intl 4287 * charsets. 4288 * From YAHOO.widget.AutoComplete.prototype._isIgnoreKey() 4289 * 4290 * @method isAutocompleteIgnoreKey 4291 * @param nKeycode {Number} Code of key pressed. 4292 * @return {Boolean} True if key should be ignored, false otherwise. 4293 */ 4294 Alfresco.util.isAutocompleteIgnoreKey = function(nKeyCode) 4295 { 4296 if ( 4297 (nKeyCode == 9) || (nKeyCode == 13) || // tab, enter 4298 (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl 4299 (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock 4300 (nKeyCode == 27) || // esc 4301 (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end 4302 (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down 4303 (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert 4304 (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229 4305 ) 4306 { 4307 return true; 4308 } 4309 return false; 4310 }; 4311 4312 4313 /** 4314 * Wrapper for helping components specify their YUI components. 4315 * @class Alfresco.util.YUILoaderHelper 4316 */ 4317 Alfresco.util.YUILoaderHelper = function() 4318 { 4319 /** 4320 * The YUILoader single instance which will load all the dependencies 4321 * @property yuiLoader 4322 * @type YAHOO.util.YUILoader 4323 */ 4324 var yuiLoader = null; 4325 4326 /** 4327 * Array to store callbacks from all component registrants 4328 * @property callbacks 4329 * @type Array 4330 */ 4331 var callbacks = []; 4332 4333 /** 4334 * Flag to indicate whether the initial YUILoader has completed 4335 * @property initialLoaderComplete 4336 * @type boolean 4337 */ 4338 var initialLoaderComplete = false; 4339 4340 /** 4341 * Create YUILoader 4342 * @function createYUILoader 4343 * @private 4344 */ 4345 var createYUILoader = function YUILoaderHelper_createYUILoader() 4346 { 4347 if (yuiLoader === null) 4348 { 4349 yuiLoader = new YAHOO.util.YUILoader( 4350 { 4351 base: Alfresco.constants.URL_RESCONTEXT + "yui/", 4352 filter: Alfresco.constants.DEBUG ? "DEBUG" : "", 4353 loadOptional: false, 4354 skin: {}, 4355 onSuccess: Alfresco.util.YUILoaderHelper.onLoaderComplete, 4356 onFailure: function(event) 4357 { 4358 alert("load failed:" + event); 4359 }, 4360 scope: this 4361 }); 4362 // Add Alfresco YUI components to YUI loader 4363 4364 // SWFPlayer 4365 yuiLoader.addModule( 4366 { 4367 name: "swfplayer", 4368 type: "js", 4369 path: "swfplayer/swfplayer.js", //can use a path instead, extending base path 4370 varName: "SWFPlayer", 4371 requires: ['uploader'] // The FlashAdapter class is located in uploader.js 4372 }); 4373 4374 // ColumnBrowser - js 4375 yuiLoader.addModule( 4376 { 4377 name: "columnbrowser", 4378 type: "js", 4379 path: "columnbrowser/columnbrowser.js", //can use a path instead, extending base path 4380 varName: "ColumnBrowser", 4381 requires: ['json', 'carousel', 'paginator'], 4382 skinnable: true 4383 }); 4384 } 4385 }; 4386 4387 return ( 4388 { 4389 /** 4390 * Main entrypoint for components wishing to load a YUI component 4391 * @method require 4392 * @param p_aComponents {Array} List of required YUI components. See YUILoader documentation for valid names 4393 * @param p_oCallback {function} Callback function invoked when all required YUI components have been loaded 4394 * @param p_oScope {object} Scope for callback function 4395 */ 4396 require: function YLH_require(p_aComponents, p_oCallback, p_oScope) 4397 { 4398 createYUILoader(); 4399 if (p_aComponents.length > 0) 4400 { 4401 /* Have all the YUI components the caller requires been registered? */ 4402 var isRegistered = true; 4403 for (var i = 0; i < p_aComponents.length; i++) 4404 { 4405 if (YAHOO.env.getVersion(p_aComponents[i]) === null) 4406 { 4407 isRegistered = false; 4408 break; 4409 } 4410 } 4411 if (isRegistered && (p_oCallback !== null)) 4412 { 4413 YAHOO.lang.later(10, p_oScope, p_oCallback); 4414 } 4415 else 4416 { 4417 /* Add to the list of components to be loaded */ 4418 yuiLoader.require(p_aComponents); 4419 4420 /* Store the callback function and scope for later */ 4421 callbacks.push( 4422 { 4423 required: Alfresco.util.arrayToObject(p_aComponents), 4424 fn: p_oCallback, 4425 scope: (typeof p_oScope != "undefined" ? p_oScope : window) 4426 }); 4427 } 4428 } 4429 else if (p_oCallback !== null) 4430 { 4431 p_oCallback.call(typeof p_oScope != "undefined" ? p_oScope : window); 4432 } 4433 }, 4434 4435 /** 4436 * Called by template once all component dependencies have been registered. Should be just before the </body> closing tag. 4437 * @method loadComponents 4438 * @param p_pageLoad {Boolean} Whether the function is being called from the page footer, or elsewhere. Only the footer is allowed to use "true" here. 4439 */ 4440 loadComponents: function YLH_loadComponents(p_pageLoad) 4441 { 4442 createYUILoader(); 4443 if (initialLoaderComplete || p_pageLoad === true) 4444 { 4445 if (yuiLoader !== null) 4446 { 4447 yuiLoader.insert(null, "js"); 4448 } 4449 } 4450 }, 4451 4452 /** 4453 * Callback from YUILoader once all required YUI componentshave been loaded by the browser. 4454 * @method onLoaderComplete 4455 */ 4456 onLoaderComplete: function YLH_onLoaderComplete() 4457 { 4458 for (var i = 0; i < callbacks.length; i++) 4459 { 4460 if (callbacks[i].fn) 4461 { 4462 callbacks[i].fn.call(callbacks[i].scope); 4463 } 4464 } 4465 callbacks = []; 4466 initialLoaderComplete = true; 4467 } 4468 }); 4469 }(); 4470 4471 4472 /** 4473 * Keeps track of Alfresco components on a page. Components should register() upon creation to be compliant. 4474 * @class Alfresco.util.ComponentManager 4475 */ 4476 Alfresco.util.ComponentManager = function() 4477 { 4478 /** 4479 * Array of registered components. 4480 * 4481 * @property components 4482 * @type Array 4483 */ 4484 var components = []; 4485 4486 return ( 4487 { 4488 /** 4489 * Main entrypoint for components wishing to register themselves with the ComponentManager 4490 * @method register 4491 * @param p_aComponent {object} Component instance to be registered 4492 */ 4493 register: function CM_register(p_oComponent) 4494 { 4495 if (p_oComponent.id !== "null" && components.hasOwnProperty(p_oComponent.id)) 4496 { 4497 var purge = components[p_oComponent.id]; 4498 if (purge.name === p_oComponent.name) 4499 { 4500 if (typeof purge.destroy === "function") 4501 { 4502 purge.destroy(); 4503 } 4504 this.unregister(components[p_oComponent.id]); 4505 } 4506 } 4507 components.push(p_oComponent); 4508 components[p_oComponent.id] = p_oComponent; 4509 }, 4510 4511 /** 4512 * Unregister a component from the ComponentManager 4513 * @method unregister 4514 * @param p_aComponent {object} Component instance to be unregistered 4515 */ 4516 unregister: function CM_unregister(p_oComponent) 4517 { 4518 for (var i = 0; i < components.length; i++) // Do not optimize 4519 { 4520 if (components[i] == p_oComponent) 4521 { 4522 components.splice(i, 1); 4523 delete components[p_oComponent.id]; 4524 break; 4525 } 4526 } 4527 }, 4528 4529 /** 4530 * Re-register a component with the ComponentManager 4531 * Component ID cannot be updated via this function, use separate unregister(), register() calls instead. 4532 * @method reregister 4533 * @param p_aComponent {object} Component instance to be unregistered, then registered again 4534 */ 4535 reregister: function CM_reregister(p_oComponent) 4536 { 4537 this.unregister(p_oComponent); 4538 this.register(p_oComponent); 4539 }, 4540 4541 /** 4542 * Allows components to find other registered components by name, id or both 4543 * e.g. find({name: "Alfresco.DocumentLibrary"}) 4544 * @method find 4545 * @param p_oParams {object} List of paramters to search by 4546 * @return {Array} Array of components found in the search 4547 */ 4548 find: function CM_find(p_oParams) 4549 { 4550 var found = []; 4551 var bMatch, component; 4552 4553 for (var i = 0, j = components.length; i < j; i++) 4554 { 4555 component = components[i]; 4556 bMatch = true; 4557 for (var key in p_oParams) 4558 { 4559 if (p_oParams[key] != component[key]) 4560 { 4561 bMatch = false; 4562 } 4563 } 4564 if (bMatch) 4565 { 4566 found.push(component); 4567 } 4568 } 4569 return found; 4570 }, 4571 4572 /** 4573 * Allows components to find first registered components by name only 4574 * e.g. findFirst("Alfresco.DocumentLibrary") 4575 * @method findFirst 4576 * @param p_sName {string} Name of registered component to search on 4577 * @return {object|null} Component found in the search 4578 */ 4579 findFirst: function CM_findFirst(p_sName) 4580 { 4581 var found = Alfresco.util.ComponentManager.find( 4582 { 4583 name: p_sName 4584 }); 4585 4586 return (typeof found[0] == "object" ? found[0] : null); 4587 }, 4588 4589 /** 4590 * Get component by Id 4591 * e.g. get("global_x002e_header-sites-menu") 4592 * @method get 4593 * @param p_sId {string} Id of registered component to return 4594 * @return {object|null} Component with given Id 4595 */ 4596 get: function CM_get(p_sId) 4597 { 4598 return (components[p_sId] || null); 4599 }, 4600 4601 /** 4602 * List all registered components 4603 * 4604 * @method list 4605 * @return {array} array of components 4606 */ 4607 list: function CM_list() 4608 { 4609 return components; 4610 } 4611 }); 4612 }(); 4613 4614 /** 4615 * Provides a common interface for displaying popups in various forms 4616 * 4617 * @class Alfresco.util.PopupManager 4618 */ 4619 Alfresco.util.PopupManager = function() 4620 { 4621 /** 4622 * Alfresco Slingshot aliases 4623 */ 4624 var $html = Alfresco.util.encodeHTML; 4625 4626 return ( 4627 { 4628 4629 /** 4630 * The html zIndex startvalue that will be incremented for each popup 4631 * that is displayed to make sure the popup is visible to the user. 4632 * 4633 * @property zIndex 4634 * @type int 4635 */ 4636 zIndex: 15, 4637 4638 /** 4639 * The default config for the displaying messages, can be overriden 4640 * when calling displayMessage() 4641 * 4642 * @property defaultDisplayMessageConfig 4643 * @type object 4644 */ 4645 defaultDisplayMessageConfig: 4646 { 4647 title: null, 4648 text: null, 4649 spanClass: "message", 4650 displayTime: 2.5, 4651 effect: YAHOO.widget.ContainerEffect.FADE, 4652 effectDuration: 0.5, 4653 visible: false, 4654 noEscape: false 4655 }, 4656 4657 /** 4658 * Intended usage: To quickly assure the user that the expected happened. 4659 * 4660 * Displays a message as a popup on the screen. 4661 * In default mode it fades, is visible for half a second and then fades out. 4662 * 4663 * @method displayMessage 4664 * @param config {object} 4665 * The config object is in the form of: 4666 * { 4667 * text: {string}, // The message text to display, mandatory 4668 * spanClass: {string}, // The class of the span wrapping the text 4669 * effect: {YAHOO.widget.ContainerEffect}, // the effect to use when shpwing and hiding the message, 4670 * // default is YAHOO.widget.ContainerEffect.FADE 4671 * effectDuration: {int}, // time in seconds that the effect should be played, default is 0.5 4672 * displayTime: {int}, // time in seconds that the message will be displayed, default 2.5 4673 * modal: {true} // if the message should modal (the background overlayed with a gray transparent layer), default is false 4674 * } 4675 * @param parent {HTMLElement} (optional) Parent element in which to render prompt. Defaults to document.body if not provided 4676 */ 4677 displayMessage: function PopupManager_displayMessage(config, parent) 4678 { 4679 var parent = parent || document.body; 4680 // Merge the users config with the default config and check mandatory properties 4681 var c = YAHOO.lang.merge(this.defaultDisplayMessageConfig, config); 4682 if (c.text === undefined) 4683 { 4684 throw new Error("Property text in userConfig must be set"); 4685 } 4686 var dialogConfig = 4687 { 4688 modal: false, 4689 visible: c.visible, 4690 close: false, 4691 draggable: false, 4692 effect: 4693 { 4694 effect: c.effect, 4695 duration: c.effectDuration 4696 }, 4697 zIndex: this.zIndex++ 4698 }; 4699 // IE browsers don't deserve fading, as they can't handle it properly 4700 if (c.effect === null || YAHOO.env.ua.ie > 0) 4701 { 4702 delete dialogConfig.effect; 4703 } 4704 // Construct the YUI Dialog that will display the message 4705 var message = new YAHOO.widget.Dialog("message", dialogConfig); 4706 4707 // Set the message that should be displayed 4708 var bd = "<span class='" + c.spanClass + "'>" + (c.noEscape ? c.text : $html(c.text)) + "</span>"; 4709 message.setBody(bd); 4710 4711 /** 4712 * Add it to the dom, center it, schedule the fade out of the message 4713 * and show it. 4714 */ 4715 message.render(parent); 4716 message.center(); 4717 // Need to schedule a fade-out? 4718 if (c.displayTime > 0) 4719 { 4720 message.subscribe("show", this._delayPopupHide, 4721 { 4722 popup: message, 4723 displayTime: (c.displayTime * 1000) 4724 }, true); 4725 } 4726 message.show(); 4727 4728 return message; 4729 }, 4730 4731 /** 4732 * Gets called after the message has been displayed as long as it was 4733 * configured. 4734 * Hides the message from the user. 4735 * 4736 * @method _delayPopupHide 4737 */ 4738 _delayPopupHide: function PopupManager__delayPopupHide() 4739 { 4740 YAHOO.lang.later(this.displayTime, this, function() 4741 { 4742 this.popup.destroy(); 4743 }); 4744 }, 4745 4746 /** 4747 * The default config for displaying "prompt" messages, can be overriden 4748 * when calling displayPrompt() 4749 * 4750 * @property defaultDisplayPromptConfig 4751 * @type object 4752 */ 4753 defaultDisplayPromptConfig: 4754 { 4755 title: null, 4756 text: null, 4757 icon: null, 4758 close: false, 4759 constraintoviewport: true, 4760 draggable: true, 4761 effect: null, 4762 effectDuration: 0.5, 4763 modal: true, 4764 visible: false, 4765 noEscape: false, 4766 buttons: [ 4767 { 4768 text: null, // Too early to localize at this time, do it when called instead 4769 handler: function() 4770 { 4771 this.destroy(); 4772 }, 4773 isDefault: true 4774 }] 4775 }, 4776 4777 /** 4778 * Intended usage: To inform the user that something unexpected happened 4779 * OR that ask the user if if an action should be performed. 4780 * 4781 * Displays a message as a popup on the screen with a button to make sure 4782 * the user responds to the prompt. 4783 * 4784 * In default mode it shows with an OK button that needs clicking to get closed. 4785 * 4786 * @method displayPrompt 4787 * @param config {object} 4788 * The config object is in the form of: 4789 * { 4790 * title: {string}, // the title of the dialog, default is null 4791 * text: {string}, // the text to display for the user, mandatory 4792 * icon: null, // the icon to display next to the text, default is null 4793 * effect: {YAHOO.widget.ContainerEffect}, // the effect to use when showing and hiding the prompt, default is null 4794 * effectDuration: {int}, // the time in seconds that the effect should run, default is 0.5 4795 * modal: {boolean}, // if a grey transparent overlay should be displayed in the background 4796 * close: {boolean}, // if a close icon should be displayed in the right upper corner, default is false 4797 * buttons: [] // an array of button configs as described by YUI's SimpleDialog, default is a single OK button 4798 * noEscape: {boolean} // indicates the the message has already been escaped (e.g. to display HTML-based messages) 4799 * } 4800 * @param parent {HTMLElement} (optional) Parent element in which to render prompt. Defaults to document.body if not provided 4801 */ 4802 displayPrompt: function PopupManager_displayPrompt(config, parent) 4803 { 4804 var parent = parent || document.body; 4805 if (this.defaultDisplayPromptConfig.buttons[0].text === null) 4806 { 4807 /** 4808 * This default value could not be set at instantion time since the 4809 * localized messages weren't present at that time 4810 */ 4811 this.defaultDisplayPromptConfig.buttons[0].text = Alfresco.util.message("button.ok", this.name); 4812 } 4813 // Merge users config and the default config and check manadatory properties 4814 var c = YAHOO.lang.merge(this.defaultDisplayPromptConfig, config); 4815 if (c.text === undefined) 4816 { 4817 throw new Error("Property text in userConfig must be set"); 4818 } 4819 4820 // Create the SimpleDialog that will display the text 4821 var prompt = new YAHOO.widget.SimpleDialog("prompt", 4822 { 4823 close: c.close, 4824 constraintoviewport: c.constraintoviewport, 4825 draggable: c.draggable, 4826 effect: c.effect, 4827 modal: c.modal, 4828 visible: c.visible, 4829 zIndex: this.zIndex++ 4830 }); 4831 4832 // Show the title if it exists 4833 if (c.title) 4834 { 4835 prompt.setHeader($html(c.title)); 4836 } 4837 4838 // Show the prompt text 4839 prompt.setBody(c.noEscape ? c.text : $html(c.text)); 4840 4841 // Show the icon if it exists 4842 if (c.icon) 4843 { 4844 prompt.cfg.setProperty("icon", c.icon); 4845 } 4846 4847 // Add the buttons to the dialog 4848 if (c.buttons) 4849 { 4850 prompt.cfg.queueProperty("buttons", c.buttons); 4851 } 4852 4853 // Add the dialog to the dom, center it and show it. 4854 prompt.render(parent); 4855 prompt.center(); 4856 prompt.show(); 4857 }, 4858 4859 /** 4860 * The default config for the getting user input, can be overriden 4861 * when calling getUserInput() 4862 * 4863 * @property defaultGetUserInputConfig 4864 * @type object 4865 */ 4866 defaultGetUserInputConfig: 4867 { 4868 title: null, 4869 text: null, 4870 input: "textarea", 4871 value: "", 4872 icon: null, 4873 close: true, 4874 constraintoviewport: true, 4875 draggable: true, 4876 effect: null, 4877 effectDuration: 0.5, 4878 modal: true, 4879 visible: false, 4880 initialShow: true, 4881 noEscape: true, 4882 html: null, 4883 callback: null, 4884 buttons: [ 4885 { 4886 text: null, // OK button. Too early to localize at this time, do it when called instead 4887 handler: null, 4888 isDefault: true 4889 }, 4890 { 4891 text: null, // Cancel button. Too early to localize at this time, do it when called instead 4892 handler: function() 4893 { 4894 this.destroy(); 4895 } 4896 }] 4897 }, 4898 4899 /** 4900 * Intended usage: To ask the user for a simple text input, similar to JavaScript's prompt() function. 4901 * 4902 * @method getUserInput 4903 * @param config {object} 4904 * The config object is in the form of: 4905 * { 4906 * title: {string}, // the title of the dialog, default is null 4907 * text: {string}, // optional label next to input box 4908 * value: {string}, // optional default value to populate textbox with 4909 * callback: {object} // Object literal specifying function callback to receive user input. Only called if default button config used. 4910 * // fn: function, obj: optional pass-thru object, scope: callback scope 4911 * icon: null, // the icon to display next to the text, default is null 4912 * effect: {YAHOO.widget.ContainerEffect}, // the effect to use when showing and hiding the prompt, default is null 4913 * effectDuration: {int}, // the time in seconds that the effect should run, default is 0.5 4914 * modal: {boolean}, // if a grey transparent overlay should be displayed in the background 4915 * initialShow {boolean} // whether to call show() automatically on the panel 4916 * close: {boolean}, // if a close icon should be displayed in the right upper corner, default is true 4917 * buttons: [] // an array of button configs as described by YUI's SimpleDialog, default is a single OK button 4918 * okButtonText: {string} // Allows just the label of the OK button to be overridden 4919 * noEscape: {boolean} // indicates the the text property has already been escaped (e.g. to display HTML-based messages) 4920 * html: {string}, // optional override for function-generated HTML <input> field. Note however that you must supply your own 4921 * // button handlers in this case in order to get the user's input from the Dom. 4922 * } 4923 * @return {YAHOO.widget.SimpleDialog} The dialog widget 4924 */ 4925 getUserInput: function PopupManager_getUserInput(config) 4926 { 4927 if (this.defaultGetUserInputConfig.buttons[0].text === null) 4928 { 4929 /** 4930 * This default value could not be set at instantion time since the 4931 * localized messages weren't present at that time 4932 */ 4933 this.defaultGetUserInputConfig.buttons[0].text = Alfresco.util.message("button.ok", this.name); 4934 } 4935 if (this.defaultGetUserInputConfig.buttons[1].text === null) 4936 { 4937 this.defaultGetUserInputConfig.buttons[1].text = Alfresco.util.message("button.cancel", this.name); 4938 } 4939 4940 // Merge users config and the default config and check manadatory properties 4941 var c = YAHOO.lang.merge(this.defaultGetUserInputConfig, config); 4942 4943 // Create the SimpleDialog that will display the text 4944 var prompt = new YAHOO.widget.SimpleDialog("userInput", 4945 { 4946 close: c.close, 4947 constraintoviewport: c.constraintoviewport, 4948 draggable: c.draggable, 4949 effect: c.effect, 4950 modal: c.modal, 4951 visible: c.visible, 4952 zIndex: this.zIndex++ 4953 }); 4954 4955 // Show the title if it exists 4956 if (c.title) 4957 { 4958 prompt.setHeader($html(c.title)); 4959 } 4960 4961 // Generate the HTML mark-up if not overridden 4962 var html = c.html, 4963 id = Alfresco.util.generateDomId(); 4964 4965 if (html === null) 4966 { 4967 html = ""; 4968 if (c.text) 4969 { 4970 html += '<label for="' + id + '">' + (c.noEscape ? c.text : $html(c.text)) + '</label><br/>'; 4971 } 4972 if (c.input == "textarea") 4973 { 4974 html += '<textarea id="' + id + '" tabindex="0">' + c.value + '</textarea>'; 4975 } 4976 else if (c.input == "text") 4977 { 4978 html += '<input id="' + id + '" tabindex="0" type="text" value="' + c.value + '"/>'; 4979 } 4980 } 4981 prompt.setBody(html); 4982 4983 // Show the icon if it exists 4984 if (c.icon) 4985 { 4986 prompt.cfg.setProperty("icon", c.icon); 4987 } 4988 4989 // Add the buttons to the dialog 4990 if (c.buttons) 4991 { 4992 if (c.okButtonText) 4993 { 4994 // Override OK button label 4995 c.buttons[0].text = c.okButtonText; 4996 } 4997 4998 // Default handler if no custom button passed-in 4999 if (typeof config.buttons == "undefined" || typeof config.buttons[0] == "undefined") 5000 { 5001 // OK button click handler 5002 c.buttons[0].handler = { 5003 fn: function(event, obj) 5004 { 5005 // Grab the input, destroy the pop-up, then callback with the value 5006 var value = null; 5007 if (YUIDom.get(obj.id)) 5008 { 5009 var inputEl = YUIDom.get(obj.id); 5010 value = YAHOO.lang.trim(inputEl.value || inputEl.text); 5011 } 5012 this.destroy(); 5013 if (obj.callback.fn) 5014 { 5015 obj.callback.fn.call(obj.callback.scope || window, value, obj.callback.obj); 5016 } 5017 }, 5018 obj: 5019 { 5020 id: id, 5021 callback: c.callback 5022 } 5023 }; 5024 } 5025 prompt.cfg.queueProperty("buttons", c.buttons); 5026 } 5027 5028 // Add the dialog to the dom, center it and show it (unless flagged not to). 5029 prompt.render(document.body); 5030 5031 // Make sure ok button only is enabled if textfield contains content 5032 if (prompt.getButtons().length > 0) 5033 { 5034 // Make sure button only is disabled if textinput has a proper value 5035 var okButton = prompt.getButtons()[0]; 5036 YAHOO.util.Event.addListener(id, "keyup", function(event, okButton) 5037 { 5038 okButton.set("disabled", YAHOO.lang.trim(this.value || this.text || "").length == 0); 5039 }, okButton); 5040 okButton.set("disabled", YAHOO.lang.trim(c.value).length == 0) 5041 } 5042 5043 // Center and display 5044 prompt.center(); 5045 if (c.initialShow) 5046 { 5047 prompt.show(); 5048 } 5049 5050 // If a default value was given, set the selectionStart and selectionEnd properties 5051 if (c.value !== "") 5052 { 5053 YUIDom.get(id).selectionStart = 0; 5054 YUIDom.get(id).selectionEnd = c.value.length; 5055 } 5056 5057 // Register the ESC key to close the panel 5058 var escapeListener = new YAHOO.util.KeyListener(document, 5059 { 5060 keys: YAHOO.util.KeyListener.KEY.ESCAPE 5061 }, 5062 { 5063 fn: function(id, keyEvent) 5064 { 5065 this.destroy(); 5066 }, 5067 scope: prompt, 5068 correctScope: true 5069 }); 5070 escapeListener.enable(); 5071 5072 if (YUIDom.get(id)) 5073 { 5074 YUIDom.get(id).focus(); 5075 } 5076 5077 return prompt; 5078 }, 5079 5080 /** 5081 * Displays a form inside a dialog 5082 * 5083 * @method displayForm 5084 * @param config 5085 * @param config.title {String} The dialog title 5086 * @param config.properties {Object} An object literal with the form properties 5087 * @param config.success {Object} A callback object literal used on form success 5088 * @param config.successMessage {String} A submit success message 5089 * @param config.failure {Object} A callback object literal used on form failure 5090 * @param config.failureMessage {String} A submit failure message 5091 */ 5092 displayForm: function PopupManager_displayForm(config) 5093 { 5094 // Use the htmlid to make sure we respond to events from the correct form instance 5095 var htmlid = config.properties.htmlid || Alfresco.util.generateDomId(); 5096 config.properties.htmlid = htmlid; 5097 5098 // Display form webscript in a dialog 5099 var panel = this.displayWebscript( 5100 { 5101 title: config.title, 5102 url: Alfresco.constants.URL_SERVICECONTEXT + "components/form", 5103 properties: YAHOO.lang.merge( 5104 { 5105 submitType: "json", 5106 showCaption: false, 5107 formUI: true, 5108 showCancelButton: true 5109 }, config.properties) 5110 }); 5111 5112 // Adjust form to work in dialog 5113 YAHOO.Bubbling.on("formContentReady", function PopupManager_displayForm_onFormContentReady(layer, args) 5114 { 5115 if (Alfresco.util.hasEventInterest(htmlid + "-form", args)) 5116 { 5117 // Change the default 'Submit' label to be 'Ok' 5118 var submitButton = args[1].buttons.submit; 5119 submitButton.set("label", Alfresco.util.message("label.ok")); 5120 5121 // Close dialog when cancel button is clicked 5122 var cancelButton = args[1].buttons.cancel; 5123 if (cancelButton) 5124 { 5125 cancelButton.addListener("click", this.panel.destroy, this.panel, true); 5126 } 5127 } 5128 }, 5129 { 5130 panel: panel, 5131 config: config 5132 }); 5133 5134 // When form is submitted make sure we hide the dialog after a successful submit and display a message when it fails. 5135 YAHOO.Bubbling.on("beforeFormRuntimeInit", function PopupManager_displayForm_onBeforeFormRuntimeInit(layer, args) 5136 { 5137 if (Alfresco.util.hasEventInterest(htmlid + "-form", args)) 5138 { 5139 args[1].runtime.setAJAXSubmit(true, 5140 { 5141 successCallback: 5142 { 5143 fn: function PopupMananger_displayForm_formSuccess(response) 5144 { 5145 this.panel.destroy(); 5146 if (this.config.success && YAHOO.lang.isFunction(this.config.success.fn)) 5147 { 5148 this.config.success.fn.call(this.config.success.scope || {}, response, this.config.success.obj) 5149 } 5150 }, 5151 scope: this 5152 }, 5153 successMessage: this.config.successMessage, 5154 failureCallback: this.config.success, 5155 failureMessage: this.config.failureMessage 5156 }); 5157 } 5158 }, 5159 { 5160 panel: panel, 5161 config: config 5162 }); 5163 }, 5164 5165 /** 5166 * Displays a webscript inside a dialog 5167 * 5168 * @method displayWebscript 5169 * @param config 5170 * @param config.title {String} The dialog title 5171 * @param config.method {String} (Optional) Defaults to "GET" 5172 * @param config.url {String} THe url to the webscript to load 5173 * @param config.properties {Object} An object literal with the webscript parameters 5174 */ 5175 displayWebscript: function PopupManager_displayWebscript(config) 5176 { 5177 // Help creating a htmlid if none has been provided 5178 config.properties.htmlid = config.properties.htmlid || Alfresco.util.generateDomId(); 5179 5180 var p = new YAHOO.widget.Dialog(config.properties.htmlid + "-panel", 5181 { 5182 visible:false, 5183 modal: true, 5184 constraintoviewport: true, 5185 fixedcenter: "contained", 5186 postmethod: "none" 5187 }); 5188 5189 // Load the form for the specific workflow 5190 Alfresco.util.Ajax.request( 5191 { 5192 method: config.method || Alfresco.util.Ajax.GET, 5193 url: config.url, 5194 dataObj: config.properties, 5195 successCallback: 5196 { 5197 fn: function PopupManager_displayWebscript_successCallback(response, config) 5198 { 5199 // Instantiate a Panel from script 5200 p.setHeader(config.title); 5201 p.setBody(response.serverResponse.responseText); 5202 p.render(document.body); 5203 p.show(); 5204 }, 5205 scope: this, 5206 obj: config 5207 }, 5208 failureMessage: Alfresco.util.message("message.failure"), 5209 scope: this, 5210 execScripts: true 5211 }); 5212 return p; 5213 } 5214 }); 5215 }(); 5216 5217 5218 /** 5219 * Keeps track of multiple filters on a page. Filters should register() upon creation to be compliant. 5220 * @class Alfresco.util.FilterManager 5221 */ 5222 Alfresco.util.FilterManager = function() 5223 { 5224 /** 5225 * Array of registered filters. 5226 * 5227 * @property filters 5228 * @type Array 5229 */ 5230 var filters = []; 5231 5232 return ( 5233 { 5234 /** 5235 * Main entrypoint for filters wishing to register themselves with the FilterManager 5236 * @method register 5237 * @param p_filterOwner {string} Name of the owner registering this filter. Used when owning exclusive filters. 5238 * @param p_filterIds {string|Array} Single or multiple filterIds this filter owns 5239 */ 5240 register: function FM_register(p_filterOwner, p_filterIds) 5241 { 5242 var i, ii, filterId; 5243 5244 if (typeof p_filterIds == "string") 5245 { 5246 p_filterIds = [p_filterIds]; 5247 } 5248 5249 for (i = 0, ii = p_filterIds.length; i < ii; i++) 5250 { 5251 filterId = p_filterIds[i]; 5252 filters.push( 5253 { 5254 filterOwner: p_filterOwner, 5255 filterId: filterId 5256 }); 5257 filters[filterId] = p_filterOwner; 5258 } 5259 }, 5260 5261 /** 5262 * Get filterOwner by filterId 5263 * 5264 * @method getOwner 5265 * @param p_filterId {string} FilterId 5266 * @return {string|null} filterOwner that has registered for the given filterId 5267 */ 5268 getOwner: function FM_getOwner(p_filterId) 5269 { 5270 return (filters[p_filterId] || null); 5271 } 5272 }); 5273 }(); 5274 5275 5276 /** 5277 * Helper class for submitting data to serverthat wraps a 5278 * YAHOO.util.Connect.asyncRequest call. 5279 * 5280 * The request methid provides default behaviour for displaying messages on 5281 * success and error events and simplifies json handling with encoding and decoding. 5282 * 5283 * @class Alfresco.util.Ajax 5284 */ 5285 Alfresco.util.Ajax = function() 5286 { 5287 // Since we mix FORM & JSON request we must make sure our Content-type headers aren't overriden 5288 //YAHOO.util.Connect.setDefaultPostHeader(false); 5289 //YAHOO.util.Connect.setDefaultXhrHeader(false); 5290 5291 return { 5292 5293 /** 5294 * Constant for contentType of type standard XHR form request 5295 * 5296 * @property FORM 5297 * @type string 5298 */ 5299 FORM: "application/x-www-form-urlencoded", 5300 5301 /** 5302 * Constant for contentType of type json 5303 * 5304 * @property JSON 5305 * @type string 5306 */ 5307 JSON: "application/json", 5308 5309 /** 5310 * Constant for method of type GET 5311 * 5312 * @property GET 5313 * @type string 5314 */ 5315 GET: "GET", 5316 5317 /** 5318 * Constant for method of type POST 5319 * 5320 * @property POST 5321 * @type string 5322 */ 5323 POST: "POST", 5324 5325 /** 5326 * Constant for method of type PUT 5327 * 5328 * @property PUT 5329 * @type string 5330 */ 5331 PUT: "PUT", 5332 5333 /** 5334 * Constant for method of type DELETE 5335 * 5336 * @property DELETE 5337 * @type string 5338 */ 5339 DELETE: "DELETE", 5340 5341 /** 5342 * The default request config used by method request() 5343 * 5344 * @property defaultRequestConfig 5345 * @type object 5346 */ 5347 defaultRequestConfig: 5348 { 5349 method: "GET", // GET, POST, PUT or DELETE 5350 url: null, // Must be set by user 5351 dataObj: null, // Will be encoded to parameters (key1=value1&key2=value2) 5352 // or a json string if contentType is set to JSON 5353 dataStr: null, // Will be used in the request body, could be a already created parameter or json string 5354 // Will be overriden by the encoding result from dataObj if dataObj is provided 5355 dataForm: null, // A form object or id that contains the data to be sent with request 5356 requestContentType: null, // Set to JSON if json should be used 5357 responseContentType: null, // Set to JSON if json should be used 5358 successCallback: null,// Object literal representing callback upon successful operation 5359 successMessage: null, // Will be displayed by Alfresco.util.PopupManager.displayMessage if no success handler is provided 5360 failureCallback: null,// Object literal representing callback upon failed operation 5361 failureMessage: null, // Will be displayed by Alfresco.util.displayPrompt if no failure handler is provided 5362 execScripts: false, // Whether embedded <script> tags will be executed within the successful response 5363 noReloadOnAuthFailure: false, // Default to reloading the page on HTTP 401 response, which will redirect through the login page 5364 object: null // An object that can be passed to be used by the success or failure handlers 5365 }, 5366 5367 /** 5368 * Wraps a YAHOO.util.Connect.asyncRequest call and provides some default 5369 * behaviour for displaying error or success messages, uri encoding and 5370 * json encoding and decoding. 5371 * 5372 * JSON 5373 * 5374 * If requestContentType is JSON, config.dataObj (if available) is encoded 5375 * to a json string and set in the request body. 5376 * 5377 * If a json string already has been created by the application it should 5378 * be passed in as the config.dataStr which will be put in the rewuest body. 5379 * 5380 * If responseContentType is JSON the server response is decoded to a 5381 * json object and set in the "json" attribute in the response object 5382 * which is passed to the succes or failure callback. 5383 * 5384 * PARAMETERS 5385 * 5386 * If requestContentType is null, config.dataObj (if available) is encoded 5387 * to a normal parameter string which is added to the url if method is 5388 * GET or DELETE and to the request body if method is POST or PUT. 5389 * 5390 * FORMS 5391 * A form can also be passed it and submitted just as desccribed in the 5392 * YUI documentation. 5393 * 5394 * SUCCESS 5395 * 5396 * If the request is successful successCallback.fn is called. 5397 * If successCallback.fn isn't provided successMessage is displayed. 5398 * If successMessage isn't provided nothing happens. 5399 * 5400 * FAILURE 5401 * 5402 * If the request fails failureCallback.fn is called. 5403 * If failureCallback.fn isn't displayed failureMessage is displayed. 5404 * If failureMessage isn't provided the "best error message as possible" 5405 * from the server response is displayed. 5406 * 5407 * CALLBACKS 5408 * 5409 * The success or failure handlers can expect a response object of the 5410 * following form (they will be called in the scope defined by config.scope) 5411 * 5412 * { 5413 * config: {object}, // The config object passed in to the request, 5414 * serverResponse: {object}, // The response provided by YUI 5415 * json: {object} // The serverResponse parsed and ready as an object 5416 * } 5417 * 5418 * @method request 5419 * @param config {object} Description of the request that should be made 5420 * The config object has the following form: 5421 * { 5422 * method: {string} // GET, POST, PUT or DELETE, default is GET 5423 * url: {string}, // the url to send the request to, mandatory 5424 * dataObj: {object}, // Will be encoded to parameters (key1=value1&key2=value2) or a json string if requestContentType is set to JSON 5425 * dataStr: {string}, // the request body, will be overriden by the encoding result from dataObj if dataObj is provided 5426 * dataForm: {HTMLElement}, // A form object or id that contains the data to be sent with request 5427 * requestContentType: {string}, // Set to JSON if json should be used 5428 * responseContentType: {string}, // Set to JSON if json should be used 5429 * successCallback: {object}, // Callback for successful request, should have the following form: {fn: successHandler, scope: scopeForSuccessHandler} 5430 * successMessage: {string}, // Will be displayed using Alfresco.util.PopupManager.displayMessage if successCallback isn't provided 5431 * failureCallback: {object}, // Callback for failed request, should have the following form: {fn: failureHandler, scope: scopeForFailureHandler} 5432 * failureMessage: {string}, // Will be displayed by Alfresco.util.displayPrompt if no failureCallback isn't provided 5433 * execScripts: {boolean}, // Whether embedded <script> tags will be executed within the successful response 5434 * noReloadOnAuthFailure: {boolean}, // Set to TRUE to prevent an automatic page refresh on HTTP 401 response 5435 * object: {object} // An object that can be passed to be used by the success or failure handlers 5436 * } 5437 */ 5438 request: function(config) 5439 { 5440 // Merge the user config with the default config and check for mandatory parameters 5441 var c = YAHOO.lang.merge(this.defaultRequestConfig, config); 5442 Alfresco.util.assertNotEmpty(c.url, "Parameter 'url' can NOT be null"); 5443 Alfresco.util.assertNotEmpty(c.method, "Parameter 'method' can NOT be null"); 5444 5445 // If a contentType is provided set it in the header 5446 if (c.requestContentType) 5447 { 5448 YAHOO.util.Connect.setDefaultPostHeader(c.requestContentType); 5449 YAHOO.util.Connect.setDefaultXhrHeader(c.requestContentType); 5450 YAHOO.util.Connect.initHeader("Content-Type", c.requestContentType); 5451 } 5452 else 5453 { 5454 YAHOO.util.Connect.setDefaultPostHeader(this.FORM); 5455 YAHOO.util.Connect.setDefaultXhrHeader(this.FORM); 5456 YAHOO.util.Connect.initHeader("Content-Type", this.FORM) 5457 } 5458 5459 // Encode dataObj depending on request method and contentType. 5460 // Note: GET requests are always queryString encoded. 5461 if (c.requestContentType === this.JSON) 5462 { 5463 if (c.method.toUpperCase() === this.GET) 5464 { 5465 if (c.dataObj) 5466 { 5467 // Encode the dataObj and put it in the url 5468 c.url += (c.url.indexOf("?") == -1 ? "?" : "&") + this.jsonToParamString(c.dataObj, true); 5469 } 5470 } 5471 else 5472 { 5473 // If json is used encode the dataObj parameter and put it in the body 5474 c.dataStr = YAHOO.lang.JSON.stringify(c.dataObj || {}); 5475 } 5476 } 5477 else 5478 { 5479 if (c.dataObj) 5480 { 5481 // Normal URL parameters 5482 if (c.method.toUpperCase() === this.GET) 5483 { 5484 // Encode the dataObj and put it in the url 5485 c.url += (c.url.indexOf("?") == -1 ? "?" : "&") + this.jsonToParamString(c.dataObj, true); 5486 } 5487 else 5488 { 5489 // Enccode the dataObj and put it in the body 5490 c.dataStr = this.jsonToParamString(c.dataObj, true); 5491 } 5492 } 5493 } 5494 5495 if (c.dataForm !== null) 5496 { 5497 // Set the form on the connection manager 5498 YAHOO.util.Connect.setForm(c.dataForm); 5499 } 5500 5501 /** 5502 * The private "inner" callback that will handle json and displaying 5503 * of messages and prompts 5504 */ 5505 var callback = 5506 { 5507 success: this._successHandler, 5508 failure: this._failureHandler, 5509 scope: this, 5510 argument: 5511 { 5512 config: config 5513 } 5514 }; 5515 5516 // Do we need to tunnel the HTTP method if the client can't support it (Adobe AIR) 5517 if (YAHOO.env.ua.air !== 0) 5518 { 5519 // Check for unsupported HTTP methods 5520 if (c.method.toUpperCase() == "PUT" || c.method.toUpperCase() == "DELETE") 5521 { 5522 // Check we're not tunnelling already 5523 var alfMethod = Alfresco.util.getQueryStringParameter("alf_method", c.url); 5524 if (alfMethod === null) 5525 { 5526 c.url += (c.url.indexOf("?") == -1 ? "?" : "&") + "alf_method=" + c.method; 5527 c.method = this.POST; 5528 } 5529 } 5530 } 5531 5532 // Make the request 5533 YAHOO.util.Connect.asyncRequest (c.method, c.url, callback, c.dataStr); 5534 }, 5535 5536 /** 5537 * Helper function for pure json requests, where both the request and 5538 * response are using json. Will result in a call to request() with 5539 * requestContentType and responseContentType set to JSON. 5540 * 5541 * @method request 5542 * @param config {object} Description of the request that should be made 5543 */ 5544 jsonRequest: function(config) 5545 { 5546 config.requestContentType = this.JSON; 5547 config.responseContentType = this.JSON; 5548 this.request(config); 5549 }, 5550 5551 /** 5552 * Helper function for pure json requests, where both the request and 5553 * response are using json. Will result in a call to request() with 5554 * responseContentType set to JSON and method set to GET. 5555 * 5556 * @method request 5557 * @param config {object} Description of the request that should be made 5558 */ 5559 jsonGet: function(config) 5560 { 5561 config.method = this.GET; 5562 this.jsonRequest(config); 5563 }, 5564 5565 /** 5566 * Helper function for pure json requests, where both the request and 5567 * response are using json. Will result in a call to request() with 5568 * requestContentType and responseContentType set to JSON and method set to POST. 5569 * 5570 * @method request 5571 * @param config {object} Description of the request that should be made 5572 */ 5573 jsonPost: function(config) 5574 { 5575 config.method = this.POST; 5576 this.jsonRequest(config); 5577 }, 5578 5579 /** 5580 * Helper function for pure json requests, where both the request and 5581 * response are using json. Will result in a call to request() with 5582 * requestContentType and responseContentType set to JSON and method set to PUT. 5583 * 5584 * @method request 5585 * @param config {object} Description of the request that should be made 5586 */ 5587 jsonPut: function(config) 5588 { 5589 config.method = this.PUT; 5590 this.jsonRequest(config); 5591 }, 5592 5593 /** 5594 * Helper function for pure json requests, where both the request and 5595 * response are using json. Will result in a call to request() with 5596 * responseContentType set to JSON and method set to DELETE. 5597 * 5598 * @method request 5599 * @param config {object} Description of the request that should be made 5600 */ 5601 jsonDelete: function(config) 5602 { 5603 config.method = this.DELETE; 5604 this.jsonRequest(config); 5605 }, 5606 5607 /** 5608 * Takes an object and creates a decoded URL parameter string of it. 5609 * Note! Does not contain a '?' character in the beginning. 5610 * 5611 * @method request 5612 * @param obj 5613 * @param encode indicates whether the parameter values should be encoded or not 5614 * @private 5615 */ 5616 jsonToParamString: function(obj, encode) 5617 { 5618 var params = "", first = true, attr; 5619 5620 for (attr in obj) 5621 { 5622 if (obj.hasOwnProperty(attr)) 5623 { 5624 if (first) 5625 { 5626 first = false; 5627 } 5628 else 5629 { 5630 params += "&"; 5631 } 5632 5633 // Make sure no user input destroys the url 5634 if (encode) 5635 { 5636 params += encodeURIComponent(attr) + "=" + encodeURIComponent(obj[attr]); 5637 } 5638 else 5639 { 5640 params += attr + "=" + obj[attr]; 5641 } 5642 } 5643 } 5644 return params; 5645 }, 5646 5647 /** 5648 * Handles successful request triggered by the request() method. 5649 * If execScripts was requested, retrieve and execute the script(s). 5650 * Otherwise, fall through to the _successHandlerPostExec function immediately. 5651 * 5652 * @method request 5653 * @param serverResponse 5654 * @private 5655 */ 5656 _successHandler: function(serverResponse) 5657 { 5658 // Get the config that was used in the request() method 5659 var config = serverResponse.argument.config; 5660 5661 // Need to execute embedded "<script>" tags? 5662 if (config.execScripts) 5663 { 5664 var result = this.sanitizeMarkup(serverResponse.responseText); 5665 5666 // Set the responseText to only contain script cleaned markup 5667 serverResponse.responseText = result[0]; 5668 5669 // Use setTimeout to execute the script. Note scope will always be "window" 5670 var scripts = result[1]; 5671 if (YAHOO.lang.trim(scripts).length > 0) 5672 { 5673 window.setTimeout(scripts, 0); 5674 // Delay-call the PostExec function to continue response processing after the setTimeout above 5675 YAHOO.lang.later(0, this, this._successHandlerPostExec, serverResponse); 5676 } 5677 else 5678 { 5679 this._successHandlerPostExec(serverResponse); 5680 } 5681 } 5682 else 5683 { 5684 this._successHandlerPostExec(serverResponse); 5685 } 5686 }, 5687 5688 /** 5689 * Parses the markup and returns an array with clean markup (without script elements) and the actual script code 5690 * 5691 * @param markup 5692 * @return An array with cleaned markup at index 0 and the script code from the script leemnts at index 1 5693 */ 5694 sanitizeMarkup: function(markup) 5695 { 5696 var scripts = []; 5697 var script = null; 5698 var regexp = /<script[^>]*>([\s\S]*?)<\/script>/gi; 5699 while ((script = regexp.exec(markup))) 5700 { 5701 scripts.push(script[1]); 5702 } 5703 scripts = scripts.join("\n"); 5704 5705 // Remove the script from the responseText so it doesn't get executed twice 5706 return [markup.replace(regexp, ""), scripts]; 5707 }, 5708 5709 /** 5710 * Follow-up handler after successful request triggered by the request() method. 5711 * If execScripts was requested, this function continues after the scripts have been run. 5712 * If the responseContentType was set to json the response is decoded 5713 * for easy access to the success callback. 5714 * If no success callback is provided the successMessage is displayed 5715 * using Alfresco.util.PopupManager.displayMessage(). 5716 * If no successMessage is provided nothing happens. 5717 * 5718 * @method request 5719 * @param serverResponse 5720 * @private 5721 */ 5722 _successHandlerPostExec: function(serverResponse) 5723 { 5724 // Get the config that was used in the request() method 5725