I'm working on a package that can instantiate multiple RTE's at once. I want to be able to style / control what the RTE can do, so I thought I'd use the tinyMceService to instantiate them.
So, I've created a custom directive that has a textarea as it's template with a unique ID set, then have the following code in my "link" function for the directive:
tinyMceService.configuration().then(function (tinyMceConfig) {
//config value from general tinymce.config file
var validElements = tinyMceConfig.validElements;
//These are absolutely required in order for the macros to render inline
//we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style]";
var invalidElements = tinyMceConfig.inValidElements;
var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
if (plugin.useOnFrontend) {
return plugin.name;
}
}).join(" ");
//var editorConfig = $scope.model.config.editor;
//if (!editorConfig || angular.isString(editorConfig)) {
editorConfig = tinyMceService.defaultPrevalues();
//}
//config value on the data type
var toolbar = editorConfig.toolbar.join(" | ");
var stylesheets = [];
var styleFormats = [];
var await = [];
//queue file loading
await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
//queue rules loading
angular.forEach(editorConfig.stylesheets, function (val, key) {
stylesheets.push("../css/" + val + ".css?" + new Date().getTime());
await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
angular.forEach(rules, function (rule) {
var r = {};
r.title = rule.name;
if (rule.selector[0] == ".") {
r.inline = "span";
r.classes = rule.selector.substring(1);
}
else if (rule.selector[0] == "#") {
r.inline = "span";
r.attributes = { id: rule.selector.substring(1) };
}
else {
r.block = rule.selector;
}
styleFormats.push(r);
});
}));
});
//stores a reference to the editor
var tinyMceEditor = null;
//wait for queue to end
$q.all(await).then(function () {
//create a baseline Config to exten upon
var baseLineConfigObj = {
mode: "exact",
skin: "umbraco",
plugins: plugins,
valid_elements: validElements,
invalid_elements: invalidElements,
extended_valid_elements: extendedValidElements,
menubar: false,
statusbar: false,
height: 200,
width: "100%",
toolbar: toolbar,
content_css: stylesheets.join(','),
relative_urls: false,
style_formats: styleFormats,
};
if (tinyMceConfig.customConfig) {
angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
}
//console.log(baseLineConfigObj);
//set all the things that user configs should not be able to override
baseLineConfigObj.elements = "rte_" + $scope.guid;
baseLineConfigObj.setup = function (editor) {
//set the reference
tinyMceEditor = editor;
//enable browser based spell checking
editor.on('init', function (e) {
editor.getBody().setAttribute('spellcheck', true);
});
//We need to listen on multiple things here because of the nature of tinymce, it doesn't
//fire events when you think!
//The change event doesn't fire when content changes, only when cursor points are changed and undo points
//are created. the blur event doesn't fire if you insert content into the editor with a button and then
//press save.
//We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
//listen to both change and blur and also on our own 'saving' event. I think this will be best because a
//timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
//save before the timeout elapsed.
editor.on('change', function (e) {
angularHelper.safeApply($scope, function () {
$scope.model.value = editor.getContent();
});
});
editor.on('blur', function (e) {
angularHelper.safeApply($scope, function () {
$scope.model.value = editor.getContent();
});
});
//Create the insert media plugin
tinyMceService.createMediaPicker(editor, $scope);
//Create the embedded plugin
tinyMceService.createInsertEmbeddedMedia(editor, $scope);
//Create the insert link plugin
tinyMceService.createLinkPicker(editor, $scope);
//Create the insert macro plugin
tinyMceService.createInsertMacro(editor, $scope);
};
/** Loads in the editor */
function loadTinyMce() {
//we need to add a timeout here, to force a redraw so TinyMCE can find
//the elements needed
$timeout(function () {
tinymce.DOM.events.domLoaded = true;
tinymce.init(baseLineConfigObj);
}, 200, false);
}
loadTinyMce();
//here we declare a special method which will be called whenever the value has changed from the server
//this is instead of doing a watch on the model.value = faster
$scope.model.onValueChanged = function (newVal, oldVal) {
//update the display val again if it has changed from the server;
tinyMceEditor.setContent(newVal, { format: 'raw' });
//we need to manually fire this event since it is only ever fired based on loading from the DOM, this
// is required for our plugins listening to this event to execute
tinyMceEditor.fire('LoadContent', null);
};
//listen for formSubmitting event (the result is callback used to remove the event subscription)
var unsubscribe = $scope.$on("formSubmitting", function () {
//TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
// we do parse it out on the server side but would be nice to do that on the client side before as well.
$scope.model.value = tinyMceEditor.getContent();
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
$scope.$on('$destroy', function () {
unsubscribe();
});
});
});
As you can see, this is pretty much a straight copy from the RTEController at the moment.
When I use this code, it sort of works. The RTE is created, but there is a weird bug happening.
On occasions, when I create the second RTE, the toolbar is attached to the correct container, but the iframe containing the editor is being attached to the iframe container of the first rte. Am I missing something as to why this would be happening? like I say, sometimes it works, and sometimes it doesn't and I can't figure out why.
The textareas definitely have unique id's so that can't be it, but I've poked around a bit and just can't find why it's not working.
It is quite strange, I remember having the same issue and we had to create unique Ids for each textarea as well. Are you sure that $scope.guid refer to the textarea and not to the property editor himself?
After poking around for a while, I think I've found the culprit. The issue is where it loads in the tinymce scripts, basically there are no checks performed to see if the tinymce script has already been loaded (likewise for the css etc) so when you queue up the scripts to load, it just outright reloads them. The problem is, tinymce maintains an internal count for issuing unique ids so my reloading the scripts you are resetting the counter and you get into a situation where it starts to reuse ids and therefor starts attaching things to other containers.
Really, I think we need (my package, and the core) to have the await registration do some checks first, ie:
if (typeof tinymce === "undefined") {
// Add tinymce to await list
}
Or, modify the assetService or such to only loads scripts once (maybe with an override to force it if that's really what you want).
I have had a look around this issue, you solution seems to be ok, but sometime the bug appears again in chrome.
For now, I added a hack in this case which injects tinyMCE through yepnope directly. Not very clean but works fine. Waiting for more info from the core...
tinyMceService weirdness
Hey Guys,
I'm working on a package that can instantiate multiple RTE's at once. I want to be able to style / control what the RTE can do, so I thought I'd use the tinyMceService to instantiate them.
So, I've created a custom directive that has a textarea as it's template with a unique ID set, then have the following code in my "link" function for the directive:
As you can see, this is pretty much a straight copy from the RTEController at the moment.
When I use this code, it sort of works. The RTE is created, but there is a weird bug happening.
On occasions, when I create the second RTE, the toolbar is attached to the correct container, but the iframe containing the editor is being attached to the iframe container of the first rte. Am I missing something as to why this would be happening? like I say, sometimes it works, and sometimes it doesn't and I can't figure out why.
The textareas definitely have unique id's so that can't be it, but I've poked around a bit and just can't find why it's not working.
Any suggestions greatly appreciated.
Matt
Hi Matt,
It is quite strange, I remember having the same issue and we had to create unique Ids for each textarea as well. Are you sure that $scope.guid refer to the textarea and not to the property editor himself?
Hi Antoine,
After poking around for a while, I think I've found the culprit. The issue is where it loads in the tinymce scripts, basically there are no checks performed to see if the tinymce script has already been loaded (likewise for the css etc) so when you queue up the scripts to load, it just outright reloads them. The problem is, tinymce maintains an internal count for issuing unique ids so my reloading the scripts you are resetting the counter and you get into a situation where it starts to reuse ids and therefor starts attaching things to other containers.
Really, I think we need (my package, and the core) to have the await registration do some checks first, ie:
Or, modify the assetService or such to only loads scripts once (maybe with an override to force it if that's really what you want).
I'm off to raise this as an issue :)
Matt
Issue raised here:
http://issues.umbraco.org/issue/U4-4724
Comment author was deleted
This is great, it's giving ideas for Archetype's issues.
I have had a look around this issue, you solution seems to be ok, but sometime the bug appears again in chrome. For now, I added a hack in this case which injects tinyMCE through yepnope directly. Not very clean but works fine. Waiting for more info from the core...
is working on a reply...