]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/jquery/jquery.sugarMenu.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / jquery / jquery.sugarMenu.js
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
4  *
5  * The html structure it expects is as follows:
6  *
7  * <ul>                                     - Menu root
8  *      <li>                                - First element in menu (visible)
9  *      <ul class="subnav">                         - Popout menu (should start hidden)
10  *          <li></li>                       - \
11  *          ...                             -  Elements in popout menu
12  *          <li></li>                       - /
13  *          <li>
14  *              <a></a> or <input></input>  - element contains submenu
15  *              <ul class="subnav-sub">     - sub-popout menu (shown when mouseover on the above element)
16  *                  <li></li>               - \
17  *                  ...                     -  Elements in sub-popout menu
18  *                  <li></li>               - /
19  *              </ul>
20  *          </li>
21  *      </ul>
22  *      </li>
23  * </ul>
24  *
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 :)
27  *
28  * Functions:
29  *
30  *              init: initializes things (called by default)... currently no options are passed
31  *
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.
37  *              returns: nothing
38  *
39  *      Finds an item in the menu (including the root node "outside" the ul structure).
40  *              findItem: (item)
41  *                      item - string of the menu item you are looking for.
42  *                      returns: index of element, or -1 if not found.
43  */
44 (function($){
45         var methods = {
46                 init: function(options){
47
48                         var menuNode = this;
49                         if(!this.hasClass("SugarActionMenu")){
50                                 //tag this element as a sugarmenu
51                                 this.addClass("SugarActionMenu");
52
53                                 //Fix custom code buttons programatically to prevent metadata edits
54                                 this.find("input[type='submit'], input[type='button']").each(function(idx, node){
55                                         var jNode = $(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>");
64
65                                         newItemA.html(jNode.val());
66                     if(!disabled )
67                     {
68                         newItemA.click(function(event){
69                                 if($(this).hasClass("void") === false ){
70                                         jNode.click();
71                                 }
72                         });
73                     }
74                     else
75                     {
76                         newItemA.addClass("disabled");
77                     }
78                                         newItemA.attr("id", jNode.attr("id"));
79                     accesskey_el.attr("id", jNode.attr("id") + "_accesskey");
80                                         jNode.attr("id", jNode.attr("id") + "_old");
81
82
83                     if(accesskey !== undefined) {
84                         if($('#'+accesskey_el.attr('id')).length === 0) {
85                             accesskey_el.attr("accesskey", accesskey).click(function() {
86                                 jNode.click();
87                             }).appendTo("#content");
88                         }
89                         jNode.attr("accesskey", '');
90                     }
91
92                                         //make sure the node we found isn't the main item of the list -- we don't want
93                                         //to show it then.
94                                         if(menuNode.sugarActionMenu("findItem", newItemA.html()) == -1){
95                         parent.prepend(newItemA);
96                     }
97
98                     //make sub sliding menu
99                     jNode.siblings(".subnav-sub").each(function(idx, node) {
100                         var _menu = $(node);
101                         var _hide_menu = function() {
102                             if( _menu.hasClass("hover") === false )
103                                 _menu.hide();
104                         };
105                         var _hide_timer = null;
106                         var _delay = 300;
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");
113                                 if(_hide_timer)
114                                     clearTimeout(_hide_timer);
115                                 _hide_timer = setTimeout(_hide_menu, _delay);
116                             });
117
118                         newItemA.mouseover(function(evt) {
119                                 $("ul.SugarActionMenu ul.subnav-sub").each(function(index, node){
120                                     $(node).removeClass("hover");
121                                     $(node).hide();
122                                 });
123                                 var _left = parent.offset().left + parent.width() - newItemA.css("paddingRight").replace("px", "");
124                                 var _top = parent.offset().top - _menu.css("paddingTop").replace("px", "");
125                                 _menu.css({
126                                     left: _left,
127                                     top: _top
128                                     });
129                                 if( _menu.hasClass("hover") === false )
130                                     _menu.addClass("hover");
131                                 if( _subnav.hasClass("subnav-sub-handler") === false )
132                                     _subnav.addClass("subnav-sub-handler");
133                                 _menu.show();
134                             }).mouseout(function(evt) {
135                                 _menu.removeClass("hover");
136                                 _subnav.removeClass("subnav-sub-handler")
137                                 if(_hide_timer)
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);
143                             }).addClass("void");
144                         menuNode.append(_menu);
145                     });
146                     jNode.css("display", "none");
147                                 });
148
149
150                                 //look for all subnavs and set them up
151                                 this.find("ul.subnav").each(function(index, node){
152                                         var jNode = $(node);
153                                         var parent = jNode.parent();
154                                         var fancymenu = "";
155                                         var slideUpSpeed = 1;
156                                         var slideDownSpeed = 1;
157                     var dropDownHandle;
158
159                                         //if the dropdown handle doesn't exist, lets create it and
160                                         //add it to the dom
161                                         if(parent.find("span").length == 0){
162
163                                                 //create dropdown handle
164                                                 dropDownHandle = $(document.createElement("span"));
165                                                 parent.append(dropDownHandle);
166
167                                         } else {
168                                                 dropDownHandle = $(parent.find("span"));
169                                         }
170                                                 if(menuNode.hasClass("fancymenu")){
171                                                         dropDownHandle.addClass("ab");
172                                                         dropDownHandle.tipTip({maxWidth: "auto",
173                                                            edgeOffset: 10,
174                                        content: "More Actions",
175                                        defaultPosition: "top"});
176
177                                                 }
178
179
180
181                                                 //add click handler to handle
182                                                 dropDownHandle.click(function(event){
183
184
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');
189                             });
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");
195                                                                 }
196                                                         });
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 .
200                                 var _animation = {
201                                     'height' : 0
202                                 };
203                                 if(jNode.hasClass("upper")) {
204                                     _animation['top'] = (dropDownHandle.height() * -1);
205                                 }
206                                                                 jNode.animate(_animation, slideUpSpeed, function() {
207                                     $(this).css({
208                                         'height' : '',
209                                         'top' : ''
210                                     }).hide().removeClass("upper ddopen");
211                                 });
212                                                         }
213                                                         else{
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;
224                                 }
225
226                                                                 jNode.height(0).show().animate(_animation,slideDownSpeed, function() {
227                                     $(this).css('height', '');
228                                 });
229                                                                 jNode.addClass("ddopen");
230                                                         }
231                                                         event.stopPropagation();
232                                                 });
233
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");
241                             }
242                         }
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');
249                                 });
250
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));
255                                     }
256                                     setTimeout(_hide, _hide_subnav_delay);
257                                                                 });
258                                 //Hide second depth submenu
259                                 $("ul.SugarActionMenu ul.subnav-sub").each(function(subIndex, node){
260                                     var _hide = function() {
261                                         $(this).removeClass("hover");
262                                         $(this).hide();
263                                     }
264                                     _timer_for_subnav = setTimeout(_hide, _hide_subnav_delay);
265                                 });
266
267                                                         });
268                                                 }
269
270                                                 //add hover handler to handle
271                                                 dropDownHandle.hover(function(){
272                                                         dropDownHandle.addClass("subhover");
273                                                 }, function(){
274                                                         dropDownHandle.removeClass("subhover");
275                                                 });
276
277
278
279
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() {
285                                 _hide_subnav(jNode);
286                             }
287                             setTimeout(_hide, _hide_subnav_delay);
288                                                 });
289                                         });
290
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(" ", "&nbsp;");
295                                                 });
296                                         });
297                                 });
298
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, '') == '') {
302                         $(subnode).remove();
303                     }
304                 });
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) {
308                     var _this = $(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)
313                             menuNode.hide();
314                         else
315                             _this.prepend(sub_items);
316                     }
317                 });
318
319
320                         }
321                         return this;
322                 },
323                 addItem : function(args){
324                         if(args.index == null){
325                                 this.find("ul.subnav").each(function(index, node){
326                                         $(node).append(args.item);
327                                 })
328                         }
329                         else{
330                                 this.find("ul.subnav").find("li").each(function(index, node){
331                                         if(args.index == index+1){
332                                                 $(node).before(args.item);
333                                         }
334                                 });
335                         }
336                         return this;
337                 },
338                 findItem: function(item){
339                         var index = -1;
340                         this.find("a").each(function(idx, node){
341                                 var jNode = $(node);
342                                 if($.trim(jNode.html()) == $.trim(item)){
343                                         index = idx;
344                                 }
345                         });
346                         return index;
347                 },
348         /**
349          * To support IE fixed size rendering,
350          * parse out dom elements out of the fixed element
351          *
352          * Prepare ===
353          * <div style=position:fixed>
354          *     ...
355          *     <li jquery-attached>
356          *         <ul style=position:absoulte>
357          *             ...
358          *         </ul>
359          *     </li>
360          * </div>
361          *
362          * Application ===
363          * <div style=position:fixed>
364          *     <li ul-child-id='auto-evaluted-id'>
365          *     ...
366          *     </li>
367          * </div>
368          *
369          * <ul id='auto-evaluted-id' style=position:fix;left/right/top-positioning:auto-calculated>
370          *     ...
371          * </ul>
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
375          */
376         IEfix: function($ul) {
377             if ($.browser.msie && $.browser.version > 6) {
378                 if($ul) {
379                     if( $ul.hasClass('iefixed') === false)
380                         return;
381                     this.each(function(){
382                         SUGAR.themes.counter = SUGAR.themes.counter ? SUGAR.themes.counter++ : 1;
383                         var $$ = $(this),
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
387                             _css = {
388                                 top: _top,
389                                 width: _width,
390                                 position: 'fixed'
391                             },
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;
397                         } else {
398                             _css['left'] = _left;
399                         }
400                         $('body').append($ul.attr("id", _id).addClass("SugarActionMenuIESub").css(_css));
401                         $$.attr("ul-child-id", _id);
402                     });
403
404                 } else {
405                     this.each(function(){
406                         var _id = $(this).attr("ul-child-id");
407                         $(this).append($("body>#"+_id).removeClass("SugarActionMenuIESub"));
408                     });
409                 }
410             }
411         }
412         }
413
414         $.fn.sugarActionMenu = function(method) {
415
416                 if (methods[method]) {
417                         return methods[method].apply(this, Array.prototype.slice.call(
418                                         arguments, 1));
419                 } else if (typeof method === 'object' || !method) {
420                         return methods.init.apply(this, arguments);
421                 } else {
422                         $.error('Method ' + method + ' does not exist on jQuery.tooltip');
423                 }
424         }
425 })(jQuery);