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 = 1;
156 var slideDownSpeed = 1;
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');
199 //Bug#50983: Popup the dropdown list above the arrow if the bottom part is cut off .
203 if(jNode.hasClass("upper")) {
204 _animation['top'] = (dropDownHandle.height() * -1);
206 jNode.animate(_animation, slideUpSpeed, function() {
210 }).hide().removeClass("upper ddopen");
214 //To support IE fixed size rendering,
215 //parse out dom elements out of the fixed element
216 parent.sugarActionMenu('IEfix', jNode);
217 var _dropdown_height = jNode.height(),
218 _animation = { 'height' : _dropdown_height },
219 _dropdown_bottom = dropDownHandle.offset().top + dropDownHandle.height() - $(document).scrollTop() + jNode.outerHeight(),
220 _win_height = $(window).height();
221 if(dropDownHandle.offset().top > jNode.height() && _dropdown_bottom > $(window).height()) {
222 jNode.css('top', (dropDownHandle.height() * -1)).addClass("upper");
223 _animation['top'] = (jNode.height() + dropDownHandle.height()) * -1;
226 jNode.height(0).show().animate(_animation,slideDownSpeed, function() {
227 $(this).css('height', '');
229 jNode.addClass("ddopen");
231 event.stopPropagation();
234 //add submenu click off to body
235 var jBody = $("body");
236 var _hide_subnav_delay = 30;
237 var _hide_subnav = function(subnav) {
238 if( subnav.hasClass("subnav-sub-handler") === false ) {
239 subnav.slideUp(slideUpSpeed);
240 subnav.removeClass("ddopen");
243 if(jBody.data("sugarActionMenu") != true){
244 jBody.data("sugarActionMenu", true);
245 jBody.bind("click", function(){
246 //retore the dom elements back by handling iefix
247 $("ul.SugarActionMenu > li").each(function() {
248 $(this).sugarActionMenu('IEfix');
251 $("ul.SugarActionMenu ul.subnav").each(function(subIndex, node){
252 //prevent hiding the submenu when user click the submenu which contains one more depth submenu
253 var _hide = function() {
254 _hide_subnav($(node));
256 setTimeout(_hide, _hide_subnav_delay);
258 //Hide second depth submenu
259 $("ul.SugarActionMenu ul.subnav-sub").each(function(subIndex, node){
260 var _hide = function() {
261 $(this).removeClass("hover");
264 _timer_for_subnav = setTimeout(_hide, _hide_subnav_delay);
270 //add hover handler to handle
271 dropDownHandle.hover(function(){
272 dropDownHandle.addClass("subhover");
274 dropDownHandle.removeClass("subhover");
280 //bind click event to submenu items to hide the menu on click
281 jNode.find("li").each(function(index, subnode){
282 //prevent hiding the submenu when user click the submenu which contains one more depth submenu
283 $(subnode).bind("click", function(evt){
284 var _hide = function() {
287 setTimeout(_hide, _hide_subnav_delay);
291 //fix up text of <a> tags so they span correctly
292 jNode.find("a").each(function(index, subnode){
293 $(subnode).html(function(index, oldhtml){
294 return oldhtml.replace(" ", " ");
299 //Bug#51579: delete li tags which contains empty due to access role
300 this.find(".subnav > li").each(function(index, subnode) {
301 if($(subnode).html().replace(/ /g, '') == '') {
305 //Bug#51579: If the first item is empty due to the access role,
306 // replace the first button from the first of the sub-list items.
307 this.find("li.sugar_action_button:first").each(function(index, node) {
309 var _first_item = $(node).find("a:first").not($(node).find(".subnav > li a"));
310 if(_first_item.length == 0) {
311 var sub_items = $(node).find(".subnav > li:first").children();
312 if(sub_items.length == 0)
315 _this.prepend(sub_items);
323 addItem : function(args){
324 if(args.index == null){
325 this.find("ul.subnav").each(function(index, node){
326 $(node).append(args.item);
330 this.find("ul.subnav").find("li").each(function(index, node){
331 if(args.index == index+1){
332 $(node).before(args.item);
338 findItem: function(item){
340 this.find("a").each(function(idx, node){
342 if($.trim(jNode.html()) == $.trim(item)){
349 * To support IE fixed size rendering,
350 * parse out dom elements out of the fixed element
353 * <div style=position:fixed>
355 * <li jquery-attached>
356 * <ul style=position:absoulte>
363 * <div style=position:fixed>
364 * <li ul-child-id='auto-evaluted-id'>
369 * <ul id='auto-evaluted-id' style=position:fix;left/right/top-positioning:auto-calculated>
372 * @param this - element container which is inside the fixed box model
373 * @param $ul - dropdown box model which needs to render out of the fixed box range
374 * if $ul is not given, it will restore back to the original structure
376 IEfix: function($ul) {
377 if ($.browser.msie && $.browser.version > 6) {
379 if( $ul.hasClass('iefixed') === false)
381 this.each(function(){
382 SUGAR.themes.counter = SUGAR.themes.counter ? SUGAR.themes.counter++ : 1;
384 _id = $$.attr("ul-child-id") ? $$.attr("ul-child-id") : ($$.parent('.SugarActionMenu').attr("id")) ? $$.parent('.SugarActionMenu').attr("id") + 'Subnav' : 'sugaractionmenu' + SUGAR.themes.counter,
385 _top = $$.position().top + $$.outerHeight(),
386 _width = 'auto', //to obtain a certain size, apply min-width in css
392 _right = $('body').width() - $$.offset().left - $$.width(),
393 _left = $$.offset().left;
394 //fixed positioning depends on the css property
395 if($ul.css('right') != 'auto') {
396 _css['right'] = _right;
398 _css['left'] = _left;
400 $('body').append($ul.attr("id", _id).addClass("SugarActionMenuIESub").css(_css));
401 $$.attr("ul-child-id", _id);
405 this.each(function(){
406 var _id = $(this).attr("ul-child-id");
407 $(this).append($("body>#"+_id).removeClass("SugarActionMenuIESub"));
414 $.fn.sugarActionMenu = function(method) {
416 if (methods[method]) {
417 return methods[method].apply(this, Array.prototype.slice.call(
419 } else if (typeof method === 'object' || !method) {
420 return methods.init.apply(this, arguments);
422 $.error('Method ' + method + ' does not exist on jQuery.tooltip');