// Copyright 2008 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Generic factory functions for creating the building blocks for * an editor toolbar. * * @author attila@google.com (Attila Bodis) */ goog.provide('goog.ui.editor.ToolbarFactory'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.string'); goog.require('goog.string.Unicode'); goog.require('goog.style'); goog.require('goog.ui.Component'); goog.require('goog.ui.Container'); goog.require('goog.ui.Option'); goog.require('goog.ui.Toolbar'); goog.require('goog.ui.ToolbarButton'); goog.require('goog.ui.ToolbarColorMenuButton'); goog.require('goog.ui.ToolbarMenuButton'); goog.require('goog.ui.ToolbarRenderer'); goog.require('goog.ui.ToolbarSelect'); goog.require('goog.userAgent'); /** * Takes a font spec (e.g. "Arial, Helvetica, sans-serif") and returns the * primary font name, normalized to lowercase (e.g. "arial"). * @param {string} fontSpec Font specification. * @return {string} The primary font name, in lowercase. */ goog.ui.editor.ToolbarFactory.getPrimaryFont = function(fontSpec) { var i = fontSpec.indexOf(','); var fontName = (i != -1 ? fontSpec.substring(0, i) : fontSpec).toLowerCase(); // Strip leading/trailing quotes from the font name (bug 1050118). return goog.string.stripQuotes(fontName, '"\''); }; /** * Bulk-adds fonts to the given font menu button. The argument must be an * array of font descriptor objects, each of which must have the following * attributes: * * @param {!goog.ui.Select} button Font menu button. * @param {!Array<{caption: string, value: string}>} fonts Array of * font descriptors. */ goog.ui.editor.ToolbarFactory.addFonts = function(button, fonts) { goog.array.forEach(fonts, function(font) { goog.ui.editor.ToolbarFactory.addFont(button, font.caption, font.value); }); }; /** * Adds a menu item to the given font menu button. The first font listed in * the {@code value} argument is considered the font ID, so adding two items * whose CSS style starts with the same font may lead to unpredictable results. * @param {!goog.ui.Select} button Font menu button. * @param {string} caption Caption to show for the font menu. * @param {string} value Value for the corresponding 'font-family' CSS style. */ goog.ui.editor.ToolbarFactory.addFont = function(button, caption, value) { // The font ID is the first font listed in the CSS style, normalized to // lowercase. var id = goog.ui.editor.ToolbarFactory.getPrimaryFont(value); // Construct the option, and add it to the button. var option = new goog.ui.Option(caption, value, button.getDomHelper()); option.setId(id); button.addItem(option); // Captions are shown in their own font. option.getContentElement().style.fontFamily = value; }; /** * Bulk-adds font sizes to the given font size menu button. The argument must * be an array of font size descriptor objects, each of which must have the * following attributes: * * @param {!goog.ui.Select} button Font size menu button. * @param {!Array<{caption: string, value:number}>} sizes Array of font * size descriptors. */ goog.ui.editor.ToolbarFactory.addFontSizes = function(button, sizes) { goog.array.forEach(sizes, function(size) { goog.ui.editor.ToolbarFactory.addFontSize(button, size.caption, size.value); }); }; /** * Adds a menu item to the given font size menu button. The {@code value} * argument must be a legacy HTML font size in the 0-7 range. * @param {!goog.ui.Select} button Font size menu button. * @param {string} caption Caption to show in the font size menu. * @param {number} value Value for the corresponding HTML font size. */ goog.ui.editor.ToolbarFactory.addFontSize = function(button, caption, value) { // Construct the option, and add it to the button. var option = new goog.ui.Option(caption, value, button.getDomHelper()); button.addItem(option); // Adjust the font size of the menu item and the height of the checkbox // element after they've been rendered by addItem(). Captions are shown in // the corresponding font size, and lining up the checkbox is tricky. var content = option.getContentElement(); content.style.fontSize = goog.ui.editor.ToolbarFactory.getPxFromLegacySize(value) + 'px'; content.firstChild.style.height = '1.1em'; }; /** * Converts a legacy font size specification into an equivalent pixel size. * For example, {@code <font size="6">} is {@code font-size: 32px;}, etc. * @param {number} fontSize Legacy font size spec in the 0-7 range. * @return {number} Equivalent pixel size. */ goog.ui.editor.ToolbarFactory.getPxFromLegacySize = function(fontSize) { return goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_[fontSize] || 10; }; /** * Converts a pixel font size specification into an equivalent legacy size. * For example, {@code font-size: 32px;} is {@code <font size="6">}, etc. * If the given pixel size doesn't exactly match one of the legacy sizes, -1 is * returned. * @param {number} px Pixel font size. * @return {number} Equivalent legacy size spec in the 0-7 range, or -1 if none * exists. */ goog.ui.editor.ToolbarFactory.getLegacySizeFromPx = function(px) { // Use lastIndexOf to get the largest legacy size matching the pixel size // (most notably returning 1 instead of 0 for 10px). return goog.array.lastIndexOf( goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_, px); }; /** * Map of legacy font sizes (0-7) to equivalent pixel sizes. * @type {!Array} * @private */ goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_ = [10, 10, 13, 16, 18, 24, 32, 48]; /** * Bulk-adds format options to the given "Format block" menu button. The * argument must be an array of format option descriptor objects, each of * which must have the following attributes: * * @param {!goog.ui.Select} button "Format block" menu button. * @param {!Array<{caption: string, command: !goog.dom.TagName}>} formats Array * of format option descriptors. */ goog.ui.editor.ToolbarFactory.addFormatOptions = function(button, formats) { goog.array.forEach(formats, function(format) { goog.ui.editor.ToolbarFactory.addFormatOption( button, format.caption, format.command); }); }; /** * Adds a menu item to the given "Format block" menu button. * @param {!goog.ui.Select} button "Format block" menu button. * @param {string} caption Caption to show in the menu. * @param {!goog.dom.TagName} tag Corresponding block format tag. */ goog.ui.editor.ToolbarFactory.addFormatOption = function(button, caption, tag) { // Construct the option, and add it to the button. // TODO(attila): Create boring but functional menu item for now... var buttonDom = button.getDomHelper(); var option = new goog.ui.Option( buttonDom.createDom(goog.dom.TagName.DIV, null, caption), tag, buttonDom); option.setId(String(tag)); button.addItem(option); }; /** * Creates a {@link goog.ui.Toolbar} containing the specified set of * toolbar buttons, and renders it into the given parent element. Each * item in the {@code items} array must a {@link goog.ui.Control}. * @param {!Array} items Toolbar items; each must * be a {@link goog.ui.Control}. * @param {!Element} elem Toolbar parent element. * @param {boolean=} opt_isRightToLeft Whether the editor chrome is * right-to-left; defaults to the directionality of the toolbar parent * element. * @return {!goog.ui.Toolbar} Editor toolbar, rendered into the given parent * element. */ goog.ui.editor.ToolbarFactory.makeToolbar = function( items, elem, opt_isRightToLeft) { var domHelper = goog.dom.getDomHelper(elem); // Create an empty horizontal toolbar using the default renderer. var toolbar = new goog.ui.Toolbar( goog.ui.ToolbarRenderer.getInstance(), goog.ui.Container.Orientation.HORIZONTAL, domHelper); // Optimization: Explicitly test for the directionality of the parent // element here, so we can set it for both the toolbar and its children, // saving a lot of expensive calls to goog.style.isRightToLeft() during // rendering. var isRightToLeft = opt_isRightToLeft || goog.style.isRightToLeft(elem); toolbar.setRightToLeft(isRightToLeft); // Optimization: Set the toolbar to non-focusable before it is rendered, // to avoid creating unnecessary keyboard event handler objects. toolbar.setFocusable(false); for (var i = 0, button; button = items[i]; i++) { // Optimization: Set the button to non-focusable before it is rendered, // to avoid creating unnecessary keyboard event handler objects. Also set // the directionality of the button explicitly, to avoid expensive calls // to goog.style.isRightToLeft() during rendering. button.setSupportedState(goog.ui.Component.State.FOCUSED, false); button.setRightToLeft(isRightToLeft); toolbar.addChild(button, true); } toolbar.render(elem); return toolbar; }; /** * Creates a toolbar button with the given ID, tooltip, and caption. Applies * any custom CSS class names to the button's caption element. * @param {string} id Button ID; must equal a {@link goog.editor.Command} for * built-in buttons, anything else for custom buttons. * @param {string} tooltip Tooltip to be shown on hover. * @param {goog.ui.ControlContent} caption Button caption. * @param {string=} opt_classNames CSS class name(s) to apply to the caption * element. * @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to * {@link goog.ui.ToolbarButtonRenderer} if unspecified. * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!goog.ui.Button} A toolbar button. */ goog.ui.editor.ToolbarFactory.makeButton = function( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper) { var button = new goog.ui.ToolbarButton( goog.ui.editor.ToolbarFactory.createContent_( caption, opt_classNames, opt_domHelper), opt_renderer, opt_domHelper); button.setId(id); button.setTooltip(tooltip); return button; }; /** * Creates a toggle button with the given ID, tooltip, and caption. Applies * any custom CSS class names to the button's caption element. The button * returned has checkbox-like toggle semantics. * @param {string} id Button ID; must equal a {@link goog.editor.Command} for * built-in buttons, anything else for custom buttons. * @param {string} tooltip Tooltip to be shown on hover. * @param {goog.ui.ControlContent} caption Button caption. * @param {string=} opt_classNames CSS class name(s) to apply to the caption * element. * @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to * {@link goog.ui.ToolbarButtonRenderer} if unspecified. * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!goog.ui.Button} A toggle button. */ goog.ui.editor.ToolbarFactory.makeToggleButton = function( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper) { var button = goog.ui.editor.ToolbarFactory.makeButton( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper); button.setSupportedState(goog.ui.Component.State.CHECKED, true); return button; }; /** * Creates a menu button with the given ID, tooltip, and caption. Applies * any custom CSS class names to the button's caption element. The button * returned doesn't have an actual menu attached; use {@link * goog.ui.MenuButton#setMenu} to attach a {@link goog.ui.Menu} to the * button. * @param {string} id Button ID; must equal a {@link goog.editor.Command} for * built-in buttons, anything else for custom buttons. * @param {string} tooltip Tooltip to be shown on hover. * @param {goog.ui.ControlContent} caption Button caption. * @param {string=} opt_classNames CSS class name(s) to apply to the caption * element. * @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to * {@link goog.ui.ToolbarMenuButtonRenderer} if unspecified. * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!goog.ui.MenuButton} A menu button. */ goog.ui.editor.ToolbarFactory.makeMenuButton = function( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper) { var button = new goog.ui.ToolbarMenuButton( goog.ui.editor.ToolbarFactory.createContent_( caption, opt_classNames, opt_domHelper), null, opt_renderer, opt_domHelper); button.setId(id); button.setTooltip(tooltip); return button; }; /** * Creates a select button with the given ID, tooltip, and caption. Applies * any custom CSS class names to the button's root element. The button * returned doesn't have an actual menu attached; use {@link * goog.ui.Select#setMenu} to attach a {@link goog.ui.Menu} containing * {@link goog.ui.Option}s to the select button. * @param {string} id Button ID; must equal a {@link goog.editor.Command} for * built-in buttons, anything else for custom buttons. * @param {string} tooltip Tooltip to be shown on hover. * @param {goog.ui.ControlContent} caption Button caption; used as the * default caption when nothing is selected. * @param {string=} opt_classNames CSS class name(s) to apply to the button's * root element. * @param {goog.ui.MenuButtonRenderer=} opt_renderer Button renderer; * defaults to {@link goog.ui.ToolbarMenuButtonRenderer} if unspecified. * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!goog.ui.Select} A select button. */ goog.ui.editor.ToolbarFactory.makeSelectButton = function( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper) { var button = new goog.ui.ToolbarSelect(null, null, opt_renderer, opt_domHelper); if (opt_classNames) { // Unlike the other button types, for goog.ui.Select buttons we apply the // extra class names to the root element, because for select buttons the // caption isn't stable (as it changes each time the selection changes). goog.array.forEach( opt_classNames.split(/\s+/), button.addClassName, button); } button.addClassName(goog.getCssName('goog-toolbar-select')); button.setDefaultCaption(caption); button.setId(id); button.setTooltip(tooltip); return button; }; /** * Creates a color menu button with the given ID, tooltip, and caption. * Applies any custom CSS class names to the button's caption element. The * button is created with a default color menu containing standard color * palettes. * @param {string} id Button ID; must equal a {@link goog.editor.Command} for * built-in toolbar buttons, but can be anything else for custom buttons. * @param {string} tooltip Tooltip to be shown on hover. * @param {goog.ui.ControlContent} caption Button caption. * @param {string=} opt_classNames CSS class name(s) to apply to the caption * element. * @param {goog.ui.ColorMenuButtonRenderer=} opt_renderer Button renderer; * defaults to {@link goog.ui.ToolbarColorMenuButtonRenderer} * if unspecified. * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!goog.ui.ColorMenuButton} A color menu button. */ goog.ui.editor.ToolbarFactory.makeColorMenuButton = function( id, tooltip, caption, opt_classNames, opt_renderer, opt_domHelper) { var button = new goog.ui.ToolbarColorMenuButton( goog.ui.editor.ToolbarFactory.createContent_( caption, opt_classNames, opt_domHelper), null, opt_renderer, opt_domHelper); button.setId(id); button.setTooltip(tooltip); return button; }; /** * Creates a new DIV that wraps a button caption, optionally applying CSS * class names to it. Used as a helper function in button factory methods. * @param {goog.ui.ControlContent} caption Button caption. * @param {string=} opt_classNames CSS class name(s) to apply to the DIV that * wraps the caption (if any). * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM * creation; defaults to the current document if unspecified. * @return {!Element} DIV that wraps the caption. * @private */ goog.ui.editor.ToolbarFactory.createContent_ = function( caption, opt_classNames, opt_domHelper) { // FF2 doesn't like empty DIVs, especially when rendered right-to-left. if ((!caption || caption == '') && goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9a')) { caption = goog.string.Unicode.NBSP; } return (opt_domHelper || goog.dom.getDomHelper()) .createDom(goog.dom.TagName.DIV, opt_classNames, caption); };