Stop infinite scroll from jumping to end of page when loading content - javascript

I have implemented infinite scroll with isotope on my website and I am having a problem with loading posts. The posts load fine, but lets say I scroll down to load more posts but I am still viewing the posts already there, infinite scroll jumps to the bottom of the page whenever new posts are loaded. How can I stop this behavior and maintain the position on the page even when more posts are loaded?
My script --
$(function () {
var selectChoice, updatePageState, updateFiltersFromObject,
$container = $('.isotope');
////////////////////////////////////////////////////////////////////////////////////
/// EVENT HANDLERS
////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Mark filtering element as active/inactive and trigger filters update
$('.js-filter').on( 'click', '[data-filter]', function (event) {
event.preventDefault();
selectChoice($(this), {click: true});
$container.trigger('filter-update');
});
//////////////////////////////////////////////////////
// Sort filtered (or not) elements
$('.js-sort').on('click', '[data-sort]', function (event) {
event.preventDefault();
selectChoice($(this), {click: true});
$container.trigger('filter-update');
});
//////////////////////////////////////////////////////
// Listen to filters update event and update Isotope filters based on the marked elements
$container.on('filter-update', function (event, opts) {
var filters, sorting, push;
opts = opts || {};
filters = $('.js-filter li.active a:not([data-filter="all"])').map(function () {
return $(this).data('filter');
}).toArray();
sorting = $('.js-sort li.active a').map(function () {
return $(this).data('sort');
}).toArray();
if (typeof opts.pushState == 'undefined' || opts.pushState) {
updatePageState(filters, sorting);
}
$container.isotope({
filter: filters.join(''),
sortBy: sorting
});
});
//////////////////////////////////////////////////////
// Set a handler for history state change
History.Adapter.bind(window, 'statechange', function () {
var state = History.getState();
updateFiltersFromObject(state.data);
$container.trigger('filter-update', {pushState: false});
});
////////////////////////////////////////////////////////////////////////////////////
/// HELPERS FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Build an URI to get the query string to update the page history state
updatePageState = function (filters, sorting) {
var uri = new URI('');
$.each(filters, function (idx, filter) {
var match = /^\.([^-]+)-(.*)$/.exec(filter);
if (match && match.length == 3) {
uri.addSearch(match[1], match[2]);
}
});
$.each(sorting, function (idx, sort) {
uri.addSearch('sort', sort);
});
History.pushState(uri.search(true), null, uri.search() || '?');
};
//////////////////////////////////////////////////////
// Select the clicked (or from URL) choice in the dropdown menu
selectChoice = function ($link, opts) {
var $group = $link.closest('.btn-group'),
$li = $link.closest('li'),
mediumFilter = $group.length == 0;
if (mediumFilter) {
$group = $link.closest('.js-filter');
}
if (opts.click) {
$li.toggleClass('active');
} else {
$li.addClass('active');
}
$group.find('.active').not($li).removeClass('active');
if (!mediumFilter) {
if ($group.find('li.active').length == 0) {
$group.find('li:first-child').addClass('active');
}
$group.find('.selection').html($group.find('li.active a').first().html());
}
};
//////////////////////////////////////////////////////
// Update filters by the values in the current URL
updateFiltersFromObject = function (values) {
if ($.isEmptyObject(values)) {
$('.js-filter').each(function () {
selectChoice($(this).find('li').first(), {click: false});
});
selectChoice($('.js-sort').find('li').first(), {click: false});
} else {
$.each(values, function (key, val) {
val = typeof val == 'string' ? [val] : val;
$.each(val, function (idx, v) {
var $filter = $('[data-filter=".' + key + '-' + v + '"]'),
$sort = $('[data-sort="' + v + '"]');
if ($filter.length > 0) {
selectChoice($filter, {click: false});
} else if ($sort.length > 0) {
selectChoice($sort, {click: false});
}
});
});
}
};
////////////////////////////////////////////////////////////////////////////////////
/// Initialization
////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Initialize Isotope
$container.imagesLoaded( function(){
$container.isotope({
masonry: { resizesContainer: true },
itemSelector: '.item',
getSortData: {
date: function ( itemElem ) {
var date = $( itemElem ).find('.thedate').text();
return parseInt( date.replace( /[\(\)]/g, '') );
},
area: function( itemElem ) { // function
var area = $( itemElem ).find('.thearea').text();
return parseInt( area.replace( /[\(\)]/g, '') );
},
price: function( itemElem ) { // function
var price = $( itemElem ).find('.theprice').text();
return parseInt( price.replace( /[\(\)]/g, '') );
}
}
});
var total = $(".next a:last").html();
var pgCount = 1;
var numPg = total;
// jQuery InfiniteScroll Plugin.
$container.infinitescroll({
contentSelector : '.isotope',
speed : 'fast',
behavior: 'simplyrecipes',
navSelector : '#pagi', // selector for the paged navigation
nextSelector : '#pagi a.next', // selector for the NEXT link (to page 2)
itemSelector : '.item', // selector for all items you'll retrieve
animate: true,
debug: true,
loading: {
selector: '#infscr-loading',
finishedMsg: 'No more content to load.'
}
},
// Trigger Masonry as a callback.
function( newElements ) {
pgCount++;
if(pgCount == numPg) {
$(window).unbind('.infscr');
$container.isotope('reload');
$container.append( newElements ).isotope( 'appended', newElements, true );
$('#infscr-loading').find('em').text('No more content to load.');
$('#infscr-loading').animate({
opacity: 1
}, 200);
setTimeout(function() {
$('#infscr-loading').animate({
opacity: 0
}, 300);
});
} else {
loadPosts(newElements);
}
});
});
function loadPosts(newElements) {
// Hide new posts while they are still loading.
var newElems = $( newElements ).css({ opacity: 0 });
// Ensure that images load before adding to masonry layout.
newElems.imagesLoaded(function() {
// Show new elements now that they're loaded.
newElems.animate({ opacity: 1 });
$container.isotope( 'appended', newElems, true );
});
}
//////////////////////////////////////////////////////
// Initialize counters
$('.stat-count').each(function () {
var $count = $(this),
filter = $count.closest('[data-filter]').data('filter');
$count.html($(filter).length);
});
//////////////////////////////////////////////////////
// Set initial filters from URL
updateFiltersFromObject(new URI().search(true));
$container.trigger('filter-update', {pushState: false});
});
});

