/** * Creates an inline wysiwyg editor. * * The `ep.uiEditor` method creates an inline wysiwyg editor. * * ### Examples * Creates a wysiwyg editor depending on a textarea with id and name attributes. Textareas with the same value in the name attribute become a shared toolbar. * * JavaScript: * * ep('#editorID').uiEditor({ * 'name': 'editorName', * 'toolbar': 'Mini', * 'disable': true, * 'hideToolbarOnStartup': false, * 'noToolbar': true, * 'useAbsolute': true, * 'blogPostMode': true, * 'pluginOptions': {} * }); * * HTML: * * <div style="clear:both; position:relative;"> * <textarea style="visiblity:hidden;" name="editorName" id="editorID">Some Content</textarea> * </div> * * * @class jQuery.ui.uiEditor * @extends jQuery.widget * * @uses ep * @uses jQuery.ckeditor.adapters.jquery * @uses jQuery.event.special.dom * @since 6.15.0 */ /** * Reloads the toolbar depending on the language of the current editor. * * @param {String} editorName Name of the editor handed over with the name option when editor widget gets initialized. * * @method reloadToolbar * @member jQuery.ui.uiEditor * * @since 6.15.0 */ /** * @cfg {String} name Name of the editor (corresponds to the name attribute of the textarea). */ /** * @cfg {String} toolbar Indicates which toolbar should be used. If option is not handed over a default value from the config.js is used (depending on screen width, content etc.). Possible values: 'Full', 'FullPageBreak', 'Mini', 'MiniPageBreak' */ /** * @cfg {Boolean} disable Boolean Flag which indicates if the editor is disabled (true) on startup. */ /** * @cfg {Boolean} hideToolbarOnStartup Boolean Flag which indicates if the toolbar should be hidden (true) on startup. */ /** * @cfg {Boolean} noToolbar Boolean Flag which indicates if editor has a toolbar. */ /** * @cfg {Boolean} useAbsolute Boolean Flag which indicates if an absolute url should be used in the linkpicker plugin. */ /** * @cfg {Boolean} blogPostMode Boolean Flag which indicates if the toolbar should contain button to insert a pagebreak. */ /** * @cfg {Object} pluginOptions Additional options for plugins */ /** * See `jQuery.ui.uiEditor` for details. * * @param {Object} options A map of additional options pass to the method. * @param {String} name Name of the editor (corresponds to the name attribute of the textarea). * @param {String} toolbar Indicates which toolbar should be used. If option is not handed over a default value from the config.js is used (depending on screen width, content etc.). Possible values: 'Full', 'FullPageBreak', 'Mini', 'MiniPageBreak' * @param {Boolean} disable Boolean Flag which indicates if the editor is disabled (true) on startup. * @param {Boolean} hideToolbarOnStartup Boolean Flag which indicates if the toolbar should be hidden (true) on startup. * @param {Boolean} noToolbar Boolean Flag which indicates if editor has a toolbar. * @param {Boolean} useAbsolute Boolean Flag which indicates if an absolute url should be used in the linkpicker plugin. * @param {Boolean} blogPostMode Boolean Flag which indicates if the toolbar should contain button to insert a pagebreak. * @param {Object} pluginOptions Additional options for plugins * * @method uiEditor * @member jQuery * * @since 6.15.0 */ /* * @copyright © Copyright 2006-2010, epages GmbH, All Rights Reserved. * * @module ep.ui.editor * * @revision $Revision: 1.21 $ */ /*global define, window*/ /*jslint vars:true, plusplus:true, nomen:true*/ define("ep/ui/editor", [ "jquery", "ep", "ckeditor/ckeditor", "ep/ui/core", "ep/fn/busy", "jquery/ui/widget", "jquery/ckeditor", "jquery/event/special/dom" ], function ($, ep, CKEDITOR) { 'use strict'; var editorStack = {}; /* // example for an entry in editorStack { 'Text' : { 'editors' : [node1, ode2], //jQuery objects of editor nodes with this particular name 'toolbar' : node, 'leftEditor' : $Object, 'rightEditor' : $Object, 'disabledLayer': $Object } } */ // recalculate toolbar width if window is resized $(window).on('resize.uiEditor', function () { var key, i; for (key in editorStack) { if (editorStack.hasOwnProperty(key)) { var editors = editorStack[key].editors; // iterate over "language" specific editors for (i = editors.length - 1; i >= 0; i--) { ep(editors[i]).uiEditor('adjustToolbar', key); } } } }); $.widget('ui.uiEditor', { // editorInstance : undefined, baseFloatZIndex: 450, options: { // name: undefined, // toolbar: undefined, hideToolbarOnStartup: false, sourceMode: false, noToolbar : false, useAbsolute: false, disable: false, blogPostMode: false, showPreview: false, enterMode: 1 // is the same as CKEDITOR.ENTER_P }, _create: function () { var self = this, o = self.options, elem = self.element, editorName = o.name = o.name || 'editor_' + new Date().getTime(); // push editor element in stack if (editorStack[editorName]) { editorStack[editorName].editors.push(elem); } else { editorStack[editorName] = { 'editors' : [elem] }; var toolbarContainer = ep('<div>') .insertBefore(elem) .attr('id', 'ep-toolbarContainer' + editorName) .hide(); editorStack[editorName].toolbar = toolbarContainer; } // if editor element is visible -> create editor instance immediately if (elem.is(':visible')) { self._createEditor(); elem.parent().on('show', function () { self.adjustToolbar(editorName); }); } else { // else: create instance when editor element becomes visible elem.one('show', function () { self._createEditor(); elem.parent().on('show', function () { self.adjustToolbar(editorName); }); }); } }, // method to create editor instance _createEditor: function () { var self = this, o = self.options, elem = self.element, editorName = o.name, stack = editorStack[editorName], // additional configuration options for the editor which will be built below (2nd argument for the .ckeditor method) editorOptions = { enterMode: o.enterMode, sharedSpaces: { top : 'ep-toolbarContainer' + editorName } }; // Pass options for plugins to the editor instance. if (o.pluginOptions) { $.extend(editorOptions, {pluginOptions: o.pluginOptions}); } if (o.toolbar) { $.extend(editorOptions, {toolbar: o.toolbar}); } // indicates whether to use a absolute/relative url in linkpicker widget/plugin if (o.useAbsolute) { $.extend(editorOptions, {useAbsolute: o.useAbsolute}); } if (o.showPreview) { $.extend(editorOptions, {coreStyles_italic: { element : 'i', overrides : 'em' }}); } var toolbarMode = 'Full'; if (o.blogPostMode) { toolbarMode += 'PageBreak'; $.extend(editorOptions, {blogPostMode: true}); } if ($('#ep-toolbarContainer' + editorName).closest('tr').width() <= 1080) { toolbarMode = toolbarMode.replace('Full', 'Mini'); } // After the first resize the help sidebar could be expanded and resize the editor width $(window).one('resize.uiEditor', function () { if ($('#ep-toolbarContainer' + editorName).closest('tr').width() <= 1080) { toolbarMode = toolbarMode.replace('Full', 'Mini'); $.extend(editorOptions, {toolbar: toolbarMode}); } }); $.extend(editorOptions, { toolbar: toolbarMode, startupMode: (o.sourceMode) ? 'source' : 'wysiwyg' }); elem.ckeditor(function (textarea) { // hide busy layer ep(elem.closest('div')).busy('hide'); self.editorInstance = elem.ckeditorGet(); self.baseFloatZIndex = self.editorInstance.config.baseFloatZIndex; var snapshotOnceEvent = function () { elem.trigger('change'); self.editorInstance.removeListener('saveSnapshot', snapshotOnceEvent); }; self.editorInstance.on('saveSnapshot', snapshotOnceEvent); //callback function after editor is ready var firstEditorNode = $(stack.editors[0].ckeditorGet().container.$), currentEditorNode = $(self.editorInstance.container.$), leftEditor = firstEditorNode, rightEditor = firstEditorNode, i, iLength, doc = new CKEDITOR.dom.document(self.editorInstance.window.$.document), editor = self.editorInstance; if (CKEDITOR.env.webkit) { // Fix problem with cursor not appearing in Chrome when clicking below the body (#10945). doc.getDocumentElement().on( 'mousedown', function( evt ) { if (evt.data.getTarget().is( 'html' )){ editor.editable().focus(); } } ); } // iterate over "language" specific editors -> find the most left and most right editor for (i = 0, iLength = stack.editors.length; i < iLength; i++) { var editorNode = $(stack.editors[i].ckeditorGet().container.$), offsetLeft = editorNode.offset().left; if (offsetLeft < leftEditor.offset().left) { leftEditor = editorNode; } else if (offsetLeft > rightEditor.offset().left) { rightEditor = editorNode; } } stack.leftEditor = leftEditor; stack.rightEditor = rightEditor; // calculate toolbar width self.adjustToolbar(editorName, true); // if toolbar should not be shown on start up if (o.hideToolbarOnStartup) { self.editorInstance.focusManager.focus = function () { self.toggleToolbar(true); self.editorInstance.fire('focus'); }; self.editorInstance.focusManager.blur = function () { self.toggleToolbar(false); }; self.toggleToolbar(false); } // trigger window resize to adjust toolbars of all created editors $(window).trigger('resize.uiEditor', [true]); // update corresponding textarea when losing focus self.editorInstance.on('blur', function () { self.editorInstance.updateElement(); }); // if disable layer is undefined -> append new one to element and add to stack if (stack.disabledLayer === undefined) { stack.disabledLayer = $("<div />"); stack.disabledLayer.insertBefore(stack.toolbar); } // style disable layer (element lies over editor and toolbar => both cannot be used) stack.disabledLayer .addClass('cke_full_disabled') .css({ // position: 'absolute', // top: 0, // left: 0, // opacity: 0.7, // backgroundColor: '#fff', zIndex: stack.toolbar.css('z-index') + 1, display: 'none', width: stack.toolbar.width() + "px", height: (stack.toolbar.height() + $(self.editorInstance.container.$).height()) + "px" }); // // In case we never display a toolbar and the editor is disabled, we go into *AlmostFullscreen* // mode for editing on click onto the *disabledLayer*. // if (o.disable && o.hideToolbarOnStartup && o.noToolbar) { stack.disabledLayer.on('click', $.proxy(function (event) { this.execCommand('epAlmostFullscreen'); }, self.editorInstance)); } // remove title attributes from dropdowns (do not show obvious tooltips) stack.toolbar.find('.cke_rcombo a').removeAttr('title'); // set initial state for disable layer self.disableEditor(o.disable); // to disable the whole editor (e.g. for not editable properties in a multistore subshop) if(elem.hasClass('Disabled') && elem.prop('disabled')) { self.disableEditor(true); } $(window).trigger('scroll'); }, editorOptions); }, // show/hide disable layer disableEditor: function (disable) { editorStack[this.options.name].disabledLayer.css('display', (disable ? 'block' : 'none')); }, // calculate new width of toolbar and resize it adjustToolbar: function (editorName, initial) { var stack = editorStack[editorName], self = this, elem = self.element; if (!stack.rightEditor || !stack.leftEditor) { return; } var oldToolbarHeight = stack.toolbar.height(), toolbarWidth = (stack.rightEditor.outerWidth(true) + stack.rightEditor.offset().left) - stack.leftEditor.offset().left; // render toolbar with new values if toolbar flag is set if (!self.options.noToolbar) { ep(stack.toolbar).css({ 'width': toolbarWidth + 'px', 'z-index': 1, display: '' }); } // calculate new toolbar height var newToolbarHeight = stack.toolbar.is(':visible') ? stack.toolbar.height() : 0, currentEditorNode = $(elem.ckeditorGet().container.$), i, iLength; // if height has changed or it is the first call -> set top padding if (oldToolbarHeight !== newToolbarHeight || initial) { for (i = 0, iLength = stack.editors.length; i < iLength; i++) { $(stack.editors[i].ckeditorGet().container.$).parent().css({ 'paddingTop' : newToolbarHeight + 'px' }); } } $(stack.toolbar).css({ 'position': 'absolute', 'margin-top' : '-' + newToolbarHeight + 'px' }); // set dimensions for disable layer if (stack.disabledLayer) { stack.disabledLayer .width(toolbarWidth) .height(newToolbarHeight + $(elem.ckeditorGet().container.$).height()); } }, toggleToolbar: function (show) { var self = this, o = self.options; // move in another threat to ensure that hide/show is really called window.setTimeout(function () { editorStack[o.name].toolbar[show ? 'show' : 'hide'](); }, 1); self.adjustToolbar(o.name, true); }, destroy: function () { var self = this, o = self.options, name = o.name, destroy = true, stack = editorStack[name], key; self._superApply(arguments); // check if all similar editor instances are undefined for (key in stack.editors) { if (stack.editors.hasOwnProperty(key)) { if (stack.editors[key].data('uiUiEditor') !== undefined) { destroy = false; } } } // if all similar editor instances are undefined if (destroy) { // destroy all instances for (key in stack.editors) { if (stack.editors.hasOwnProperty(key)) { CKEDITOR.instances[stack.editors[key].attr('id')].destroy(); } } // remove according toolbar stack.toolbar.remove(); delete editorStack[name]; } } }); return ep; });