/** * ColorSplitButton.js * * Copyright 2009, Moxiecode Systems AB * Released under LGPL License. * * License: http://tinymce.moxiecode.com/license * Contributing: http://tinymce.moxiecode.com/contributing */ (function(tinymce) { var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; /** * This class is used to create UI color split button. A color split button will present show a small color picker * when you press the open menu. * * @class tinymce.ui.ColorSplitButton * @extends tinymce.ui.SplitButton */ tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { /** * Constructs a new color split button control instance. * * @constructor * @method ColorSplitButton * @param {String} id Control id for the color split button. * @param {Object} s Optional name/value settings object. * @param {Editor} ed The editor instance this button is for. */ ColorSplitButton : function(id, s, ed) { var t = this; t.parent(id, s, ed); /** * Settings object. * * @property settings * @type Object */ t.settings = s = tinymce.extend({ colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', grid_width : 8, default_color : '#888888' }, t.settings); /** * Fires when the menu is shown. * * @event onShowMenu */ t.onShowMenu = new tinymce.util.Dispatcher(t); /** * Fires when the menu is hidden. * * @event onHideMenu */ t.onHideMenu = new tinymce.util.Dispatcher(t); /** * Current color value. * * @property value * @type String */ t.value = s.default_color; }, /** * Shows the color menu. The color menu is a layer places under the button * and displays a table of colors for the user to pick from. * * @method showMenu */ showMenu : function() { var t = this, r, p, e, p2; if (t.isDisabled()) return; if (!t.isMenuRendered) { t.renderMenu(); t.isMenuRendered = true; } if (t.isMenuVisible) return t.hideMenu(); e = DOM.get(t.id); DOM.show(t.id + '_menu'); DOM.addClass(e, 'mceSplitButtonSelected'); p2 = DOM.getPos(e); DOM.setStyles(t.id + '_menu', { left : p2.x, top : p2.y + e.clientHeight, zIndex : 200000 }); e = 0; Event.add(DOM.doc, 'mousedown', t.hideMenu, t); t.onShowMenu.dispatch(t); if (t._focused) { t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { if (e.keyCode == 27) t.hideMenu(); }); DOM.select('a', t.id + '_menu')[0].focus(); // Select first link } t.isMenuVisible = 1; }, /** * Hides the color menu. The optional event parameter is used to check where the event occured so it * doesn't close them menu if it was a event inside the menu. * * @method hideMenu * @param {Event} e Optional event object. */ hideMenu : function(e) { var t = this; if (t.isMenuVisible) { // Prevent double toogles by canceling the mouse click event to the button if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) return; if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { DOM.removeClass(t.id, 'mceSplitButtonSelected'); Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); Event.remove(t.id + '_menu', 'keydown', t._keyHandler); DOM.hide(t.id + '_menu'); } t.isMenuVisible = 0; } }, /** * Renders the menu to the DOM. * * @method renderMenu */ renderMenu : function() { var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); DOM.add(m, 'span', {'class' : 'mceMenuLine'}); n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); tb = DOM.add(n, 'tbody'); // Generate color grid i = 0; each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { c = c.replace(/^#/, ''); if (!i--) { tr = DOM.add(tb, 'tr'); i = s.grid_width - 1; } n = DOM.add(tr, 'td'); n = DOM.add(n, 'a', { role : 'option', href : 'javascript:;', style : { backgroundColor : '#' + c }, 'title': t.editor.getLang('colors.' + c, c), 'data-mce-color' : '#' + c }); if (t.editor.forcedHighContrastMode) { n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); if (n.getContext && (context = n.getContext("2d"))) { context.fillStyle = '#' + c; context.fillRect(0, 0, 16, 16); } else { // No point leaving a canvas element around if it's not supported for drawing on anyway. DOM.remove(n); } } }); if (s.more_colors_func) { n = DOM.add(tb, 'tr'); n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); Event.add(n, 'click', function(e) { s.more_colors_func.call(s.more_colors_scope || this); return Event.cancel(e); // Cancel to fix onbeforeunload problem }); } DOM.addClass(m, 'mceColorSplitMenu'); new tinymce.ui.KeyboardNavigation({ root: t.id + '_menu', items: DOM.select('a', t.id + '_menu'), onCancel: function() { t.hideMenu(); t.focus(); } }); // Prevent IE from scrolling and hindering click to occur #4019 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); Event.add(t.id + '_menu', 'click', function(e) { var c; e = DOM.getParent(e.target, 'a', tb); if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) t.setColor(c); return Event.cancel(e); // Prevent IE auto save warning }); return w; }, /** * Sets the current color for the control and hides the menu if it should be visible. * * @method setColor * @param {String} c Color code value in hex for example: #FF00FF */ setColor : function(c) { this.displayColor(c); this.hideMenu(); this.settings.onselect(c); }, /** * Change the currently selected color for the control. * * @method displayColor * @param {String} c Color code value in hex for example: #FF00FF */ displayColor : function(c) { var t = this; DOM.setStyle(t.id + '_preview', 'backgroundColor', c); t.value = c; }, /** * Post render event. This will be executed after the control has been rendered and can be used to * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). * * @method postRender */ postRender : function() { var t = this, id = t.id; t.parent(); DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); }, /** * Destroys the control. This means it will be removed from the DOM and any * events tied to it will also be removed. * * @method destroy */ destroy : function() { this.parent(); Event.clear(this.id + '_menu'); Event.clear(this.id + '_more'); DOM.remove(this.id + '_menu'); } }); })(tinymce);