[Rt-commit] rt branch, lcore, updated. 15860210fb79387908c4dd82a92d3893c4e6a927

clkao at bestpractical.com clkao at bestpractical.com
Wed Sep 9 18:04:10 EDT 2009


The branch, lcore has been updated
       via  15860210fb79387908c4dd82a92d3893c4e6a927 (commit)
       via  d15b8b65d2a72aa55334b9a0da3935f209877e48 (commit)
       via  b8729a19683024382fb16ff74bd1e332f4133a86 (commit)
      from  704f7f33e51b2981d0fbd9f2582fb7eab6bfb64c (commit)

Summary of changes:
 lib/RT.pm                               |    2 +-
 lib/RT/View/RuleBuilder.pm              |   14 +-
 share/web/static/css/app.css            |    1 +
 share/web/static/css/jquery.menu.css    |  281 +++++++++
 share/web/static/images/arrow_right.gif |  Bin 0 -> 55 bytes
 share/web/static/js/jquery.menu.js      |  943 +++++++++++++++++++++++++++++++
 share/web/static/js/rulebuilder.js      |   47 +-
 7 files changed, 1264 insertions(+), 24 deletions(-)
 create mode 100644 share/web/static/css/jquery.menu.css
 create mode 100644 share/web/static/images/arrow_right.gif
 create mode 100644 share/web/static/js/jquery.menu.js

- Log -----------------------------------------------------------------
commit b8729a19683024382fb16ff74bd1e332f4133a86
Author: Chia-liang Kao <clkao at bestpractical.com>
Date:   Wed Sep 9 12:18:35 2009 +0900

    - retrieve function list via ajax query.
    - display expressions before functions.

diff --git a/lib/RT/View/RuleBuilder.pm b/lib/RT/View/RuleBuilder.pm
index ed613ed..98ae5d8 100644
--- a/lib/RT/View/RuleBuilder.pm
+++ b/lib/RT/View/RuleBuilder.pm
@@ -16,14 +16,18 @@ sub _function_as_hash {
              parameters => [ map { { name => $_->name, type => _type_as_string($_->type) } } @{ $func->parameters || [] } ] };
 }
 
