I have what I want working in tinyMCE v4.x, but am really styruggling to update the code to work with the latest version (6.x) of this editor.
Basically, my custom button opens a new (popup) window (passing various arguments) in which the user can do various things which produces some HTML, and this is then pasted back into the tinyMCE editor window (at the current insertion point) via editor.insertContent([whatever]);
Here's my v4 code to add the button to the editor toolbar:
editor.addButton('myButton', {
text: 'XYZ',
tooltip: 'Do something',
icon: false,
onclick: function () {
editor.windowManager.open({
title: 'XYZ',
url: 'mypopuppage.html',
width: 800,
height: 600
}, {
arg0: '0',
arg1: '1',
arg2: editor.selection.getContent({ format: 'html' }),
arg3: '',
arg4: ''
});
}
});
});
As I say, this is fine - it works. For v6 I've tried, but can't get it to work. I have not managed to find any code examples online that do anything similar for tinyMCE v6.x, though I'm sure there must be some... this is my effort, but clearly requires more, as it doesn;t work - the button shows, but nothing happens when I click it:
selector: "textarea#txtA",
menubar: false,
statusbar: false,
plugins: "myButton",
setup: (editor) => {
editor.ui.registry.addButton('myButton', {
text: 'XYZ',
onAction: (_) => {
editor.windowManager.open({
title: 'XYZ',
url: 'mypopuppage.html',
width: 800,
height: 600
}, {
arg0: '0',
arg1: '1',
arg2: editor.selection.getContent({ format: 'html' }),
arg3: '',
arg4: ''
});
}
});
},
toolbar1: "myButton"
});
Any help gratefully received...
[edit] OK - getting there; silly me: I should use
editor.windowManager.openUrl({
to open the dialog... now I just have to figure out how to send the HTML back to the editor...
..not sure I'm passing my arguments corectly for this version of tinyMCE... or not reading them correctly on the popup page. In v4x I could use
var args = top.tinymce.activeEditor.windowManager.getParams();
but this doesn't seem to work with v6x... I can use a workaorund of passing the arguments as parameters in the URL I call, but I suspect there is a more robust way of doing it as per v4x - damned if I can find it though... that asied, I'm just about there now :)
Related
I am migrating from v4 to v6. We are using top.tinymce.activeEditor.windowManager.getParams(). However, getParams() has been removed from new version. And I am not able to figure what to use in replacement.
In my example, oninsert() is the custom method in openUrl(). I am not sure if we can use the custom property/methods in v6. It was working fine in v4.
Below is the code snippet
tinymce.init({
selector: '.tinymce-large',
plugins: [
'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'insertdatetime',
'media', 'table', 'template'
],
toolbar: 'undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | ' +
'bullist numlist outdent indent | link image media| code preview ',
menubar: 'file edit insert view',
browser_spellcheck: true,
file_picker_types: 'image',
file_picker_callback: function (callback, value, meta) {
myImagePicker(callback, value, meta);
}
});
function myImagePicker(callback, value, meta) {
tinymce.activeEditor.windowManager.openUrl({
title: 'Image Browser',
url: '/FileManager/Picker?type=' + meta.filetype,
width: window.innerWidth - 200,
height: 600
},
oninsert: function (url, objVals) {
callback(url, objVals);
}
});
}
//myImagePicker() will open up our custom file manager. Through file manager, the user will select an image and through JS, I want to pass the url in oninsert event through below code
top.tinymce.activeEditor.windowManager.getParams().oninsert(url, objVals);
TinyMCE 5 and 6 no longer recommends using the window.top variable to access the original window TinyMCE API and as such getParams has been removed. Instead, it's built around the more modern postMessage API instead.
Now, for this specific case it sounds like a custom message might be what you need. So as it's done via messaging you should be able to do something like this instead:
// TinyMCE window
function myImagePicker(callback, value, meta) {
tinymce.activeEditor.windowManager.openUrl({
title: 'Image Browser',
url: '/FileManager/Picker?type=' + meta.filetype,
width: window.innerWidth - 200,
height: 600,
onMessage: function(api, details) {
if (details.mceAction === 'customInsertImage') {
var data = details.data;
api.close();
callback(data.url, data.objVals);
}
}
});
}
// Filemanager window
window.parent.postMessage({
mceAction: 'customInsertImage',
data: {
url: url,
objVals: objVals
}
}, '*');
There are somethings to remember though, the data must be serializable to be sent via postMessage. Also be sure to use something other than the * wildcard for the message target for security purposes as noted in the documentation.
Also in case you haven't seen it yet, the documentation for URL dialogs can be found here at https://www.tiny.cloud/docs/tinymce/6/urldialog/ and https://www.martyfriedel.com/blog/tinymce-5-url-dialog-component-and-window-messaging is also a good blog post describing how they work in TinyMCE 5.
I have initiated two instances of the TinyMCE editor on the same webpage...shown below.
tinymce.init({
selector: '#post-content',
placeholder: 'Type your post here...',
elementpath: false,
resize: false,
plugins: '',
toolbar: '',
menubar: '',
});
tinymce.init({
selector: '.post-comment-form-input',
placeholder: 'Type your comment here...',
elementpath: false,
resize: false,
height: "100",
plugins: '',
toolbar: '',
menubar: '',
});
When I execute a command on the first instance, it all goes just fine (shown below).
$('btn').click(function() {
tinyMCE.get('post-content').setContent('');
});
However, when I execute a command on the second instance, I get the following "Cannot read property 'setContent' of null".
$('btn').click(function() {
tinyMCE.get('post-comment-form-input').setContent('');
});
Both TinyMCE editors show just fine on the webpage, so I know they are initializing properly. It's just when I try to use a command on that second instance it doesn't work. I have tried including a period in the 2nd function since it's a class, but that doesn't work either.
Thank you.
The tinymce.get() requires you to pass the ID of the HTML element. Based on your init() sections the second selector is using a CLASS which is not valid for the tinymce.get() API.
I'm looking at this example w.r.t creating tinyMCE plugin. What I want to do is to open
a popup, and the content inside the popup is specified programmatically, without having to load a physical page at certain url:
Add an input element of type=file in tinymce container
Basically the author solved the issue about a plugin he was trying to create. I'm trying the same code but the popup is completely empty for me, no errors, any suggestions? Where can I find info about the "body" parameter when calling "windowManager.open", like:
// Open window
editor.windowManager.open({
title: 'Example plugin',
body: [{
type: 'textbox',
name: 'code',
label: 'Video code'
}],
...
Try giving the textbox a size:
// Open window
editor.windowManager.open(
{title: 'Example plugin',
body: [
{ type: 'textbox',
size: 40,
name: 'code',
label: 'Video code'
}
],
.....
I am very confused by the Sencha documentation for ExtJS. The documentation begins with a Getting Started guide which highlights and illustrates the importance on a suitable structure for the classes and source code of your application. But the provided examples then break all the conventions laid down by the Getting Started guide. Instead of code being broken down into appropriate Model, Store, View, etc. class files the examples are provided as a single file with example source code which is not easily re-usable in separate source files.
I started by following the Portal example (http://docs.sencha.com/ext-js/4-1/#!/example/portal/portal.html) as this is the sort of application I want to create. I wanted to enhance the Portal example and add in a screen which would display a grid and use a RESTful web service as the data backend. I have created the backend I just want to create the front-end. So I looked at the RESTful example (http://docs.sencha.com/ext-js/4-1/#!/example/restful/restful.html)
I have tried to copy the RESTful example into the recommended pattern of seperate classes e.g. Model, Store, View:
Model:
Ext.define('MyLodge.model.Member', {
extend: 'Ext.data.Model',
fields: [
{name: 'name', type: 'string'},
{name: 'email', type: 'string'},
{name: 'href', type: 'string'}
]
});
Store:
Ext.require('MyLodge.model.Member');
Ext.define('MyLodge.store.Members', {
autoLoad: true,
autoSync: true,
model: 'MyLodge.model.Member',
proxy: {
type: 'rest',
url: 'http://localhost:8888/rest/memberapi/members' ,
reader: {
type: 'json',
root: 'data'
},
writer: {
type: 'json'
}
},
listeners: {
write: function(store, operation){
var record = operation.getRecords()[0],
name = Ext.String.capitalize(operation.action),
verb;
if (name == 'Destroy' ) {
record = operation.records[0];
verb = 'Destroyed';
} else {
verb = name + 'd';
}
Ext.example.msg(name, Ext.String.format( "{0} member: {1}", verb, record.getId()));
}
}
});
View:
Ext.define('MyLodge.view.content.MemberGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.membergrid',
initComponent: function(){
var store = Ext.create('MyLodge.store.Members' );
Ext.apply( this, {
height: this.height,
store: store,
stripeRows: true,
columnLines: true,
columns: [{
id : 'name',
text : 'Name',
flex: 1,
sortable : true,
dataIndex: 'name'
},{
text : 'E-Mail',
width : 150,
sortable : true,
dataIndex: 'email'
},{
text : 'Href',
width : 200,
sortable : true,
dataIndex: 'href'
}],
dockedItems: [{
xtype: 'toolbar',
items: [{
text: 'Add',
iconCls: 'icon-add',
handler: function(){
// empty record
store.insert(0, new MyLodge.model.Member());
rowEditing.startEdit(0, 0);
}
}, '-', {
itemId: 'delete',
text: 'Delete',
iconCls: 'icon-delete',
disabled: true,
handler: function(){
var selection = grid.getView().getSelectionModel().getSelection()[0];
if (selection) {
store.remove(selection);
}
}
}]
}]
});
this.callParent(arguments);
}
});
But I am not sure where to put the code to control the grid row selection and enable the Delete button:
grid.getSelectionModel().on('selectionchange', function(selModel, selections){
grid.down('#delete').setDisabled(selections.length === 0);
});
Also when I press the Add button I get the following error:
Uncaught TypeError: Object [object Object] has no method 'insert'.
Any help would be appreciated.
You are having scoping issues. Basically the variable store is defined only in the initComponent function and therefore of local function scope.
Your handler function has it's own scope. It is firing in the scope of the toolbar button. So if you say this in the handler it would refer to the button. Hence you can say this.up('panel').store - and that gives you the correct reference to the store backing your grid panel.
Another advice is not to implement everything at once. Write a little bit to see if it works and then add to it little by little.
RE: the docs examples, I agree that it's frustrating, but there's not many options. Having a fully-MVC-style implementation of each example would not only be onerous to produce, but would also probably make point of the example get lost in the structure.
RE: your question about the where to "put" the code to control the grid, I would recommend setting up a controller with listeners for the events on the grid in the control() section. This will let you decouple the handling of the events that are fired by your grid from the view itself.
Just diving into SenchaTouch which seems very promising.
I'm building my first application, a simple login form check source http://pastebin.com/8Zddr9cj
I'm looking for a way to do the following things :
Display 'nice' error message when the login/password is wrong. Can be in red to replace the 'Please enter your credentials); i don't know how to access this property.
If login success, close the form and load the application (probably another js file).
Quite simple, but i'm a newbie to this,
1) Fieldset has a method called setInstructions which you can call to update the instructions. So, you could specify an id configuration in your field set, then use that later on when you want to update the instructions.
...
items: [
{
xtype: 'fieldset',
id: 'fieldset',
title: 'Login',
instructions: 'Please enter your credentials',
defaults: {
required: true,
labelAlign: 'left',
labelWidth: '40%'
},
items: [
{
xtype: 'emailfield',
name : 'email',
label: 'Email',
placeHolder: 'your#email.com',
useClearIcon: true
}, {
xtype: 'passwordfield',
name : 'password',
label: 'Password',
useClearIcon: false
}]
}
],
...
//wherever you want to update the instructions
var fieldset = Ext.getCmp('fieldset');
fieldset.setInstructions('My new instructions!');
2) Here is a simple demo of this:
//create a panel, which is full screen, and will contain your form, and another item
//which you want to show at some point
var wrapper = new Ext.Panel({
fullscreen: true,
layout: 'card',
//my two items
items: [
form,
{
xtype: 'panel',
html: 'my second panel, which is not visible on render.'
}
]
});
//change the active card/item, when you need to
wrapper.setActiveItem(1); //starts at 0
Make sure you remove fullscreen from your form, as it is no longer fullscreen (this wrapper panel is).