4 * @param {Object} parent Reference to instance of Editor instance
5 * @param {Element} container Reference to the toolbar container element
9 * <a data-wysihtml5-command="createLink">insert link</a>
10 * <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
14 * var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
17 (function(wysihtml5) {
18 var CLASS_NAME_COMMAND_DISABLED = "wysihtml5-command-disabled",
19 CLASS_NAME_COMMANDS_DISABLED = "wysihtml5-commands-disabled",
20 CLASS_NAME_COMMAND_ACTIVE = "wysihtml5-command-active",
21 CLASS_NAME_ACTION_ACTIVE = "wysihtml5-action-active",
24 wysihtml5.toolbar.Toolbar = Base.extend(
25 /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
26 constructor: function(editor, container, showOnInit) {
28 this.container = typeof(container) === "string" ? document.getElementById(container) : container;
29 this.composer = editor.composer;
31 this._getLinks("command");
32 this._getLinks("action");
35 if (showOnInit) { this.show(); }
37 if (editor.config.classNameCommandDisabled != null) {
38 CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
40 if (editor.config.classNameCommandsDisabled != null) {
41 CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
43 if (editor.config.classNameCommandActive != null) {
44 CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
46 if (editor.config.classNameActionActive != null) {
47 CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
50 var speechInputLinks = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
51 length = speechInputLinks.length,
53 for (; i<length; i++) {
54 new wysihtml5.toolbar.Speech(this, speechInputLinks[i]);
58 _getLinks: function(type) {
59 var links = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
60 length = links.length,
62 mapping = this[type + "Mapping"] = {},
68 for (; i<length; i++) {
70 name = link.getAttribute("data-wysihtml5-" + type);
71 value = link.getAttribute("data-wysihtml5-" + type + "-value");
72 group = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
73 dialog = this._getDialog(link, name);
75 mapping[name + ":" + value] = {
86 _getDialog: function(link, command) {
88 dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
93 if (wysihtml5.toolbar["Dialog_" + command]) {
94 dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
96 dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
99 dialog.on("show", function() {
100 caretBookmark = that.composer.selection.getBookmark();
102 that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
105 dialog.on("save", function(attributes) {
107 that.composer.selection.setBookmark(caretBookmark);
109 that._execCommand(command, attributes);
111 that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
114 dialog.on("cancel", function() {
115 that.editor.focus(false);
116 that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
124 * var toolbar = new wysihtml5.Toolbar();
125 * // Insert a <blockquote> element or wrap current selection in <blockquote>
126 * toolbar.execCommand("formatBlock", "blockquote");
128 execCommand: function(command, commandValue) {
129 if (this.commandsDisabled) {
133 var commandObj = this.commandMapping[command + ":" + commandValue];
135 // Show dialog when available
136 if (commandObj && commandObj.dialog && !commandObj.state) {
137 commandObj.dialog.show();
139 this._execCommand(command, commandValue);
143 _execCommand: function(command, commandValue) {
144 // Make sure that composer is focussed (false => don't move caret to the end)
145 this.editor.focus(false);
147 this.composer.commands.exec(command, commandValue);
148 this._updateLinkStates();
151 execAction: function(action) {
152 var editor = this.editor;
153 if (action === "change_view") {
154 if (editor.textarea) {
155 if (editor.currentView === editor.textarea) {
156 editor.fire("change_view", "composer");
158 editor.fire("change_view", "textarea");
162 if (action == "showSource") {
163 editor.fire("showSource");
167 _observe: function() {
169 editor = this.editor,
170 container = this.container,
171 links = this.commandLinks.concat(this.actionLinks),
172 length = links.length,
175 for (; i<length; i++) {
176 // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
177 // (you know, a:link { ... } doesn't match anchors with missing href attribute)
178 if (links[i].nodeName === "A") {
180 href: "javascript:;",
184 dom.setAttributes({ unselectable: "on" }).on(links[i]);
188 // Needed for opera and chrome
189 dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
191 dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
193 command = link.getAttribute("data-wysihtml5-command"),
194 commandValue = link.getAttribute("data-wysihtml5-command-value");
195 that.execCommand(command, commandValue);
196 event.preventDefault();
199 dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
200 var action = this.getAttribute("data-wysihtml5-action");
201 that.execAction(action);
202 event.preventDefault();
205 editor.on("interaction:composer", function() {
206 that._updateLinkStates();
209 editor.on("focus:composer", function() {
210 that.bookmark = null;
213 if (this.editor.config.handleTables) {
214 editor.on("tableselect:composer", function() {
215 that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
217 editor.on("tableunselect:composer", function() {
218 that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
222 editor.on("change_view", function(currentView) {
223 // Set timeout needed in order to let the blur event fire first
224 if (editor.textarea) {
225 setTimeout(function() {
226 that.commandsDisabled = (currentView !== "composer");
227 that._updateLinkStates();
228 if (that.commandsDisabled) {
229 dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
231 dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
238 _updateLinkStates: function() {
240 var commandMapping = this.commandMapping,
241 actionMapping = this.actionMapping,
246 // every millisecond counts... this is executed quite often
247 for (i in commandMapping) {
248 command = commandMapping[i];
249 if (this.commandsDisabled) {
251 dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
253 dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
255 if (command.dialog) {
256 command.dialog.hide();
259 state = this.composer.commands.state(command.name, command.value);
260 dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
262 dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
265 if (command.state === state) {
269 command.state = state;
271 dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
273 dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
275 if (command.dialog) {
276 if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
278 if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
279 // Grab first and only object/element in state array, otherwise convert state into boolean
280 // to avoid showing a dialog for multiple selected elements which may have different attributes
281 // eg. when two links with different href are selected, the state will be an array consisting of both link elements
282 // but the dialog interface can only update one
283 state = state.length === 1 ? state[0] : true;
284 command.state = state;
286 command.dialog.show(state);
288 command.dialog.hide();
292 dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
294 dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
296 if (command.dialog) {
297 command.dialog.hide();
302 for (i in actionMapping) {
303 action = actionMapping[i];
305 if (action.name === "change_view") {
306 action.state = this.editor.currentView === this.editor.textarea;
308 dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
310 dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
317 this.container.style.display = "";
321 this.container.style.display = "none";