TinyMCE focus in and focus out loop - javascript

I have a problem with TinyMCE 4.x and its behavior. Whenever I initialize TinyMCE on popup i get focus in and focus out loop and its looks like flickering.
Here is a gif that shows exactly what is happening. You can see that on main page where i initialize TinyMCE everything is fine and its working as expected, but on popup its kinda start focusing in and out. You can see it in developer tool as well, that class for it changes from mce-content-body to mce-content-body mce-edit-focus and then back.
Here is code how I initialize the TinyMCE.:
initializeTinyMCE(element){
var self = this;
if (element == 'p') {
var toolbar = 'forecolor | fontsizeselect fontselect | align | bold italic underline | bullist numlist | link | strokes | done';
} else if (element == 'h') {
var toolbar = 'forecolor | fontsizeselect | fontselect | H | align | bold italic underline | link | strokes | done';
}
if(self.el.parent().hasClass('popup_content')){
var inline = false
} else {
var inline = true
}
tinyMCE.init({
selector: `#${self.el.find('.content_element').attr('id')}`,
auto_focus: self.testFocus(self.el.find('.content_element').attr('id')),
content_css : 'assets/tinymce/custom/fonts.css',
inline: true,
menubar: false,
theme: 'modern',
skin: 'pagesource',
plugins: ['lists link textcolor colorpicker strokes'],
toolbar: toolbar,
fontsize_formats: '8px 10px 12px 14px 18px 24px 36px 48px',
setup: function (editor) {
self.clonedEl = self.el.clone()
editor.addButton('align', {
type: 'menubutton',
text: '',
icon: 'alignleft',
menu: [{
text: '',
icon: 'alignleft',
onclick: function () {
editor.execCommand('JustifyLeft');
}
}, {
text: '',
icon: 'aligncenter',
onclick: function () {
editor.execCommand('JustifyCenter');
}
}, {
text: '',
icon: 'alignright',
onclick: function () {
editor.execCommand('JustifyRight');
}
}, {
text: '',
icon: 'alignjustify',
onclick: function () {
editor.execCommand('JustifyFull');
}
}]
}),
editor.addButton('H', {
type: 'menubutton',
text: 'H',
icon: false,
menu: [{
text: 'H1',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h1');
}
}, {
text: 'H2',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h2');
}
}, {
text: 'H3',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h3');
}
}, {
text: 'H4',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h4');
}
}, {
text: 'H5',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h5');
}
}, {
text: 'H6',
onclick: function() {
editor.execCommand('FormatBlock', false, 'h6');
}
}]
});
editor.addButton('done', {
text: 'Done',
icon: false,
onclick: function () {
tinyMCE.remove();
self.openEditManager();
self.el.find('.ui-resizable-handle').removeClass('hidden');
}
});
editor.on('remove', function(ed) {
self.removeTinyMCE(ed);
window.historyMem.addRecord(self.clonedEl, self.el.clone())
});
},
init_instance_callback: function (editor) {
editor.on('focus', function (e) {
console.log(2)
});
}
});
}
And this is the element and structure on which the problem persists:
<div class="body_wrapper">
<div class="project_wrapper" data-uuid="<%= #page.uuid %>">
<div class="edit_grid">
<div class="popup_editor">
<div class="popup">
<div class="popup_content">
<div class='widget'>
<div class='tinymce_editor'> # this is the element
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Note: When not doing inline editor it works fine.
I don't need exact solution, any suggestions and ideas would be appreciated. I have tried almost everything, like moving editor popup in different position in DOM, stripping all of the settings, etc. Thank you.

Related

How to call javascript function in TinyMCE?

