/**
* Creates an inline editor for the preview view.
*
* The `ep.uiPrevieweditor` method creates an inline editor for the preview view. In addition it provides necessary methods to connect the external toolbar to the CKEditor plugins.
*
* ### Examples
* Create a preview editor.
*
* JavaScript:
*
* ep('#editorID').uiPrevieweditor({
* 'name' : 'editorName',
* 'navbar' : 'Content',
* 'pageBreak': false,
* 'height': $('#baseId').height(),
* 'width': $('#baseId').width(),
* 'holder': $('#holderId'),
* 'base': $('#baseId')
* });
*
* HTML:
*
* <div id="baseId">Some Content</div>
* <div id="holderId" style="clear:both; position:relative;display: none;">
* <textarea style="visiblity:hidden;" name="editorName" id="editorID">Some Content</textarea>
* </div>
*
*
* @class jQuery.ui.uiPrevieweditor
* @extends jQuery.widget
*
* @uses ep
* @uses jQuery.ui.widget
* @uses jQuery.ckeditor.adapters.jquery
* @uses ep.alert
* @since 6.15.0
*/
/**
* @cfg {String} name Name of the editor (corresponds to the name attribute of the textarea).
*/
/**
* @cfg {Boolean} pageBreak Flag that indicates if the pagebreak icon is shown (e.g. for blog posts).
*/
/**
* @cfg {String} navbar Add class to the html element inside the iframe to set special CSS for preview inline mode.
*/
/**
* @cfg {Integer} width Width for the editor.
*/
/**
* @cfg {Integer} height Height for the editor.
*/
/**
* @cfg {Object} holder jQuery object of the DOM element which holds the textarea/editor.
*/
/**
* @cfg {Object} base jQuery object of the DOM element which contents the source html.
*/
/**
* See `jQuery.ui.uiPrevieweditor` 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 {Boolean} pageBreak Flag that indicates if the pagebreak icon is shown (e.g. for blog posts).
* @param {String} navbar Add class to the html element inside the iframe to set special CSS for preview inline mode.
* @param {Integer} width Width for the editor.
* @param {Integer} height Height for the editor.
* @param {Object} holder jQuery object of the DOM element which holds the textarea/editor.
* @param {Object} base jQuery object of the DOM element which contents the source html.
*
* @method uiPrevieweditor
* @member jQuery
*
* @since 6.15.0
*/
/*
* @copyright © Copyright 2006-2010, epages GmbH, All Rights Reserved.
*
* @module ep.ui.dialog
*/
/*jslint nomen: true, browser: true, plusplus: true, ass: true, unparam: true*/
/*global define,top,jq,$$,epages*/
// load ep.alert, because it is used in CKEditor plugin epPageBreak
define("ep/ui/previeweditor", [
"jquery",
"ep",
"ckeditor/ckeditor",
"jquery/ui/widget",
"jquery/ckeditor",
"ep/alert",
"ep/ui/core"
], function ($, ep, CKEDITOR) {
'use strict';
/*
{
'Text' : {
'editors' : [node1,node2], //jQuery objects of editor nodes with this particular name
'toolbar' : node,
'leftEditor' : $Object,
'rightEditor' : $Object,
'disabledLayer': $Object
}
}
*/
var editorStack = {},
top$ = $;
ep('<div>').prependTo('body').attr('id', 'uieditor_preview_toolbar').hide();
try {
if (top.jQuery) {
top$ = top.jQuery;
}
} catch (ignore) {}
// on window resize -> adjust width of editors to avoid display errors
$(window).on('resize', function () {
// move to another thread
setTimeout(function () {
var key, instance, iframe;
// loop over editor instances
for (key in CKEDITOR.instances) {
if (CKEDITOR.instances.hasOwnProperty(key)) {
instance = CKEDITOR.instances[key];
iframe = jq(instance.container.$).find('iframe');
// set possible width
iframe.width('100%');
}
}
}, 1);
});
// register method to manage availability of toolbar elements
CKEDITOR.manageToolbar = function (disabled) {
var toolbarElements = top$('#EditorTools *[data-ep-uieditor], #EditorTools select');
//toolbarElements[!!disabled ? 'addClass' : 'removeClass']('Disabled');
toolbarElements.toggleClass('Disabled', !!disabled);
};
// define ckeditor on parent frame
$.widget('ui.uiPrevieweditor', {
editorInstance: undefined,
toolbarId: 'uieditor_preview_toolbar',
heightCorrect: 25,
options: {
// name: undefined,
// navbar: undefined,
pageBreak: false,
holder: undefined,
base: undefined,
height: 300,
width: 400
},
_create: function () {
var self = this,
o = self.options,
elem = self.element,
editorName = o.name = o.name || ('editor_' + new Date().getTime());
self.id = elem.attr('id');
// create new stack entry if necessary
if (!editorStack[editorName]) {
editorStack[editorName] = {};
}
// register toolbar in stack
editorStack[editorName].toolbar = $('#' + self.toolbarId);
// create editor
self._createEditor();
},
_createEditor: function () {
var self = this,
o = self.options,
elem = self.element;
// create editor
elem.ckeditor(
function (textarea) {
var undoObject,
undoNode,
ckeIframe;
self.editorInstance = elem.ckeditorGet();
//#JSCOVERAGE_IF false
// commented out for jscoverage because it is just relevant for the preview with an external toolbar in a higher leveled iframe
// set flag to identify if PageBreak icon has to be shown in external toolbar
self.editorInstance.pageBreak = !!o.pageBreak;
// register textarea for undo
if ($$ !== undefined) {
undoObject = $$('undoWidget').getUndoObject();
undoNode = $('<input type="hidden">').appendTo('body').attr('name', elem.attr('name'));
undoNode.val(elem.val());
undoObject.registerInput(undoNode[0]);
// call toggleButtons to change the save button state when focus is on editorInstance
self.editorInstance.on('focus', function () {
var isTyping = undoObject.isTyping;
undoObject.isTyping = true;
$$('undoWidget').toggleButtons();
undoObject.isTyping = isTyping;
});
self.editorInstance.toggleView = function (show) {
// hide base div if defined
if (o.base) {
o.base[show ? 'hide' : 'show']();
}
// show editor
if (o.holder) {
o.holder[show ? 'show' : 'hide']();
}
if (show) {
// set focus to editor
self.editorInstance.focusManager.focus();
// set cursor in editor
self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START); // POSITION_AFTER_END
} else {
// set to source mode if wysiwyg is active to update content
if (self.editorInstance.mode === 'wysiwyg') {
self.editorInstance.execCommand('source');
}
self.editorInstance.focusManager.blur();
// write textarea data to base DIV
o.base.html(self.editorInstance._.data);
// trigger event to render gadgets in updated preview
$('body').trigger('updatepreview');
// trigger mode change after a delay to make sure that wysiwyg mode is active
window.setTimeout(function() {
if (self.editorInstance.mode === 'source') {
self.editorInstance.execCommand('source');
}
}, 200);
}
};
//only change ckeditor content when input was changed via undo/redo call
undoNode.on('change', function () {
if (top.epages.isUndoCall) {
self.editorInstance.setData(undoNode[0].value);
}
});
}
// handler if editor gets the focus
self.editorInstance.focusManager.focus = function () {
self.toggleToolbar(true);
self.editorInstance.fire('focus');
// manage visibility of the two external toolbars
top.jq('#PageTools').addClass('NavBarInvisible').removeClass('NavBarVisible');
top.jq('#EditorTools').addClass('NavBarVisible').removeClass('NavBarInvisible');
// show/hide PageBreak icon
top.jq('#EditorTools .MCEElementsBox-InsertPageBreak')[self.editorInstance.pageBreak ? 'removeClass' : 'addClass']('HideElement');
};
// handler if editor loses the focus
self.editorInstance.focusManager.blur = function () {
// update textarea
self.editorInstance.updateElement();
self.toggleToolbar(false);
//check if something was changed inside the editor and write it back to the hidden input for undo handling
if (undoNode && undoNode.val() !== elem.val()) {
undoNode.val(elem.val());
epages.event.fire(undoNode[0], 'change');
}
};
// autogrow handling (extraPlugin)
self.editorInstance.on('autogrow', function () {
o.holder.height(ckeIframe.height());
});
//check change content on ckeditor undo handling and add it to epages undo handling
self.editorInstance.on('saveSnapshot', function () {
if (undoNode && undoNode.val() !== elem.val()) {
undoNode.val(elem.val());
epages.event.fire(undoNode[0], 'change');
}
});
self.toggleToolbar(false);
// handling to mark toolbar elements if the current selection has changed
self.editorInstance.on('selectionChange', function (ev) {
var pathElements = ev.data.path.elements,
toolbarCommands = top$('#EditorTools *[data-ep-uieditor]'),
commandsArray = [],
// takes all commands (handed over by classes)
pathArray = [],
// takes tag names (lowercase) of all path elements
styles = {},
// takes all the relevant styles
styleTupel,
styleArray,
styleKey,
idKey,
attributes,
key,
_key,
styleCount,
styleCountLength,
className,
element,
replaceFn = function (strMatch, strBracket, strPos, strSrc) {
return strBracket.toUpperCase();
};
// loop over all elements depending on the selection
for (key in pathElements) {
if (pathElements.hasOwnProperty(key)) {
idKey = pathElements[key].$.tagName.toLowerCase();
attributes = pathElements[key].$.attributes[0];
// check if path element has an attributes and style property
if (attributes !== undefined) {
// if a style is set
if (attributes.name === "style") {
// split style definitions
styleArray = attributes.value.substr(0, attributes.value.length - 1).split(";");
// loop over style definitions
styleCountLength = styleArray.length;
for (styleCount = 0; styleCount < styleCountLength; styleCount++) {
// split value (e.g. [font-size]['9px']
styleTupel = styleArray[styleCount].split(":");
// generate camel-case key (e.g. font-size -> fontSize)
styleKey = styleTupel[0].replace(/\-(\w)/g, replaceFn);
// set style definition only from the deepest selection
if (styles[styleKey] === undefined) {
switch (styleKey) {
// indent -> data-ep-uieditor="indent"
case 'marginLeft':
styles[styleKey] = $.merge(['indent', idKey], styleTupel);
break;
// color -> data-ep-uieditor="fontcolor"
case 'color':
styles[styleKey] = $.merge(['fontcolor', idKey], styleTupel);
break;
// backgroundColor -> data-ep-uieditor="backgroundColor"
case 'backgroundColor':
styles[styleKey] = $.merge(['backgroundColor', idKey], styleTupel);
break;
// default -> data-ep-uieditor="$.trim(styleTupel[1])"
default:
styles[styleKey] = $.merge([$.trim(styleTupel[1]), idKey], styleTupel);
break;
}
}
}
}
}
// push tag name in array
pathArray.push(idKey);
// get element's class name, concatenate it with tag name and push in array
className = pathElements[key].$.className.toLowerCase();
if (className) {
idKey += '_' + className;
}
commandsArray.push(idKey);
}
}
// loop over all toolbar elements (reset select fields)
toolbarCommands.each(function (i, elem) {
// reset options
if (elem.tagName.toLowerCase() === 'option') {
$(elem).closest('select')[0].selectedIndex = 0;
}
});
// loop over all toolbar elements
toolbarCommands.each(function (i, elem) {
var dataValue = $(elem).data('epUieditor');
// select/mark or reset element
if (elem.tagName.toLowerCase() === 'option') {
if ($.inArray(dataValue, pathArray) !== -1) {
$(elem).closest('select').val(dataValue);
}
} else {
$(elem)[$.inArray(dataValue, commandsArray) !== -1 ? 'addClass' : 'removeClass']('Selected');
}
});
// loop over registered style properties
for (_key in styles) {
if (styles.hasOwnProperty(_key)) {
// get associated toolbar element
element = top$('#EditorTools *[data-ep-uieditor="' + styles[_key][0] + '"]');
// select or mark element
if (element[0].tagName.toLowerCase() === 'option') {
element.closest('select').val(element.val());
} else {
element.addClass('Selected');
// if an element exists to display the current color
if (element.find('div.ColorBox').length) {
element.find('div.ColorBox').css('background-color', styles[_key][3]);
}
}
delete styles[_key];
}
}
// set default for text-align when nothing is selected
if ($.grep(['right', 'center', 'justify'], function (value, index) {
return top$('#EditorTools *[data-ep-uieditor="' + value + '"]').hasClass('Selected');
}).length === 0) {
top$('#EditorTools *[data-ep-uieditor="left"]').addClass('Selected');
}
});
self.editorInstance.on('dataReady', function( e ){
self.editorInstance.epDataReady = true;
});
elem.on('change', function () {
self.editorInstance.setData();
});
//#JSCOVERAGE_ENDIF
// get iframe from editor instance
ckeIframe = $(self.editorInstance.container.$).find('iframe');
// add class to iframe to set special CSS for preview inline mode and remove inline styles build by CKEditor
ckeIframe.addClass('cke_preview').removeAttr('style').parent().removeAttr('style');
// add class to the html element inside the iframe to set special CSS for preview inline mode
ckeIframe.contents().find('html').addClass(function () {
var addedClass = 'cke_preview';
if (o.navbar) {
addedClass += ' cke_' + o.navbar;
}
return addedClass;
});
// hide base div if defined
if (o.base) {
o.base.hide();
}
// show editor
if (o.holder) {
o.holder
.width(o.width)
.show();
}
// set initial dimensions
ckeIframe
.height('100%')
.width(o.holder ? "100%" : o.width);
// set focus to editor
self.editorInstance.focusManager.focus();
// set cursor to editor
self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START); // POSITION_AFTER_END
// save initial width
self.editorInstance.initWidth = o.width;
$(window).trigger('resize');
},
//options for editor
{
extraPlugins: 'autogrow',
autoGrow_minHeight: 80,
sharedSpaces: {
top: 'uieditor_preview_toolbar'
},
toolbar: o.pageBreak ? 'FullPageBreak' : 'Full'
}
);
},
// set cursor to editor
// position can be: POSITION_AFTER_END, POSITION_BEFORE_START
// call: self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START);
setCursor: function (editorInstance, position) {
editorInstance.focus();
var selection = editorInstance.getSelection(),
range = selection.getRanges()[0],
paragraph,
newRange;
if (range) {
paragraph = (range.startContainer.getName && range.startContainer.getName() === "body") ? range.startContainer : range.startContainer.getAscendant({p: 2}, true);
newRange = new CKEDITOR.dom.range(range.document);
if (newRange.endOffset !== null) {
newRange.moveToPosition(paragraph, position);
newRange.select();
}
}
},
/**
* Handles settings for the current editor if the external toolbar is toggled.
*
* @param {Boolean} blnShow Specifies path and name of the file containing the new preview image.
*
* @method toggleToolbar
* @member jQuery.ui.uiPrevieweditor
*
* @since 6.15.0
*/
toggleToolbar: function (show) {
try {
if (show && this.editorInstance) {
top.EP_CKEDITOR = this.editorInstance;
}
} catch (e) {
$.error(e);
}
return this;
},
destroy: function () {
var self = this,
id = self.id;
// destroy according ckeditor instance
CKEDITOR.instances[id].destroy();
self._superApply(arguments);
}
});
return ep;
});