How to execute jquery on page update - javascript

I have used jquery multi-select to render my django app muslitiselect in an user friendly way. When My Page First render I have putted script given below on page, this script bind any element having class multiselect to jquery multiselect.
$(function() {
$(".multiselect").multiselect();
}
);
Now I have rendered few divs, and when user click on edit page Query render these divs with bounded forms using.
function inline_modal_form(href, data) {
url = '.';
modal_link = '#' + String(href);
$(modal_link).load(url, data);
}
Now when new form rendered have element having class multiselect, but can not get bind with jquery multiselect. How Can I execute this script when ever my page update.
I have searched for methods like .on(previously .live, .delegate) but could not understood How to execute my script.
Jquery 1.8

There is no plugin delegation, delegation is for event binding.
In your case, you should use load callback function:
function inline_modal_form(href, data) {
url = '.';
modal_link = '#' + String(href);
$(modal_link).load(url, data, function () {
$(modal_link).find('.multiselect').multiselect();
});
}

Instead of calling the plugin directly in the root code, wrap it in a function and call that:
var bindMultiselects = function () {
$(".multiselect").multiselect();
};
$(function() {
bindMultiselects();
// any other page load code, etc.
});
Then, when you load the new content, call that function again:
function inline_modal_form(href, data) {
url = '.';
modal_link = '#' + String(href);
$(modal_link).load(url, data, bindMultiselects);
}
Of course, this only works if the act of initializing the plugin has no side-effects if called twice. It shouldn't, but I can't be certain. If that's the case, then you'll need a better selector than ".multiselect" because you'd need to distinguish only the elements that were just dynamically added, not the ones which have already been initialized with the plugin.

Related

Can I make Javascript always call a method after JQuery Load?

We have started using jquery load in our site to load contents into a div rather than re-loading whole page. However in the complete function we have a method that re-applies various bindings. Is it possible to provide load method with a default complete function? So developers don't have to specify it in the jquery load complete function.
As we currently are providing a lot of duplicate complete functions
E.g.
$('#Target').load(callBackRedirect, function () {
ApplyBindings('#Target');
});
These bindings can't be applied using on and need to be re-applied on page loads. We also do some other work that we want to do on every page load.
The answer is no.
You need the callback because that's what the method calls when the request is done.
This works with on method to, you might be doing something wrong out there in the code.
You could create a helper function for this.
function loadSomething(targetElement, uri,callback) {
targetElement.load(uri, callback);
}
loadSomething(
$('myElement'),
'mylink.com/content',
function() {
applyBindings($(this));
}
)
Yes. Check out the list of Global AJAX Event Handlers.
e.g.
$(document).ajaxComplete(function() {
alert('Triggered ajaxComplete handler.');
});
That said, you shouldn't need to reapply your bindings after an AJAX call. If you need to do this, you're probably doing something wrong. Check out jQuery.on, which explains how to bind to content which is added dynamically.
Try $.ajaxSetup:
$.ajaxSetup({
complete: function() {
ApplyBindings('#target');
}
});
EDIT
You could also make a named function like:
var ajaxApplyBindings = function() {
ApplyBindings('#Target');
// anything else...
};
And then pass it to load:
$('#Target').load(callBackRedirect, ajaxApplyBindings);

JS for different dynamically loaded content in a fully ajaxed website

