So I've been learning javascript for 2 months now and I decided to make a scroll thing where when you scroll the page you get more content (more paragraphs in div). I watched tutorial on youtube and did something like that but it doesn't work as i expected. At first it looks like it works but i noticed that i don't need to scroll down(or to scroll to bottom) to load more content but can go scroll up as well. To save you some time (if not interested in reading whole script) this is what i am checking (code near the end of the script):
$(window).scrollTop() + $(window).height() > $("#load_data").height()
Which is in Vanilla JS from my understanding this:
window.innerHeight + window.scrollY > document.querySelector('#load_div').offsetHeight
scrollTop() and scrollY should return(or set) value of pixels from the top of window(in this case) scrolled. Then we add inner height of window. Here is what i don't understand then, Shouldn't those 2 always be bigger then the divs height? So that explains why wherever i scroll it triggers. But how to set this up so it only triggers when i am at the bottom or near it? Here is full code (without php response part which only returns paragraph that got data from database):
<div id="load_data"></div>
<div id="load_data_message"></div>
</div>
<script>
$(document).ready(function(){
var limit = 8;
var start = 0;
var action = 'inactive';
function load_data(limit, start){
$.ajax({
url: 'infinitload.php',
method: 'POST',
data: {limit: limit, start: start},
cache: false,
success: function(data){
$('#load_data').append(data); //Appending data;
if(data == ''){
$('#load_data_message').html('<button>No results found</button>');
action = 'active';//if
}else{
$('#load_data_message').html('<button>Loading Content</button>');
action = 'inactive';
}//else
}//success
})//ajax
}//load_data()
//Loading for the first time
if(action == 'inactive'){
action = 'active';
load_data(limit, start);
}
$(window).scroll(function(){
if( $(window).scrollTop() + $(window).height() > $("#load_data").height() && action == 'inactive'){
$('button:contains("Loading Content")').toggle();
action = 'active';
start = start + limit;
setTimeout(function(){
load_data(limit, start);
}, 1500);
}
});
});//dom ready
</script>
</body>
UPDATE:
I tried code like this http://jsfiddle.net/nick_craver/gWD66/
which totally makes sense and it works in example there but not in my case. Confused. It's probably something else leading to my problem so i ll try to look into my code again. Thanks for all replies.
Try to replace
$("#load_data").height()
with
$("#load_data").offset().top
Problem is finally fixed. I forgot html DOCTYPE thing. Never got annoyed more :)
Related
so I want to know how I can get what section or part of my html I’m currently on. An example
So how do I know if a user has already scrolled down over part 2 using JavaScript
Or if they’re currently at part 1
<html>
<head>
</head>
<body>
<section class=“part 1”>
</section>
<section class= “part2>
</section>
</body>
</html>
The following codes will give you a little idea about how to handle this situation. Essentially you are going to want to get the scrollbar position which you can do using:
document.documentElement.scrollTop
You also want to get a range where the element you are looking for resides, in our case, it is .part1 and .part2. We can get that range by using offsetTop as the beginning of the limit and offsetTop + clientHeight to determine the end.
You are going to have to keep track of the window scroll event.
The following example is generic:
$(window).scroll(function(e) {
if (document.documentElement.scrollTop > 0
&& document.documentElement.scrollTop < $('.part2').offset().top ) {
$('div').html("At part1")
} else {
$('div').html("At part2")
}
});
JSFiddle
Likewise, if you want a little bit of modularity:
$(window).scroll(function(e) {
let watchList = ['part1', 'part2', 'part3'];
let scrollTop = document.documentElement.scrollTop;
for (var classname of watchList) {
let el = document.getElementsByClassName(classname)[0];
if (scrollTop > el.offsetTop &&
scrollTop < el.offsetTop + el.clientHeight) {
$('div').html("At <strong>"+classname+"</strong>");
}
}
});
JSFiddle
The possibilities are limitless to continue and make this more useful, but I'll leave that up to you.
you can use is[":focus"] function to find which div has focus currently.
if($(".part1").is(":focus"))
{
//you code
}
else if($(".part2").is(":focus"))
{
//you code
}
you can use mouseenter function it fires when the mouse goes into that div for the first time.
$(".part1").on('mouseenter', function(){
//your command
});
you can use mouseover function to find where is mouse right now. it fires when mouse moves inside that div.
$(".part1").on('mouseover', function(){
//your command
});
You can use javascripts offsetTop functionality. This is a parameter that returns how far down from the top a div is in the number of pixels.
It can also return how far down the user has scrolled when called on the window object itself. Then it's just a matter of math. See if the user has scolled down far enough to be past the div in reference.
For example:
var part1DivOffset = document.getElementsByClassName("part 1")[0].offsetTop;
var part2DivOffset = document.getElementsByClassName("part2")[0].offsetTop;
var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
This code will get you 3 variables. The first 2 lines save the offsetTop of the div's. While the third line detects how far down the user has scrolled. Then you can do math with the variables:
if(scrollTop >= part1DivOffset){
//we are past part1
}
if(scrollTop >= part2DivOffset){
//We are past part 2
}
if(scrollTop >= part1DivOffset && scrollTop < part2DivOffset){
//We are past part 1 but not past part 2
}
I am using infinity scroll in my project. First it will fetch few records from MySQL database and show on page. Once page scroll down it make ajax query to server and load more data.
Is it possible to fetch all data at once from mysql database in json format and then perform load more on client side. So, basically I want no ajax request to database.
Here is the code which is working fine if I make ajax request on page scroll.
flag = true;
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() == $(document).height()){
first = $('#first').val();
limit = $('#limit').val();
no_data = true;
if(flag && no_data){
flag = false;
$('#loader').show();
$.ajax({
url : 'ajax_html.php',
method: 'post',
data: {
start : first,
limit : limit
},
success: function( data ) {
flag = true;
$('#loader').hide();
if(data !=''){
first = parseInt($('#first').val());
limit = parseInt($('#limit').val());
$('#first').val( first+limit );
$('#timeline-conatiner').append( '<li class="year">'+year+'</li>');
$('#timeline-conatiner').append( data );
year--;
}else{
alert('No more data to show');
no_data = false;
}
},
error: function( data ){
flag = true;
$('#loader').hide();
no_data = false;
alert('Something went wrong, Please contact admin');
}
});
}
}
});
This is untested and just an example of the method, Sorry I don't have time for a full example but this should give you the idea.
//data cache
var cache = ['one','two', 'three' .....];
//current location in cache
var current = 0;
//ajax request object
var request;
if($(window).scrollTop() + $(window).height() == $(document).height()){
var cache_total = cache.length;
//add next item to page & increase the current pointer
$('#timeline-conatiner').append( cache[current] );
++current;
if( current - cache.length < 50 ){
//if we only have 50 items not shown in cache pull next results with ajax and add to cache
if( !request ){
//also youll want to keep track if you have a pending request that hasn't returned yet, to prevent race conditions.
request = $.ajax( .... ).success( function( data ){
$.extend( cache, data ); //or push each if you like
}).always( function(){
request = false; //should be false on finish but just because.
});
} //end if request
//make sure to properly offset the data using the total in the cache to prevent pulling duplicates. eg SELECT ... LIMIT {cache.length}, 50
} // end if cached
} //end scroll
See the issue with what you have above is the user needs to wait for the ajax call to finish, which essentially turns it into a synchronous request ( like reloading the page ). You want to keep it asynchronous as ajax is intended to be. So instead of displaying the results of the ajax, you buffer them and keep some data you are not showing. Then on scroll show some buffered data and after that fill the cache back up.
Obviously this makes it much more complex to code, but the advantage is you don't need to pull a massive amount of data at one time and the user wont get the delay from halting on the ajax request. You'll have to balance the time the request takes with the amount of data in the cache so you always have some data in there. This depends on how much space data takes to render and how long the ajax query takes.
I would say you don't need infinity scroller (or whatever) at all to do what u want... but since u have it all nicely working and I suspect u might decide u want that 'lazy load' type functionality back at some point u could do the lazy thing...
Simply try playing around with the first and limit param values in your example...
Say...
first = 0 and
limit = some really big number...
I think u can set some values and force a single ajax call on page load very easily.
If all data has been retrieved and added to DOM, then following script may be helpful. First add hidden class to extra elements. Run this script after DOM is created.
$(".elements").each(function(index, el) { // `elements` is the common class for all elements on which this functionality is to be done
if (index >= maxLimitToShow) { // maxLimitToShow - number of elements to show in the beginning
$(el).addClass("hidden"); // for extra elements
}
});
Add css for class hidden
.hidden {
display: none;
}
And check when scroll has reached the bottom of page to show more items.
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() == $(document).height()) { // checks if scroll reached at bottom
var elements = $(".elements.hidden"); // fetch all hidden elements
elements.each(function (index, el) {
if (index < singleRowLimit) { // singleRowLimit - number of elements to show in one scroll
$(el).removeClass('hidden');
}
});
}
});
EDIT - without using CSS - hide/show
If all data has been retrieved and added to DOM, then following script may be helpful. First add hidden class to extra elements. Run this script after DOM is created.
$(".elements").each(function(index, el) { // `elements` is the common class for all elements on which this functionality is to be done
if (index >= maxLimitToShow) { // maxLimitToShow - number of elements to show in the beginning
$(el).addClass("hidden").hide(); // for extra elements
}
});
And check when scroll has reached the bottom of page to show more items.
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() == $(document).height()) { // checks if scroll reached at bottom
var elements = $(".elements.hidden"); // fetch all hidden elements
elements.each(function (index, el) {
if (index < singleRowLimit) { // singleRowLimit - number of elements to show in one scroll
$(el).removeClass('hidden').show();
}
});
}
});
Ok I will try to make it simple.
1) I have 3 links that execute an Ajax Request and update a div with some content.
The DIV
<div id="content-to-update"></div>
The 3 links that update #content-to-update
example 1
example 2
example 3
Each link update the div #content-to-update with the content just below with one parameter named CODE
The the div #content-to-update is updated with the code below.
var loading = false;
$(window).scroll(function () {
var winTop = $(window).scrollTop();
var docHeight = $(document).height();
var winHeight = $(window).height();
//if user reach bottom of the page
if (!loading && (winTop / (docHeight - winHeight)) > 0.95) {
loading = true;
//the CODE parameter is different on each call from the links that I
//talked earlier.
$.get("/items/next/?list_name=" + CODE, function(data){
//executing some javascript to display next items
}).done(function() {
loading = false;
})
}
});
});
The problem is that it seams the browser keep all different version of the updated div.
Its like the old content is not erased before the new content is added.
If I click on the first link and scroll I get the right items OK!.
Then if I click on the second link, when I scroll I get the Item twice (duplicated- it calls the code from the previous ajax call)
Then if I click on the third link, when I scroll I get the Item 3 times. (it calls the code from the 2 previous ajax call )
When I use the Chrome debugger I see that it goes first in the code that have received the parameter EXAMPLE_1 then it goes in the code that has received the parameter EXAMPLE_2 etc
But this code should has been overridden by the call of the EXAMPLE_2 link.
It is difficult to explain I don't know if someone understand what Im trying to explain but I give it a try :) and again sorry for my english.
Thanks
I'm a bit picky about POST and GET, so even though Wayne is technically correct, the fact that you are retrieving data makes your use of GET the right way of doing it.
The way around caching is either by using jQuery's ajax method and setting cache to false, like so:
$.ajax({
url: "/items/next/?list_name=" + CODE,
type: 'GET',
success: function(data) {
$('#content-to-update').html(data);
},
cache:false,
error: function(e) {
alert("Server failure! Is the server turned off?");
}
});
You can also trick the browser by adding a random string to the end of the URL, which is what I usually do. Something like this:
$.get("/items/next/?list_name=" + CODE + '&cache_buster=' + (new Date()).getTime().toString(), function(data){
//executing some javascript to display next items
}).done(function() {
loading = false;
})
If you are using .html() to set the content, the error is definitely somewhere else. Ensure that you are not appending the new content to the div, which seems like what you are doing.
Also, your functions should act independently of one another. Your current process seems to support that, but your problem seem to suggest otherwise.
Try the suggestions first and if they don't work, post more code.
Update
Try this:
var loading = false;
function executeSomeAjax(CODE){
$(window).scroll(function () {
var winTop = $(window).scrollTop();
var docHeight = $(document).height();
var winHeight = $(window).height();
//if user reach bottom of the page
if (!loading && (winTop / (docHeight - winHeight)) > 0.95) {
loading = true;
//the CODE parameter is different on each call from the links that I
//talked earlier.
$.get("/items/next/?list_name=" + CODE, function(data){
//executing some javascript to display next items
}).done(function() {
loading = false;
})
}
});
});
}
As you can see, the variable loading is now a global variable. I suspect that it was a local variable in your original function and as a result was set to false anytime the function ran.
Making it a global variable should resolve your issue.
Hope this helps.
UPDATE
Ok this is the final working code thanks to everybody for helping me out !
I think the problem was coming from low memory on my computer. The code you see below was used yesterday and was not working.
Since I rebooted the computer this morning everything works like a charm. I have 4GO of memory and working with Grails 2.2.2 and Intellij IDEA Im often with 100Mo of memory left I guess this should have a side effect. I cant see other explanations.
If That can help anyone to read this post
var loading = false;
function nextProject(){
$('.logo').html('<img src="/images/ajax-loader-transparent.gif">');
$.ajax({
type:'GET',
url: "/project/next/",
data:"list_name=" + CODE,
beforeSend:function(){
console.log("loading : " + loading)
}
}).done(function(data) {
if(data != ""){
var arrayOfObjects = eval(data);
for(var i=0; i < arrayOfObjects.length; i++){
TrackManager.newTrack(btoa(arrayOfObjects[i].base64Params));
var projectMgr = new ProjectManager(arrayOfObjects[i].id);
projectMgr.socialShare();
<sec:ifNotLoggedIn >
projectMgr.runDeny();
</sec:ifNotLoggedIn>
<sec:ifLoggedIn >
projectMgr.runGranted(arrayOfObjects[i].likeUp, arrayOfObjects[i].inPlayList );
</sec:ifLoggedIn>
INC++;
}
loading = false;
$('.logo').html('<img src="/images/soundshare_logo_32.png">');
console.log(INC + "/" + PROJECT_COUNT );
}
}).fail(function(){
console.error("Ajax error!")
});
}
$(window).scroll(function(){
var winTop = $(window).scrollTop();
var docHeight = $(document).height();
var winHeight = $(window).height();
if ((winTop / (docHeight - winHeight)) > 0.95) {
if(INC < PROJECT_COUNT){
if(!loading){
loading = true;
nextProject()
}
}
}
});
I am building a website which can be viewed here: argit.bounde.co.uk
I have done the majority of the content and I am now trying to work on the navigation. I have three navigation bars (only one is ever visible) and need this method to work no matter which is showing. If you resize your browser to make your window narrower that will show a second, and then when you scroll the navigation that appears is a third.
I have got it working to a fashion but the problem is when I click a link it jumps to where it wants to go momentarily, then returns and then scrolls as it is meant to. This is because of the "href="#target" that i have left in the nav. I have tried including a "return false" but then if the broswer doesnt support JS then the navigation doesnt work at all.
The next problem is I want a way to make the target "over". Currently when you click a link it scrolls to the selected one and the nav updates which link is "over" as it passes them. I want this for when the user is scrolling up and down the page, but if they click a link I want that link to be "over" (and the respective links from other navigations) and not be affected by the scroll checks that would normally override it.
The solution I am using for my onClick navigation is below, I know there are plug ins that will do this kind of thing but I want to write it myself so i can get a better understanding of jQuery. Im not sure if the solution I am using at the moment is a good one, if not please advise me:
function navigation() {
$('html, body').stop().animate({
scrollTop: $($(this).attr('href')).offset().top
}, 1500);
}
The solution I am using for the scrolling checks I found by accident and is below, It works by over riding the equation above it and is actually quite simple. There is also an action to fix the navigation when scrolling.
function navCheck() {
var documentHeight = $(document).height();
var windowHeight = $(window).height();
var windowTop = $(window).scrollTop() + 100;
var navTop = $("#scrollanchor").offset().top;
var mobileTop = $("#mobileanchor").offset().top;
var mobileHeight = $("#mobileanchor").height();
var overviewTop = $("#slider").offset().top;
var bioTop = $("#bio").offset().top;
var solutionsTop = $("#solutions").offset().top;
var experianceTop = $("#experiance").offset().top;
var contactTop = $("#contact").offset().top;
if($(window).scrollTop() > navTop) {
$("#leftfixer").addClass("leftfix");
} else {
$("#leftfixer").removeClass("leftfix");
}
if($(window).width() < 1200){
if(windowTop - 90 > mobileTop + mobileHeight) {
$("#mobilefix").slideDown();
} else {
if(windowTop - 96 <= mobileTop) {
$("#mobilefix").hide();
}
}
} else {
$("#mobilefix").slideUp();
}
$("li").removeClass("over");
$("li.navoverview").addClass("over");
if(windowTop > bioTop) {
$("li").removeClass("over");
$("li.navbio").addClass("over");
}
if(windowTop > solutionsTop) {
$("li").removeClass("over");
$("li.navsolutions").addClass("over");
}
if(windowTop > experianceTop) {
$("li").removeClass("over");
$("li.navexperiance").addClass("over");
}
if(windowTop > contactTop || windowTop > documentHeight - windowHeight) {
$("li").removeClass("over");
$("li.navcontact").addClass("over");
}
}
This is my first post here so if I have missed out any information Im sorry! I have also looked for similar posts but it seems most people go for a plugin when doing this kind of thing. Thank you
UPDATE: The page jumping to the anchor has been fixed
Normally, if you use JavaScript to scroll the page, you would put return false; after calling the function. This prevents the page from scrolling momentarily to the anchor.
Something like
function navigation() {
$('html, body').stop().animate({
scrollTop: $($(this).attr('href')).offset().top
}, 1500);
return false;
}
I have been building my own carasol over the past few days.
My Jquery is based on tutorials on the web and also from help and advice from SO.
I am not a Jquery guru just an enthusiast and think my code is a little sloppy, hence the post.
here is a link to the working code: http://jsfiddle.net/JHqBA/2/ (updated link)
basically what happens is:
if someone hits the page with a # values in the url it will show the appropriate slide and example would be www.hello.com#two, this would slide to slide two
if someone clicks the numbers it will show the appropriate slide
next and prev also slide through the slides.
The question is, is there anything i could have wrote better as i know there is alot of duplicate code.
I understand its a big ask but it would help me learn a little more (i think my code is a little old school)
if anyone has any questions please feel free to ask and ill answer what it does or is supposed to do.
Sluap
--- Edit ----
I have made only one aniamtion function now which has got rid of alot of duplicate code.
I have yet to look into on function but will do soon.
I would like to know more about the create a new function, outside of the jQuery ready block as i cant get this working or quite understand how i can get it to work sorry
any more tips would be great ill carry on working on this project till i am happy with it.
also is there a better way to write:
if ($slideNumber == 1) {
$('#prev').attr("class", "not_active")
$('#next').attr("class", "active")
}
else if ($slideNumber == divSum) {
$('#next').attr("class", "not_active");
$('#prev').attr("class", "active");
}
else {
$('#prev').attr("class", "active")
$('#next').attr("class", "active")
};
Jquery full:
$(document).ready(function () {
//////////////////////////// INITAL SET UP /////////////////////////////////////////////
//Get size of images, how many there are, then determin the size of the image reel.
var divWidth = $(".window").width();
var divSum = $(".slide").size();
var divReelWidth = divWidth * divSum;
//Adjust the image reel to its new size
$(".image_reel").css({ 'width': divReelWidth });
//set the initial not active state
$('#prev').attr("class", "not_active");
//////////////////////////// SLIDER /////////////////////////////////////////////
//Paging + Slider Function
rotate = function () {
var triggerID = $slideNumber - 1; //Get number of times to slide
var image_reelPosition = triggerID * divWidth; //Determines the distance the image reel needs to slide
//sets the active on the next and prev
if ($slideNumber == 1) {
$('#prev').attr("class", "not_active")
$('#next').attr("class", "active")
}
else if ($slideNumber == divSum) {
$('#next').attr("class", "not_active");
$('#prev').attr("class", "active");
}
else {
$('#prev').attr("class", "active")
$('#next').attr("class", "active")
};
//Slider Animation
$(".image_reel").animate({
left: -image_reelPosition
}, 500);
};
//////////////////////////// SLIDER CALLS /////////////////////////////////////////////
//click on numbers
$(".paging a").click(function () {
$active = $(this); //Activate the clicked paging
$slideNumber = $active.attr("rel");
rotate(); //Trigger rotation immediately
return false; //Prevent browser jump to link anchor
});
//click on next button
$('#next').click(function () {
if (!$(".image_reel").is(':animated')) { //prevent clicking if animating
var left_indent = parseInt($('.image_reel').css('left')) - divWidth;
var slideNumberOn = (left_indent / divWidth);
var slideNumber = ((slideNumberOn * -1) + 1);
$slideNumber = slideNumber;
if ($slideNumber <= divSum) { //do not animate if on last slide
rotate(); //Trigger rotation immediately
};
return false; //Prevent browser jump to link anchor
}
});
//click on prev button
$('#prev').click(function () {
if (!$(".image_reel").is(':animated')) { //prevent clicking if animating
var left_indent = parseInt($('.image_reel').css('left')) - divWidth;
var slideNumberOn = (left_indent / divWidth);
var slideNumber = ((slideNumberOn * -1) - 1);
$slideNumber = slideNumber;
if ($slideNumber >= 1) { //do not animate if on first slide
rotate(); //Trigger rotation immediately
};
}
return false; //Prevent browser jump to link anchor
});
//URL eg:www.hello.com#one
var hash = window.location.hash;
var map = {
one: 1,
two: 2,
three: 3,
four: 4
};
var hashValue = map[hash.substring(1)];
//animate if hashValue is not null
if (hashValue != null) {
$slideNumber = hashValue;
rotate(); //Trigger rotation immediately
return false; //Prevent browser jump to link anchor
};
});
Question and answer has been moved over to https://codereview.stackexchange.com/questions/8634/jquery-carasol-build-finished-and-would-like-advice-on-best-practice-neateni/8635#8635
1) Separation of Concerns
Start by refactorring your code in to more granular functions.
You can read more about SoF at http://en.wikipedia.org/wiki/Separation_of_concerns
Update:
E.g. Instead of having your reel resizing code inline, put it in it's own function, like this:
function setImageReelWidth () {
//Get size of images, how many there are, then determin the size of the image reel.
var divWidth = $(".window").width();
var divSum = $(".slide").size();
var divReelWidth = divWidth * divSum;
//Adjust the image reel to its new size
$(".image_reel").css({ 'width': divReelWidth });
}
This achieves 2 things:
a. First, it groups a block of code that is logically cohesive, removing it from the main code which results in a much cleaner code habitat.
b. It effectively gives a label to the code block via the function name that is descriptive of what it does, and therefore makes understanding of the code much simpler.
Later, you can also encapsulate the whole thing in it's own "class" (function) and you can move it into it's own js file.
2) The jQuery "on" function
Use the "on" function to attach your click events, rather than the "click" function.
http://api.jquery.com/on/
This has the added advantage of also binding it to future elements matching your selector, even though they do not exist yet.
3) The ready function
// I like the more succinct:
$(handler)
// Instead of:
$(document).ready(handler)
But you might like the more obvious syntax.
Those are just a few things to start with.
-- Update 1 --
Ok, StackOverflow is not really suited to a refactoring work in progress, but we'll make do. I think you should keep your original code block in your question, so that future readers can see where it started and how it systematically improved.
I would like to know more about the create a new function, outside of
the jQuery ready block as i cant get this working or quite understand
how i can get it to work sorry
I am not familiar with jsfiddle.net, but it looks cool and helpful, but might also be a bit confusing if you don't know what is going on. I am not sure I do :), but I think that script editor window results in a .js file that is automatically referenced by the html file.
So here is an example of a function defined outside of the ready block, but referenced from within.
function testFunction () {
alert ('it works');
}
$(document).ready(function () {
testFunction();
// ... other code
});
This should pop up an alert box that says, "it works" when the page is loaded.
You can try it for yourself.
Then, once you got that working, you can refactor other logically cohesive blocks of code into their own functions. Later you can wrap them all up into their own javascript 'class'. But we'll get to that.