You can start by overriding the default animate property of infinite-scroll
$container.infinitescroll({
animate:false, //this does just that
});
And about your dublicate entry for every request (or perhaps some requests ),
It has something to do with your backend i.e the way you are offseting the data
Infinite Scroll works great with page numbers i.e it
Sends 2,3,4,5,6 ... So For every request its sends that request.
I think you are using page offset instead , and you will probably end up with duplicate entries .
If you are using php as your receiving end ... this might help
//In your Controller
$temp = ( $page_offset * $per_page );
$offset = $temp - $per_page ;
//In your model
$this->db->query("SELECT * FROM GALLERY OFFSET $offset limit 10");
This is just a rough idea , I hope it helps.
If it does , Care to Check out Sticky Bubble ? - A game available for free on Google Play .
Cheers :)

Related

Item soft rejected due to Proper Event Binding issue

An item I've submitted to themeforest.net got soft rejected with the following message:
PROPER EVENT BINDING: Consider using the preferred .on() method rather than .click(), .bind(), .hover(), etc. For best performance and concise code use event delegation whenever possible
I have no idea what to do actually and would appreciate some help.
This is my code (it’s quite long sorry):
jQuery(document).ready(function($) {
"use strict";
// PRELOADER
$(window).load(function() {
$('#preloader').fadeOut('slow', function() {
$(this).remove();
});
});
// NAV BR RESIZING
$(document).on("scroll", function() {
if ($(document).scrollTop() > 50) {
$("header").removeClass("large").addClass("small");
} else {
$("header").removeClass("small").addClass("large");
}
});
// MOBILE MENU TRIGGER
$('.menu-item').addClass('menu-trigger');
$('.menu-trigger').click(function() {
$('#menu-trigger').toggleClass('clicked');
$('.container').toggleClass('push');
$('.pushmenu').toggleClass('open');
});
// SEARCH
$('.search').click(function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
$('.close-search').click(function(e) {
$(".search-overlay").removeClass("visible");
e.preventDefault();
});
// FOUNDATION INITIALIZER
$(document).foundation();
// LIGHTCASE
$('a[data-rel^=lightcase]').lightcase({
showSequenceInfo: false,
});
// CONTDOWN
$('[data-countdown]').each(function() {
var $this = $(this),
finalDate = $(this).data('countdown');
$this.countdown(finalDate, function(event) {
$this.html(event.strftime('' +
'<span class="time">%D <span>days</span></span> ' +
'<span class="time">%H <span>hr</span></span> ' +
'<span class="time">%M <span>min</span></span> ' +
'<span class="time">%S <span>sec</span></span>'));
});
});
// SCROLLDOWN BUTTON
$(".show-scrolldown-btn").append("<div class='scrolldown-btn reveal-from-bottom'></div>")
$('.scrolldown-btn').on('click', function() {
var ele = $(this).closest("div");
// this will search within the section
$("html, body").animate({
scrollTop: $(ele).offset().top + 70
}, 500);
return false;
});
// ISOTOPE MASONRY
$(window).load(function() {
var $container = $('.grid');
$container.isotope({
itemSelector: '.grid-item',
columnWidth: '.grid-sizer',
});
var $optionSets = $('.filter'),
$optionLinks = $optionSets.find('a');
$optionLinks.click(function() {
var $this = $(this);
if ($this.hasClass('active')) {
return false;
}
var $optionSet = $this.parents('.filter');
$optionSet.find('.active').removeClass('active');
$this.addClass('active');
// make option object dynamically, i.e. { filter: '.my-filter-class' }
var options = {},
key = $optionSet.attr('data-option-key'),
value = $this.attr('data-option-value');
value = value === 'false' ? false : value;
options[key] = value;
if (key === 'layoutMode' && typeof changeLayoutMode === 'function') {
changeLayoutMode($this, options);
} else {
$container.isotope(options);
}
return false;
});
});
//BACK TO TOP
var offset = 300,
offset_opacity = 1200,
scroll_top_duration = 700,
$back_to_top = $('.backtotop');
$(window).scroll(function() {
($(this).scrollTop() > offset) ? $back_to_top.addClass('is-visible'): $back_to_top.removeClass('is-visible fade-out');
if ($(this).scrollTop() > offset_opacity) {
$back_to_top.addClass('fade-out');
}
});
$back_to_top.on('click', function(event) {
event.preventDefault();
$('body,html').animate({
scrollTop: 0,
}, scroll_top_duration);
});
});
So you would change event listener assignments like the following:
$('.search').click(function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
...to use the corresponding on method instead, passing the event name as an argument:
$('.search').on("click", function(e) {
$(".search-overlay").addClass("visible");
e.preventDefault();
});
Event delegation is avoiding adding several event listeners to specific nodes and instead adding a single event listener to a common parent element, which then looks to see which child element was clicked on.
There's a good article here:
https://www.google.co.uk/amp/s/davidwalsh.name/event-delegate/amp

Isotope with Filter Count and imagesLoaded

I have a large grid of objects that I'm filtering with isotope. I'm using combination filters and a search box, and I'm also using imagesLoaded (necessary because of the large number of items). That all works ok.
I also want to print the number of items filtered. I followed instructions here
And here's my code:
jQuery(document).ready(function($){
// quick search regex
var qsRegex;
// init Isotope
var $grid = $('.grid').imagesLoaded( function() {
$grid.isotope({
itemSelector: '.status',
getSortData: {
name: '.elemsort',
},
sortBy : 'name',
});
});
var iso = $grid.data('isotope');
var $filterCount = $('.filter-count');
// store filter for each group
var filters = {};
$('.filtersbutton').on( 'click', '.button', function() {
var $this = $(this);
// get group key
var $buttonGroup = $this.parents('.button-group');
var filterGroup = $buttonGroup.attr('data-filter-group');
// set filter for group
filters[ filterGroup ] = $this.attr('data-filter');
// combine filters
var filterValue = concatValues( filters );
// set filter for Isotope
$grid.isotope({ filter: filterValue });
updateFilterCount();
});
// bind filter on select change
$('.filterstextsearch').on( 'change', '.button-group', function() {
var $this = $(this);
// get group key
var filterGroup = $this.attr('data-filter-group');
// set filter for group
filters[ filterGroup ] = $this.find(':selected').attr('data-filter');
// combine filters
var filterValue = concatValues( filters );
// set filter for Isotope
$grid.isotope({ filter: filterValue });
updateFilterCount();
$(':selected', this).addClass('is-checked').siblings().removeClass('is-checked')
});
// change is-checked class on buttons
$('.button-group').each( function( i, buttonGroup ) {
var $buttonGroup = $( buttonGroup );
$buttonGroup.on( 'click', '.button', function() {
$buttonGroup.find('.is-checked').removeClass('is-checked');
$( this ).addClass('is-checked');
});
});
// flatten object by concatting values
function concatValues( obj ) {
var value = '';
for ( var prop in obj ) {
value += obj[ prop ];
}
return value;
}
// use value of search field to filter
var $quicksearch = $('.isofilter').keyup( debounce( function() {
qsRegex = new RegExp( $quicksearch.val(), 'gi' );
$grid.isotope({
filter: function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
});
updateFilterCount();
}, 200 ) );
function updateFilterCount() {
$filterCount.text( iso.filteredItems.length + ' items' );
}
updateFilterCount();
// debounce so filtering doesn't happen every millisecond
function debounce( fn, threshold ) {
var timeout;
return function debounced() {
if ( timeout ) {
clearTimeout( timeout );
}
function delayed() {
fn();
timeout = null;
}
timeout = setTimeout( delayed, threshold || 100 );
}
}
});
I suspect the reason it doesn't work is that I use imagesLoaded? In console I get "TypeError: iso is undefined". But I can't figure out how to fix it.
Alright, 2 days later, I managed to find a solution by myself. The problem was because of imagesLoaded. So I replaced the "// init Isotope" portion of the code like this and now the counts are working.
// init Isotope
var $grid = $('.breakdown').isotope({
itemSelector: '.status',
getSortData: {
name: '.elementsort',
},
sortBy : 'name',
});
$grid.imagesLoaded().progress( function() {
$grid.isotope('layout');
});
The issue with this option is that the page is much slower to load now than when I initialized Isotope after all images were loaded. If anyone has a solution to this, it would be much appreciated.

Filtering items with infinite-scroll

I'm using masonry to generate 'tiles' - which I'm filtering with jQuery, and infinite scroll - which I'm using to load more tiles server-side.
The filter works, however once a filter is applied and more tiles are loaded via the infinite scroll, unfiltered items are loaded. I know that the reason behind this is because the unloaded tiles aren't generated client side yet, but I'd rather keep it that way because the website is going to get very data-heavy very fast.
Is there any way to do this without loading all of the items client side? I would be extremely appreciative of any input. I've included the infinite scroll script below. I noticed this link (www.creativebloq.com/web-design/filter-web-content-infinite-scroll-10134808), which sounds fairly relatable, however I'm not sure how it would be implemented.
!function($){
var $container = $(InfiniteConfig.container);
var pageCount = 0;
var cpage = 1;
var _next = $('div.k2Pagination a:contains("Next")'),
_divNext = $('.view-more'),
_btnNext = $('a#viewplus'),
_divLoading = $('div.k2-masonry-loading'),
_btnLoading = $('div.loading_spinner_wrapper');
$container.infinitescroll({
navSelector : InfiniteConfig.navSelector,
nextSelector: _next,
itemSelector: InfiniteConfig.itemSelector,
loading : {
selector : _divLoading,
img : _btnLoading.html(),
msgText : '',
speed: 'fast',
finishedMsg : InfiniteConfig.finishedMsg,
finished : function() {
_btnLoading.css('display','none');
setTimeout(function(){
_divNext.css('display','block');
},500);
},
},
errorCallback: function(){
_divNext.css('display','block');
_divLoading.remove();
_divNext.addClass('finished').html(InfiniteConfig.finishedMsg);
},
debug : true,
path : function () {
pageCount += 1;
var path = $(_next).attr('href')
var _url = [];
_url = path.split('start=');
_url[0] += "start";
url = _url[0];
numItems = parseInt(_url[1]);
nextLimit = numItems * (pageCount);
return [InfiniteConfig.baseURL + url + '=' + nextLimit];
}
},
function (newElements, data, url) {
var elemWidth = $('#itemListPrimary .itemContainer').width(),
$newElems = $( newElements ).css({ opacity: 0 , width: elemWidth});
$newElems.imagesLoaded(function(){
// show elems now they're ready
$newElems.animate({ opacity: 1 });
$container.masonry( 'appended', $newElems, true );
});
});
$(window).unbind('.infscr');
_btnNext.click(function(){
_divNext.css('display','none');
_btnLoading.css('display','block');
$container.infinitescroll('retrieve');
return false;});
}(jQuery);
Thanks again.
Seeing as I know how much of a pain infinite scroll is, this is how I got round this.
I filtered the items by class in the newElements function, shown below.
function (newElements, data, url) {
var elemWidth = $('#itemListPrimary .itemContainer').width(),
$newElems = $( newElements ).css({ opacity: 0 , width: elemWidth});
$newElems.imagesLoaded(function(){
// show new elements
$newElems.animate({ opacity: 1 });
$container.masonry( 'appended', $newElems, true );
var children = $newElems.children();
// if filter is empty select all elements
if (!window.data) {
console.log("nothing selected");
}
// if filter is selected filter elements
else {
for (var i = 0; i < children.length; i++) {
if ($(this).hasClass(window.data)) {
$('.itemContainer').show();
} else {
$('.itemContainer').not('.' + window.data).hide();
}
// append layout
$(window).bind('resize.masonry orientationchange.masonry').trigger('resize.masonry');
};
};
});
});

Filters + Search with Isotopes Breaks Search?

I am using Isotopes (v1) and have created a search field following an example in a Pen.
Initially it works, however, if I filter the Isotope gallery then the search field stops working.
I believe the search function still runs just doesn't filter the gallery and I am unsure how to fix the problem. In fact I am unsure what the exact problem is as no errors are thrown.
Here is a Fiddle with a working example.
Here is the search, filter and isotope JavaScript:
var $container = $('.isotope'),
qsRegex,
filters = {};
$container.isotope({
itemSelector : '.element',
masonry : {
columnWidth : 120
},
getSortData : {
name : function ( $elem ) {
return $elem.find('.name').text();
}
},
filter: function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
});
function searchFilter() {
qsRegex = new RegExp( $quicksearch.val(), 'gi' );
$container.isotope();
}
// use value of search field to filter
var $quicksearch = $('#quicksearch').keyup( debounce( searchFilter ) );
$('#reset').on( 'click', function() {
$quicksearch.val('');
searchFilter()
});
// store filter for each group
$('#filters').on( 'click', '.button', function() {
var $this = $(this);
// get group key
var $buttonGroup = $this.parents('.button-group');
var filterGroup = $buttonGroup.attr('data-filter-group');
// set filter for group
filters[ filterGroup ] = $this.attr('data-filter');
// combine filters
var filterValue = '';
for ( var prop in filters ) {
filterValue += filters[ prop ];
}
// set filter for Isotope
$container.isotope({ filter: filterValue });
});
// debounce so filtering doesn't happen every millisecond
function debounce( fn, threshold ) {
var timeout;
return function debounced() {
if ( timeout ) {
clearTimeout( timeout );
}
function delayed() {
fn();
timeout = null;
}
setTimeout( delayed, threshold || 100 );
}
}
How do I solve the problem?
Note: I am using jQuery 2.1.1.
In you example $('#filters').on('click', '.button', function () stoping the search function and you reset buton placed inside #filters div so when you click it search engine is stoped too.
I have not the best solution, but it solve some problems:
Idea in using function to call engine back:
var iso = function() {
//engine here
}
and
$(function () {
iso();
$('.iso').click(function(){
setTimeout(iso, 500);
});
});
without setTimeout it can't work.
But it don't solve the main problem
look at FIDDLE and you'll understand what I mean
Or you just can place reset and Show All buttons outside #filters div
I faced the same problem implementing Filters + Search functionality.
I solved this problem passing the filter function to the Isotope call ($container.isotope();) in the search function (function searchFilter(){...}) instead of when initializing the Isotope instance.
So, in your code it should be like this:
// No filter specified when initializing the Isotope instance
$container.isotope({
itemSelector : '.element',
masonry : {
columnWidth : 120
},
getSortData : {
name : function ( $elem ) {
return $elem.find('.name').text();
}
}
});
// Instead, the filter is specified here
function searchFilter() {
qsRegex = new RegExp( $quicksearch.val(), 'gi' );
$container.isotope({
filter: function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
});
}

Click on object triggers a separate link on the same page

Hi I am using the bootstrap-tab.js (below) to change tabs and I would like to activate one of the tabs using a separate link on the page. I can quite easily access the second tabs content by linking to "#tab2" but the problem is that the tab system doesn't reflect the change. So my Tab1 is active while Tab2 content is displayed, if that makes sense.
I suspect the the easiest way to do this (given the js) is to trigger the link click from the actual tab using js. Any idea how to achieve this?
!function ($) {
"use strict"; // jshint ;_;
/* TAB CLASS DEFINITION
* ==================== */
var Tab = function (element) {
this.element = $(element)
}
Tab.prototype = {
constructor: Tab
, show: function () {
var $this = this.element
, $ul = $this.closest('ul:not(.dropdown-menu)')
, selector = $this.attr('data-target')
, previous
, $target
, e
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
if ( $this.parent('li').hasClass('active') ) return
previous = $ul.find('.active a').last()[0]
e = $.Event('show', {
relatedTarget: previous
})
$this.trigger(e)
if (e.isDefaultPrevented()) return
$target = $(selector)
this.activate($this.parent('li'), $ul)
this.activate($target, $target.parent(), function () {
$this.trigger({
type: 'shown'
, relatedTarget: previous
})
})
}
, activate: function ( element, container, callback) {
var $active = container.find('> .active')
, transition = callback
&& $.support.transition
&& $active.hasClass('fade')
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
callback && callback()
}
transition ?
$active.one($.support.transition.end, next) :
next()
$active.removeClass('in')
}
}
/* TAB PLUGIN DEFINITION
* ===================== */
$.fn.tab = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tab')
if (!data) $this.data('tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tab.Constructor = Tab
/* TAB DATA-API
* ============ */
$(function () {
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
e.preventDefault()
$(this).tab
('show')
})
})
}(window.jQuery);
Ok, sorry about this I figured it out.
$(document).ready(function(){
$('#scoretab').click(function (e) {
$('#myTab a[href="#tab2"]').tab('show');
})
});

Categories

Resources