I want to use TinyMCE in a Blazor server side app, but it shows up on page load for a second and then disappears. I blame it on StatehasChanged() thus I have written an interop function that re-initializes TinyMCE and is called in the OnAfterRender() of the page.
This is the JS interop function:
initTinyMce: function (tinyMceID) {
tinymce.init({
selector: 'textarea.tinyMce'
});
return "";
//var editor = tinyMCE.get(tinyMceID);
//if (editor && editor instanceof tinymce.Editor) {
// editor.init();
//}
}
In the OnAfterRender I call it like so:
protected override void OnAfterRender() {
base.OnAfterRender();
string a = jsInterop.InitTinyMce("myTinyMce").Result;
}
But still it disappears a second after it shows itself. How to fix this problem?
I wasn't able to get it to mimic this behaviour so maybe you were using a different version of tinyMce and Blazor. I'm using TinyMce 5.3.1 and .Net Core 3.1. I see you switched to using SyncFusion but maybe this answer will help anyone else who comes along here looking to try to do this in Blazor like I was yesterday.
I think the main thing is disposing of the element when you navigate away and re-initializing when it comes into view / focus / is loaded / etc...
Here's a component that I put together last night that should take care of all the pieces and make it bind-able like a regular <InputTextArea>
I'm new to Blazor and components. This is the second bind-able form control I've made. If anyone has any comments or suggestions on how this could be better please edit or let me know and I can update. Thanks!
In the JS block in _Host.html (i'm using a local version of TinyMCE)
<script src="/js/tinymce/tinymce.min.js"></script>
/Shared/Components/Textarea.razor
<textarea id="#Id" #bind-value="Value" #bind-value:event="onchange" />
/Shared/Components/Textarea.razor.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
namespace Application.Shared.Components
{
public partial class Textarea : ComponentBase, IDisposable
{
[Inject] private IJSRuntime JSRuntime { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public string Id { get; set; } = null;
private DotNetObjectReference<Textarea> _elementRef;
[Parameter] public MenuModeEnum MenuMode { get; set; } = MenuModeEnum.standard;
protected string FieldClass => GivenEditContext?.FieldCssClass(FieldIdentifier) ?? string.Empty;
protected EditContext GivenEditContext { get; set; }
protected FieldIdentifier FieldIdentifier { get; set; }
protected string CurrentValue
{
get => Value;
set
{
var hasChanged = !EqualityComparer<string>.Default.Equals(value, Value);
if (!hasChanged) return;
_ = value;
Value = value;
_ = ValueChanged.InvokeAsync(value);
GivenEditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
this.Id = Id ?? Guid.NewGuid().ToString();
_elementRef = DotNetObjectReference.Create(this);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("TinyMce.init", Id, Enum.GetName(typeof(MenuModeEnum), MenuMode), _elementRef);
}
}
[JSInvokable("textArea_OnChange")]
public void Change(string value)
{
CurrentValue = value;
}
protected virtual void Dispose(bool disposing)
{
JSRuntime.InvokeVoidAsync("TinyMce.dispose", Id, _elementRef);
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
}
internal void DismissInstance()
{
JSRuntime.InvokeVoidAsync("TinyMce.dispose", Id, _elementRef);
StateHasChanged();
}
}
}
/Shared/Components/Enums/MenuModeEnum.cs:
namespace Application.Shared.Components.Enums
{
public enum MenuModeEnum
{
standard,
minimal,
grouped,
bloated
}
}
/wwwroot/js/site.js:
if (!window.TinyMce) {
window.TinyMce = {};
}
window.TinyMce = {
params : {
standard: {
plugins: 'code codesample link image autolink lists media paste table table spellchecker',
toolbar1: 'undo redo | paste | removeformat styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | link image media codesample | table | code | spellchecker',
menubar: false,
branding: false,
toolbar_mode: 'floating'
},
minimal: {
toolbar1: 'bold italic underline',
menubar: false,
branding: false,
toolbar_mode: 'floating'
},
grouped: {
plugins: "emoticons hr image link lists charmap table",
toolbar: "formatgroup paragraphgroup insertgroup",
toolbar_groups: {
formatgroup: {
icon: 'format',
tooltip: 'Formatting',
items: 'bold italic underline strikethrough | forecolor backcolor | superscript subscript | removeformat'
},
paragraphgroup: {
icon: 'paragraph',
tooltip: 'Paragraph format',
items: 'h1 h2 h3 | bullist numlist | alignleft aligncenter alignright alignjustify | indent outdent'
},
insertgroup: {
icon: 'plus',
tooltip: 'Insert',
items: 'link image emoticons charmap hr'
}
},
menubar: false,
branding: false
},
bloated: {
plugins: 'code codesample link image autolink lists media paste table table spellchecker',
toolbar1: 'undo redo | styleselect | forecolor | bold italic underline strikethrough | link image media codesample | table | code | spellchecker',
toolbar2: 'h1 h2 h3 | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent | emoticons charmap hr',
menubar: false,
branding: false,
toolbar_mode: 'floating'
}
},
init: function (id, mode, dotnetHelper) {
var params = window.TinyMce.params[mode];
params.selector = '#' + id;
params.setup = function (editor) {
editor.on('change', function (e) {
console.log($('#' + id).val());
$('#' + id).val(editor.getContent());
$('#' + id).change();
console.log($('#' + id).val());
dotnetHelper.invokeMethodAsync("textArea_OnChange", $('#' + id).val());
});
}
tinymce.init(params);
},
dispose: function (id, dotnetHelper) {
tinymce.remove('#' + id);
}
};
Usage:
<Textarea MenuMode="#MenuModeEnum.minimal" #bind-Value="#SomeObject.Comments" />
Related
I'm trying to integrate the well-known lite-youtube plugin for writing tinymce's rich text.
And I did not succeed in understanding how to integrate the plugin into the system of tinymce, I would appreciate it if someone could help me with this, thanks
This is my code:
<script>
tinymce.init({
selector: '#myTextarea',
plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount image',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table | align lineheight | numlist bullist indent outdent | emoticons charmap | removeformat | image | media',
language: 'he_IL',
image_class_list: [{title: 'Full size', value: 'great_picture_of_the_article'}],
image_dimensions: false,
link_class_list: [{title: 'Formatted link', value: 'link_to_external_site'}],
</script>
Here are the codes I tried to attach:
media_url_resolver: function (data, resolve) {
if (data.url.indexOf('YOUR_SPECIAL_VIDEO_URL') !== -1) {
var embedHtml = '<lite-youtube videoid=' + data.url +
'"></lite-youtube>';
resolve({html: embedHtml});
} else {
resolve({html: ''});
}
},
video_template_callback: function(data) {
return '<lite-youtube videoid="' + data.source1 + '"></lite-youtube>';
},
I am new in Reactjs,working with nextjs and i am trying to integrate Editor
in my page but i am getting following error
"TypeError: editor1 is null"
How can i fix this ? I tried with following code, Where i am wrong ?
const handleSubmit = (e: any) => {
e.preventDefault();
let editor: any = null;
const content = editor.getContent();
const data = {
first: e.target.name.value,
last: e.target.cat_name.value,
content: content, // add the content to the data object
}
};
<Editor
onInit={(evt, ed) => editor = ed} // set the editor reference
initialValue="<p>This is the initial content of the editor.</p>"
init={{
height: 500,
menubar: false,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste code help wordcount'
],
toolbar: 'undo redo | formatselect | ' +
'bold italic backcolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | help',
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
}}
/>
I am trying to open MatDialog from tinymce custom button but that matdialog is not getting initialized until I resize the window. I am aware that it is due to the method is written inside tinymce init json object and hence causing problem but don't know what to do:
in component class, I mentioned it like this:
tinyMceConfig: any = {
menubar: false, plugins: ['advlist autolink lists link image charmap print preview hr anchor pagebreak','searchreplace wordcount visualblocks visualchars code fullscreen','insertdatetime media nonbreaking save table contextmenu directionality','emoticons template paste textcolor colorpicker textpattern'], toolbar1: 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | code | link Upload',
setup: (editor) => {
editor.ui.registry.addButton('Upload', {
icon: 'browse',
onAction: ()=> {
const dialogRef: MatDialogRef<any> = this.dialog.open(UploadmediaComponent, { width: '95vw', disableClose: true, panelClass: 'uploadPanel'});
dialogRef.afterClosed().subscribe(res => {
if (!res) {
return;
}
res.forEach(image => {
console.log(image.url);
editor.insertContent(image.url);
});
});
}
});
}
};
In component:
<editor formControlName="popupText" name="popupText" apiKey="xxxx"
[init]="tinyMceConfig"></editor>
I had borken down this code further and separated setup part in ngOnInit but that made no difference.
Just to wrap the initialization of the dialog with ngZone.run and it worked perfect for me:
onAction: ()=> {
this.ngZone.run(()=> {
const dialogRef: MatDialogRef<any> = this.dialog.open(UploadmediaComponent, {
width: '95vw', disableClose: true, panelClass: 'uploadPanel'});
dialogRef.afterClosed().subscribe(res => {
if (!res) {
return;
}
res.forEach(image => {
editor.insertContent('<img src="' + image.url + '">');
});
});
});
}
I add "file_picker_callback" to my TinyMCE, when I tying word to TinyMCE, function "onChange" is called two time and then, TinyMCE is lost focus. If I want to enter more words, I must focus to TinyMCE again
Anyone can help?
<TinyMCE
content={'' ?description == false: description}
config={{
plugins: 'advlist paste table textcolor image',
toolbar: 'undo redo | bold italic underline | alignleft aligncenter alignright | sizeselect | fontselect | fontsizeselect | forecolor | bullist | image',
forced_root_block: "",
selector: '#ProductDesc',
elementpath: false,
height: "300",
paste_retain_style_properties: "all",
file_browser_callback_types: 'image',
file_picker_callback: function (callback, value, meta) {
if (meta.filetype == 'image') {
var input = document.getElementById('my-file');
input.click();
input.onchange = function () {
var file = input.files[0];
var reader = new FileReader();
reader.onload = function (e) {
callback(e.target.result, {
alt: file.name
});
};
reader.readAsDataURL(file);
};
}
}
}}
onChange={this.onChangeDesc}
/>
I am using TinyMce from within a Windows Form through the Awesomium browser. I created a User Control like this that I am placing on the Windows Form:
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Data
Imports System.Text
Imports System.Windows.Forms
Imports System.IO
Imports Awesomium.Core
Imports Awesomium.Windows
Partial Public Class TinyMCE
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
Public Property HtmlContent() As String
Get
Dim content As String = String.Empty
If Not WebCore.IsInitialized Then
WebCore.Initialize(New WebConfig() With {.LogLevel = LogLevel.Verbose})
Else
Dim html As JSValue = webBrowserControl.ExecuteJavascriptWithResult("GetContent")
content = html.ToString
End If
Return content
End Get
Set(value As String)
If WebCore.IsInitialized Then
Dim objResult As JSObject = webBrowserControl.ExecuteJavascriptWithResult("window")
objResult.InvokeAsync("SetContent", value)
End If
End Set
End Property
Public Sub CreateEditor()
If File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "html\template\tinymce\js\tinymce\tinymce.min.js")) Then
webBrowserControl.Source = New Uri("file:///" & Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "html\template\tinymce.htm").Replace("\"c, "/"c))
Else
MessageBox.Show("Could not find the tinyMCE script directory. " & (Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "html\template\tinymce\js\tinymce\tinymce.min.js")), "Error", MessageBoxButtons.OK, MessageBoxIcon.[Error])
End If
End Sub
End Class
My HTML is fairly simplistic as far as the TinyMCE goes:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>tinyMCE WYSIWYG Editor</title>
</head>
<script type="text/javascript" src="tinymce/js/tinymce/tinymce.min.js"></script>
<!-- Script functions to expose tinyMCE internals that get called from code using InvokeScript method. -->
<script type="text/javascript">
function GetContent() {
return tinyMCE.get('tinyMceEditor').getContent();
}
function SetContent(htmlContent) {
tinyMCE.get('tinyMceEditor').setContent(htmlContent);
}
</script>
<!-- TinyMCE -->
<script type="text/javascript">
tinymce.init({
selector: 'textarea',
menu: {
file: { title: 'File', items: 'newdocument' },
edit: { title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall' },
insert: { title: 'Insert', items: 'link media | template hr' },
view: { title: 'View', items: 'visualaid' },
format: { title: 'Format', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat' },
table: { title: 'Table', items: 'inserttable tableprops deletetable | cell row column' },
tools: { title: 'Tools', items: 'spellchecker code' },
plantwatch: { title: 'My Menu', items: 'dataloop collectorloop processloop historian emailgroup alertgroup menusave' }
},
menubar : 'plantwatch file edit insert view format table tools',
plugins: [
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
'searchreplace wordcount visualblocks visualchars code fullscreen',
'insertdatetime media nonbreaking save table contextmenu directionality',
'emoticons template paste textcolor colorpicker textpattern imagetools codesample toc'
],
toolbar1: 'undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
toolbar2: 'forecolor backcolor emoticons | codesample',
image_advtab: true,
setup: function (editor) {
editor.addMenuItem('dataloop', {
text: 'Data Loop',
onclick: function () {
editor.insertContent('{LOOP:Data}');
}
});
editor.addMenuItem('collectorloop', {
text: 'Collector Loop',
onclick: function () {
editor.insertContent('{LOOP:Collector}');
}
});
editor.addMenuItem('processloop', {
text: 'Process Loop',
onclick: function () {
editor.insertContent('{LOOP:Process}');
}
});
editor.addMenuItem('historian', {
text: 'Historian Server Name',
onclick: function () {
editor.insertContent('{HistorianServerName}');
}
});
editor.addMenuItem('emailgroup', {
text: 'Email Group Name',
onclick: function () {
editor.insertContent('{EmailGroupName}');
}
});
editor.addMenuItem('alertgroup', {
text: 'Alert Group Name',
onclick: function () {
editor.insertContent('{AlertGroupName}');
}
});
}
});
</script>
<!-- /TinyMCE -->
<body>
<form method="post">
<!-- Gets replaced with TinyMCE, remember HTML in a textarea should be encoded -->
<textarea name="tinyMceEditor" cols="1" rows="1" style="width:100%; height: 78%"></textarea>
</form>
</body>
</html>
I then have a button on my Windows Form that calls the following:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MsgBox(tinyMceEditor.HtmlContent)
End Sub
I would have expected that to return the content, but instead it is only returning the full text of the function itself, which is this:
function GetContent() {
return tinyMCE.get('tinyMceEditor').getContent();
}
It is returning that in string form, of course, since that is how my code structured it. I'm not sure why it isn't returning the HTML, though.
Right now you're just referencing the GetContent function so it's returning the definition, you need to call it to get the result.
Change:
Dim html As JSValue = webBrowserControl.ExecuteJavascriptWithResult("GetContent")
To:
Dim html As JSValue = webBrowserControl.ExecuteJavascriptWithResult("GetContent();")