PDA

View Full Version : TinyMCE form field



daanlib
26 Jun 2011, 12:32 PM
Edit: this first post is no longer of relevance, please see below!

Althought I love Ext JS, the HTLMEditor form field is not very nice, so I took a stab at adapting the existing TinyMCE form control (http://blogs.byte-force.com/xor/tinymce/) (not made by me) for use with Ext JS 4 (and TinyMCE 3.4). Because I can imagine there are more people working on this I have posted my code so far on my website (click (http://daandeschepper.nl/tinymce-field-test)).

Some of the stuff works already (there is a working TinyMCE editor instance visible in an Ext layout, using Ext windows). There are some major bugs though: I get lots of "Uncaught TypeError: Cannot call method 'call' of undefined" errors in the Chrome developers tools and for some reason the width of the editor is way to small. Also labels do not appear to work. I suspect some more testing will show more bugs, but at least I made a start.

I know the code quality is quite bad, but I only focussed on adapting the existing implementation, not on completely rewriting it. Feel free to change stuff and post the changes back in this thread.

I encourage anyone to add to this work to make this a solid ux

daanlib
27 Jun 2011, 6:33 AM
Ok, forget that first post, I completely rewrote the ux to get it working with the fieldLabel. This is a very basic implementation that extends Ext.form.field.TextArea, but does not do any validation, nor does is fire events (I think 90% of the uses of this type of field do not need any validation, nor do they need events to be fired).

The code:


if(window.tinymce) {
Ext.define("EC.common.tinymce.WindowManager", {
extend: tinymce.WindowManager,

constructor: function(cfg) {
tinymce.WindowManager.call(this, cfg.editor);
},

alert: function(txt, cb, s) {
Ext.MessageBox.alert("", txt, function() {
if (!Ext.isEmpty(cb)) {
cb.call(this);
}
}, s);
},

confirm: function(txt, cb, s) {
Ext.MessageBox.confirm("", txt, function(btn) {
if (!Ext.isEmpty(cb)) {
cb.call(this, btn == "yes");
}
}, s);
},

open: function(s, p) {
s = s || {};
p = p || {};

if(!s.type) {
this.bookmark = this.editor.selection.getBookmark('simple');
}

s.width = parseInt(s.width || 320);
s.height = parseInt(s.height || 240) + (tinymce.isIE ? 8 : 0);
s.min_width = parseInt(s.min_width || 150);
s.min_height = parseInt(s.min_height || 100);
s.max_width = parseInt(s.max_width || 2000);
s.max_height = parseInt(s.max_height || 2000);
s.movable = true;
s.resizable = true;
p.mce_width = s.width;
p.mce_height = s.height;
p.mce_inline = true;

this.features = s;
this.params = p;

var win = Ext.create("Ext.window.Window", {
title: s.name,
width: s.width,
height: s.height,
minWidth: s.min_width,
minHeight: s.min_height,
resizable: true,
maximizable: s.maximizable,
minimizable: s.minimizable,
modal: true,
stateful: false,
constrain: true,
layout: "fit",
items: [
Ext.create("Ext.Component", {
autoEl: {
tag: 'iframe',
src: s.url || s.file
},
style : 'border-width: 0px;'
})
]
});

p.mce_window_id = win.getId();

win.show(null, function() {
if (s.left && s.top)
win.setPagePosition(s.left, s.top);
var pos = win.getPosition();
s.left = pos[0];
s.top = pos[1];
this.onOpen.dispatch(this, s, p);
}, this);

return win;
},

close: function(win) {
// Probably not inline
if (!win.tinyMCEPopup || !win.tinyMCEPopup.id) {
tinymce.WindowManager.prototype.close.call(this, win);
return;
}

var w = Ext.getCmp(win.tinyMCEPopup.id);
if (w) {
this.onClose.dispatch(this);
w.close();
}
},

setTitle: function(win, ti) {
if (!win.tinyMCEPopup || !win.tinyMCEPopup.id) {
tinymce.WindowManager.prototype.setTitle.call(this, win, ti);
return;
}

var w = Ext.getCmp(win.tinyMCEPopup.id);
if (w) w.setTitle(ti);
},

resizeBy: function(dw, dh, id) {
var w = Ext.getCmp(id);
if (w) {
var size = w.getSize();
w.setSize(size.width + dw, size.height + dh);
}
},

focus: function(id) {
var w = Ext.getCmp(id);
if (w) w.setActive(true);
}
});

Ext.define("EC.common.tinymce.Editor", {
extend:'Ext.form.field.TextArea',

alias: 'widget.TinyMCEEditor',
alternateClassName: 'Ext.form.TinyMCEEditor',

statics: {
tinyMCEInitialized: false,

//settings for each TinyMCE instance, override before instantiating any TinyMCE editor to change there.
globalSettings: {
accessibility_focus: false,
language: "en",
mode: "none",
skin : "o2k7",
theme: "advanced",
theme_advanced_resizing: false
},

setGlobalSettings: function(settings) {
Ext.apply(this.globalSettings, settings);
}
},

config: {
height: 300
},

constructor: function(config) {
//override default tinyMCESettings
config.tinyMCESettings = Ext.Object.merge(this.statics().globalSettings, config.tinyMCESettings);

this.callParent([config]);

return this;
},

afterRender: function() {
this.callParent(arguments);

this.tinyMCESettings.height = this.height - 11;

this.editor = new tinymce.Editor(this.inputEl.id, this.tinyMCESettings);
this.editor.render();
tinyMCE.add(this.editor);

this.editor.onPostRender.add(Ext.Function.bind(function(editor, controlManager) {
editor.windowManager = Ext.create("EC.common.tinymce.WindowManager", {
editor: this.editor
});
}, this));

window.b = this.editor;
},

getValue: function() {
return this.editor.getContent();
},

setValue: function(val) {
if(this.editor && this.editor.initialized) {
this.editor.setContent(val);
} else {
Ext.Function.createDelayed(function() {
this.setValue(val);
}, 200, this)();
}
},

getSubmitData: function() {
var ret = {};
ret[this.getName()] = this.getValue();
return ret;
},

onDestroy: function() {
this.editor.remove();
this.editor.destroy();
this.callParent(arguments);
}
});
}

(the big if() around it is not needed)

usage:


<!doctype html>
<html>
<head>
<link rel="stylesheet" href="ext-4.0.2a/resources/css/ext-all.css">

<script src="ext-4.0.2a/ext-all-debug.js"></script>
<script src="tinymce/jscripts/tiny_mce/tiny_mce_src.js"></script>

<script src="EC.common.tinymce.WindowManager.js"></script>
<script src="EC.common.tinymce.Editor.js"></script>
<script>
Ext.onReady(function() {
EC.common.tinymce.Editor.setGlobalSettings({

});

var win = Ext.create("Ext.window.Window", {
height: 700,
width: 800,
layout: 'fit',
items: [
{
xtype: "form",
defaultType: 'textfield',
id: "test",
items: [
{
fieldLabel: "hallo test 1",
value: "hoi"
}, {
xtype: "TinyMCEEditor",
fieldLabel: "test",
name: "testName",
tinyMCESettings: {
plugins: "pagebreak,style,layer,table,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,noneditable,visualchars,nonbreaking,xhtmlxtras,template",
theme_advanced_buttons1: "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect",
theme_advanced_buttons2: "formatselect,fontselect,fontsizeselect,code",
//theme_advanced_buttons2: "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
//theme_advanced_buttons3: "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|",
//theme_advanced_buttons4: "insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak",
theme_advanced_toolbar_location: "top",
theme_advanced_toolbar_align: "left",
theme_advanced_statusbar_location: "bottom",
extended_valid_elements: "a[name|href|target|title|onclick],img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]",
template_external_list_url: "example_template_list.js"

},
value: "<h1>Demo</h1><p>Ext.ux.TinyMCE works...</p>",
width: 500,
height: 300
}, {
fieldLabel: "hallo test 2",
value: "hoi"
}
]
}
],
buttons: [{
text: "alert content",
handler: function() {
alert(Ext.getCmp("test").getValues()["testName"]);
}
}]
});

window.a = Ext.getCmp("test");

win.show();

});
</script>
</head>
<body>

</body>
</body>


as you can see, you can set globalsettings by calling EC.common.tinymce.Editor.setGlobalSettings(). These settings will be applied to all tinymce instances and can be overridden by configuration per instance.

Of course any help is still appreciated :)

