3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('exec-command', function (Y, NAME) {
11 * Plugin for the frame module to handle execCommands for Editor
12 * @class Plugin.ExecCommand
16 * @submodule exec-command
18 var ExecCommand = function() {
19 ExecCommand.superclass.constructor.apply(this, arguments);
22 Y.extend(ExecCommand, Y.Base, {
24 * An internal reference to the keyCode of the last key that was pressed.
30 * An internal reference to the instance of the frame plugged into.
36 * Execute a command on the frame's document.
38 * @param {String} action The action to perform (bold, italic, fontname)
39 * @param {String} value The optional value (helvetica)
40 * @return {Node/NodeList} Should return the Node/Nodelist affected
42 command: function(action, value) {
43 var fn = ExecCommand.COMMANDS[action];
46 return fn.call(this, action, value);
48 return this._command(action, value);
52 * The private version of execCommand that doesn't filter for overrides.
55 * @param {String} action The action to perform (bold, italic, fontname)
56 * @param {String} value The optional value (helvetica)
58 _command: function(action, value) {
59 var inst = this.getInstance();
62 inst.config.doc.execCommand('styleWithCSS', null, 1);
65 inst.config.doc.execCommand('useCSS', null, 0);
69 inst.config.doc.execCommand(action, null, value);
74 * Get's the instance of YUI bound to the parent frame
76 * @return {YUI} The YUI instance bound to the parent frame
78 getInstance: function() {
80 this._inst = this.get('host').getInstance();
84 initializer: function() {
85 Y.mix(this.get('host'), {
86 execCommand: function(action, value) {
87 return this.exec.command(action, value);
89 _execCommand: function(action, value) {
90 return this.exec._command(action, value);
94 this.get('host').on('dom:keypress', Y.bind(function(e) {
95 this._lastKey = e.keyCode;
98 _wrapContent: function(str, override) {
99 var useP = (this.getInstance().host.editorPara && !override ? true : false);
102 str = '<p>' + str + '</p>';
127 * Static object literal of execCommand overrides
133 * Wraps the content with a new element of type (tag)
134 * @method COMMANDS.wrap
136 * @param {String} cmd The command executed: wrap
137 * @param {String} tag The tag to wrap the selection with
138 * @return {NodeList} NodeList of the items touched by this command.
140 wrap: function(cmd, tag) {
141 var inst = this.getInstance();
142 return (new inst.EditorSelection()).wrapContent(tag);
145 * Inserts the provided HTML at the cursor, should be a single element.
146 * @method COMMANDS.inserthtml
148 * @param {String} cmd The command executed: inserthtml
149 * @param {String} html The html to insert
150 * @return {Node} Node instance of the item touched by this command.
152 inserthtml: function(cmd, html) {
153 var inst = this.getInstance();
154 if (inst.EditorSelection.hasCursor() || Y.UA.ie) {
155 return (new inst.EditorSelection()).insertContent(html);
157 this._command('inserthtml', html);
161 * Inserts the provided HTML at the cursor, and focuses the cursor afterwards.
162 * @method COMMANDS.insertandfocus
164 * @param {String} cmd The command executed: insertandfocus
165 * @param {String} html The html to insert
166 * @return {Node} Node instance of the item touched by this command.
168 insertandfocus: function(cmd, html) {
169 var inst = this.getInstance(), out, sel;
170 if (inst.EditorSelection.hasCursor()) {
171 html += inst.EditorSelection.CURSOR;
172 out = this.command('inserthtml', html);
173 sel = new inst.EditorSelection();
174 sel.focusCursor(true, true);
176 this.command('inserthtml', html);
181 * Inserts a BR at the current cursor position
182 * @method COMMANDS.insertbr
184 * @param {String} cmd The command executed: insertbr
186 insertbr: function(cmd) {
187 var inst = this.getInstance(),
188 sel = new inst.EditorSelection(),
189 html = '<var>|</var>', last = null,
190 q = (Y.UA.webkit) ? 'span.Apple-style-span,var' : 'var';
192 if (sel._selection.pasteHTML) {
193 sel._selection.pasteHTML(html);
195 this._command('inserthtml', html);
198 var insert = function(n) {
199 var c = inst.Node.create('<br>');
200 n.insert(c, 'before');
204 inst.all(q).each(function(n) {
208 if (n.get('innerHTML') === '|') {
214 if ((!last.previous() || !last.previous().test('br')) && Y.UA.gecko) {
215 var s = last.cloneNode();
216 last.insert(s, 'after');
222 if (Y.UA.webkit && last) {
224 sel.selectNode(last);
228 * Inserts an image at the cursor position
229 * @method COMMANDS.insertimage
231 * @param {String} cmd The command executed: insertimage
232 * @param {String} img The url of the image to be inserted
233 * @return {Node} Node instance of the item touched by this command.
235 insertimage: function(cmd, img) {
236 return this.command('inserthtml', '<img src="' + img + '">');
239 * Add a class to all of the elements in the selection
240 * @method COMMANDS.addclass
242 * @param {String} cmd The command executed: addclass
243 * @param {String} cls The className to add
244 * @return {NodeList} NodeList of the items touched by this command.
246 addclass: function(cmd, cls) {
247 var inst = this.getInstance();
248 return (new inst.EditorSelection()).getSelected().addClass(cls);
251 * Remove a class from all of the elements in the selection
252 * @method COMMANDS.removeclass
254 * @param {String} cmd The command executed: removeclass
255 * @param {String} cls The className to remove
256 * @return {NodeList} NodeList of the items touched by this command.
258 removeclass: function(cmd, cls) {
259 var inst = this.getInstance();
260 return (new inst.EditorSelection()).getSelected().removeClass(cls);
263 * Adds a forecolor to the current selection, or creates a new element and applies it
264 * @method COMMANDS.forecolor
266 * @param {String} cmd The command executed: forecolor
267 * @param {String} val The color value to apply
268 * @return {NodeList} NodeList of the items touched by this command.
270 forecolor: function(cmd, val) {
271 var inst = this.getInstance(),
272 sel = new inst.EditorSelection(), n;
275 this._command('useCSS', false);
277 if (inst.EditorSelection.hasCursor()) {
278 if (sel.isCollapsed) {
279 if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
280 sel.anchorNode.setStyle('color', val);
283 n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
284 sel.focusCursor(true, true);
288 return this._command(cmd, val);
291 this._command(cmd, val);
295 * Adds a background color to the current selection, or creates a new element and applies it
296 * @method COMMANDS.backcolor
298 * @param {String} cmd The command executed: backcolor
299 * @param {String} val The color value to apply
300 * @return {NodeList} NodeList of the items touched by this command.
302 backcolor: function(cmd, val) {
303 var inst = this.getInstance(),
304 sel = new inst.EditorSelection(), n;
306 if (Y.UA.gecko || Y.UA.opera) {
310 this._command('useCSS', false);
312 if (inst.EditorSelection.hasCursor()) {
313 if (sel.isCollapsed) {
314 if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
315 sel.anchorNode.setStyle('backgroundColor', val);
318 n = this.command('inserthtml', '<span style="background-color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
319 sel.focusCursor(true, true);
323 return this._command(cmd, val);
326 this._command(cmd, val);
330 * Sugar method, calles backcolor
331 * @method COMMANDS.hilitecolor
333 * @param {String} cmd The command executed: backcolor
334 * @param {String} val The color value to apply
335 * @return {NodeList} NodeList of the items touched by this command.
337 hilitecolor: function() {
338 return ExecCommand.COMMANDS.backcolor.apply(this, arguments);
341 * Adds a font name to the current selection, or creates a new element and applies it
342 * @method COMMANDS.fontname2
345 * @param {String} cmd The command executed: fontname
346 * @param {String} val The font name to apply
347 * @return {NodeList} NodeList of the items touched by this command.
349 fontname2: function(cmd, val) {
350 this._command('fontname', val);
351 var inst = this.getInstance(),
352 sel = new inst.EditorSelection();
354 if (sel.isCollapsed && (this._lastKey != 32)) {
355 if (sel.anchorNode.test('font')) {
356 sel.anchorNode.set('face', val);
361 * Adds a fontsize to the current selection, or creates a new element and applies it
362 * @method COMMANDS.fontsize2
365 * @param {String} cmd The command executed: fontsize
366 * @param {String} val The font size to apply
367 * @return {NodeList} NodeList of the items touched by this command.
369 fontsize2: function(cmd, val) {
370 this._command('fontsize', val);
372 var inst = this.getInstance(),
373 sel = new inst.EditorSelection();
375 if (sel.isCollapsed && sel.anchorNode && (this._lastKey != 32)) {
377 if (sel.anchorNode.getStyle('lineHeight')) {
378 sel.anchorNode.setStyle('lineHeight', '');
381 if (sel.anchorNode.test('font')) {
382 sel.anchorNode.set('size', val);
383 } else if (Y.UA.gecko) {
384 var p = sel.anchorNode.ancestor(inst.EditorSelection.DEFAULT_BLOCK_TAG);
386 p.setStyle('fontSize', '');
392 * Overload for COMMANDS.list
393 * @method COMMANDS.insertorderedlist
395 * @param {String} cmd The command executed: list, ul
397 insertunorderedlist: function(cmd) {
398 this.command('list', 'ul');
401 * Overload for COMMANDS.list
402 * @method COMMANDS.insertunorderedlist
404 * @param {String} cmd The command executed: list, ol
406 insertorderedlist: function(cmd) {
407 this.command('list', 'ol');
410 * Noramlizes lists creation/destruction for IE. All others pass through to native calls
411 * @method COMMANDS.list
413 * @param {String} cmd The command executed: list (not used)
414 * @param {String} tag The tag to deal with
416 list: function(cmd, tag) {
417 var inst = this.getInstance(), html, self = this,
419 The yui3- class name below is not a skinnable class,
420 it's a utility class used internally by editor and
421 stripped when completed, calling getClassName on this
422 is a waste of resources.
424 DIR = 'dir', cls = 'yui3-touched',
425 dir, range, div, elm, n, str, s, par, list, lis,
426 useP = (inst.host.editorPara ? true : false),
427 sel = new inst.EditorSelection();
429 cmd = 'insert' + ((tag === 'ul') ? 'un' : '') + 'orderedlist';
431 if (Y.UA.ie && !sel.isCollapsed) {
432 range = sel._selection;
433 html = range.htmlText;
434 div = inst.Node.create(html) || inst.one('body');
436 if (div.test('li') || div.one('li')) {
437 this._command(cmd, null);
441 elm = range.item ? range.item(0) : range.parentElement();
446 lis.each(function(l) {
447 str = self._wrapContent(l.get('innerHTML'));
450 s = inst.Node.create(str);
451 if (n.get('parentNode').test('div')) {
452 n = n.get('parentNode');
454 if (n && n.hasAttribute(DIR)) {
456 s.all('p').setAttribute(DIR, n.getAttribute(DIR));
458 s.setAttribute(DIR, n.getAttribute(DIR));
462 n.replace(s.get('innerHTML'));
466 if (range.moveToElementText) {
467 range.moveToElementText(s._node);
471 par = Y.one(range.parentElement());
472 if (!par.test(inst.EditorSelection.BLOCKS)) {
473 par = par.ancestor(inst.EditorSelection.BLOCKS);
476 if (par.hasAttribute(DIR)) {
477 dir = par.getAttribute(DIR);
480 if (html.indexOf('<br>') > -1) {
481 html = html.split(/<br>/i);
483 var tmp = inst.Node.create(html),
484 ps = tmp ? tmp.all('p') : null;
486 if (ps && ps.size()) {
488 ps.each(function(n) {
489 html.push(n.get('innerHTML'));
495 list = '<' + tag + ' id="ie-list">';
496 Y.each(html, function(v) {
497 var a = inst.Node.create(v);
498 if (a && a.test('p')) {
499 if (a.hasAttribute(DIR)) {
500 dir = a.getAttribute(DIR);
502 v = a.get('innerHTML');
504 list += '<li>' + v + '</li>';
506 list += '</' + tag + '>';
507 range.pasteHTML(list);
508 elm = inst.config.doc.getElementById('ie-list');
511 elm.setAttribute(DIR, dir);
513 if (range.moveToElementText) {
514 range.moveToElementText(elm);
518 } else if (Y.UA.ie) {
519 par = inst.one(sel._selection.parentElement());
521 if (par && par.hasAttribute(DIR)) {
522 dir = par.getAttribute(DIR);
524 html = Y.EditorSelection.getText(par);
528 sdir = ' dir="' + dir + '"';
530 list = inst.Node.create(Y.Lang.sub('<{tag}{dir}><li></li></{tag}>', { tag: tag, dir: sdir }));
532 sel.selectNode(list.one('li'));
534 this._command(cmd, null);
537 this._command(cmd, null);
540 inst.all(tag).addClass(cls);
541 if (sel.anchorNode.test(inst.EditorSelection.BLOCKS)) {
542 par = sel.anchorNode;
544 par = sel.anchorNode.ancestor(inst.EditorSelection.BLOCKS);
546 if (!par) { //No parent, find the first block under the anchorNode
547 par = sel.anchorNode.one(inst.EditorSelection.BLOCKS);
550 if (par && par.hasAttribute(DIR)) {
551 dir = par.getAttribute(DIR);
553 if (par && par.test(tag)) {
554 var hasPParent = par.ancestor('p');
555 html = inst.Node.create('<div/>');
557 elm.each(function(h) {
558 html.append(self._wrapContent(h.get('innerHTML'), hasPParent));
562 html.all('p').setAttribute(DIR, dir);
564 html.setAttribute(DIR, dir);
568 html = inst.Node.create(html.get('innerHTML'));
570 var fc = html.get('firstChild');
574 this._command(cmd, null);
576 list = inst.all(tag);
580 list.each(function(n) {
581 if (!n.hasClass(cls)) {
582 n.setAttribute(DIR, dir);
588 list.removeClass(cls);
592 * Noramlizes alignment for Webkit Browsers
593 * @method COMMANDS.justify
595 * @param {String} cmd The command executed: justify (not used)
596 * @param {String} val The actual command from the justify{center,all,left,right} stubs
598 justify: function(cmd, val) {
600 var inst = this.getInstance(),
601 sel = new inst.EditorSelection(),
602 aNode = sel.anchorNode;
604 var bgColor = aNode.getStyle('backgroundColor');
606 sel = new inst.EditorSelection();
607 if (sel.anchorNode.test('div')) {
608 var html = '<span>' + sel.anchorNode.get('innerHTML') + '</span>';
609 sel.anchorNode.set('innerHTML', html);
610 sel.anchorNode.one('span').setStyle('backgroundColor', bgColor);
611 sel.selectNode(sel.anchorNode.one('span'));
618 * Override method for COMMANDS.justify
619 * @method COMMANDS.justifycenter
622 justifycenter: function(cmd) {
623 this.command('justify', 'justifycenter');
626 * Override method for COMMANDS.justify
627 * @method COMMANDS.justifyleft
630 justifyleft: function(cmd) {
631 this.command('justify', 'justifyleft');
634 * Override method for COMMANDS.justify
635 * @method COMMANDS.justifyright
638 justifyright: function(cmd) {
639 this.command('justify', 'justifyright');
642 * Override method for COMMANDS.justify
643 * @method COMMANDS.justifyfull
646 justifyfull: function(cmd) {
647 this.command('justify', 'justifyfull');
653 * This method is meant to normalize IE's in ability to exec the proper command on elements with CSS styling.
656 * @param {String} cmd The command to execute
657 * @param {String} tag The tag to create
658 * @param {String} rule The rule that we are looking for.
660 var fixIETags = function(cmd, tag, rule) {
661 var inst = this.getInstance(),
662 doc = inst.config.doc,
663 sel = doc.selection.createRange(),
664 o = doc.queryCommandValue(cmd),
665 html, reg, m, p, d, s, c;
669 reg = new RegExp(rule, 'g');
673 html = html.replace(rule + ';', '').replace(rule, '');
675 sel.pasteHTML('<var id="yui-ie-bs">');
677 p = doc.getElementById('yui-ie-bs');
678 d = doc.createElement('div');
679 s = doc.createElement(tag);
682 if (p.parentNode !== inst.config.doc.body) {
688 p.parentNode.replaceChild(s, p);
690 Y.each(c, function(f) {
694 if (sel.moveToElementText) {
695 sel.moveToElementText(s);
704 ExecCommand.COMMANDS.bold = function() {
705 fixIETags.call(this, 'bold', 'b', 'FONT-WEIGHT: bold');
707 ExecCommand.COMMANDS.italic = function() {
708 fixIETags.call(this, 'italic', 'i', 'FONT-STYLE: italic');
710 ExecCommand.COMMANDS.underline = function() {
711 fixIETags.call(this, 'underline', 'u', 'TEXT-DECORATION: underline');
715 Y.namespace('Plugin');
716 Y.Plugin.ExecCommand = ExecCommand;
720 }, '3.7.2', {"requires": ["frame"]});