/* * Code editor form field control * * Data attributes: * - data-control="codeeditor" - enables the code editor plugin * - data-vendor-path="/" - sets the path to find Ace editor files * - data-language="php" - set the coding language used * - data-theme="textmate" - the colour scheme and theme * * JavaScript API: * $('textarea').codeEditor({ vendorPath: '/', language: 'php '}) * * Dependancies: * - Ace Editor (ace.js) */ +function ($) { "use strict"; var Base = $.wn.foundation.base, BaseProto = Base.prototype // CODEEDITOR CLASS DEFINITION // ============================ var CodeEditor = function(element, options) { Base.call(this) this.options = options this.$el = $(element) this.$textarea = this.$el.find('>textarea:first') this.$toolbar = this.$el.find('>.editor-toolbar:first') this.$code = null this.editor = null this.$form = null // Toolbar links this.isFullscreen = false this.$fullscreenEnable = this.$toolbar.find('li.fullscreen-enable') this.$fullscreenDisable = this.$toolbar.find('li.fullscreen-disable') this.isSearchbox = false this.$searchboxEnable = this.$toolbar.find('li.searchbox-enable') this.$searchboxDisable = this.$toolbar.find('li.searchbox-disable') this.isReplacebox = false this.$replaceboxEnable = this.$toolbar.find('li.replacebox-enable') this.$replaceboxDisable = this.$toolbar.find('li.replacebox-disable') $.wn.foundation.controlUtils.markDisposable(element) this.init(); this.$el.trigger('oc.codeEditorReady') } CodeEditor.prototype = Object.create(BaseProto) CodeEditor.prototype.constructor = CodeEditor CodeEditor.DEFAULTS = { fontSize: 12, wordWrap: 'off', codeFolding: 'manual', autocompletion: 'manual', tabSize: 4, theme: 'textmate', showInvisibles: true, highlightActiveLine: true, useSoftTabs: true, autoCloseTags: true, showGutter: true, enableEmmet: true, language: 'php', margin: 0, vendorPath: '/', showPrintMargin: false, highlightSelectedWord: false, hScrollBarAlwaysVisible: false, scrollPastEnd: 0, readOnly: false } CodeEditor.prototype.init = function (){ var self = this; /* * Control must have an identifier */ if (!this.$el.attr('id')) { this.$el.attr('id', 'element-' + Math.random().toString(36).substring(7)) } /* * Create code container */ this.$code = $('
') .addClass('editor-code') .attr('id', this.$el.attr('id') + '-code') .css({ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }) .appendTo(this.$el) /* * Initialize ACE editor */ var editor = this.editor = ace.edit(this.$code.attr('id')), options = this.options, $form = this.$el.closest('form'); // Fixes a weird notice about scrolling editor.$blockScrolling = Infinity this.$form = $form this.$textarea.hide(); editor.getSession().setValue(this.$textarea.val()) editor.on('change', this.proxy(this.onChange)) $form.on('oc.beforeRequest', this.proxy(this.onBeforeRequest)) $(window).on('resize', this.proxy(this.onResize)) $(window).on('oc.updateUi', this.proxy(this.onResize)) this.$el.one('dispose-control', this.proxy(this.dispose)) /* * Set theme, anticipated languages should be preloaded */ assetManager.load({ js:[ // options.vendorPath + '/mode-' + options.language + '.js', options.vendorPath + '/theme-' + options.theme + '.js' ] }, function(){ editor.setTheme('ace/theme/' + options.theme) var inline = options.language === 'php' editor.getSession().setMode({ path: 'ace/mode/'+options.language, inline: inline }) }) /* * Config editor */ editor.wrapper = this editor.setShowInvisibles(options.showInvisibles) editor.setBehavioursEnabled(options.autoCloseTags) editor.setHighlightActiveLine(options.highlightActiveLine) editor.renderer.setShowGutter(options.showGutter) editor.renderer.setShowPrintMargin(options.showPrintMargin) editor.setHighlightSelectedWord(options.highlightSelectedWord) editor.renderer.setHScrollBarAlwaysVisible(options.hScrollBarAlwaysVisible) editor.setDisplayIndentGuides(options.displayIndentGuides) editor.getSession().setUseSoftTabs(options.useSoftTabs) editor.getSession().setTabSize(options.tabSize) editor.setReadOnly(options.readOnly) editor.getSession().setFoldStyle(options.codeFolding) editor.setFontSize(options.fontSize) editor.on('blur', this.proxy(this.onBlur)) editor.on('focus', this.proxy(this.onFocus)) editor.setOption("scrollPastEnd", options.scrollPastEnd) this.setWordWrap(options.wordWrap) // Set the vendor path for Ace's require path ace.require('ace/config').set('basePath', this.options.vendorPath) editor.setOptions({ enableEmmet: options.enableEmmet, enableBasicAutocompletion: options.autocompletion === 'basic', enableSnippets: options.enableSnippets, enableLiveAutocompletion: options.autocompletion === 'live' }) editor.renderer.setScrollMargin(options.margin, options.margin, 0, 0) editor.renderer.setPadding(options.margin) /* * Toolbar */ this.$toolbar.find('>ul>li>a') .each(function(){ var abbr = $(this).find('>abbr'), label = abbr.text(), help = abbr.attr('title'), title = label + ' (' + help + ')'; $(this).attr('title', title) }) .tooltip({ delay: 500, placement: 'top', html: true }) ; this.$fullscreenDisable.hide() this.$fullscreenEnable.on('click.codeeditor', '>a', $.proxy(this.toggleFullscreen, this)) this.$fullscreenDisable.on('click.codeeditor', '>a', $.proxy(this.toggleFullscreen, this)) this.$searchboxDisable.hide() this.$searchboxEnable.on('click.codeeditor', '>a', $.proxy(this.toggleSearchbox, this)) this.$searchboxDisable.on('click.codeeditor', '>a', $.proxy(this.toggleSearchbox, this)) this.$replaceboxDisable.hide() this.$replaceboxEnable.on('click.codeeditor', '>a', $.proxy(this.toggleReplacebox, this)) this.$replaceboxDisable.on('click.codeeditor', '>a', $.proxy(this.toggleReplacebox, this)) /* * Hotkeys */ this.$el.hotKey({ hotkey: 'esc', callback: this.proxy(this.onEscape) }) editor.commands.addCommand({ name: 'toggleFullscreen', bindKey: { win: 'Ctrl+Shift+F', mac: 'Ctrl+Shift+F' }, exec: $.proxy(this.toggleFullscreen, this), readOnly: true }) } CodeEditor.prototype.dispose = function() { if (this.$el === null) return this.unregisterHandlers() this.disposeAttachedControls() this.$el = null this.$textarea = null this.$toolbar = null this.$code = null this.$fullscreenEnable = null this.$fullscreenDisable = null this.$searchboxEnable = null this.$searchboxDisable = null this.$replaceboxEnable = null this.$replaceboxDisable = null this.$form = null this.options = null BaseProto.dispose.call(this) } CodeEditor.prototype.disposeAttachedControls = function() { this.editor.destroy() var keys = Object.keys(this.editor.renderer) for (var i=0, len=keys.length; i