1 /* This is a simple plugin to render action dropdown menus from html.
2 * John Barlow - SugarCRM
3 * add secondary popup implementation by Justin Park - SugarCRM
5 * The html structure it expects is as follows:
8 * <li> - First element in menu (visible)
9 * <ul class="subnav"> - Popout menu (should start hidden)
11 * ... - Elements in popout menu
14 * <a></a> or <input></input> - element contains submenu
15 * <ul class="subnav-sub"> - sub-popout menu (shown when mouseover on the above element)
17 * ... - Elements in sub-popout menu
25 * By adding a class of "fancymenu" to the menu root, the plugin adds an additional "ab" class to the
26 * dropdown handle, allowing you to make the menu "fancy" with additional css if you would like :)
30 * init: initializes things (called by default)... currently no options are passed
32 * Adds item to the menu at position index
33 * addItem: (item, index)
34 * item - created dom element or string that represents one
35 * index(optional) - the position you want your new menuitem. If you leave this off,
36 * the item is appended to the end of the list.
39 * Finds an item in the menu (including the root node "outside" the ul structure).
41 * item - string of the menu item you are looking for.
42 * returns: index of element, or -1 if not found.
46 init: function(options){
49 if(!this.hasClass("SugarActionMenu")){
50 //tag this element as a sugarmenu
51 this.addClass("SugarActionMenu");
53 //Fix custom code buttons programatically to prevent metadata edits
54 this.find("input[type='submit'], input[type='button']").each(function(idx, node){
56 var parent = jNode.parent();
57 var _subnav = menuNode.find("ul.subnav");
58 var _timer_for_subnav = null;
59 var disabled = $(this).prop('disabled');
60 var newItem = $(document.createElement("li"));
61 var newItemA = $(document.createElement("a"));
62 var accesskey = jNode.attr("accesskey");
63 var accesskey_el = $("<a></a>");
65 newItemA.html(jNode.val());
68 newItemA.click(function(event){
69 if($(this).hasClass("void") === false ){
76 newItemA.addClass("disabled");
78 newItemA.attr("id", jNode.attr("id"));
79 accesskey_el.attr("id", jNode.attr("id") + "_accesskey");
80 jNode.attr("id", jNode.attr("id") + "_old");
83 if(accesskey !== undefined) {
84 if($('#'+accesskey_el.attr('id')).length === 0) {
85 accesskey_el.attr("accesskey", accesskey).click(function() {
87 }).appendTo("#content");
89 jNode.attr("accesskey", '');
92 //make sure the node we found isn't the main item of the list -- we don't want
94 if(menuNode.sugarActionMenu("findItem", newItemA.html()) == -1){
95 parent.prepend(newItemA);
98 //make sub sliding menu
99 jNode.siblings(".subnav-sub").each(function(idx, node) {
101 var _hide_menu = function() {
102 if( _menu.hasClass("hover") === false )
105 var _hide_timer = null;
107 _menu.mouseover(function(evt){
108 if( $(this).hasClass("hover") === false )
109 $(this).addClass("hover");
110 }).mouseout(function(evt){
111 if( $(this).hasClass("hover") )
112 $(this).removeClass("hover");
114 clearTimeout(_hide_timer);
115 _hide_timer = setTimeout(_hide_menu, _delay);
118 newItemA.mouseover(function(evt) {
119 $("ul.SugarActionMenu ul.subnav-sub").each(function(index, node){
120 $(node).removeClass("hover");
123 var _left = parent.offset().left + parent.width() - newItemA.css("paddingRight").replace("px", "");
124 var _top = parent.offset().top - _menu.css("paddingTop").replace("px", "");
129 if( _menu.hasClass("hover") === false )
130 _menu.addClass("hover");
131 if( _subnav.hasClass("subnav-sub-handler") === false )
132 _subnav.addClass("subnav-sub-handler");
134 }).mouseout(function(evt) {
135 _menu.removeClass("hover");
136 _subnav.removeClass("subnav-sub-handler")
138 clearTimeout(_hide_timer);
139 _hide_timer = setTimeout(_hide_menu, _delay);
140 }).click(function(evt){
141 if(_timer_for_subnav)
142 clearTimeout(_timer_for_subnav);
144 menuNode.append(_menu);
146 jNode.css("display", "none");
150 //look for all subnavs and set them up
151 this.find("ul.subnav").each(function(index, node){
153 var parent = jNode.parent();
155 var slideUpSpeed = 0;
156 var slideDownSpeed = 0;
159 //if the dropdown handle doesn't exist, lets create it and
161 if(parent.find("span").length == 0){
163 //create dropdown handle
164 dropDownHandle = $(document.createElement("span"));
165 parent.append(dropDownHandle);
168 dropDownHandle = $(parent.find("span"));
170 if(menuNode.hasClass("fancymenu")){
171 dropDownHandle.addClass("ab");
172 dropDownHandle.tipTip({maxWidth: "auto",
174 content: "More Actions",
175 defaultPosition: "top"});
181 //add click handler to handle
182 dropDownHandle.click(function(event){
185 //close all other open menus
186 //retore the dom elements back by handling iefix
187 $("ul.SugarActionMenu > li").each(function() {
188 $(this).sugarActionMenu('IEfix');
190 $("ul.SugarActionMenu ul.subnav").each(function(subIndex, node){
191 var subjNode = $(node);
192 if(!(subjNode[0] === jNode[0])){
193 subjNode.slideUp(slideUpSpeed);
194 subjNode.removeClass("ddopen");
197 if(jNode.hasClass("ddopen")){
198 parent.sugarActionMenu('IEfix');
200 jNode.slideUp(slideUpSpeed);
201 jNode.removeClass("ddopen");
204 //To support IE fixed size rendering,
205 //parse out dom elements out of the fixed element
206 parent.sugarActionMenu('IEfix', jNode);
208 jNode.slideDown(slideDownSpeed).show();
209 jNode.addClass("ddopen");
211 event.stopPropagation();
214 //add submenu click off to body
215 var jBody = $("body");
216 var _hide_subnav_delay = 30;
217 var _hide_subnav = function(subnav) {
218 if( subnav.hasClass("subnav-sub-handler") === false ) {
219 subnav.slideUp(slideUpSpeed);
220 subnav.removeClass("ddopen");
223 if(jBody.data("sugarActionMenu") != true){
224 jBody.data("sugarActionMenu", true);
225 jBody.bind("click", function(){
226 //retore the dom elements back by handling iefix
227 $("ul.SugarActionMenu > li").each(function() {
228 $(this).sugarActionMenu('IEfix');
231 $("ul.SugarActionMenu ul.subnav").each(function(subIndex, node){
232 //prevent hiding the submenu when user click the submenu which contains one more depth submenu
233 var _hide = function() {
234 _hide_subnav($(node));
236 setTimeout(_hide, _hide_subnav_delay);
238 //Hide second depth submenu
239 $("ul.SugarActionMenu ul.subnav-sub").each(function(subIndex, node){
240 var _hide = function() {
241 $(this).removeClass("hover");
244 _timer_for_subnav = setTimeout(_hide, _hide_subnav_delay);
250 //add hover handler to handle
251 dropDownHandle.hover(function(){
252 dropDownHandle.addClass("subhover");
254 dropDownHandle.removeClass("subhover");
260 //bind click event to submenu items to hide the menu on click
261 jNode.find("li").each(function(index, subnode){
262 //prevent hiding the submenu when user click the submenu which contains one more depth submenu
263 $(subnode).bind("click", function(evt){
264 var _hide = function() {
267 setTimeout(_hide, _hide_subnav_delay);
271 //fix up text of <a> tags so they span correctly
272 jNode.find("a").each(function(index, subnode){
273 $(subnode).html(function(index, oldhtml){
274 return oldhtml.replace(" ", " ");
279 //Bug#51579: delete li tags which contains empty due to access role
280 this.find(".subnav > li").each(function(index, subnode) {
281 if($(subnode).html().replace(/ /g, '') == '') {
285 //Bug#51579: If the first item is empty due to the access role,
286 // replace the first button from the first of the sub-list items.
287 this.find("li.sugar_action_button:first").each(function(index, node) {
289 var _first_item = $(node).find("a:first").not($(node).find(".subnav > li a"));
290 if(_first_item.length == 0) {
291 var sub_items = $(node).find(".subnav > li:first").children();
292 if(sub_items.length == 0)
295 _this.prepend(sub_items);
303 addItem : function(args){
304 if(args.index == null){
305 this.find("ul.subnav").each(function(index, node){
306 $(node).append(args.item);
310 this.find("ul.subnav").find("li").each(function(index, node){
311 if(args.index == index+1){
312 $(node).before(args.item);
318 findItem: function(item){
320 this.find("a").each(function(idx, node){
322 if($.trim(jNode.html()) == $.trim(item)){
329 * To support IE fixed size rendering,
330 * parse out dom elements out of the fixed element
333 * <div style=position:fixed>
335 * <li jquery-attached>
336 * <ul style=position:absoulte>
343 * <div style=position:fixed>
344 * <li ul-child-id='auto-evaluted-id'>
349 * <ul id='auto-evaluted-id' style=position:fix;left/right/top-positioning:auto-calculated>
352 * @param this - element container which is inside the fixed box model
353 * @param $ul - dropdown box model which needs to render out of the fixed box range
354 * if $ul is not given, it will restore back to the original structure
356 IEfix: function($ul) {
357 if ($.browser.msie && $.browser.version > 6) {
359 if( $ul.hasClass('iefixed') === false)
361 this.each(function(){
362 SUGAR.themes.counter = SUGAR.themes.counter ? SUGAR.themes.counter++ : 1;
364 _id = $$.attr("ul-child-id") ? $$.attr("ul-child-id") : ($$.parent('.SugarActionMenu').attr("id")) ? $$.parent('.SugarActionMenu').attr("id") + 'Subnav' : 'sugaractionmenu' + SUGAR.themes.counter,
365 _top = $$.position().top + $$.outerHeight(),
366 _width = 'auto', //to obtain a certain size, apply min-width in css
372 _right = $('body').width() - $$.offset().left - $$.width(),
373 _left = $$.offset().left;
374 //fixed positioning depends on the css property
375 if($ul.css('right') != 'auto') {
376 _css['right'] = _right;
378 _css['left'] = _left;
380 $('body').append($ul.attr("id", _id).addClass("SugarActionMenuIESub").css(_css));
381 $$.attr("ul-child-id", _id);
385 this.each(function(){
386 var _id = $(this).attr("ul-child-id");
387 $(this).append($("body>#"+_id).removeClass("SugarActionMenuIESub"));
394 $.fn.sugarActionMenu = function(method) {
396 if (methods[method]) {
397 return methods[method].apply(this, Array.prototype.slice.call(
399 } else if (typeof method === 'object' || !method) {
400 return methods.init.apply(this, arguments);
402 $.error('Method ' + method + ' does not exist on jQuery.tooltip');