My application can edit certain word but not all, using contenteditable HTML attribute I can do that. Now The problem is I want to move <?php echo $konten;?> inside textarea with id post_content witch is make textarea become TinyMCE text editor. But my javascript function become not working. I already using init_instance_callback but it still not work. This is my code :
<style>
*[contenteditable="true"] {
background-color: rgba(217, 245, 255,0.5);
}
</style>
<?php
// $konten = $template['konten'];
$konten = "<p>Hai this is the content.</br> My name is : {{name}}</br></p><p></br><table border ='1'><tr><td>This is table</td></tr><tr><td>{{loop_table_one}}</td></tr></table></br><ol><li>{{loop_list_one}}</li></ol></p>";
preg_match_all('/{{(.*?)}}/', $konten, $variable);
$a = array_unique($variable[0]);
foreach($a as $item) {
$b = preg_replace('/{{(.*?)}}/is', "$1", $item);
if (strtolower(substr($b, 0, 9)) == 'loop_list') {
$konten = str_ireplace($item, '<span class="input-list input-'.$b.'" name="'.$b.'">'.$item.'</span>', $konten);
} else if (strtolower(substr($b, 0, 10)) == 'loop_table') {
$konten = str_ireplace($item, '<span class="input-table input-'.$b.'" name="'.$b.'">'.$item.'</span>', $konten);
} else {
$konten = str_ireplace($item, '<span class="input-variable input-'.$b.'" name="'.$b.'" contentEditable="true">'.$item.'</span>', $konten);
}
}
?>
<textarea id="post_content">
<?php echo $konten;?>
</textarea>
<script>
$(document).ready(function(){
$(".input-variable").on('DOMSubtreeModified',function(e){
e.stopPropagation();
let cls = $(this).prop('class');
// remove string inputt
let id = cls.replace("input-variable ", "");
let val = $(this).html();
$('.'+id).each(function () {
if ($(this).html() != val) {
$(this).html(val);
}
})
e.stopPropagation();
});
$( ".input-list" ).closest('ol').add($( ".input-list" ).closest('ul')).prop("contentEditable", "true");
$( ".input-table" ).closest('table').prop("contentEditable", "true");
});
function myCustomInitInstance(inst) {
// i already move my function here but still now working
}
tinymce.init({
init_instance_callback : "myCustomInitInstance",
table_class_list: [
{title: 'None', value: ''},
{title: 'Editable Table', value: 'editablecontent'}
],
readonly:1,
content_style: "body { font-family: Cambria; }",
selector: "#post_content",
toolbar: '#mytoolbar',
lineheight_formats: "8pt 9pt 10pt 11pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 36pt",
// ukuran A4 Potrait
width : "742",
height : "842",
plugins: 'textcolor table paste',
font_formats:
"Cambria=cambria;Calibri=calibri;Andale Mono=andale mono,times; Arial=arial,helvetica,sans-serif; Arial Black=arial black,avant garde; Book Antiqua=book antiqua,palatino; Comic Sans MS=comic sans ms,sans-serif; Courier New=courier new,courier; Georgia=georgia,palatino; Helvetica=helvetica; Impact=impact,chicago; Oswald=oswald; Symbol=symbol; Tahoma=tahoma,arial,helvetica,sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva; Webdings=webdings; Wingdings=wingdings,zapf dingbats",
plugins: [
"advlist autolink lists link image charmap print preview hr anchor pagebreak",
"searchreplace wordcount visualblocks visualchars code fullscreen",
"insertdatetime nonbreaking save table contextmenu directionality",
"emoticons template paste textcolor colorpicker textpattern"
],
style_formats: [
{
title: 'Line height',
items: [
{
title: 'Default',
inline: 'span',
styles: { 'line-height': 'normal', display: 'inline-block' }
},
{
title: '1',
inline: 'span',
styles: {'line-height': '1', display: 'inline-block'}
},
{
title: '1.1',
inline: 'span',
styles: {'line-height': '1.1', display: 'inline-block'}
},
{
title: '1.2',
inline: 'span',
styles: {'line-height': '1.2', display: 'inline-block'}
},
{
title: '1.3',
inline: 'span',
styles: {'line-height': '1.3', display: 'inline-block'}
},
{
title: '1.4',
inline: 'span',
styles: {'line-height': '1.4', display: 'inline-block'}
},
{
title: '1.5',
inline: 'span',
styles: {'line-height': '1.5', display: 'inline-block'}
},
{
title: '2 (Double)',
inline: 'span',
styles: {'line-height': '2', display: 'inline-block'}
}
]
},
{title : 'Table row 1', selector : 'tr', classes : 'tablerow1'}
],
toolbar: "insertfile undo redo | styleselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image responsivefilemanager | formatselect | strikethrough | forecolor backcolor fontselect fontsizeselect | pastetext |",
convert_fonts_to_spans: true,
paste_word_valid_elements: "b,strong,i,em,h1,h2,u,p,ol,ul,li,a[href],span,color,font-size,font-color,font-family,mark,table,tr,td",
paste_retain_style_properties: "all",
automatic_uploads: true,
image_advtab: true,
images_upload_url: "<?php echo base_url("template/tinymce_upload")?>",
file_picker_types: 'image',
paste_data_images:true,
relative_urls: false,
remove_script_host: false,
file_picker_callback: function(cb, value, meta) {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function() {
var file = this.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
var id = 'post-image-' + (new Date()).getTime();
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
var blobInfo = blobCache.create(id, file, reader.result);
blobCache.add(blobInfo);
cb(blobInfo.blobUri(), { title: file.name });
};
};
input.click();
}
});
</script>
If you are trying to use init_instance_callback you have to pass it a function. In your code example you have:
init_instance_callback : "myCustomInitInstance",
...so you are passing a string to a configuration option that expects a function. Here is a TinyMCE Fiddle that shows how to pass a function to the init_instance_callback configuration option:
https://fiddle.tiny.cloud/18haab

