I make use of jQuery and history.js to manage dynamic transitions between partial-pages; such that I avoid reloading entire documents. Some of these partial-pages call their own unique javascript files. While the transitions between pages work well, remnants of executed javascript remain active after the partial page that called it has been dynamically replaced.
How can I unload javascript that was introduced with a dynamic page load, and later asynchronously replaced by another page?
The finer details
Master template
My master template (used for all pages) can be thought of as a simple:
<html>
<head>
<script>
//scripts to manage asynchronous loading of partial-pages into #content div
</script>
</head>
<body>
<div id="content"></div>
</body>
</html>
User profile
One partial page that renders inside the #content div is for a user's Profile:
<script src="profile.js"></script>
<form>
<input type="file" name="profile-picture">
</form>
The contents of profile.js are similar to:
$(function() {
$('input').change(function() {
// upload profile picture asynchronously
});
});
User settings
Another partial page that is loaded inside the #content div of the master template is the user's Settings:
<script src="settings.js"></script>
<form>
<input type="text" name="first-name">
<input type="text" name="last-name">
</form>
The contents of settings.js are similar to:
$(function() {
setInterval(function() {
// auto-submit form every 10 seconds
$('form').submit();
}, 10000);
}
});
The problems
Certain javascript functions continue to run (e.g. setInterval) after the partial page that called them has been replaced by another.
This business of loading new javascript for each partial page feels messy; but for the life of me, I can't find any recommendations for best practices.
What is the better way to achieve this effect of dynamic partial-page loading/unloading while allowing page-specific scripts for each partial page?
Thank you!
Firstly...once you load javascript...you can't unload it
The setInterval problem will require using clearInterval
Declare some esoteric name that would make it fairly unique as a global variable when you initialize setiIterval; Make sure you declare the var outside of $(document).ready before using it
var my_super_form_submitter
$(document.ready)function(){...
my_super_form_submitter=setInterval(func.....
Then whenever you load a new page
if(my_super_form_submitter)
clearInterval(my_super_form_submitter)
As for collisions with other methods....you could adopt a content class protocol for your page specific code. On each page load, change class of content div...then use that class within selectors for jQuery
For first problem:
Question is what functions continues to run after replacing packages. SetInterval that you mentioned uses js timers, and simply replacing package wont stop timer automatically.
You should clear all timers started via setInterval before replacing.
E.g.
$(function() {
var timer = setInterval(function() {
// auto-submit form every 10 seconds
$('form').submit();
}, 10000);
}
function uninitialize(){
clearInterval(timer);
}
Just call uninitialize before replacing package, and it should work.
UnfortunatelyI don't know any proper way for loading partial pages, but I hope it helps you somehow (:
Related
Is there a way I can wrap an external JS script embed with lazy-load behavior to only execute when the embed is in the viewport?
Context: I have an external javascript embed that when run, generates an iframe with a scheduling widget. Works pretty well, except that when the script executes, it steals focus and scrolls you down to the widget when it’s done executing. The vendor has been looking at a fix for a couple weeks, but it’s messing up my pages. I otherwise like the vendor.
Javascript embed call:
<a href=https://10to8.com/book/zgdmlguizqqyrsxvzo/ id="TTE-871dab0c-4011-4293-bee3-7aabab857cfd" target="_blank">See
Online Booking Page</a>
<script src=https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js> </script> <script>
window.TTE.init({
targetDivId: "TTE-871dab0c-4011-4293-bee3-7aabab857cfd",
uuid: "871dab0c-4011-4293-bee3-7aabab857cfd",
service: 1158717
});
</script>
While I'm waiting for the vendor to fix their js, I wondered if lazy-loading the JS embed may practically eliminate the poor user experience. Warning: I'm a JS/webdev noob, so probably can't do anything complicated. A timer-based workaround is not ideal because users may still be looking at other parts of the page when the timer runs out. Here are the things I’ve tried and what happens:
I tried:
What happened:
Add async to one or both of the script declarations above
Either only shows the link or keeps stealing focus.
Adding type=”module” to one or both script declarations above
Only rendered the link.
Wrapping the above code in an iframe with the appropriate lazy-loading tags
When I tried, it rendered a blank space.
Also, I realize it's basically the same question as this, but it didn't get any workable answers.
I actually also speak french but I'll reply in english for everybody.
Your question was quite interesting because I also wanted to try out some lazy loading so I had a play on Codepen with your example (using your booking id).
I used the appear.js library because I didn't really want to spend time trying some other APIs (perhaps lighter so to take in consideration).
The main JS part I wrote is like this:
// The code to init the appear.js lib and add our logic for the booking links.
(function(){
// Perhaps these constants could be put in the generated HTML. I don't really know
// where they come from but they seem to be related to an account.
const VENDOR_LIB_SRC = "https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js";
const UUID = "871dab0c-4011-4293-bee3-7aabab857cfd";
const SERVICE = 1158717;
let vendorLibLoaded = false; // Just to avoid loading several times the vendor's lib.
appear({
elements: function() {
return document.querySelectorAll('a.booking-link');
},
appear: function(bookingLink) {
console.log('booking link is visible', bookingLink);
/**
* A function which we'll be able to execute once the vendor's
* script has been loaded or later when we see other booking links
* in the page.
*/
function initBookingLink(bookingLink) {
window.TTE.init({
targetDivId: bookingLink.getAttribute('id'),
uuid: UUID,
service: SERVICE
});
}
if (!vendorLibLoaded) {
// Load the vendor's JS and once it's loaded then init the link.
let script = document.createElement('script');
script.onload = function() {
vendorLibLoaded = true;
initBookingLink(bookingLink);
};
script.src = VENDOR_LIB_SRC;
document.head.appendChild(script);
} else {
initBookingLink(bookingLink);
}
},
reappear: false
});
})();
I let you try my codepen here: https://codepen.io/patacra/pen/gOmaKev?editors=1111
Tell me when to delete it if it contains sensitive data!
Kind regards,
Patrick
This method will Lazy Load HTML Elements only when it is visible to User, If the Element is not scrolled into viewport it will not be loaded, it works like Lazy Loading an Image.
Add LazyHTML script to Head.
<script async src="https://cdn.jsdelivr.net/npm/lazyhtml#1.0.0/dist/lazyhtml.min.js" crossorigin="anonymous" debug></script>
Wrap Element in LazyHTML Wrapper.
<div class="lazyhtml" data-lazyhtml onvisible>
<script type="text/lazyhtml">
<!--
<a href=https://10to8.com/book/zgdmlguizqqyrsxvzo/ id="TTE-871dab0c-4011-4293-bee3-7aabab857cfd" target="_blank">See
Online Booking Page</a>
<script src=https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js>
</script>
<script>
window.TTE.init({
targetDivId: "TTE-871dab0c-4011-4293-bee3-7aabab857cfd",
uuid: "871dab0c-4011-4293-bee3-7aabab857cfd",
service: 1158717
});
</script>
-->
</script>
</div>
I have a page that forces some JS to load on a page that I need to override. I can load a separate JS file to do this. I want to have the page do the .show for any of the .below-the-folds on the page. I guess the best way to say it is, I want all the "more" things on the page to be expanded when the page loads, rather than making a person click more to see what's below the fold on all these.
This is the JS I need to override, I can't change it since it's loaded by the app automatically. There can be more than one of the lists hidden, I'm not sure how much harder that makes things.
function MoreFacets($more_facets_div) {
this.$more_facets_div = $more_facets_div;
this.bind_events();
};
MoreFacets.prototype.bind_events = function() {
var self = this;
self.$more_facets_div.find('.more').on('click', function (e) {
$(this).siblings('.below-the-fold').show();
$(this).hide();
});
self.$more_facets_div.find('.less').on('click', function (e) {
$(this).parent().hide();
$(this).parent().parent().find('.more').show();
});
};
$(function() {
$('.more-facets').each(function() {
new MoreFacets($(this));
});
});
It's loaded on the page and the HTML looks like this:
<h3>Additional filters: </h3>
<dl id="facets">
<dt>Collecting Area</dt>
<dd> Here's Something in the list</dd>
<dd> Here's the last in the list</dd>
<div class="more-facets">
<span class="more btn">∨ more</span>
<div class="below-the-fold">
<dd>Something That's hidden is here</dd>
<dd>Something more in this hidden list</dd>
So when the ∨ more is clicked is when the others below-the-fold appear, and that's what I want to load when the page loads. There's usually a few different lists like this on the page.
So I'm thinking what I need to do is something like run the ('.below-the-fold').show() for all the lists when the page loads?
Update A note to clarify: when the page loads now they're all hidden. I'd like them to all show when the page is loaded so no one has to click anything to have everything showing.
Another note based on another question below... It's loaded in a separate file, and I can load my file before that one. I do know that I can override other JS on the page, so I assume I can override this as well.
Based on your last edit, it sounds like you're already onto the fastest solution to your problem.
Please note, this will only work if the script is not loaded asynchronously, but if you have control of the order the scripts are loaded in, you can insert your script between the problem script and jQuery.
Your script can be something as easy as redefining the function it's using to something like this:
MoreFacets.prototype.bind_events = function() {
var self = this;
//Autostart in our open state without completely disabling the functionality
self.$more_facets_div.find('.below-the-fold').show();
self.$more_facets_div.find('.more').hide();
self.$more_facets_div.find('.more').on('click', function (e) {
$(this).siblings('.below-the-fold').show();
$(this).hide();
});
self.$more_facets_div.find('.less').on('click', function (e) {
$(this).parent().hide();
$(this).parent().parent().find('.more').show();
});
};
Now, that won't work if you don't have control over the script loading, but you might have hope even in that case, because document ready functions in jQuery are invoked in the order they're registered, so if you can't really control where your script is you might play with an alternative
$(function() {
$('.more-facets').each(function() {
$(this).find('.below-the-fold').show();
$(this).find('.more').hide();
});
});
The first will be cleaner, but the second is a fallback for more restrictive situations, and both should achieve your desired effect without completely removing the functionality, just changing the default state on load.
I am relatively new to web-programming and am looking for a simple pattern to show a "loading"/waiting view for the web. Before I say anything else, I am sending only the minimum amount of data from the server, and start sending the JS/HTML resources to client while the client is using AJAX to request more data from the server (this might be suboptimal but bear with me). So basically, in theory this should mean the web view pops up earlier initially, but spends more time loading some of the data and corresponding subviews. Thus the need for a loading view.
So we have the standard jQuery function .ready()
fetchSomeDataAsynchronously(); //self-explanatory
$(document).ready(function () {
//should I load waiting view here or can I initialize it earlier??
window.mainUserHomeView = new MainUserHomeView({el: $("#user-home-main-div")});
window.mainUserHomeView.render();
window.userHomeMainTableView = new UserHomeMainTableView({el: $("#user-home-main-table-div")});
window.userHomeMainTableView.render();
fetchTeamSnapTeams(); //fetch more data asynchronously
});
Maybe my question is simply - can I show a loading screen before .ready() fires and what does that look like?
Yes you can. Just include the script to show the view in your HTML's head or at the beginning of the body. Make sure that you put it after you've loaded your required resources (e.g. jQuery) though.
Simplified example:
<html>
<head>
<script type="text/javascript">
function showLoadingView() {
// ...
}
showLoadingView();
</script>
</head>
<body>
<!-- ... -->
</body>
</html>
What am doing is writing wizards using existing forms and list views. we want to combine these forms in single page. here is a script we have used to get form from url then called function to bind widgets. first line is loading content of form but bindWidgets is not working. While bindWidgets is working on preloaded content which is default loaded with page.
<script>
$(document).ready(function() {
$("#template_form").load("/push_templates/pushtemplate/create/ #zform");
bindWidgets();
});
</script>
Do we need to wait for load, as it seems that 2nd line is executed prior to content loaded. How can we go to wait stat or better way to call bind function after load complete.
Use this;
<script>
$(document).ready(function() {
$("#template_form").load("/push_templates/pushtemplate/create/ #zform", function() {
bindWidgets();
});
});
</script>
You can see demo here: jsfiddle
I have a rails app in which I would like only one view to be refreshed every five seconds. I put the following in assets/javascripts/application.js
function timedRefresh(timeoutPeriod) {
setTimeout("location.reload(true);",timeoutPeriod);
}
$(document).ready(function(){
timedRefresh(5000);
});
However this will cause every view to be refreshed every five seconds. How could I make this JavaScript apply to only one view?
(using Rails 3.2.5)
If you want to keep your JS in application.js you can simply check for the presence of a specific element and act accordingly. For example:
some_vew.html.erb
<div id = "refreshable">
<!-- So on and so forth... -->
</div>
application.js
$(function() {
// function timeRefresh()...
if ($('#refreshable').length) {
timedRefresh(5000);
}
});
You can make use of the tag in the bottom of your view.
some_view.html.erb
...
contents
...
<script type="text/javascript">
Put javascript code here...
</script>
This will make this script only available to someview.html.erb. The application.js makes the script global.