hexawing
21 Jul 2011, 12:25 AM
=D>Great work! thx!

Dumbledore
24 Jul 2011, 11:19 PM
wow - really nice!

yonas.yanfa
25 Jul 2011, 9:55 PM
daanlib,

Thanks for posting your work here. I'd like to add your work to my site. We're getting ready to offer all sorts of TinyMCE plugins, enhancements, and integrations for our clients.

If this is OK with you, please let me know which license you are using here and the name and email address to use for attribution.

Cheers,
Yonas
--
TinymceSupport.com - Support, Consulting, and Development

scottmartin
29 Jul 2011, 7:40 AM
First off, thanks for working on this control. Extjs.HtmlEditor is quite troublesome, so I look forward to getting this to work.

Several issues I encountered:

Running your sample (and using it in my app): If you cut/paste the text into the editor, the editor becomes unresponsive. I can click buttons, but I cannot select, add any more text.
(not a problem in IE, locks in FF and Chrome)

The control will not follow my anchor settings. If I resize the form, I cannot get your control to resize along with the other controls. I have it set inside a tabPanel and it will not grow with the panel.

Regards,
Scott.

Dumbledore
10 Aug 2011, 9:27 PM
for correct resizing:



[...]
afterRender: function() {
var me = this;
this.callParent(arguments);

this.tinyMCESettings.height = this.height - 11;

this.editor = new tinymce.Editor(this.inputEl.id, this.tinyMCESettings);
this.editor.render();
tinyMCE.add(this.editor);

this.editor.onPostRender.add(Ext.Function.bind(function(editor, controlManager) {
editor.windowManager = Ext.create("Ext.ux.tinymce.WindowManager", {
editor: this.editor
});

this.tableEl = Ext.get(this.editor.id + "_tbl");
this.iframeEl = Ext.get(this.editor.id + "_ifr");

}, this));

window.b = this.editor;

this.on('resize', this.onResize, this);
},