how to use TinyMCE visual_anchor_class

Using TinyMCE, I want to be able to add a custom class to some of the anchor tags in my editor. I found visual_anchor_class which I can't get to work. I've created a class called .anchorBtn and added contextmenu to my init.
When I right-click over my anchor (where it says "DONATE"), I see an empty white box.
tinymce.init({
selector: selector,
menubar: false,
height: 300,
plugins: [
"link anchor lists code paste image contextmenu"
],
statusbar: false,
toolbar: 'undo redo | bold italic underline | bullist numlist | buttons link image | code',
content_css : "css/tinyMCE.css",
link_list:"cgi/linkList.exe?token="+tkn,
force_br_newlines : false,
force_p_newlines : false,
forced_root_block : '',
body_class: "mceBackColor",
oninit : "setPlainText("+selector+")",
visual_anchor_class: 'anchorBtn',
contextmenu: "anchorBtn",
paste_as_text: true,
file_browser_callback: function(field_name, url, type, win) {
var tkn=getToken();
$("#PDFuform").attr("action", "cgi/pdfUpload.exe");
$("#pdfToken").val(tkn);
$("#pdfField").val(field_name);
$('#PDFuform input').click();
},
setup: function (editor) {
editor.addButton('buttons', {
type: 'menubutton',
text: 'Custom Buttons',
icon: false,
menu: [
{
image : '../img/tourBtn.jpg',
title : "SCHEDULE A TOUR",
text: "SCHEDULE A TOUR",
icon: false,
onclick: function () {
editor.insertContent("<div class='bbtn bSAT'>SCHEDULE A TOUR</div>");
}
}
]
})
}
});
What am I doing wrong?

Tinymce editor becomes empty when ng-repeat list is reordered

