JS for different dynamically loaded content in a fully ajaxed website - javascript

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.

Related

Notify main page of variable changed by external js

I'm attempting to create a modular sign in script for some webpages I'm developing. In short, I load the script on the main page, fire the main signIn function from a button press, and an overlay div is created on the main page which is managed by the external signIn.js. The external js sets some sessionStorage variables that will be utilized in the main page.
The hope for modularity would be to have signIn.js handle the authentication from the database and have the main page do with the process of signing in as needed (in this specific instance, it gives users access to their projects). Ideally, the sign in will not force a refresh of the main page due to other project goals.
The problem I'm encountering, is how do I notify the main page that the user has signed in without destroying any sense of modularity?
On top of other efforts, the most hopeful was attempting to create a custom event on the main page's document using $(document).on('userSignedIn', function() {...}); but signIn.js apparently cannot trigger this event.
Any suggestions for how to accomplish this or am I just going about this entirely wrong?
EDIT:
So, this was definitely a scope related issue I was experiencing. To flesh out the process, if anyone finds it relevant, signIn.js adds an overlay div to mainPage.html. $("#signInContainerDiv").load("signIn.html") is used to load the sign in form into the page. It turns out, when I was trying to reference $(document), it was using signIn.html's document, and not mainPage.html's. Upon that realization, I just created a div (signInNotify) on the mainPage that I bind the event to ($("#signInNotify").on("userSignedIn", function() {...});) and trigger it in signIn.js.
My own inexperience has conquered me, yet again.
jQuery can help you out when it comes to this. Here's an example from the main page for trigger
$( "#foo" ).on( "custom", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );
jQuery Page Reference
Another solution is to use some library like amplify.js, it has publish/subscribe functionality which can be useful for implementing the "observer pattern". You could also implement your own library for that, the code could be something like this:
// the implementation
function Notify () {
this.listeners = {};
}
Notify.prototype.subscribe = function (event, callback, context) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push({ callback: callback, context: context || null});
};
Notify.prototype.publish = function (event/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1);
(this.listeners[event] || []).forEach(function (x) {
x.callback.apply(x.callback.context, args);
});
};
// usage:
// an instance, or can be implemented as a singleton
var global_events = new Notify();
// wherever you want to be notified of login events
global_events.subscribe('login_success', function () {
// do something with the arguments
}, myContext/*optional*/);
// after success login
global_events.publish('login_success', user_credentials, other_data);
// and all subscribers (listeners) will be called after this
I have used that code for similar purposes and also used amplifyjs a couple times, you can read more about Amplify Pub/Sub.

using jQuery .ajax & .text() to load template to DOM

