]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/jquery/jquery.sugarMenu.js
Release 6.5.0beta6
[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 = 0;
156                                         var slideDownSpeed = 0;
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
200                                                                 jNode.slideUp(slideUpSpeed);
201                                                                 jNode.removeClass("ddopen");
202                                                         }
203                                                         else{
204                                 //To support IE fixed size rendering,
205                                 //parse out dom elements out of the fixed element
206                                 parent.sugarActionMenu('IEfix', jNode);
207
208                                                                 jNode.slideDown(slideDownSpeed).show();
209                                                                 jNode.addClass("ddopen");
210                                                         }
211                                                         event.stopPropagation();
212                                                 });
213
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");
221                             }
222                         }
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');
229                                 });
230
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));
235                                     }
236                                     setTimeout(_hide, _hide_subnav_delay);
237                                                                 });
238                                 //Hide second depth submenu
239                                 $("ul.SugarActionMenu ul.subnav-sub").each(function(subIndex, node){
240                                     var _hide = function() {
241                                         $(this).removeClass("hover");
242                                         $(this).hide();
243                                     }
244                                     _timer_for_subnav = setTimeout(_hide, _hide_subnav_delay);
245                                 });
246
247                                                         });
248                                                 }
249
250                                                 //add hover handler to handle
251                                                 dropDownHandle.hover(function(){
252                                                         dropDownHandle.addClass("subhover");
253                                                 }, function(){
254                                                         dropDownHandle.removeClass("subhover");
255                                                 });
256
257
258
259
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() {
265                                 _hide_subnav(jNode);
266                             }
267                             setTimeout(_hide, _hide_subnav_delay);
268                                                 });
269                                         });
270
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(" ", "&nbsp;");
275                                                 });
276                                         });
277                                 });
278
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, '') == '') {
282                         $(subnode).remove();
283                     }
284                 });
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) {
288                     var _this = $(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)
293                             menuNode.hide();
294                         else
295                             _this.prepend(sub_items);
296                     }
297                 });
298
299
300                         }
301                         return this;
302                 },
303                 addItem : function(args){
304                         if(args.index == null){
305                                 this.find("ul.subnav").each(function(index, node){
306                                         $(node).append(args.item);
307                                 })
308                         }
309                         else{
310                                 this.find("ul.subnav").find("li").each(function(index, node){
311                                         if(args.index == index+1){
312                                                 $(node).before(args.item);
313                                         }
314                                 });
315                         }
316                         return this;
317                 },
318                 findItem: function(item){
319                         var index = -1;
320                         this.find("a").each(function(idx, node){
321                                 var jNode = $(node);
322                                 if($.trim(jNode.html()) == $.trim(item)){
323                                         index = idx;
324                                 }
325                         });
326                         return index;
327                 },
328         /**
329          * To support IE fixed size rendering,
330          * parse out dom elements out of the fixed element
331          *
332          * Prepare ===
333          * <div style=position:fixed>
334          *     ...
335          *     <li jquery-attached>
336          *         <ul style=position:absoulte>
337          *             ...
338          *         </ul>
339          *     </li>
340          * </div>
341          *
342          * Application ===
343          * <div style=position:fixed>
344          *     <li ul-child-id='auto-evaluted-id'>
345          *     ...
346          *     </li>
347          * </div>
348          *
349          * <ul id='auto-evaluted-id' style=position:fix;left/right/top-positioning:auto-calculated>
350          *     ...
351          * </ul>
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
355          */
356         IEfix: function($ul) {
357             if ($.browser.msie && $.browser.version > 6) {
358                 if($ul) {
359                     if( $ul.hasClass('iefixed') === false)
360                         return;
361                     this.each(function(){
362                         SUGAR.themes.counter = SUGAR.themes.counter ? SUGAR.themes.counter++ : 1;
363                         var $$ = $(this),
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
367                             _css = {
368                                 top: _top,
369                                 width: _width,
370                                 position: 'fixed'
371                             },
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;
377                         } else {
378                             _css['left'] = _left;
379                         }
380                         $('body').append($ul.attr("id", _id).addClass("SugarActionMenuIESub").css(_css));
381                         $$.attr("ul-child-id", _id);
382                     });
383
384                 } else {
385                     this.each(function(){
386                         var _id = $(this).attr("ul-child-id");
387                         $(this).append($("body>#"+_id).removeClass("SugarActionMenuIESub"));
388                     });
389                 }
390             }
391         }
392         }
393
394         $.fn.sugarActionMenu = function(method) {
395
396                 if (methods[method]) {
397                         return methods[method].apply(this, Array.prototype.slice.call(
398                                         arguments, 1));
399                 } else if (typeof method === 'object' || !method) {
400                         return methods.init.apply(this, arguments);
401                 } else {
402                         $.error('Method ' + method + ' does not exist on jQuery.tooltip');
403                 }
404         }
405 })(jQuery);