How to insert javascript into an AJAX Django app? - javascript

I have a Django app where the main content changes frequently via AJAX loads. Since I need to run a lot of javascript on the newly loaded content, what I do now is include a javascript block with several document.ready and other functions at the end of the loaded HTML template.
Is this the proper way to load javascript on AJAX loaded content? If I replace the content block with new (AJAX loaded) HTML + scripts, will the old scripts be removed cleanly?
Are there monitoring tools to detect javascript / memory usage? I am running into some page crashes so I suspect there are some leaks occurring.
Thanks!

You should definitely look into using .live() rather than including new javascript blocks within the loaded html content:
http://api.jquery.com/live/
From the docs:
The .live() method is able to affect elements that have not yet been
added to the DOM through the use of event delegation: a handler bound
to an ancestor element is responsible for events that are triggered on
its descendants.

IMHO you should avoid inserting javascript code into AJAX loading page elements. It's not he best way to do that.
Why cant you make a javascript function and activate it after AJAX finishes load? Or put it on click or hover event...
JavaScript Onclick:
<a class="orange"
onclick="return voteClick(this, '{{ item.pk }}', '1', '{% url ajaxvote %}')"
href=''>Button text</a>
and then in your script:
function voteClick(self, pk, multiplier, url){
//some actions function makes
//for e.g. making POST or changing DOM
// you can use vars that are generated by django template system
return false;
};
jQuery Way:
$.post(url, { param1: value1, param2: value2 }, function(data){
//some function actions upon your AJAX complete
//call your function with code here
// you can load some 'data' for this script from django
// and not the whole script
voteClick(self, value1, value2, data);
return false;
} //end function
);//end POST
ou can read more on this: jQuery POST
So main idea here is that you must load script as a static content from your MEDIA_ROOT and pass it some needed parameters. It's hard way to load your scripts with live() functions. You must take care of tracing lot's of functions and params.
Of course if you're using jQuery it's easy to trace code events with it's selectors. But again... Why do you need to load code with AJAX request? It's definitely static file to be used here, as your scripts file...

Related

Is there an event in an MVC 5 View or jquery raised when a partial view has been loaded and its elements are accessible

I have an MVC 5 view that acts as a parent view. Based on certain activities a user performs, I will load a partial view into the parent view. The partial view is loaded as part of a javascript function call. In my javascript call, I am loading my partial view with the content returned in the "data" variable below:
$.get(url, function (data) {
$('#id-ContainerForMainFormPartialView').html(data);
});
The data is written to an HTML div as follows:
<div class="container" id="id-ContainerForMainFormPartialView">
</div>
Immediately after the $.get call I run the following statement to disable a button that is part of the view that has been returned and written to the div:
$('#idAddLineItem').prop("disabled", true);
When the javascript function has completed the button is not disabled. Yet, I am able to disable the button using the same disable statement above using a button. I think that after the $.get invocation has written the partial view it is too soon to try and do something to any elements that are part of the partial view.
Is there an event I can hook into or something that will signal me when the time is right to try and do something to any of the elements of the partial view that has been loaded such as disabling a button which is what I am trying to do? Something like the javascript addEventListener() method that allows you to run code when certain events happen but in this case I need it to fire after a partial-view load is considered completely rendered and ready to use. An example would be greatly appreciated.
Thanks in advance.
Based on blex's statement the solution is as follows:
The correct way to disable a button that's part of a partial view being output:
// Correct Approach
$.get(url, function (data) {
$('#id-ContainerForMainFormPartialView').html(data);
$('#idAddLineItem').prop("disabled", true);
});
The incorrect way to disable a button that's part of a partial view being output:
//Incorrect Approach
$.get(url, function (data) {
$('#id-ContainerForMainFormPartialView').html(data);
});
$('#idAddLineItem').prop("disabled", true);
I originally had the disable statement outside the $.get call which allowed the code to run past it before the view was ready due to the asynchronous nature. Placing it inside the $.get allows it to not run until the partial view is done being output.

Force re-firing of all (scripts / functions) on ajaxed content