i've inherited some code that I'm not sure I fully understand...
we're building a cordova app that uses micro templates
and this particular piece of code has me slightly confused :
this.loadExternalTemplates = function(templateElements, index, callback) {
var self = this;
if (index >= templateElements.length) {
callback();
} else {
// Load each of the templates into DOM
var $currElement = $(templateElements[index]);
$.ajax({
type : 'GET',
url : $currElement.attr("src"),
dataType : 'text',
timeout : 3000,
success : function(template) {
// Write the template to script tag
$currElement.text(template);
// Iterate / loop to load next template
self.loadExternalTemplates(templateElements, ++index, callback);
},
error : function(error) {
helper.alert("Error occured. Please try again.", error);
}
});
}
};
the loadExternalTemplates() function variable is called by the following:
this.onDeviceReady = function() {
var self = this;
// Load all referenced templates
self.loadExternalTemplates($("script[type='text/html']"), 0, function() {
// Initialize the sammy object
self.initSammyBindings();
});
};
in our main.html we have the script / template tags like:
<script type="text/html" id="about-template" src="templates/about.tmpl.html"></script>
The code works as it is (ie templates are loaded), but i'm not completely following the logic.
I understand the following :
scripts with type="text/html" are ignored by / hidden from the browser since "text/html" doesn't mean anything in a script tag
the call within onDeviceReady() function uses jQuery to get all scripts with type="text/html" and loads them to the loadExternalTemplates() function (as the templateElements variable)
the ajax call uses the src value of each of the script elements to load the contents (html) via GET
the jQuery .txt function is used to add the html to the DOM script tag
What I don't understand is
why the jQuery .text(text) method is used instead of the .html() method ? doesn't .text strip out the html ? (again it's working, so obviously not, but i'm not fully getting why)
wouldn't it be more efficient to use jQuery .load() instead of .ajax() ?
why load all templates to the DOM at the same time ? doesn't that defeat the purpose of having templates ?
why loop that way (surely there's a better for each() or for() loop methodology ) - just bad coding or something i'm missing ?
is there a better (standard?) way of loading templates to the DOM ? on demand ?

Call initialization function per script block

I made a little script that makes my JS initialization in Partial Pages a bit easier.
It simply searches for all data-onload attributes, and executes the function defined there on-load.
There is also some other functionality. So is the data-onload called automatically when that specific partial view is loaded through an AJAX call.
Anyway, the syntax looks like this:
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-onload="partialInit">
function partialInit()
{
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
function otherFunctions()
{
// [...]
}
</script>
The only thing that I still would love to tackle is that right now I need to have a unique functionName for every partial page (otherwise the names will clash when they are both loaded).
So I have manageProfileInit(), editImageInit() etc.
Now is the OCD-devil in me wondering if there is some way to clean this up even further (without too many negative consequences). I would love to have the situation where I can have a simple clean functon init() in any scriptblocks, and have the same funcionality described above.
Of course in the current situation all the functions will override each other. But does anyone know a nice trick or workaround how this could work?
To summarize, I want to make a script that makes sure this will work on every Partial Page, without any clashes.
<div class="some-partial-html-stuff">
<button>[...]</button>
</div>
<script data-autoinit>
function init()
{
// this method is automatically called if the 'data-autoinit' is defined
// executes onload and on-ajax-load stuff for this Partial Page
$('.some-partial-html-stuff button').doSomething();
}
</script>
When I do stuff like this, I call them features. Tags look like this:
<div data-feature="featureName"></div>
Then we get all of the tags that have the data-feature tag and loop over them, creating an array of features the page is going to use:
var featureObjects = $('[data-feature]');
var features = [];
if ( !featureObjects.length ) return false;
for ( var i = 0, j=featureObjects.length; i<j; i++ ) {
var feature = $(featureObjects[i]).data('features');
if ($.inArray(feature, features) == -1){
if (feature !== ""){
features.push(feature);
}
}
};
Now you'll want to load the JS file asychronously and call it's init function once it's loaded:
for (var i=0, j=features.length; i<j; i++){
var feature = features[i];
$.ajax({
url: "path/to/js/" + feature + ".js",
dataType: "script",
async: false,
success: function () {
App.features[feature].init();
},
error: function () {
throw new Error("Could not load script " + script);
}
});
}
The actual modules look like this and attach themselves to App.features for later use:
App.features.featureName = (function(feature){
// INIT FUNCTION
feature.init = function(){
};
return feature;
}(App.features.featureName || {}));
Just remember to make sure App.features is an array before doing all of this, hopefully somewhere towards the top of your main.js file. I keep other functionality such as helpers and utilities in the app, so I usually kick it off with something like:
var App = {
utilities: {},
features: {},
helpers: {},
constants: {}
};
Now you can just tag DOM objects with a data-feature tag and functionality will be added automatically and as-needed, keeping a nice tie between specific JavaScript and specific DOM, but without the need of having to keep the JS inline next to the actual DOM. It also makes those "blurbs" re-usable should they need to be used elsewhere, which lowers maintenance overhead when working on your application.

How to unload a javascript from an html?

How can I unload a JavaScript resource with all its defined objects from the DOM?
Im developing a simple framework that enables to load html fragments into a "main" html. Each fragment is self contained and may include references to additional JS and CSS files. The JS and CSS resources are parsed and dynamically added to the html. When the fragment is removed/replaced from the DOM I want to remove its JS and CSS.
If I remove the script element in the example below, the functions defined in page1.js are still available.
<html>
<head>
<script src="page1.js" type="text/javascript"></script>
</head>
<body>
...
Is there a way to unload page1.js objects from the DOM?
========= The test code I use =======
I tried the advice i got in the comments below; to delete the added objects using a cleanup function - but even this fails. The sources i used for testing:
<html>
<head>
<script type="text/javascript">
function loadJSFile(){
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", "simple.js");
var head = document.getElementsByTagName("head")[0];
head.appendChild(scriptTag);
}
function unloadJSFile(){
delete window.foo;
delete window.cleanup;
alert("cleanedup. typeof window.foo is " + (typeof window.foo));
}
</script>
</head>
<body>
Hello JavaScript Delete
<br/>
<button onclick="loadJSFile();">Click to load JS</button>
<br/>
<button onclick="foo();">call foo()</button>
<br/>
<button onclick="unloadJSFile();">Click to unload JS</button>
</body>
</html>
simple.js source:
var foo = function(){
alert("hello from foo");
}
This cannot be done.
When a script is executed, function definitions are added to the global window object. There may be debugging symbols attached to the function that indicate where the function came from, but this information is not available to scripts.
About the only way you could achieve something like this would be to create a pseudo-namespace in the script and then throw away that whole namespace when you are done with it. However, this question hints to me that you are trying to do something the wrong way. Perhaps elaborating on your scenario would help us provide alternate solutions.
No, that is not possible. You could build a simple cleanup function that removes all variables that were defined in that file:
var foo = 'bar';
var cleanup = function () {
delete window.foo;
delete window.cleanup;
};
// unload all resources
cleanup();
Another approach would be to use a modular object structure for your fragments, that clean up after themselves. That involves a slightly higher overhead but is probably worth it, as it makes the code much easier to read and maintain:
// creates the object using the second parameter as prototype.
// .create() is used as constructor
GlobalModuleHandlerThingy.addModule('my_module', {
create: function () {
this.foo = 'bar';
return this;
},
foo: null,
destroy: function () {
// unload events, etc.
}
});
GlobalModuleHandlerThingy.getModule('my_module').foo; // => bar
GlobalModuleHandlerThingy.unloadModule('my_module'); // calls .destroy() on the module and removes it.
perhaps you need to consider conditionally loading it rather than conditionally unloading it...
you can make them = null
function fnc1 (){
}
window.fnc1 = null
//or
window["fnc1"] = null
If you need to unload a specific object, it's fairly easy: just set it to {}
ie: myobj = {};
So if you know what objects are created in a particular include, it won't be hard at all.
On the other hand, if you don't know what objects are created in a particular include, there isn't a mechansim to find out - you can't ask Javascript to tell you what was defined in a particular include.
However, I would say that if you don't know what objects are being loaded in a particular javascript file, you're probably not doing yourself any favours in loading it (you should always have a reasonable idea what code does in your site), or in trying to unload it manually (if you don't know what it does, that implies its a third party include, which means that unsetting it manually is likely to break things).
Was researching for something like that myself and thought I'll post my findings
Wrap your stuff in a global namespace in js file so it can be removed easily, ie
var stuff = { blabla: 1, method: function(){} };
When you need to get rid of it, simply set stuff = {}, or null even
Remove script tag from page
*** If you use requirejs - require js remove definition to force reload
Note: as long as you don't reference modules inside the namespace from anywhere else everything will be collected by GC and you are good to go.
I figured a trick for this. I was wondering here days finding an answer for this and I just realized a perfect trick to do this without trying to unload the java Script. only you have to do is create a global variable like currentPage in your main page's java script and when you loading the page assign the page name to currentPage . then in every other .js file use $('document').ajaxComplete() insted of $('document').ready() add an if statement as first line inside every $('document').ajaxComplete() function. set it to check currentPage variable equals to a new page name. add all other events inside if statement. i don't know English very well so check my code. and This is my first answer here so sorry if i make some mistakes.
main.html
<body>
<div id='container></div>
<button id="load1">
<button id="load1">
</body>
main.js
var currentPage = "";
$(document).ready(function () {
$('#load1').click(function () {
loadSource('page1', 'body');
});
$('#load2').click(function () {
loadSource('page2', 'body');
});
});
function loadSource( page, element){
currentPage = page;
$('#container').load('views/' + page + '.php', element);
$.getScript('js/' + page + '.js');
$('#css').prop('disabled', true).remove();
$('head').append('<link rel="stylesheet" href="css/' + page + '.css" type="text/css" />');
}
all of my pages scripts and styles are in seperate folders views, js, css.
page1.html
<body>
<button id="test1">
<button id="test2">
</body>
page1.js
$(document).ajaxComplete(function () {
if(currentPage == 'page1'){
/*$('#test1').click(function () {
console.log('page1');
});*/
$('#test2').click(function () {
console.log('page1');
});
}
});
page2.html
<body>
<button id="test1">
<button id="test2">
</body>
page2.js
$(document).ajaxComplete(function () {
if(currentPage == 'page2'){
$('#test1').click(function () {
console.log('page2');
});
/*$('#test2').click(function () {
console.log('page2');
});*/
}
});
i commented one button in each script to check if that button still has old script's affect.

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