/**
 * AS4ResourceBrowser
 * Resource Browser replaces the Resource Manager component
 *
 * @author Chris Blunt
 * @version 1.0
 */

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var AS4ResourceBrowser = Class.create({
	/**
	 * @type {integer} The currently displayed folder's id
	 */
	currentFolderId: null,
	
	/**
	 * @type {integer} The target folder's id
	 */
	targetFolderId: null,

	/**
	 * @type {Ajax.InPlaceEditor} Cached event handler for the folder name in place editor, allows this functionality to be toggled
	 */
	cachedFolderNameInPlaceEditor: null,

	/**
	 * @type {Sortable} Cached instance of the subfolder sortable
	 */
	cachedSubFolderSortable: null,

	/**
	 * @type {Function} Cached, bound copy of the onExpanderClicked method. We need this so we can accurately call Event.stopObserving(). See http://www.prototypejs.org/api/event/stopObserving for more info.
	 */
	cachedOnExpanderClicked: null,

	/**
	 * @type {Function} Cached, bound copy of the onLeafClicked method. We need this so we can accurately call Event.stopObserving(). See http://www.prototypejs.org/api/event/stopObserving for more info.
	 */
	cachedOnLeafClicked: null,

	/**
	 * @type {Element} Store a reference to the currently dragging SuperDraggable
	 */
	draggableClone: null,

	/**
	 * @type {Array} Store an array of all currently draggable resources
	 */
	draggableResources: $A([]),

	/**
	 * @type {Droppable} The linked folder resource droppable zone
	 */
	linkedResourceDroppable: null,

	/**
	 * @type {string} The current search terms for this resource browser. Default is an empty string.
	 */
	searchTerms: null,
	
	/**
	 * @type {Hash} The current advanced search terms for this resource browser. Default is an empty string.
	 */
	advSearchTerms: null,

	/**
	 * @type {Array} Optional array of resource types by which returned resources will be filtered. If empty, all applicable resource types will be returned.
	 */
	searchTypes: null,

	/**
	 * @type Optional, triggers a callback in widget options for multiple resource selectors.
	 */
	updateFlag: null,
		
	/**
	 * @type {string} The current sort key. Default is 'name'. 
	 */
	sortKey: null,

	/**
	 * @type {integer} The current page of resources to view. Default is 1.
	 */
	resourcesPage: null,

	/**
	 * @type {integer} The number of resources to display per-page. Default is 10.
	 */
	resourcesPerPage: null,

	/**
	 * @type {string} The current display icon mode for output. One of the AS4ResourceBrowser.DISPLAY_MODE constants
	 */
	iconDisplayMode: null,

	/**
	 * @type {boolean} Indicate if this resource browser is running as a 'media browser' object. If so, only public, approved resources will ever be returned
	 */
	isMediaBrowser: null,

	/**
	 * @type {Hash} [ajaxUpdateOptions] Options that will be passed to any ajaxUpdate calls
	 */
	ajaxUpdateOptions: null,
	
	/**
	 * @type {Hash} [objContextMenuTemplates] Defines various context menus.
	 */
	objContextMenuTemplates: null,

    selectedLinkedResourcePosition: null,
    
    movingResource: null,
    
    movingResourceJSON: null,
    
    secureUrl: null,
    
    installationId: null,

	// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
	// Functional Methods

	/**
	 * @constructor
	 */
	initialize: function()
	{
		// Make sure to override the default jQuery shortcut to avoid conflicting with Prototype.
		// This needs to be done DIRECTLY after including the jQuery library and before anything else (ideally, but seems to work here...).
		$j = jQuery.noConflict();
		
		// Define the available context menu types used on the page.
		this.objContextMenuTemplates = 
		{
			exampleMenu:'<ul id="exampleMenu" class="contextMenu">' +
						   '<li class="edit"><a href="#edit">Edit</a></li>' +
						   
						   '<li class="cut separator"><a href="#cut">Cut</a></li>' +
						   '<li class="copy"><a href="#copy">Copy</a></li>' +
						   '<li class="paste"><a href="#paste">Paste</a></li>' +
						   '<li class="delete"><a href="#delete">Delete</a></li>' +
						   
						   '<li class="quit separator"><a href="#quit">Quit</a></li>' +
						'</ul>',
			
			rootFolderMenu:'<ul id="rootFolderMenu" style="" class="contextMenu">' +
							   '<li class="open"><a href="#openFolder">Open folder</a></li>' +
						   '</ul>',
			
			userRootFolderMenu:'<ul id="userRootFolderMenu" style="width:200px" class="contextMenu">' +
							   '<li class="expandMenu"><a href="#expandMenu">Expand</a></li>' +
							   '<li class="collapseMenu"><a href="#collapseMenu">Collapse</a></li>' +	
							  
							   '<li class="separator"><a href="#newFolder">New folder</a></li>' +
							  
							   '<li class="open separator"><a href="#openFolder">Open folder</a></li>' +
							   '<li class="view"><a href="#view">View folder</a></li>' +
							   '<li class="edit"><a href="#edit">Edit folder layout</a></li>' +
							   '<li class="edit details"><a href="#editDetails">Edit folder details</a></li>' +
							   '<li class="upload"><a href="#upload">Upload resource to folder</a></li>' +
							   '<li class="link"><a href="#link">Link folder to resource</a></li>' +							   
							 
							   '<li class="paste separator"><a href="#paste">Paste</a></li>' +
							   
							   '<li class="hide separator"><a href="#hide">Hide</a></li>' +
							   '<li class="show"><a href="#show">Show</a></li>' +
						   '</ul>',
						   
			userFolderMenu:'<ul id="userFolderMenu" style="width:200px" class="contextMenu">' +
							   '<li class="expandMenu"><a href="#expandMenu">Expand</a></li>' +
							   '<li class="collapseMenu"><a href="#collapseMenu">Collapse</a></li>' +	
							  
							   '<li class="separator"><a href="#newFolder">New folder</a></li>' +
							  
							   '<li class="open separator"><a href="#openFolder">Open folder</a></li>' +
							   '<li class="view"><a href="#view">View folder</a></li>' +
							   '<li class="edit"><a href="#edit">Edit folder layout</a></li>' +
							   '<li class="edit details"><a href="#editDetails">Edit folder details</a></li>' +
							   '<li class="upload"><a href="#upload">Upload resource to folder</a></li>' +
							   '<li class="link"><a href="#link">Link folder to resource</a></li>' +
							   
							   '<li class="rename separator"><a href="#rename">Rename</a></li>' +
							   '<li class="delete"><a href="#delete">Delete</a></li>' +
							   
							   '<li class="cut separator"><a href="#cut">Cut</a></li>' +
							   '<li class="copy disabled"><a href="#copy">Copy</a></li>' +
							   '<li class="paste"><a href="#paste">Paste</a></li>' +
							   
							   '<li class="hide separator"><a href="#hide">Hide</a></li>' +
							   '<li class="show"><a href="#show">Show</a></li>' +
						   '</ul>',
						   
			contentPaneFolderMenu:'<ul id="contentPaneFolderMenu" style="width:200px" class="contextMenu">' +
							  
							   '<li class=""><a href="#newFolder">New folder</a></li>' +
							  
							   '<li class="open separator"><a href="#openFolder">Open folder</a></li>' +
							   '<li class="open"><a href="#viewFolder">View folder</a></li>' +
							   '<li class="edit"><a href="#edit">Edit folder layout</a></li>' +							   							   							   
							   '<li class="edit details"><a href="#editDetails">Edit folder details</a></li>' +							   							   							   
							   '<li class="upload"><a href="#upload">Upload resource to folder</a></li>' +
							   '<li class="link"><a href="#link">Link folder to resource</a></li>' +
							   
							   '<li class="rename separator"><a href="#renameFolder">Rename</a></li>' +
							   '<li class="delete"><a href="#deleteFolder">Delete</a></li>' +
							   
							   '<li class="cut separator"><a href="#cutFolder">Cut</a></li>' +
							   '<li class="copy disabled"><a href="#copyFolder">Copy</a></li>' +
							   '<li class="paste"><a href="#paste">Paste</a></li>' +
							   
							   '<li class="hide separator"><a href="#hideFolder">Hide</a></li>' +
							   '<li class="show"><a href="#showFolder">Show</a></li>' +
						   '</ul>',
						   
			contentPaneResourceMenu:'<ul id="contentPaneResourceMenu" style="width:200px" class="contextMenu">' +
							  
							   '<li class="preview"><a href="#previewResource">Preview resource</a></li>' +
							   '<li class="view"><a href="#viewResource">View resource</a></li>' +	
							   '<li class="edit"><a href="#editResource">Edit resource</a></li>' +
							   
							   '<li class="rename separator"><a href="#renameResource">Rename</a></li>' +
							   '<li class="delete"><a href="#deleteResource">Delete</a></li>' +
							   
							   '<li class="cut separator"><a href="#cutResource">Cut</a></li>' +
							   '<li class="copy disabled"><a href="#copyResource">Copy</a></li>' +							   
							   
							   '<li class="unapprove separator"><a href="#unapproveResource">Unapprove</a></li>' +
							   '<li class="approve"><a href="#approveResource">Approve</a></li>' +
							   
							   '<li class="hide separator"><a href="#hideResource">Hide</a></li>' +
							   '<li class="show"><a href="#showResource">Show</a></li>' +
						   '</ul>',
						   
			newResourceMenu:'<ul id="newResourceMenu" style="width:200px" class="contextMenu">' +
							  
							  '<li class=""><a href="#folder">Folder</a></li>' +
							  
							   '<li class="separator"><a href="#webPage">Web Page</a></li>' +
							   '<li class=""><a href="#webLink">Web Link</a></li>' +
							   '<li class=""><a href="#blog">Blog</a></li>' +
							   '<li class=""><a href="#slideshow">Slideshow</a></li>' +
							    '<li class=""><a href="#form">Form</a></li>' +
							   
							   '<li class="separator"><a href="#upload">Upload</a></li>' +
							   '<li class="separator"><a href="#secure-upload">Secure Upload</a></li>' +
							  
						   '</ul>',
						   
			batchActionsMenu:'<ul id="batchActionsMenu" style="width:200px" class="contextMenu">' +
							  
							   '<li class=""><a href="#hide">Hide</a></li>' +
							   '<li class=""><a href="#show">Show</a></li>' +
							   
							   '<li class="separator"><a href="#unapprove">Unapprove resources</a></li>' +
							   '<li class=""><a href="#approve">Approve resources</a></li>' +
							   
							   '<li class="separator"><a href="#delete">Delete</a></li>' +
							   
							   '<li class="cut separator"><a href="#cut">Cut</a></li>' +
							   '<li class="copy disabled"><a href="#copy">Copy</a></li>' +							   
							  
						   '</ul>'
		};
		
		this.iconDisplayMode = AS4ResourceBrowser.DISPLAY_MODE_LIST;

		// Cached event listeners
		this.cachedOnExpanderClicked = this.onExpanderClicked.bindAsEventListener(this);
		this.cachedOnLeafClicked = this.onLeafClicked.bindAsEventListener(this);

		this.prepareRootExpanders();

		this.resourcesPage = 1;
		this.resourcesPerPage = 10;
		this.totalPages = 0;
		this.sortKey = 'name';
		this.searchTerms = '';
		this.advSearchTerms = 
		{
			terms: '',
			createdBy:  '',
			resourceType:  '',
			dateAfter:  '',
			dateBefore:  '',
			searchAuthorized:  'both',
			restrictedGroups:  '',
			restrictedReason:  '',
			resourceToSearch:  'current_folder_only',
			searchSubfolders:  false
		}
		this.searchTypes = $A([]);
		this.isMediaBrowser = false;
		this.ajaxUpdateOptions = {showIndicator: false};
		
		this.strBrowser = BrowserDetect.browser;
		this.intVersion = BrowserDetect.version;
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Has array to store selected folder/resource ids and the actions associated with them.
		// This can be used as a reference at any point to check what items are selected and what
		// needs to be done with them.
		this.objSelectedItems = {};
		
		// Also store details of the previous selected item.
		this.objPreviousSelectedItem = {};
		
		// When cutting/copying, store the source folder id at time of cut/copy (otherwise if the user navigates to another folder before pasting, we cant work out this information).
		this.objCutSourceFolder = {};
		this.objCopySourceFolder = {};
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Create context menus for folder content pane header.
		// jQuery does not have an equivilent of Prototype bind() so for now just store reference of 'this' in '_this'
		var _this = this;
		$j('#newResourceButton').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'newResourceMenu', _this.handleContextMenuClicked_NewResourceButton, _this.handleContextMenuOpened_NewResourceButton, _this.handleContextMenuClosed_NewResourceButton, 'left');
		});
		$j('#batchActionsButton').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'batchActionsMenu', _this.handleContextMenuClicked_BatchActionsButton, _this.handleContextMenuOpened_BatchActionsButton, _this.handleContextMenuClosed_BatchActionsButton, 'left');
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
				
		var _this = this;
				
		// Hide the footer, and stop scrolling, just for this page.
		document.observe('dom:loaded', function(e)
		{
			$j('body').css({overflow:'hidden'});
			$j('#page_footer').css({display:'none'});
			this.resizeResourceBrowser();
		}.bind(this));
				
		// Resize resources manager to optimum as/when required.		
		$j(window).resize(function()
		{
			_this.resizeResourceBrowser();
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// Watch for keyboard events.
		
		// Store status of keys in object.
		this.objKeyboardKeyStatus = {};
		
		$j(document).keydown(function(event)
		{	
			switch (event.keyCode)
			{
				// Shift key pressed.
				case 16:
					_this.objKeyboardKeyStatus['shift'] = 'down';
				break;
				
				// Ctrl key pressed.
				case 17:					
					_this.objKeyboardKeyStatus['ctrl'] = 'down';
				break;
			}
		});
		$j(document).keyup(function(event)
		{
			switch (event.keyCode)
			{
				// Shift key released.
				case 16:
					_this.objKeyboardKeyStatus['shift'] = 'up';
				break;
				
				// Ctrl key release.
				case 17:					
					_this.objKeyboardKeyStatus['ctrl'] = 'up';
				break;
			}
		});
	},
	
	resizeResourceBrowser: function()
	{
		// Resize the folder pane/folder content area so they can use as much space as possible.
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		var intPrimaryColWidth = $j('#primary_column').width();
		var intSecondaryColWidth = $j('#secondary_column').width();
		
		// Do not allow resource browser to be resized < 1024 width.
		var intPageContainerWidth = (intBrowserViewportWidth <= 1024) ? 1024 : intBrowserViewportWidth;		
			
		// If we are currently running resolution lower than 1024 width, re-enable the browser scrollbars.
		if(intBrowserViewportWidth < 1024)
		{
			$j('html').css({overflow:'auto'});	// For IE7!
			$j('body').css({overflow:'auto'});  // For everything else!
			$j('#page_wrapper').css({overflow:'auto'});
			$j('#page_container').css({overflow:'auto'});
			$j('#page_content').css({overflow:'auto'});
		}
		else
		{
			$j('html').css({overflow:'hidden'});
			$j('body').css({overflow:'hidden'});
			$j('#page_wrapper').css({overflow:'hidden'});
			$j('#page_container').css({overflow:'hidden'});
			$j('#page_content').css({overflow:'hidden'});
		}
		
		// Make the resource manager fullscreen (by specifying !important next to the width we avoid being caught out by existing !important styles assgined).
		$j('body').attr('style', 'width:'+intPageContainerWidth+'px !important;padding:0px;margin:0px;');
		$j('#page_wrapper').attr('style', 'width:'+intPageContainerWidth+'px !important;padding:0px !important;margin:0px !important;');
		$j('#page_container').attr('style', 'width:'+intPageContainerWidth+'px !important;padding:0px !important;margin:0px !important;');
		$j('#page_content').attr('style', 'width:'+intPageContainerWidth+'px !important;padding:0px !important;margin:0px !important;');
		$j('#page_container .user_toolbar').attr('style', 'width:auto !important;');
		 		
		var intLooseAmount = 20;
		if(this.strBrowser == 'Explorer' && this.intVersion <= 6)
		{
			intLooseAmount += 15;
		}
		
		var intAvailbleWidthForSecondColumn = Math.floor((intPageContainerWidth - intPrimaryColWidth) - intLooseAmount);
		var intAvailbleHeight = Math.floor(intBrowserViewportHeight - 90);
				
		var intFilesListBorderWidth = 2;
		if(this.strBrowser == 'Explorer' && this.intVersion <= 6)
		{
			intFilesListBorderWidth = 0;
		}
		
		$j('#primary_column').css({height:intAvailbleHeight+'px'});
		$j('#secondary_column').css({width:intAvailbleWidthForSecondColumn+'px', height:intAvailbleHeight+'px'});
			
		$j('#files_list_container').css({width:(intAvailbleWidthForSecondColumn - intFilesListBorderWidth)+'px', height:(intAvailbleHeight-77)+'px'});
						
		// Resize any currently visible columns in IE6 (it can't handle the % based CSS widths defined in stylesheet).
		if(this.strBrowser == 'Explorer' && this.intVersion <= 6)
		{
			this.resizeVisibleFolderPaneColumns();
		}
	},
	resizeVisibleFolderPaneColumns: function()
	{		
		// First get the width of the 2nd column.
		var intSecondaryColWidth = $j('#secondary_column').width();
		
		// Define the widths for the columns as pecentages.
		var intCheckboxColWidthAsPer = 3;
		var intNameColWidthAsPer = 29;
		var intCreatorColWidthAsPer = 8;
		var intDateColWidthAsPer = 9;
		var intAuthorizedColWidthAsPer = 9;
		var intRestrictionColWidthAsPer = 9;
		var intTypeColWidthAsPer = 8;
		var intActionsColWidthAsPer = 18;
		
		// Define the widths for the columns as pixel values.
		var intCheckboxColWidth = Math.floor((intSecondaryColWidth / 100) * intCheckboxColWidthAsPer);
		var intNameColWidth = Math.floor((intSecondaryColWidth / 100) * intNameColWidthAsPer);
		var intCreatorColWidth = Math.floor((intSecondaryColWidth / 100) * intCreatorColWidthAsPer);
		var intDateColWidth = Math.floor((intSecondaryColWidth / 100) * intDateColWidthAsPer);
		var intAuthorizedColWidth = Math.floor((intSecondaryColWidth / 100) * intAuthorizedColWidthAsPer);
		var intRestrictionColWidth = Math.floor((intSecondaryColWidth / 100) * intRestrictionColWidthAsPer);
		var intTypeColWidth = Math.floor((intSecondaryColWidth / 100) * intTypeColWidthAsPer);
		var intActionsColWidth = Math.floor((intSecondaryColWidth / 100) * intActionsColWidthAsPer);
		
		// Update column headers/rows to absolute pixel values.
		$j('#files_list_column_header .cell column_header_checkbox').css({width:intCheckboxColWidth+'px'});
		$j('#files_list_column_header .cell column_header_name').css({width:intNameColWidth+'px'});
		$j('#files_list_column_header .cell column_header_creator').css({width:intCreatorColWidth+'px'});
		$j('#files_list_column_header .cell column_header_date').css({width:intDateColWidth+'px'});
		$j('#files_list_column_header .cell column_header_authorized').css({width:intAuthorizedColWidth+'px'});
		$j('#files_list_column_header .cell column_header_restriction').css({width:intRestrictionColWidth+'px'});
		$j('#files_list_column_header .cell column_header_type').css({width:intTypeColWidth+'px'});
		$j('#files_list_column_header .cell column_header_actions').css({width:intActionsColWidth+'px'});
		
		$j('.list_item .checkbox').css({width:intCheckboxColWidth+'px'});
		$j('.list_item .name').css({width:intNameColWidth+'px'});
		$j('.list_item .creator').css({width:intCreatorColWidth+'px'});
		$j('.list_item .date').css({width:intDateColWidth+'px'});
		$j('list_item .status_container').css({width:intAuthorizedColWidth+'px'});
		$j('.list_item .restriction').css({width:intRestrictionColWidth+'px'});
		$j('.list_item .type').css({width:intTypeColWidth+'px'});
		$j('.list_item .actions').css({width:intActionsColWidth+'px'});	
	},
	
	createPagingBar: function(intPageNumber, intResultsPerPage, intPageCount)
	{
		// Store the starting values.
		this.resourcesPage = intPageNumber;
		this.resourcesPerPage = intResultsPerPage;
		this.totalPages = intPageCount;
			
		this.updatePagingBar();
	},
	
	handlePagingBarClick: function(intPageClickedNumber, context)
	{
		var _this = context;
		
		_this.resourcesPage = intPageClickedNumber;
	
		_this.updatePagingBar();
		
		_this.fetchResources();
	},
	
	updatePagingBar: function()
	{
		// If we have more than 1 page, show the paging bar.
		if(this.totalPages > 1)
		{
			$j('#pagingBarContainer').show();
			$j("#pagingBar").pager({ pagenumber: this.resourcesPage, pagecount: this.totalPages, buttonClickCallback: this.handlePagingBarClick, context: this});
		}
		// Otherwise hide it!
		else
		{
			$j('#pagingBarContainer').hide();
		}
	},
	
	// Given an array of item ids, store them with corresponding type (folder,resource)/action (delete, hide, unhide etc).
	recordSelectedItems: function(arrItems, strItemType, strAction)
	{
		if(typeof(this.objSelectedItems[strAction]) == 'undefined')
		{
			this.objSelectedItems[strAction] = {};
		}
		
		this.objSelectedItems[strAction][strItemType] = arrItems;
	},
	// Remove the ids currently assigned to a given action.
	clearSelectedItems: function(strAction)
	{
		delete this.objSelectedItems[strAction];
	},
	// Return the ids currently assigned to a given action.
	getSelectedItems: function(strAction)
	{
		var arrItems = this.objSelectedItems[strAction];
		
		var objItems = {'folders':[],'resources':[]};
		
		if(arrItems)
		{
			var arrFolderItems = this.objSelectedItems[strAction]['folders'];
			var arrResourceItems = this.objSelectedItems[strAction]['resources'];
			
			if(!arrFolderItems)
			{
				objItems['folders'] = [];
			}
			else
			{
				objItems['folders'] = this.makeUniqueArray(arrFolderItems);
			}
			if(!arrResourceItems)
			{
				objItems['resources'] = [];
			}			
			else
			{
				objItems['resources'] = this.makeUniqueArray(arrResourceItems);
			}	
		}
		
		return objItems;		
	},
	
	// Return new array with duplicate values removed
	makeUniqueArray: function(arrArray)
	{
		var a = [];
		
	    var l = arrArray.length;
	    for(var i=0; i<l; i++) {
	      for(var j=i+1; j<l; j++) {
	        // If this[i] is found later in the array
	        if (arrArray[i] === arrArray[j])
	          j = ++i;
	      }
	      a.push(arrArray[i]);
	    }
	    return a;		
	},	
	
	setSortColumn: function(strColumnName)
	{
		this.sortKey = strColumnName;
	},
	
	/**
	 * Prepares the root tree expanders (all items, all channels, etc.) with events
	 */
	prepareRootExpanders: function()
	{
		// Prepare all static-loaded expanders
		$$('.expander').each(function(element) 
		{
			//alert("ELE"+element.id);
			$(element).stopObserving('click', this.cachedOnExpanderClicked);
			$(element).observe('click', this.cachedOnExpanderClicked);
		}.bind(this));

		// Prepare all static-loaded tree leaves
		$$('.leaf').each(function(element) {
			$(element).stopObserving('click', this.cachedOnLeafClicked);
			$(element).observe('click', this.cachedOnLeafClicked);
		}.bind(this));
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Context menus.
		
		// jQuery does not have an equivilent of Prototype bind() so for now just store reference of 'this' in '_this'
		var _this = this;
		
		// Remember, in jQuery, 'this' keyword always refers to the element being looped over ('this' keyword is overriden by jQuery during iteration) and
		// not the object context. Slightly silly.
		$j('#primary_column').find('.tree_node .label:not(.top_channel_node .label)').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'rootFolderMenu', _this.handleContextMenuClicked_RootFolder, _this.handleContextMenuOpened_RootFolder, _this.handleContextMenuClosed_RootFolder, 'right');
		});
		
		$j('#primary_column').find('.tree_node.top_channel_node .label').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'userRootFolderMenu', _this.handleContextMenuClicked_FolderListPane, _this.handleContextMenuOpened_FolderListPane, _this.handleContextMenuClosed_FolderListPane, 'right');
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  		
	},
	
	/**
	 * Intiates an AJAX fetch for a folder list from the server for the given folder. If a target node is set, that node will
	 * be updated with the retrieve content, otherwise the instance element this.foldersListElement node will be updated.
	 *
	 * options: {
	 *   [crumbs_to_root] : {string} Optional CSV separated list of channel ids that descend from the root channel to the current channel. Each leaf with a corresponding id will be displayed as open in the folder list
	 * }
	 *
	 * @param {string} path The location to load. If numeric, the corresponding folder id will be retrieved.
	 * @param {target} [target] The node element to update with the returned content. If undefined, $('folders_tree') will be used.
	 * @param {Hash} [options] Additional options that may be passed to this method as explained above. Defaults as above.
	 */
	fetchFolders: function(path, target, options, arrActivityTargets)
	{
		var target = (target ? target : $('folders_tree'));
		
		// Update the cursor style on the supplied list of elements to indicate that something is happening.
		var arrActivityTargets = (typeof(arrActivityTargets) == 'undefined') ? [] : arrActivityTargets;
		$j.each(arrActivityTargets, function(intKey, strVal)
		{
			$j(strVal).css({cursor:'wait'});
		});
		
		var url = "/resource_browser/fetch_folders";
		var options = Object.extend({}, options);

		var params = {
			renderMode: 'update',
			path: path,
			options: $H(options).toJSON()
		};

		AS4Shell.getInstance().ajaxUpdate(url, params, target, $A([this.onFetchFoldersSuccess.bind(this, arrActivityTargets)]), {showIndicator: false, updateCondition: function() {return false;}});
	},
	
	// Regenerate the required part of the folder pane on the left hand side of the screen.
	onFetchFoldersSuccess: function(arrActivityTargets, transport, target)
	{			
		// Update the target tree node.
		$(target).select('.tree_list').invoke('remove'); // Removes all child nodes of the current folder list.	
		$(target).insert(transport.responseText); // Add the new folder list.

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Add expander observers.
		// These will fire off request to populate child folder lists when clicked.
		$(target).select('.expander').each(function(element) {
			$(element).stopObserving('click', this.cachedOnExpanderClicked);
			$(element).observe('click', this.cachedOnExpanderClicked);
		}.bind(this));

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Add selector observers.
		// These will fire off request to populate child folder lists when clicked (as above), additionally they
		// will select and highlight the required folder.
		$(target).select('.label').each(function(element) {
			$(element).stopObserving('click', this.cachedOnLeafClicked);
			$(element).observe('click', this.cachedOnLeafClicked);
		}.bind(this));
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Make folders draggable/dropable (for organising folders/subfolders).
		
		// jQuery does not have an equivilent of Prototype bind() so for now just store reference of 'this' in '_this'
		var _this = this;
		
		$j(target).find('.tree_node').each(function(i)
		{
			// The id of the folder being dragged.
			var strId = $j(this).attr('id');
			var arrIdParts = strId.split('_');
			var intIdNumPart = arrIdParts[1];
			
			// The parent id of the folder being dragged.
			var strDraggableParentId = $j(this).parents('.tree_node:first').attr('id');
			var arrDraggableParentIdParts = strDraggableParentId.split('_');
			var intDraggableParentIdNumPart = arrDraggableParentIdParts[1];
			
			$j('#'+strId).draggable(
			{
				handle:'span.label, div.icon',
				opacity:0.7,
				helper:'clone',				
				appendTo: 'body',
				cursorAt: { left: 15 },
				
				// When the drag action starts, create the available dropzones for this draggable on the fly, depending
				// on current folder state. Make sure to respect folder heirarchy, eg. do not let a parent folder be
				// dropped onto one of its decendent folders!
				start: function(event, ui)
				{	
					// Find all '.tree_node' (excluding underneath the current folder, and the current folder itself) elements within the folder pane and make them droppable.
					// NOTE - have changed this to target the .label child elements for various reasons.
					$j('.tree_list .tree_node .label:not(#'+strId+' .label, #'+strId+' *), .tree_list .tree_node.top_channel_node .label:not(#'+strId+' .label, #'+strId+' *)').each(function(i)
					{
						// The id of the folder being dropped onto.
						var strDropableId = $j(this).parent().attr('id');
						var arrDropableIdParts = strDropableId.split('_');
						var intDropableIdNumPart = arrDropableIdParts[1];
						
						$j(this).droppable(
						{
							// Stop drop event recursion.
							greedy:true,
							tolerance:'pointer',
							
							over: function(event, ui)
							{								
								// Alter background colour to indicate that we are over this folder.
								$j(this).css({'background-color':'#EFEFEF'});
							},
							
							out: function(event, ui)
							{
								// Alter background colour to indicate that we are off this folder.
								$j(this).css({'background-color':''});
							},
							
							drop: function(event, ui)
							{
								// Remove background colour.
								$j(this).css({'background-color':''});
								
								// Move DOM nodes for the dragged folder underneath the dropped folder,
								// this gives instant feedback that something has moved...
								
								// This is the element we want to move. It should be an "<li id='folderleaf_[id]'>" node.
								var objMoveElement = $j('#'+strId).get(0);
								
								// When moving to the target folder, we need to make sure that there is a "<ul id='channelId'>" element.
								// If the destination folder has not been expanded, then this will not yet exist!
								var arrTargetULElement = $j(this).find('ul.tree_list:first');
								var objTargetULElement = (arrTargetULElement.length == 1) ? arrTargetULElement[0] : false;
								
								// If the target UL element does not exist yet, create it!
								if(!objTargetULElement)
								{
									$j(this).append('<ul id="channelId" class="tree_list"></ul>');
									objTargetULElement = $j(this).find('ul.tree_list:first').get(0);
								}
								
								// Move the element to its new parent.
								objTargetULElement.appendChild(objMoveElement);
								
								// Expand the new parent folder if it is not already...
								_this.expandCollapseFolder(intDropableIdNumPart, 'expand', false);
								
								// Then fire of AJAX request to update server.
								var url = '/resource_browser/move_folder';
								var params = {
									folder_id: intIdNumPart,
									source_folder_id: intDraggableParentIdNumPart,
									dest_folder_id: intDropableIdNumPart
								};
								AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving folder...' });
								
								// Finally, fire off AJAX request to re-populate this part of the folder list.	
								var intDestinationParentFolderId = _this.getFoldersParentFolderId(intDropableIdNumPart);
								_this.fetchFolders(intDestinationParentFolderId, $('folderleaf_'+intDestinationParentFolderId), {});
							}
						});
					});
								
					// Find all '#subfolders_list .folder.list_item' elements (excluding underneath the current folder and the current folder itself) within the folder content pane and make them droppable.						
					$j('#subfolders_list .folder.list_item:not(#folder_'+intIdNumPart+')').each(function(i)
					{
						// The id of the folder being dropped onto.
						var strDropableId = $j(this).attr('id');
						var arrDropableIdParts = strDropableId.split('_');
						var intDropableIdNumPart = arrDropableIdParts[1];
						
						$j(this).droppable(
						{
							// Stop drop event recursion.
							greedy:true,								
							
							over: function(event, ui)
							{									
								// Alter background colour to indicate that we are over this folder.
								$j(this).find('.cell.name').css({'background-color':'#EFEFEF'});
							},
							
							out: function(event, ui)
							{									
								// Alter background colour to indicate that we are off this folder.
								$j(this).find('.cell.name').css({'background-color':''});
							},
							
							drop: function(event, ui)
							{									
								// Remove background colour.
								$j(this).find('.cell.name').css({'background-color':''});
														
								// Remove the visible element from the current folder content pane (if it exists!).
								$j('#subfolders_list #folder_'+intIdNumPart).css({display:'none'});
																	
								// Then fire of AJAX request to update server.
								var url = '/resource_browser/move_folder';								
								var params = {
									folder_id: intIdNumPart,
									source_folder_id: intDraggableParentIdNumPart,
									dest_folder_id: intDropableIdNumPart
								};
								AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving folder...' });
								
								// Finally, fire off AJAX request to re-populate this part of the folder list.
								var intDestinationParentFolderId = _this.getFoldersParentFolderId(intDropableIdNumPart);
								_this.fetchFolders(intDestinationParentFolderId, $('folderleaf_'+intDestinationParentFolderId), {});
							}
						});
					});
				},
				
				// When drag stops, remove all the dropzones we created above!
				stop: function(event, ui)
				{				
					$j('.tree_list .tree_node .label:not(#'+strId+' .label, #'+strId+' *), .tree_list .tree_node.top_channel_node .label:not(#'+strId+' .label, #'+strId+' *)').each(function(i)
					{
						$j(this).droppable('destroy');
					});			
					
					$j('#subfolders_list .folder.list_item:not(#folder_'+intIdNumPart+')').each(function(i)
					{
						$j(this).droppable('destroy');
					});
					
					// Sync the content pane with the folder folder list.
					_this.syncContentPaneWithFolderList();					
				}
			});
		});
					
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Insert in-place editors for the folder titles.
		$j(target).find('.label').each(function(i)
		{
			// The id of the folder being renamed.
			var strParentId = $j(this).parents('.tree_node:first').attr('id');
			var arrParentIdParts = strParentId.split('_');
			var intParentIdNumPart = arrParentIdParts[1];	
			
			$j(this).editable('/resource_browser/rename_folder', 
			{
				event:'dblclick',
				id: 'defaultId',
				submitdata: { id: intParentIdNumPart },
				style   : 'display: inline;',
				width : 'none',
				onblur : 'submit',
				callback: function(value, settings)
				{
					// console.log(this);
			        // console.log(value);
			        // console.log(settings);
					
					// Sync the content pane with the folder folder list.
					_this.syncContentPaneWithFolderList();
				}
			});
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
		// Context menus.
		// Enable the context menu for each DOM node required.
				
		// Remember, in jQuery, 'this' keyword always refers to the element being looped over ('this' keyword is overriden by jQuery during iteration) and
		// not the object context. Slightly silly.
		$j(target).find('.label').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'userFolderMenu', _this.handleContextMenuClicked_FolderListPane, _this.handleContextMenuOpened_FolderListPane, _this.handleContextMenuClosed_FolderListPane, 'right');
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  	
		
		// Update the cursor style on the supplied list of elements to indicate that everything has completed.
		$j.each(arrActivityTargets, function(intKey, strVal)
		{
			$j(strVal).css({cursor:'default'});
		});	
	},
	
	/**
	 * Trigger an expansion of the clicked node.
	 *
	 * @param {Event} e
	 */
	onExpanderClicked: function(event)
	{		
		var parts = $(event.target).id.split('_');
		var folderId = parts[1];

		// Toggle the folder contents (but do not select/highlight it!).
		this.expandCollapseFolder(folderId);
		
		// Stop the event from bubbling and triggering the onLeafClicked() method.
		event.stop();
	},

	/**
	 * Trigger a selection of the clicked node. 
	 *
	 * @param {Event} event The supplied Event object
	 */
	onLeafClicked: function(event)
	{		
		
		/**
		if the folder clicked on is restricted so that only the name will show then we dont want to fetch resources.
		**/
		if($j(event.target).hasClass("view-only"))
			return false;
		
		
		var strId = $j(event.target).parents('.tree_node:first').attr('id');
		var parts = strId.split('_');
		var folderId = parts[1];
	
		// If current folder id is a number...
		var strNumExpression = /^(\s|\d)+$/;
		if(strNumExpression.test(folderId))
		{
			// User generated folder format id.
			strCurrentFolderId = 'folderleaf_'+folderId;
			
			// Expand the folder.
			this.expandCollapseFolder(folderId, 'expand');
		}
		else
		{
			// Pre-defined folder format id.
			strCurrentFolderId = strId;
		}	
		
		// Remove all other selected nodes
		$j('.selected').each(function(i) {
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});
		
		// Set this as the selected leaf
		$j('#'+strCurrentFolderId).addClass('selected');
		$j('#'+strCurrentFolderId).addClass('highlighted');

		// Begin an AJAX load of the folder's contents
		var options = ('{' + $j('#'+strCurrentFolderId).attr('rel') + '}').evalJSON();

		this.resourcesPage = 1;
		resourceBrowser.fetchResources(options.path);
		
		// Stop the event from bubbling and re-triggering the same method again.
		event.stop();
	},
	
	simulateLeafClicked: function(folderId)
	{
		// If current folder id is a number...
		var strNumExpression = /^(\s|\d)+$/;
		if(strNumExpression.test(folderId))
		{
			// User generated folder format id.
			strCurrentFolderId = 'folderleaf_'+folderId;
			
			// Expand the folder.
			this.expandCollapseFolder(folderId, 'expand');
		}
		else
		{
			// Pre-defined folder format id.
			strCurrentFolderId = folderId;
		}	
	
		// Remove all other selected nodes
		$j('.selected').each(function(i) {
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});
		
		// Set this as the selected leaf
		$j('#'+strCurrentFolderId).addClass('selected');
		$j('#'+strCurrentFolderId).addClass('highlighted');

		// Begin an AJAX load of the folder's contents
		var options = ('{' + $j('#'+strCurrentFolderId).attr('rel') + '}').evalJSON();		

		this.resourcesPage = 1;
		resourceBrowser.fetchResources(options.path);
	},
	
	// Have abstracted the actual logic involved for expanding/collapsing a folder so that it can be called by both the
	// folder title and the expander icon. Additionally, as well as a toggle feature, it can now be forced expanded/collapsed.
	// Args:
	// folderId: [folders id]
	// strAction: 'toggle', 'expand', 'collapse'
	expandCollapseFolder: function(folderId, strAction, blnRepopulateFolderContent)
	{
		//console.log(folderId);console.log(strAction);return false;
		
		// What action do we need to perform on this folder?
		var strAction = (typeof(strAction) == 'undefined') ? 'toggle' : strAction;
		
		// Do we need to repopulate the folder content?
		var blnRepopulateFolderContent = (typeof(blnRepopulateFolderContent) == 'undefined') ? true : blnRepopulateFolderContent;
		
		// Get handle on the folder leaf element.
		var objFolderLeafElement = $j('#folderleaf_'+folderId);
		
		// Get handle on the expander element.
		var objExpanderElement = $j('#expand_'+folderId);
		
		// Get a handle on the subfolders element.
		var objSubfolderElement = objFolderLeafElement.find('.tree_list:first');
				
		var arrActivityTargets = [objFolderLeafElement,objExpanderElement];
				
		switch(strAction)
		{
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Toggle the folder content.
			case 'toggle':			
				
				// If this folder is already expanded...collapse it.
				if(objExpanderElement.hasClass('open'))
				{
					// Alter appearance of expander element itself.
					objExpanderElement.removeClass('open');
					
					// Handle the actual subfolder content...
					// Ensure if the subfolder(s) we are about to hide/remove are droppable, we remove it's droppable status first.					
					objSubfolderElement.each(function(i)
					{
						Droppables.remove(this);
					});
					// Then remove the child elements.
					objSubfolderElement.remove();
					
				}
				// If this folder is collapsed...expand it.
				else
				{
					// Alter appearance of expander element itself.
					objExpanderElement.addClass('open');
					
					// Then fire of AJAX request to retrieve subfolders.
					if(blnRepopulateFolderContent)
					{
						resourceBrowser.fetchFolders(folderId, $('folderleaf_'+folderId), {}, arrActivityTargets);						
					}					
				}
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Force folder content to expand.
			case 'expand':
							
				// Alter appearance of expander element itself.
				objExpanderElement.addClass('open');
				
				// Then fire of AJAX request to retrieve subfolders.
				if(blnRepopulateFolderContent)
				{
					resourceBrowser.fetchFolders(folderId, $('folderleaf_'+folderId), {}, arrActivityTargets);
				}
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Force folder content to collapse.
			case 'collapse':
			
				// Alter appearance of expander element itself.
				objExpanderElement.removeClass('open');
				
				// Handle the actual subfolder content...
				// Ensure if the subfolder(s) we are about to hide/remove are droppable, we remove it's droppable status first.					
				objSubfolderElement.each(function(i)
				{
					Droppables.remove(this);
				});
				// Then remove the child elements.
				objSubfolderElement.remove();				
			
			break;
		}
	},
	
	// Given a context, element reference and menu type (menu types defined in constructor), this method will hook a context menu into the DOM.	
	// Now takes 3 callback methods for click/open/close events of menu.
	// Optionally takes strMouseButton argument ('left' or 'right').
	generateContextMenu: function(context, element, strMenuType, menuClickedCallback, menuOpenedCallback, menuClosedCallback, strMouseButton)
	{
		var _this = context;		
		var menuClickedCallback = (typeof(menuClickedCallback) != 'undefined') ? menuClickedCallback : function(){alert('No menu clicked callback specified!');};
		var menuOpenedCallback = (typeof(menuOpenedCallback) != 'undefined') ? menuOpenedCallback : function(){alert('No menu opened callback specified!');};
		var menuClosedCallback = (typeof(menuClosedCallback) != 'undefined') ? menuClosedCallback : function(){alert('No menu closed callback specified!');};
		var strMouseButton = (typeof(strMouseButton) != 'undefined') ? strMouseButton : 'right';
		
		// Add the element to the DOM ready for use (if does not already exist!).
		var arrExistingMenuType = $j('#'+strMenuType);
		if(arrExistingMenuType.length == 0)
		{
			$j('body').prepend(_this.objContextMenuTemplates[strMenuType]);			
		}
		
		$j(element).contextMenu(
		
			// Menu id.
			{menu:strMenuType},
			
			// Menu item click callback.
		    menuClickedCallback,
			
			// Menu opened callback.
			menuOpenedCallback,
			
			// Menu closed callback.
			menuClosedCallback,
			
			// Pass in the mouse button we want to initiate the menu.
			strMouseButton,
			
			// Pass through the object context.
			_this
		);
	},
		
	// Add a drop shadow to a given element, and remove any other active drop shadows.
	addDropShadow: function(element, objCSS)
	{
		var objCSS = (typeof(objCSS) != 'undefined') ? objCSS : {};		
	
		// First find and remove any other currently visible drop shadows.
		$j('.dropShadow').remove();
		
		// Then add the drop shadow.
		element.dropShadow(objCSS);
	},
		
	// Menu opened/closed callback handlers for the new resource menu (folder content header panel).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	// menu: The menu element.
	handleContextMenuOpened_NewResourceButton: function(el, pos, menu, menuType, context)
	{
		//console.log('Opened callback (NewResourceButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// In the event of the menu not being completely visible, re-position it.		
		_this.respositionElementToBeVisible(menuType);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// Add drop shadow.
		_this.addDropShadow(menu);
	},
	handleContextMenuClosed_NewResourceButton: function(el, pos, menu, menuType, context)
	{
		//console.log('Closed callback (NewResourceButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		// Remove drop shadow.
		$j(menu).removeShadow();
	},
	// Menu click callback handlers for the new resource menu (folder content header panel).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	handleContextMenuClicked_NewResourceButton: function(action, el, pos, menu, menuType, context)
	{
		//console.log('Clicked callback (NewResourceButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		switch(action)
		{
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create web page.
			case 'webPage':
			
				_this.createResource('web_page');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create web link.
			case 'webLink':
			
				_this.createResource('web_link');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create blog.
			case 'blog':
			
				_this.createResource('blog');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create slideshow.
			case 'slideshow':
			
				_this.createResource('slideshow');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create form.
			case 'form':
			
				_this.createResource('form');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Create folder.
			case 'folder':
			
				_this.showCreateSubfolderLightbox();
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// New upload.
			case 'upload':
			
				_this.startUpload(_this.currentFolderId);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// New upload.
			case 'secure-upload':
			
				_this.startSecureUpload(_this.currentFolderId);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Default
			default:
			
				alert("Context menu action: '" + action + "' has not yet been implemented!");
			
			break;
		}		
	},
		
	// Menu opened/closed callback handlers for the batch actions menu (folder content header panel).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	// menu: The menu element.
	handleContextMenuOpened_BatchActionsButton: function(el, pos, menu, menuType, context)
	{
		//console.log('Opened callback (BatchActionsButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// In the event of the menu not being completely visible, re-position it.		
		_this.respositionElementToBeVisible(menuType);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// Add drop shadow.
		_this.addDropShadow(menu);
	},
	handleContextMenuClosed_BatchActionsButton: function(el, pos, menu, menuType, context)
	{
		//console.log('Closed callback (BatchActionsButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		// Remove drop shadow.
		$j(menu).removeShadow();
	},
	// Menu click callback handlers for the batch actions menu (folder content header panel).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	handleContextMenuClicked_BatchActionsButton: function(action, el, pos, menu, menuType, context)
	{
		//console.log('Clicked callback (BatchActionsButton)!');console.log(arguments);return false;
		
		var _this = context;
		
		switch(action)
		{			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Hide folders/resources.
			case 'hide':
			
				_this.toggleHideSubfolders(true);
				_this.toggleHideResources(true);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Show folders/resources.
			case 'show':
			
				_this.toggleHideSubfolders(false);
				_this.toggleHideResources(false);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Unapprove resources.
			case 'unapprove':
			
				_this.unapproveResources();
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Approve resources.
			case 'approve':
			
				_this.approveResources();
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Delete folders/resources.
			case 'delete':
			
				_this.resourceAttachedToWidget();
			
			break;
		
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Cut a folders/resources.
			case 'cut':
			
				_this.cutFolders(_this.currentFolderId);
				_this.cutResources(_this.currentFolderId);
			
			break;		
		
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Default
			default:
			
				alert("Context menu action: '" + action + "' has not yet been implemented!");
			
			break;
		}		
	},
		
	// Menu opened/closed callback handlers for the root folders menu (left hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	// menu: The menu element.
	handleContextMenuOpened_RootFolder: function(el, pos, menu, menuType, context)
	{
		//console.log('Opened callback (BatchActionsButton)!');console.log(arguments);return false;		
	
		var _this = context;
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// In the event of the menu not being completely visible, re-position it.		
		_this.respositionElementToBeVisible(menuType);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// Add drop shadow.
		_this.addDropShadow(menu);
		
		// Remove all other highlighted classes.
		$j('.selected').each(function(i)
		{
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});
		
		// Enable highlighted classes on ourselves.
		$j(el).parents('.tree_node:first').each(function(i)
		{
			$j(this).addClass('selected');
			$j(this).addClass('highlighted');
		});
	},
	handleContextMenuClosed_RootFolder: function(el, pos, menu, menuType, context)
	{
		//console.log('Closed callback (BatchActionsButton)!');console.log(arguments);return false;
	
		var _this = context;		
		
		// Remove drop shadow.
		$j(menu).removeShadow();
		
		// Remove highlighted classes from ourselves.
		$j(el).parents('.tree_node:first').each(function(i)
		{
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});					
		
		// Re-enable the classes on the current selected folder.
		var strCurrentFolderId = '';
		
		// If current folder id is a number...
		var strNumExpression = /^(\s|\d)+$/;
		if(strNumExpression.test(_this.currentFolderId))
		{
			// User generated folder format id.
			strCurrentFolderId = 'folderleaf_'+_this.currentFolderId;
		}
		else
		{
			// Pre-defined folder format id.
			strCurrentFolderId = _this.currentFolderId+'_node';
		}
		
		$j('#'+strCurrentFolderId).addClass('selected');
		$j('#'+strCurrentFolderId).addClass('highlighted');
	},
	// Menu click callback handlers for the root folders menu (left hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	handleContextMenuClicked_RootFolder: function(action, el, pos, menu, menuType, context)
	{
		//console.log('Clicked callback (BatchActionsButton)!');console.log(arguments);return false;
			
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).parent().attr('id');		
		
		switch(action)
		{
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Open folder.
			case 'openFolder':
			
				_this.simulateLeafClicked(strId);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Default
			default:
			
				alert("Context menu action: '" + action + "' has not yet been implemented!");
			
			break;			
		}	
			
	},
		
	// Menu opened/closed callback handlers for folder list pane menus (left hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	// menu: The menu element.
	handleContextMenuOpened_FolderListPane: function(el, pos, menu, menuType, context)
	{
		//console.log('Opened callback (FolderListPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).parent().attr('id');
		var arrIdParts = strId.split('_');
		var intIdNumPart = arrIdParts[1];
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// In the event of the menu not being completely visible, re-position it.		
		_this.respositionElementToBeVisible(menuType);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// Add drop shadow.
		_this.addDropShadow(menu);
		
		// Remove all other highlighted classes.
		$j('.selected').each(function(i)
		{
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});
		
		// Enable highlighted classes on ourselves.
		$j(el).parents('.tree_node:first').each(function(i)
		{
			$j(this).addClass('selected');
			$j(this).addClass('highlighted');
		});
		
		// Also highlight folder/resource in the content pane.
		$j("#subfolders_list #folder_"+intIdNumPart).css({'background-color':'#EFEFEF'});
		$j("#resources_list #resourceitem_"+intIdNumPart).css({'background-color':'#EFEFEF'});	
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// If any items in the menu are context sensitive (eg paste) then handle them here.
		
		// Disable paste option if nothing in clipboard (cut or copied).		
		var objCurrentCutItems = _this.getSelectedItems('cut');
		var arrCurrentCutFolderItems = objCurrentCutItems['folders'];
		var arrCurrentCutResourceItems = objCurrentCutItems['resources'];
		if(arrCurrentCutFolderItems.length == 0 && arrCurrentCutResourceItems.length == 0)
		{			
			menu.disableContextMenuItems('#paste');
		}
		else
		{			
			menu.enableContextMenuItems('#paste');
		}				
	},
	handleContextMenuClosed_FolderListPane: function(el, pos, menu, menuType, context)
	{
		//console.log('Closed callback (FolderListPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).parent().attr('id');
		var arrIdParts = strId.split('_')
		var intIdNumPart = arrIdParts[1];
		
		// Remove drop shadow.
		$j(menu).removeShadow();
		
		// Remove highlighted classes from ourselves.
		$j(el).parents('.tree_node:first').each(function(i)
		{
			$j(this).removeClass('selected');
			$j(this).removeClass('highlighted');
		});					
		
		// Re-enable the classes on the current selected folder.
		var strCurrentFolderId = '';
		
		// If current folder id is a number...
		var strNumExpression = /^(\s|\d)+$/;
		if(strNumExpression.test(_this.currentFolderId))
		{
			// User generated folder format id.
			strCurrentFolderId = 'folderleaf_'+_this.currentFolderId;
		}
		else
		{
			// Pre-defined folder format id.
			strCurrentFolderId = _this.currentFolderId+'_node';
		}
		
		$j('#'+strCurrentFolderId).addClass('selected');
		$j('#'+strCurrentFolderId).addClass('highlighted');	
		
		// Also unhighlight folder/resource in the content pane.
		$j("#subfolders_list #folder_"+intIdNumPart).css({'background-color':''});
		$j("#resources_list #resourceitem_"+intIdNumPart).css({'background-color':''});
	},
	// Menu click callback handler for folder list pane menus (left hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	handleContextMenuClicked_FolderListPane: function(action, el, pos, menu, menuType, context)
	{
		//console.log('Clicked callback (FolderListPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).parent().attr('id');
		var arrIdParts = strId.split('_')
		var intIdNumPart = arrIdParts[1];
		
		switch(action)
		{
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Expand a folder.
			case 'expandMenu':
			
				_this.expandCollapseFolder(intIdNumPart, 'expand');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Collapse a folder.
			case 'collapseMenu':
			
				_this.expandCollapseFolder(intIdNumPart, 'collapse');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// New folder.
			case 'newFolder':
			
				_this.targetFolderId = intIdNumPart;
				_this.showCreateSubfolderLightbox();
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Edit folder layout.
			case 'edit':
			
				redirectLocation('/channel_control_panel/channel_homepage/id/'+intIdNumPart);
			
			break;
		
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Edit folder details.
			case 'editDetails':
			
				redirectLocation('/folder/edit/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Open folder.
			case 'openFolder':
			
				_this.simulateLeafClicked(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// View folder.
			case 'view':
			
				redirectLocation('/channel/index/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Link resource to folder.
			case 'link':
							
				_this.showLinkFolderToResourceLightbox(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Upload to folder.
			case 'upload':
			
				_this.startUpload(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Rename a folder.
			case 'rename':
			
				// Trigger click event on the existing item name to initiate the inplace editor.				
				$j('#folderleaf_'+intIdNumPart).find('.label').trigger('dblclick');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Delete a folder.
			case 'delete':
			
				_this.resourceAttachedToWidget({arrFolders:[intIdNumPart],arrResources:[]});
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Cut a folder.
			case 'cut':
			
				// Get the parent folder of this one.				
				var intParentFolderId = _this.getFoldersParentFolderId(intIdNumPart);
			
				_this.cutFolders(intParentFolderId, {arrFolders:[intIdNumPart],arrResources:[]});
				_this.cutResources(intParentFolderId, {arrFolders:[],arrResources:[]}); // Call cutResources to clear any currenty cut resources.
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Paste items.
			case 'paste':
				
				_this.pasteItems(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Hide a folder.
			case 'hide':
			
				_this.toggleFolderVisibility(intIdNumPart, true);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Show a folder.
			case 'show':
			
				_this.toggleFolderVisibility(intIdNumPart, false);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Default
			default:
			
				alert("Context menu action: '" + action + "' has not yet been implemented!");
			
			break;			
		}		
	},	
	
	// Menu opened/closed callback handlers for folder content pane menus (right hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	// menu: The menu element.
	handleContextMenuOpened_ContentPane: function(el, pos, menu, menuType, context)
	{
		//console.log('Opened callback (ContentPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).attr('id');
		var arrIdParts = strId.split('_')
		var intIdNumPart = arrIdParts[1];
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// In the event of the menu not being completely visible, re-position it.		
		_this.respositionElementToBeVisible(menuType);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// Add drop shadow.
		_this.addDropShadow(menu);
		
		// Highlight ourselves.
		$j("#subfolders_list #folder_"+intIdNumPart).css({'background-color':'#EFEFEF'});
		$j("#resources_list #resourceitem_"+intIdNumPart).css({'background-color':'#EFEFEF'});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		
		// If any items in the menu are context sensitive (eg paste) then handle them here.
		
		// Disable paste option if nothing in clipboard (cut or copied).		
		var objCurrentCutItems = _this.getSelectedItems('cut');
		var arrCurrentCutFolderItems = objCurrentCutItems['folders'];
		var arrCurrentCutResourceItems = objCurrentCutItems['resources'];
		if(arrCurrentCutFolderItems.length == 0 && arrCurrentCutResourceItems.length == 0)
		{			
			menu.disableContextMenuItems('#paste');
		}
		else
		{			
			menu.enableContextMenuItems('#paste');
		}		
	},
	handleContextMenuClosed_ContentPane: function(el, pos, menu, menuType, context)
	{
		//console.log('Closed callback (ContentPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).attr('id');
		var arrIdParts = strId.split('_')
		var intIdNumPart = arrIdParts[1];
		
		// Remove drop shadow.
		$j(menu).removeShadow();
		
		// Unhighlight ourselves.
		$j("#subfolders_list #folder_"+intIdNumPart).css({'background-color':''});
		$j("#resources_list #resourceitem_"+intIdNumPart).css({'background-color':''});
	},
	// Menu click callback handler for folder content pane menus (right hand column).
	// Args:
	// action: A string representing the item that was clicked (based on the href value assigned to the menu items <a> element.
	// el: The element that invoked the menu.
	// pos: The X/Y screen pos that was clicked.
	handleContextMenuClicked_ContentPane: function(action, el, pos, menu, menuType, context)
	{
		//console.log('Clicked callback (ContentPane)!');console.log(arguments);return false;
		
		var _this = context;
		
		// The id of the folder/resource being clicked.
		var strId = $j(el).attr('id');
		var arrIdParts = strId.split('_')
		var intIdNumPart = arrIdParts[1];
		
		switch(action)
		{				
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// New folder.
			case 'newFolder':
			
				_this.targetFolderId = intIdNumPart;
				_this.showCreateSubfolderLightbox();
			
			break;
		
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Edit folder layout.
			case 'edit':
			
				redirectLocation('/channel_control_panel/channel_homepage/id/'+intIdNumPart);
			
			break;
		
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Edit folder details.
			case 'editDetails':
			
				redirectLocation('/folder/edit/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Edit resource.
			case 'editResource':
								  
				redirectLocation('/resource_editor/edit/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Open folder.
			case 'openFolder':
			
				_this.simulateLeafClicked(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// View folder.
			case 'viewFolder':
			
				redirectLocation('/channel/index/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Preview resource.
			case 'previewResource':
			
				_this.showPreviewResourceLightbox(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// View resource.
			case 'viewResource':
			
				redirectLocation('/channel/view_resource/id/'+intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Link resource to folder.
			case 'link':
			
				_this.showLinkFolderToResourceLightbox(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Upload to folder.
			case 'upload':
			
				_this.startUpload(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Rename a folder.
			case 'renameFolder':
			
				// Trigger click event on the existing item name to initiate the inplace editor.				
				$j('#subfolders_list #folder_'+intIdNumPart+' .cell.name span.folder').trigger('dblclick');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Rename a resources.
			case 'renameResource':
			
				// Trigger click event on the existing item name to initiate the inplace editor.				
				$j('#resources_list #resourceitem_'+intIdNumPart+' .cell.name span.resource').trigger('dblclick');
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Delete a folder.
			case 'deleteFolder':
			
				_this.resourceAttachedToWidget({arrFolders:[intIdNumPart],arrResources:[]});
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Delete a resource.
			case 'deleteResource':
			
				_this.resourceAttachedToWidget({arrFolders:[],arrResources:[intIdNumPart]});
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Cut a folder.
			case 'cutFolder':
			
				_this.cutFolders(_this.currentFolderId, {arrFolders:[intIdNumPart],arrResources:[]});
				_this.cutResources(_this.currentFolderId, {arrFolders:[],arrResources:[]}); // Call cutResources to clear any currenty cut resources.
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Cut a resource.
			case 'cutResource':
			
				_this.cutFolders(_this.currentFolderId, {arrFolders:[],arrResources:[]}); // Call cutFolders to clear any currenty cut folders.
				_this.cutResources(_this.currentFolderId, {arrFolders:[],arrResources:[intIdNumPart]});				
				
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Paste items.
			case 'paste':
				
				_this.pasteItems(intIdNumPart);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Hide a folder.
			case 'hideFolder':
			
				_this.toggleFolderVisibility(intIdNumPart, true);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Show a folder.
			case 'showFolder':
			
				_this.toggleFolderVisibility(intIdNumPart, false);
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Unapprove a resource.
			case 'unapproveResource':
							
				_this.unapproveResources({arrFolders:[],arrResources:[intIdNumPart]})
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Approve a resource.
			case 'approveResource':
			
				_this.approveResources({arrFolders:[],arrResources:[intIdNumPart]})
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Hide a resource.
			case 'hideResource':
							
				_this.toggleHideResources(true, [intIdNumPart])
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Show a resource.
			case 'showResource':
			
				_this.toggleHideResources(false, [intIdNumPart])
			
			break;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - 
			// Default
			default:
			
				alert("Context menu action: '" + action + "' has not yet been implemented!");
			
			break;
		}
	},
		
	/**
	 * Intiates an AJAX fetch for a file list from the server for the given folder. If a target node is set, that node will
	 * be updated with the retrieve content, otherwise the instance element this.foldersListElement node will be updated.
	 *
	 * @param {string} [path] The location to load. If numeric, the corresponding folder id will be retrieved. If null, the currently displayed path will be used.
	 * @param {target} [target] The node element to update with the returned content. If undefined, $('folders_tree') will be used.
	 */
	fetchResources: function(path, target, blnInvisibleUpdate)
	{		
		var blnInvisibleUpdate = (typeof(blnInvisibleUpdate) != 'undefined') ? blnInvisibleUpdate : false;
        var path = (path ? path : this.currentFolderId);
		var target = (target ? target : $('files_list'));

                if (target==$('files_list'))
                    {
                    this.onBeginFetchResources(blnInvisibleUpdate);
                    }
                    else if (target==$('files_list_thumbnail'))
                    {
                     this.onBeginFetchResources_thumbnail(blnInvisibleUpdate);
                    }

        if (target==$('files_list_thumbnail'))
            {
            var iconDisMode="resource_editor_thumbnail";
            }
        else
            var iconDisMode=this.iconDisplayMode;

		var url = "/resource_browser/fetch_resources";
		var params = {
			renderMode: 'update',
			path: path,
			terms: this.searchTerms,
			advTerms: $H(this.advSearchTerms).toJSON(),
			filters: this.searchTypes.toJSON(),
			sort_key: this.sortKey,
			sort_direction: 'ascending',
			resources_page: this.resourcesPage,
			resources_per_page: this.resourcesPerPage,
			icon_display_mode: iconDisMode,
            position:this.selectedLinkedResourcePosition,
			is_media_browser: this.isMediaBrowser
		};

		// Are sub-sections visible?
		if($('subfolders_list'))
			params.subfolders_collapsed = !$('subfolders_list').visible();

		if($('resources_list'))
			params.resources_collapsed = !$('resources_list').visible();

		AS4Shell.getInstance().ajaxUpdate(url, params, target, $A([this.onFetchResourcesSuccess.bind(this)]), this.ajaxUpdateOptions);
	},
	
	onFetchResourcesSuccess: function(transport, target)
	{
		// Ignore exceptions
		if(transport.headerJSON && transport.headerJSON.exception)
			return;
 
 		// jQuery does not have an equivilent of Prototype bind() so for now just store reference of 'this' in '_this'
		var _this = this;
  
 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Prepare subfolder cells for editing, dragging, sorting etc.
		this.prepareSubfolderCells();

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Clear list of selected rows from previous folder content.
		this.clearSelectedItems('selected');

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Make resources draggable/dropable (for organising resources into folders/subfolders).
		// Allow them to be dropped onto folders at the top of the list, as well as over to the folder list pane
		// on the left.
		$j("#resources_list").find('.resource.list_item .draggable_elements').each(function(i)
		{
			// The id of the resource being dragged.
			var strId = $j(this).attr('id');
			var arrIdParts = strId.split('_')
			var intIdNumPart = arrIdParts[1];
			
			// The parent id of the resource being dragged.
			var intDraggableParentIdNumPart = _this.currentFolderId;
			
			$j('#'+strId).draggable(
			{
				handle:'cell.name',
				opacity:0.7,
				helper:'clone',					
				appendTo: 'body',	
				cursorAt: { left: 5 },					
				
				// When the drag action starts, create the available dropzones for this draggable on the fly, depending
				// on current folder state.
				start: function(event, ui)
				{					
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
					// Handle dragging of multiple items!
					// If the user has selected multiple rows and starts dragging, we can drag them all at once!
					var strSelectedRowsHTML = _this.getSelectedRowsHTML(intIdNumPart);
					
					// Add the element to the DOM ready for use.
					if(strSelectedRowsHTML != '')
					{
						$j('body').prepend(strSelectedRowsHTML);								
					}
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
				
					// Find all '.tree_node' elements within the folder pane and make them droppable.	
					// NOTE - have changed this to target the .label child elements for various reasons.					
					$j('.tree_list.root_node .tree_node .label').each(function(i)
					{						
						// The id of the folder being dropped onto.
						var strDropableId = $j(this).parent().attr('id');
						var arrDropableIdParts = strDropableId.split('_')
						var intDropableIdNumPart = arrDropableIdParts[1];
						
						$j(this).droppable(
						{
							// Stop drop event recursion.
							greedy:true,
							tolerance:'pointer',
							
							over: function(event, ui)
							{
								// Alter background colour to indicate that we are over this folder.
								$j(this).css({'background-color':'#EFEFEF'});
							},
							
							out: function(event, ui)
							{
								// Alter background colour to indicate that we are off this folder.
								$j(this).css({'background-color':''});
							},
							
							drop: function(event, ui)
							{
								// Remove background colour.
								$j(this).css({'background-color':''});									
													
								// Remove the visible element from the current folder content pane.
								_this.movingResource = $j('#resources_list #resourceitem_'+intIdNumPart);
								//$j('#resources_list #resourceitem_'+intIdNumPart).css({display:'none'});
								
								// Then fire of AJAX request to update server.
								var url = '/resource_browser/move_resource';
								var params = {
									resource_id: intIdNumPart,
									source_folder_id: intDraggableParentIdNumPart,
									dest_folder_id: intDropableIdNumPart
								};
								
								AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([_this.onMoveResourceSuccess.bind(_this)]), { message: 'Moving resource...' });
								
								// Move any additional items that were multidragged!
								_this.moveAdditionalSelectedItems(intDraggableParentIdNumPart, intDropableIdNumPart);
							}
						});
					});
					
					// Find all '#subfolders_list .folder.list_item' elements within the folder content pane and make them droppable.						
					$j('#subfolders_list .folder.list_item').each(function(i)
					{	
						// The id of the folder being dropped onto.
						var strDropableId = $j(this).attr('id');
						var arrDropableIdParts = strDropableId.split('_')
						var intDropableIdNumPart = arrDropableIdParts[1];
						
						$j(this).droppable(
						{
							// Stop drop event recursion.
							greedy:true,								
							
							over: function(event, ui)
							{									
								// Alter background colour to indicate that we are over this folder.
								$j(this).find('.cell.name').css({'background-color':'#EFEFEF'});									
							},
							
							out: function(event, ui)
							{									
								// Alter background colour to indicate that we are off this folder.
								$j(this).find('.cell.name').css({'background-color':''});
							},
							
							drop: function(event, ui)
							{									
								// Remove background colour.
								$j(this).find('.cell.name').css({'background-color':''});
														
								// Remove the visible element from the current folder content pane.
								$j('#resources_list #resourceitem_'+intIdNumPart).css({display:'none'});
																	
								// Then fire of AJAX request to update server.
								var url = '/resource_browser/move_resource';
								var params = {
									resource_id: intIdNumPart,
									source_folder_id: intDraggableParentIdNumPart,
									dest_folder_id: intDropableIdNumPart
								};
								
								AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving resource...' });
								
								// Move any additional items that were multidragged!
								_this.moveAdditionalSelectedItems(intDraggableParentIdNumPart, intDropableIdNumPart);
							}
						});
					});	
					
				},
				
				// During drag...
				drag: function(event, ui)
				{
					// Then position it underneath the existing drag item.
					var intDraggingItemHeight = $j('.ui-draggable-dragging').height();										
					
					var intNewX = ui.offset.left;
					var intNewY = ui.offset.top + intDraggingItemHeight ;
					
					$j('#multiDragHelper_'+intIdNumPart).css({display:'', opacity:'0.7', left:intNewX+'px', top:intNewY+'px'});			
				},
				
				// When drag stops, remove all the dropzones we created above!
				stop: function(event, ui)
				{					
					$j('.tree_list.root_node .tree_node .label').each(function(i)
					{
						$j(this).droppable('destroy');
					});
					
					$j('#subfolders_list .folder.list_item').each(function(i)
					{
						$j(this).droppable('destroy');
					});
					
					// Remove multiDragHelper from DOM.					
					$j('.multiDragHelper').remove();
				}
			});
		});
			
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Create context menus for the actions button at the end of each row.
		$j("#resources_list").find('.resource.list_item .cell.actions .button').each(function(i)
		{
			_this.generateContextMenu(_this, this, 'contentPaneResourceMenu', _this.handleContextMenuClicked_ContentPane, _this.handleContextMenuOpened_ContentPane, _this.handleContextMenuClosed_ContentPane, 'left');
		});

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Create context menus for the right click on each row.
		$j("#resources_list").find('.resource.list_item').each(function(i)
		{		
			_this.generateContextMenu(_this, this, 'contentPaneResourceMenu', _this.handleContextMenuClicked_ContentPane, _this.handleContextMenuOpened_ContentPane, _this.handleContextMenuClosed_ContentPane, 'right');
		});

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Observe approval markers.
		$$('.managable .cell.status_container .status').each(function(element) {
			$(element).observe('click', function(event) {
				// Trigger an approval status update
				var url = "/resource_browser/toggle_resource_approval";
				var params = {
					id: $(event.target).up('.list_item').id.split('_')[1]
				}

				// Change the approval icon to a spinner
				$(event.target).addClassName('ajax_updating');

				AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([this.onToggleResourceApprovalComplete.bind(this)]), { method: 'post', showIndicator: false });
			}.bind(this));
		}.bind(this));
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Insert in-place editors for the resource titles.
		$j('.managable .cell.name span.resource').each(function(i)
		{
			// The id of the folder being renamed.
			var strId = $j(this).parents('.list_item:first').attr('id');
			var arrIdParts = strId.split('_');
			var intIdNumPart = arrIdParts[1];	
			
			$j(this).editable('/resource_browser/rename_resource', 
			{
				event:'dblclick',
				id: 'defaultId',
				submitdata: { id: intIdNumPart },
				style   : 'display: inline;',
				width : 'none',
				onblur : 'submit'
			});
		});

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Make resource icons/names double clickable for preview.		
		$j("#resources_list").find('.resource.list_item .draggable_elements .cell.name').each(function(i)
		{
			// The id of the folder being renamed.
			var strId = $j(this).parents('.list_item:first').attr('id');
			var arrIdParts = strId.split('_');
			var intIdNumPart = arrIdParts[1];	

			$j(this).dblclick(function ()
			{
				_this.showPreviewResourceLightbox(intIdNumPart);
		    });
		});
	
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Make each row selectable by single click. Enabable multi-row select
		// if Ctrl+click or Shift+click event is triggered.
		$j("#resources_list").find('.resource.list_item').each(function(i)
		{	
			// The id of the resource being clicked.
			var strId = $j(this).attr('id');
			var arrIdParts = strId.split('_')
			var intIdNumPart = arrIdParts[1];
			
			// The parent id of the resource being clicked.
			var intParentIdNumPart = _this.currentFolderId;
		
			$j(this).click(function (event)
			{
				_this.handleRowClicked(intIdNumPart, 'resource');
				event.stopImmediatePropagation();
		    });
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Make each row selectable by checkbox. Enabable multi-row select
		// if this is clicked by default (Ctrl + click not required).
		$j("#resources_list").find('.resource.list_item .include_in_select_all').each(function(i)
		{	
			// The id of the resource being clicked.
			var strId = $j(this).attr('id');
			var arrIdParts = strId.split('_')
			var intIdNumPart = arrIdParts[1];
			
			// The parent id of the resource being clicked.
			var intParentIdNumPart = _this.currentFolderId;
					
			$j(this).click(function (event)
			{
				_this.handleRowClicked(intIdNumPart, 'resource', 'append');
				event.stopImmediatePropagation();
		    });			   
		});
				
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Update the folder properties panel

		// Store the currently displayed channel/folder's id
		this.currentFolderId = transport.headerJSON.current_folder.id

		if($('current_folder_name'))
		{
			switch(transport.headerJSON.current_folder.id)
			{
				case AS4ResourceBrowser.ALL_RESOURCES_PATH:
					$('current_folder_name').update('All Resources');
					$('current_folder_icon').addClassName('all_resources');	

					$('current_folder_icon').removeClassName('my_resources');
					$('current_folder_icon').removeClassName('hidden_resources');
					$('current_folder_icon').removeClassName('unapproved_resources');
					
					break;

				case AS4ResourceBrowser.MY_RESOURCES_PATH:
				
					$('current_folder_name').update('My Resources');
					$('current_folder_icon').addClassName('my_resources');	

					$('current_folder_icon').removeClassName('all_resources');
					$('current_folder_icon').removeClassName('hidden_resources');
					$('current_folder_icon').removeClassName('unapproved_resources');
					
					break;

				case AS4ResourceBrowser.HIDDEN_RESOURCES_PATH:
					$('current_folder_name').update('Hidden Resources');
					$('current_folder_icon').addClassName('hidden_resources');	

					$('current_folder_icon').removeClassName('my_resources');
					$('current_folder_icon').removeClassName('all_resources');
					$('current_folder_icon').removeClassName('unapproved_resources');

					break;

				case AS4ResourceBrowser.UNAPPROVED_RESOURCES_PATH:
					$('current_folder_name').update('Unapproved Resources');
					$('current_folder_icon').addClassName('unapproved_resources');	

					$('current_folder_icon').removeClassName('my_resources');
					$('current_folder_icon').removeClassName('all_resources');
					$('current_folder_icon').removeClassName('hidden_resources');

					break;

				default:					

					$('current_folder_name').update(transport.headerJSON.current_folder.name);
					$('current_folder_icon').removeClassName('my_resources');	
					$('current_folder_icon').removeClassName('all_resources');
					$('current_folder_icon').removeClassName('hidden_resources');
					$('current_folder_icon').removeClassName('unapproved_resources');
					
					break;
			}
		}
		
		// Resize any currently visible columns in IE6 (it can't handle the % based CSS widths defined in stylesheet).
		if(this.strBrowser == 'Explorer' && this.intVersion <= 6)
		{
			this.resizeVisibleFolderPaneColumns();
		}
		
		// Update the column header style.
		$j('#files_list_column_header .cell').removeClass('sortColumn');
		switch(this.sortKey)
		{
			case 'name':
				$j('#files_list_column_header .cell.column_header_name').addClass('sortColumn');
			break;
			case 'creator':
				$j('#files_list_column_header .cell.column_header_creator').addClass('sortColumn');
			break;
			case 'create_date':
				$j('#files_list_column_header .cell.column_header_date').addClass('sortColumn');
			break;
			case 'approved':
				$j('#files_list_column_header .cell.column_header_authorized').addClass('sortColumn');
			break;
			case 'restricted':
				$j('#files_list_column_header .cell.column_header_restriction').addClass('sortColumn');
			break;
			case 'type':
				$j('#files_list_column_header .cell.column_header_type').addClass('sortColumn');
			break;
		}
	},
	
	/**
	 * Prepare the subfolder cells for editing, drag-drop, etc.
	 * Called by AJAX callbacks after a successful request
	 */
	prepareSubfolderCells: function()
	{
		// jQuery does not have an equivilent of Prototype bind() so for now just store reference of 'this' in '_this'
		var _this = this;

		// If we have some subfolders, make them sortable.
		if($('subfolders_list'))
		{
			Position.includeScrollOffsets = true;
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make folders sortable (for organising the folder/menu order).
			$j("#subfolders_list").sortable(
			{
				axis:'y',
				containment:'parent',
				tolerance:'pointer',
				handle:'div.sort_handle',
				opacity:'0.6',
				
				start: function(event, ui)
				{
					// Remove hover style while dragging.
					$j(ui.item).css({'background-color':'#FFFFFF'});
					
					if(_this.strBrowser != "Explorer")
					{
						// Add drop shadow to element that is being dragged.
						var objDropShadowPlaceholder = $j(ui.item).find('.drop_shadow_placeholder:first').get(0);
						$j(objDropShadowPlaceholder).css({'background-color':'#FFFFFF'});
						_this.addDropShadow($j(objDropShadowPlaceholder), {opacity:'0.6'});
					}
				},
				
				stop: function(event, ui)
				{
					if(_this.strBrowser != "Explorer")
					{
						// Remove drop shadow to element that is being dragged.
						var objDropShadowPlaceholder = $j(ui.item).find('.drop_shadow_placeholder:first').get(0);
						$j(objDropShadowPlaceholder).removeShadow();
						$j(objDropShadowPlaceholder).css({'background-color':'transparent'});
					}
					
					// Re-add hover style when dragging stoped.
					$j(ui.item).css({'background-color':''});
				},
				
				update: function(event, ui)
				{
					var url = '/resource_browser/update_folder_order_index';
					AS4Shell.getInstance().ajaxUpdate(url, $j("#subfolders_list").sortable('serialize', {key:'subfolders_list[]'}), null, $A([_this.onUpdateOrderIndexSuccess]), {message: 'Saving order...'});
				
					// Sync the folder list with the folder content pane.
					_this.syncFolderListWithContentPane();
				}
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make each rows Sortable handle clickable to stop event propagation when sortables moved....
			// This is a work around for a currently *unfixed* jQuery bug that allows clicks to propagate on Sortable handles.
			$j("#subfolders_list").find('.folder.list_item .sort_handle').each(function(i)
			{				
				$j(this).click(function (event)
				{
					//console.log('Handle clicked, stopping propagation...');
					event.stopImmediatePropagation();
			    });
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make folders draggable/dropable (for organising folders/subfolders).
			// Allow them to be dropped on each other, as well as over to the folder list pane
			// on the left.
			$j("#subfolders_list").find('.folder.list_item .draggable_elements').each(function(i)
			{
				// The id of the folder being dragged.
				var strId = $j(this).attr('id');
				var arrIdParts = strId.split('_');
				var intIdNumPart = arrIdParts[1];
				
				// The parent id of the folder being dragged.
				var intDraggableParentIdNumPart = _this.currentFolderId;
				
				$j('#'+strId).draggable(
				{
					handle:'cell.name',
					opacity:0.7,
					helper:'clone',					
					appendTo: 'body',	
					cursorAt: { left: 5 },
					
					// When the drag action starts, create the available dropzones for this draggable on the fly, depending
					// on current folder state. Make sure to respect folder heirarchy, eg. do not let a parent folder be
					// dropped onto one of its decendent folders!
					start: function(event, ui)
					{
						// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
						// Handle dragging of multiple items!
						// If the user has selected multiple rows and starts dragging, we can drag them all at once!
						var strSelectedRowsHTML = _this.getSelectedRowsHTML(intIdNumPart);
						
						// Add the element to the DOM ready for use.
						if(strSelectedRowsHTML != '')
						{
							$j('body').prepend(strSelectedRowsHTML);	
						}					
						// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
						
						// Find all '.tree_node' (excluding underneath the current folder and the current folder itself) elements within the folder pane and make them droppable.	
						// NOTE - have changed this to target the .label child elements for various reasons.						
						$j('.tree_list .tree_node .label:not(#folderleaf_'+intIdNumPart+' .label, #folderleaf_'+intIdNumPart+' *)').each(function(i)
						{
							// The id of the folder being dropped onto.
							var strDropableId = $j(this).parent().attr('id');
							var arrDropableIdParts = strDropableId.split('_');
							var intDropableIdNumPart = arrDropableIdParts[1];
							
							$j(this).droppable(
							{
								// Stop drop event recursion.
								greedy:true,
								tolerance:'pointer',
								
								over: function(event, ui)
								{
									// Alter background colour to indicate that we are over this folder.
									$j(this).css({'background-color':'#EFEFEF'});
								},
								
								out: function(event, ui)
								{
									// Alter background colour to indicate that we are off this folder.
									$j(this).css({'background-color':''});
								},
								
								drop: function(event, ui)
								{
									// Remove background colour.
									$j(this).css({'background-color':''});									
														
									// Remove the visible element from the current folder content pane.
									$j('#subfolders_list #folder_'+intIdNumPart).css({display:'none'});
																		
									// Then fire of AJAX request to update server.
									var url = '/resource_browser/move_folder';
									var params = {
										folder_id: intIdNumPart,
										source_folder_id: intDraggableParentIdNumPart,
										dest_folder_id: intDropableIdNumPart
									};
									AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving folder...' });
									
									// Move any additional items that were multidragged!
									_this.moveAdditionalSelectedItems(intDraggableParentIdNumPart, intDropableIdNumPart);
									
									// Finally, fire off AJAX request to re-populate this part of the folder list.	
									var intDestinationParentFolderId = _this.getFoldersParentFolderId(intDropableIdNumPart);					
									_this.fetchFolders(intDestinationParentFolderId, $('folderleaf_'+intDestinationParentFolderId), {});
								}
							});
						});						
						
						// Find all '#subfolders_list .folder.list_item' elements (excluding underneath the current folder and the current folder itself) within the folder content pane and make them droppable.						
						$j('#subfolders_list .folder.list_item:not(#folder_'+intIdNumPart+')').each(function(i)
						{						
							// The id of the folder being dropped onto.
							var strDropableId = $j(this).attr('id');
							var arrDropableIdParts = strDropableId.split('_');
							var intDropableIdNumPart = arrDropableIdParts[1];
							
							$j(this).droppable(
							{
								// Stop drop event recursion.
								greedy:true,								
								
								over: function(event, ui)
								{									
									// Alter background colour to indicate that we are over this folder.
									$j(this).find('.cell.name').css({'background-color':'#EFEFEF'});									
								},
								
								out: function(event, ui)
								{									
									// Alter background colour to indicate that we are off this folder.
									$j(this).find('.cell.name').css({'background-color':''});
								},
								
								drop: function(event, ui)
								{									
									// Remove background colour.
									$j(this).find('.cell.name').css({'background-color':''});
															
									// Remove the visible element from the current folder content pane.
									$j('#subfolders_list #folder_'+intIdNumPart).css({display:'none'});
																		
									// Then fire of AJAX request to update server.
									var url = '/resource_browser/move_folder';
									var params = {
										folder_id: intIdNumPart,
										source_folder_id: intDraggableParentIdNumPart,
										dest_folder_id: intDropableIdNumPart
									};
									AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving folder...' });
									
									// Move any additional items that were multidragged!
									_this.moveAdditionalSelectedItems(intDraggableParentIdNumPart, intDropableIdNumPart);
									
									// Finally, fire off AJAX request to re-populate this part of the folder list.	
									var intDestinationParentFolderId = _this.getFoldersParentFolderId(intDropableIdNumPart);					
									_this.fetchFolders(intDestinationParentFolderId, $('folderleaf_'+intDestinationParentFolderId), {});																		
								}
							});
						});
					},
					
					// During drag...
					drag: function(event, ui)
					{
						// Then position it underneath the existing drag item.
						var intDraggingItemHeight = $j('.ui-draggable-dragging').height();
						
						var intNewX = ui.offset.left;
						var intNewY = ui.offset.top + intDraggingItemHeight;
						
						$j('#multiDragHelper_'+intIdNumPart).css({display:'', opacity:'0.7', left:intNewX+'px', top:intNewY+'px'});			
					},
					
					// When drag stops, remove all the dropzones we created above!
					stop: function(event, ui)
					{
						$j('.tree_list .tree_node .label:not(#folderleaf_'+intIdNumPart+' .label, #folderleaf_'+intIdNumPart+' *)').each(function(i)
						{
							$j(this).droppable('destroy');
						});
						
						$j('#subfolders_list .folder.list_item:not(#folder_'+intIdNumPart+')').each(function(i)
						{
							$j(this).droppable('destroy');
						});
						
						// Remove multiDragHelper from DOM.					
						$j('.multiDragHelper').remove();
						
						// Sync the folder list with the folder content pane.
						_this.syncFolderListWithContentPane();
					}
				});
			});			
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make folders icons/names double clickable for navigation.		
			$j("#subfolders_list").find('.folder.list_item .draggable_elements .cell.name').each(function(i)
			{
				// The id of the resource being clicked.
				var strId = $j(this).parent().attr('id');
				var arrIdParts = strId.split('_')
				var intIdNumPart = arrIdParts[1];
				
				// The parent id of the resource being clicked.
				var intParentIdNumPart = _this.currentFolderId;
				
				$j(this).dblclick(function ()
				{
					_this.simulateLeafClicked(intIdNumPart);
			    });
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Create context menus for the actions button at the end of each row.
			$j("#subfolders_list").find('.folder.list_item .cell.actions .button').each(function(i)
			{
				_this.generateContextMenu(_this, this, 'contentPaneFolderMenu', _this.handleContextMenuClicked_ContentPane, _this.handleContextMenuOpened_ContentPane, _this.handleContextMenuClosed_ContentPane, 'left');			
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Create context menus for the right click on each row.
			$j("#subfolders_list").find('.folder.list_item').each(function(i)
			{
				_this.generateContextMenu(_this, this, 'contentPaneFolderMenu', _this.handleContextMenuClicked_ContentPane, _this.handleContextMenuOpened_ContentPane, _this.handleContextMenuClosed_ContentPane, 'right');			
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Insert in-place editors for the folder titles.
			$j('.cell.name span.folder').each(function(i)
			{
				// The id of the folder being renamed.
				var strParentId = $j(this).parents('.list_item:first').attr('id');
				var arrParentIdParts = strParentId.split('_');
				var intParentIdNumPart = arrParentIdParts[1];	
				
				$j(this).editable('/resource_browser/rename_folder', 
				{
					event:'dblclick',
					id: 'defaultId',
					submitdata: { id: intParentIdNumPart },
					style   : 'display: inline;',
					width : 'none',
					onblur : 'submit',
					callback: function(value, settings)
					{
						// console.log(this);
				        // console.log(value);
				        // console.log(settings);
						
						// Sync the folder list with the folder content pane.
						_this.syncFolderListWithContentPane();
					}
				});
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make each row selectable by single click. Enabable multi-row select
			// if Ctrl+click or Shift+click event is triggered.
			$j("#subfolders_list").find('.folder.list_item').each(function(i)
			{	
				// The id of the resource being clicked.
				var strId = $j(this).attr('id');
				var arrIdParts = strId.split('_')
				var intIdNumPart = arrIdParts[1];
				
				// The parent id of the resource being clicked.
				var intParentIdNumPart = _this.currentFolderId;
			
				$j(this).click(function (event)
				{		
					_this.handleRowClicked(intIdNumPart, 'folder');	
					event.stopImmediatePropagation();
			    });
			});
			
			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			// Make each row selectable by checkbox. Enabable multi-row select
			// if this is clicked by default (Ctrl + click not required).
			$j("#subfolders_list").find('.folder.list_item .include_in_select_all').each(function(i)
			{	
				// The id of the resource being clicked.
				var strId = $j(this).attr('id');
				var arrIdParts = strId.split('_')
				var intIdNumPart = arrIdParts[1];
				
				// The parent id of the resource being clicked.
				var intParentIdNumPart = _this.currentFolderId;
						
				$j(this).click(function (event)
				{
					_this.handleRowClicked(intIdNumPart, 'folder', 'append');
					event.stopImmediatePropagation();
			    });			   
			});
		}
	},

	moveAdditionalSelectedItems: function(intSourceFolderId, intDestFolderId)
	{
		// Additionally make sure to move any items that were multidragged!
		var objCurrentSelectedItems = this.getSelectedItems('selected');
		var arrCurrentSelectedFolderItems = objCurrentSelectedItems['folders'];
		var arrCurrentSelectedResourceItems = objCurrentSelectedItems['resources'];
		//console.log(arrCurrentSelectedFolderItems);console.log(arrCurrentSelectedResourceItems);
		
		if(arrCurrentSelectedFolderItems.length > 0 || arrCurrentSelectedResourceItems.length > 0)
		{
			
		
			var url = '/resource_browser/move_resources';
			var params = {
				folder_ids: $A(arrCurrentSelectedFolderItems).toJSON(),
				resource_ids: $A(arrCurrentSelectedResourceItems).toJSON(),
				source_folder_id: intSourceFolderId,
				dest_folder_id: intDestFolderId
			};
			
			AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving additional resources...' });
		}
		
		// Remove the visible elements from the current folder list/content pane.
		$j.each(arrCurrentSelectedFolderItems, function(intKey, strVal)
		{
			var intOtherSelectedFolderId = strVal;
			$j('#subfolders_list #folder_'+intOtherSelectedFolderId).css({display:'none'});
		});
		$j.each(arrCurrentSelectedResourceItems, function(intKey, strVal)
		{
			var intOtherSelectedResourceId = strVal;
			$j('#resources_list #resourceitem_'+intOtherSelectedResourceId).css({display:'none'});
		});
		
		// Clear the recorded selected items list.
		this.clearSelectedItems('selected');
	},

	getSelectedRowsHTML: function(intElementID)
	{	
		var strSelectedRowHTML = '';
	
		var objCurrentSelectedItems = this.getSelectedItems('selected');
		var arrCurrentSelectedFolderItems = objCurrentSelectedItems['folders'];
		var arrCurrentSelectedResourceItems = objCurrentSelectedItems['resources'];
		//console.log(arrCurrentSelectedFolderItems);console.log(arrCurrentSelectedResourceItems);
		
		if(arrCurrentSelectedFolderItems.length > 0 || arrCurrentSelectedResourceItems.length > 0)
		{
			// Create a 'dummy' element underneath the cloned visible dragging element to display other currently selected items.
			strSelectedRowHTML += '<div id="multiDragHelper_'+intElementID+'" class="multiDragHelper" style="border:0px solid red;position:absolute;display:none;">';
			
			// Loop over other selected folders and grab their html.
			$j.each(arrCurrentSelectedFolderItems, function(intKey, strVal)
			{
				var intOtherSelectedFolderId = strVal;
				
				// If this is not the folder we are already dragging.
				if(intOtherSelectedFolderId != intElementID)
				{								
					var strOtherSelectedFolderHTML = $j("#subfolders_list #folder_"+intOtherSelectedFolderId+" .draggable_elements").html();											
					strSelectedRowHTML += strOtherSelectedFolderHTML;
				}
			});
			
			// Loop over other selected resources and grab their html.
			$j.each(arrCurrentSelectedResourceItems, function(intKey, strVal)
			{
				var intOtherSelectedResourceId = strVal;
				
				// If this is not the resource we are already dragging.
				if(intOtherSelectedResourceId != intElementID)
				{								
					var strOtherSelectedResourceHTML = $j("#resources_list #resourceitem_"+intOtherSelectedResourceId+" .draggable_elements").html();											
					strSelectedRowHTML += strOtherSelectedResourceHTML;
				}
			});						
			
			strSelectedRowHTML	+= '</div>';
		}
		
		return strSelectedRowHTML;
	},

	// Given the id of a row, determine if it is a folder or resource type.
	getRowTypeById: function(itemId)
	{
		var strRowType = '';
		
		var arrFoldersWithId = $j('#folder_'+itemId);
		
		if(arrFoldersWithId.length > 0)
		{
			strRowType = 'folder';
		}
		else
		{
			strRowType = 'resource';
		}
		
		return strRowType;
	},

	// Toggle the mass selecting/deselecting of rows.
	toggleSelectAllRows: function()
	{
		var _this = this;
		
		var blnChecked = $j('#checkAllCheckbox').get(0).checked;
		
		$j('.include_in_select_all').each(function(i)
		{
			// Get the id of the current row item.
			var itemId = $j(this).attr('id').split('_')[1];
			
			// Get the type of row.
			var strRowType = _this.getRowTypeById(itemId);
			
			switch(blnChecked)
			{
				case true:
					_this.markRowSelected(itemId, strRowType);
				break;
				
				case false:
					_this.markRowDeselected(itemId, strRowType);
				break;
			}
		});
	},

	// When an item is clicked in the content pane, emulate windows style
	// behaviour for selecting individual, multiple (via Ctrl+click or Shift+click)
	// items.
	handleRowClicked: function(itemId, itemType, strOverrideMode)
	{
		var strOverrideMode = (typeof(strOverrideMode) != 'undefined') ? strOverrideMode : null;
		
		// If ctrl/shift keys are NOT down, select the current row exclusively.
		if((this.objKeyboardKeyStatus['shift'] != 'down' && this.objKeyboardKeyStatus['ctrl'] != 'down' && strOverrideMode == null) || strOverrideMode == 'exclusive')
		{
			this.toggleRowSelected(itemId, itemType, 'exclusive');
		}

		// If ctrl key down, add the current row to the already selected ones.
		if(this.objKeyboardKeyStatus['ctrl'] == 'down' || strOverrideMode == 'append')
		{
			this.toggleRowSelected(itemId, itemType, 'append');
		}

		// If shift key down, select all rows beween the current row and the last selected row.
		if(this.objKeyboardKeyStatus['shift'] == 'down' || strOverrideMode == 'range')
		{
			this.toggleRowSelected(itemId, itemType, 'range');
		}
	},
	
	toggleRowSelected: function(itemId, itemType, strMode)
	{	
		var _this = this;
	
		// First lookup the selected status of this row.
		var arrSelectedItems = this.getSelectedItems('selected');		
		var arrSelectedItemsOfThisType = (itemType == 'folder') ? arrSelectedItems['folders'] : arrSelectedItems['resources'];
	
		// Grab the previous selected details before overwriting them.
		var intPreviousSelectedItemId =  this.objPreviousSelectedItem['intId'];
		
		// If this row is already selected, then de-select.
		if($j.inArray(itemId, arrSelectedItemsOfThisType) != -1)
		{			
			this.markRowDeselected(itemId, itemType);	
		}
		// If this row is not selected, then select it.
		else
		{			
			this.markRowSelected(itemId, itemType);
		}
		
		// Depending on the selection mode (varies depending on keyboard modifiers currently active), 
		// manipulate the selected rows accordingly.
		switch(strMode)
		{
			// De-select all other currently selected rows.
			case 'exclusive':
				var arrSelectedItems = this.getSelectedItems('selected');
				var arrSelectedFolders = arrSelectedItems['folders'];
				var arrSelectedResources = arrSelectedItems['resources'];
				$j.each(arrSelectedFolders, function(intKey, strVal)
				{
					if(strVal != itemId)
					{
						_this.markRowDeselected(this, 'folder');
					}
				});
				$j.each(arrSelectedResources, function(intKey, strVal)
				{
					if(strVal != itemId)
					{
						_this.markRowDeselected(this, 'resource');
					}
				});
			break;
			
			// Add the current row to the already selected ones.
			case 'append':
				
				// Nothing to do! This will be the default behaviour!
			
			break;
			
			// Select all rows beween the current row and the previously selected row.
			case 'range':
			
				// Store merged list of folders/resources in here.
				var arrAllListItems = [];				
				var arrFolderListItems = $j('#subfolders_list .list_item');
				var arrResourceListItems = $j('#resources_list .list_item');
				arrAllListItems = $j.merge($j.makeArray(arrFolderListItems), $j.makeArray(arrResourceListItems));
				
				// Now loop over the items, selecting the range between the previous and current selected row.
				var blnRangeSelectingOn = false;
				$j(arrAllListItems).each(function(i)
				{
					// Get id of current row.
					var currentRowId = $j(this).attr('id').split('_')[1];
					var strCurrentRowType = _this.getRowTypeById(currentRowId);
					
					// As soon as we encounter either of the ids, start the range, then on the next occurance stop it again.
					if(currentRowId == intPreviousSelectedItemId || currentRowId == itemId)
					{
						blnRangeSelectingOn = !blnRangeSelectingOn;
					}
					
					if(blnRangeSelectingOn || currentRowId == intPreviousSelectedItemId || currentRowId == itemId)
					{
						_this.markRowSelected(currentRowId, strCurrentRowType);
					}
					else
					{
						_this.markRowDeselected(currentRowId, strCurrentRowType);
					}
				});
				
			break;
		}
	},

	markRowSelected: function(itemId, itemType)
	{		
		switch(itemType)
		{
			case 'folder':
	
				// Add the selected style.
				$j('#folder_'+itemId).addClass('selected');
			
				// Check checkbox.
				$j('#rowItemCheckbox_'+itemId).get(0).checked = true;
				
				// Add entry into selected object.
				var arrSelectedItems = this.getSelectedItems('selected');
				var arrSelectedFolders = arrSelectedItems['folders'];
				arrSelectedFolders.push(itemId);
				this.recordSelectedItems(arrSelectedFolders, 'folders', 'selected');
				
			break;
			
			case 'resource':
			
				// Add the selected style.
				$j('#resourceitem_'+itemId).addClass('selected');
				
				// Check checkbox.
				$j('#rowItemCheckbox_'+itemId).get(0).checked = true;
								
				// Add entry into selected object.
				var arrSelectedItems = this.getSelectedItems('selected');
				var arrSelectedResources = arrSelectedItems['resources'];
				arrSelectedResources.push(itemId);
				this.recordSelectedItems(arrSelectedResources, 'resources', 'selected');
				
				// Register this row as the 'previous' row selected, so that on the next
				// call we can easily use the information with the various selection methods.
				this.recordSelectedItems([itemId], 'resources', 'previousSelected');		
				
			break;
		}
		
		// Register this row as the 'previous' row selected, so that on the next
		// call we can easily use the information with the various selection methods.
		this.objPreviousSelectedItem['strType'] = itemType;
		this.objPreviousSelectedItem['intId'] = itemId;
	},

	markRowDeselected: function(itemId, itemType)
	{		
		switch(itemType)
		{
			case 'folder':
	
				// Remove the selected style.
				$j('#folder_'+itemId).removeClass('selected');
				
				// Uncheck checkbox.
				$j('#rowItemCheckbox_'+itemId).get(0).checked = false;
				
				// Remove entry from selected object.
				var arrSelectedItems = this.getSelectedItems('selected');
				var arrSelectedFoldersBeforeRemoved = arrSelectedItems['folders'];
				
				arrSelectedFolders = $j.grep(arrSelectedFoldersBeforeRemoved, function(value)
				{
					return value != itemId;
				});
				
				this.recordSelectedItems(arrSelectedFolders, 'folders', 'selected');
	
			break;
			
			case 'resource':
			
				// Add the selected style.
				$j('#resourceitem_'+itemId).removeClass('selected');
				
				// Uncheck checkbox.
				$j('#rowItemCheckbox_'+itemId).get(0).checked = false;
				
				// Remove entry from selected object.
				var arrSelectedItems = this.getSelectedItems('selected');
				var arrSelectedResourcesBeforeRemoved = arrSelectedItems['resources'];
				
				arrSelectedResources = $j.grep(arrSelectedResourcesBeforeRemoved, function(value)
				{
					return value != itemId;
				});
				
				this.recordSelectedItems(arrSelectedResources, 'resources', 'selected');
	
			break;
		}
	},

	// If needed, move an element away from side/bottom of browser to make it visible.
	respositionElementToBeVisible: function(elementId)
	{
		// Get the browser size.
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		// Get the element size/position.
		var intMenuHeight = $j('#'+elementId).height();
		var intMenuWidth = $j('#'+elementId).width();
		var intMenuX = $j('#'+elementId).offset().left;
		var intMenuY = $j('#'+elementId).offset().top;
		
		// The overlap ammounts.
		var intOverlapX = (intMenuX + intMenuWidth) - intBrowserViewportWidth;
		var intOverlapY = (intMenuY + intMenuHeight) - intBrowserViewportHeight;
		
		// Shunt the element inside the visible area.
		if(intOverlapX > 0)
		{
			var intNewX = intMenuX - intOverlapX;
			$j('#'+elementId).css({left:intNewX+'px'});
		}
		if(intOverlapY > 0)
		{
			var intNewY = intMenuY - intOverlapY;
			$j('#'+elementId).css({top:intNewY+'px'});
		}
	},

	onToggleResourceApprovalComplete: function(transport, target)
	{
		var element = $('approval_status_' + transport.headerJSON.id);

		// remove ajax spinner
		element.removeClassName('ajax_updating');

		// Update icon to reflect new status
		if(transport.responseText == 'true')
			element.addClassName('approved');
		else
			element.removeClassName('approved');
	},
	
	/**
	* Check if the resource is attached to a widget.
	*/	
	resourceAttachedToWidget: function(objOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var objOverrideIds = (typeof(objOverrideIds) != "undefined") ? objOverrideIds : false;
		var arrFinalFolderIds = [];
		var arrFinalResourceIds = [];
		
		if(objOverrideIds)
		{
			arrFinalFolderIds = objOverrideIds['arrFolders'];
			arrFinalResourceIds = objOverrideIds['arrResources'];
		}
		else
		{
			var arrCheckedFolderItems = $j('#subfolders_list_form :checkbox:checked');
			var arrCheckedResourceItems = $j('#files_list_form :checkbox:checked');	
			
			$j(arrCheckedFolderItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalFolderIds.push(intIdValue);
			});	
			$j(arrCheckedResourceItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalResourceIds.push(intIdValue);
			});
		}
		
		// Build the string to submit.
		var idParams = '';
		$j.each(arrFinalFolderIds, function(intKey, strVal)
		{
			idParams += '&subfolders[]='+strVal;
		});
		$j.each(arrFinalResourceIds, function(intKey, strVal)
		{
			idParams += '&files[]='+strVal;
		});
		
		// Add items to the selected items object.
		this.recordSelectedItems(arrFinalFolderIds, 'folders', 'delete');
		this.recordSelectedItems(arrFinalResourceIds, 'resources', 'delete');
					
		var url = '/resource_browser/check_resource_attached_to_widget';		

		var params = {
			renderMode: 'update'
		}
		
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onResourceAttachedToWidgetCheck.bind(this)]));
	},
	
	onResourceAttachedToWidgetCheck:function(transport,target)
	{		
		if(transport.headerJSON.resource_widget_checks)
		{
			arrResources = $A(transport.headerJSON.resource_widget_checks);
			intResourceCount = 0;
			msg = "";
			
			arrResources.each(function(id)
			{
				intResourceCount++;
			});			
			
			if(intResourceCount > 0)
			{				
				arrResources.each(function(id) 
				{	
					msg = msg + "Resource '"+id.resource_name+"' used by '"+id.widget_name.replace(/_/, " ")+"' panel in  '"+id.channel_name+"'\n";
				});

//we dont want to allow users to delete a resource that is being used.
/*				if(confirm("Some resources you have selected to be deleted are currently being used by a panel.\n"+msg+"\n\nClick OK to continue and remove all selected resources.\nClick Cancel to return to the Resource Browser."))
				{
					resourceBrowser.showConfirmDeleteResourcesLightbox();
				}
				else
				{
					return;
				}
*/
				
				if(intResourceCount == "1")
				{
					alert("The resource listed below cannot be deleted as they are currently being used by a panel.\n"+msg+"\n\nRemove the resource from all folder layouts to continue.");
				}
				else
				{
					alert("The resources listed below cannot be deleted as they are currently being used by panels.\n"+msg+"\n\nRemove the resources from all folder layouts to continue.");
				}
				
				
				return;
			}
			else
			{
				resourceBrowser.showConfirmDeleteResourcesLightbox();
			}
		}
	},
	
	/**
	 * Show the confirm delete lightbox
	 */
	showConfirmDeleteResourcesLightbox: function(intResourceId)
	{	
		var intLightboxWidth = 400;
		var intLightboxHeight = 160;

		var params = {
			callback: this.onCreateShowConfirmDeleteResourcesLightbox.bind(this),
			height: intLightboxHeight,
			width: intLightboxWidth
		};
		
		AS4Shell.getInstance().showLightbox(params);
	},
	
	onCreateShowConfirmDeleteResourcesLightbox: function()
	{			
		return	'<div>' +
					'<div style="display:block;margin:10px;height:100px;overflow:auto;">' + 
						'<h3 style="border:0px;padding-left:0px;margin-left:0px;">Delete confirmation</h3>' + 
						'<p>Are you sure want to delete the selected folders/resources? <span style="font-weight:bold;">If a selected folder contains resources it will be undeletable untill resources within it are deleted.</span>.</p>' + 						
					'</div>' +
					'<div style="text-align:center; width:400px;height:40px; margin-top:5px; padding-top:5px; border-top: 1px dashed #000000"> <a href="#" onclick="resourceBrowser.deleteResources();AS4Shell.getInstance().closeLightbox();return false;" class="button small">Confirm</a><a href="#" onclick="AS4Shell.getInstance().closeLightbox();return false;" class="button small">Cancel</a></div>' +
				'</div>';
	},
	onCreateShowConfirmMoveResourcesLightbox: function()
	{			
		return	'<div>' +
					'<div style="display:block;margin:10px;height:100px;overflow:auto;">' + 
						'<h3 style="border:0px;padding-left:0px;margin-left:0px;">Duplicate Resource Name Confirmation</h3>' + 
						'<p>The destination folder already contains a resource named "'+this.movingResourceJSON.resource_title+'". Are you sure you want to continue? If so, a number will be appended to the end of this resource name.</p>' + 						
					'</div>' +
					'<div style="text-align:center; width:400px;height:40px; margin-top:5px; padding-top:5px; border-top: 1px dashed #000000"> <a href="#" onclick="resourceBrowser.confirmResourceMove();AS4Shell.getInstance().closeLightbox();return false;" class="button small">Confirm</a><a href="#" onclick="AS4Shell.getInstance().closeLightbox();return false;" class="button small">Cancel</a></div>' +
				'</div>';
	},
	
	/**
	 * Attempt to delete the selected resources
	 */
	deleteResources: function()
	{
		var url = '/resource_browser/delete_resources';
		
		// Build the string to submit.
		var arrItemsToDelete = this.getSelectedItems('delete');		
		var idParams = '';
		$j.each(arrItemsToDelete['folders'], function(intKey, strVal)		
		{
			idParams += '&subfolders[]='+strVal;
		});
		$j.each(arrItemsToDelete['resources'], function(intKey, strVal)
		{
			idParams += '&files[]='+strVal;
		});
				
		var params = {
			renderMode: 'update'
		}
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onDeleteResourcesSuccess.bind(this)]));
	},
	
	onDeleteResourcesSuccess: function(transport, target)
	{
		$j(transport.headerJSON.subfolders.deleted_ids).each(function(i)
		{
			var folderId = this;

			// Update the folder list pane and content pane rows.
			$j('#folderleaf_'+folderId).remove();
			$j('#folder_'+folderId).remove();		
		});
		$j(transport.headerJSON.resources.deleted_ids).each(function(i)
		{
			var resourceId = this;

			// Update the content pane rows.
			$j('#resourceitem_'+resourceId).remove();
		});
		
		// Show error message for items that were not deleted!		
		if(transport.headerJSON.subfolders.failed_ids.length > 0 || transport.headerJSON.resources.failed_ids.length > 0)
		{
			var strErrorMessage = 'Some folders/resources could not be deleted!. Please make sure folders are empty before deleting.';
			alert(strErrorMessage);
		}		
	},

	/**
	 * Toggle the visibility of an arbitrary folder.
	 *
	 * @param {Event} event
	 * @param {boolean} status Visibility of the current folder
	 */
	toggleFolderVisibility: function(folderId, status)
	{	
		var url = '/resource_browser/toggle_folder_visibility';
		var params = {
			folder_id: folderId,
			visibility: status ? true : false
		};

		AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([this.onToggleFolderVisibilitySuccess.bind(this)]), {showMessage: false});
	},
	onToggleFolderVisibilitySuccess: function(transport, target)
	{
		var blnHidden = (transport.headerJSON.visibility) ? true : false;				
		var folderId = transport.headerJSON.folder_id;

		// Update the folder list pane and content pane rows.
		if(blnHidden)
		{
			$j('#folderleaf_'+folderId).addClass('is_hidden');
			$j('#folder_'+folderId).addClass('is_hidden');
		}
		else
		{
			$j('#folderleaf_'+folderId).removeClass('is_hidden');
			$j('#folder_'+folderId).removeClass('is_hidden');
		}
	},

	/**
	 * Attempt to (un)hide the selected resources
	 * @param {boolean} newState The new hidden status for the resources.
	 */
	toggleHideResources: function(newState, arrOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var arrOverrideIds = (typeof(arrOverrideIds) != "undefined") ? arrOverrideIds : false;
		
		if(arrOverrideIds)
		{
			var idParams = '';
			$j.each(arrOverrideIds, function(intKey, strVal)
			{
				idParams += '&files[]='+strVal;
			});			
		}
		else
		{
			var idParams = '&'+$('files_list_form').serialize();
		}		
		
		var url = '/resource_browser/toggle_hide_resources';

		var params = {
			renderMode: 'update',
			status: newState
		}
		
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onToggleHideResourcesSuccess.bind(this)]));
	},
	onToggleHideResourcesSuccess: function(transport, target)
	{
		$j(transport.headerJSON.hidden_ids).each(function(i)
		{
			var resourceId = this;

			// Update the content pane rows.		
			$j('#resourceitem_'+resourceId).addClass('is_hidden');		
		});
		$j(transport.headerJSON.unhidden_ids).each(function(i)
		{
			var resourceId = this;

			// Update the content pane rows.		
			$j('#resourceitem_'+resourceId).removeClass('is_hidden');
		});
	},

	/**
	 * Attempt to (un)hide the selected subfolders 
	 * @param {boolean} newState The new hidden status for the subfolders.
	 */
	toggleHideSubfolders: function(newState, arrOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var arrOverrideIds = (typeof(arrOverrideIds) != "undefined") ? arrOverrideIds : false;
		
		if(arrOverrideIds)
		{
			var idParams = '';
			$j.each(arrOverrideIds, function(intKey, strVal)
			{
				idParams += '&files[]='+strVal;
			});			
		}
		else
		{
			var idParams = '&'+$('subfolders_list_form').serialize();
		}		
		
		var url = '/resource_browser/toggle_hide_subfolders';

		var params = {
			renderMode: 'update',
			status: newState
		}
		
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onToggleHideSubfoldersSuccess.bind(this)]));
	},
	onToggleHideSubfoldersSuccess: function(transport, target)
	{								
		$j(transport.headerJSON.hidden_ids).each(function(i)
		{
			var folderId = this;

			// Update the folder list pane and content pane rows.		
			$j('#folderleaf_'+folderId).addClass('is_hidden');
			$j('#folder_'+folderId).addClass('is_hidden');			
		});
		$j(transport.headerJSON.unhidden_ids).each(function(i)
		{
			var folderId = this;

			// Update the folder list pane and content pane rows.		
			$j('#folderleaf_'+folderId).removeClass('is_hidden');
			$j('#folder_'+folderId).removeClass('is_hidden');			
		});
	},

	approveResources: function(objOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var objOverrideIds = (typeof(objOverrideIds) != "undefined") ? objOverrideIds : false;
		var arrFinalResourceIds = [];
		
		if(objOverrideIds)
		{
			arrFinalResourceIds = objOverrideIds['arrResources'];
		}
		else
		{
			var arrCheckedResourceItems = $j('#files_list_form :checkbox:checked');	
			
			$j(arrCheckedResourceItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalResourceIds.push(intIdValue);
			});
		}
		
		// Build the string to submit.
		var idParams = '';
		$j.each(arrFinalResourceIds, function(intKey, strVal)
		{
			idParams += '&files[]='+strVal;
			
			// Change the approval icon to a spinner
			//$j('#approval_status_' + strVal).removeClass('approved');
			//$j('#approval_status_' + strVal).removeClass('unapproved');
			//$j('#approval_status_' + strVal).addClass('ajax_updating');
		});
		
		// Add items to the selected items object.
		this.recordSelectedItems(arrFinalResourceIds, 'resources', 'approve');
				
		// Trigger an approval status update
		var url = "/resource_browser/mark_resources_approved";

		var params = {
		}
		
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onApproveResourcesSuccess.bind(this)]));
	},
	onApproveResourcesSuccess: function(transport, target)
	{
		$j(transport.headerJSON.approved_ids).each(function(i)
		{
			var resourceId = this;

			var element = $j('#approval_status_' + resourceId);
	
			element.removeClass('ajax_updating');
            element.removeClass('unapproved');
			element.addClass('approved');
		});
	},

	unapproveResources: function(objOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var objOverrideIds = (typeof(objOverrideIds) != "undefined") ? objOverrideIds : false;
		var arrFinalResourceIds = [];
		
		if(objOverrideIds)
		{
			arrFinalResourceIds = objOverrideIds['arrResources'];
		}
		else
		{
			var arrCheckedResourceItems = $j('#files_list_form :checkbox:checked');	
			
			$j(arrCheckedResourceItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalResourceIds.push(intIdValue);
			});
		}
		
		// Build the string to submit.
		var idParams = '';
		$j.each(arrFinalResourceIds, function(intKey, strVal)
		{
			idParams += '&files[]='+strVal;
			
			// Change the approval icon to a spinner
			//$j('#approval_status_' + strVal).removeClass('approved');
			//$j('#approval_status_' + strVal).removeClass('unapproved');
			//$j('#approval_status_' + strVal).addClass('ajax_updating');
		});
		
		// Add items to the selected items object.
		this.recordSelectedItems(arrFinalResourceIds, 'resources', 'unapprove');
				
		// Trigger an approval status update
		var url = "/resource_browser/mark_resources_unapproved";

		var params = {
		}
		
		AS4Shell.getInstance().ajaxUpdate(url, $H(params).toQueryString() + idParams, null, $A([this.onUnapproveResourcesSuccess.bind(this)]));
	},
	onUnapproveResourcesSuccess: function(transport, target)
	{
		$j(transport.headerJSON.unapproved_ids).each(function(i)
		{
			var resourceId = this;

			var element = $j('#approval_status_' + resourceId);
	
			element.removeClass('ajax_updating');
            element.removeClass('approved');
			element.addClass('unapproved');
		});
	},
	
	cutFolders: function(intSourceFolderId, objOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var objOverrideIds = (typeof(objOverrideIds) != "undefined") ? objOverrideIds : false;
		var arrFinalFolderIds = [];
	
		// Before cutting the current item, make sure to un-cut any previous cut elements.
		var arrCurrentCutItems = this.getSelectedItems('cut');		
		var arrCurrentCutFolderItems = arrCurrentCutItems['folders'];
		$j.each(arrCurrentCutFolderItems, function(intKey, strVal)
		{			
			$j('#folderleaf_'+strVal).removeClass('is_cut');
			$j('#folder_'+strVal).removeClass('is_cut');
		});
		
		if(objOverrideIds)
		{
			arrFinalFolderIds = objOverrideIds['arrFolders'];
		}
		else
		{
			var arrCheckedFolderItems = $j('#subfolders_list_form :checkbox:checked');	
			
			$j(arrCheckedFolderItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalFolderIds.push(intIdValue);
			});
		}		
				
		// Change appearance of cut rows.
		$j.each(arrFinalFolderIds, function(intKey, strVal)
		{
			$j('#folderleaf_'+strVal).addClass('is_cut');
			$j('#folder_'+strVal).addClass('is_cut');
		});
		
		// Add items to the selected items object.
		this.recordSelectedItems(arrFinalFolderIds, 'folders', 'cut');
		
		// Store the source folder.
		this.objCutSourceFolder = {'intSourceFolderId':intSourceFolderId};
	},
	
	cutResources: function(intSourceFolderId, objOverrideIds)
	{
		// If an array of ids was passed, then use that instead.
		var objOverrideIds = (typeof(objOverrideIds) != "undefined") ? objOverrideIds : false;
		var arrFinalResourceIds = [];
		
		// Before cutting the current item, make sure to un-cut any previous cut elements.
		var arrCurrentCutItems = this.getSelectedItems('cut');
		var arrCurrentCutResourceItems = arrCurrentCutItems['resources'];
		$j.each(arrCurrentCutResourceItems, function(intKey, strVal)
		{
			$j('#resourceitem_'+strVal).removeClass('is_cut');
		});
		
		if(objOverrideIds)
		{
			arrFinalResourceIds = objOverrideIds['arrResources'];
		}
		else
		{
			var arrCheckedResourceItems = $j('#files_list_form :checkbox:checked');	
			
			$j(arrCheckedResourceItems).each(function(i)
			{
				var intIdValue = $j(this).attr('value');
				arrFinalResourceIds.push(intIdValue);
			});
		}
								
		// Build the string to submit.		
		$j.each(arrFinalResourceIds, function(intKey, strVal)
		{
			// Change appearance of cut rows.
			$j('#resourceitem_'+strVal).addClass('is_cut');
		});
		
		// Add items to the selected items object.
		this.recordSelectedItems(arrFinalResourceIds, 'resources', 'cut');
		
		// Store the source folder.
		this.objCutSourceFolder = {'intSourceFolderId':intSourceFolderId};
	},
	
	pasteItems: function(intDestinationFolderId)
	{
		var _this = this;
		
		// Get any current cut elements.
		var objCurrentCutItems = this.getSelectedItems('cut');
		var arrCurrentCutFolderItems = objCurrentCutItems['folders'];
		var arrCurrentCutResourceItems = objCurrentCutItems['resources'];
		
		// Get the source folder details.
		var intSourceFolderId = this.objCutSourceFolder['intSourceFolderId'];
		
		// Loop over folders and move them.	
		$j.each(arrCurrentCutFolderItems, function(intKey, strVal)
		{			
			// Make sure we are not trying to paste a folder into itself!
			if(strVal != intDestinationFolderId)
			{
				// Remove the visible element from the current folder content pane (if it exists).
				$j('#subfolders_list #folder_'+strVal).css({display:'none'});
													
				// Then fire of AJAX request to update server.
				var url = '/resource_browser/move_folder';
				var params = {
					folder_id: strVal,
					source_folder_id: intSourceFolderId,
					dest_folder_id: intDestinationFolderId
				};
				AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Pasting folder...' });
			}
			else
			{
				// Remove the cut class.
				$j('#folder_'+strVal).removeClass('is_cut');
			}
		});
		
		// Loop over resources and move them.	
		$j.each(arrCurrentCutResourceItems, function(intKey, strVal)
		{
			// Remove the visible element from the current folder content pane (if it exists).
			//$j('#resources_list #resourceitem_'+strVal).css({display:'none'});
			
			_this.movingResource = $j('#resources_list #resourceitem_'+strVal);
												
			// Then fire of AJAX request to update server.
			var url = '/resource_browser/move_resource';
			var params = {
				resource_id: strVal,
				source_folder_id: intSourceFolderId,
				dest_folder_id: intDestinationFolderId
			};
			AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([_this.onMoveResourceSuccess.bind(_this)]), { message: 'Pasting resource...' });
		});
		
		// Clear the cut items.
		this.clearSelectedItems('cut');
		
		// Finally, fire off AJAX request to re-populate this part of the folder list.
		var intDestinationParentFolderId = this.getFoldersParentFolderId(intDestinationFolderId);
		this.fetchFolders(intDestinationParentFolderId, $('folderleaf_'+intDestinationParentFolderId), {});
		
		// Sync the folder list with the folder content pane.
		this.syncFolderListWithContentPane();
	},
	
	// Whever an action is performed that effects the heirarchy of folders in the content pane (drag/drop/re-order/cut/paste etc.),
	// this method can be called to bring the folder list back into sync with the content pane.
	syncFolderListWithContentPane: function()
	{
		// Get the current set of folders in the folder list view.
		var arrFolderListFolders = $j("#folderleaf_"+this.currentFolderId).find('.tree_node');
		//console.log(arrFolderListFolders);
				
		// Get the current set of folders in the content pane view.
		var arrContentPaneFolders = $j("#subfolders_list").find('.folder.list_item');
		//console.log(arrContentPaneFolders);
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// Handle folder re-ordering.
		
		// Insert a dummy target element at the top of the folder list pane and assign as the first target.
		$j("#folderleaf_"+this.currentFolderId).find('.tree_list:first').prepend('<li id="folderleaf_insertAfterDummyTarget"></li>');
		var intTargetInsertAfterId = 'insertAfterDummyTarget';
		
		// Now loop over the above items and re-order folder list items to match.		
		arrContentPaneFolders.each(function(i)
		{
			// Get the id of this folder.
			var intFolderId = $j(this).attr('id').split('_')[1];
			
			// Insert the corresponding folder list element after its new target.			
			$j("#folderleaf_"+intFolderId).insertAfter("#folderleaf_"+intTargetInsertAfterId);			
			
			// Records this element as the target for the next insertAfter.
			intTargetInsertAfterId = intFolderId;
		});
		
		// Remove dummy target element.
		$j("#folderleaf_"+this.currentFolderId).find('#folderleaf_insertAfterDummyTarget').remove();
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// Handle renamed folders.
		
		// Loop over the above items and rename folder list items to match.		
		arrContentPaneFolders.each(function(i)
		{
			// Get the id of this folder.
			var intFolderId = $j(this).attr('id').split('_')[1];
			
			// Get the name of this folder.
			var strFolderName = $j(this).find('.cell.name span.folder').text();
			
			// Update the corresponding folder list elements name.
			$j("#folderleaf_"+intFolderId).find('span.label').text(strFolderName);
		});
		
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// Handle added/deleted/moved folders.
		
		// TODO.
		
	},
	
	// Whever an action is performed that effects the heirarchy of folders in the folder list (drag/drop/cut/paste etc.),
	// this method can be called to bring the content pane back into sync with the folder list.
	syncContentPaneWithFolderList: function()
	{
		// Just refresh the current content pane!
		var options = ('{' + $j('#folderleaf_'+this.currentFolderId).attr('rel') + '}').evalJSON();
		this.resourcesPage = 1;
		resourceBrowser.fetchResources(options.path, false, true);
	},	

	// Attempt to move the current folder up a level.
	navigateLevelUp: function()
	{	
		// Look to see if we have a parent folder.
		var intParentFolderId = this.getFoldersParentFolderId(this.currentFolderId);

		if(intParentFolderId != null)
		{
			// Trigger the change.
			this.simulateLeafClicked(intParentFolderId);
		}
		else
		{
			alert('Already at top folder level!');
		}
	},
	
	// Attempt to get the id of the current folder parent folder.
	getFoldersParentFolderId: function(intFolderId)
	{
		// Loop to see if we have a parent '.tree_node' element.
		var objParentTreeNode = $j('#folderleaf_'+intFolderId).parents('.tree_node:first').get(0);
		
		var intParentFolderId = null;
		
		if(objParentTreeNode)
		{
			// The parent id of the current folder.
			var strParentId = $j(objParentTreeNode).attr('id');
			var arrParentIdParts = strParentId.split('_');
			var intParentFolderId = arrParentIdParts[1];
		}
		
		return intParentFolderId;
	},
	
	/**
	 * Show the preview resource lightbox
	 */
	showPreviewResourceLightbox: function(intResourceId)
	{
		// Store the id of the resource being previewed.
		this.recordSelectedItems([intResourceId], 'resources', 'preview');

		// Get the optimum size for the lightbox preview...
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		var intLightboxWidth = intBrowserViewportWidth - 100;
		var intLightboxHeight = intBrowserViewportHeight - 100;

		var params = {
			callback: this.onCreatePreviewResourceLightbox.bind(this),
			height: intLightboxHeight,
			width: intLightboxWidth
		};
		
		AS4Shell.getInstance().showLightbox(params);
	},
	
	onCreatePreviewResourceLightbox: function()
	{
		// Get the resource id to preview.
		var arrItemsToPreview = this.getSelectedItems('preview');		
		var intResourceId = arrItemsToPreview['resources'][0];
		
		//var url = '/channel/view_resource/id/'+intResourceId;
		var url = '/resources/view/id/'+intResourceId+'/inline/true/preview/true';
		
		// Get the optimum size for the lightbox preview...
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		var intLightboxWidth = intBrowserViewportWidth - 100;
		var intLightboxHeight = intBrowserViewportHeight - 100;
				
		var intIframeWidth = intLightboxWidth;
		var intIframeHeight = intLightboxHeight - 50;
				
		var intToolbarWidth = intIframeWidth;
		var intToolbarHeight = 40;
				
		return	'<div>' +
					'<div style="display:block;">' + 
						'<iframe src="'+ url +'" style="width:'+ intIframeWidth +'px;height:'+ intIframeHeight +'px;"></iframe>' + 
					'</div>' +
					'<div style="text-align:center; width:'+ intToolbarWidth +'px;height:'+ intToolbarHeight +'px; margin-top:5px; padding-top:5px; border-top: 1px dashed #000000"><a href="#" onclick="AS4Shell.getInstance().closeLightbox();return false;" class="button small">Close preview</a></div>' +
				'</div>';
	},

	// Open a resource library lightbox to choose a resource to link to the given folder.
	showLinkFolderToResourceLightbox: function(intFolderId)
	{		
		// Record the folder we are linking a resource to.
		this.recordSelectedItems([intFolderId], 'folders', 'linkResource');
		
		// Get the optimum size for the lightbox preview...
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		var intLightboxWidth = intBrowserViewportWidth - 100;
		var intLightboxHeight = intBrowserViewportHeight - 100;
		
		var params = {
			callback: this.onShowLinkFolderToResourceLightbox.bind(this),
			height: intLightboxHeight,
			width:intLightboxWidth
		};
		
		AS4Shell.getInstance().showLightbox(params);
	},	
	onShowLinkFolderToResourceLightbox: function()
	{		
		// Get the optimum size for the lightbox preview...
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		var intLightboxWidth = intBrowserViewportWidth - 100;
		var intLightboxHeight = intBrowserViewportHeight - 100;
		
		var intMediaLibraryWidth = intLightboxWidth;
		var intMediaLibraryHeight = intLightboxHeight - 50;
				
		var intToolbarWidth = intMediaLibraryWidth;
		var intToolbarHeight = 40;
	
		var url = '/resource_browser/fetch_resources';
		var params = {
			renderMode: 'update',
			path: 'all_resources',
			terms: '',
			filters: [],
			sort_key: '',
			sort_direction: 'ascending',
			resources_page: '',
			resources_per_page: 10,
			icon_display_mode: 'linkToFolder',
			is_media_browser: true
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, 'media_library', $A([]), {showMessage: false});
		
		// Get the folder we are linking the resource to.
		var objLinkResourceItems = this.getSelectedItems('linkResource');
		var arrLinkResourceFolderItems = objLinkResourceItems['folders'];				
		var intLinkedFolderId = arrLinkResourceFolderItems[0];		
				
		return	'<div style="overflow:hidden;">' +
					
					'<div id="resourcePicker" style="width:'+ intMediaLibraryWidth +'px;height:'+ intMediaLibraryHeight +'px;overflow-y:scroll;">' + 
										
						'<div id="media_library">' + 							
						
							'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br />Loading...' +
						
						'</div>' + 
						
					'</div>' + 
					
					'<div style="text-align:center; width:'+ intToolbarWidth +'px;height:'+ intToolbarHeight +'px; margin-top:5px; padding-top:5px; border-top: 1px dashed #000000"><a href="#" onclick="resourceBrowser.removeFolderLinkedResource('+intLinkedFolderId+');return false;" class="button small">Remove currently linked resource</a><a href="#" onclick="AS4Shell.getInstance().closeLightbox();return false;" class="button small">Close resource picker</a></div>' +
					
				'</div>';
	},
	linkFolderToResource: function(intResourceId, objResourceDetails)
	{	
		// Get the folder we are linking the resource to.
		var objLinkResourceItems = this.getSelectedItems('linkResource');
		var arrLinkResourceFolderItems = objLinkResourceItems['folders'];
		
		var url = '/resource_browser/link_folder_to_resource';
		var params = {
			folder_id: arrLinkResourceFolderItems[0],
			resource_id: intResourceId,
			renderMode: 'lightbox'
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, 'resourcePicker', $A([this.onLinkFolderToResourceSuccess.bind(this)]), {showMessage: false});		
	},
	onLinkFolderToResourceSuccess: function(transport, target)
	{
		AS4Shell.getInstance().closeLightbox();
		
		// Refresh the current folder content pane.
		this.simulateLeafClicked(this.currentFolderId);
	},
	/**
	 * Removes the currently linked resource from the displayed folder
	 *
	 * @param {integer} folderId The id of the folder for which the linked resource will be removed 
	 */
	removeFolderLinkedResource: function(folderId)
	{
		var url = '/resource_browser/link_folder_to_resource';
		var params = {
			folder_id: folderId
		};

		AS4Shell.getInstance().ajaxUpdate(url, params, 'resourcePicker', $A([this.onLinkFolderToResourceSuccess.bind(this)]), {showMessage: false});
	},

	/**
	 * Show the create new subfolder lightbox
	 */
	showCreateSubfolderLightbox: function()
	{
		var params = {
			callback: this.onCreateSubfolderLightbox.bind(this),
			height: 140
		};
		
		AS4Shell.getInstance().showLightbox(params);
	},	
	onCreateSubfolderLightbox: function()
	{
		var url = '/resource_browser/create_subfolder_lightbox';

		var params = {
			renderMode: 'lightbox'
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, 'create_subfolder_container', $A(), {showIndicator: false});
		
		return	'<div id="create_subfolder_container">' +
					'<div class="centre" style="padding-top: 40%">' +
						'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br />Loading...' +
					'</div>' +
				'</div>';
	},
	/**
	 * Attempt to create a new subfolder in the current location
	 * @param {string} [folderName] Optional name of the new folder. Default is 'New Folder'.
	 */
	createSubfolder: function(folderName)
	{
		var intTargetFolderId = (this.targetFolderId != null) ? this.targetFolderId : this.currentFolderId;
		
		if(this.targetFolderId != null)
		{
			this.targetFolderId = null;			
		}
		
		var url = '/resource_browser/create_subfolder';
		var params = {
			renderMode: 'update',
			parent_id: intTargetFolderId,
			folder_name: folderName ? folderName : 'New Folder'
		};

		AS4Shell.getInstance().closeLightbox();
		AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([this.onCreateSubfolderSuccess.bind(this, intTargetFolderId)]), {message: 'Creating folder...'});
	},	
	onCreateSubfolderSuccess: function(intTargetFolderId, transport, target)
	{		
		this.simulateLeafClicked(intTargetFolderId);
	},
		
	/**
	 * Create a new folder from the information in the create_folder_panel
	 */
	startUpload: function(intOverrideFolderId)
	{
		var intTargetFolderId = (typeof(intOverrideFolderId) != 'undefined') ? intOverrideFolderId : this.currentFolderId;
		
		if(!intTargetFolderId ||
			$A([AS4ResourceBrowser.ROOT_RESOURCES_PATH, 
				AS4ResourceBrowser.MY_RESOURCES_PATH, 
				AS4ResourceBrowser.HIDDEN_RESOURCES_PATH,
				AS4ResourceBrowser.UNAPPROVED_RESOURCES_PATH]).indexOf(intTargetFolderId) != -1)
		{
			alert('The current folder is not valid for uploads');
			return;
		}
		
		// Redirect to the upload form.
		AS4Shell.getInstance().redirect('/resource_editor/upload_files/channel_id/' + intTargetFolderId);
	},	
	
	/**
	 * Create a new folder from the information in the create_folder_panel
	 */
	startSecureUpload: function(intOverrideFolderId)
	{
		var intTargetFolderId = (typeof(intOverrideFolderId) != 'undefined') ? intOverrideFolderId : this.currentFolderId;
		
		if(!intTargetFolderId ||
			$A([AS4ResourceBrowser.ROOT_RESOURCES_PATH, 
				AS4ResourceBrowser.MY_RESOURCES_PATH, 
				AS4ResourceBrowser.HIDDEN_RESOURCES_PATH,
				AS4ResourceBrowser.UNAPPROVED_RESOURCES_PATH]).indexOf(intTargetFolderId) != -1)
		{
			alert('The current folder is not valid for uploads');
			return;
		}
		// console.log(this.installationId);
		// Redirect to the upload form.
		
		window.open(this.secureUrl+'/resource_editor/upload_files/installation_id/' + this.installationId + '/channel_id/' + intTargetFolderId + '/secure/true');
		//AS4Shell.getInstance().redirect(this.secureUrl+'/resource_editor/upload_files/installation_id/' + this.installationId + '/channel_id/' + intTargetFolderId + '/secure/true');
	},

	/**
	 * Check that the currently entered search terms match what was set by the delayed action, and if so
	 * initiate a new resource fetch supplying the current search terms.
	 *
	 * @param {string} terms The terms that were entered when the method was bound; Used to ignore a search if the terms have changed since
	 */
	startSearch: function(terms, blnKeepAdvanced)
	{
		if(terms != this.searchTerms)
			return;

		var blnKeepAdvanced = (typeof(blnKeepAdvanced) != "undefined") ? blnKeepAdvanced : false;

		// Clear any advanced terms if doing a normal search.
		if(!blnKeepAdvanced)
		{
			this.advSearchTerms = 
			{
				terms: '',
				createdBy:  '',
				resourceType:  '',
				dateAfter:  '',
				dateBefore:  '',
				searchAuthorized:  'both',
				restrictedGroups:  '',
				restrictedReason:  '',
				resourceToSearch:  'current_folder_only',
				searchSubfolders:  false
			}
		}

		this.fetchResources();
	},

    startSearch_thumbnail: function(terms, blnKeepAdvanced)
	{

        var target = $('files_list_thumbnail');

		if(terms != this.searchTerms)
			return;

		var blnKeepAdvanced = (typeof(blnKeepAdvanced) != "undefined") ? blnKeepAdvanced : false;

		// Clear any advanced terms if doing a normal search.
		if(!blnKeepAdvanced)
		{
			this.advSearchTerms =
			{
				terms: '',
				createdBy:  '',
				resourceType:  '',
				dateAfter:  '',
				dateBefore:  '',
				searchAuthorized:  'both',
				restrictedGroups:  '',
				restrictedReason:  '',
				resourceToSearch:  'current_folder_only',
				searchSubfolders:  false
			}
		}

		this.fetchResources(null,target);
	},


	startAdvancedSearch: function(terms)
	{
		// Do some basic validation.
		var strDateBefore = $j('#adv_date_before').val();
		if(strDateBefore != '' && strDateBefore != 'DD/MM/YYYY')
		{
			var strNumExpression = /^([0-9]){2}\/([0-9]){2}\/([0-9]){4}$/;
			if(strNumExpression.test(strDateBefore) === false)
			{
				alert("Please enter date values in the format 'DD/MM/YYYY'");
				return false;
			}
		}
		var strDateAfter = $j('#adv_date_after').val();
		if(strDateAfter != '' && strDateAfter != 'DD/MM/YYYY')
		{
			var strNumExpression = /^([0-9]){2}\/([0-9]){2}\/([0-9]){4}$/;
			if(strNumExpression.test(strDateAfter) === false)
			{
				alert("Please enter date values in the format 'DD/MM/YYYY'");
				return false;
			}
		}
		
		// Then get values from the advanced search lightbox before submitting.
		var strAdvSearchTerms = ($j('#adv_search_terms').val() != 'Enter free text to search name, description, meta') ? $j('#adv_search_terms').val() : '';
		var strCreatedBy = ($j('#adv_created_by').val() != 'Enter username, first name or last name') ? $j('#adv_created_by').val() : '';
		var strDateAfter = ($j('#adv_date_after').val() != 'DD/MM/YYYY') ? $j('#adv_date_after').val() : '';
		var strDateBefore = ($j('#adv_date_before').val() != 'DD/MM/YYYY') ? $j('#adv_date_before').val() : '';
				
		var advSearchTerms = 
		{
			terms: strAdvSearchTerms,
			createdBy: strCreatedBy,
			resourceType: $j('#adv_resource_types').val(),
			dateAfter: strDateAfter,
			dateBefore: strDateBefore,
			searchAuthorized: this.getSelectedRadioValue('adv_search_authorized'),
			restrictedGroups: $j('#adv_restricted_groups').val(),
			restrictedReason: $j('#adv_restricted_reason').val(),
			resourceToSearch: this.getSelectedRadioValue('adv_resources_to_search'),
			searchSubfolders: ($j('#adv_search_subfolders').get(0).checked) ? true : false
		}		
		
		// Store the search terms.
		this.advSearchTerms = advSearchTerms;
						
		// Copy any advanced relevant terms back to simple terms GUI components.
		$j('#search_terms').val(advSearchTerms.terms);
		this.searchTerms = advSearchTerms.terms;
				
		// Switch to the appropriate top level pseudo folder before searching.		
		switch(this.advSearchTerms.resourceToSearch)
		{
			case 'current_folder_only':
			
				// Start the search.
				this.startSearch(advSearchTerms.terms, true);
			
			break;
			case 'all_installation_resources':
			
				strAdvSearchFolderId = AS4ResourceBrowser.ALL_RESOURCES_PATH;
				this.simulateLeafClicked(strAdvSearchFolderId+'_node');
			
			break;	
			case 'just_my_resources':
			
				strAdvSearchFolderId = AS4ResourceBrowser.MY_RESOURCES_PATH;
				this.simulateLeafClicked(strAdvSearchFolderId+'_node');
			
			break;	
			case 'hidden_resources_only':
			
				strAdvSearchFolderId = AS4ResourceBrowser.HIDDEN_RESOURCES_PATH;
				this.simulateLeafClicked(strAdvSearchFolderId+'_node');
			
			break;
		}
			
		// Finally close the lightbox.
		AS4Shell.getInstance().closeLightbox();
	},

	getSelectedRadioValue: function(strRadioGroupName)
	{
		var objSelectedRadioElement = $j('input[type="radio"][name="' + strRadioGroupName + '"]:checked');
	
		if(objSelectedRadioElement.length > 0)
		{
			return objSelectedRadioElement.val();
		}
		else
		{
			return '';
		}
	},
	setSelectedRadioValue: function(strRadioGroupName, selectedValue)
	{
		$j('input[type="radio"][name="' + strRadioGroupName + '"]').each(function(i)
		{			
			if($j(this).val() == selectedValue)
			{
				this.checked = true;
			}
		});	
	},

	showAdvancedSearch: function()
	{		
		var params = {
			callback: this.onShowAdvancedSearchLightbox.bind(this),
			height: 415,
			width: 700
		};
		
		AS4Shell.getInstance().showLightbox(params);
	},	
	onShowAdvancedSearchLightbox: function()
	{
		// Get the optimum size for the lightbox preview...
		var intBrowserViewportWidth = $j(window).width();
		var intBrowserViewportHeight = $j(window).height();
		
		var intLightboxWidth = 700;
		var intLightboxHeight = 425;
		
		var intSearchWidth = intLightboxWidth;
		var intSearchHeight = intLightboxHeight - 50;
				
		var intToolbarWidth = intSearchWidth;
		var intToolbarHeight = 40;
		
		var url = '/resource_browser/show_advanced_search_lightbox';

		var params = {
			renderMode: 'lightbox'
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, 'advanced_search', $A(), {showIndicator: false});
		
		return	'<div>' +
					'<div id="advanced_search_container" style="width:'+ intSearchWidth +'px;height:'+ intSearchHeight +'px; overflow:auto;">' + 
						
						 '<div id="advanced_search" class="centre" style="padding-top: 40%">' +
							'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br />Loading...' +
						'</div>' +
					
					'</div>' +
					'<div style="text-align:center; width:'+ intToolbarWidth +'px;height:'+ intToolbarHeight +'px; margin-top:5px; padding-top:5px; border-top: 1px dashed #000000"><a href="#" onclick="resourceBrowser.destroyAdvancedSearchFieldEvents();resourceBrowser.startAdvancedSearch();return false;" class="button small">Go ></a></div>' +
				'</div>';
	},

	createAdvancedSearchFieldEvents: function()
	{		
		// Advanced search terms field.
		$j('#adv_search_terms').focus(function ()
		{
			if($j('#adv_search_terms').val() == 'Enter free text to search name, description, meta')
			{
				$j('#adv_search_terms').val('');				
			}
		});
		$j('#adv_search_terms').blur(function ()
		{
			if($j('#adv_search_terms').val() == '')
			{
				$j('#adv_search_terms').val('Enter free text to search name, description, meta');				
			}
		});
		
		// Created by field.
		$j('#adv_created_by').focus(function ()
		{
			if($j('#adv_created_by').val() == 'Enter username, first name or last name')
			{
				$j('#adv_created_by').val('');				
			}
		});
		$j('#adv_created_by').blur(function ()
		{
			if($j('#adv_created_by').val() == '')
			{
				$j('#adv_created_by').val('Enter username, first name or last name');				
			}
		});
		
		// Date after field.
		$j('#adv_date_after').focus(function ()
		{
			if($j('#adv_date_after').val() == 'DD/MM/YYYY')
			{
				$j('#adv_date_after').val('');				
			}
		});
		$j('#adv_date_after').blur(function ()
		{
			if($j('#adv_date_after').val() == '')
			{
				$j('#adv_date_after').val('DD/MM/YYYY');				
			}
		});
		
		// Date before field.
		$j('#adv_date_before').focus(function ()
		{
			if($j('#adv_date_before').val() == 'DD/MM/YYYY')
			{
				$j('#adv_date_before').val('');				
			}
		});
		$j('#adv_date_before').blur(function ()
		{
			if($j('#adv_date_before').val() == '')
			{
				$j('#adv_date_before').val('DD/MM/YYYY');				
			}
		});
	},
	
	destroyAdvancedSearchFieldEvents: function()
	{		
		$j('#adv_search_terms').unbind();
		$j('#adv_created_by').unbind();
		$j('#adv_date_after').unbind();
		$j('#adv_date_before').unbind();
	},

	preloadAdvancedSearchForm: function()
	{		
		var strAdvSearchTerms = ($j('#search_terms').val() != '') ? $j('#search_terms').val() : 'Enter free text to search name, description, meta';
		var strCreatedBy = (this.advSearchTerms.createdBy != '') ? this.advSearchTerms.createdBy : 'Enter username, first name or last name';
		var strDateAfter = (this.advSearchTerms.dateAfter != '') ? this.advSearchTerms.dateAfter : 'DD/MM/YYYY';
		var strDateBefore = (this.advSearchTerms.dateBefore != '') ? this.advSearchTerms.dateBefore : 'DD/MM/YYYY';
			
		$j('#adv_search_terms').val(strAdvSearchTerms);
		$j('#adv_created_by').val(strCreatedBy);
		$j('#adv_date_after').val(strDateAfter);
		$j('#adv_date_before').val(strDateBefore);
		$j('#adv_resource_types').val(this.advSearchTerms.resourceType);
		
		this.setSelectedRadioValue('adv_search_authorized', this.advSearchTerms.searchAuthorized);
		this.setSelectedRadioValue('adv_resources_to_search', this.advSearchTerms.resourceToSearch);
		
		$j('#adv_search_subfolders').get(0).checked = this.advSearchTerms.searchSubfolders;
	},

	/**
	 * Sets a single resource type by which results filtered. Passing a non-numeric or null parameter will result to 
	 * the default of not filtering resources. Triggers a refresh of the browser.
	 *
	 * @param {integer} resourceType id of the resource type by which results will be filtered
	 */
	setSearchType: function(resourceType, options)
	{
    var options = Object.extend({refresh: true}, options || {});

		if(isNaN(resourceType) || !resourceType)
			this.searchTypes = $A([]);
		else
			this.searchTypes = $A([resourceType]);

		if(options.refresh)
			this.fetchResources();
	},

	/**
	 * Sets the current page of resources to fetch
	 *
	 * @param {integer} page The page of resources to load.
	 */
	setResourcesPage: function(page, options)
	{
		var options = Object.extend({refresh: true}, options || {});

		this.resourcesPage = page;

		if(options.refresh)
			this.fetchResources();
	},
    setResourcesPage_thumbnail: function(page, position, options)
	{
		var options = Object.extend({refresh: true}, options || {});

		this.resourcesPage = page;
        if (page==1)
            this.selectedLinkedResourcePosition = position;

		if(options.refresh)
			this.fetchResources(null,$('files_list_thumbnail'));
     
	},
	/**
	 * Sets the number of resources to fetch per-page
	 *
	 * @param {integer} count The number of resources to load per page. Should be one of 10, 25, 50, 100
	 */
	setResourcesPerPage: function(count)
	{
		this.resourcesPerPage = count;
		this.resourcesPage = 1;
		
		this.fetchResources();
	},
    setResourcesPerPage_thumbnail: function(count)
	{
		this.resourcesPerPage = count;
		this.resourcesPage = 1;

		this.fetchResources(null,$('files_list_thumbnail'));
	},
	/**
	 * Remove any subfolder droppables
	 */
	removeSubfolderDroppables: function()
	{
		if(!$('subfolders_list'))
			return;

		$('subfolders_list').select('.icon').each(function(element) {
			Droppables.remove(element);
		});
	},
	
	/**
	 * Creates a new resource within the current folder
	 *
	 * @param {string} type The type of resource to create
	 */
	createResource: function(type)
	{
		if(!this.currentFolderId || !this.currentFolderId.toString().match('^[+]?\\d*$'))
			return;
		
		var url = '/resource_browser/create_resource';

		var params = {
			renderMode: 'update',
			channel_id: this.currentFolderId,
			type_name: type
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([this.onCreateResourceSuccess.bind(this)]), {message: 'Creating resource...'} );
	},

	/**
	 * Makes a tree node/leaf droppable for move operations
	 *
	 * @param {Element} element The element to make droppable
	 * @param {integer} [destId] Destination folder id. If null, the folder id is automatically built from the current node
	 */
	makeNodeDroppable: function(element, destId)
	{
		Droppables.add(element, {
			accept: 'draggable',
			hoverclass: 'droppable', 
			onDrop: function(draggable, droppable) {
				var resourceId = $(draggable).id.split('_')[1];
				var folderId = (destId == null ? $(droppable).id.split('_')[1] : destId);

				if(this.currentFolderId == folderId)
					return;

				var url = '/resource_browser/move_resource';
				var params = {
					resource_id: resourceId,
					source_folder_id: this.currentFolderId,
					dest_folder_id: folderId
				};

				AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([this.onMoveResourceSuccess.bind(this)]), { message: 'Moving Resource...' })
				$(draggable).remove();
			}.bind(this)
		});
	},

	// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
	// AJAX Callbacks

	onBeginFetchResources: function(blnInvisibleUpdate)
	{
		var blnInvisibleUpdate = (typeof(blnInvisibleUpdate) != 'undefined') ? blnInvisibleUpdate : false;

		if(!blnInvisibleUpdate)
		{
			// Update the resource browser list to show a loading panel
			$('files_list').update('<div id="files_list" class="centre" style="padding-top: 20%">' +
			'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br /><span id="loading_spinner_text">...Loading Resources...</span>' +
			'</div>');			
		}
	},
        onBeginFetchResources_thumbnail: function(blnInvisibleUpdate)
	{
		var blnInvisibleUpdate = (typeof(blnInvisibleUpdate) != 'undefined') ? blnInvisibleUpdate : false;

		if(!blnInvisibleUpdate)
		{
			// Update the resource browser list to show a loading panel
			$('files_list_thumbnail').update('<div id="files_list_thumbnail" style="align: centre;padding-top: 20%">' +
			'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br /><span id="loading_spinner_text">...Loading Resources...</span>' +
			'</div>');
		}
	},
	onUpdateOrderIndexSuccess: function(transport, target)
	{
		if(!transport.headerJSON.order || !transport.headerJSON.success)
			return;

		// Update the treeview to reflect the new order
		if($('folderleaf_' + this.currentFolderId) && $('folderleaf_' + this.currentFolderId).down('.expander').hasClassName('open'))
			this.fetchFolders(this.currentFolderId, $('folderleaf_' + this.currentFolderId));
	},
	
	onResourceWidgetAttachmentChecked: function(transport, target)
	{
		if(!transport.headerJSON.resource_widget_checks)
			return;
			
		msg = "The following resources have been assigned to widget in page layouts. Click ok to Delete all of the selected resources. \n";
		
	},

	onCreateResourceSuccess: function(transport, target)
	{
		
		if(!transport.headerJSON.resource_id)
			return;

		// Redirect the browser to edit the newly create resource
		AS4Shell.getInstance().redirect('/resource_editor/edit/id/' + transport.headerJSON.resource_id);
	},

	onMoveResourceSuccess: function(transport, target)
	{
		if(!transport.headerJSON.resource_id)
			return;

		switch(transport.headerJSON.success)
		{
			case "true":
				var resourceId = transport.headerJSON.resource_id;
				new Effect.Fade($('resourceitem_' + resourceId));
			break;
			
			case "confirm":
			
			this.movingResourceJSON = transport.headerJSON;
				
			var intLightboxWidth = 400;
			var intLightboxHeight = 160;
	
			var params = {
				callback: this.onCreateShowConfirmMoveResourcesLightbox.bind(this),
				height: intLightboxHeight,
				width: intLightboxWidth
			};
			
			AS4Shell.getInstance().showLightbox(params);
			break;	
		}
		
	},

	confirmResourceMove: function(header)
	{
		
		this.movingResource.css("display", "none");	
	
		// Then fire of AJAX request to update server.
		var url = '/resource_browser/move_resource';
		var params = {
			resource_id: this.movingResourceJSON.resource_id,
			source_folder_id: this.movingResourceJSON.src_folder_id,
			dest_folder_id: this.movingResourceJSON.dest_folder_id,
			confirm: "true"
		};
		
		AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving resource...' });
	},

	cancelResourceMove: function(header)
	{
		
		this.movingResource.css("display", "none");	
	
		// Then fire of AJAX request to update server.
		var url = '/resource_browser/move_resource';
		var params = {
			resource_id: this.movingResourceJSON.resource_id,
			dest_folder_id: this.movingResourceJSON.src_folder_id,
			source_folder_id: this.movingResourceJSON.dest_folder_id,
			confirm: "true"
		};
		
		//AS4Shell.getInstance().ajaxUpdate(url, params, null, $A([function(){}]), { message: 'Moving resource...' });
	}
});

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// Resource Display Modes
//
AS4ResourceBrowser.DISPLAY_MODE_BLOCK = 'block';
AS4ResourceBrowser.DISPLAY_MODE_LIST = 'list';
AS4ResourceBrowser.DISPLAY_MODE_THUMBNAIL = 'thumbnail';

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// Resource Locations
AS4ResourceBrowser.ROOT_RESOURCES_PATH = 'root_resources';
AS4ResourceBrowser.MY_RESOURCES_PATH = 'my_resources';
AS4ResourceBrowser.ALL_RESOURCES_PATH = 'all_resources';
AS4ResourceBrowser.ALL_FOLDERS_PATH = 'all_folders';
AS4ResourceBrowser.HIDDEN_RESOURCES_PATH = 'hidden_resources';
AS4ResourceBrowser.UNAPPROVED_RESOURCES_PATH = 'unapproved_resources';

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// Have included media library code here, as could not find a way in AS4 of loading the JS files
// defined in the view class in order so as to meet dependancy requirements...
// ... somebody please change if there is a better way! 
/**
 * AS4ResourceLibrary Component Controller
 */
