Some background;
By default when you click a link to a separate HTML page JQM loads the first data-role="page" on that page and attaches it to the DOM of the first page, the thing is JQM only loads that page div and not any scripts etc. that are outside that container.
I have a jQuery mobile app with 5 pages "login.html" "menu.html" "account.html" "settings.html" ..etc
I change pages with like;
$.mobile.changepage("account.html")
Also I put all my page load logic to my first page login.html like this;
<script>
$(document).on('pageinit', '#loginpage', function() {
//do some stuff run some ajax scripts and add some html to its body
});
$(document).on('pageinit', '#accountpage', function() {
//do some stuff run some ajax scripts and add some html to its body
});
$(document).on('pageinit', '#settingspage', function() {
//do some stuff run some ajax scripts and add some html to its body
});
</script>
While this app works well, Problem is I find it very fragile and hard to survive from unexpected errors. for example;
Since how every page's body html will load is defined in login.html, this means if any moment user manually refreshs any of these pages, the page will load without running those scripts and load an empty page a without a body. In that moment since the correct DOM is deleted from memory, user is stuck in an app with full of empty pages, only way is he is smart enough to go and type "login.html" to address bar and only then all process can start smoothly.
I think we cant %100 hide address bar, it is visible again after scroll down.
SO this is one problem I come up with, some other weird things can happen and if somehow the DOM gets corrupted only way to use app again is typing login.html address bar, which users probably will not thing about it.
How can I make this app more robust like detecting any DOM corruption or refresh and forward the user to login.html, so he does not stuck in an app with empty pages.
One way to alleviate some pain is to put your page-specific scripts inside data-role="page" element in the appropriate html files and keep scripts that are the same for every page outside that element (at the and of the body and/or head).
That way even if the user refreshes the page all necessary scripts will still be executed.
One problem though, before binding any handlers you need to unbind them first. Otherwise you'll end up having multiple handlers attached.
To illustrate this:
in login.html (updated):
<div data-role="page" id="loginpage">
<script>
$(document).off('pageinit', '#loginpage').on('pageinit', '#loginpage', function() {
$(document).off('click', '#btnaccount').on('click', '#btnaccount', function(){
$.mobile.changePage("jqmaccount.html", {reloadPage: true});
});
console.log("paginit in loginpage");
});
$(document).off('pageshow', '#loginpage').on('pageshow', '#loginpage', function() {
console.log("pageshow in loginpage");
});
</script>
<div data-role="content">
<a id="btnaccount" href="#" data-role="button">Account</a>
</div>
</div>
in account.html (updated):
<div data-role="page" id="accountpage">
<script>
$(document).off('pageinit', '#accountpage').on('pageinit', '#accountpage', function() {
$(document).off('click', '#btnlogout').on('click', '#btnlogout', function(){
$.mobile.changePage("jqmlogin.html", {reloadPage: true});
});
console.log("pageinit in accountpage");
});
$(document).off('pageshow', '#accountpage').on('pageshow', '#accountpage', function() {
console.log("pageshow in accountpage");
});
</script>
<div data-role="content">
<a id="btnlogout" href="#" data-role="button">Logout</a>
</div>
</div>
In this setup if the user refreshes account.html the Logout button on that page will still work.
Related
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.
I know there're a lot of duplicate questions out there, I checked almost all of them, but I just couldn't find a solution in my case. So, here's my problem:
I have a banner which will show on every page of the project, inside the banner there's a close button to close the banner, and a download button which leads user to the app store to download the app. My banner works perfect only except I have to refresh the page to get these two buttons works. Here's my code:
$(document).on("pageinit", function () {
$("#close").on("click", CloseBanner);
$("#download").on("click", SetAppStorePath);
//alert("pageinit");
});
function SetAppStorePath() {
if (isIOS) {
window.location = "https://itunes.apple.com/us/app/myapp/id.....";
}
else if (isAndroid) {
window.location = "https://play.google.com/store/apps/details?id=.....";
}
}
function CloseBanner() {
$('.banner').hide();
}
Here's the simplified html:
<div class="banner">
<div class="container">
<a id="close">×</a>
<a id="download">Download</a>
</div>
</div>
I did a little test, and found something tricky: I added that alert inside pageinit, I noticed that the alert is always executed(means my button on click events are always registered) when I jump from page A to page B. But when the button works, I see page A is gone, blank page shows, alert shows, then page B shows, the buttons work. When it doesn't work, the order is different, I still can see page A, then I see alert(I still can see page A now), then it changes to page B, the buttons don't work.
So seems that when pageinit executed after page jumped, it works,but sometimes pageinit executed before page jumped, then it doesn't work.
I think your elements are dynamically created. You may need event delegation.
$(document).on("pagecreate", function () {
$("body").on("click", "#close", CloseBanner);
$("body").on("click", "#download", SetAppStorePath);
});
Move following outside of the init and change it like following
$(document).on("click","#close", CloseBanner);
$(document).on("click","#download", SetAppStorePath);
when page init executes, DOM is not ready. that is the reason it is not binding to close or download elements. and this is the way to overcome that. you dont need pageinit event here
I currently have Jquery tabs for different content on a page but this does not help me SEO wise as google and other places will not pick up this content.
I am looking for "tabs" but instead when you click on a link it loads that bit of information.
An example of this would be here http://www.gamespot.com/watch-dogs
You click on a "tab" say reviews and it loads that content along with a url of http://www.gamespot.com/watch-dogs/reviews/
I am looking to replicate something like this rather than using Jquery tabs.
Your help would be grand!
If you want the content to be picked up by search engines, it has to be in the original HTML. They won't run scripts that update the DOM on the client. So you need to do it in PHP:
<div>
<?php readfile("url"); ?>
</div>
However, if the URL is for a full web page, this is probably a bad idea. It will have its own <html>, <head>, and <body> tags, and these should appear only once on a page, not embedded inside another page.
<div id="myContent"></div>
load method:
$("#myContent").load("url to your content which you want to load", function(){
// things which you want to do with.
});
Try jQuery's load function:
$( "#element to put data in" ).load( "address", function() {
alert( "Load was performed." );
//what to do when done loading page
});
In order to make both search engines (which likely won't execute JQuery) and users (who might like dynamic elements) happy, you could do two solutions. You might have a link which when clicked goes to a brand new HTML page. Search engines will follow this link and see a whole separate page. Then, you might have a javascript within your page (or within the link's click event) that intercepts and overrides the original link's target and instead executes something like JQuery's $().load(). To reuse content you could have the target page be capable of loading both with and without a wrapper.
Target File:
<?php if(!isset($_GET['skipWrapper'])) { include('header.php'); } ?>
Here's the content that matters!
<?php if(!isset($_GET['skipWrapper'])) { include('footer.php'); } ?>
Source File:
<?php if(!isset($_GET['skipWrapper'])) { include('header.php'); } ?>
Here's the orginal content!
<a href="TargeFile.php" onclick="asyncLoad('TargeFile.php'); return false;">
<?php if(!isset($_GET['skipWrapper'])) { include('footer.php'); } ?>
Definition of asyncLoad:
function asyncLoad(url) { $('#targetElementId').load(url + '?skipWrapper=true'); }
However, it might be best to just stick with the traditional links rather than doing async tabs. Users get confused when the back button doesn't work or when common behaviors (like middle click to open a link in a new browser tab) no longer work.
I have a pageLoad js function in master page,
and another one inside content page.
Does the first one (in master page) prevent the second one (in content page) from firing ?
It's the other way round. The one on the Content Page will be fired first. To know if one is messing with the other, simply debug it on Firebug or even on Visual Studio.
You can use the jQuery syntax to chain functions to run on page load in both Master and Child pages like this:
$().ready(function() {
// Do stuff
}
Contents will be execute in the order that the appear on the page, from top to bottom.
You could force items to be run in a specific order with a construct like this in your MasterPage:
<script>
$().ready(function() {
// Do MasterPage stuff
if (onChildPageLoad) onChildPageLoad();
});
</script>
and in the child page place a script block like this:
<script>
var onChildPageLoad = function() {
// Do childpage stuff
}
</script>
I'm sure I'm missing something pretty basic, but I have just started to get myself up to speed on jQuery and Javascript programming. Previously I was doing server side programming with PHP.
I'm now in the middle of creating a prototype for HTML5 webapp, where I would like to have different screens. Now with PHP that was pretty easy, I could just used server side templates like Smarty and be done with it.
However to make my app more webapp like, I would like to dynamically change between screens without having to reload the window.
I have looked into several options that might be anwsers to my question, but I'm not sure whether I'm on the right track.
I have checked for example JsRender, JsViews or even the pure jquery load command.
But what I'm not sure is whether these things would allow me to have something like this:
HEADER_PART
MAIN_CONTENT
FOOTER_PART (also contains links to common JS files that I use)
I would like to dynamically update the MAIN_CONTENT part. Currently my application is only one page, and all my custom logic that belongs to that page is in one JS file. In this JS file, I use a simple $(function() { ... to load my page, so whenever it gets loaded, parts of my page get updated asyncronously. This is fine, since all my blocks in this certain page would have to be loaded when that one page gets loaded.
But what if I have a link, like main.html#otherscreen, and when I click that screen, I would like to change my MAIN_CONTENT and also run another page load specific JS that handles blocks on that other screen, not the first page?
I know I could still use probably server side templating and load my pages using AJAX requrests, but again, not sure whether that is the right approach.
Could you please enlighten me? :)
Thanks & best regards,
Bence
Check out jQuery.load(). Using this function you can dynamically load content into a div on the page, which is what I think you want to do. Just find the div on the page you want to load content into and call
$('#mydiv').load(url, data, function(response){
//do something once it's done.
});
Per your comments...
This is actually very easy. .load() should replace the content in the div (I think. If not, just call .empty() first). Of course you could get fancy and add effects, like
function changePages(url) {
$('#mydiv').fadeOut('fast', function() {
$(this).load(url, function(response){
$('#mydiv').fadeIn('fast');
});
});
}
To handle things like the hash in the URL, in your click event you have to make sure you first call e.preventDefault():
$('#mylink').click(function(e){
e.preventDefault(); //e is a jquery event object
var link = $(this);
var hash = link.attr('href'); // get the hashtag if the href is '#something'
changePages(someUrl + hash);
});
For dynamic loading of data into the page without changing your header and footer you should use jQuery's AJAX function. It allows you to post requests to the server and receive data back without reloading the page. A simple example would be something like:
<html>
<head>
<title>Example</title>
<!-- Assuming jQuery is already referenced -->
<script type="text/javascript">
$('span.buttonish').click(function(){
$.ajax({
// The URL can be a file or a PHP script of your choosing
// it can also be pure HTML without the <html> tags as they
// are already in your file
url: 'path/to/the/file/that/return/data',
success: function(receivedData) {
// The received data is the content of the file or the return
// data of the script and you can use it as you would with any data
$('#content').html(receivedData);
}
});
});
</script>
</head>
<body>
<div id="header">
<!-- Something -->
</div>
<div id="content">
<span class="buttonish">Click me to change the text... </span>
</div>
</div id="footer">
<!-- Something -->
</div>
</body>
<html>