I'm using Tinymce (with jQuery) in a project I'm working at; we use a rich text editor for users to input information; however, sometimes when loading the page Firefox and Chrome will detect a 'tinymce is not defined' error (sometimes at different lines of the code), while other times the page will load just fine. What's weird is that it works perfectly with IE.
Here's a bit of the code I'm using:
view.find('textarea.rich-text').each(function () {
$(this).tinymce( /* ...rules... */);
});
And later on
_variable.find("#summary").tinymce().setContent(content);
This line is where the error (sometimes) gets caught. It seems to me that the problem is a loading issue, even though the tinyMCE plugin is initialized about 5000 lines prior this line.
Update: For now I have managed to 'solve' the problem with a setTimeout, but this seems like a really ugly way to do it.
A few points:
You don't mention whether or not the TinyMCE initialization is done within a jQuery ready event function. It should be of course.
You don't need the each loop. You can just say:
$('textarea.rich-text').tinymce({
script_url : '../js/tinymce/jscripts/tiny_mce/tiny_mce.js',
theme : "advanced",
...
});
You don't need the call to find since you are just selecting by id. Just do:
$("#summary").tinymce().setContent(content);
Your real issue is probably that tinymce has not finished initializing itself when you get the error. You see it has to load a script from the configured script_url. That may take a while. Therefore, you have to make use of a callback such as oninit.
If you do not have control over init method of TinyMCE then, you can follow this solution.
jQuery(document).ready(function($) {
function myCustomSetContent( id, content ) {
// Check if TinyMCE is defined or not.
if( typeof tinymce != "undefined" ) {
var editor = tinymce.get( id );
// Check if TinyMCE is initialized properly or not.
if( editor && editor instanceof tinymce.Editor ) {
editor.setContent( text );
editor.save( { no_events: true } );
} else {
// Fallback
// If TinyMCE is not initialized then directly set the value in textarea.
//TinyMCE will take up this value when it gets initialized.
jQuery( '#'+id ).val( text );
}
return true;
}
return false;
}
function myCustomGetContent( id ) {
// Check if TinyMCE is defined or not.
if( typeof tinymce != "undefined" ) {
var editor = tinymce.get( id );
// Check if TinyMCE is initialized properly or not.
if( editor && editor instanceof tinymce.Editor ) {
return editor.getContent();
} else {
// Fallback
// If TinyMCE is not initialized then directly set the value in textarea.
// TinyMCE will take up this value when it gets initialized.
return jQuery( '#'+id ).val();
}
}
return '';
}
$(".class-to-update-content").on("click", function(e) {
myCustomSetContent( "tinymce-editor-id", "New Content in Editor" );
});
$(".class-to-get-content").on("click", function(e) {
$("div.class-to-display-content").html( myCustomGetContent( "tinymce-editor-id" ) );
});
});
Ref : http://blog.incognitech.in/tinymce-undefined-issue/
EDIT: Solution included
Related
The PlainClipboard shown in the doc is very useful when we want Quill to clear all formats when content is pasted (e.g., copied from webpages). But if we register this module as 'modules/clipboard', any formats in the initial editor container will be wiped out as well:
<div id="editor-container">
<ol><li>1</li><li>2</li><li>3</li></ol>
</div>
because the initial content is also processed by the convert() function when the editor is initialized. To workaround this, I used a flag to indicate whether it's editor initialization or normal user pasting:
var quill_initializing = true;
class PlainClipboard extends Clipboard {
convert(html = null) {
if (quill_initializing) {
quill_initializing = false;
return super.convert(html);
} else {
if (typeof html === 'string') {
this.container.innerHTML = html;
}
let text = this.container.innerText;
this.container.innerHTML = '';
return new Delta().insert(text);
}
}
}
This works for Quill 1.3.1 but after I updated to Quill 1.3.2, this approach stops working.
Is there a way to fix this?
I'm working on a CKEditor plugin for annotating text and adding margin comments, but I'd like some of my custom toolbar buttons to be enabled only when the user has already selected a range of text. Whenever the user is typing, or the cursor is at a single-point (instead of a range), the buttons (and their associated commands) should be disabled.
I'm a pretty experienced plugin author, and I've spent a fair amount of time hunting through the core API docs, but I haven't found anything yet that looks like it'll help.
Your case is a little tricky, because selection change event is not well implemented across browsers, FF is the main problem.
In your case you'll going to need check selection changes very frequently, as you're interested in all selection changes therefore CKEditor selectionChange won't fit it.
Fortunately there's a selectionCheck event in editor that fires much more frequently and is implemented for FF.
Solution:
Here you have init method of a plugin that I've mocked to solve your problem. It will disable / enable Source button the way you explained.
I've already added throttling to this function, so that customers with less expansive machine can admire your feature :)
init: function( editor ) {
// Funciton depending on editor selection (taken from the scope) will set the state of our command.
function RefreshState() {
var editable = editor.editable(),
// Command that we want to control.
command = editor.getCommand( 'source' ),
range,
commandState;
if ( !editable ) {
// It might be a case that editable is not yet ready.
return;
}
// We assume only one range.
range = editable.getDocument().getSelection().getRanges()[ 0 ];
// The state we're about to set for the command.
commandState = ( range && !range.collapsed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
command.setState( commandState );
}
// We'll use throttled function calls, because this event can be fired very, very frequently.
var throttledFunction = CKEDITOR.tools.eventsBuffer( 250, RefreshState );
// Now this is the event that detects all the selection changes.
editor.on( 'selectionCheck', throttledFunction.input );
// You'll most likely also want to execute this function as soon as editor is ready.
editor.on( 'instanceReady', function( evt ) {
// Also do state refresh on instanceReady.
RefreshState();
} );
}
If you are working on a plugin, I guess that you are registering commands with the ckeditor.
In that case, you should provide a refresh method, which will be called by the CKEditor to update the state of the button when needed:
Defined by the command definition, a function to determine the command
state. It will be invoked when the editor has its states or selection
changed.
You can see examples of implementation in several of the plugins developed by the CKEditor team. Here is one taken from the source code of the Link plugin:
refresh: function( editor, path ) {
var element = path.lastElement && path.lastElement.getAscendant( 'a', true );
if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) && element.getChildCount() )
this.setState( CKEDITOR.TRISTATE_OFF );
else
this.setState( CKEDITOR.TRISTATE_DISABLED );
}
Here is the code required to answer exactly the question using the refresh method as suggested by Gyum Fox.
I mention that to make it work, contextSensitive has to be set to 1.
editor.addCommand( 'myCommand', {
exec: function( editor ) {
// Custom logic of the command
},
refresh: function( editor ) {
var editable = editor.editable();
range = editable.getDocument().getSelection().getRanges()[ 0 ]; // We assume only one range.
commandState = ( range && !range.collapsed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
this.setState( commandState );
},
contextSensitive: 1
});
EDIT: I noticed some refresh issues on my side. So, because of that, I'd go for the Marek Lewandowski answer.
The Event object in jQuery has this helpful preventDefault() method that prevents the default behaviour, obviously.
This is usually used to prevent click events from performing the browser default behaviour.
It seems like it would also be useful for custom events as well.
The task I'd like to achieve with this behaviour is a separate concern but I will explain it as an example for the behaviour I'm looking for:
I have a simple plugin that creates a popup out of a div. I found it on the internet.
$(selector).pop();
I have hacked it to close when you click on anything but a child of the popup, and to prevent default click behaviour on the clicked element.
function closeInactivePop() {
var foundAny = false;
jQ.each(function (i) {
var $this = $(this);
if ($this.hasClass('active') && ! $this.data('activePop')) {
$this.removeClass('active');
foundAny = true;
}
});
return foundAny;
}
$('body').click(function(){
// If we closed any, cancel the propagation. Otherwise let it be.
if (closeInactivePop()) {
$(document).trigger('jQuery.pop.menuClosed');
return false;
}
});
(Now that I paste it I realise I could have done this a bit better, but that notwithstanding).
Now I have added a new plugin that draws a colour picker inside the popup. Except the DOM that this colour picker creates is not inside the popup; it is only inside it visually. The DOM structure is separate.
In the aforementioned hack I would prefer to in fact fire another event, one whose default behaviour is to close the popup.
function closeInactivePop() {
var foundAny = false;
jQ.each(function (i) {
var $this = $(this);
if ($this.hasClass('active') && ! $this.data('activePop')) {
$(document).trigger('jQuery.pop.menuClosed');
$this.removeClass('active');
foundAny = true;
}
});
return foundAny;
}
$('*').click(function(e) {
var $this = $(this);
// This bit is pseudocode, where the Function is the default behaviour
// for this event.
// It is helpful that $this is actually the clicked element and not the body.
$this.trigger('jQuery.pop.menuBeforeClose', function() {
// if we run default behaviour, try to close the popup, or re-trigger the click.
if (!closeInactivePop()) {
$this.trigger(e);
}
});
});
Then I could later do
$('#colour-picker').bind('jQuery.pop.menuBeforeClose', function(e) {
e.preventDefault();
});
And this would prevent the closeInactivePopup default behaviour running when the target of the original click event was the colour picker or something inside it.
Can I do this somehow, even hackily?
I doubt that there is a native way to do that. However, you can either use "triggerHandler()" instead of "trigger()", which provides the ability to return values from the event handlers. Another relatively simple solution is to pass a custom "event" object that can be used to cancel the planned action:
function Action() {
var status = true;
this.cancel = function() { status = false; };
this.status = function() { return status; };
}
$('button').click(function() {
var action = new Action();
$(this).trigger('foo', [action]);
if (action.status()) {
// ... perform default action
}
});
In the event handler:
$('*').bind('foo', function(event, action) {
action.cancel();
});
I have buttons that trigger jQuery validation. If the validation fails, the button is faded to help draw attention away from the button to the validation messages.
$('#prev,#next').click(function (e)
{
var qform = $('form');
$.validator.unobtrusive.parse(qform);
if (qform.valid())
{
// Do stuff then submit the form
}
else
{
$('#prev').fadeTo(500, 0.6);
$('#next').fadeTo(500, 0.6);
}
That part works fine.
However, I would like to unfade the buttons once the invalid conditions have been cleared.
Is it possible to hook into jQuery Validation to get an appropriate event (without requiring the user to click a button)? How?
Update
Based on #Darin's answer, I have opened the following ticket with the jquery-validation project
https://github.com/jzaefferer/jquery-validation/issues/459
It might sound you strange but the jQuery.validate plugin doesn't have a global success handler. It does have a success handler but this one is invoked per-field basis. Take a look at the following thread which allows you to modify the plugin and add such handler. So here's how the plugin looks after the modification:
numberOfInvalids: function () {
/*
* Modification starts here...
* Nirmal R Poudyal aka nicholasnet
*/
if (this.objectLength(this.invalid) === 0) {
if (this.validTrack === false) {
if (this.settings.validHandler) {
this.settings.validHandler();
}
this.validTrack = true;
} else {
this.validTrack = false;
}
}
//End of modification
return this.objectLength(this.invalid);
},
and now it's trivial in your code to subscribe to this event:
$(function () {
$('form').data('validator').settings.validHandler = function () {
// the form is valid => do your fade ins here
};
});
By the way I see that you are calling the $.validator.unobtrusive.parse(qform); method which might overwrite the validator data attached to the form and kill the validHandler we have subscribed to. In this case after calling the .parse method you might need to reattach the validHandler as well (I haven't tested it but I feel it might be necessary).
I ran into a similar issue. If you are hesitant to change the source as I am, another option is to hook into the jQuery.fn.addClass method. jQuery Validate uses that method to add the class "valid" to the element whenever it is successfully validated.
(function () {
var originalAddClass = jQuery.fn.addClass;
jQuery.fn.addClass = function () {
var result = originalAddClass.apply(this, arguments);
if (arguments[0] == "valid") {
// Check if form is valid, and if it is fade in buttons.
// this contains the element validated.
}
return result;
};
})();
I found a much better solution, but I am not sure if it will work in your scenario because I do not now if the same options are available with the unobtrusive variant. But this is how i did it in the end with the standard variant.
$("#form").validate({
unhighlight: function (element) {
// Check if form is valid, and if it is fade in buttons.
}
});
I'm using this jquery plugin ajaxFileupload in our project. My design is I have a file upload control and set the opacity to 0.01 and then using an anchor link, I trigger the file upload control click event. This works fine until I try to click the anchor link the second time which it doesn't open the file dialog box.
Here is my code.
$(".btnUpload").live("click", function () {
$(".lblUploadError").text("");
$(".fleAttachment").trigger("click");
});
$(".fleAttachment").change(function () {
var reg = /^.*\.(jpg|JPG|gif|GIF|jpeg|JPEG)$/;
var vals = $(this).val(),
val = vals.length ? vals.split("\\").pop() : "";
if (reg.test(vals) == false) {
$(".lblUploadError").text("Invalid Image Type. We only accept .GIF or .JPG");
} else {
ajaxFileUpload();
eval($(".btnRefreshAttachmentList").attr("href"));
}
});
I don't see any error in the console so it makes it difficult to debug it.
Change
$(".fleAttachment").change(function() {
to
$(".fleAttachment").live('change', function() {
$( document ).on( "click", ".fleAttachment", function() {
//--> Logic Here // jQuery 1.7+
});
this.value="";
at the end should work