This is a simplified example of a bug we faced in our project recently.
I have a list of objects with "name" and "position" properties and wanna use TinyMCE editor instead of textarea to display "name".
Besides, the list is ordered by "position" property which is editable.
Noticed that once "position" property is changed(list is reordered), TinyMCE editor becomes empty.
Anyone has any ideas why it happens and how to fix this?
Example code: JsFiddle
HTML
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="//tinymce.cachefly.net/4.1/tinymce.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<p>List of activities:</p>
<div ng-repeat="activity in model.activities | orderBy: 'position'">
<label for="$index">Position</label>
<input id="$index" type="number" ng-model="activity.position" style="width: 50px">
<textarea ng-model="activity.name" ui-tinymce="tinyMceOptions" rows="2" cols="10"></textarea>
<hr>
</div>
</div>
</body>
JS
var myApp = angular.module('myApp', ['ui.tinymce']);
/**
* Binds a TinyMCE widget to <textarea> elements.
*/
angular.module('ui.tinymce', [])
.value('uiTinymceConfig', {})
.directive('uiTinymce', ['uiTinymceConfig', function (uiTinymceConfig) {
uiTinymceConfig = uiTinymceConfig || {};
var generatedIds = 0;
return {
priority: 10,
require: 'ngModel',
link: function (scope, elm, attrs, ngModel) {
var expression, options, tinyInstance,
updateView = function () {
ngModel.$setViewValue(elm.val());
if (!scope.$root.$$phase) {
scope.$apply();
}
};
// generate an ID if not present
if (!attrs.id) {
attrs.$set('id', 'uiTinymce' + generatedIds++);
}
if (attrs.uiTinymce) {
expression = scope.$eval(attrs.uiTinymce);
} else {
expression = {};
}
// make config'ed setup method available
if (expression.setup) {
var configSetup = expression.setup;
delete expression.setup;
}
options = {
// Update model when calling setContent (such as from the source editor popup)
setup: function (ed) {
var args;
ed.on('init', function(args) {
ngModel.$render();
ngModel.$setPristine();
});
// Update model on button click
ed.on('ExecCommand', function (e) {
ed.save();
updateView();
});
// Update model on keypress
ed.on('KeyUp', function (e) {
ed.save();
updateView();
});
// Update model on change, i.e. copy/pasted text, plugins altering content
ed.on('SetContent', function (e) {
if (!e.initial && ngModel.$viewValue !== e.content) {
ed.save();
updateView();
}
});
ed.on('blur', function(e) {
elm.blur();
});
// Update model when an object has been resized (table, image)
ed.on('ObjectResized', function (e) {
ed.save();
updateView();
});
if (configSetup) {
configSetup(ed);
}
},
mode: 'exact',
elements: attrs.id
};
// extend options with initial uiTinymceConfig and options from directive attribute value
angular.extend(options, uiTinymceConfig, expression);
setTimeout(function () {
tinymce.init(options);
});
ngModel.$render = function() {
if (!tinyInstance) {
tinyInstance = tinymce.get(attrs.id);
}
if (tinyInstance) {
tinyInstance.setContent(ngModel.$viewValue || '');
}
};
scope.$on('$destroy', function() {
if (!tinyInstance) { tinyInstance = tinymce.get(attrs.id); }
if (tinyInstance) {
tinyInstance.remove();
tinyInstance = null;
}
});
}
};
}]);
myApp.controller("MyCtrl", ["$scope", function($scope) {
$scope.model = {
activities: [
{name: "activity 1", position: 1},
{name: "activity 2", position: 2},
{name: "activity 3", position: 3},
{name: "activity 4", position: 4},
{name: "activity 5", position: 5}
]
};
$scope.tinyMceOptions = {
selector: "textarea",
theme: "modern",
plugins: [
"autolink lists link charmap print preview hr anchor pagebreak autoresize",//advlist
"searchreplace visualblocks visualchars code",
"insertdatetime nonbreaking save table directionality",
"emoticons template paste textcolor colorpicker textpattern"
],
toolbar1: "bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | forecolor backcolor | undo redo | link | pastetext",
paste_auto_cleanup_on_paste: true,
paste_strip_class_attributes: 'mso',
paste_data_images: false,
theme_advanced_buttons3_add: "pastetext,pasteword,selectall",
image_advtab: true,
//templates: [
// {title: 'Test template 1', content: 'Test 1'},
// {title: 'Test template 2', content: 'Test 2'}
//],
browser_spellcheck: true,
menubar: false,
//theme_advanced_disable: "bullist,numlist",
target_list: [{ title: 'New page', value: '_blank' }],
//advlist_number_styles: [
// {title : 'Standard', styles : {listStyleType : ''}},
// {title : 'a. b. c.', styles : {listStyleType : 'lower-alpha'}}
//],
//spellchecker_languages: "+English=en",
//spellchecker_rpc_url: 'spellchecker.php',
handle_event_callback: function (e) {
// put logic here for keypress
}
};
}]);