[...]


/**
*
* @param component
* @param adjWidth
* @param adjHeight
*/
onResize : function(component, adjWidth, adjHeight){
var width;
var bodyWidth = component.bodyEl.getWidth();

if (component.iframeEl){
width = bodyWidth - component.iframeEl.getBorderWidth('lr') - 2;
component.iframeEl.setWidth(width);
}
if (component.tableEl){
width = bodyWidth - component.tableEl.getBorderWidth('lr') - 2;
component.tableEl.setWidth(width);
}
}


this will run fine in my case...

Dmoney
4 Oct 2011, 6:31 PM
@Dumbledore

Your resizing solution seems to work. but once I implement the changes the window for the html view no longer works.

I get this error in firebug

v.editor.settings is undefined

Dmoney
7 Oct 2011, 2:14 PM
It was my code not yours causing the problem. Works like a charm now, thanks!

Dmoney
10 Oct 2011, 10:59 PM
This plugin works great, and I'm thrilled to be able to replace the htmlEditor with tinyMCE. but now I'm trying to add some functionality and I've hit a roadblock I'm hoping someone can give me some advice. I've added a button to the editor that opens up a window like this :

var view = Ext.widget('pickerWindow');

when a selection is made in the window I want it to add my variable text to the editor like this:

var ed = tinyMCE.get('content');
ed.selection.setContent(myContent);

But I get the error that ed is undefined. how can I reference the tinyMCE editor from my window?

If I use firebug it shows that the textArea does not have the id I specified "content" instead it has "ext-gen1145"

So the following works but how is that id generated is it subject to change?
var ed = tinyMCE.get('ext-gen1145');
ed.selection.setContent(myContent);

scottmartin
22 Oct 2011, 8:42 AM
You can try to give your item an ID and see if that helps:



{
xtype: "TinyMCEEditor",
itemId: 'MyMCE',
fieldLabel: "Label",
name: "testName",
tinyMCESettings: { .. },
width: 500,
height: 300
},


var ed = form.down('#MyMCE');




Regards,
Scott.