This is a completely updated post to explain the problem in a better way with an improved concept an code (based on the answers given here so far)
I try to realize a completely ajaxed website, but I got some problems with multiple bound events.
This is the basic HTML:
<header id="navigation">
<ul>
<li class="red" data-type="cars">Get Cars</li>
<li class="red" data-type="vegetables">Get Vegetables</li>
</ul>
</header>
<div id="anything">
<section id="dymanic-content"></section>
</div>
The navigation is been created dynamically (as the content of #navigation can be replaced with another navigation), so the binding for the nav-elements would look like this:
$('#navigation').off('click', '.red').on('click', '.red', function() {
var type = $(this).attr('data-type');
var data = { 'param': 'content', 'type': type };
ajaxSend(data);
});
The content of the site is also being loaded dynamically. For example there are two different content:
1:
<div id="vegetables">Here are some information about vegetables: <button>Anything</button></div>
2:
<div id="cars"><img src="car.jpg"></div>
While loading the content, I will also load a specific JS-file, which has all the bindings needed, for this type of content. So the loading-script looks like this:
var ajaxSend = function(data) {
$.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
.done(function( json ) {
if (json.logged === false) { login(ajaxSend, data); }
else {
$.getScript( 'js/' + json.file + '.js' )
.done(function( script, textStatus ) {
$('#result').html(json.antwort);
});
}
});
}
As you pass the parameter for the type of results you need (i.e. vegetables or cars), the result will be shown in #result. Also the files cars.js or vegetables.js would be loaded.
So my problem is to avoid multiple event bindings. This is how I'm doing it:
cars.js:
$('#result').off('mouseover', 'img').on('mouseover', 'img', function () {
// do anything
});
vegetables.js:
$('#result').off('click', 'button').on('click', 'button', function () {
// do anything
});
Is this the proper way? I think it is just a workaround to use off(). So I would appreciate any improvements!
Furthermore I don't know if there is a problem, if the user clicks on the navigation multiple times: In that case the js-files are loaded multiple times, aren't they? So are there multiple bindings with that concept?
When you refer to a a fully ajaxed website, I think a SPA -- Single Page Application.
The distinction may be semantics, but AJAX implies DOM manipulation, while SPA implies Templating and Navigation.
HTML templates are loaded when your page is loaded. Each template maps to particular navigation route. The major changes are NOT with event mapping, but with which Template is shown, and whether new data has been loaded.
See my example AngularJS SPA Plunkr
AngularJS routing looks like this:
scotchApp.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl: 'pages/home.html',
controller: 'mainController'
})
// route for the cars page
.when('/cars', {
templateUrl: 'pages/Cars.html',
controller: 'CarsController'
})
// route for the vegetables page
.when('/vegetables', {
templateUrl: 'pages/Vegetables.html',
controller: 'VegetablesController'
});
});
So each route has a corresponding HTML Template and Controller (where call back functions are defined).
For CDN purposes, templates can be passed back as JSON
// route for the vegetables page
.when('/vegetables', {
template: '<div class="jumbotron text-center"><div class="row"><h3>Cars Page</h3>Available Cars: <a class="btn btn-primary" ng-click='LoadCars()'>LoadCars</a></div><div class="col-sm-4"><a class="btn btn-default" ng-click="sort='name'"> Make/Model</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='year'"> Year</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='price'"> Price</a></div><div class="row" ng-repeat="car in cars | orderBy:sort"><div class="row"></div><div class="col-sm-4">{{ car.name }}</div><div class="col-sm-2">{{ car.year }}</div><div class="col-sm-2">${{ car.price }}</div></div></div>',
controller: 'VegetablesController'
});
In "templated" applications, HTML of each type is loaded once.
Events and controls are bound once.
The incremental changes are JSON being passed back and forth. Your end points are not responsible for rendering HTML. They can be restful and there is a clear Separation of Concerns.
You can create templated SPA applications with AngularJS, Ember, Backbone, JQuery, and more.
cars.js:
$('#result').off('mouseover', 'img').on('mouseover', 'img', function () {
// do anything
});
vegetabels.js:
$('#result').off('click', 'button').on('click', 'button', function () {
// do anything
});
I am not sure, but, if user first click cars type on nav, then the ('mouseover', 'img') listeners deregister and then register again, right?
Then when user click vegetables type on nav ('click', 'button') - deregistered (but!!! 'mouseover', 'img' - are kept!!! ), and if then user clicks some type nav which script have no ('mouseover', 'img') listener but content have img - then there illegal listener for content occurs (from pre previous action).
So, you need to clear all registered to #result listeners BEFORE start loading new content and script, maybe:
var ajaxSend = function(data) {
$.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
.done(function( json ) {
if (json.logged === false) { login(ajaxSend, data); }
else {
$('#result').off();
$.getScript( 'js/' + json.file + '.js' )
.done(function( script, textStatus ) {
$('#result').html(json.antwort);
});
}
});
}
or
cars.js:
$('#result').off().on('mouseover', 'img', function () {
// do anything
});
vegetabels.js:
$('#result').off().on('click', 'button', function () {
// do anything
});
Edit: About loading scripts multiple times. I didn't find clear answer, and I think it is browser depend and jquery implementation and it is possible that each time new script are created new script will be created even if it was created earlier, so there could be 2 disadvantages:
repeated loads of same script form server, if not browser nor jquery didn't cache it
flooding DOM anp browser's interpreter by scripts
BUT, depending on JQuery documentation.
Caching Responses
By default, $.getScript() sets the cache setting to false. This appends a timestamped query parameter to the request URL to ensure that the browser downloads the script each time it is requested. You can override this feature by setting the cache property globally using $.ajaxSetup()
$.ajaxSetup({ cache: true });
You may rely on JQuery cache (there is an option to cache only scripts), or implement your own, for ex.:
var scriptCache = [];
function loadScript(scriptName, cb){
if (scriptCache[scriptName]) {
jQuery.globalEval(scriptCache[scriptName]);
cb();
} else {
$.getScript( scriptName )
.done(function( script, textStatus ) {
scriptCache[scriptName] = script;
//$('#result').html(json.antwort);
cb();
});
}
}
First, I suggest you to pick a framework like AngularJS, as others have proposed.
But, aside of that, you could also consider using namespaces:
cars.js:
$('#result').off('mouseover.cars', 'img').on('mouseover.cars', 'img', function () {
// do anything
});
vegetables.js:
$('#result').off('click.vegetables', 'button').on('click.vegetables', 'button', function () {
// do anything
});
It would be an improvement (and a bit less workaround), because:
(It would do the work) without disturbing other click event handlers
attached to the elements.
-- .on() documentation
You could create a function that takes the name of the page to load and use a single function for loading the pages. Then have the callback function load a javascript file (with a common init function) of the same name. Like:
function loadPage( pageName ) {
$('#dymanic-content').load( pageName +'.php', function() {
$.getScript( pageName +'.js', function() {
init();
});
});
}
Or you can pass the callback function name to the function.
function loadPage( pageName, cb ) {
$('#dymanic-content').load( pageName +'.php', function() {
$.getScript( pageName +'.js', function() {
cb();
});
});
}
You could do this with promises instead of call backs as well.
If you going the AJAX way of the web, consider using PJAX. It is a battle tested library for creating AJAX websites, and is in use on github.
Complete example with PJAX below:
HTML:
data-js attribute will be used to run our function, once the loading of scripts is complete. This needs to be different for each page.
data-url-js attribute contains the list of JS scripts to load.
<div id="content" data-js="vegetablesAndCars" data-urljs="['/js/library1.js','/js/vegetablesAndCars.js']">
<ul class="navigation">
<li>Link 1</li>
<li>Link 2</li>
</ul>
<div id="vegetables">
</div>
<div id="cars">
</div>
</div>
Template: All your pages must have as container the #content div, with the two attribute mentioned above.
JS:
App.js - This file needs to be included with every page.
/*
* PJAX Global Defaults
*/
$.pjax.defaults.timeout = 30000;
$.pjax.defaults.container = "#content";
/*
* Loads JS scripts for each page
*/
function loadScript(scriptName, callback) {
var body = document.getElementsByTagName('body')[0];
$.each(scriptArray,function(key,scripturl){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scripturl;
// fire the loading
body.appendChild(script);
});
}
/*
* Execute JS script for current Page
*/
function executePageScript()
{
//Check if we have a script to execute
if(typeof document.getElementById('content').getAttribute('data-js') !== null)
{
var functionName = document.getElementById('content').getAttribute('data-js').toString();
if(typeof window[functionName] === "undefined")
{
var jsUrl = document.getElementById('content').getAttribute('data-url-js').toString();
if(typeof jsUrl !== "undefined")
{
jsLoader(JSON.parse(jsUrl));
}
else
{
console.log('Js Url not set');
}
}
else
{
//If script is already loaded, just execute the script
window[functionName]();
}
}
}
$(function(){
/*
* PJAX events
*/
$(document).on('pjax:success, pjax:end',function(){
//After Successful loading
//Execute Javascript
executePageScript();
}).on('pjax:beforeSend',function(){
//Before HTML replace. You might want to show a little spinning loader to your users here.
console.log('We are loading our stuff through pjax');
});
});
vegetableAndCars.js - This is your page specific js file. All your page-specific js scripts will have to follow this template.
/*
* Name: vegetablesAndCars Script
* Description: Self-executing function on document load.
*/
(window.vegetablesAndCars = function() {
$('#cars').on('click',function(){
console.log('Vegetables and cars dont mix');
});
$('.navigation a').on('click',function() {
//Loads our page through pjax, i mean, ajax.
$.pjax({url:$(this).attr('href')});
});
})();
More explanation:
A function name has been attached to the window global js namespace, so that the function can be re-executed without reloading the scripts. As you have figured out, this function name has to be unique.
The function is self executable, so that it will execute itself if the user reaches the page without the use of AJAX (i.e goes straight to the page URL).
You might be asking, what about all those bindings that i have on my HTML elements? Well, once the elements are destroyed/replaced, the code bindings to them will be garbage collected by the browser. So your memory usage won't spike off the roofs.
The above pattern for implementing an ajax based website, is currently in production at one of my client's place. So it has been very much tested for all scenarios.
When you are doing $('#navigation').on('some-event', '.red',function(){});
You bind event to the #navigation element (you can see this with $('#navigation').data('events')), but not to the .red-element which is inside that's why when you load new elements with new js-logic you are getting new and old bindings.
If this is possible in your case just use straight binding like $('#navigation .red').some-event(function(){}); for all events which should be removed/reloaded together with elements.
For the most part, everything that you can probably imagine to do in web development, has already been done. You just need to find it and get it to work with your environment. There are a number of issues with your code but there is something else that is bothering me more - why is nobody referring to angularJS or requireJS? There are great benefits to using such frameworks and libraries, which include
Thousands of tutorials all over the place
Thousands and thousands of questions on SO
They (mostly) have amazing plugins which are just ready to go
They probably have wider functionality as compared to your implementations
And also here are the benefits of using your own code
You are the only one who understands it.
Anything?
My point here is that you should use what others have already built, which in 99% of the cases is completely FREE.
Additionally using frameworks like angular you will eventually end up having much cleaner and maintainable code.
With the .off(...).on(...) approach you guarantee that events will be clear before a new bind in case you have multiple .js files binding to the same event (ie: both cars and vegetables have a button click with different logic).
However, if this is not the case, you can use class filters to detect which element already have the event bounded:
$('#result:not(.click-bound)').addClass('click-bound').on('click', 'button', function() {
// your stuff in here
});
That way the selector will bind events only to the elements that aren't already decorated with the class click-bound.

