editor_plugin.js:
/* * This plugin allows centered alignment for numeric tabular data. * For example: * * +---------------------+ * | 1 | * +---------------------+ * | 1,000 | * +---------------------+ * | 1,000,000 | * +---------------------+ * * This is achieved by wrapping the data in a right-aligned span, which is itself centered within the cell: * * <!-- Before --> * <td>1,000</td> * * <!-- After --> * <td style="text-align: center;"> * <span style="display: inline-block; text-align: right; white-space: nowrap; width: 5em;">1,000</span> * </td> * * All spans in the column must be given an explicit (user-specified) width that can accommodate the widest data point. * Some experimentation may be required to determine the appropriate width. */ tinymce.create("tinymce.plugins.NumAlignPlugin", { init: function (editor, url) { editor.addCommand("mceNumAlign", function () { editor.windowManager.open({ file: url + "/dialog.html", width: 240, height: 80, inline: true }, { plugin_url: url }); }); editor.addButton("numalign", { title: "Align Numbers", cmd: "mceNumAlign", image: url + "/button.gif" }); editor.onNodeChange.add(function (editor, controlManager, node) { var cell = editor.dom.getParent(node, "td, th"); controlManager.setDisabled("numalign", cell === null); }); } }); tinymce.PluginManager.add("numalign", tinymce.plugins.NumAlignPlugin);
dialog.html:
<!DOCTYPE html> <html> <head> <title>Align Numbers</title> <script type="text/javascript" src="../../tiny_mce_popup.js"></script> <script type="text/javascript" src="dialog.js"></script> </head> <body> <form> <label> Width: <input type="text" id="width" name="width" /> </label> <div class="mceActionPanel"> <input type="submit" id="insert" value="OK" /> <input type="button" id="cancel" value="Cancel" /> </div> </form> </body> </html>
dialog.js:
var P = tinyMCEPopup; // Initializes the dialog. function init() { var width = P.dom.get("width"); width.value = P.getWindowArg("width", "5em"); width.select(); P.dom.bind(P.dom.get("insert"), "click", update); P.dom.bind(P.dom.get("cancel"), "click", close); } // Aligns data in the currently selected column and closes the dialog. function update() { var cell = P.editor.dom.getParent(P.editor.selection.getStart(), "td, th"); var index = getIndex(cell); var table = P.editor.dom.getParent(cell, "table"); var rows = table.getElementsByTagName("tr"); for (var i = 0; i < rows.length; i++) { var cell = getCell(rows[i], index); if (cell.nodeName === "TD") { align(cell, P.dom.get("width").value); } } P.editor.execCommand("mceEndUndoLevel"); close(); } // Returns the column index of the given cell. function getIndex(cell) { var index = 0; var child = P.editor.dom.getParent(cell, "tr").firstElementChild; while (child !== null) { if (child === cell) { return index; } index += getColSpan(child); child = child.nextElementSibling; } return -1; } // Returns the cell at the given column index. function getCell(row, index) { var current = 0; var child = row.firstElementChild; while (child !== null) { var next = current + getColSpan(child); if (current <= index && index < next) { return child; } current = next; child = child.nextElementSibling; } return null; } // If the given node is a cell, returns the column span. // Otherwise, returns zero. function getColSpan(node) { if (node.nodeName === "TD" || node.nodeName === "TH") { return node.colSpan; } else { return 0; } } // Updates the given cell's contents and styles for centered numeric alignment. function align(cell, width) { P.editor.dom.setStyle(cell, "text-align", "center"); // We need the cell's contents to be wrapped in a span // If the cell contains only a single span (ignoring leading and trailing whitespace text nodes), then use this span // (This allows us to run the plugin repeatedly on the same column) // Otherwise, remove the cell's child nodes, add them to a newly created span, and add the span to the cell var nodes = trim(cell.childNodes); var span; if (nodes.length === 1 && nodes[0].nodeName === "SPAN") { span = nodes[0]; } else { nodes = P.editor.dom.remove(cell.childNodes); span = P.editor.dom.create("span"); for (var i = 0; i < nodes.length; i++) { P.editor.dom.add(span, nodes[i]); } P.editor.dom.add(cell, span); } P.editor.dom.setStyle(span, "display", "inline-block"); P.editor.dom.setStyle(span, "text-align", "right"); P.editor.dom.setStyle(span, "white-space", "nowrap"); P.editor.dom.setStyle(span, "width", width); } // Removes leading and trailing whitespace text nodes. function trim(nodes) { if (nodes.length === 0) { return []; } var start = 0; var end = nodes.length; while (start < end && isWhitespace(nodes[start])) { start++; } while (end > start && isWhitespace(nodes[end - 1])) { end--; } var result = new Array(end - start); for (var i = 0; i < result.length; i++) { result[i] = nodes[start + i]; } return result; } // Returns whether the given node is a whitespace text node. function isWhitespace(node) { return node.nodeName === "#text" && node.textContent.trim().length === 0; } // Closes the dialog. function close() { P.close(); } P.onInit.add(init);