How do I add toolbar buttons to a custom tinymce dropdown menu?

I've created a custom dropdown in tinymce like this:
tinymce.init({
toolbar: "alignment",
setup: function(editor) {
editor.addButton('alignment', {
type: 'menubutton',
text: 'Alignment',
icon: false,
menu: [
{ text: 'left', onclick: function() {tinymce.activeEditor.formatter.toggle('alignleft');}},
{ text: 'center', onclick: function() {tinymce.activeEditor.formatter.toggle('aligncenter');}},
{ text: 'right', onclick: function() {tinymce.activeEditor.formatter.toggle('alignright');}},
{ text: 'justify', onclick: function() {tinymce.activeEditor.formatter.toggle('alignjustify');}},
]
});
}
});
which creates this:
However what I'd like is to just move the alignment buttons from the main toolbar in the dropdown menu.
How do I got about putting these actual buttons from the toolbar, into a dropdown menu? Is it like the code above or is a a totally different way?
So basically put these buttons in the dropdown above with the toggle states for on and off too.
Try this setup - Plunker
tinymce.init({
selector: "textarea",
toolbar: "styleselect | bold italic | alignment | alignmentv2",
setup: function(editor) {
editor.addButton('alignment', {
type: 'listbox',
text: 'Alignment',
icon: false,
onselect: function(e) {
tinyMCE.execCommand(this.value());
},
values: [
{icon: 'alignleft', value: 'JustifyLeft'},
{icon: 'alignright', value: 'JustifyRight'},
{icon: 'aligncenter', value: 'JustifyCenter'},
{icon: 'alignjustify', value: 'JustifyFull'},
],
onPostRender: function() {
// Select the firts item by default
this.value('JustifyLeft');
}
});
editor.addButton('alignmentv2', {
type: 'menubutton',
text: 'Alignment v2',
icon: false,
menu: [
{icon: 'alignleft', onclick: function() { console.log(editor); tinyMCE.execCommand('JustifyLeft'); }},
{icon: 'alignright', onclick: function() { tinyMCE.execCommand('JustifyRight'); }}
]
});
}
});
#NoBugs, you can enhance the onselect method to perform the alignment icon update.
At first, by examining structure of this object in the onselect method we'll see that this.settings.values property stores an array with early defined values.
By using one of many find utility functions we get the selected value item and update the icon as needed:
onselect: function() {
selectedItem = find(this.settings.values, {value: this.value()})
this.icon(selectedItem.icon)
tinyMCE.execCommand(this.value());
}
Hope, this helps. Cheers!
This is probably best solved using a custom split button. That way we can assign the last selected option to the main button.
See the result here - CodePen
tinymce.init({
selector: '#editor',
menubar: false,
toolbar: 'bold italic underline | alignmentsplit | bullist numlist outdent indent',
setup: function (editor) {
editor.on('init', function() {
this.getDoc().body.style.fontSize = '16px';
this.getDoc().body.style.fontFamily = 'Georgia';
});
editor.addButton('alignmentsplit', {
type: 'splitbutton',
text: '',
icon: 'alignleft',
onclick: function(e) {
tinyMCE.execCommand(this.value);
},
menu: [{
icon: 'alignleft',
text: 'Align Left',
onclick: function() {
tinyMCE.execCommand('JustifyLeft');
this.parent().parent().icon('alignleft');
this.parent().parent().value = 'JustifyLeft'
}
}, {
icon: 'alignright',
text: 'Align Right',
onclick: function() {
tinyMCE.execCommand('JustifyRight');
this.parent().parent().icon('alignright');
this.parent().parent().value = 'JustifyRight';
}
}, {
icon: 'aligncenter',
text: 'Align Center',
onclick: function() {
tinyMCE.execCommand('JustifyCenter');
this.parent().parent().icon('aligncenter');
this.parent().parent().value = 'JustifyCenter';
}
}, {
icon: 'alignjustify',
text: 'Justify',
onclick: function() {
tinyMCE.execCommand('JustifyFull');
this.parent().parent().icon('alignjustify');
this.parent().parent().value = 'JustifyFull';
}
}
],
onPostRender: function() {
// Select the first item by default
this.value ='JustifyLeft';
}
});
}
});
Note: If you re-select an alignment option on content that is already aligned that way, TinyMCE toggles the alignment formatting off. This is default TinyMCE behaviour, but you would need to indicate that the section already has that formatting via a toggle state on the button for this to make more sense to the user. This has not been implemented above.