What is the preferred pattern for re-binding jQuery-style UI interfaces after AJAX load?

This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here

$.getJSON trigger select change event

I'm trying to add categories to a drop down list using jQuery Ajax. When an option is selected I would like to load the subcategories.
The problem that I'm facing is that the addition of options to the drop down list by the Ajax function seems to trigger the change event as well. How can I avoid this or rewrite my code better in order to avoid this behavior?
Here's my code:
categoryHelper.loadLevel1 = function () {
// The control that needs to be filled with categories
var $control = $("#select1");
// This function runs $.getJSON() and fills the drop down list
// with option elements
fillDropDown(url, null, $control);
// When I select a value I want to run the function loadLevel2 which
// takes the currently selected value from level 1 as parameter
$control.change(categoryHelper.loadLevel2($control.val()));
};
Code for the fillDropDown:
function fillDropDown(url, parameters, dropdown) {
/// all data has been loaded</param>
$.getJSON(url, parameters, function (data) {
$(dropdown).empty();
$(dropdown).append(createOption("", ""));
$(data).each(function () {
$(dropdown).append(createOption(this.value, this.text));
});
});
}
All help is appreciated!
This line:
$control.change(categoryHelper.loadLevel2($control.val()));
will pass the result of calling categoryHelper.loadLevel2($control.val()) to .change(); if that isn't a function, then you're not going to be adding an event handler to the element but instead triggering any event handlers that are already bound. Change it to:
$control.change(function() {categoryHelper.loadLevel2($control.val())});
so that you're actually passing a function to .change(), and it should work.

