Ext.ns('Ext.ux.RapidApp');

Ext.ux.RapidApp.AppTreeLoader = Ext.extend(Ext.tree.TreeLoader,{
  getParams: function(node) {
    var params = Ext.ux.RapidApp.AppTreeLoader.superclass.getParams.apply(this,arguments);
    var tp = node.getOwnerTree();
    if(tp.root == node) {
      params.root_node = true;
    }
    return params;
  }
});

Ext.ux.RapidApp.AppTree = Ext.extend(Ext.tree.TreePanel,{
	
	add_node_text: 'Add',
	add_node_iconCls: 'ra-icon-add',
	add_node_url: null,
	
	delete_node_text: 'Delete',
	delete_node_iconCls: 'ra-icon-delete',
	delete_node_url: null,
	
	rename_node_text: 'Rename',
	rename_node_iconCls: 'ra-icon-textfield-rename',
	rename_node_url: null,
	
	reload_node_text: 'Reload',
	reload_node_iconCls: 'ra-icon-refresh',
	
	copy_node_text: 'Copy',
	copy_node_iconCls: 'ra-icon-element-copy',
	copy_node_url: null,
	
	node_action_reload: true,
	node_action_expandall: true,
	node_action_collapseall: true,
	
	use_contextmenu: false,
	no_dragdrop_menu: false,
	setup_tbar: false,
	no_recursive_delete: false,
	no_recursive_copy: false,
	
	// Controls if nodes can drag/drop between nodes as well as into (append) nodes
	ddAppendOnly: true,
	
	// Set this to true to display extra options to dump the node and tree to
	// the firebug console in the node right-click context menu
	debug_menu_options: false,
	
	initComponent: function() {
  
  // --------
  // initialize the loader 
  //   use our customized Ext.ux.RapidApp.AppTreeLoader
  var l = this.loader;
  if(!l){
      l = new Ext.ux.RapidApp.AppTreeLoader({
          dataUrl: this.dataUrl,
          requestMethod: this.requestMethod
      });
  }else if(Ext.isObject(l) && !l.load){
      l = new Ext.ux.RapidApp.AppTreeLoader(l);
  }
  this.loader = l;
  // --------
		
		this.initDragAndDrop();
		
		if(!this.node_actions) {
			this.node_actions = [];
			
			if(this.node_action_reload) {
				this.node_actions.push({
					text: this.reload_node_text,
					iconCls: this.reload_node_iconCls,
					handler: this.nodeReload,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.node_action_expandall) {
				this.node_actions.push({
					text: 'Expand All',
					iconCls: 'ra-icon-tree-expand',
					handler: this.nodeExpandAll,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.node_action_collapseall) {
				this.node_actions.push({
					text: 'Collapse All',
					iconCls: 'ra-icon-tree-collapse',
					handler: this.nodeCollapseAll,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			
			if(this.node_actions.length > 0) {
				this.node_actions.push('-');
			}
			
			if(this.rename_node_url) {
				this.node_actions.push({
					text: this.rename_node_text,
					iconCls: this.rename_node_iconCls,
					handler: this.nodeRename,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.delete_node_url) {
				this.node_actions.push({
					text: this.delete_node_text,
					iconCls: this.delete_node_iconCls,
					handler: this.nodeDelete,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: true
				});
			}
			
			if(this.add_node_url) {
				this.node_actions.push({
					text: this.add_node_text,
					iconCls: this.add_node_iconCls,
					handler: this.nodeAdd,
					rootValid: true,
					leafValid: false,
					noTbar: false,
					tbarIconOnly: false
				});
			}
			
			if(this.copy_node_url) {
				this.node_actions.push({
					text: this.copy_node_text,
					iconCls: this.copy_node_iconCls,
					handler: this.nodeCopyInPlace,
					rootValid: false,
					leafValid: true,
					noTbar: false,
					tbarIconOnly: false
				});
			}
			
			if(this.expand_node_url) {
				this.on('expandnode',function(node){
					this.persistNodeExpandState(node,1);
				},this);
				this.on('collapsenode',function(node){
					this.persistNodeExpandState(node,0);
				},this);
			}
			
				
			if(Ext.isArray(this.extra_node_actions)) {
				Ext.each(this.extra_node_actions,function(action) {
					this.node_actions.push(action);
				},this);
			}
			
			// Remove the divider if it is the last item:
			if(this.node_actions.length > 0 && this.node_actions[this.node_actions.length - 1] == '-') {
				this.node_actions.pop();
			}
		}
		
		if(this.setup_tbar) {
		
			var init_tbar_items = [];
			if(Ext.isArray(this.tbar)) { init_tbar_items = this.tbar; }
			if(!Ext.isObject(this.tbar)) {
				this.tbar = {
					xtype: 'toolbar',
					enableOverflow: true,
					items: init_tbar_items
				};
			}
			
			var tbar_items = this.getTbarActionsButtons();
			if(tbar_items.length > 0) {
				this.tbar.items.push('->');
				Ext.each(tbar_items,function(item) {
					this.tbar.items.push(item);
				},this);
			}
		}
		
		if(this.use_contextmenu) { 
			this.on('contextmenu',this.onContextmenu,this);
		}
		
		this.on('afterrender',function() {
			// Init button states with the root node first:
			this.notifyActionButtons(this.root);
			
			this.getSelectionModel().on('selectionchange',function(selMod,node) {
				this.notifyActionButtons(node);
			},this);
		},this);
		
		
    // -----
    // Show a load mask for the loading of the root node - GitHub #126
    this.on('beforeload',function(node) {
      if(node == this.root) {
        if(!this.rootLoadMask) {
          // For good measure, try set the mask on the parent container. This is useful
          // because the height of TreePanel might be 0px before the root node is loaded
          var targetCmp = this.ownerCt ? this.ownerCt : this;
          this.rootLoadMask = new Ext.LoadMask(targetCmp.getEl(), {msg:"Loading Tree Data..."});
        }
        this.rootLoadMask.show(); 
      }
    },this);
    
    this.on('load',function(node) {
      this.rootLoadMask && this.rootLoadMask.hide();
    },this);
    // -----
    
    // -- Reset node expand state on exception - GitHub Issue #127
    this.loader.on('loadexception',function(loader,node,response){
      if(node.expanded) { node.collapse(true); }
      node.loaded = false;
      node.childrenRendered = false;
      node.ui.updateExpandIcon();
    },this,{ delay: 50 });
		
		Ext.ux.RapidApp.AppTree.superclass.initComponent.call(this);
	},
  
  expandChildNodes: function() {
    if(!this.childNodes) { return; }
    return Ext.ux.RapidApp.AppTree.superclass.expandChildNodes.apply(this,arguments);
  },
	
	persistNodeExpandState: function(node,state) {
		if(node == this.root) { return false; } // <-- ignore the root node
		this.queuePersistExpandUpdates(node.id,state);
	},
	
	queuePersistExpandUpdates: function(id,state) {
		this.initPersistExpandQueue();
		this.persistExpandQueue.nodes.push(id);
		this.persistExpandQueue.states.push(state);
		
		if(!this.processPersistExpandPending) {
			this.processPersistExpandPending = true;
			this.processPersistExpandQueue.defer(1000,this);
		}
	},
	
	initPersistExpandQueue: function(delete_current) {
		if(delete_current && this.persistExpandQueue) { 
			delete this.persistExpandQueue;
		}
		if(! this.persistExpandQueue) {
			this.persistExpandQueue = { nodes: [], states: [] };
		}
	},
	
	processPersistExpandQueue: function() {
		this.processPersistExpandPending = false;
		
		// do nothing if the queue is empty:
		if(this.persistExpandQueue.nodes.length == 0) { return true; }
		
		var queue = this.persistExpandQueue;
		this.initPersistExpandQueue(true);
		
		Ext.Ajax.request({
			url: this.expand_node_url,
			params: { node: queue.nodes, expanded: queue.states },
			scope: this,
			success: Ext.emptyFn //<-- assume it worked, don't do anything if it didn't
		});
	},
	
	initDragAndDrop: function() {
		if(this.copy_node_url || this.move_node_url) {
			this.enableDD = true;
			//this.ddAppendOnly = true; //<-- this disables setting "order"
			this.on('nodedragover',this.onNodeDragOver,this);
			this.on('beforenodedrop',this.beforeNodeDropHandler,this);
		}
	},
	
	onNodeDragOver: function(dragOverEvent) {
		var t = dragOverEvent.target;
		var leafOnly = false;
		
		// Nodes with allowLeafDropOnly will only allow leaf nodes dropped on them:
		if(t.attributes.allowLeafDropOnly) { leafOnly = true; }
		
		// parents can also restrict their children with allowChildrenLeafDropOnly:
		if(t.parentNode && t.parentNode.attributes.allowChildrenLeafDropOnly) { leafOnly = true; }
		
		if(leafOnly && !dragOverEvent.data.node.isLeaf()) {
			dragOverEvent.cancel = true;
		}
	},
	
	beforeNodeDropHandler: function(dropEvent) {
		// nothing but 'append' should get this far if ddAppendOnly is true
		if(this.ddAppendOnly && dropEvent.point !== 'append') { return; }
		
		var node = dropEvent.data.node;
		var target = dropEvent.target;
		var e = dropEvent.rawEvent;
		var point = dropEvent.point;
		var point_node;
		
		// point of 'before' or 'after' for order/positioning:
		if(point !== 'append') {
			point_node = target;
			target = target.parentNode;
		}
		
		if(this.nodeDropMenu(node,target,e,point,point_node)) {
			// If we're here it means that the menu has been displayed.
			// We are setting these attributes to prevent the "repair" ui
			// since we have to run an async round-trip to the server
			dropEvent.cancel = true;
			dropEvent.dropStatus = true;
		}
	},
	
	nodeDropMenu: function(node,target,e,point,point_node) {

		var menuItems = [];
		
		/* New: Disable drop/copy option if 'no_dragdrop_menu' is true.
		   the original logic/intent was that this setting would allow
		   either *copy* or *move* to happen automatically, but after
		   thinking about it more, automatic drag/drop copy doesn't make
		   a lot of sense, and now a "Copy In-place" option is being added
		   as a right-click "action" and will use 'copy_node_url' too, so
		   I am just disabling this auto copy or auto move feature for now,
		   leaving only 'auto move' when no_dragdrop_menu is turned on
		*/
		if(this.copy_node_url && !this.no_dragdrop_menu) {
			menuItems.push({
				text: 'Copy here',
				iconCls: 'ra-icon-element-copy',
				handler: function(no_reloads) { 
					this.nodeCopyMove(node,target,this.copy_node_url,false,point,point_node,no_reloads); 
				},
				scope: this
			});
		}
		
		if(this.move_node_url) {
			menuItems.push({
				text: 'Move here',
				iconCls: 'ra-icon-element-into',
				handler: function(no_reloads) { 
					this.nodeCopyMove(node,target,this.move_node_url,true,point,point_node,no_reloads); 
				},
				scope: this
			});
		}
		
		if(!menuItems.length) { return false; }
		
		// -- If no drop menu is set, and there is exactly 1 option (copy or move, but not both), 
		// run that one option automatically:
		// Update:
		//  see above 'copy' comment. menuItems.length should now always be 1 if no_dragdrop_menu
		//  is on, but I am leaving the redundant check in place in case this auto copy or auto
		//  move feature wants to be turned back on...
		if(this.no_dragdrop_menu && menuItems.length == 1) {
			var item = menuItems[0];
			var no_reloads = true;
			item.handler.defer(10,item.scope,[no_reloads]);
			//return true;
			
			// return false to *prevent* cancelling the GUI drag/drop:
			return false;
		}
		// --
		
		
		menuItems.push('-',{
			text: 'Cancel',
			iconCls: 'x-tool x-tool-close',
			handler: Ext.emptyFn
		});
		
		var menu = new Ext.menu.Menu({ items: menuItems });
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
		return true;
	},
	
	nodeCopyMove: function(node,target,url,remSrc,point,point_node,no_reloads) {
		
		var params = { 
			node: node.id,
			target: target.id,
			point: point
		};
		
		if(point_node) { params.point_node = point_node.id; }
		
		Ext.Ajax.request({
			url: url,
			params: params,
			scope: this,
			success: function() {
				
				// no_reloads will be on when there is only one copy/move action
				// setup and thus no need for a menu, and thus no need to cancel
				// the GUI move, and thus no need to do node reloading because
				// the GUI move operation is properly tracking what happened by itself:
				if(!no_reloads) {
					
					// no_reloads also overrides/disables remSrc setting... so far this
					// logic was tested and needed with "move" as the only DD operation
					// and no menu display... again, since we don't cancel the GUI move,
					// ExtJS is automatically handling this... TODO: what happens if copy
					// were the only operation with no menu? Would that even make sense? 
					// probably not...
					if(remSrc) {
						node.parentNode.removeChild(node,true);
					}

					this.nodeReload(target);
				}
			},
			failure: function() {
				// If the operation failed on the server side, reload the whole tree to
				// be safe and avoid any possible interface/database inconsistency
				this.nodeReload(this.root);
			}
		});
	},
	
	actionValidForNode: function(action,node) {
		if(!node) { return false; }
		
		/* Broad validation by node type and action rules: */
		if(!action.rootValid && (node == this.root || node.attributes.rootValidActions)) { 
			return false; 
		}
		
		if(!action.leafValid && node.isLeaf()) { 
			return false; 
		}
		
		
		/* Per-action name validations: */
		if(action.text == this.add_node_text) {
			// The add action can be turned off for any given node by setting "allowDelete" to false:
			if(typeof node.attributes.allowAdd !== "undefined" && !node.attributes.allowAdd) {
				return false;
			}
		}
		
		else if(action.text == this.rename_node_text) {
			// The rename action can be turned off for any given node by setting "allowRename" to false:
			if(typeof node.attributes.allowRename !== "undefined" && !node.attributes.allowRename) {
				return false;
			}
		}
		
		else if(action.text == this.reload_node_text) {
			// Nodes with static array of children can't be reloaded from the server:
			// Update: this is now handled in the nodeReload function
			//if(typeof node.attributes.children !== "undefined") {
			//	return false;
			//}
			
			// The reload action can be turned off for any given node by setting "allowReload" to false:
			if(typeof node.attributes.allowReload !== "undefined" && !node.attributes.allowReload) {
				return false;
			}
		}
		
		else if(action.text == this.delete_node_text) {
			if(this.no_recursive_delete && node.isLoaded && node.isLoaded() && node.hasChildNodes()) { 
				return false; 
			}
			// The delete action can be turned off for any given node by setting "allowDelete" to false:
			if(typeof node.attributes.allowDelete !== "undefined" && !node.attributes.allowDelete) {
				return false;
			}
		}
		
		
		else if(action.text == this.copy_node_text) {
			if(this.no_recursive_copy && node.isLoaded && node.isLoaded() && node.hasChildNodes()) { 
				return false; 
			}
			// The copy action can be turned off for any given node by setting "allowCopy" to false:
			if(typeof node.attributes.allowCopy !== "undefined" && !node.attributes.allowCopy) {
				return false;
			}
		}
		
		// If we made it to the end without being invalidated, then the action is valid for this node:
		return true;
	},
	
	notifyActionButtons: function(node) {
		Ext.each(this.tbarActionsButtons,function(btn) {
			if(btn.notifyCurrentNode) {
				btn.notifyCurrentNode.call(btn,node);
			}
		},this);
	},
	
	getTbarActionsButtons: function() {
		var items = [];
		Ext.each(this.node_actions,function(action) {
			if(Ext.isString(action)) {
				items.push(action);
				return;
			}
			var cnf = {
				tree: this,
				nodeAction: action,
				xtype: 'button',
				text: action.text,
				iconCls: action.iconCls,
				handler: function() {
					var node = this.getSelectionModel().getSelectedNode();
					action.handler.call(this,node);
				},
				scope: this
			};
			if (action.tbarIconOnly) {
				cnf.tooltip = cnf.text;
				cnf.overflowText = cnf.text;
				delete cnf.text;
			}
			
			cnf.notifyCurrentNode = function(node) {
				var valid = this.tree.actionValidForNode(this.nodeAction,node);
				this.setDisabled(!valid);
			}
			
			var button = new Ext.Button(cnf);
			items.push(button);
		},this);
		this.tbarActionsButtons = items;
		return this.tbarActionsButtons;
	},
	
	onContextmenu: function(node,e) {

		var menuItems = [];
		Ext.each(this.node_actions,function(action) {
			if(Ext.isString(action)) {
				// Prevent adding a divider as the first item:
				if(action == '-' && menuItems.length == 0) { return; }
				menuItems.push(action);
				return;
			}
			if(!this.actionValidForNode(action,node)) { return; }
			menuItems.push({
				text: action.text,
				iconCls: action.iconCls,
				handler: function() { action.handler.call(this,node); },
				scope: this
			});
			
		},this);
		
		
		
		//-- for debugging:
		if(this.debug_menu_options) {
			menuItems.push(
				'-',
				{
					text: 'console.dir(node)',
					handler: function() { console.dir(node); }
				},
				{
					text: 'console.dir(tree)',
					handler: function() { console.dir(node.getOwnerTree()); }
				}
			);
		}
		//--
		
		// remove a divider if it ends up as the last item:
		if(menuItems.length && menuItems[menuItems.length-1] == '-') {
			menuItems.pop();
		}
		
		
		if(menuItems.length == 0){ return false; }
		
		var menu = new Ext.menu.Menu({ items: menuItems });
		node.select();
		var pos = e.getXY();
		pos[0] = pos[0] + 10;
		pos[1] = pos[1] + 5;
		menu.showAt(pos);
	},
	
	nodeReload: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		return this.nodeReloadRecursive(node);
	},
	
	// Recursively calls itself on parent nodes until it reaches a 
	// node that can be reloaded:
	nodeReloadRecursive: function(node) {
		node = node || this.root; //<-- default to the root node
		if(node !== this.root) {
			// Leaf nodes can't be reloaded from the server, but neither can
			// non-leaf nodes if they have a static defined list of children:
			if(node.isLeaf() || node.attributes.children) {
				return this.nodeReloadRecursive(node.parentNode);
			}
		}
		this.getLoader().load(node,function(tp){
			node.expand();
		});
	},
	
	nodeExpandAll: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node.isLeaf() && node.parentNode) { node = node.parentNode; }
		node.expand(true);
	},
	
	nodeCollapseAll: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node.isLeaf() && node.parentNode) { node = node.parentNode; }
		node.collapse(true);
	},
	
	nodeRename: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		if(node == this.root) { return; }
		return this.nodeApplyDialog(node,{
			title: this.rename_node_text,
			url: this.rename_node_url,
			value: node.attributes.text
		});
	},
	
	nodeAdd: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }
		
		return this.nodeApplyDialog(node,{
			title: this.add_node_text,
			url: this.add_node_url
		});
	},
	
	nodeDelete: function(node) {
		if (! node) { 
			Ext.Msg.alert('Nothing selected to Delete','You must select an item to delete.');
			return;
		}
		// Ignore attempts to delete the root node:
		if(node == this.root) { return; }
		var tree = this;
		var params = { node: node.id };

		var ajaxFunc = function() {
			Ext.Ajax.request({
				url: tree.delete_node_url,
				params: params,
				success: function() {
					node.parentNode.removeChild(node,true);
					//var pnode = node.parentNode;
					//tree.getLoader().load(pnode,function(tp){
					//	pnode.expand();
					//});
				}
			});
		};

		var Func = ajaxFunc;

		if (node.hasChildNodes()) {
			
			if(this.no_recursive_delete) {
				Ext.Msg.alert(
					'Cannot Delete',
					'"' + node.attributes.text + '" cannot be deleted because it contains child items.'
				);
				return;
			}
			
			params['recursive'] = true;
			Func = function() {
				Ext.ux.RapidApp.confirmDialogCall(
					'Confirm Recursive Delete',
					'"' + node.attributes.text + '" contains child items, they will all be deleted.<br><br>' +
					 'Are you sure you want to continue ?',
					ajaxFunc
				);
			}
		}

		Ext.ux.RapidApp.confirmDialogCall(
			'Confirm Delete',
			'Really delete "' + node.attributes.text + '" ?',
			Func
		);
	},
	
	// This works like an action (right-click) instead of a drag-drop
	// like nodeCopyMove. So it is really more like nodeAdd
	nodeCopyInPlace: function(node) {
		if(!node) { node = this.activeNonLeafNode(); }

		return this.nodeApplyDialog(node,{
			title: this.copy_node_text,
			url: this.copy_node_url,
			params : {
				node: node.id,
				target: node.parentNode.id,
				point: 'below',
				point_node: node.id
			},
			value: node.attributes.text + ' (Copy)'
		});
	},
	
	// General purpose functon for several operations, like add, rename
	nodeApplyDialog: function(node,opt) {
		var tree = this;
		var cnf = Ext.apply({
			url: null, // <-- url is required
			title: 'Apply Node',
			name: 'name',
			fieldLabel: 'Name',
			labelWidth: 40,
			height: 130,
			width: 350,
			params: { node: node.id },
			value: null
		},opt);
		
		if(!cnf.url) { throw "url is a required parameter"; }
		
		var Field = Ext.create({
			xtype: 'textfield',
			name: cnf.name,
			fieldLabel: cnf.fieldLabel,
			value: cnf.value,
			anchor: '100%'
		},'field');
		
		Field.on('afterrender',function(field){ field.show.defer(300,field); });
		
		//Focus the field and put the cursor at the end
		Field.on('show',function(field){
			field.focus();
			field.setCursorPosition(1000000);
		},this);

		var fieldset = {
			xtype: 'fieldset',
			style: 'border: none',
			hideBorders: true,
			labelWidth: cnf.labelWidth,
			border: false,
			//items: items
			items: Field
		};

		var winform_cfg = {
			title: cnf.title,
			height: cnf.height,
			width: cnf.width,
			url: cnf.url,
			useSubmit: true,
			params: cnf.params,
			fieldset: fieldset,
			
			success: function(response,options) {
				var res = options.result;
				
				// if 'new_text' is supplied in the response then update the text of current node
				if (res.new_text) {
					node.setText(res.new_text);
				}
				
				// if 'child' is supplied in the response then we add it as a child to the current node
				if (res.child) {
					var newChild = tree.getLoader().createNode(res.child);
					
					if(res.child_after) { // <-- for 'copy in place'
						node.parentNode.insertBefore(newChild,node.nextSibling);
					}
					else {
						node.expand();
						node.appendChild(newChild);
					}
					//newChild.ensureVisible();
				}
				
				// If neither 'child' nor 'new_text' is in the reponse we reload the node
				if(!res.new_text && !res.child) {
					tree.nodeReload(node);
					
				}
				
				
			}
		};
		Ext.ux.RapidApp.WinFormPost(winform_cfg);
	},
	
	activeNonLeafNode: function() {
		var node = this.getSelectionModel().getSelectedNode();
		if(node) {
			// If this is a leaf node, it can't have childred, so use the parent node:
			if(node.isLeaf() && node.parentNode) { 
				node = node.parentNode;
			}
		}
		else {
			node = this.root;
		}
		return node;
	}
	
});
Ext.reg('apptree',Ext.ux.RapidApp.AppTree);



Ext.ux.RapidApp.AppTree_rename_node = function(node) {
	var tree = node.getOwnerTree();

	return tree.nodeApplyDialog(node,{
		title: "Rename",
		url: tree.rename_node_url,
		value: node.attributes.text
	});
	
	
	var items = [
		{
			xtype: 'textfield',
			name: 'name',
			fieldLabel: 'Name',
			value: node.attributes.text,
			anchor: '100%',
			listeners: {
				'afterrender': function() { 
					// try to focus the field:
					this.focus('',10); 
					this.focus('',200);
					this.focus('',500);
				}
			}
		}
	];

	var fieldset = {
		xtype: 'fieldset',
		style: 'border: none',
		hideBorders: true,
		labelWidth: 40,
		border: false,
		items: items
	};

	var winform_cfg = {
		title: "Rename",
		height: 130,
		width: 350,
		url: tree.rename_node_url,
		useSubmit: true,
		params: {
			node: node.id
		},
		fieldset: fieldset,
		
		success: function(response,options) {
			var res = options.result;
			if(res.new_name) {
				node.setText(res.new_name);
			}
		}
	};
	
	Ext.ux.RapidApp.WinFormPost(winform_cfg);
}


Ext.ux.RapidApp.AppTree_contextmenu_handler = function(node,e) {

		var menu = new Ext.menu.Menu({
			items: [{
				iconCls: 'ra-icon-textfield-rename',
				text: 'Rename',
				handler: function(item) {
					Ext.ux.RapidApp.AppTree_rename_node(node);
				}
			}]
		});
		node.select();
		menu.showAt(e.getXY());
		//menu.show(node.ui.getEl());
}

Ext.ux.RapidApp.AppTree_select_handler = function(tree) {

	var node = tree.getSelectionModel().getSelectedNode();

	return {
		value: node.id,
		display: node.attributes.text
	};

}


Ext.ux.RapidApp.AppTree_setValue_translator = function(val,tf,url) {
	if(val.indexOf('/') > 0) { tf.translated = false; }
	if(!tf.translated) {
		Ext.Ajax.request({
			url: url,
			params: { node: val },
			success: function(response) {
				var res = Ext.decode(response.responseText);
				tf.translated = true; // <-- prevent recursion
				tf.dataValue = res.id;
				tf.setValue(res.text);
			}
		});
	}
	else {
		return val;
	}
}


Ext.ns('Ext.ux.RapidApp.AppTree');
Ext.ux.RapidApp.AppTree.jump_to_node_id = function(tree,id) {

	var parents_arr = function(path,arr) {
		if (!arr) arr = [];
		if (path.indexOf('/') < 0) {
			return arr;
		}

		var path_arr = path.split('/');

		var item = path_arr.pop();
		var path_str = path_arr.join('/');
		arr.push(path_str);
		return parents_arr(path_str,arr);
	}

	var select_child = function(id,parents,lastpass) {

		var par = parents.pop();
		if(!par) return;

		var node = tree.getNodeById(par);
		if(!node) return;

		node.loaded = false;
		node.expand(false,false,function(){
			if(parents.length > 0) {
				select_child(id,parents);
			}
			else {
				node.select();
			}
		});
	}

	var parents = parents_arr(id);
	parents.unshift(id);

	return select_child(id,parents);
};


Ext.ux.RapidApp.AppTree.get_selected_node = function(tree) {

	var node = tree.getSelectionModel().getSelectedNode();
	if(node) {
		// If this is a leaf node, it can't have childred, so use the parent node:
		if(node.isLeaf() && node.parentNode) { 
			var parent = node.parentNode;
			node = parent;
		}
		id = node.id;
	}
	else {
		node = tree.root;
	}

	return node;
}

Ext.ux.RapidApp.AppTree.add = function(tree,cfg) {

	var url;
	if (Ext.isObject(cfg)) {
	
	}
	else {
		url = cfg;
	}
	
	var items = [
		{
			xtype: 'textfield',
			name: 'name',
			fieldLabel: 'Name',
			listeners: {
				'afterrender': function() { 
					// try to focus the field:
					this.focus('',10); 
					this.focus('',200);
					this.focus('',500);
				}
			}
		}
	];

	var fieldset = {
		xtype: 'fieldset',
		style: 'border: none',
		hideBorders: true,
		labelWidth: 60,
		border: false,
		items: items
	};

	var node = Ext.ux.RapidApp.AppTree.get_selected_node(tree);
	var id = node.id;

/*
	var node = tree.getSelectionModel().getSelectedNode();
	var id = "root";
	if(node) {
		// If this is a leaf node, it can't have childred, so use the parent node:
		if(node.isLeaf() && node.parentNode) { 
			var parent = node.parentNode;
			node = parent;
		}
		id = node.id;
	}
*/
	
	var winform_cfg = {
		title: "Add",
		height: 130,
		width: 250,
		url: url,
		useSubmit: true,
		params: {
			node: id
		},
		fieldset: fieldset,
		success: function(response) {
			tree.getLoader().load(node,function(tp){
				node.expand();
			});
		}
	};
	
	Ext.apply(winform_cfg,cfg);
	Ext.ux.RapidApp.WinFormPost(winform_cfg);
}


Ext.ux.RapidApp.AppTree.del = function(tree,url) {

	var node = tree.getSelectionModel().getSelectedNode();
	var id = "root";
	if(node) id = node.id;

	var params = {
		node: id
	};

	var ajaxFunc = function() {
		Ext.Ajax.request({
			url: url,
			params: params,
			success: function() {
				var pnode = node.parentNode;
				tree.getLoader().load(pnode,function(tp){
					pnode.expand();
				});
			}
		});
	};

	var Func = ajaxFunc;

	if (node.hasChildNodes()) {
		params['recursive'] = true;
		Func = function() {
			Ext.ux.RapidApp.confirmDialogCall(
				'Confirm Recursive Delete',
				'"' + node.attributes.text + '" contains child items, they will all be deleted.<br><br>' +
				 'Are you sure you want to continue ?',
				ajaxFunc
			);
		}
	}

	Ext.ux.RapidApp.confirmDialogCall(
		'Confirm Delete',
		'Really delete "' + node.attributes.text + '" ?',
		Func
	);
}







Ext.ux.RapidApp.AppTree.ensure_recursive_load = function(tree,callback,scope) {
	
	var func = function() {
		if(callback) {
			if(!scope) { scope = tree; }
			callback.call(scope);
		}
	};
	
	if(tree.recursive_load_complete) { return func(); }
	
	var pnode = tree.root;
	var expand_func;
	expand_func = function(node) {
		tree.recursive_load_complete = true;
		this.un('expand',expand_func);
		func();
	}
	pnode.on('expand',expand_func,pnode);
	pnode.collapse();
	pnode.loaded = false;
	
	var loader = tree.getLoader();
	
	var rfunc;
	rfunc = function(treeLoader,node) {
		this.baseParams.recursive = true;
		this.un("beforeload",rfunc);
	}
	loader.on("beforeload",rfunc,loader);
	
	pnode.expand(true,false);
}


Ext.ns('Ext.ux.RapidApp.AppTree');

Ext.ux.RapidApp.AppTree.FilterPlugin = Ext.extend(Ext.util.Observable,{
	
	fieldIndex: 0,
	
	init: function(tree) {
		this.tree = tree;
		var Filter = this;
		
		if(tree.filterConfig) { Ext.apply(this,tree.filterConfig); }

		var fieldConfig = {
			xtype:'trigger',
			emptyText: 'Type to Find',
			triggerClass:'x-form-clear-trigger',
			onTriggerClick:function() {
				this.setValue('');
				tree.filter.clear();
			},
			enableKeyEvents:true,
			listeners:{
				keyup:{
					buffer: 150, 
					fn: function(field, e) {
						if(Ext.EventObject.ESC == e.getKey()) {
							field.onTriggerClick();
						}
						//else {
						else if (Ext.EventObject.ENTER == e.getKey()){
							//Filter.treeLoadAll();
							var callback = function() {
								var val = field.getRawValue();
								Ext.ux.RapidApp.AppTree.set_next_treeload_params(tree,{search:val});
								var re = new RegExp('.*' + val + '.*', 'i');
								tree.filter.clear();
								tree.filter.filter(re, 'text');
							}
							
							Ext.ux.RapidApp.AppTree.ensure_recursive_load(tree,callback);
						}
					}
				}
			}
		};
			
		if(this.fieldConfig) {
			Ext.apply(fieldConfig,this.fieldConfig);
		}
		
		tree.filter = new Ext.ux.tree.TreeFilterX(tree);
		tree.filter.searchField = Ext.ComponentMgr.create(fieldConfig);
		var Tbar = tree.getTopToolbar();
		Tbar.insert(this.fieldIndex,tree.filter.searchField);
	}
});
Ext.preg('apptree-filter',Ext.ux.RapidApp.AppTree.FilterPlugin);



Ext.ux.RapidApp.AppTree.reload = function(tree,recursive) {
	if(Ext.isFunction(tree.onReload)) { tree.onReload.call(tree); }
	tree.root.collapse();
	tree.root.loaded = false;
	tree.root.expand();
}

Ext.ux.RapidApp.AppTree.ServerFilterPlugin = Ext.extend(Ext.util.Observable,{
	
	fieldIndex: 0,
	
	init: function(tree) {
		this.tree = tree;
    
    var Tbar = tree.getTopToolbar();
    // New: do not load the plugin if there is no toolbar or
    // a 'searchField' property already exists in the treepanel
    if(!Tbar || tree.searchField) { return; }
		
		tree.onReload = function() {
			delete tree.next_load_params;
			tree.searchField.setValue('');
		};

		var loader = tree.getLoader();
		loader.on("beforeload",function(){
			this.baseParams = {};
			if(tree.next_load_params) {
				this.baseParams = tree.next_load_params;
				delete tree.next_load_params;
			}
		});
		
		if(tree.filterConfig) { Ext.apply(this,tree.filterConfig); }

		var fieldConfig = {
			emptyText: 'Type to Find',
			trigger1Class:'x-form-clear-trigger',
			trigger2Class: 'x-form-search-trigger',
			onTrigger1Click: function() {
				Ext.ux.RapidApp.AppTree.reload(tree);
			},
			onTrigger2Click:function() {
				this.runSearch.call(this);
			},
			runSearch: function() {
				var val = this.getRawValue();
				if(val == '') { return this.onTrigger1Click(); }
				tree.next_load_params = {
					search: val,
					recursive: true
				};
				tree.root.collapse();
				tree.root.loaded = false;
				tree.root.expand();
			},
			enableKeyEvents:true,
			listeners:{
				keyup:{
					buffer: 150, 
					fn: function(field, e) {
						if(Ext.EventObject.ESC == e.getKey()) {
							field.onTrigger1Click();
						}
						else if (Ext.EventObject.ENTER == e.getKey()){
							return field.runSearch();
						}
					}
				}
			}
		};
			
		if(this.fieldConfig) {
			Ext.apply(fieldConfig,this.fieldConfig);
		}
		
		tree.searchField = new Ext.form.TwinTriggerField(fieldConfig);
		Tbar.insert(this.fieldIndex,tree.searchField);
	}
});
Ext.preg('apptree-serverfilter',Ext.ux.RapidApp.AppTree.ServerFilterPlugin);




/**
 * @class   Ext.ux.tree.TreeFilterX
 * @extends Ext.tree.TreeFilter
 *
 * <p>
 * Shows also parents of matching nodes as opposed to default TreeFilter. In other words
 * this filter works "deep way".
 * </p>
 *
 * @author   Ing. Jozef Sakálo
 * @version  1.0
 * @date     17. December 2008
 * @revision $Id: Ext.ux.tree.TreeFilterX.js 589 2009-02-21 23:30:18Z jozo $
 * @see      <a href="http://extjs.com/forum/showthread.php?p=252709">http://extjs.com/forum/showthread.php?p=252709</a>
 *
 * @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum     55489
 * @demo      http://remotetree.extjs.eu
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.tree');

/**
 * Creates new TreeFilterX
 * @constructor
 * @param {Ext.tree.TreePanel} tree The tree panel to attach this filter to
 * @param {Object} config A config object of this filter
 */
Ext.ux.tree.TreeFilterX = Ext.extend(Ext.tree.TreeFilter, {
	/**
	 * @cfg {Boolean} expandOnFilter Deeply expands startNode before filtering (defaults to true)
	 */
	 expandOnFilter:true

	// {{{
    /**
     * Filter the data by a specific attribute.
	 *
     * @param {String/RegExp} value Either string that the attribute value 
     * should start with or a RegExp to test against the attribute
     * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
     * @param {TreeNode} startNode (optional) The node to start the filter at.
     */
	,filter:function(value, attr, startNode) {

		// expand start node
		if(false !== this.expandOnFilter) {
			startNode = startNode || this.tree.root;
			var animate = this.tree.animate;
			this.tree.animate = false;
			startNode.expand(true, false, function() {

				// call parent after expand
				Ext.ux.tree.TreeFilterX.superclass.filter.call(this, value, attr, startNode);

			}.createDelegate(this));
			this.tree.animate = animate;
		}
		else {
			// call parent
			Ext.ux.tree.TreeFilterX.superclass.filter.apply(this, arguments);
		}

	} // eo function filter
	// }}}
	// {{{
    /**
     * Filter by a function. The passed function will be called with each 
     * node in the tree (or from the startNode). If the function returns true, the node is kept 
     * otherwise it is filtered. If a node is filtered, its children are also filtered.
	 * Shows parents of matching nodes.
	 *
     * @param {Function} fn The filter function
     * @param {Object} scope (optional) The scope of the function (defaults to the current node) 
     */
	,filterBy:function(fn, scope, startNode) {
		startNode = startNode || this.tree.root;
		if(this.autoClear) {
			this.clear();
		}
		var af = this.filtered, rv = this.reverse;

		var f = function(n) {
			if(n === startNode) {
				return true;
			}
			if(af[n.id]) {
				return false;
			}
			var m = fn.call(scope || n, n);
			if(!m || rv) {
				af[n.id] = n;
				n.ui.hide();
				return true;
			}
			else {
				n.ui.show();
				var p = n.parentNode;
				while(p && p !== this.root) {
					p.ui.show();
					p = p.parentNode;
				}
				return true;
			}
			return true;
		};
		startNode.cascade(f);

        if(this.remove){
           for(var id in af) {
               if(typeof id != "function") {
                   var n = af[id];
                   if(n && n.parentNode) {
                       n.parentNode.removeChild(n);
                   }
               }
           }
        }
	} // eo function filterBy
	// }}}

}); // eo extend


// ----- 
// Overrides to prevent exceptions when panel is closed while an Ajax load is in-progress
Ext.override(Ext.tree.TreeNode,{
    _orig: {
      expand: Ext.tree.TreeNode.prototype.expand,
      expandChildNodes: Ext.tree.TreeNode.prototype.expandChildNodes,
      renderChildren: Ext.tree.TreeNode.prototype.renderChildren,
    },
    expand : function(){
      if(!this.getOwnerTree()) { return; }
      return this._orig.expand.apply(this,arguments);
    },
    expandChildNodes: function() {
      if(!this.childNodes) { return; }
      return this._orig.expandChildNodes.apply(this,arguments);
    },
    renderChildren: function() {
      if(!this.childNodes) { return; }
      return this._orig.renderChildren.apply(this,arguments);
    }
});

Ext.override(Ext.tree.AsyncTreeNode,{
    loadComplete : function(deep, anim, callback, scope){
        this.loading = false;
        this.loaded = true;
        if(this.ui) { this.ui.afterLoad(this); }
        this.fireEvent("load", this);
        this.expand(deep, anim, callback, scope);
    }
});
// ----- 

