When clicking on a link smoothstate loads the new page's content, I'd like to identify the referring page with a class visible on the new page. Is there any way to do this?
This isn't the most dynamic solution, but what I ended up doing was setting classes to the anchor tags on the referring page. SmoothState has the onBefore() callback, which passes $currentTarget, i.e. the anchor element you clicked to start the transition. You could just as easily read the href off the anchor element and detect what you want to do that way as well (or store it off somewhere until later).
So in my HTML, I could declare a link like so:
New Page
Then when I'm setting up smoothState, I declare my onBefore() callback:
onBefore: function( $currentTarget, $container ) {
if ( $currentTarget.is( ".special_case" ) ) {
// Some logic here
globalSpecialCase = true;
}
}
Since smoothState actually keeps you in the same place and just does an Ajax call to request new page HTML, then swaps out the page content, your current Javascript environment remains, global variables and all. So I set up a global flag (or you could make whatever data structure you want), and for that case with the special link, set the flag.
At the new page load, smoothState runs onReady(), that signals it's got the new page content and is ready to replace it and run the intro animations. You could check your flag here to do things before the page starts rendering, or you could wait until the onAfter() callback happens, which means the page is fully loaded and all the transition animations have run. At this point, I check the flag, run some logic, and unset it to make sure I'm ready for a new link click later.
onReady: {
duration: 0,
render: function( $container, $newContent ) {
if ( globalSpecialCase ) {
// Logic because this page was loaded from a special link
globalSpecialCase = false;
}
// Inject the new content
$container.html( $newContent );
}
}
One thing to note: pretty much all the smoothState examples have the onReady duration set to 0, which means the onAfter() callback will happen immediately following onReady(), regardless of how long your animations take. If you actually want onAfter() to wait until the animations are done, make sure to set duration to the milliseconds your animations take.
Related
If in a web page I load some content after the initial page load and DOM ready event, when are the inserted nodes "ready", in the sense of having their sizes computed, taking CSS rules into consideration and all?
$.ajax({
url: ajaxUrl,
success: function (data) {
var page = $(data).find('.page').first();
page.appendTo(pages_container);
// if I try to get the width of an element here, I get 0
console.log(page.width()); // -> 0
// but if I do it, let's say 500ms later, now it is computed
setTimeout(function () {
console.log(page.width()); // -> correct width, non zero
}, 500);
}
});
Is there any kind of event, like "on inserted dom ready" or something to be able to execute a function after the sizes/layout of the ajax inserted content has been computed?
Your problem should not occur as you outline. Appending elements into a page is not an asynchronous operation. Javascript DOM manipulation is not multi-threaded. It simply returns after the DOM changes have been applied.
The DOM should be ready to use as soon as your append completes (including size properties):
page.appendTo(pages_container);
The usual cause is accessing the properties before the jQuery object is inserted, or that the new jQuery object is visually hidden and revealed later (fadein etc), but I see no transitions in your code.
Questions/thoughts:
Can you mock up a JSFiddle to replicate your problem?
Also check for other plugins etc that could modify your DOM after insertion.
Is there JS code in the inserted page?
Also what browser did this occur with?
I'm using the Boxer library from www.formstone.it to display a modal popup window over my HTML page. On triggering the modal window, HTML content gets loaded into the modal DIV from a file on my server. The Boxer code:
$(".boxer.boxer_object").click(function(e) {
e.preventDefault();
e.stopPropagation();
$obj = $('<div class="outer_container" />').load("http://www.myserver.com/game_modal.html",function(){
setTimeout(function (){
... Kinetic code which loads several image and GUI elements for a simple game ...
}, 2000); // delay Kinetic code execution with 2 seconds
$.boxer($obj);
});
Even though it does seemingly only execute the KineticJS code AFTER the HTML code has loaded, I do still sporadically get the following error:
Uncaught TypeError: Cannot set property 'innerHTML' of null
As I understand it, this error occurs when the canvas is trying to target a DIV which does not yet exist. In other words, the KineticJS code executes AFTER the code has loaded but BEFORE the relevant container DIV has become part of the page structure.
As seen in my code above, I now use a setTimeout() function to delay the KineticJS code execution with 2 seconds. Even though less than ideal, I have not seen the error again, with the game graphics loading every time. However, this is a fix that may be working on my browser but which may fail for someone else in other conditions.
Is there a proper way in which to ensure that the KineticJS code will ALWAYS execute AFTER the externally loaded HTML code has become part of the page structure? i.e. after the container DIV which the KineticJS code targets for the HTML5 canvas actually exists?
You should be able to skip the ajax call and render the game's container and loading markup manually:
var $game = $('<div id="outside_container" style="text-align:center; width:900px; height=600px;"><span style="display:inline-block; line-height:600px; font-size: 4em;">LOADING...</span></div>');
Then use the 'callback' option to initialize the game:
$boxer($game, {
callback: initGame
});
function initGame() {
// kinetic js code
...
}
Disclaimer: I haven't used boxer.
I took a quick peek at your boxer link.
There is a callback which executes "after opening instance".
How about putting your Kinetic code in that callback function you can supply to boxer.
From what I can understand, you are trying to create a div with the class of 'outer_container' when the onclick event occurs. You then want to 'load' your game modal from your web service and then run the kinetic js and boxer code asynchronously (via the callback) when it has been returned.
Asynchronous functionality can always been a bit fiddly. In your asynchronous chain of events, I think creating the div at the same time as attaching a .load() function to it means that sometimes the web service may be ready before the div has been created.
Have you tried creating the divelement before calling a web service pointing at that element?
You could either create thediv when you first intialise the page or try this...
$(".boxer.boxer_object").click(function(e) {
e.preventDefault();
e.stopPropagation();
//create the div first before the web service call to ensure it exists...
var obj = document.createElement('div');
obj.setAttribute('class', 'outer_container');
$('.outer_container').load("http://www.myserver.com/game_modal.html",function(){
//your code here, called after the web service has returned data...
$.boxer(obj);
});
I would personally just declare the 'outer_container' div before the declaration of the onclick event.
I hope this helps :).
I have div with vertical scroll bar. Div is being updated dynamically via ajax and html is inserted using jQuery's .html method.
After div is updated scroll bar returns to top and I am trying to keep it in the previous position.
This is how I'm trying it:
var scrollPos = $('div#some_id').scrollTop(); //remember scroll pos
$.ajax({...
success: function(data) {
$('div#some_id').html(data.html_content); //insert html content
$('div#some_id').scrollTop(scrollPos); //restore scroll pos
}
});
This fails. My best guess is that it is failing due to inserted html not rendered (ie. no scroll).
For example this works.
setTimeout(function(){
$('div#some_id').scrollTop(scrollPos);
}, 200);
But this is dirty hack in my opinion. I have no way of knowing that some browsers won't take more then these 200ms to render inserted content.
Is there a way to wait for browser to finish rendering inserted html before continuing ?
It's still a hack, and there really is no callback available for when the HTML is actually inserted and ready, but you could check if the elements in html_content is inserted every 200ms to make sure they really are ready etc.
Check the last element in the HTML from the ajax call:
var timer = setInterval(function(){
if ($("#lastElementFromAjaxID").length) {
$('div#some_id').scrollTop(scrollPos);
clearInterval(timer);
}
}, 200);
For a more advanced option you could probably do something like this without the interval, and bind it to DOMnodeInserted, and check if the last element is inserted.
I will just like to point out one difference here: One thing, is when the .html() have completed loading, but the browser actually render the content is something different. If the loaded content is somewhat complex, like tables, divs, css styling, images, etc - the rendering will complete somewhat later than all the dom ellements are present on the page. To check if everything is there, does not mean the rendering is complete. I have been looking for an answer to this by myself, as now I use the setTimeout function.
Such callback does not exists because .html() always works synchronously
If you are waiting for images loading, there's one approach https://github.com/desandro/imagesloaded
I have the following problem: on a customer's homepage the navibar is loaded by javascript, but I need to change some URL's on it. If I just start my script on $(document).ready() it runs before the customers script and has no effect. I only can use setTimeout for my function to wait until the other script is ready, but it's not good or safe at all. I can't change anything on the website, only add a javascript - is there a way to time it after the other one?
You can use repeated setTimeout, in order to check if menu is accessible.
function check_menu(){
if(document.getElementById('my_menu')==null){
setTimeout('check_menu()',500);
} else {
//do some stuff
}
}
If you have information about the menu like the id or class, use the onLoad() jQuery method on the element. For example if the code is loading asynchronously, and you add the onload to one of the last elements it should fire after the content has finished.
$.post('AsyncCodeLoad.php', function(data) {
$('#lastElementToLoad').onLoad(RunMyFunction);
});
Or if you have no chance to insert your code into the async loading just add to the bottom of the </body>:
$('#lastElementToLoad').onLoad(RunMyFunction);
Just a thought.
Yes, add your script at the bottom of the <body /> tag to ensure it does not run until all other scripts have run. This will only work however if your customer is loading the nav links synchronously.
If the nav is being loaded asynchronously, use JS's setInterval to repeatedly check the contents of the nav for links. When you determine the links have been added, cancel your interval check and call your script's logic entry point.
Cheers
Fiddle: http://jsfiddle.net/iambriansreed/xSzjA/
JavaScript
var
menu_fix = function(){
var menu = $('#menu');
if(menu.length == 0) return;
clearInterval(menu_fix_int);
$('a', menu).text('Google Search').attr('href','http://google.com');
},
menu_fix_int = setInterval(menu_fix, 100);
HTML
<div id="menu">Bing Search</div>
Upon loading, my web site runs this code if the users is using an alternate theme:
ReplaceJSCSSFile("css/skin1.css", "css/skin2.css", "css");
AJAX_LoadResponseIntoElement("skinContainer", "skin" + themeSelect + ".txt", function() {
AJAX_LoadResponseIntoElement("contentdiv", "index.txt", initPage);
});
What AJAX_LoadResponseIntoElement does ought to be obvious, but here's a short explanation of ReplaceJSCSSFile: It basically searches for link elements within the web page with their src property equal to "css/skin1.css", at which point it creates a new link element (src="css/skin2.css") and uses .parentNode.replaceChild to replace the old with the new.
The problem with this is that initPage() sets the positions and of certain divs that move around on the screen in relation to the positions of static elements. The position of those static elements is fetched from CSS, but the CSS is not being applied before the code is running, and initPage() is putting things in the wrong place because of it.
I have tried using a callback:
ReplaceJSCSSFile("css/skin1.css", "css/skin2.css", "css", function() {
AJAX_LoadResponseIntoElement("skinContainer", "skin" + themeSelect + ".txt", function() {
AJAX_LoadResponseIntoElement("contentdiv", "index.txt", initPage);
});
});
Unfortunately, this didn't work. Presumably, it tells the browser to replace the CSS file, and as soon as it tells the browser to do that, it moves on to the callback function, using AJAX to fetch the page contents. I'm thinking the page contents come back prior to the CSS file, which is why the new CSS is not being applied by the time initPage() is being executed.
Is it possible to fix this without using alternate stylesheets?
The only thing that I can come up with other than alternate stylesheets is fetching the CSS contents with AJAX (which would actually wait for the server's response before executing the callback function) to replace the "css/skin1.css" link element.
You can fudge it and use setTimeout in your callback so that the body of the callback isn't run until a bit later. This gives the browser some time to recover.