What I'm trying to do is push new ".post" (which have been retrieved by the infinite-scroll script) into my array which holds all of the ".post".
Full Code: https://gist.github.com/sxxfi/8296403
Live Demo: http://soof-show.tumblr.com (navigate through posts with keyboard arrow keys)
Initially, five ".post" show up in the ".content" div, then each time the 'MORE' button is clicked it adds 5 more ".post" to ".content".
The problem is the array length doesn't count for the new ".post" so it keeps looping the first 5.
Thank you in advance if you're able to help solve this.
Javascript:
$(document).ready(function(){
// I N F I N I T E S C R O L L
$('.content').infinitescroll({
navSelector : ".navigation",
nextSelector : ".navigation .next_page",
itemSelector : ".content .post",
behavior: "twitter"
});
$(window).unbind('.infscr');
$('.next_page').click(function(){
$(document).trigger('retrieve.infscr');
return false;
});
$(document).ajaxError(function(e,xhr,opt){
if (xhr.status == 404) $('.next_page').remove();
});
// I M A G E S L I D E R
var obj = $(".post");
var arr = $.makeArray(obj);
var lastDiv = $(".post");
var indexNum = arr.length-1;
$(window).keydown(function (e) {
if (e.keyCode == 37) { // L E F T
lastDiv.fadeOut(800);
indexNum--;
if (indexNum == -1) indexNum = arr.length-1; //if goes beyond first post, goto the last post
$(arr[indexNum]).appendTo(".postslide").fadeIn(800);
// C E N T E R
var width = $(arr[indexNum]).width();
var marginLeft = width / 2;
$(arr[indexNum]).css('margin-left', -marginLeft);
}
if (e.keyCode == 39) { // R I G H T
$('#instruction').fadeOut();
lastDiv.fadeOut(800);
indexNum++;
if (indexNum == arr.length) indexNum = 0; //if goes beyond last post, goto the first post
$(arr[indexNum]).appendTo(".postslide").fadeIn(800);
// C E N T E R
var width = $(arr[indexNum]).width();
var marginLeft = width / 2;
$(arr[indexNum]).css('margin-left', -marginLeft);
}
});
});
UPDATE:
$(document).ready(function(){
// I N F I N I T E S C R O L L
$('.content').infinitescroll({
navSelector : ".navigation",
// selector for the paged navigation (it will be hidden)
nextSelector : ".navigation .next_page",
// selector for the NEXT link (to page 2)
itemSelector : ".content .post",
// selector for all items you'll retrieve
behavior: "twitter"
});
// kill scroll binding
$(window).unbind('.infscr');
// manual click
$('.next_page').click(function(){
$(document).trigger('retrieve.infscr');
return false;
});
// remove the paginator done
$(document).ajaxError(function(e,xhr,opt){
if (xhr.status == 404) $('.next_page').remove();
});
// I M A G E S L I D E R
var obj = $(".post");
var indexNum = obj.length-1;
var lastDiv = $(".post");
$(window).keydown(function (e) {
if (e.keyCode == 37) { // L E F T
lastDiv.fadeOut(800);
indexNum--;
if (indexNum == -1) indexNum = obj.length-1; //if goes beyond first post, goto the last post
$(obj[indexNum]).appendTo(".postslide").fadeIn(800);
// C E N T E R
var width = $(obj[indexNum]).width();
var marginLeft = width / 2;
$(obj[indexNum]).css('margin-left', -marginLeft);
}
if (e.keyCode == 39) { // R I G H T
$('#instruction').fadeOut();
lastDiv.fadeOut(800);
indexNum++;
if (indexNum == obj.length) indexNum = 0; //if goes beyond last post, goto the first post
$(obj[indexNum]).appendTo(".postslide").fadeIn(800);
// C E N T E R
var width = $(obj[indexNum]).width();
var marginLeft = width / 2;
$(obj[indexNum]).css('margin-left', -marginLeft);
}
});
});
-
1I don't see where you're adding anything to the array.Barmar– Barmar2014年01月07日 20:09:46 +00:00Commented Jan 7, 2014 at 20:09
-
@Barmar That's what I need help with, I'm unsure how to add to the array. I tried using push but that didn't work so I took it out the code.digimane– digimane2014年01月07日 20:15:57 +00:00Commented Jan 7, 2014 at 20:15
-
I also don't see where you're getting the 5 new posts, so I can't figure out where you want to add the new elements to the array. Should I be looking at the live demo instead of the question to see what you're talking about?Barmar– Barmar2014年01月07日 20:22:09 +00:00Commented Jan 7, 2014 at 20:22
-
@Barmar Yeah if you 'inspect element' via Chrome on the live demo you can see the new posts being generated in the "content" divdigimane– digimane2014年01月07日 20:23:53 +00:00Commented Jan 7, 2014 at 20:23
-
Does the infinite scroll widget let you supply a callback when the new elements have been added? That's where you need to add to the array.Barmar– Barmar2014年01月07日 20:29:26 +00:00Commented Jan 7, 2014 at 20:29
2 Answers 2
jQuery selectors query the DOM at the moment the jQuery function is called, so they select the elements matching the selector at the given moment.
Even if new elements that should match your selector are added to the DOM later, they will not get selected as nothing notifies jQuery to reevaluate the selector, and even if something did, jQuery has no inner state holding all the jQuery objects created. This means that the selectors are not live.
Your main problem here is that you never update the array of selected DOM elements so even when new elements are added to the DOM you still loop over the original array.
Updating the jQuery object is easy, you just need to recall obj = $(".post")
at any time in your code, and the obj
variable gets updated. We just need to figure out when to call it.
Since you are using Infinite Scroll to load new elements we need to look at it's documentation, or since the documentation currently has large holes in it, it might be better to look at the source code.
Look at the infinitescroll
function:
$.fn.infinitescroll = function infscr_init(options, callback) {
...
The second parameter is called callback
and upon examining the source code and the way this parameter is passed and called, you can see that it will be called after new elements were retrieved. Let's try this parameter out:
$('.content').infinitescroll({
navSelector : ".navigation",
// selector for the paged navigation (it will be hidden)
nextSelector : ".navigation .next_page",
// selector for the NEXT link (to page 2)
itemSelector : ".content .post",
// selector for all items you'll retrieve
behavior: "twitter"
}, function () {
obj = $(".post");
console.log("Current length is: " + obj.length); //this line is not necessary, just here for debug purposes.
});
Assuming that variable obj
already exists this will update it's value.
Note that in a previous version of this answer I called this callback function recountPosts()
. I have decided later that it can simply be an inline anonymous function since it will be only called from one place.
You are nearly finished, but... you have other variables depending on this selector. Namely arr
and lastDiv
. Upon examining your source code it seems that none of them are necessary:
arr
is an array made from the elements selected by the jQuery object and although the jQuery object is not an array, it has a zero based numeric indexer and a length property just like an array, so you can substitutearr
withobj
anywhere in your code.lastDiv
has a misleading name. It holds the same elements asobj
not just the last. Therefore it can also be substituted withobj
.
So the part where you declare your variables can be shrunk to:
// I M A G E S L I D E R
var obj = $(".post");
var indexNum = obj.length-1;
I have also looked at your keydown
event handler. There is a lot of duplicated code in it. This is prone to introduce bugs later on (in fact you already have a bug: if you press left instead of right at the start, the initial picture of the keyboard will not disappear).
Let me restructure it for you a little bit:
$(window).keydown(function (e) {
if (e.keyCode === 37) { // L E F T
indexNum--;
}
if (e.keyCode === 39) { // R I G H T
indexNum++;
}
if (e.keyCode === 37 || e.keyCode === 39) {
$('#instruction').fadeOut();
obj.fadeOut(800);
if (indexNum === -1) {
indexNum = obj.length-1; //if goes beyond first post, goto the last post
}
if (indexNum === obj.length) {
indexNum = 0; //if goes beyond last post, goto the first post
}
$(obj[indexNum]).appendTo(".postslide").fadeIn(800);
var width = $(arr[indexNum]).width();
var marginLeft = width / 2;
$(obj[indexNum]).css('margin-left', -marginLeft);
}
});
And it is done!
No, of course it is not done. It is far from done, there is still many, many things to improve.
Like removing the ugly recalculation of
margin-left
on every keypress and solve it with a few lines of css. As a thumb rule: if something can be solved in css instead of javascript it should be.Or like calling
infinitescroll(...)
on$(".postslide")
instead of$(".content")
and removing the completely unnecessary.appendTo(".postslide")
from beforefadeIn()
, as the elements are already appended and it is a waste of resouces to reappend them.Or moving constants, like the 800 ms fade length to the beginning of the code giving them a variable like
var fadeLength = 800;
and use it.
And I'm pretty sure there are lot of other things that could be done better, but that is just how programming is :)
EDIT:
So you also want the more button only to appear if you are at the end of the slides. You have written this:
if (indexNum === 14){
$('.next_page').html('<img src="http://long_url/">');
}
Why do you hard-code 14 into the source? Why do you even care on how many pictures are loaded and how many will load? The given part of the code need only to know if you are at the end. If you have a variable on how many slides are loaded, you can easily check if you are at the last index. You do have that variable, the obj.length
:
if (indexNum === obj.length-1){
$('.next_page').html('<img src="http://long_url/">');
}
This way the more button will appear on the last slide not caring if it's the 30th, 45th or 9001st.
You would also need this button to disappear after new content has been loaded. This goes into the callback of infinitescroll:
function () {
obj = $(".post");
console.log("Current length is: " + obj.length); //this line is not necessary, just here for debug purposes.
$('.next_page').html('');
}
An other thing you could do is instead of showing the more button, you could trigger the more event from code. This way the new slides get loaded automatically when the user reaches the last slide.
Also this only needs to happen if there are actually more slides to load. You can check the current state's isDone
property, which tells you if the "infinite scroll is done" so there are no more elements to load.
$('.content').data('infinitescroll').options.state.isDone === false
So instead of the part that shows the more button, you could just write this:
if (indexNum === obj.length-1 && !$('.content').data('infinitescroll').options.state.isDone){
$(document).trigger('retrieve.infscr');
}
27 Comments
recountPosts();
to the handler of the MORE button.lastDiv
variable and can simply call obj.fadeOut(800);
return
! It will never be executed! Also since the event is an async call, the given line will possibly run before the pictures are loaded.Problem:
You are setting value as soon as page loads and not updating it when scroll happens or at other key bindings
Solution:
// I M A G E S L I D E R
var obj = $(".post");
var arr = $.makeArray(obj);
var lastDiv = $(".post:last");
var indexNum = arr.length-1;
$(window).keydown(function (e) {
obj = $(".post");
arr = $.makeArray(obj);
lastDiv = $(".post:last");
indexNum = arr.length-1;
//other code
});
5 Comments
obj = $(".post");
again.obj
isn't an array, it's a jQuery object.arr
and indexNum
, but you don't re-assign obj
first.