Call initialization function per script block - javascript

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.

Related

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.

provide jquery script that won't interrupt client's page

I've got a script started that I will need to offer my customers. The idea is that they drop a line of code in their web page and my script does what it needs for them. (in this case, all it is doing is loading content into a div; an image with a link)
so..the customer drops in a <div id="name_of_div"></div> and a script tag pointing to this:
if (!window.jQuery) {
var jq = document.createElement('script'); jq.type = 'text/javascript';
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
}
if ($('#name_of_div').length) {
$('#name_of_div').load('[content url]');
};
}
I'm pretty sure this roughly works how I'd like, however, I've been reading some things about "never do things globally" and something about putting this stuff inside a function so it doesn't encroach on the page's DOM.. do I have that right?
I'm thinking I can enclose in a function to guarantee my own scope...but what other steps should I take to make sure my script, when included into someone else's page, doesn't block or mess with anything already existing?
Secondly, is it better to use jquery.load() or a simple iframe?
thanks
Putting the code into a function is one step toward isolating it. Putting it into an anonymous and immediately-invoked function expression is the next step. Consider this:
(function () {
if (!window.jQuery) {
var jq = document.createElement('script'); jq.type = 'text/javascript';
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
}
if ($('#name_of_div').length) {
$('#name_of_div').load('[content url]');
}
})();
Nothing outside of that function can see its internals, so it doesn't pollute global scope. (Just be sure not to, for example, create values on the window object within that function.) The function is created and immediately executed (note the parentheses at the end), allowing it to just do its thing and be done.
If there are further interactions that would need to take place, such as exposing an API for your users, then you can return the API object from that function. Something like this:
var yourAPI = (function () {
var someAPIFunction = function () {
// some piece of functionality a user may call later
};
return {
someAPIFunction = someAPIFunction
};
})();
Now the user can create that yourAPI object and put it in whatever scope he would like. It exposes only what you want it to expose, and the rest of your internal work is simply performed and completed that one time when the page loads.

Javascript: add functions (with parameters) to array and execute them when onload event fires

I had to manage an array of functions (with parameters), and execute them when onload event fires.
I know I could use jQuery's $(window).load(), but we all know that to reduce page loading time, every js script (jquery, jquery plugins, ...) must be inserted at page bottom, just before </body>.
Therefore the only js I'm loading in <head> is this:
var fn_chain = [];
function addFn2Load(fn) {
if(typeof fn != 'function')return;
fn_chain.push(fn);
}
function doLoad() {
for(var i=0,iL=fn_chain.length;i<iL;i++) {
fn_chain[i]();
}
}
if (window.addEventListener) {
window.addEventListener("load", doLoad, false);
} else if (window.attachEvent) {
window.attachEvent("onload", doLoad);
} else if (window.onLoad) {
window.onload = doLoad;
}
Then I can push functions in my array from everywhere in the page, without the need of loading jQuery first. I just write
addFn2Load(function() {
foo(arg1, arg2, argN);
bar(arg1, arg2);
$("#myElementId").myCoolPlugin(); // I can use $ even if jQuery is not loaded yet
});
My solution simply works... Could some javascript guru tell me if I'm doing it right? Is it improvable?
The window.load events fire AFTER the jQuery.ready event. See this other question. So the answer is no, you're not doing this quite right. The only reason you're able to use $ is becuase jQuery did already load.
As #Kevin B mentioned, if you have code that does not require for the document to be ready, you should separate those and run those directly (don't use window.onLoad or jQuery.ready).
<script>
/* Do all your non jQuery stuff here, don't bind to any event handlers. */
</script>
<script src="http://code.jquery.com/jquery-1.11.0.min.js" />
<script>
jQuery(document).ready(function($){
/* do your jQuery stuff here */
});
</script>
<script src="js/my_jq_plugins_minified.js" />
If you want a better way to execute callback functions with arbitrary arguments, I would use:
<script>
function _trigger_callbacks(callbacks) {
var i;
for(i = 0; i < callbacks.length; i++) {
callbacks[i][0].apply(this, callbacks[i][1]);
}
}
// Prepare an array where each entry in this array is an array. For each array:
// - the first entry should be a function
// - the second entry should an array of arguments
fxns = [
[function(a){ alert("Do " + a);}, ["Apple"]],
[function(a, b, c){ alert("Minus = " + (a - b - c));}, [1,2,3]],
];
_trigger_callbacks(fxns);
</script>
For more on the Function.apply method, see this other question.
Doing this with javascript makes little to no sense. The purpose of including your external scripts at the bottom of the page is to allow the page to be generated before downloading the javascript which will result in a faster looking page load.
If you are including scripts in the middle of the page that add function calls to an array that gets executed at the bottom, that defeats the whole purpose of including the scripts at the end because those scripts will have to be downloaded before the rest of the page after said scripts are generated.
If you're pages are dynamically generated with a dynamic header and footer, it would be much easier and more efficient to do this work server-side. If they aren't being generated dynamically, then i don't understand why you are including scripts in the middle in the first place.

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.