tl;dr
I use ajax to fetch new content. The content is fetched and added to the page. However, scripts don't "re-fire" because their calls are outside of the ajaxed div.
The scripts load and fire without any problem on initial page load but not when I add new content via ajax. I get no console errors and there are no issues if I visit the URL directly.
Related:
Forcing Script To Run In AJAX Loaded Page - Relates to one specific script. I want to fix (refire) all scripts including dynamic ones from Cloudflare apps
Using jQuery script with Ajax in Wordpress - Same as above, this relates only to one specific script
ajax loaded content, script is not executing
Intro:
I use Ajaxify WordPress Site(AWS) on a WordPress website.
The plugin lets you select a div by its id and then fetches new content inside that div using ajax
html markup
<html>
<head></head>
<body>
<div id="page">
<header></header>
<main id="ajax"> <!-- This is what I want to ajaxify -->
<div class="container">
main page content
</div>
</main> <!-- end of ajaxfied content -->
<footer></footer>
</div> <!-- #page -->
</body>
</html>
Problem
The plugin works and I get fresh content loaded and styled but there is an issue. Some of my page scripts and function calls are outside of the div that I use ajax on. I have two examples
1- Masonry is loaded and called in the <footer>
<html>
<head></head>
<body>
<div id="page">
<header></header>
<main id="ajax"> <!-- This is what I want to ajaxify -->
<div class="container">
main page content
</div>
</main> <!-- end of ajaxfied content -->
<footer></footer> <!-- Masonry script is loaded and called here -->
</div> <!-- #page -->
</body>
</html>
2- Google maps call is in the <head>
<html>
<head></head> <!-- Google maps is called here -->
<body>
<div id="page">
<header></header>
<main id="ajax"> <!-- This is what I want to ajaxify -->
<div class="container">
main page content
</div>
</main> <!-- end of ajaxfied content -->
<footer></footer>
</div> <!-- #page -->
</body>
</html>
These are just two examples. There are others in other locations. As you can tell, such scripts won't be re-called as the only thing that reloads on the page is <main id="ajax">. While the new content inside <main> is there, some of the scripts required to render it properly are not re-called and so I end up with missing elements / broken layout.
I am not the only one who has faced this problem; a quick look at the plugin's support forum on wordpress.org shows that this issue is common.
Note: I wouldn't try to fix this if the plugin had many other issues. It works for me I just need the scripts to re-fire.
The official response is that it's possible to reload / re-fire scripts by adding the following into the plugin's php files:
$.getScript(rootUrl + 'PATH TO SCRIPT');
Which works. It works well. for example if I add
$.getScript(rootUrl + '/Assets/masonry.js');
Then the masonry function calls get re-fired when the ajaxed content is fetched even if masonry.js is loaded outside of the ajaxed div
I refer you to the plugin's files on github for more clarity on what the fix actually does (I can't make sense of what happens when $.getScript is used)
In summary
The official fix works fine if you have 3-4 scripts that need to be re-fired on ajaxed content.
This does not work for my goal because
it's too rigid and hard-coded
Some of the scripts are added to the page dynamically via Cloudflare apps
A possible solution might involve adding an event mimics the trigger that causes the scripts to fire at the bottom of the ajaxed div
Question:
How do I force all scripts - including dynamically added ones - to re-fire when only a certain part of the page has been reloaded via ajax?
Notes:
I am trying to avoid calling out scripts one by one as that would require knowledge of their calls before hand. I am probably talking way over my head but...
I am trying to mimic the page load and / or document ready events - at which most conditional scripts are fired (correct me if I'm wrong) - at the end of <main> in my html when new ajaxed content is added but without affecting the document when the page is loaded via using the url directly...or any other similar approach.
Just for a bit of context, here is a list of some the event listeners on the page while the plugin is off. I know there are things in there I won't have to trigger. I just added this for reference. Also, please note that this is a sample taken from one of the pages. other pages may differ.
DOMContentLoaded
beforeunload
blur
click
focus
focusin
focusout
hashchange
keydown
keyup
load
message
mousedown
mousemove
mouseout
mouseover
mouseup
mousewheel
orientationchange
readystatechange
resize
scroll
submit
touchscreen
unload
The solution you choose here will have to depend on how the scripts are initialized. There are a couple common possibilities:
The script's actions are evoked immediately upon loading of the script. In this case, the script might look something like this:
(function() {
console.log('Running script...');
})();
The script's actions are evoked in response to some event (such as document ready (JQuery) or window onload (JavaScript) events). In this case, the script might look something like this:
$(window).on('load', function() {
console.log('Running script...');
});
Some options for these two possibilities are considered below.
For scripts that run immediately on loading
One option would be to just remove the <script> elements you want to trigger from the DOM and then reattach them. With JQuery, you could do
$('script').remove().appendTo('html');
Of course, if the above snippet is itself in a <script> tag, then you will create an infinite loop of constantly detaching and re-attaching all the scripts. In addition, there may be some scripts you don't want to re-run. In this case, you can add classes to the scripts to select them either positively or negatively. For instance,
// Positively select scripts with class 'reload-on-ajax'
$('script.reload-on-ajax').remove().appendTo('html');
// Negatively select scripts with class 'no-reload'
$('script').not('no-reload').remove().appendTo('html')
In your case, you would place one of the above snippets in the event handler for AJAX completion. The following example uses a button-click in lieu of an AJAX completion event, but the concept is the same (note that this example doesn't work well within the StackOverflow sandbox, so try loading it as a separate page for the best result):
<html>
<head></head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script class="reload-on-ajax">
console.log('Script after head run.');
</script>
<body>
<button id="reload-scripts-button">Reload Scripts</button>
</body>
<script class="reload-on-ajax">
console.log('Script after body run.');
</script>
<script>
$('#reload-scripts-button').click( () => $('script.reload-on-ajax').remove().appendTo('html') );
</script>
</html>
Note that if the scripts are not inline (e.g. fetched via the src attribute), then they will be reloaded from the server or retrieved from browser cache, depending on the browser and settings. In this case, the best approach is probably to remove all the <script>s that operate on the AJAX-loaded content, and load/run them via something like JQuery's getScript() function from an AJAX completion event handler. This way you will only be loading/running the scripts once at the appropriate time (i.e. after the AJAX content is loaded):
// In AJAX success event handler
$.getScript('script1.js');
$.getScript('script2.js');
A potential problem with both variants of this approach is that asynchronous loading of the script is subject to cross-origin restrictions. So if the scripts are hosted on a different domain and cross-origin requests are not allowed, it won't work.
For scripts that run in response to an event
If the scripts are triggered on window load, then you can just trigger this event:
$(window).trigger('load');
Unfortunately, if the scripts themselves use JQuery's document ready event, then I'm not aware of an easy way to trigger it manually. It's also possible that the scripts run in response to some other event.
Obviously, you can combine the above approaches as necessary. And, as others have mentioned, if there's some initialization functions in the scripts that you could just call, then that's the cleanest way.
If you can identify a global initialising function or code block in your external scripts, you could take a look at the 'ajaxComplete' event. You can put this code in your page head and put the initialising function calls or code blocks inside the ajaxComplete callback.
$(document).ajaxComplete(function(){
module1.init();
$('#my_id').module2_init({
foo : 'bar',
baz : 123
});
});
When the scripts you are talking about don't have such easy-to-use exposed initialising functions, but initialise themselves on scriptload, I think there will be no out of the box method that works for all scripts.
Here's what you can try -
Most of the scripts like masonry or Google Map are set to re-init on window resize. So, if you trigger the resize event after ajax complete, it will help to re-fire those scripts automatically.
Try the following code -
$( document ).ajaxComplete( function() {
$( window ).trigger('resize');
} );
This will force the scripts to re-init once ajax is completed as it will now trigger the resize event after the content is loaded.
Maybe risky, but you should be able to use DOMSubtreeModified on your <main> element for this.
$('#ajaxed').bind('DOMSubtreeModified', function(){
//your reload code
});
Then you should be able to just append all your scripts again in your reload area
var scripts = document.getElementsByTagName('script');
for (var i=0;i<scripts.length;i++){
var src = scripts[i].src;
var sTag = document.createElement('script');
sTag.type = 'text/javascript';
sTag.src = src;
$('head').append(sTag);
}
Another option could be create your own event listener and have the same reload code in it.
$(document).on('ajaxContentLoaded', function(){//same reload code});
Then you could trigger an event in the plugin once the content had been updated.
$(document).trigger('ajaxContentLoaded');
Or possibly a combination of editing the plugin to trigger a listener and adding to your codebase to re-run anything you feel you need to have re-ran off that listener, rather than reload anything.
$(document).on('ajaxContentLoaded', function(){
//Javascript object re-initialization
myObj.init();
//Just a portion of my Javascript?
myObj.somePortion();
});
A solution could be duplicating all the scripts...
$(document).ajaxSuccess((event, xhr, settings) => {
// check if the request is your reload content
if(settings.url !== "myReloadedContentCall") {
return;
}
return window
.setTimeout(rejs, 100)
;
});
function rejs() {
const scripts = $('script[src]');
// or, maybe alls but not child of #ajax
// const scripts = $('*:not(#ajax) script[src]');
Array
.prototype
.forEach
.call(scripts, (script, index) => {
const $script = $(script);
const s = $('<script />', {
src: $script.attr('src')
});
// const s = $script.clone(); // should also work
return s
.insertAfter($script)
.promise()
.then(() => $script.remove()) // finally remove it
;
})
;
}
I had this exact problem when attempting to use ajax to reload a page with browser states and history.js, in wordpress. I enqueued history.js directly, instead of using a plugin to do that for me.
I had tons of JS that needed to be "re-ran" whenever a new page was clicked. To do this, I created a global function in my main javascript file called global_scripts();
Firstly, make sure this JS file is enqueued after everything else, in your footer.php.
That could look something like this:
wp_enqueue_script('ajax_js', 'url/to/file.js', 'google-maps', 1, true);
My javascript that I enqueue is below.
jQuery(document).ready(function($) {
// scripts that need to be called on every page load
window.global_scripts = function(reload) {
// Below are samples of the javascript I ran that I needed to re run.
// This includes lazy image loading, paragraph truncation.
/* I would put your masonry and google maps code in here. */
bLazy = new Blazy({
selector: '.featured-image:not(.no-blazy), .blazy', // all images
offset: 100
});
/* truncate meeeee */
$('.post-wrapper .post-info .dotdotdot').dotdotdot({
watch: 'window'
});
}
// call the method on initial page load (optional)
//global_scripts();
});
I then hooked into the JS that ran whenever a page was loaded with history.js and called global_scripts();
It seems as though the plugin you are using also uses history.js. I haven't tested it with your plugin specifically, but you can try what I posted below (which is what I use in my app).
This would go in the same JS file above.
History.Adapter.bind(window, 'statechange', function() {
global_scripts();
}
My code is a bit more complicated than what is simply pasted above. I actually check the event to determine what content is being loaded, and load specific JS functions based on that. It allows for more control over what gets reloaded.
note: I use window.global_scripts when declaring the function because I have separate JS files that hold this code. You could chose to remove this if they are all in the same.
note2: I realize this answer requires knowing exactly what needs to be reloaded, so it ignores your last note, which asks that this doesn't require such knowledge. It may still help you find your answer though.
Short answer:
It is not possible to this in a generic way. How should your script know which events needs to be fired?
Longer answer:
It is more like a structural question than a programmatic one. Who is responsible for the desired functionality? Lets take masonry as an example:
Masonry.js itself does not listen to any events. You need to create a masonry instance by your own (which is most probably done on domready in your Wordpress plugin). If you enable the resize option it will add a listener to the resize event. But what you actually want is some listener on "DOM content change" (see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver for possible solution). Since masonry.js does not provide such a function you have the following options:
Wait for the implementation (or do it yourself) in masonry.js
Wait for the implementation (or do it yourself) in masonry Wordpress plugin.
Add the functionality somewhere else (your template, your ajax plugin, etc.)
4.
As you can see every option includes some work to be done and this work needs to be done for every script you want to listen to your AJAX invoked DOM changes.

How to make AJAX loaded content affected by parent page?

This problem has bugged me for some time.. If I load something in through AJAX, I have to have the javascript inside the AJAX loaded file (or call an external JS).
How can I make AJAX loaded content be controlled by the master or parent page??
I'd like to take control of all links to pass them through javascript (jquery) and do some fun stuff, but I don't really want to make it an external JS and use $.getScript. I just want my main javascript to be able to work with ajax loaded content.
(Here's an example)
$('a').click(function(e){
e.preventDefault();
// Fun stuff
})
Bind your method, using .on(), to the closest parent container that isn't replaced. For example:
$(function(){
$('body').on('click', 'a', function(e){
e.preventDefault();
// Fun stuff
})
});
See: http://api.jquery.com/on/
Can you show your full example? After you load the content you want trough AJAX you can control the content by Javascript after you have appended it to the DOM.
For example:
success: function(data) {
$('#newContent').hide().html(data)
.find('a').click(function(e) {
e.preventDefault();
});
}

Json request data and jquery prettyphoto binding issue

There is a situation I am having with jQuery. In particular its prettyPhoto library and getJSON function.
I have a page which loads HTML, it calls jQuery and prettyPhoto. There is some inline JS which makes a JSON request further down the page:
It should work like the below:
1) Page loads,
2) Javascript code run,
3) Script runs a jQuery JSON request which returns and has HTML (a-tags and images inside each a-tag) inside,
4) Script then prints the HTML from inside the JSON to the screen,
5) User clicks a-tag/image and it opens in prettyPhoto's iframe popup.
NOTE -> Each a-tag has a prettyPhoto id attached (to load the image in prettyPhoto using iframe popup).
The problem is the images (a-links) do not open with prettyPhoto and I am not sure why. There is no JS Error.
However, it does work if i manually have the HTML (a-links/image) already there (so just loading their HTML from the JSON request seems to make the difference).
Seems by time the JSON request returns (with HTML) prettyphoto already binds to a-tags (or lack off).
Tested so far:
Tried putting JSON request in 'document.ready' and prettyPhoto in 'window.load'. So does JSON requests early and prettyPhoto binds when everything else loads - failed
Tried using jQuery AJAX instead of JSON - failed
Dont need the code especially but having trouble with the logic.
It sounds like the HTML from the JSON (a-links/images) doesnt come back quick enough (before 'window.load' runs).
Try putting the prettyPhoto JS into the success callback (i.e. where returns data).
Below load_images.json is the JSON request you do which returns the HTML (a-links and their images):
$.getJSON("load_html.json", function() {
//grab HTML data (images/a-links) from json file and print into page
})
.success(function() {
//JS code running prettyPhoto inside here. Now will bind to a-links.
});
PrettyPhoto now binds to A-links AFTER the JSON has loaded them.
Hopefully will help having the prettyPhoto stuff AFTER the a-links.
If that fails try putting the prettyPhoto code inside the complete callback which occurs after success callback. Like the below:
$.getJSON("load_html.json", function() {
//grab HTML data (images/a-links) from json file and print into page
})
.success(function() {
//nothing
})
.complete(function() {
//JS code running prettyPhoto inside here. Now will bind to a-links.
});
This way you are giving prettyPhoto plenty of time to bind to the correct a-links which are marked for it.
Try that.

jQuery live doesn't works as expected

i have problems with ajax requests and simple <input type="submit"/>.
i use to load views inside other views, modular i mean, with jquery using .load(url) from one view to another.
so the problem is that if i load view_2 inside view_1 and the js script for view_2 is inside view_1 i need to use live('click') for example to launch an xhr request from view_2, so when i try it launches 3 (multiple) xhr at same time, instead of only 1 at time, don't know why.
the only thing i know is:
using live('click') in view_1 it launches 3 multiple XHR.
using click() in view_1 it doesn't work(obviously i think).
using click() directly inside view_2 it works (but i can't use js
in loaded views, i can use js only in "parents" views)
the functions are really simple, really don't know why i have this problem (i also disabled submit in ajax beforeSend) check this is a view_1 code which runs on loaded view_2 and launches 3 XHR for click :|
$(document).ready(function(){
$('#save-doc').live('click',function(){
var _title = $('#doc-title').val();
var _doc = $('#doc-doc').val();
update_doc(url_update_doc,{'title':_title,'doc':_doc,'id_doc':_choosed_doc,'id_project':id_project},this);
});
});
function update_doc(_url,_data,_starter){
$.ajax({
type:'POST',
data:_data,
url:_url,
dataType:'json',
beforeSend:function(){
$('.ajax-loading').show();
$(_starter).attr('disabled','disabled');
},
error:function(){
$('.ajax-loading').hide();
$(_starter).removeAttr('disabled');
},
success:function(json){
$('.ajax-loading').hide();
$(_starter).removeAttr('disabled');
if(json.error){
$('#error-title').html(json.error_title);
$('#error-doc').html(json.error_doc);
$.scrollTo('.append-form-edit-doc','fast');
}
if(json.confirm){
$.scrollTo('#top','fast');
$.gritter.add({
title:'Document Saved',
text:json.confirm
});
}
}
});
}
If that's a submit button inside the form then unless you prevent the default action, the form will be submitted. (That'd account for 2 POSTs, but not three.)
Remember that .live() is binding the event handler to the document itself. With that in mind, it is searching for #save-doc throughout the document on every click.
If there are multiple elements in the document with the 'save-doc' ID then they'll all be triggered.
However, what I bet is happening to you is you may have multiple forms layered which are all being executed by this one input.
Edit: Third possibility, is what Pointy mentions. Executing a submit via your event handler and another submit occurring because of browser behavior.
Please provide the HTML and what is being loaded into them.

Categories

Resources