2
votes

Is it possible to use another feature of CKEditor to add just a css class for the wysiwyg mode while editing (and not for the resulting content)? (Like the spellchecker/scayt is adding a span with wavy underline styles only in the wysiwyg mode)

My wanted scenario

I've created a plugin for CKEditor 4.7 which search for specific tags with specific content (e.g. an empty paragraph which can lead to not intended "spaces" on the final website) and adds a css class to tags. The class adds a red border to inform the editor about the "empty" tag. I actually use editor.document.$.getElementsByTagName(tagName); and plain javascript to add the css class rte-empty.

My problem

My approach adds the css class also to the final content of the <textarea />.

Here's my code while posting the question:

/**
 * Check for empty tags plugin
 */

'use strict';

(function () {
	CKEDITOR.plugins.add('emptytags', {
		lang: "de,en",
		onLoad: function(editor) {
			CKEDITOR.addCss(
				'.cke_editable .rte-empty {' +
					' border: 1px dotted red;' +
				'}'
			);
		},
		init: function (editor) {

			// Default Config
			var defaultConfig = {
				tagsToCheck: {0: 'p'}
			};
			var config = CKEDITOR.tools.extend(defaultConfig, editor.config.emptytags || {}, true);

			editor.addCommand('checkForEmptyTags', {
				exec: function (editor) {
					var editorContent = editor.getData();
					// Stop check and inform editor if the editor has no content.
					if (editorContent === '') {
						alert(editor.lang.emptytags.AlertEditorContentEmpty)
						return;
					}
					// Check if tag name's to check are set
					if (config.tagsToCheck.length > 0 && config.tagsToCheck[0] !== null) {
						var index;
						for (index = 0; index < config.tagsToCheck.length; ++index) {
							var tagName = config.tagsToCheck[index];
							var tags = editor.document.$.getElementsByTagName(tagName);
							for (var i=0; i < tags.length; i++) {
								if (checkForRealEmptyTag(tags[i].innerHTML)
									|| checkForEmptyTagWithSpace(tags[i].innerHTML)
									|| checkForEmptyTagWithNbsp(tags[i].innerHTML)
								) {
									if(tags[i].className.indexOf("rte-empty") < 0){
										tags[i].className += "rte-empty";
									}
									var noEmptyTagFound = false;
								} else {
									tags[i].classList.remove("rte-empty");
								}
							}
						}
						// Inform editor that no empty tag can be found (anymore)
						if (noEmptyTagFound === true) {
							alert(editor.lang.emptytags.AlertEditorNoEmptyTagFound);
						}
					}
				}
			});
			editor.ui.addButton && editor.ui.addButton('Check for empty tags', {
				label: editor.lang.emptytags.ToolbarButton,
				command: 'checkForEmptyTags',
				toolbar: 'insertcharacters'
			});
		}
	});

	function checkForRealEmptyTag(content) {
		return content.length === 0;

	}

	function checkForEmptyTagWithNbsp(content) {
		return content === '&nbsp;' || content.trim() === '<br>';
	}

	function checkForEmptyTagWithSpace(content) {
		return content.trim().length === 0;
	}

})();

Therefore

I'm searching for a possibility like the SCAYT plugin does: Adding span tags with a class for adding a wavy underline to words which could not be found in dictionaries.

1

1 Answers

2
votes

I would personally listen to the toDataFormat and toHtml events to add then remove the CSS class to the elements you want. This way, the user will not see the class when retrieving the data back from CKEditor or when switching to source mode.

Here is the updated code (you still need to customise it according to your needs):

CKEDITOR.plugins.add('emptytags', {
    lang: "de,en",
    onLoad: function(editor) {
        CKEDITOR.addCss(
        '.cke_editable .rte-empty {' +
            ' border: 1px dotted red;' +
        '}'
        );
    },
    init: function (editor) {

        editor.on('toHtml', function (evt) {

            markEmptyChildren(evt.data.dataValue);

        }, null, null, 14);

        editor.on('toDataFormat', function (evt) {

            unmarkEmptyChildren(evt.data.dataValue);

        },
        null, null, 14);

        function markEmptyChildren(element) {
            var children = element.children;
            if (children) {
                for (var i = children.length; i--; ) {
                var child = children[i];
                if (child.name == "p") {
                    if (isEmpty(child)) {
                    child.addClass("rte-empty")
                    } else {
                    child.removeClass("rte-empty")
                    }
                }
                markEmptyChildren(child);
                }
            }
        }

        function unmarkEmptyChildren(element) {
            var children = element.children;
            if (children) {
                for (var i = children.length; i--; ) {
                var child = children[i];
                if (child.name == "p") {
                    child.removeClass("rte-empty")
                }
                unmarkEmptyChildren(child);
                }
            }
        }

        function isEmpty(node) {

            if (node instanceof CKEDITOR.htmlParser.element) {
                if (node.name == "br") {
                    return true;
                } else {
                    var children = node.children;
                    for (var i = children.length; i--; ) {
                        var child = children[i];
                        if (!isEmpty(children[i])) {
                            return false;
                        }
                    }
                    return true;
                }
            } else if (node instanceof CKEDITOR.htmlParser.text) {
                return node.value.trim().length === 0;
            } else {
                return true;
            }

        }
    }
});

See JSFiddle here.