Trigger $document.ready (so AJAX code I can't modify is executed)

My requirements are the following:
I've got a rich webpage that at a certain moment loads a bunch of HTML in a div, via AJAX.
The HTML I retrieve does have javascript (<script>...</script>)
The retrieved javascript contains $('document').ready( ... ) parts
I can not modify the retrieved javascript; it comes from an external lib
I've got a javascript function that is called when the AJAX is loaded. I'm trying to "trick it" into executing by doing:
function AjaxLoaded() {
$('document').trigger('ready');
}
That doesn't cut it, I'm afraid.
I've seen several responses on Stack Overflow that "evade" this question by changing the code that is returned on the AJAX (make it a function and call it after loading, or just remove the $(document).ready()). I need to stress out that I can't change the retrieved code on this case.
Afer some research i created a way to get it to work.
here is my test that shows it working: http://www.antiyes.com/test/test2.php
here is the relevant code:
<script>
// easy copy of an array
Array.prototype.copy = function() {
return [].concat(this);
};
// this function is added to jQuery, it allows access to the readylist
// it works for jQuery 1.3.2, it might break on future versions
$.getReadyList = function() {
if(this.readyList != null)
this.myreadylist = this.readyList.copy();
return this.myreadylist;
};
$(document).ready(function() {
alert("blah");
});
</script>
<script>
// this should be added last so it gets all the ready event
$(document).ready(function() {
readylist = $.getReadyList();
});
</script>
then in the body I have:
<input type="button" onclick="$(readylist).each(function(){this();});" value="trigger ready" />
basically what i did was add a function to jQuery that copies the readyList before it's cleared out, then it will be available to be used by you.
it looks like the code below doesnt work:
function AjaxLoaded() {
$(document).trigger('ready');
}
drop the quotes around document.
Since the jQuery readyList is not exposed as of version 1.4 (discussed here) the nice solutions above are broken.
A way around this is by creating your own readyList, through overriding the original jQuery-ready method. This needs to be done before other scripts that use the original ready method are loaded. Otherwise just the same code as John/Kikito:
// Overrides jQuery-ready and makes it triggerable with $.triggerReady
// This script needs to be included before other scripts using the jQuery-ready.
// Tested with jQuery 1.7
(function(){
var readyList = [];
// Store a reference to the original ready method.
var originalReadyMethod = jQuery.fn.ready;
// Override jQuery.fn.ready
jQuery.fn.ready = function(){
if(arguments.length && arguments.length > 0 && typeof arguments[0] === 'function') {
readyList.push(arguments[0]);
}
// Execute the original method.
originalReadyMethod.apply( this, arguments );
};
// Used to trigger all ready events
$.triggerReady = function() {
$(readyList).each(function(){this();});
};
})();
I'm not sure whether it is advisable to override the ready method. Feel free to advise me on that. I have not yet found any side effects myself though.
Just in case anyone needs it, I refined John's solution a bit so it could be used directly as an included javascript file.
// jquery_trigger_ready.js
// this function is added to jQuery, it allows access to the readylist
// it works for jQuery 1.3.2, it might break on future versions
$.getReadyList = function() {
if(this.readyList != null) { this.myreadylist = [].concat(this.readyList); }
return this.myreadylist;
};
$(document).ready(function() {
readylist = $.getReadyList();
});
$.triggerReady = function() {
$(readylist).each(function(){this();});
}
Including this file after including jquery allows for triggering ready by invoking $.triggerReady(). Example:
<html>
<head>
<title>trigger ready event</title>
<script src="test2_files/jquery-1.js" type="text/javascript"></script>
<script src="jquery_trigger_ready.js" type="text/javascript"></script>
</head>
<body>
<input onclick="$.triggerReady();" value="trigger ready" type="button">
<script type="text/javascript">
$(document).ready(function(){
alert("blah");
});
</script>
</body>
</html>
By the way, I wanted to make it $(document).triggerReady(). If anyone is willing to share some advice on that, ill be appreciated.
We had the same problem and solved it another way.
Instead of
$(document).ready(function () {
$('.specialClass').click(....
We used :
$(document).bind('ready', function(event) {
$('.specialClass', event.target).click(..
jQuery will trigger a "ready" event on the document as usual. When we load the content of a new div via ajax, we can write:
loadedDiv.trigger('ready')
And have all the initialization performed only on the div, obtaining what expected.
Simone Gianni's Answer I think is the most elegant and clean.
and you can even simplify it to become even more easy to use:
jQuery.fn.loadExtended = function(url,completeCallback){
return this.load(url,function(responseText, textStatus, XMLHttpRequest) {
if (completeCallback !== undefined && completeCallback !== null) {
completeCallback(responseText, textStatus, XMLHttpRequest);
}
$(this).trigger("ready");
});
};
So, now instead of using:
$(".container").load(url,function(responseText, textStatus, XMLHttpRequest) {
$(this).trigger("ready");
});
you can just use:
$(".container").loadExtended("tag_cloud.html");
or:
$(".container").loadExtended("tag_cloud.html",function(){
alert('callback function')
});
This has the advantage of only applying the trigger on the div that's being updated.
If your new loaded HTML contain <script> elements and you try insert it into main HTML with pure JS (element.innerHTML = newHTML), then $(document).ready handlers at newHTML and wrapped functions like (function() { /* some functions */ })(); - will not execute because JQuery unbind 'ready' event after first triggering and you can not trigger it repeatly. PS. But you can use $.holdReady(true) and trigger when need.
So, try insert code with jquery method, $(element).html(newHTML). This solved similar problem for me, seems jquery handle js before inserting. Using this method you also will not see the <script> elements among DOM nodes (at browser's Elements Inspector for ex.)

Categories

Resources