var AS4ResourceLibraryLinkToFolder = Class.create(AS4ResourceBrowser, {
	
	/**
	 * @constructor
	 */
	initialize: function($super)
	{
		$super;

 		this.iconDisplayMode = 'linkToFolder';
		this.currentFolderId = AS4ResourceBrowser.MY_RESOURCES_PATH;
		
		// Cached event listeners
		this.cachedOnExpanderClicked = this.onExpanderClicked.bindAsEventListener(this);
		this.cachedOnLeafClicked = this.onLeafClicked.bindAsEventListener(this);

		// Default pagination options
		this.resourcesPerPage = 10;

		// Media browsers show only public, approved and unhidden resources
		this.isMediaBrowser = true;

		// Hide the global ajax status indicator
		this.ajaxUpdateOptions = { showIndicator: false };

		// Default do not filter by type
		this.searchTypes = $A([]);
	},
	
	/**
	 * Override the fetchResources method to forward to fetchFolders if the currently selected folder is Channels
	 * and Folders
	 * 
	 * @param {string} [path] The location to load. If numeric, the corresponding folder id will be retrieved. If null, the currently displayed path will be used.
	 * @param {target} [target] The node element to update with the returned content. If undefined, $('folders_tree') will be used.
	 */
	fetchResources: function($super, path)
	{
		var path = (path ? path : this.currentFolderId);
		
		if(path == 'folders')
			this.fetchFolders();	
		else
			$super(path, $('media_library'));
	},

	/**
	 * Override the fetchFolders method to return all folders managable by the current user
	 *
	 */
	fetchFolders: function()
	{
		var target = $('media_library');
		var url = "/resource_browser/fetch_folders";
		var options = Object.extend({}, options);

		this.onBeginFetchResources();

		var params = {
			renderMode: 'update',
			path: AS4ResourceBrowser.ALL_FOLDERS_PATH, 
			options: $H(options).toJSON(),
			icon_display_mode: this.iconDisplayMode,
			is_media_browser: this.isMediaBrowser,
			folderPage: this.resourcesPage,
			folderLimit: this.resourcesPerPage,
			terms: this.searchTerms
		};

		AS4Shell.getInstance().ajaxUpdate(url, params, target, $A([this.onFetchFoldersSuccess.bind(this)]), { showIndicator: false } );
	},
	

	// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
	// AJAX Callback Methods
	
	/**
	 * Callback handler triggered by a resource being clicked within the library. Can be overridden
	 * by the page controller for custom functionality
	 *
	 * options can be 
	 * {
	 *  title: string Title of the selected resource
	 *  [type]: {string} One of 'resource' or 'folder'. Default is resource.
	 * }
	 *
	 * @param {integer} id Id of the resource to insert
	 * @param {Hash} type Type of the resource, either 'resource' or 'folder'. Default is 'resource'.
	 */
	onResourceClicked: function(resource_id, options)
	{
		options = Object.extend({
				title: null,
				type: 'resource',
        event: null
			},
			options || {});
	},

	onBeginFetchResources: function($super)
	{
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
		// Update spinners
		$('media_library').update('<div id="ml_status">' +
		'<img src="/app/common/assets/images/ajax-loader.gif" alt="Loading..>" width="24" height="24" /><br />...Loading Media...' +
		'</div>');
	},

	onFetchResourcesSuccess: function(transport, target)
	{
		// Ignore exceptions
		if(transport.headerJSON && transport.headerJSON.exception)
			return;
	},
 
 	onFetchFoldersSuccess: function(transport, target)
	{
		// Ignore exceptions
		if(transport.headerJSON && transport.headerJSON.exception)
			return;
	}
});