+template 'allfunctions.json' => sub {
+    Jifty->handler->apache->header_out('Content-Type' => 'application/json; charset=UTF-8' );
+    Jifty->handler->send_http_header;
+
+    my $functions = $RT::Lorzy::LCORE->env->all_functions;
+    my $data = { map { $_ => _function_as_hash($functions->{$_}) } keys %$functions };
+    print to_json($data);
+};
+
 template 'index.html' => page {
     title => "rule",
 } content {
-    my $l = $RT::Lorzy::LCORE;
-    my $functions = $l->env->all_functions;
-    my $data = { map { $_ => _function_as_hash($functions->{$_}) } keys %$functions };
-    pre { to_json($data) };
-
     h1 { "Rule Builder"};
     # given transaction :: RT::Model::Transaction
     #       ticket :: RT::Model::Ticket
diff --git a/share/web/static/js/rulebuilder.js b/share/web/static/js/rulebuilder.js
index 0eee9cf..7a03bf4 100644
--- a/share/web/static/js/rulebuilder.js
+++ b/share/web/static/js/rulebuilder.js
@@ -1,22 +1,17 @@
 RuleBuilder = function (sel) {
     this.sel = sel;
     /* defaults for now, should use ajax query */
-    this.functions = RuleBuilder.functions;
     this.expressions = RuleBuilder.expressions;
 
     this.current_application = null;
-    this.init();
-};
 
-RuleBuilder.functions = {
-    'or': { 'return_type': 'Bool' },
-    'and': { 'return_type': 'Bool' },
-    'RT.Condition.OnCreate': { 'return_type': 'Bool',
-                               'parameters':
-                               [{name: 'ticket', type: 'RT::Model::Ticket'},
-                                {name: 'transaction', type: 'RT::Model::Transaction'}
-                               ]},
-    'blah': { 'return_type': 'Str' }
+    var that = this;
+    jQuery.get('/rulebuilder/allfunctions.json', {},
+               function(response, status) {
+                   that.functions = response;
+                   that.init();
+               },
+               'json'); // handle errors.
 };
 
 RuleBuilder.expressions = [
@@ -43,7 +38,11 @@ RuleBuilder.prototype.init = function () {
     jQuery("#add-expression")
 		.appendTo(jQuery(".application"));
 
-  	ebuilder.append('<div class="functions">');
+
+    this.update_expressions();
+
+    ebuilder.append('<div class="functions">');
+
     functions_div = jQuery('.functions');
     functions_div.append('<h3>Functions</h3>');
     jQuery.each(this.functions,
@@ -51,9 +50,6 @@ RuleBuilder.prototype.init = function () {
                     functions_div.append('<div class="function ret_'+val.return_type+'"> <span class="return-type">'+val.return_type+'</span> <span class="function-name">'+key+'</span>'+render_signature(val.parameters).html() +'</div>');
                 });
 
-
-    this.update_expressions();
-
     jQuery(this.sel+' div.function').click(
         function(e) {
             var func_name = jQuery('span.function-name', this).text();

commit d15b8b65d2a72aa55334b9a0da3935f209877e48
Author: Chia-liang Kao <clkao at bestpractical.com>
Date:   Wed Sep 9 12:22:57 2009 +0900

    use prepend for expressions div.

diff --git a/share/web/static/js/rulebuilder.js b/share/web/static/js/rulebuilder.js
index 7a03bf4..3e6046c 100644
--- a/share/web/static/js/rulebuilder.js
+++ b/share/web/static/js/rulebuilder.js
@@ -86,7 +86,7 @@ RuleBuilder.prototype.update_expressions = function() {
     jQuery._div({'class': 'expressions'})
             ._h3_().text("Current Expressions")
           .div_()
-        .appendTo(ebuilder);
+        .prependTo(ebuilder);
 
     var expressions_div = jQuery('div.expressions', ebuilder);
 

commit 15860210fb79387908c4dd82a92d3893c4e6a927
Author: Chia-liang Kao <clkao at bestpractical.com>
Date:   Wed Sep 9 15:26:56 2009 +0900

    prepare dropdown menu for ticket attributes.

diff --git a/lib/RT.pm b/lib/RT.pm
index 4b8c8e5..7b57d5b 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -414,7 +414,7 @@ sub start {
 
     Jifty->web->add_javascript(
         qw( titlebox-state.js util.js ahah.js fckeditor.js list.js class.js
-            jquery.createdomnodes.js combobox.js  cascaded.js rulebuilder.js
+            jquery.createdomnodes.js jquery.menu.js combobox.js  cascaded.js rulebuilder.js
       )
     );
 
diff --git a/share/web/static/css/app.css b/share/web/static/css/app.css
index db359d5..5269f16 100644
--- a/share/web/static/css/app.css
+++ b/share/web/static/css/app.css
@@ -15,4 +15,5 @@
 @import "yui/calendar/calendar.css";
 
 @import "rulebuilder.css";
+ at import "jquery.menu.css";
 
diff --git a/share/web/static/css/jquery.menu.css b/share/web/static/css/jquery.menu.css
new file mode 100644
index 0000000..a5a956a
--- /dev/null
+++ b/share/web/static/css/jquery.menu.css
@@ -0,0 +1,281 @@
+html>body div.outerbox
+{
+	padding: 0 5px 5px 0;
+}
+html>body div.outerbox div.shadowbox1
+{
+	position: absolute;
+	right: 0;
+	bottom: 5px;
+	width: 5px;
+	height: 100%;
+	background: url(myshadow.png) no-repeat right top;
+}
+html>body div.outerbox div.shadowbox2
+{
+	position: absolute;
+	bottom: 0;
+	right: 5px;
+	height: 5px;
+	width: 100%;
+	background: url(myshadow.png) left bottom;
+}
+html>body div.outerbox div.shadowbox3
+{
+	position: absolute;
+	bottom: 0;
+	right: 0;
+	height: 5px;
+	width: 5px;
+	background: url(myshadow.png) no-repeat right bottom;
+}
+html>body .innerbox
+{
+	margin: 0;
+	display: inherit;
+}
+
+#root-menu-div ul {
+	border: 1px solid #000;
+}
+#root-menu-div li{
+	white-space:nowrap;
+}
+* html #root-menu-div li{
+	height: 1.5em; /* fixing ie6 problem */
+}
+ul.menu,
+#root-menu-div ul {
+	background-color: #fff;
+	list-style: none;
+	margin: 0;
+	padding: 0;
+}
+li.menu-separator.active{
+	background-color: transparent;
+}
+li.active {
+	background-color: #888;
+}
+.activetarget{
+	background-color: white;
+}
+
+* html div.menu-item {
+	display: inline; /* fixes problem in ie6 */
+}
+
+li.menumain {
+	float: left;
+	padding: 0 10px;
+}
+div.menu-item {
+	padding: 1px 10px 1px 4px;
+}
+img.menu-item-arrow{
+	position: absolute;
+	right: 4px;
+	top: 8px;
+}
+li.menu-separator{
+	border-bottom: 1px solid #000;
+	font-size: 0; /* for ie */
+	height: 0;
+	line-height: 0; /* for ie */
+	margin: 2px 0;
+}
+li.red {
+	color: red;
+}
+li.blue {
+	color: blue;
+}
+
+
+
+/* syntaxhighlight stuff */
+.dp-highlighter
+{
+	font-family: "Consolas", "Courier New", Courier, mono, serif;
+	font-size: 12px;
+	background-color: #E7E5DC;
+	width: 99%;
+	overflow: auto;
+	margin: 18px 0 18px 0 !important;
+	padding-top: 1px; /* adds a little border on top when controls are hidden */
+}
+
+/* clear styles */
+.dp-highlighter ol,
+.dp-highlighter ol li,
+.dp-highlighter ol li span 
+{
+	margin: 0;
+	padding: 0;
+	border: none;
+}
+
+.dp-highlighter a,
+.dp-highlighter a:hover
+{
+	background: none;
+	border: none;
+	padding: 0;
+	margin: 0;
+}
+
+.dp-highlighter .bar
+{
+	padding-left: 45px;
+}
+
+.dp-highlighter.collapsed .bar,
+.dp-highlighter.nogutter .bar
+{
+	padding-left: 0px;
+}
+
+.dp-highlighter ol
+{
+	list-style: decimal; /* for ie */
+	background-color: #fff;
+	margin: 0px 0px 1px 45px !important; /* 1px bottom margin seems to fix occasional Firefox scrolling */
+	padding: 0px;
+	color: #5C5C5C;
+}
+
+.dp-highlighter.nogutter ol,
+.dp-highlighter.nogutter ol li
+{
+	list-style: none !important;
+	margin-left: 0px !important;
+}
+
+.dp-highlighter ol li,
+.dp-highlighter .columns div
+{
+	list-style: decimal-leading-zero; /* better look for others, override cascade from OL */
+	list-style-position: outside !important;
+	border-left: 3px solid #6CE26C;
+	background-color: #F8F8F8;
+	color: #5C5C5C;
+	padding: 0 3px 0 10px !important;
+	margin: 0 !important;
+	line-height: 14px;
+}
+
+.dp-highlighter.nogutter ol li,
+.dp-highlighter.nogutter .columns div
+{
+	border: 0;
+}
+
+.dp-highlighter .columns
+{
+	background-color: #F8F8F8;
+	color: gray;
+	overflow: hidden;
+	width: 100%;
+}
+
+.dp-highlighter .columns div
+{
+	padding-bottom: 5px;
+}
+
+.dp-highlighter ol li.alt
+{
+	background-color: #FFF;
+	color: inherit;
+}
+
+.dp-highlighter ol li span
+{
+	color: black;
+	background-color: inherit;
+}
+
+/* Adjust some properties when collapsed */
+
+.dp-highlighter.collapsed ol
+{
+	margin: 0px;
+}
+
+.dp-highlighter.collapsed ol li
+{
+	display: none;
+}
+
+/* Additional modifications when in print-view */
+
+.dp-highlighter.printing
+{
+	border: none;
+}
+
+.dp-highlighter.printing .tools
+{
+	display: none !important;
+}
+
+.dp-highlighter.printing li
+{
+	display: list-item !important;
+}
+
+/* Styles for the tools */
+
+.dp-highlighter .tools
+{
+	padding: 3px 8px 3px 10px;
+	font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif;
+	color: silver;
+	background-color: #f8f8f8;
+	padding-bottom: 10px;
+	border-left: 3px solid #6CE26C;
+}
+
+.dp-highlighter.nogutter .tools
+{
+	border-left: 0;
+}
+
+.dp-highlighter.collapsed .tools
+{
+	border-bottom: 0;
+}
+
+.dp-highlighter .tools a
+{
+	font-size: 9px;
+	color: #a0a0a0;
+	background-color: inherit;
+	text-decoration: none;
+	margin-right: 10px;
+}
+
+.dp-highlighter .tools a:hover
+{
+	color: red;
+	background-color: inherit;
+	text-decoration: underline;
+}
+
+/* About dialog styles */
+
+.dp-about { background-color: #fff; color: #333; margin: 0px; padding: 0px; }
+.dp-about table { width: 100%; height: 100%; font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; }
+.dp-about td { padding: 10px; vertical-align: top; }
+.dp-about .copy { border-bottom: 1px solid #ACA899; height: 95%; }
+.dp-about .title { color: red; background-color: inherit; font-weight: bold; }
+.dp-about .para { margin: 0 0 4px 0; }
+.dp-about .footer { background-color: #ECEADB; color: #333; border-top: 1px solid #fff; text-align: right; }
+.dp-about .close { font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; background-color: #ECEADB; color: #333; width: 60px; height: 22px; }
+
+/* Language specific styles */
+
+.dp-highlighter .comment, .dp-highlighter .comments { color: #008200; background-color: inherit; }
+.dp-highlighter .string { color: blue; background-color: inherit; }
+.dp-highlighter .keyword { color: #069; font-weight: bold; background-color: inherit; }
+.dp-highlighter .preprocessor { color: gray; background-color: inherit; }
diff --git a/share/web/static/images/arrow_right.gif b/share/web/static/images/arrow_right.gif
new file mode 100644
index 0000000..610344d
Binary files /dev/null and b/share/web/static/images/arrow_right.gif differ
diff --git a/share/web/static/js/jquery.menu.js b/share/web/static/js/jquery.menu.js
new file mode 100644
index 0000000..56c9945
--- /dev/null
+++ b/share/web/static/js/jquery.menu.js
@@ -0,0 +1,943 @@
+/*
+ * jQuery Menu plugin
+ * Version: 0.0.9
+ *
+ * Copyright (c) 2007 Roman Weich
+ * http://p.sohei.org
+ *
+ * Dual licensed under the MIT and GPL licenses 
+ * (This means that you can choose the license that best suits your project, and use it accordingly):
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Changelog: 
+ * v 0.0.9 - 2008-01-19
+ */
+
+(function($)
+{
+	var menus = [], //list of all menus
+		visibleMenus = [], //list of all visible menus
+		activeMenu = activeItem = null,
+		menuDIVElement = $('<div class="menu-div outerbox" style="position:absolute;top:0;left:0;display:none;"><div class="shadowbox1"></div><div class="shadowbox2"></div><div class="shadowbox3"></div></div>')[0],
+		menuULElement = $('<ul class="menu-ul innerbox"></ul>')[0],
+		menuItemElement = $('<li style="position:relative;"><div class="menu-item"></div></li>')[0],
+		arrowElement = $('<img class="menu-item-arrow" />')[0],
+		$rootDiv = $('<div id="root-menu-div" style="position:absolute;top:0;left:0;"></div>'), //create main menu div
+		defaults = {
+			// $.Menu options
+			showDelay : 200,
+			hideDelay : 200,
+			hoverOpenDelay: 0,
+			offsetTop : 0,
+			offsetLeft : 0,
+			minWidth: 0,
+			onOpen: null,
+			onClose: null,
+
+			// $.MenuItem options
+			onClick: null,
+			arrowSrc: null,
+			addExpando: false,
+			
+			// $.fn.menuFromElement options
+			copyClassAttr: false
+		};
+	
+	$(function(){
+		$rootDiv.appendTo('body');
+	});
+	
+	$.extend({
+		MenuCollection : function(items) {
+		
+			this.menus = [];
+		
+			this.init(items);
+		}
+	});
+	$.extend($.MenuCollection, {
+		prototype : {
+			init : function(items)
+			{
+				if ( items && items.length )
+				{
+					for ( var i = 0; i < items.length; i++ )
+					{
+						this.addMenu(items[i]);
+						items[i].menuCollection = this;
+					}
+				}
+			},
+			addMenu : function(menu)
+			{
+				if ( menu instanceof $.Menu )
+					this.menus.push(menu);
+				
+				menu.menuCollection = this;
+			
+				var self = this;
+				$(menu.target).hover(function(){
+					if ( menu.visible )
+						return;
+
+					//when there is an open menu in this collection, hide it and show the new one
+					for ( var i = 0; i < self.menus.length; i++ )
+					{
+						if ( self.menus[i].visible )
+						{
+							self.menus[i].hide();
+							menu.show();
+							return;
+						}
+					}
+				}, function(){});
+			}
+		}
+	});
+
+	
+	$.extend({
+		Menu : function(target, items, options) {
+			this.menuItems = []; //all direct child $.MenuItem objects
+			this.subMenus = []; //all subMenus from this.menuItems
+			this.visible = false;
+			this.active = false; //this menu has hover or one of its submenus is open
+			this.parentMenuItem = null;
+			this.settings = $.extend({}, defaults, options);
+			this.target = target;
+			this.$eDIV = null;
+			this.$eUL = null;
+			this.timer = null;
+			this.menuCollection = null;
+			this.openTimer = null;
+
+			this.init();
+			if ( items && items.constructor == Array )
+				this.addItems(items);
+		}
+	});
+
+	$.extend($.Menu, {
+		checkMouse : function(e)
+		{
+			var t = e.target;
+
+			//the user clicked on the target of the currenty open menu
+			if ( visibleMenus.length && t == visibleMenus[0].target )
+				return;
+			
+			//get the last node before the #root-menu-div
+			while ( t.parentNode && t.parentNode != $rootDiv[0] )
+				t = t.parentNode;
+
+			//is the found node one of the visible menu elements?
+			if ( !$(visibleMenus).filter(function(){ return this.$eDIV[0] == t }).length )
+			{
+				$.Menu.closeAll();
+			}
+		},
+		checkKey : function(e)
+		{
+			switch ( e.keyCode )
+			{
+				case 13: //return
+					if ( activeItem )
+						activeItem.click(e, activeItem.$eLI[0]);
+					break;
+				case 27: //ESC
+					$.Menu.closeAll();
+					break;
+				case 37: //left
+					if ( !activeMenu )
+						activeMenu = visibleMenus[0];
+					var a = activeMenu;
+					if ( a && a.parentMenuItem ) //select the parent menu and close the submenu
+					{
+						//unbind the events temporary, as we dont want the hoverout event to fire
+						var pmi = a.parentMenuItem;
+						pmi.$eLI.unbind('mouseout').unbind('mouseover');
+						a.hide();
+						pmi.hoverIn(true);
+						setTimeout(function(){ //bind again..but delay it
+							pmi.bindHover();
+						});
+					}
+					else if ( a && a.menuCollection ) //select the previous menu in the collection
+					{
+						var pos,
+							mcm = a.menuCollection.menus;
+						if ( (pos = $.inArray(a, mcm)) > -1 )
+						{
+							if ( --pos < 0 )
+								pos = mcm.length - 1;
+							$.Menu.closeAll();
+							mcm[pos].show();
+							mcm[pos].setActive();
+							if ( mcm[pos].menuItems.length ) //select the first item
+								mcm[pos].menuItems[0].hoverIn(true);
+						}
+					}
+					break;
+				case 38: //up
+					if ( activeMenu )
+						activeMenu.selectNextItem(-1);
+					break;
+				case 39: //right
+					if ( !activeMenu )
+						activeMenu = visibleMenus[0];
+					var m,
+						a = activeMenu,
+						asm = activeItem ? activeItem.subMenu : null;
+					if ( a )
+					{
+						if ( asm && asm.menuItems.length ) //select the submenu
+						{
+							asm.show();
+							asm.menuItems[0].hoverIn();
+						}
+						else if ( (a = a.inMenuCollection()) ) //select the next menu in the collection
+						{
+							var pos,
+								mcm = a.menuCollection.menus;
+							if ( (pos = $.inArray(a, mcm)) > -1 )
+							{
+								if ( ++pos >= mcm.length )
+									pos = 0;
+								$.Menu.closeAll();
+								mcm[pos].show();
+								mcm[pos].setActive();
+								if ( mcm[pos].menuItems.length ) //select the first item
+									mcm[pos].menuItems[0].hoverIn(true);
+							}
+						}
+					}
+					break;
+				case 40: //down
+					if ( !activeMenu )
+					{
+						if ( visibleMenus.length && visibleMenus[0].menuItems.length )
+							visibleMenus[0].menuItems[0].hoverIn();
+					}
+					else
+						activeMenu.selectNextItem();
+					break;
+			}
+			if ( e.keyCode > 36 && e.keyCode < 41 )
+				return false; //this will prevent scrolling
+		},
+		closeAll : function()
+		{
+			while ( visibleMenus.length )
+				visibleMenus[0].hide();
+		},
+		setDefaults : function(d)
+		{
+			$.extend(defaults, d);
+		},
+		prototype : {
+			/**
+			 * create / initialize new menu
+			 */
+			init : function()
+			{
+				var self = this;
+				if ( !this.target )
+					return;
+				else if ( this.target instanceof $.MenuItem )
+				{
+					this.parentMenuItem = this.target;
+					this.target.addSubMenu(this);
+					this.target = this.target.$eLI;
+				}
+
+				menus.push(this);
+
+				//use the dom methods instead the ones from jquery (faster)
+				this.$eDIV = $(menuDIVElement.cloneNode(1));
+				this.$eUL = $(menuULElement.cloneNode(1));
+				this.$eDIV[0].appendChild(this.$eUL[0]);
+				$rootDiv[0].appendChild(this.$eDIV[0]);
+
+				//bind events
+				if ( !this.parentMenuItem )
+				{
+					$(this.target).click(function(e){
+						self.onClick(e);
+					}).hover(function(e){
+						self.setActive();
+
+						if ( self.settings.hoverOpenDelay )
+						{
+							self.openTimer = setTimeout(function(){
+								if ( !self.visible )
+									self.onClick(e);
+							}, self.settings.hoverOpenDelay);
+						}
+					}, function(){
+						if ( !self.visible )
+							$(this).removeClass('activetarget');
+
+						if ( self.openTimer )
+							clearTimeout(self.openTimer);
+					});
+				}
+				else
+				{
+					this.$eDIV.hover(function(){
+						self.setActive();
+					}, function(){});
+				}
+			},
+			setActive : function()
+			{
+				if ( !this.parentMenuItem )
+					$(this.target).addClass('activetarget');
+				else
+					this.active = true;
+			},
+			addItem : function(item)
+			{
+				if ( item instanceof $.MenuItem )
+				{
+					if ( $.inArray(item, this.menuItems) == -1 )
+					{
+						this.$eUL.append(item.$eLI);
+						this.menuItems.push(item);
+						item.parentMenu = this;
+						if ( item.subMenu )
+							this.subMenus.push(item.subMenu);
+					}
+				}
+				else
+				{
+					this.addItem(new $.MenuItem(item, this.settings));
+				}
+			},
+			addItems : function(items)
+			{
+				for ( var i = 0; i < items.length; i++ )
+				{
+					this.addItem(items[i]);
+				}
+			},
+			removeItem : function(item)
+			{
+				var pos = $.inArray(item, this.menuItems);
+				if ( pos > -1 )
+					this.menuItems.splice(pos, 1);
+				item.parentMenu = null;
+			},
+			hide : function()
+			{
+				if ( !this.visible )
+					return;
+				
+				var i, 
+					pos = $.inArray(this, visibleMenus);
+
+				this.$eDIV.hide();
+
+				if ( pos >= 0 )
+					visibleMenus.splice(pos, 1);
+				this.visible = this.active = false;
+
+				$(this.target).removeClass('activetarget');
+
+				//hide all submenus
+				for ( i = 0; i < this.subMenus.length; i++ )
+				{
+					this.subMenus[i].hide();
+				}
+
+				//set all items inactive (e.g. remove hover class..)
+				for ( i = 0; i < this.menuItems.length; i++ )
+				{
+					if ( this.menuItems[i].active )
+						this.menuItems[i].setInactive();
+				}
+
+				if ( !visibleMenus.length ) //unbind events when the last menu was closed
+					$(document).unbind('mousedown', $.Menu.checkMouse).unbind('keydown', $.Menu.checkKey);
+
+				if ( activeMenu == this )
+					activeMenu = null;
+					
+				if ( this.settings.onClose )
+					this.settings.onClose.call(this);
+			},
+			show : function(e)
+			{
+				if ( this.visible )
+					return;
+
+				var zi, 
+					pmi = this.parentMenuItem;
+
+				if ( this.menuItems.length ) //show only when it has items
+				{
+					if ( pmi ) //set z-index
+					{
+						zi = parseInt(pmi.parentMenu.$eDIV.css('z-index'));
+						this.$eDIV.css('z-index', (isNaN(zi) ? 1 : zi + 1));
+					}
+					this.$eDIV.css({visibility: 'hidden', display:'block'});
+
+					//set min-width
+					if ( this.settings.minWidth )
+					{
+						if ( this.$eDIV.width() < this.settings.minWidth )
+							this.$eDIV.css('width', this.settings.minWidth);
+					}
+					
+					this.setPosition();
+					this.$eDIV.css({display:'none', visibility: ''}).show();
+
+					//IEs default width: auto is bad! ie6 and ie7 have are producing different errors.. (7 = 5px shadowbox + 2px border)
+					if ( $.browser.msie )
+						this.$eUL.css('width', parseInt($.browser.version) == 6 ? this.$eDIV.width() - 7 : this.$eUL.width());
+
+					if ( this.settings.onOpen )
+						this.settings.onOpen.call(this);
+				}
+				if ( visibleMenus.length == 0 )
+					$(document).bind('mousedown', $.Menu.checkMouse).bind('keydown', $.Menu.checkKey);
+
+				this.visible = true;
+				visibleMenus.push(this);
+			},
+			setPosition : function()
+			{
+				var $t, o, posX, posY, 
+					pmo, //parent menu offset
+					wst, //window scroll top
+					wsl, //window scroll left
+					ww = $(window).width(), 
+					wh = $(window).height(),
+					pmi = this.parentMenuItem,
+					height = this.$eDIV[0].clientHeight,
+					width = this.$eDIV[0].clientWidth,
+					pheight; //parent height
+
+				if ( pmi )
+				{
+					//position on the right side of the parent menu item
+					o = pmi.$eLI.offset();
+					posX = o.left + pmi.$eLI.width();
+					posY = o.top;
+				}
+				else
+				{
+					//position right below the target
+					$t = $(this.target);
+					o = $t.offset();
+					posX = o.left + this.settings.offsetLeft;
+					posY = o.top + $t.height() + this.settings.offsetTop;
+				}
+
+				//y-pos
+				if ( $.fn.scrollTop )
+				{
+					wst = $(window).scrollTop();
+					if ( wh < height ) //menu is bigger than the window
+					{
+						//position the menu at the top of the visible area
+						posY = wst;
+					}
+					else if ( wh + wst < posY + height ) //outside on the bottom?
+					{
+						if ( pmi )
+						{
+							pmo = pmi.parentMenu.$eDIV.offset();
+							pheight = pmi.parentMenu.$eDIV[0].clientHeight;
+							if ( height <= pheight )
+							{
+								//bottom position = parentmenu-bottom position
+								posY = pmo.top + pheight - height;
+							}
+							else
+							{
+								//top position = parentmenu-top position
+								posY = pmo.top;
+							}
+							//still outside on the bottom?
+							if ( wh + wst < posY + height )
+							{
+								//shift the menu upwards till the bottom is visible
+								posY -= posY + height - (wh + wst);
+							}
+						}
+						else
+						{
+							//shift the menu upwards till the bottom is visible
+							posY -= posY + height - (wh + wst);
+						}
+					}
+				}
+				//x-pos
+				if ( $.fn.scrollLeft )
+				{
+					wsl = $(window).scrollLeft();
+					if ( ww + wsl < posX + width )
+					{
+						if ( pmi )
+						{
+							//display the menu not on the right side but on the left side
+							posX -= pmi.$eLI.width() + width;
+							//outside on the left now?
+							if ( posX < wsl )
+								posX = wsl;
+						}
+						else
+						{
+							//shift the menu to the left until it fits
+							posX -= posX + width - (ww + wsl);
+						}
+					}
+				}
+
+				//set position
+				this.$eDIV.css({left: posX, top: posY});
+			},
+			onClick : function(e)
+			{
+				if ( this.visible )
+				{
+					this.hide();
+					this.setActive(); //the class is removed in the hide() method..add it again
+				}
+				else
+				{
+					//close all open menus
+					$.Menu.closeAll();
+					this.show(e);
+				}
+			},
+			addTimer : function(callback, delay)
+			{
+				var self = this;
+				this.timer = setTimeout(function(){
+					callback.call(self);
+					self.timer = null;
+				}, delay);
+			},
+			removeTimer : function()
+			{
+				if ( this.timer )
+				{
+					clearTimeout(this.timer);
+					this.timer = null;
+				}
+			},
+			selectNextItem : function(offset)
+			{
+				var i, pos = 0,
+					mil = this.menuItems.length,
+					o = offset || 1;
+				
+				//get current pos
+				for ( i = 0; i < mil; i++ )
+				{
+					if ( this.menuItems[i].active )
+					{
+						pos = i;
+						break;
+					}
+				}
+				this.menuItems[pos].hoverOut();
+
+				do //jump over the separators
+				{
+					pos += o;
+					if ( pos >= mil )
+						pos = 0;
+					else if ( pos < 0 )
+						pos = mil - 1;
+				} while ( this.menuItems[pos].separator );
+				this.menuItems[pos].hoverIn(true);
+			},
+			inMenuCollection : function()
+			{
+				var m = this;
+				while ( m.parentMenuItem )
+					m = m.parentMenuItem.parentMenu;
+				return m.menuCollection ? m : null;
+			},
+			destroy : function() //delete menu
+			{
+				var pos, item;
+
+				this.hide();
+
+				//unbind events
+				if ( !this.parentMenuItem )
+					$(this.target).unbind('click').unbind('mouseover').unbind('mouseout');
+				else
+					this.$eDIV.unbind('mouseover').unbind('mouseout');
+
+				//destroy all items
+				while ( this.menuItems.length )
+				{
+					item = this.menuItems[0];
+					item.destroy();
+					delete item;
+				}
+
+				if ( (pos = $.inArray(this, menus)) > -1 )
+					menus.splice(pos, 1);
+
+				if ( this.menuCollection )
+				{
+					if ( (pos = $.inArray(this, this.menuCollection.menus)) > -1 )
+						this.menuCollection.menus.splice(pos, 1);
+				}
+					
+				this.$eDIV.remove();
+			}
+		}
+	});
+
+	$.extend({
+		MenuItem : function(obj, options)
+		{
+			if ( typeof obj == 'string' )
+				obj = {src: obj};
+
+			this.src = obj.src || '';
+			this.url = obj.url || null;
+			this.urlTarget = obj.target || null;
+			this.addClass = obj.addClass || null;
+			this.data = obj.data || null;
+
+			this.$eLI = null;
+			this.parentMenu = null;
+			this.subMenu = null;
+			this.settings = $.extend({}, defaults, options);
+			this.active = false;
+			this.enabled = true;
+			this.separator = false;
+
+			this.init();
+			
+			if ( obj.subMenu )
+				new $.Menu(this, obj.subMenu, options);
+		}
+	});
+
+	$.extend($.MenuItem, {
+		prototype : {
+			init : function()
+			{
+				var i, isStr,
+					src = this.src,
+					self = this;
+
+				this.$eLI = $(menuItemElement.cloneNode(1));
+
+				if ( this.addClass )
+					this.$eLI[0].setAttribute('class', this.addClass);
+
+				if ( this.settings.addExpando && this.data )
+					this.$eLI[0].menuData = this.data;
+					
+				if ( src == '' )
+				{
+					this.$eLI.addClass('menu-separator');
+					this.separator = true;
+				}
+				else
+				{
+					isStr = typeof src == 'string';
+					if ( isStr && this.url ) //create a link node, when we have an url
+						src = $('<a href="' + this.url + '"' + (this.urlTarget ? 'target="' + this.urlTarget + '"' : '') + '>' + src + '</a>');
+					else if ( isStr || !src.length )
+						src = [src];
+					//go through the passed DOM-Elements (or jquery objects or text nodes.) and append them to the menus list item
+					//this.$eLI.append(this.src) is really slow when having a lot(!!!) of items
+					for ( i = 0; i < src.length; i++ )
+					{
+						if ( typeof src[i] == 'string' )
+						{
+							//we cant use createTextNode, as html entities won't be displayed correctly (eg. &copy;)
+							elem = document.createElement('span');
+							elem.innerHTML = src[i];
+							this.$eLI[0].firstChild.appendChild(elem);
+						}
+						else
+							this.$eLI[0].firstChild.appendChild(src[i].cloneNode(1));
+					}
+				}
+
+				this.$eLI.click(function(e){
+					self.click(e, this);
+				});
+				this.bindHover();
+			},
+			click : function(e, scope)
+			{
+				if ( this.enabled && this.settings.onClick )
+					this.settings.onClick.call(scope, e, this);
+			},
+			bindHover : function()
+			{
+				var self = this;
+				this.$eLI.hover(function(){
+						self.hoverIn();
+					}, function(){
+						self.hoverOut();
+				});
+			},
+			hoverIn : function(noSubMenu)
+			{
+				this.removeTimer();
+
+				var i, 
+					pms = this.parentMenu.subMenus,
+					pmi = this.parentMenu.menuItems,
+					self = this;
+
+				//remove the timer from the parent item, when there is one (e.g. to close the menu)
+				if ( this.parentMenu.timer )
+					this.parentMenu.removeTimer();
+
+				if ( !this.enabled )
+					return;
+					
+				//deactivate all menuItems on the same level
+				for ( i = 0; i < pmi.length; i++ )
+				{
+					if ( pmi[i].active )
+						pmi[i].setInactive();
+				}
+
+				this.setActive();
+				activeMenu = this.parentMenu;
+
+				//are there open submenus on the same level? close them!
+				for ( i = 0; i < pms.length; i++ )
+				{
+					if ( pms[i].visible && pms[i] != this.subMenu && !pms[i].timer ) //close if there is no closetimer running already
+						pms[i].addTimer(function(){
+							this.hide();
+						}, pms[i].settings.hideDelay);
+				}
+
+				if ( this.subMenu && !noSubMenu )
+				{
+					//set timeout to show menu
+					this.subMenu.addTimer(function(){
+						this.show();
+					}, this.subMenu.settings.showDelay);
+				}
+			},
+			hoverOut : function()
+			{
+				this.removeTimer();
+
+				if ( !this.enabled )
+					return;
+				
+				if ( !this.subMenu || !this.subMenu.visible )
+					this.setInactive();
+			},
+			removeTimer : function()
+			{
+				if ( this.subMenu )
+				{
+					this.subMenu.removeTimer();
+				}
+			},
+			setActive : function()
+			{
+				this.active = true;
+				this.$eLI.addClass('active');
+
+				//set the parent menu item active too if necessary
+				var pmi = this.parentMenu.parentMenuItem;
+				if ( pmi && !pmi.active )
+					pmi.setActive();
+
+				activeItem = this;
+			},
+			setInactive : function()
+			{
+				this.active = false;
+				this.$eLI.removeClass('active');
+				if ( this == activeItem )
+					activeItem = null;
+			},
+			enable : function()
+			{
+				this.$eLI.removeClass('disabled');
+				this.enabled = true;
+			},
+			disable : function()
+			{
+				this.$eLI.addClass('disabled');
+				this.enabled = false;
+			},
+			destroy : function()
+			{
+				this.removeTimer();
+
+				this.$eLI.remove();
+
+				//unbind events
+				this.$eLI.unbind('mouseover').unbind('mouseout').unbind('click');
+				//delete submenu
+				if ( this.subMenu )
+				{
+					this.subMenu.destroy();
+					delete this.subMenu;
+				}
+				this.parentMenu.removeItem(this);
+			},
+			addSubMenu : function(menu)
+			{
+				if ( this.subMenu )
+					return;
+				this.subMenu = menu;
+				if ( this.parentMenu && $.inArray(menu, this.parentMenu.subMenus) == -1 )
+					this.parentMenu.subMenus.push(menu);
+				if ( this.settings.arrowSrc )
+				{
+					var a = arrowElement.cloneNode(0);
+					a.setAttribute('src', this.settings.arrowSrc);
+					this.$eLI[0].firstChild.appendChild(a);
+				}
+			}
+		}
+	});
+	
+	
+	$.extend($.fn, {
+		menuFromElement : function(options, list, bar)
+		{
+			var createItems = function(ul)
+			{
+				var menuItems = [], 
+					subItems,
+					menuItem,
+					lis, $li, i, subUL, submenu, target, 
+					classNames = null;
+
+				lis = getAllChilds(ul, 'LI');
+				for ( i = 0; i < lis.length; i++ )
+				{
+					subItems = [];
+
+					if ( !lis[i].childNodes.length ) //empty item? add separator
+					{
+						menuItems.push(new $.MenuItem('', options));
+						continue;
+					}
+
+					if ( (subUL = getOneChild(lis[i], 'UL')) )
+					{
+						subItems = createItems(subUL);
+						//remove subUL from DOM
+						$(subUL).remove();
+					}
+
+					//select the target...get the elements inside the li
+					$li = $(lis[i]);
+					if ( $li[0].childNodes.length == 1 && $li[0].childNodes[0].nodeType == 3 )
+						target = $li[0].childNodes[0].nodeValue;
+					else
+						target = $li[0].childNodes;
+
+					if ( options && options.copyClassAttr )
+						classNames = $li.attr('class');
+						
+					//create item
+					menuItem = new $.MenuItem({src: target, addClass: classNames}, options);
+					menuItems.push(menuItem);
+					//add submenu
+					if ( subItems.length )
+						new $.Menu(menuItem, subItems, options);
+					
+				}
+				return menuItems;
+			};
+			return this.each(function()
+			{
+				var ul, m;
+				//get the list element
+				if ( list || (ul = getOneChild(this, 'UL')) )
+				{
+					//if a specific list element is used, clone it, as we probably need it more than once
+					ul = list ? $(list).clone(true)[0] : ul;
+					menuItems = createItems(ul);
+					if ( menuItems.length )
+					{
+						m = new $.Menu(this, menuItems, options);
+						if ( bar )
+							bar.addMenu(m);
+					}
+					$(ul).hide();
+				}
+			});
+		},
+		menuBarFromUL : function(options)
+		{
+			return this.each(function()
+			{
+				var i,
+					lis = getAllChilds(this, 'LI');
+
+				if ( lis.length )
+				{
+					bar = new $.MenuCollection();
+					for ( i = 0; i < lis.length; i++ )
+						$(lis[i]).menuFromElement(options, null, bar);
+				}
+			});
+		},
+		menu : function(options, items)
+		{
+			return this.each(function()
+			{
+				if ( items && items.constructor == Array )
+					new $.Menu(this, items, options);
+				else
+				{
+					if ( this.nodeName.toUpperCase() == 'UL' )
+						$(this).menuBarFromUL(options);
+					else
+						$(this).menuFromElement(options, items);
+				}
+			});
+		}
+	});
+
+	//faster than using jquery
+	var getOneChild = function(elem, name)
+	{
+		if ( !elem )
+			return null;
+
+		var n = elem.firstChild;
+		for ( ; n; n = n.nextSibling ) 
+		{
+			if ( n.nodeType == 1 && n.nodeName.toUpperCase() == name )
+				return n;
+		}
+		return null;
+	};
+	//faster than using jquery
+	var getAllChilds = function(elem, name)
+	{
+		if ( !elem )
+			return [];
+
+		var r = [],
+			n = elem.firstChild;
+		for ( ; n; n = n.nextSibling ) 
+		{
+			if ( n.nodeType == 1 && n.nodeName.toUpperCase() == name )
+				r[r.length] = n;
+		}
+		return r;
+	};
+
+})(jQuery);
diff --git a/share/web/static/js/rulebuilder.js b/share/web/static/js/rulebuilder.js
index 3e6046c..f9f7102 100644
--- a/share/web/static/js/rulebuilder.js
+++ b/share/web/static/js/rulebuilder.js
@@ -107,8 +107,23 @@ RuleBuilder.prototype.update_expressions = function() {
                         else {
                             alert("must select param first");
                         }
-                      }).appendTo(expressions_div);
+                          })
+                    .appendTo(expressions_div);
                 });
+
+    var options = {
+        onClick: function(e,item) { x=e;y=item },
+        minWidth: 120,
+        arrowSrc: '/images/arrow_right.gif',
+        hoverOpenDelay: 500,
+        hideDelay: 500 };
+
+    var items = [   {src: 'test' },
+        {src: ''}, /* separator */
+        {src: 'test2', subMenu: [   {src: 'sub 1'},
+                                    {src: 'sub 2' },
+                                    {src: 'sub 3'}]}];
+    jQuery('.ret_'+e_sel('RT::Model::Ticket')).menu(options, items);
 };
 
 

-----------------------------------------------------------------------


More information about the Rt-commit mailing list