]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/ui/DropMenu.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / ui / DropMenu.js
1 /**
2  * DropMenu.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
13
14         /**
15          * This class is used to create drop menus, a drop menu can be a
16          * context menu, or a menu for a list box or a menu bar.
17          *
18          * @class tinymce.ui.DropMenu
19          * @extends tinymce.ui.Menu
20          */
21         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
22                 /**
23                  * Constructs a new drop menu control instance.
24                  *
25                  * @constructor
26                  * @method DropMenu
27                  * @param {String} id Button control id for the button.
28                  * @param {Object} s Optional name/value settings object.
29                  */
30                 DropMenu : function(id, s) {
31                         s = s || {};
32                         s.container = s.container || DOM.doc.body;
33                         s.offset_x = s.offset_x || 0;
34                         s.offset_y = s.offset_y || 0;
35                         s.vp_offset_x = s.vp_offset_x || 0;
36                         s.vp_offset_y = s.vp_offset_y || 0;
37
38                         if (is(s.icons) && !s.icons)
39                                 s['class'] += ' mceNoIcons';
40
41                         this.parent(id, s);
42                         this.onShowMenu = new tinymce.util.Dispatcher(this);
43                         this.onHideMenu = new tinymce.util.Dispatcher(this);
44                         this.classPrefix = 'mceMenu';
45                 },
46
47                 /**
48                  * Created a new sub menu for the drop menu control.
49                  *
50                  * @method createMenu
51                  * @param {Object} s Optional name/value settings object.
52                  * @return {tinymce.ui.DropMenu} New drop menu instance.
53                  */
54                 createMenu : function(s) {
55                         var t = this, cs = t.settings, m;
56
57                         s.container = s.container || cs.container;
58                         s.parent = t;
59                         s.constrain = s.constrain || cs.constrain;
60                         s['class'] = s['class'] || cs['class'];
61                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
62                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
63                         s.keyboard_focus = cs.keyboard_focus;
64                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
65
66                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
67
68                         return m;
69                 },
70                 
71                 focus : function() {
72                         var t = this;
73                         if (t.keyboardNav) {
74                                 t.keyboardNav.focus();
75                         }
76                 },
77
78                 /**
79                  * Repaints the menu after new items have been added dynamically.
80                  *
81                  * @method update
82                  */
83                 update : function() {
84                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
85
86                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
87                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
88
89                         if (!DOM.boxModel)
90                                 t.element.setStyles({width : tw + 2, height : th + 2});
91                         else
92                                 t.element.setStyles({width : tw, height : th});
93
94                         if (s.max_width)
95                                 DOM.setStyle(co, 'width', tw);
96
97                         if (s.max_height) {
98                                 DOM.setStyle(co, 'height', th);
99
100                                 if (tb.clientHeight < s.max_height)
101                                         DOM.setStyle(co, 'overflow', 'hidden');
102                         }
103                 },
104
105                 /**
106                  * Displays the menu at the specified cordinate.
107                  *
108                  * @method showMenu
109                  * @param {Number} x Horizontal position of the menu.
110                  * @param {Number} y Vertical position of the menu.
111                  * @param {Numner} px Optional parent X position used when menus are cascading.
112                  */
113                 showMenu : function(x, y, px) {
114                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
115
116                         t.collapse(1);
117
118                         if (t.isMenuVisible)
119                                 return;
120
121                         if (!t.rendered) {
122                                 co = DOM.add(t.settings.container, t.renderNode());
123
124                                 each(t.items, function(o) {
125                                         o.postRender();
126                                 });
127
128                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
129                         } else
130                                 co = DOM.get('menu_' + t.id);
131
132                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
133                         if (!tinymce.isOpera)
134                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
135
136                         DOM.show(co);
137                         t.update();
138
139                         x += s.offset_x || 0;
140                         y += s.offset_y || 0;
141                         vp.w -= 4;
142                         vp.h -= 4;
143
144                         // Move inside viewport if not submenu
145                         if (s.constrain) {
146                                 w = co.clientWidth - ot;
147                                 h = co.clientHeight - ot;
148                                 mx = vp.x + vp.w;
149                                 my = vp.y + vp.h;
150
151                                 if ((x + s.vp_offset_x + w) > mx)
152                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
153
154                                 if ((y + s.vp_offset_y + h) > my)
155                                         y = Math.max(0, (my - s.vp_offset_y) - h);
156                         }
157
158                         DOM.setStyles(co, {left : x , top : y});
159                         t.element.update();
160
161                         t.isMenuVisible = 1;
162                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
163                                 var m;
164
165                                 e = e.target;
166
167                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
168                                         m = t.items[e.id];
169
170                                         if (m.isDisabled())
171                                                 return;
172
173                                         dm = t;
174
175                                         while (dm) {
176                                                 if (dm.hideMenu)
177                                                         dm.hideMenu();
178
179                                                 dm = dm.settings.parent;
180                                         }
181
182                                         if (m.settings.onclick)
183                                                 m.settings.onclick(e);
184
185                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
186                                 }
187                         });
188
189                         if (t.hasMenus()) {
190                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
191                                         var m, r, mi;
192
193                                         e = e.target;
194                                         if (e && (e = DOM.getParent(e, 'tr'))) {
195                                                 m = t.items[e.id];
196
197                                                 if (t.lastMenu)
198                                                         t.lastMenu.collapse(1);
199
200                                                 if (m.isDisabled())
201                                                         return;
202
203                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
204                                                         //p = DOM.getPos(s.container);
205                                                         r = DOM.getRect(e);
206                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
207                                                         t.lastMenu = m;
208                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
209                                                 }
210                                         }
211                                 });
212                         }
213                         
214                         Event.add(co, 'keydown', t._keyHandler, t);
215
216                         t.onShowMenu.dispatch(t);
217
218                         if (s.keyboard_focus) { 
219                                 t._setupKeyboardNav(); 
220                         }
221                 },
222
223                 /**
224                  * Hides the displayed menu.
225                  *
226                  * @method hideMenu
227                  */
228                 hideMenu : function(c) {
229                         var t = this, co = DOM.get('menu_' + t.id), e;
230
231                         if (!t.isMenuVisible)
232                                 return;
233
234                         if (t.keyboardNav) t.keyboardNav.destroy();
235                         Event.remove(co, 'mouseover', t.mouseOverFunc);
236                         Event.remove(co, 'click', t.mouseClickFunc);
237                         Event.remove(co, 'keydown', t._keyHandler);
238                         DOM.hide(co);
239                         t.isMenuVisible = 0;
240
241                         if (!c)
242                                 t.collapse(1);
243
244                         if (t.element)
245                                 t.element.hide();
246
247                         if (e = DOM.get(t.id))
248                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
249
250                         t.onHideMenu.dispatch(t);
251                 },
252
253                 /**
254                  * Adds a new menu, menu item or sub classes of them to the drop menu.
255                  *
256                  * @method add
257                  * @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu.
258                  * @return {tinymce.ui.Control} Same as the input control, the menu or menu item.
259                  */
260                 add : function(o) {
261                         var t = this, co;
262
263                         o = t.parent(o);
264
265                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
266                                 t._add(DOM.select('tbody', co)[0], o);
267
268                         return o;
269                 },
270
271                 /**
272                  * Collapses the menu, this will hide the menu and all menu items.
273                  *
274                  * @method collapse
275                  * @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well.
276                  */
277                 collapse : function(d) {
278                         this.parent(d);
279                         this.hideMenu(1);
280                 },
281
282                 /**
283                  * Removes a specific sub menu or menu item from the drop menu.
284                  *
285                  * @method remove
286                  * @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu.
287                  * @return {tinymce.ui.Control} Control instance or null if it wasn't found.
288                  */
289                 remove : function(o) {
290                         DOM.remove(o.id);
291                         this.destroy();
292
293                         return this.parent(o);
294                 },
295
296                 /**
297                  * Destroys the menu. This will remove the menu from the DOM and any events added to it etc.
298                  *
299                  * @method destroy
300                  */
301                 destroy : function() {
302                         var t = this, co = DOM.get('menu_' + t.id);
303
304                         if (t.keyboardNav) t.keyboardNav.destroy();
305                         Event.remove(co, 'mouseover', t.mouseOverFunc);
306                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
307                         Event.remove(co, 'click', t.mouseClickFunc);
308                         Event.remove(co, 'keydown', t._keyHandler);
309
310                         if (t.element)
311                                 t.element.remove();
312
313                         DOM.remove(co);
314                 },
315
316                 /**
317                  * Renders the specified menu node to the dom.
318                  *
319                  * @method renderNode
320                  * @return {Element} Container element for the drop menu.
321                  */
322                 renderNode : function() {
323                         var t = this, s = t.settings, n, tb, co, w;
324
325                         w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
326                         if (t.settings.parent) {
327                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
328                         }
329                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
330                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
331
332                         if (s.menu_line)
333                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
334
335 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
336                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
337                         tb = DOM.add(n, 'tbody');
338
339                         each(t.items, function(o) {
340                                 t._add(tb, o);
341                         });
342
343                         t.rendered = true;
344
345                         return w;
346                 },
347
348                 // Internal functions
349                 _setupKeyboardNav : function(){
350                         var contextMenu, menuItems, t=this; 
351                         contextMenu = DOM.select('#menu_' + t.id)[0];
352                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
353                         menuItems.splice(0,0,contextMenu);
354                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
355                                 root: 'menu_' + t.id,
356                                 items: menuItems,
357                                 onCancel: function() {
358                                         t.hideMenu();
359                                 },
360                                 enableUpDown: true
361                         });
362                         contextMenu.focus();
363                 },
364
365                 _keyHandler : function(evt) {
366                         var t = this, e;
367                         switch (evt.keyCode) {
368                                 case 37: // Left
369                                         if (t.settings.parent) {
370                                                 t.hideMenu();
371                                                 t.settings.parent.focus();
372                                                 Event.cancel(evt);
373                                         }
374                                         break;
375                                 case 39: // Right
376                                         if (t.mouseOverFunc)
377                                                 t.mouseOverFunc(evt);
378                                         break;
379                         }
380                 },
381
382                 _add : function(tb, o) {
383                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
384
385                         if (s.separator) {
386                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
387                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
388
389                                 if (n = ro.previousSibling)
390                                         DOM.addClass(n, 'mceLast');
391
392                                 return;
393                         }
394
395                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
396                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
397                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
398
399                         if (s.parent) {
400                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
401                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
402                         }
403
404                         DOM.addClass(it, s['class']);
405 //                      n = DOM.add(n, 'span', {'class' : 'item'});
406
407                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
408
409                         if (s.icon_src)
410                                 DOM.add(ic, 'img', {src : s.icon_src});
411
412                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
413
414                         if (o.settings.style)
415                                 DOM.setAttrib(n, 'style', o.settings.style);
416
417                         if (tb.childNodes.length == 1)
418                                 DOM.addClass(ro, 'mceFirst');
419
420                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
421                                 DOM.addClass(ro, 'mceFirst');
422
423                         if (o.collapse)
424                                 DOM.addClass(ro, cp + 'ItemSub');
425
426                         if (n = ro.previousSibling)
427                                 DOM.removeClass(n, 'mceLast');
428
429                         DOM.addClass(ro, 'mceLast');
430                 }
431         });
432 })(tinymce);