Integrate Ace Editor in CKEditor Source View

Integrate Ace Editor in CKEditor Source View

Have you ever wanted to have code highlighting and other advanced features in the CKEditor source view? Well, now you can! I've written up a script that will allow you to integrate any web based code editor into CKEditor's source view. It's extremely easy to do using my code. Simply set up a web page running your preferred source code editor in fullscreen mode and then use my script to inject it into the CKEditor. No more screwing around, just awesomeness in your editor!

You can copy and paste the code from below or, download it from https://gist.github.com/matthewkastor/5275437. See it in action at http://atropa-editors.net63.net/ckeditor.html

/*jslint
    indent: 4,
    maxerr: 50,
    white: true,
    browser: true,
    vars: true
*/
/*global
    CKEDITOR    
*/
/**
 * CKEditor SourceViewReplacer Class.
 * @author <a href="mailto:matthewkastor@gmail.com">
 *  Matthew Christopher Kastor-Inare III </a><br />
 *  ☭ Hial Atropa!! ☭
 * @version 20130330
 * @class CKEditor SourceViewReplacer Class.
 * @param {CKEditor.editor} editor The ck editor instance.
 * @param {object} highlighter An options object for setting up the code highlighter. It 
 *  must have a parameter `url` pointing to a page containing a code highlighter
 *  which will be loaded into an iframe. This iframe will replace the plain
 *  text area in the source view. The highlighter must also have a method
 *  named `hook` which will take the window object of the iframe with your code
 *  highlighter loaded into it and return an object having both a `getValue` and
 *  `setValue` method which will get and set the contents of your source code
 *  highlighter.
 * @example
 * var highlighter = {
 *     'url' : 'http://atropa-editors.net63.net/ACEeditor.html.html',
 *     'hook' : function (frameWindow) {
 *         var out = {};
 *         out.getValue = function () {
 *             return frameWindow.editor.session.getValue();
 *         };
 *         out.setValue = function (val) {
 *             return frameWindow.editor.session.setValue(val);
 *         };
 *         return out;
 *     }
 * };
 * 
 * // given a textarea with the id "newFile"
 * 
 * CKEDITOR.replace('newFile', {
 *     on: {
 *         'instanceReady': function instanceReadyFn (evt) {
 *             "use strict";
 *             var sourceViewer = new SourceViewReplacer(evt.editor, highlighter);
 *         }
 *     }
 * });
 */
function SourceViewReplacer (editor, highlighter) {
    "use strict";
    var my = this;
    my.highlighter = highlighter || {
        'url' : 'http://atropa-editors.net63.net/ACEeditor.html.html',
        'hook' : function (frameWindow) {
            var out = {};
            out.getValue = function () {
                return frameWindow.editor.session.getValue();
            };
            out.setValue = function (val) {
                return frameWindow.editor.session.setValue(val);
            };
            return out;
        }
    };
    
    function highlighterSetup(textarea) {
        function l () {
            textarea.value = my.codeHighlighter.getValue();
        }
        my.editor.on('mode', function () {
            my.editor.removeListener('beforeCommandExec', l);
        });
        my.editor.on('beforeCommandExec', l);
        my.codeHighlighter.setValue(textarea.value);
    }
        
    function sourceViewOverride() {
        var hook;
        var textarea = my.getSourceEditorTextarea();
        textarea.style.cssText = textarea.style.cssText + ';display:none;';
        
        my.codeHighlighter.frame = document.createElement('iframe');
        my.codeHighlighter.frame.setAttribute('src', my.highlighter.url);
        my.codeHighlighter.frame.setAttribute('style', 
            'padding: 0; margin: 0; border: none; ' +
            'height: 100%; width: 100%; overflow: auto;'
        );
        
        textarea.parentNode.appendChild(my.codeHighlighter.frame);
        
        function waitForHighlighter() {
            try {
                hook = my.highlighter.hook(my.codeHighlighter.frame.contentWindow);
                my.codeHighlighter.getValue = hook.getValue;
                my.codeHighlighter.setValue = hook.setValue;
                highlighterSetup(textarea);
            } catch (e) {
                setTimeout(waitForHighlighter, 250);
            }
        }
        waitForHighlighter();
    }
    
    function modeSwitch () {
        if(my.editor.mode === 'source') {
            sourceViewOverride();
        }
    }
    
    this.getSourceEditorTextarea = function () {
        return document.getElementsByClassName('cke_source')[0];
    };
    
    this.setEditorValue = function (value) {
        my.editor.setData(value);
        modeSwitch();
    };
    
    this.codeHighlighter = {
        'highlighter' : {},
        'getValue' : function () {
            throw new Error('implement this on codeHighlighter ' +
                'instantiation');
        },
        'setValue' : function () {
            throw new Error('implement this on codeHighlighter ' +
                'instantiation');
        }
    };
    
    my.editor = editor;
    my.editor.on('mode', modeSwitch);
    modeSwitch();
}