I am creating a landing page which should exist in two languages. The texts that should be shown are in two JSON files, called accordingly "ru.json" and "en.json". When a user clicks on the "Change language" button, the following function is executed:
function changeLang(){
if (userLang == 'ru') {
userLang = 'en';
document.cookie = 'language=en';
}
else {
userLang = 'ru';
document.cookie = 'language=ru';
}
var translate = new Translate();
var attributeName = 'data-tag';
translate.init(attributeName, userLang);
translate.process();
}
Where Translate() is the following:
function Translate() {
//initialization
this.init = function(attribute, lng){
this.attribute = attribute;
if (lng !== 'en' && lng !== 'ru') {
this.lng = 'en'
}
else {
this.lng = lng;
}
};
//translate
this.process = function(){
_self = this;
var xrhFile = new XMLHttpRequest();
//load content data
xrhFile.open("GET", "./resources/js/"+this.lng+".json", false);
xrhFile.onreadystatechange = function ()
{
if(xrhFile.readyState === 4)
{
if(xrhFile.status === 200 || xrhFile.status == 0)
{
var LngObject = JSON.parse(xrhFile.responseText);
var allDom = document.getElementsByTagName("*");
for(var i =0; i < allDom.length; i++){
var elem = allDom[i];
var key = elem.getAttribute(_self.attribute);
if(key != null) {
elem.innerHTML = LngObject[key] ;
}
}
}
}
};
xrhFile.send();
}
Everything works fine, however, when a user opens the page for the first time, if his Internet connection is bad, he just sees the elements of the page without text. It is just 1-2 seconds, but still annoying.
The question is, is there any way to check the text has loaded and display the page elements only on this condition?
You can use $(document).ready() in this way
$(document).ready(function(){
//your code here;
})
You can use the JavaScript pure load event in this way
window.addEventListener('load', function () {
//your code right here;
}, false);
Source: Here
translate.process() is asynchronous code which needs to make a call to a server and wait for its response. What it means is that, when you call this function, it goes in the background to go do its own thing while the rest of the page continues loading. That is why the user sees the page while this function is still running.
One minimal way I can think around this is by adding this to your css files in the head tag.
body { display: none }
And then, under this.process function, after the for loop ends, add
document.body.style.display = 'block'
If you want to suppori IE8:
document.onreadystatechange = function () {
if (document.readyState == "interactive") {
// run some code.
}
}
Put the code you want to execute when the user initially loads the page in a DOMContentLoaded event handler like below:
document.addEventListener('DOMContentLoaded', function() {
console.log('Whereas code execution in here will be deffered until the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.');
});
console.log('This will log immediatley');
It's important to note that DOMContentLoaded is different than the load event
Related
I am using this code to infinite load a page on squarespace. My problem is the reloading doesn't capture the filtering that I have set up in my url. It cannot seem to 'see' the variables or even the url or categoryFilter in my collection. I've tried to use a .var directive but the lazy loaded items cannot see the scope of things defined before it. I'm running out of ideas here please help!
edit: I've since found the answer but gained another question.
I was able to use window.location.href instead of window.location.pathname to eventually get the parameters that way. Except this doesn't work in IE11 so now I have to search for this.
<script>
function infiniteScroll(parent, post) {
// Set some variables. We'll use all these later.
var postIndex = 1,
execute = true,
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY(),
urlQuery = window.location.pathname,
postNumber = Static.SQUARESPACE_CONTEXT.collection.itemCount,
presentNumber = Y.all(post).size();
Y.on('scroll', function() {
if (presentNumber >= postNumber && execute === true) {
Y.one(parent).append('<h1>There are no more posts.</h1>')
execute = false;
} else {
// A few more variables.
var spaceHeight = document.documentElement.clientHeight + window.scrollY,
next = false;
/*
This if statement measures if the distance from
the top of the page to the bottom of the content
is less than the scrollY position. If it is,
it's sets next to true.
*/
if (stuffBottom < spaceHeight && execute === true) {
next = true;
}
if (next === true) {
/*
Immediately set execute back to false.
This prevents the scroll listener from
firing too often.
*/
execute = false;
// Increment the post index.
postIndex++;
// Make the Ajax request.
Y.io(urlQuery + '?page=' + postIndex, {
on: {
success: function (x, o) {
try {
d = Y.DOM.create(o.responseText);
} catch (e) {
console.log("JSON Parse failed!");
return;
}
// Append the contents of the next page to this page.
Y.one(parent).append(Y.Selector.query(parent, d, true).innerHTML);
// Reset some variables.
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY();
presentNumber = Y.all(post).size();
execute = true;
}
}
});
}
}
});
}
// Call the function on domready.
Y.use('node', function() {
Y.on('domready', function() {
infiniteScroll('#content','.lazy-post');
});
});
</script>
I was able to get this script working the way I wanted.
I thought I could use:
Static.SQUARESPACE_CONTEXT.collection.itemCount
to get {collection.categoryFilter} like with jsont, like this:
Static.SQUARESPACE_CONTEXT.collection.categoryFilter
or this:
Static.SQUARESPACE_CONTEXT.categoryFilter
It didn't work so I instead changed
urlQuery = window.location.pathname
to
urlQuery = window.location.href
which gave me the parameters I needed.
The IE11 problem I encountered was this script uses
window.scrollY
I changed it to the ie11 compatible
Window.pageYOffset
and we were good to go!
Forgive my naivety, this probably is quite obvious, I just can't see it now.
Please tell me what is wrong with the following code:
$('#iframe1').load(function(){
$('#iframe2').load(function(){
alert('loaded!');
});
});
The idea is to wait until both iframes have fully loaded, then alert "loaded" - of course this is a simplified example for the sake of stack.
The script sits in script tags at the end of the body of the html doc.
#Quertiy answer is perfectly fine, but not very jQuery-ish. It is hard-coded for 2 iframes only.
The beauty of jQuery is that you can make it work for the most number of people, with as little friction as possible.
I've advised a very simplistic plugin that does nearly what is present on that answer, but in a more open way. It not only works on iframes, but also on images, audio, video and whatever has a onload event!
Without further due, here's the code:
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
It works by adding a load handler to every element in that selection. Since it is a plugin, you can use in whatever way you decide to use it.
Here's an example, that loads 30 random images:
//plugin code
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
$(function(){
//generates the code for the 30 images
for(var i = 0, html = ''; i < 30; i++)
html += '<img data-src="http://lorempixel.com/g/400/200/?_=' + Math.random() + '">';
//stuffs the code into the body
$('#imgs').html(html);
//we select all images now
$('img')
.allLoaded(function(){
//runs when done
alert('loaded all')
})
.each(function(){
//the image URL is on a `data` attribute, to delay the loading
this.src = this.getAttribute('data-src')
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
<div id="imgs"></div>
Your problem, as said before many times, is that you have a load event attached to your iframe. That event is fired everytime the content change.
After that, you set a new event on #iframe2. When it's content changes, it will fire events left and right, above and beyound what you wish!
The best aproach is to keep track of which ones you loaded or not. After all have been loaded, you simply run the function.
The problem is that you're waiting until #iframe1 loads before you attach a handler for #iframe2 loading. So if #iframe2 loads first, you'll never get your callback.
Instead, watch the load event on both of them and track which ones you've seen:
var seen1 = false,
seen2 = false;
$('#iframe1, #iframe2').load(function(){
if (this.id == "iframe1") {
seen1 = true;
} else {
seen2 = true;
}
if (seen1 && seen2) {
alert('loaded!');
}
});
Why do you expect 2nd iframe to load after the first one?
~function () {
var loaded = 0;
$('#iframe1, #iframe2').load(function (){
if (++loaded === 2) {
alert('loaded!');
}
});
}()
I'm working on a chrome extension, and I set window.title in the onload handler. It seems, though, that the page I'm modifying sets the document title dynamically as well. There's a huge collection of scripts being linked. Is there any way for me to prevent anyone else from modifying document.title or any of its variants, without knowing where the modification is coming from? Alternatively, is there a quick way for me to see where the change is coming from?
I had same problem, some external scripts are changed my page title by document.title = "..."
I've made own solution for it:
try {
window.originalTitle = document.title; // save for future
Object.defineProperty(document, 'title', {
get: function() {return originalTitle},
set: function() {}
});
} catch (e) {}
See the answer to how to listen for changes to the title element?. Notably:
function titleModified() {
window.alert("Title modifed");
}
window.onload = function() {
var titleEl = document.getElementsByTagName("title")[0];
var docEl = document.documentElement;
if (docEl && docEl.addEventListener) {
docEl.addEventListener("DOMSubtreeModified", function(evt) {
var t = evt.target;
if (t === titleEl || (t.parentNode && t.parentNode === titleEl)) {
titleModified();
}
}, false);
} else {
document.onpropertychange = function() {
if (window.event.propertyName == "title") {
titleModified();
}
};
}
};
This SO answer suggest a technique for how to listen for changes to the document title.
Perhaps you could use that technique to create a callback which changes the title back to whatever you want it to be, as soon as some other script tries to change it.
main question
Is there a javascript way to identify if we are accessing a page for the first time or it is a cause of a back?
My problem
I'm implementing html5 navigation in my ajax driven webpage.
On the main script, I initialize a variable with some values.
<script>
var awnsers=[];
process(awnsers);
<script>
Process(awnsers) will update the view according to the given awnsers, using ajax.
In the funciton that calls ajax, and replaces the view, I store the history
history.pushState(state, "", "");
I defined the popstate also, where I restore the view according to the back. Moreover, I modify the global variable awnsers for the old value.
function popState(event) {
if (event.state) {
state = event.state;
awnsers=state.awnsers;
updateView(state.view);
}
}
Navigation (back and forth) goes corectly except when I go to an external page, and press back (arrving to my page again).
As we are accessing the page, first, the main script is called,the valiable awnsers is updated, and the ajax starts. Meanwile, the pop state event is called, and updates the view. After that the main ajax ends, and updates the view according to empty values.
So I need the code:
<script>
var awnsers=[];
process(awnsers);
<script>
only be called when the user enters the page but NOT when it is a back. Any way to do this?
THanks!
Possible solution
After the first awnser I have thought of a possible solution. Tested and works, whoever, I don't know if there is any cleaner solution. I add the changes that I've done.
First I add:
$(function() {
justLoaded=true;
});
then I modify the popState function, so that is in charge to initialize the variables
function popState(event) {
if (event.state) {
state = event.state;
awnsers=state.awnsers;
updateView(state.view);
} else if(justLoaded){
awnsers=[];
process(awnsers);
}
justLoaded=false;
}
Thats all.
what about using a global variable?
var hasLoaded = false;
// this function can be called by dom ready or window load
function onPageLoad() {
hasLoaded = true;
}
// this function is called when you user presses browser back button and they are still on your page
function onBack() {
if (hasLoaded) {
// came by back button and page was loaded
}
else {
// page wasn't loaded. this is first visit of the page
}
}
Use cookie to store the current state.
yeah! This is what I have:
var popped = (($.browser.msie && parseInt($.browser.version, 10) < 9) ? 'state' in window.history : window.history.hasOwnProperty('state')), initialURL = location.href;
$(window).on('popstate', function (event) {
var initialPop = !popped && location.href === initialURL, state;
popped = true;
if (initialPop) { return; }
state = event.originalEvent.state;
if (state && state.reset) {
if (history.state === state) {
$.ajax({url: state.loc,
success: function (response) {
$(".fragment").fadeOut(100, function () {
$(".fragment").html($(".fragment", response).html()).fadeIn(100);
);
document.title = response.match(/<title>(.*)<\/title>/)[1];
}
});
} else { history.go(0); }
else {window.location = window.location.href; }
});
And:
$.ajax({url:link,
success: function (response) {
var replace = args.replace.split(",");
$.each(replace, function (i) {
replace[i] += ($(replace[i]).find("#video-content").length > 0) ? " #video-content" : "";
var selector = ".fragment "+replace[i];
$(selector).fadeOut(100, function () {
$(selector).html($(selector,response).html()).fadeIn(100, function () {
if (base.children("span[data-video]")[0]) {
if ($.browser.msie && parseInt($.browser.version, 10) === 7) {
$("#theVideo").html("");
_.videoPlayer();
} else {
_.player.cueVideoById(base.children("span[data-video]").attr("data-video"));
}
}
});
});
});
document.title = response.match(/<title>(.*)<\/title>/)[1];
window.history.ready = true;
if (history && history.pushState) { history.pushState({reset:true, loc:link}, null, link); }
}
});
I need to create a simple javascript embed type thing where the user adds a small amount of javascript to their site and my script does the rest.
This is the sort of thing I would like to see on the user site:
Included once:
<script type="text/javascript" src="path/to/doEverythingScript.js"></script>
And this in the HTML:
<div id="target-div">
<script type="text/javascript">
doThis(1714581, "#target-div");
</script>
</div>
I would also need to be able to have multiple versions running on the same page.
The id (eg: 1714581) is passed to my function along with my target div.
This issue is I need to have the one script load all the dependancies:
eg:
path/to/style1.css
path/to/style2.css
path/to/style3.css
path/to/jquery.js
path/to/script1.js
path/to/script2.js
path/to/script3.js
path/to/script4.js
And once they are all loaded I would then be able to run my function.
If my function ran before they all loaded It naturally wouldn't work.
I tried using LazyLoad and LAB but couldn't work out how to have one JS file sort all this out with only one linkied script and a small function on the page.
What I have writen so far doesnt work becuas the function tries to run before the dependecies are loaded.
Any help with this issue is much appreciated.
Let this be your loaded script (configure the first block to meet your wishes). On request of the OP, I've also added a queue function.
Note: Add _REQUIRED_ instead of an external resource, if you want to delay loading additional scripts until the previously requests has been finished.
//#author Rob W. See: http://stackoverflow.com/q/7698018#7698219
//The "real" doThis. You can define any number of arguments.
function _$doThis(a,b){
}
var rw$loader_queue = []; //Unique name, to prevent messing with the code
function doThis(){rw$loader_queue.push(arguments)}
function _doThisAll(){
for(var i=0,len=rw$loader_queue.length; i<len; i++){
//Function.apply: First argument = context (default window)
// second argument = array of arguments
_$doThis.apply(null, rw$loader_queue.shift());
}
}
(function(){//Anonymous function = no variable leaking
//Append the scripts.styles to the head, if existent. Otherwise, try body
var main = document.getElementsByTagName("head")[0] || document.body;
var _REQUIRED_ = ((new Date).getTime()*(new Date).getTime()||Math.random()).toString(36);
/***CONFIGURATION**/
var nocache = false; //false = allow caching
/*Usage: initLoad("type",
(multidimensional) array | string,
optional function: called when initLoad finishes
) */
initLoad("style", [
//'sc-player-widget/css/themes/dark-hive/jquery-ui-1.8.16.custom.css',
'sc-player-widget/css/themes/base/jquery.ui.all.css',
'sc-player-widget/css/themes/base/jquery.ui.selectmenu.css',
'sc-player-widget/css/sc-player.css',
'sc-player-widget/colorbox/colorbox.css'
]);
initLoad("script", [
'http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js',
_REQUIRED_,
'sc-player-widget/js/ui/jquery.ui.core.js',
'sc-player-widget/js/ui/jquery.ui.widget.js',
'sc-player-widget/js/ui/jquery.ui.position.js',
'sc-player-widget/js/ui/jquery.ui.selectmenu.js',
'sc-player-widget/js/ui/jquery.ui.button.js',
'sc-player-widget/colorbox/jquery.colorbox-min.js',
], _doThisAll);
/*END OF CONFIG*/
function initLoad(type, source, ready){
var currentSources = source instanceof Array ? source : [source];
var beforeRequire = 0;
var paused = false;
var hasFinished = false;
var num_resources = 0;
var load_success = 0;
next(); //Start walking through the requested resource;
return;
function next(){
if(!currentSources.length){ //End of sources
if(typeof ready == "string") ready = window[ready];
if(typeof ready == "function" && !hasFinished) (hasFinished=true) && ready();
return;
}
var current = currentSources.shift();
if(current === _REQUIRED_){
if(!currentSources.length) return next();
paused = true;
return;
}
beforeRequire++;
num_resources++;
loadResource(type, current, finished);
if(currentSources.length) next();
// If length == 0, wait until the resources have finished loading
}
function finished(){
load_success++;
if(!--beforeRequire && paused) next();
else if(num_resources == load_success && !currentSources.length) next();
//If end of queue, next() = ready()
}
}
function loadResource(type, src, finish){
if(nocache) src += "?"+(new Date).getTime();
var s = document.createElement(type=="style"?"link":type);
var executed = false;
function oncomplete(){
if(executed) return;
executed = true;
s.onreadystatechange = s.onload = null;
finish();
}
s.onreadystatechange = function(){
if(this.readyState == "loaded" || this.readyState == "complete") oncomplete();
}
s.onload = oncomplete;
if(type == "style"){
s.type = "text/css";
s.rel = "stylesheet";
s.href = src;
} else s.src = src;
main.appendChild(s);
}
})();
In your HTML source:
<script src="path/to/doEverythingScript.js"></script>
...
<script>
doThis(1714581, "#target-div");
</script>
Have you tried using document.write() to insert new <script> and <style> tags into the page to load your resources?