/** * InputfieldCKEditor.js * * Initialization for CKEditor * */ /** * Get ProcessWire config settings for given CKE editor object or name * * @param editor * @returns {*} * */ function ckeGetProcessWireConfig(editor) { var editorName = typeof editor == "string" ? editor : editor.name; var configName = editorName.replace('Inputfield_', 'InputfieldCKEditor_'); var $repeaterItem = ''; var settings = {}; configName = configName.replace('Inputfield_', 'InputfieldCKEditor_'); if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('_repeater') > 0) { configName = configName.replace(/_repeater[0-9]+/, ''); $repeaterItem = $('#' + editorName).closest('.InputfieldRepeaterItem'); } if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('_ckeditor') > 0) { configName = configName.replace(/_ckeditor$/, ''); // inline only } if(typeof ProcessWire.config[configName] == "undefined" && configName.indexOf('__') > 0) { configName = configName.replace(/__\d+$/, ''); // remove language-id } if(typeof ProcessWire.config[configName] == "undefined") { settings.error = 'Cannot find CKEditor settings for ' + configName; } else { settings = ProcessWire.config[configName]; } if($repeaterItem.length) { settings['repeaterItem'] = $repeaterItem; } else { settings['repeaterItem'] = ''; } return settings; } /** * Add external plugins * * These are located in: * /wire/modules/Inputfield/InputfieldCKEditor/plugins/[name]/plugin.js (core external plugins) * /site/modules/InputfieldCKEditor/plugins/[name]/plugin.js (site external plugins) * */ function ckeLoadPlugins() { for(var name in ProcessWire.config.InputfieldCKEditor.plugins) { var file = ProcessWire.config.InputfieldCKEditor.plugins[name]; CKEDITOR.plugins.addExternal(name, file, ''); } } ckeLoadPlugins(); /** * Event called when an editor is blurred, so that we can check for changes * * @param event * */ function ckeBlurEvent(event) { var editor = event.editor; var $textarea = $(editor.element.$); if(editor.checkDirty()) { // value changed if($textarea.length) { if($textarea.is("textarea")) $textarea.change(); $textarea.closest(".Inputfield").addClass('InputfieldStateChanged'); } } } /** * Event called when an editor is focused * * @param event * */ function ckeFocusEvent(event) { var editor = event.editor; var $textarea = $(editor.element.$); $textarea.trigger('pw-focus'); } /** * Event called when an editor is resized vertically * * @param event * */ function ckeResizeEvent(event) { var editor = event.editor; var $textarea = $(editor.element.$); if($textarea.length) { $textarea.closest(".Inputfield").trigger('heightChanged'); } } /** * Handler for fileUploadRequest event * * @param event * */ function ckeUploadEvent(event) { var xhr = event.data.fileLoader.xhr; var fileLoader = event.data.fileLoader; var settings = ckeGetProcessWireConfig(event.editor); var uploadFieldName = settings ? settings.pwUploadField : '_unknown'; var $imageInputfield = $('#Inputfield_' + uploadFieldName); if(typeof settings.repeaterItem != "undefined" && settings.repeaterItem.length) { var $repeaterImageField = settings.repeaterItem.find('.InputfieldImage:not(.InputfieldFileSingle)'); if($repeaterImageField.length) $imageInputfield = $repeaterImageField; } if($imageInputfield.length) { xhr.open("POST", fileLoader.uploadUrl, true); $imageInputfield.trigger('pwimageupload', { 'name': fileLoader.fileName, 'file': fileLoader.file, 'xhr': xhr }); // Prevented the default behavior. event.stop(); } else { if(typeof settings.error != "undefined" && settings.error.length) { ProcessWire.alert(settings.error); } else { ProcessWire.alert('Unable to find images field for upload'); } event.stop(); return false; } } /** * Attach events common to all CKEditor instances * * @param editor CKEditor instance * */ function ckeInitEvents(editor) { editor.on('blur', ckeBlurEvent); editor.on('focus', ckeFocusEvent); editor.on('change', ckeBlurEvent); editor.on('resize', ckeResizeEvent); editor.on('fileUploadRequest', ckeUploadEvent, null, null, 4); var $textarea = $(editor.element.$); var $inputfield = $textarea.closest('.Inputfield.InputfieldColumnWidth'); if($inputfield.length) setTimeout(function() { $inputfield.trigger('heightChanged'); }, 1000); } /** * Called on saveReady or submit to copy inline contents to a form element in the POST request * * @param $inputfield * */ function ckeSaveReadyInline($inputfield) { if(!$inputfield.length) return; var $inlines = $inputfield.hasClass('.InputfieldCKEditorInline') ? $inputfield : $inputfield.find(".InputfieldCKEditorInline"); if($inlines.length) $inlines.each(function() { var $t = $(this); var value; if($t.hasClass('InputfieldCKEditorLoaded')) { var editor = CKEDITOR.instances[$t.attr('id')]; // getData() ensures there are no CKE specific remnants in the markup if(typeof editor != "undefined") { if(editor.focusManager.hasFocus) { // TMP: CKEditor 4.5.1 / 4.5.2 has documented bug that causes JS error on editor.getData() here // this section of code can be removed after they fix it (presumably in 4.5.3) editor.focusManager.focus(true); editor.focus(); } value = editor.getData(); } } else { value = $t.html(); } var $input = $t.next('input'); $input.attr('value', value); }); } /** * Called on saveReady event to force an editor.updateElement() to update original textarea * * @param $inputfield * */ function ckeSaveReadyNormal($inputfield) { var $normals = $inputfield.hasClass('InputfieldCKEditorNormal') ? $inputfield : $inputfield.find(".InputfieldCKEditorNormal"); $normals.each(function() { var $t = $(this); if(!$t.hasClass('InputfieldCKEditorLoaded')) return; var editor = CKEDITOR.instances[$t.attr('id')]; editor.updateElement(); }); } /** * Mouseover or focus event that activates inline CKEditor instances * * @param event * */ function ckeInlineMouseoverEvent(event) { // we initialize the inline editor only when moused over // so that a page can handle lots of editors at once without // them all being active var $t = $(this); if($t.hasClass("InputfieldCKEditorLoaded")) return; $t.effect('highlight', {}, 500); $t.attr('contenteditable', 'true'); var configName = $t.attr('data-configName'); if(event.type == 'focusin') { CKEDITOR.once('instanceReady', function(event) { $(':focus').blur(); event.editor.focus(); }); } var editor = CKEDITOR.inline($(this).attr('id'), ProcessWire.config[configName]); ckeInitEvents(editor); $t.addClass("InputfieldCKEditorLoaded"); } /** * CKEditors hidden in jQuery UI tabs sometimes don't work so this initializes them when they become visible * */ function ckeInitTab(event, ui) { var $t = ui.newTab; var $a = $t.find('a'); if($a.hasClass('InputfieldCKEditor_init')) return; var editorID = $a.attr('data-editorID'); var configName = $a.attr('data-configName'); var editor = CKEDITOR.replace(editorID, config[configName]); ckeInitEvents(editor); $a.addClass('InputfieldCKEditor_init'); ui.oldTab.find('a').addClass('InputfieldCKEditor_init'); // in case it was the starting one var $editor = $("#" + editorID); $editor.addClass('InputfieldCKEditorLoaded'); } /** * Initialize a normal CKEditor instance for the given textarea ID * * @param editorID * */ function ckeInitNormal(editorID) { var $editor = $('#' + editorID); var $parent = $editor.parent(); if(typeof ProcessWire.config.InputfieldCKEditor.editors[editorID] != "undefined") { var configName = ProcessWire.config.InputfieldCKEditor.editors[editorID]; } else { var configName = $editor.attr('data-configName'); } if($parent.hasClass('ui-tabs-panel') && $parent.css('display') == 'none') { // CKEditor in a jQuery UI tab (like langTabs) var parentID = $editor.parent().attr('id'); var $a = $parent.closest('.ui-tabs, .langTabs').find('a[href=#' + parentID + ']'); $a.attr('data-editorID', editorID).attr('data-configName', configName); $parent.closest('.ui-tabs, .langTabs').on('tabsactivate', ckeInitTab); } else { // visible CKEditor var editor; if(typeof ProcessWire.config[configName] != "undefined") { var editor = CKEDITOR.replace(editorID, ProcessWire.config[configName]); } else if(typeof $editor.attr('data-configdata') != "undefined") { // allow for alternate option of config data being passed through a data attribute // useful for some dynamic/ajax situations var configData = JSON.parse($editor.attr('data-configdata')); ProcessWire.config[configName] = configData; var editor = CKEDITOR.replace(editorID, configData); } if(editor) { ckeInitEvents(editor); $editor.addClass('InputfieldCKEditorLoaded'); } } } /** * Prepare inline editors * */ $(document).ready(function() { /** * Override ckeditor timestamp for cache busting * */ CKEDITOR.timestamp = ProcessWire.config.InputfieldCKEditor.timestamp; /** * Regular editors * */ for(var editorID in ProcessWire.config.InputfieldCKEditor.editors) { ckeInitNormal(editorID); } $(document).on('reloaded', '.InputfieldCKEditor', function() { // reloaded event is sent to .Inputfield when the contents of the .Inputfield // have been replaced with new markup var $editor = $(this).find('.InputfieldCKEditorNormal:not(.InputfieldCKEditorLoaded)'); $editor.each(function() { ckeInitNormal($(this).attr('id')); }); return false; }); $(document).on('image-edit', '.InputfieldCKEditor', function() { // re-initialize CKE when image-edit event triggers (from InputfieldImage.js) via rpsallis var $editor = $(this).find('.InputfieldCKEditorNormal'); $editor.each(function() { var editorID = $(this).attr('id'); if(typeof CKEDITOR.instances[editorID] !== 'undefined') CKEDITOR.instances[editorID].destroy(); ckeInitNormal(editorID); }); }); /** * Inline editors * */ CKEDITOR.disableAutoInline = true; $(document).on('mouseover focus', '.InputfieldCKEditorInlineEditor', ckeInlineMouseoverEvent); $(document).on('submit', 'form.InputfieldForm', function() { ckeSaveReadyInline($(this)); // note: not necessary for regular editors since CKE takes care // of populating itself to the textarea on it's own during submit }); /** * saveReady event handler * * saveReady is sent by some form-to-ajax page save utils in ProcessWire * found it was necessary for normal CKE instances because a cancelled submit * event was not updating the original textarea, so we do it manually * */ $(document).on('saveReady', '.InputfieldCKEditor', function() { ckeSaveReadyNormal($(this)); ckeSaveReadyInline($(this)); }); });