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