Knockout JS issue with tinymce textarea

Javascript
var tiny_options = {
height: 120,
width: 300,
mode: 'textareas',
theme: 'advanced',
theme_advanced_buttons1: 'bold,italic,underline',
theme_advanced_buttons2: '',
theme_advanced_fonts: 'Arial=arial,helvetica,sans-serif,Courier New=courier new,courier,monospace,Georgia=georgia,times new roman,times,serif,Tahoma=tahoma,arial,helvetica,sans-serif,Times=times new roman,times,serif,Verdana=verdana,arial,helvetica,sans-serif',
theme_advanced_toolbar_location: 'top',
theme_advanced_toolbar_align: 'left'
};
//tinymce.init(tiny_options); // Please, remove comment to activate the tinymce
var initData = function (d) {
this.id = ko.observable(d.id);
this.text = ko.observable(d.text);
};
var viewModel = function () {
var self = this,
data = [{
id: 1,
text: 'some text 1'
}, {
id: 2,
text: 'some text 2'
}];
self.dataSet = ko.observableArray([]);
$.each(data, function (i, d) {
self.dataSet.push(new initData(d));
});
};
var model = new viewModel();
ko.applyBindings(model);
UI
<!-- ko foreach : dataSet -->
<br>
<textarea data-bind="value: text, valueUpdate : 'change'"></textarea>
<br>
<!-- /ko -->
Link to Demo
Above, code is working fine, i.e. model data is updating nicely without tinymce binding, but when I activate the tinymce, view model observable is not updating. I tried this also, but no result.
So, Please help me to configure, how can I update the view model observables using tinymce binding?
Looks like you need a custom binding that will bind up the value and apply the TinyMCE Editor to your <textarea>. The net result looking something like this;
<textarea data-bind="wysiwyg: text"></textarea>
Give the one I've put together on Github a try https://github.com/michaelpapworth/tinymce-knockout-binding
This is a simple custom binding to update observable:
ko.bindingHandlers.richTextEditor = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
window.tinymce.init({
target: element,
skin: "lightgray",
menubar: false,
statusbar: false,
forced_root_block: false,
browser_spellcheck: true,
toolbar: "bold italic underline",
valid_elements: "strong,br,em,span[style|class|id|data],i[class]",
formats: {
bold: { inline: "strong" },
italic: { inline: "em" },
underline: { inline: "span", styles: { "text-decoration": "underline" } }
},
plugins: "paste",
resize: false,
setup: function (editor) {
editor.on("change", function () {
var textInputBinding = allBindings().textInput;
var content = this.getContent();
textInputBinding && textInputBinding(content);
});
}
});
}
};
the binding on textarea should be data-bind="textInput: yourObservable".

Categories

Resources