CKEditor instance already exists

I am using jquery dialogs to present forms (fetched via AJAX). On some forms I am using a CKEditor for the textareas. The editor displays fine on the first load.
When the user cancels the dialog, I am removing the contents so that they are loaded fresh on a later request. The issue is, once the dialog is reloaded, the CKEditor claims the editor already exists.
uncaught exception: [CKEDITOR.editor] The instance "textarea_name" already exists.
The API includes a method for destroying existing editors, and I have seen people claiming this is a solution:
if (CKEDITOR.instances['textarea_name']) {
CKEDITOR.instances['textarea_name'].destroy();
}
CKEDITOR.replace('textarea_name');
This is not working for me, as I receive a new error instead:
TypeError: Result of expression 'i.contentWindow' [null] is not an object.
This error seems to occur on the "destroy()" rather than the "replace()". Has anyone experienced this and found a different solution?
Is is possible to 're-render' the existing editor, rather than destroying and replacing it?
UPDATED
Here is another question dealing with the same problem, but he has provided a downloadable test case.
For this to work you need to pass boolean parameter true when destroying instance:
var editor = CKEDITOR.instances[name];
if (editor) { editor.destroy(true); }
CKEDITOR.replace(name);
function loadEditor(id)
{
var instance = CKEDITOR.instances[id];
if(instance)
{
CKEDITOR.remove(instance);
}
CKEDITOR.replace(id);
}
I had this problem too, but I solved it in a much simpler way...
I was using the class "ckeditor" in my jQuery script as the selector for which textareas I wanted use for CKEditor. The default ckeditor JS script also uses this class to identify which textareas to use for CKEditor.
This meant there is a conflict between my jQuery script and the default ckeditor script.
I simply changed the class of the textarea and my jQuery script to 'do_ckeditor'(you could use anything except "ckeditor") and it worked.
This is the simplest (and only) solution that worked for me:
if(CKEDITOR.instances[editorName])
delete CKEDITOR.instances[editorName];
CKEDITOR.replace(editorName);
Deleting this entry in the array prevents this form safety check from destroying your application.
destroy() and remove() did not work for me.
Perhaps this will help you out - I've done something similar using jquery, except I'm loading up an unknown number of ckeditor objects. It took my a while to stumble onto this - it's not clear in the documentation.
function loadEditors() {
var $editors = $("textarea.ckeditor");
if ($editors.length) {
$editors.each(function() {
var editorID = $(this).attr("id");
var instance = CKEDITOR.instances[editorID];
if (instance) { instance.destroy(true); }
CKEDITOR.replace(editorID);
});
}
}
And here is what I run to get the content from the editors:
var $editors = $("textarea.ckeditor");
if ($editors.length) {
$editors.each(function() {
var instance = CKEDITOR.instances[$(this).attr("id")];
if (instance) { $(this).val(instance.getData()); }
});
}
UPDATE: I've changed my answer to use the correct method - which is .destroy(). .remove() is meant to be internal, and was improperly documented at one point.
var e= CKEDITOR.instances['sample'];
e.destroy();
e= null;
I've had similar issue where we were making several instances of CKeditor for the content loaded via ajax.
CKEDITOR.remove()
Kept the DOM in the memory and didn't remove all the bindings.
CKEDITOR.instance[instance_id].destroy()
Gave the error i.contentWindow error whenever I create new instance with new data from ajax. But this was only until I figured out that I was destroying the instance after clearing the DOM.
Use destroy() while the instance & it's DOM is present on the page, then it works perfectly fine.
For ajax requests,
for(k in CKEDITOR.instances){
var instance = CKEDITOR.instances[k];
instance.destroy()
}
CKEDITOR.replaceAll();
this snipped removes all instances from document.
Then creates new instances.
The i.contentWindow is null error seems to occur when calling destroy on an editor instance that was tied to a textarea no longer in the DOM.
CKEDITORY.destroy takes a parameter noUpdate.
The APIdoc states:
If the instance is replacing a DOM element, this parameter indicates whether or not to update the element with the instance contents.
So, to avoid the error, either call destroy before removing the textarea element from the DOM, or call destory(true) to avoid trying to update the non-existent DOM element.
if (CKEDITOR.instances['textarea_name']) {
CKEDITOR.instances['textarea_name'].destroy(true);
}
(using version 3.6.2 with jQuery adapter)
This is what worked for me:
for(name in CKEDITOR.instances)
{
CKEDITOR.instances[name].destroy()
}
CKEDITOR.instances = new Array();
I am using this before my calls to create an instance (ones per page load). Not sure how this affects memory handling and what not. This would only work if you wanted to replace all of the instances on a page.
I've prepared my own solution based on all above codes.
$("textarea.ckeditor")
.each(function () {
var editorId = $(this).attr("id");
try {
var instance = CKEDITOR.instances[editorId];
if (instance) { instance.destroy(true); }
}
catch(e) {}
finally {
CKEDITOR.replace(editorId);
}
});
It works perfectly for me.
Sometimes after AJAX request there is wrong DOM structure.
For instace:
<div id="result">
<div id="result>
//CONTENT
</div>
</div>
This will cause issue as well, and ckEditor will not work. So make sure that you have correct DOM structure.
i had the same problem with instances, i was looking everywhere and finally this implementation works for me:
//set my instance id on a variable
myinstance = CKEDITOR.instances['info'];
//check if my instance already exist
if (myinstance) {
CKEDITOR.remove(info)
}
//call ckeditor again
$('#info').ckeditor({
toolbar: 'Basic',
entities: false,
basicEntities: false
});
You can remove any ckeditor instance by remove method of ckeditor. Instance will be id or name of the textarea.
if (CKEDITOR.instances[instance_name]) {
CKEDITOR.remove(CKEDITOR.instances[instance_name]);
}
Indeed, removing the ".ckeditor" class from your code solves the issue. Most of us followed the jQuery integration example from the ckeditor's documentation:
$('.jquery_ckeditor')
.ckeditor( function() { /* callback code */ }, { skin : 'office2003' } );
and thought "... maybe I can just get rid or the '.jquery_' part".
I've been wasting my time tweaking the callback function (because the {skin:'office2003'} actually worked), while the problem was coming from elsewhere.
I think the documentation should mention that the use of "ckeditor" as a class name is not recommended, because it is a reserved keyword.
Cheers.
I learned that
delete CKEDITOR.instances[editorName];
by itself, actually removed the instance. ALL other methods i have read and seen, including what was found here at stackoverflow from its users, did not work for me.
In my situation, im using an ajax call to pull a copy of the content wrapped around the and 's. The problem happens to be because i am using a jQuery .live event to bind a "Edit this document" link and then applying the ckeditor instance after success of the ajax load. This means, that when i click another link a link with another .live event, i must use the delete CKEDITOR.instances[editorName] as part of my task of clearing the content window (holding the form), then re-fetching content held in the database or other resource.
I hade the same problem with a jQuery Dialog.
Why destroy the instance if you just want to remove previous data ?
function clearEditor(id)
{
var instance = CKEDITOR.instances[id];
if(instance)
{
instance.setData( '' );
}
}
I chose to rename all instances instead of destroy/replace - since sometimes the AJAX loaded instance doesn't really replace the one on the core of the page... keeps more in RAM, but less conflict this way.
if (CKEDITOR && CKEDITOR.instances) {
for (var oldName in CKEDITOR.instances) {
var newName = "ajax"+oldName;
CKEDITOR.instances[newName] = CKEDITOR.instances[oldName];
CKEDITOR.instances[newName].name = newName;
delete CKEDITOR.instances[oldName];
}
}
I am in the situation where I have to controls that spawn dialogs, each of them need to have a ckeditor embedded inside these dialogs. And it just so happens the text areas share the same id. (normally this is very bad practice, but I have 2 jqGrids, one of assigned items and another of unassigned items.) They share almost identical configuration. Thus, I am using common code to configure both.
So, when I load a dialog, for adding rows, or for editing them, from either jqGrid; I must remove all instances of CKEDITOR in all textareas.
$('textarea').each(function()
{
try
{
if(CKEDITOR.instances[$(this)[0].id] != null)
{
CKEDITOR.instances[$(this)[0].id].destroy();
}
}
catch(e)
{
}
});
This will loop over all textareas, and if there is a CKEDITOR instance, then destroy it.
Alternatively if you use pure jQuery:
$('textarea').each(function()
{
try
{
$(this).ckeditorGet().destroy();
}
catch(e)
{
}
});
remove class="ckeditor" , it might have triggered ckeditor initialization
I had the same problem where I was receiving a null reference exception and the word "null" would be displayed in the editor. I tried a handful of solutions, including upgrading the editor to 3.4.1 to no avail.
I ended up having to edit the source. At about line 416 to 426 in _source\plugins\wysiwygarea\plugin.js, there's a snippet like this:
iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + ... + '></iframe>' );
In FF at least, the iframe isn't completely instantiated by the time it's needed. I surrounded the rest of the function after that line with a setTimeout function:
iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + ... + '></iframe>' );
setTimeout(function()
{
// Running inside of Firefox chrome the load event doesn't bubble like in a normal page (#5689)
...
}, 1000);
};
// The script that launches the bootstrap logic on 'domReady', so the document
...
The text renders consistently now in the modal dialogs.
To support dynamic (Ajax) loading of forms (without page refreshes between) which contain textareas with the same (same form is called again) or different ID's (previously unloaded form) and convert them to CKEditor elements I did the following (using the JQuery adapter):
After the page has finished every Ajax call that delivers a textarea to be converted, I make a call to the following function:
setupCKeditor()
This looks like this (it assumes your textareas to be converted to RTE's have class="yourCKClass"):
/* Turns textAreas into TinyMCE Rich Text Editors where
* class: tinymce applied to textarea.
*/
function setupCKeditor(){
// define editor configuration
var config = {skin : 'kama'};
// Remove and recreate any existing CKEditor instances
var count = 0;
if (CKEDITOR.instances !== 'undefined') {
for(var i in CKEDITOR.instances) {
var oEditor = CKEDITOR.instances[i];
var editorName = oEditor.name;
// Get the editor data.
var data = $('#'+editorName).val();
// Check if current instance in loop is the same as the textarea on current page
if ($('textarea.yourCKClass').attr('id') == editorName) {
if(CKEDITOR.instances[editorName]) {
// delete and recreate the editor
delete CKEDITOR.instances[editorName];
$('#'+editorName).ckeditor(function() { },config);
count++;
}
}
}
}
// If no editor's exist in the DOM, create any that are needed.
if (count == 0){
$('textarea.yourCKClass').each( function(index) {
var editorName = $(this).attr('id');
$('#'+editorName).ckeditor(function() { $('#'+editorName).val(data); },config);
});
}
}
I should mention that the line:
$('#'+editorName).ckeditor(function() { $('#'+editorName).val(data); },config);
could (and should) be simply:
$('#'+editorName).ckeditor(function() { },config);
however I found that the editor would often show the correct content for a second after loading and them empty the editor of the desired content. So that line with the callback code forces the CKEditor content to be the same as the originating textarea content. Causes a flicker when used. If you can avoid using it, do so..
I had exactly the same problem like jackboberg. I was using dynamic form loading into jquery dialogs then attaching various widgets (datepickers, ckeditors etc...).
And I tried all solutions noted above, none of them worked for me.
For some reason ckeditor only attached the first time I loaded form, the second time I got exactly the same error message jackboberg did.
I've analyzed my code and discovered that if you attach ckeditor in "mid-air" that is while form content is still not placed into dialog, ckeditor won't properly attach its bindings. That is since ckeditor is attached in "mid-air", second time you attach it in "mid-air"... poof ... an error is thrown since the first instance was not properly removed from DOM.
This was my code that ptoduced the error:
var $content = $(r.content); // jQuery can create DOM nodes from html text gotten from <xhr response> - so called "mid-air" DOM creation
$('.rte-field',$content).ckeditor(function(){});
$content.dialog();
This is the fix that worked:
var $content = $(r.content).dialog(); // first create dialog
$('.rte-field',$content).ckeditor(function(){}); // then attach ckeditor widget
I ran into this exact same thing and the problem was that the wordcount plugin was taking too long to initialize. 30+ seconds. The user would click into the view displaying the ckeditor, then cancel, thereby ajax-loading a new page into the dom. The plugin was complaining because the iframe or whatever contentWindow is pointing to was no longer visible by the time it was ready to add itself to the contentWindow. You can verify this by clicking into your view and then waiting for the Word Count to appear in the bottom right of the editor. If you cancel now, you won't have a problem. If you don't wait for it, you'll get the i.contentWindow is null error. To fix it, just scrap the plugin:
if (CKEDITOR.instances['textarea_name'])
{
CKEDITOR.instances['textarea_name'].destroy();
}
CKEDITOR.replace('textarea_name', { removePlugins: "wordcount" } );
If you need a word counter, register for the paste and keyup events on the editor with a function that counts the words.
For those using the jquery "adapter" and having trouble (as I was), as super hackish yet working solution is to do something like this:
// content editor plugin
(function($){
$.fn.contentEditor = function( params ) {
var xParams = $.extend({}, $.fn.contentEditor.defaultParams, params);
return this.each( function() {
var $editor = $(this);
var $params = $.extend({}, xParams, $editor.data());
// if identifier is set, detect type based on identifier in $editor
if( $params.identifier.type ) {
$params.type = $editor.find($params.identifier.type).val();
}
$editor.data('type', $params.type);
// edit functionality
editButton = $('<button>Edit Content</button>').on('click',function(){
// content container
var $cc = $('#' + $editor.data('type'));
// editor window
var $ew = $('<form class="editorWindow" />');
$ew.appendTo('body');
// editor content
$ec = $('<textarea name="editorContent" />').val($cc.html());
$ec.appendTo($ew);
$ec.ckeditor();
//$ec.ckeditorGet().setMode('source');
$ew.dialog({
"autoOpen": true,
"modal": true,
"draggable": false,
"resizable": false,
"width": 850,
"height": 'auto',
"title": "Content Editor",
"buttons": {
'Save': function() {
$cc.html( $ec.val() );
$ec.ckeditorGet().destroy();
$ew.remove();
},
'Cancel / Close': function() {
$ec.ckeditorGet().destroy();
$ew.remove();
}
},
'close': function() {
$ec.ckeditorGet().destroy();
},
'open': function() {
$ew.find('a.cke_button_source').click();
setTimeout(function(){
$ew.find('a.cke_button_source.cke_on').click();
}, 500);
}
});
return false;
});
editButton.appendTo( $editor );
});
}
// set default option values
$.fn.contentEditor.defaultParams = {
'identifier': {
'type': 'input[name="type"]'
}
};
})(jQuery);
$(function(){
$('form.contentEditor').contentEditor();
});
The key point being this part:
'open': function() {
$ew.find('a.cke_button_source').click();
setTimeout(function(){
$ew.find('a.cke_button_source.cke_on').click();
}, 500);
}
This fixes the problem with the editor text not being visible the next time you open the dialog. I realise this is very hackish, but considering that most of these are going to be used for admin tools, I don't think that's as big a concern as it normally would be.. and this works, so hopefully it will save someone some time ;)
This is the fully working code for jquery .load() api and ckeditor, in my case I am loading a page with ckeditor into div with some jquery effects. I hope it will help you.
$(function() {
runEffect = function(fileload,lessonid,act) {
var selectedEffect = 'drop';
var options = {};
$( "#effect" ).effect( selectedEffect, options, 200, callback(fileload,lessonid,act) );
};
function callback(fileload,lessonid,act) {
setTimeout(function() {//load the page in effect div
$( "#effect" ).load(fileload,{lessonid:lessonid,act:act});
$("#effect").show( "drop",
{direction: "right"}, 200 );
$("#effect").ajaxComplete(function(event, XMLHttpRequest, ajaxOptions) {
loadCKeditor(); //call the function after loading page
});
}, 100 );
};
function loadCKeditor()
{//you need to destroy the instance if already exist
if (CKEDITOR.instances['introduction'])
{
CKEDITOR.instances['introduction'].destroy();
}
CKEDITOR.replace('introduction').getSelection().getSelectedText();
}
});
===================== button for call the function ================================
<input type="button" name="button" id="button" onclick="runEffect('lesson.php','','add')" >
Its pretty simple. In my case, I ran the below jquery method that will destroy ckeditor instances during a page load. This did the trick and resolved the issue -
JQuery method -
function resetCkEditorsOnLoad(){
for(var i in CKEDITOR.instances) {
editor = CKEDITOR.instances[i];
editor.destroy();
editor = null;
}
}
$(function() {
$(".form-button").button();
$(".button").button();
resetCkEditorsOnLoad(); // CALLING THE METHOD DURING THE PAGE LOAD
.... blah.. blah.. blah.... // REST OF YOUR BUSINESS LOGIC GOES HERE
});
That's it. I hope it helps you.
Cheers,
Sirish.
This functions works for me in CKEditor version 4.4.5, it does not have any memory leaks
function CKEditor_Render(CkEditor_id) {
var instance = CKEDITOR.instances[CkEditor_id];
if (CKEDITOR.instances.instance) {
CKEDITOR.instances.instance.destroy();
}
CKEDITOR.replace(CkEditor_id);
}
// call this function as below
var id = 'ckeditor'; // Id of your textarea
CKEditor_Render(id);
CKeditor 4.2.1
There is a lot of answers here but for me I needed something more (bit dirty too so if anyone can improve please do). For me MODALs where my issue.
I was rendering the CKEditor in a modal, using Foundation. Ideally I would have destoryed the editor upon closing, however I didn't want to mess with Foundation.
I called delete, I tried remove and another method but this was what I finally settled with.
I was using textarea's to populate not DIVs.
My Solution
//hard code the DIV removal (due to duplication of CKeditors on page however they didn't work)
$("#cke_myckeditorname").remove();
if (CKEDITOR.instances['myckeditorname']) {
delete CKEDITOR.instances['myckeditorname'];
CKEDITOR.replace('myckeditorname', GetCKEditorSettings());
} else {
CKEDITOR.replace('myckeditorname', GetCKEditorSettings());
}
this was my method to return my specific formatting, which you might not want.
function GetCKEditorSettings()
{
return {
linkShowAdvancedTab: false,
linkShowTargetTab: false,
removePlugins: 'elementspath,magicline',
extraAllowedContent: 'hr blockquote div',
fontSize_sizes: 'small/8px;normal/12px;large/16px;larger/24px;huge/36px;',
toolbar: [
['FontSize'],
['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink'],
['Smiley']
]
};
}
Try this:
for (name in CKEDITOR.instances)
{
CKEDITOR.instances[name].destroy(true);
}

Categories

Resources