Rectangle 27 106

This is a mix of an infinite table and an infinite scroll scenario. The best abstraction I found for this is the following:

Make a <List> component that takes an array of all children. Since we do not render them, it's really cheap to just allocate them and discard them. If 10k allocations is too big, you can instead pass a function that takes a range and return the elements.

<List>
  {thousandelements.map(function() { return <Element /> })}
</List>

Your List component is keeping track of what the scroll position is and only renders the children that are in view. It adds a large empty div at the beginning to fake the previous items that are not rendered.

Now, the interesting part is that once an Element component is rendered, you measure its height and store it in your List. This lets you compute the height of the spacer and know how many elements should be displayed in view.

You are saying that when the image are loading they make everything "jump" down. The solution for this is to set the image dimensions in your img tag: <img src="..." width="100" height="58" />. This way the browser doesn't have to wait to download it before knowing what size it is going to be displayed. This requires some infrastructure but it's really worth it.

If you can't know the size in advance, then add onload listeners to your image and when it is loaded then measure its displayed dimension and update the stored row height and compensate the scroll position.

If you need to jump at a random element in the list that's going to require some trickery with scroll position because you don't know the size of the elements in between. What I suggest you to do is to average the element heights you already have computed and jump to the scroll position of last known height + (number of elements * average).

Since this is not exact it's going to cause issues when you reach back to the last known good position. When a conflict happens, simply change the scroll position to fix it. This is going to move the scroll bar a bit but shouldn't affect him/her too much.

You want to provide a key to all the rendered elements so that they are maintained across renders. There are two strategies: (1) have only n keys (0, 1, 2, ... n) where n is the maximum number of elements you can display and use their position modulo n. (2) have a different key per element. If all the elements share a similar structure it's good to use (1) to reuse their DOM nodes. If they don't then use (2).

I would only have two pieces of React state: the index of the first element and the number of elements being displayed. The current scroll position and the height of all the elements would be directly attached to this. When using setState you are actually doing a rerender which should only happen when the range changes.

Here is an example http://jsfiddle.net/vjeux/KbWJ2/9/ of infinite list using some of the techniques I describe in this answer. It's going to be some work but React is definitively a good way to implement an infinite list :)

This is an awesome technique. Thanks! I got it working on one of my components. However, I have another component that I'd like to apply this to, but the rows don't have a consistent height. I'm working on augmenting your example to calculate the displayEnd/visibleEnd to account for the varying heights... unless you have a better idea?

I have implemented this with a twist, and ran into an issue: For me, the records I'm rendering are somewhat complex DOM, and because of the # of them it is not prudent to load them all into the browser, so I am doing async fetches from time to time. For some reason, on occasion when I scroll and position jumps very far (say I go off the screen and back), the ListBody doesn't re-render, even though the state changes. Any ideas why this might be? Great example otherwise!

Your JSFiddle currently throws an error: Uncaught ReferenceError: generate is not defined

I have made an updated fiddle, I think it should work the same. Anyone care to verify? @Meglio

@ThomasModeneis hi, can you clarify the calculations done on line 151 and 152, the displayStart and displayEnd

javascript - ReactJS: Modeling Bi-Directional Infinite Scrolling - Sta...

javascript html infinite-scroll reactjs
Rectangle 27 5

I've looked at the page. Your misconception is probably that you think the .badge-post-grid-load-more element vanishes as soon as the next elements are loaded. This is not the case. It doesn't change at all. You have to find another way to test whether new elements were put into the DOM.

You could for example retrieve the current number of elements and use waitFor to detect when the number changes.

function getNumberOfItems(casper) {
    return casper.getElementsInfo(".listview .badge-grid-item").length;
}

function tryAndScroll(casper) {
  casper.page.scrollPosition = { top: casper.page.scrollPosition["top"] + 4000, left: 0 };
  var info = casper.getElementInfo('.badge-post-grid-load-more');
  if (info.visible) {
    var curItems = getNumberOfItems(casper);
    casper.waitFor(function check(){
      return curItems != getNumberOfItems(casper);
    }, function then(){
      tryAndScroll(this);
    }, function onTimeout(){
      this.echo("Timout reached");
    }, 20000);
  } else {
    casper.echo("no more items");
  }
}

I've also streamlined tryAndScroll a little. There were completely unnecessary functions: the first casper.waitFor wasn't waiting at all and because of that the onTimeout callback could never be invoked.

+1 Great, you are right about the misconception i had. Now when I run there is error: Wait timeout of 10000ms expired, exiting.. Could you post the whole code block? I must be putting it together wrong

Yes, the problem was that without passing an onTimeout callback the timeout error is thrown and the script stops prematurely. I added the callback and increased the timeout. Keep in mind that now there are 600 links on the page and a newly created link to open the second page which can be scrolled to load more items.

perfect! I understand now the issue wit the onTimeout callback

You are right it scrapes all 600 results, could it be possible just to scrape max 100 results?

Sure, the curItems variable is right there. You can use another if block to check if it is larger than 100 and only if it is smaller do the casper.waitFor after that.

javascript - Scraping an infinite scroll page stops without scrolling ...

javascript phantomjs casperjs
Rectangle 27 9

Select2 will only enable infinite scroll, if ajax is enabled. Fortunately we can enable it and still use our own adapter. So putting empty object into ajax option will do the trick.

$("select").select2({
  ajax: {},
  dataAdapter: CustomData
});
query
pagination
CustomData.prototype.query = function (params, callback) {
        if (!("page" in params)) {
            params.page = 1;
        }
        var data = {};
        # you probably want to do some filtering, basing on params.term
        data.results = items.slice((params.page - 1) * pageSize, params.page * pageSize);
        data.pagination = {};
        data.pagination.more = params.page * pageSize < items.length;
        callback(data);
    };

Searching seems to be broken with this solution?

Sign up for our newsletter and get our top new questions delivered to your inbox (see an example).

javascript - How to enable infinite scrolling in select2 4.0 without a...

javascript jquery jquery-select2 jquery-select2-4
Rectangle 27 8

I felt the answers above needed better demonstration. Select2 4.0.0 introduces the ability to do custom adapters. Using the ajax: {} trick, I created a custom dataAdapter jsonAdapter that uses local JSON directly. Also notice how Select2's 4.0.0 release has impressive performance using a big JSON string. I used an online JSON generator and created 10,000 names as test data. However, this example is very muddy. While this works, I would hope there is a better way.

$.fn.select2.amd.define('select2/data/customAdapter', ['select2/data/array', 'select2/utils'],
    function (ArrayData, Utils) {
        function CustomDataAdapter($element, options) {
            CustomDataAdapter.__super__.constructor.call(this, $element, options);
        }

        Utils.Extend(CustomDataAdapter, ArrayData);

        CustomDataAdapter.prototype.current = function (callback) {
            var found = [],
                findValue = null,
                initialValue = this.options.options.initialValue,
                selectedValue = this.$element.val(),
                jsonData = this.options.options.jsonData,
                jsonMap = this.options.options.jsonMap;

            if (initialValue !== null){
                findValue = initialValue;
                this.options.options.initialValue = null;  // <-- set null after initialized              
            }
            else if (selectedValue !== null){
                findValue = selectedValue;
            }

            if(!this.$element.prop('multiple')){
                findValue = [findValue];
                this.$element.html();     // <-- if I do this for multiple then it breaks
            }

            // Query value(s)
            for (var v = 0; v < findValue.length; v++) {              
                for (var i = 0, len = jsonData.length; i < len; i++) {
                    if (findValue[v] == jsonData[i][jsonMap.id]){
                       found.push({id: jsonData[i][jsonMap.id], text: jsonData[i][jsonMap.text]}); 
                       if(this.$element.find("option[value='" + findValue[v] + "']").length == 0) {
                           this.$element.append(new Option(jsonData[i][jsonMap.text], jsonData[i][jsonMap.id]));
                       }
                       break;   
                    }
                }
            }

            // Set found matches as selected
            this.$element.find("option").prop("selected", false).removeAttr("selected");            
            for (var v = 0; v < found.length; v++) {            
                this.$element.find("option[value='" + found[v].id + "']").prop("selected", true).attr("selected","selected");            
            }

            // If nothing was found, then set to top option (for single select)
            if (!found.length && !this.$element.prop('multiple')) {  // default to top option 
                found.push({id: jsonData[0][jsonMap.id], text: jsonData[0][jsonMap.text]}); 
                this.$element.html(new Option(jsonData[0][jsonMap.text], jsonData[0][jsonMap.id], true, true));
            }

            callback(found);
        };        

        CustomDataAdapter.prototype.query = function (params, callback) {
            if (!("page" in params)) {
                params.page = 1;
            }

            var jsonData = this.options.options.jsonData,
                pageSize = this.options.options.pageSize,
                jsonMap = this.options.options.jsonMap;

            var results = $.map(jsonData, function(obj) {
                // Search
                if(new RegExp(params.term, "i").test(obj[jsonMap.text])) {
                    return {
                        id:obj[jsonMap.id],
                        text:obj[jsonMap.text]
                    };
                }
            });

            callback({
                results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
                pagination:{
                    more:results.length >= params.page * pageSize
                }
            });
        };

        return CustomDataAdapter;

    });

var jsonAdapter=$.fn.select2.amd.require('select2/data/customAdapter');

I wish I could up-vote this 10,000+ times. I'm sure there's good reason, but it seems v4 is over-engineered. This took hours to figure out, but thankfully I stumbled across your answer. Thank you @prograhammer

This is a great adapter example. However, I'm trying to figure out the best way to map in other custom object values. Normally, you can retrieve the $(selector).select2("data") and get things other than id and text, but this adapter limits that part.

Any chance of putting example on how this adapter would be implemented on huge select lists (in my case required by design), and accessing data-* attributes? Thanks

@Kosta, I've since abandoned the use of Select2. The code was just too messy IMHO. I'm using VueJS now and it's sooo easy and clean to implement this yourself if you wanted to. And there's existing components out there already that are super simple to use: github.com/monterail/vue-multiselect

javascript - How to enable infinite scrolling in select2 4.0 without a...

javascript jquery jquery-select2 jquery-select2-4
Rectangle 27 7

Also referenced this example of how to achieve infinite scrolling using a client side data source, with select2 version 3.4.5.

This example uses the oringal options in a select tag to build the list instead of item array which is what was called for in my situation.

function contains(str1, str2) {
    return new RegExp(str2, "i").test(str1);
}

CustomData.prototype.query = function (params, callback) {
    if (!("page" in params)) {
        params.page = 1;
    }
    var pageSize = 50;
    var results = this.$element.children().map(function(i, elem) {
        if (contains(elem.innerText, params.term)) {
            return {
                id:[elem.innerText, i].join(""),
                text:elem.innerText
            };
        }
    });
    callback({
        results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
        pagination:{
            more:results.length >= params.page * pageSize
        }
    });
};

Can you provide a working jsFiddle? I'm not able to get this one working either.

@prograhammer there you go homie

yeah, the problem was you are using option tags and I was using JSON directly.

indeed! sorry you didnt catch that earlier but glad you were able to get it working. the fiddle can help anybody that needs it in the future anyways ;-)

javascript - How to enable infinite scrolling in select2 4.0 without a...

javascript jquery jquery-select2 jquery-select2-4
Rectangle 27 4

$(window).scrollTop() > $(document).height() - $(window).height()

After that you must need to add a flag to check if the new content has been loaded or not and then reinitialize the scrolling function to resume back the old one.

// Add a flag
var scrolled = false;
// Change the logic
$(window).scrollTop() > $(document).height() - $(window).height()
// allow AJAX call only if not scrolled
if (!scrolled) {
  // immediately, revert the flag:
  scrolled = true;
  $.ajax(function () {
    // inside the AJAX call, restore it
    scrolled = false;
  });
}

Thanks, but that didn't work. When I do this, nothing fires. Thinking that perhaps the left side is never able to be greater than the right side, I also tried $(window).scrollTop() > $(document).height() - $(window).height() - 1 and that gave the same results as my original post.

@user1883050 Check the updated answer.

It doesn't fire when the left side is greater than the right side.

@user1883050 I said you need to re-init the AJAX call. Which means, redefine. So the same code will redefine dynamically.

javascript - AJAX firing twice (infinite scroll). Why? - Stack Overflo...

javascript jquery ajax
Rectangle 27 4

As you said, it could be because the user is scrolling too fast and the document does not get to be updated with the new results.

Try using a flag that will prevent calling the loadMoreResults() while the function is still executing.

You set it to true when the function starts and at the end, after you get your results, you set it to false. The check of the flag can be placed right at the beginning of the loadMoreresults() function, before setting the flag to true.

function loadMoreResults() {
  if (flag) return;
  flag = true;
[...]
  flag = false;
}

javascript - Ajax infinite scroll feature firing twice. - Stack Overfl...

javascript ajax jquery
Rectangle 27 1

You need to keep track of the "isLoading" state and stop the autoload behaviour while it's the case.

Even better, remove the scroll even listener while the ajax request is being loaded. Add it back once you get a response.

javascript - AJAX firing twice (infinite scroll). Why? - Stack Overflo...

javascript jquery ajax
Rectangle 27 1

I was facing a similar challenge for modeling single-direction infinite scrolling with heterogeneous item heights and so made an npm package out of my solution:

You can check out the source code for the logic, but I basically followed the recipe @Vjeux outlined in the above answer. I haven't yet tackled jumping to a particular item, but I'm hoping to implement that soon.

Here's the nitty-gritty of what the code currently looks like:

var React = require('react');
var areNonNegativeIntegers = require('validate.io-nonnegative-integer-array');

var InfiniteScoller = React.createClass({
  propTypes: {
    averageElementHeight: React.PropTypes.number.isRequired,
    containerHeight: React.PropTypes.number.isRequired,
    preloadRowStart: React.PropTypes.number.isRequired,
    renderRow: React.PropTypes.func.isRequired,
    rowData: React.PropTypes.array.isRequired,
  },

  onEditorScroll: function(event) {
    var infiniteContainer = event.currentTarget;
    var visibleRowsContainer = React.findDOMNode(this.refs.visibleRowsContainer);
    var currentAverageElementHeight = (visibleRowsContainer.getBoundingClientRect().height / this.state.visibleRows.length);
    this.oldRowStart = this.rowStart;
    var newRowStart;
    var distanceFromTopOfVisibleRows = infiniteContainer.getBoundingClientRect().top - visibleRowsContainer.getBoundingClientRect().top;
    var distanceFromBottomOfVisibleRows = visibleRowsContainer.getBoundingClientRect().bottom - infiniteContainer.getBoundingClientRect().bottom;
    var rowsToAdd;
    if (distanceFromTopOfVisibleRows < 0) {
      if (this.rowStart > 0) {
        rowsToAdd = Math.ceil(-1 * distanceFromTopOfVisibleRows / currentAverageElementHeight);
        newRowStart = this.rowStart - rowsToAdd;

        if (newRowStart < 0) {
          newRowStart = 0;
        } 

        this.prepareVisibleRows(newRowStart, this.state.visibleRows.length);
      }
    } else if (distanceFromBottomOfVisibleRows < 0) {
      //scrolling down, so add a row below
      var rowsToGiveOnBottom = this.props.rowData.length - 1 - this.rowEnd;
      if (rowsToGiveOnBottom > 0) {
        rowsToAdd = Math.ceil(-1 * distanceFromBottomOfVisibleRows / currentAverageElementHeight);
        newRowStart = this.rowStart + rowsToAdd;

        if (newRowStart + this.state.visibleRows.length >= this.props.rowData.length) {
          //the new row start is too high, so we instead just append the max rowsToGiveOnBottom to our current preloadRowStart
          newRowStart = this.rowStart + rowsToGiveOnBottom;
        }
        this.prepareVisibleRows(newRowStart, this.state.visibleRows.length);
      }
    } else {
      //we haven't scrolled enough, so do nothing
    }
    this.updateTriggeredByScroll = true;
    //set the averageElementHeight to the currentAverageElementHeight
    // setAverageRowHeight(currentAverageElementHeight);
  },

  componentWillReceiveProps: function(nextProps) {
    var rowStart = this.rowStart;
    var newNumberOfRowsToDisplay = this.state.visibleRows.length;
    this.props.rowData = nextProps.rowData;
    this.prepareVisibleRows(rowStart, newNumberOfRowsToDisplay);
  },

  componentWillUpdate: function() {
    var visibleRowsContainer = React.findDOMNode(this.refs.visibleRowsContainer);
    this.soonToBeRemovedRowElementHeights = 0;
    this.numberOfRowsAddedToTop = 0;
    if (this.updateTriggeredByScroll === true) {
      this.updateTriggeredByScroll = false;
      var rowStartDifference = this.oldRowStart - this.rowStart;
      if (rowStartDifference < 0) {
        // scrolling down
        for (var i = 0; i < -rowStartDifference; i++) {
          var soonToBeRemovedRowElement = visibleRowsContainer.children[i];
          if (soonToBeRemovedRowElement) {
            var height = soonToBeRemovedRowElement.getBoundingClientRect().height;
            this.soonToBeRemovedRowElementHeights += this.props.averageElementHeight - height;
            // this.soonToBeRemovedRowElementHeights.push(soonToBeRemovedRowElement.getBoundingClientRect().height);
          }
        }
      } else if (rowStartDifference > 0) {
        this.numberOfRowsAddedToTop = rowStartDifference;
      }
    }
  },

  componentDidUpdate: function() {
    //strategy: as we scroll, we're losing or gaining rows from the top and replacing them with rows of the "averageRowHeight"
    //thus we need to adjust the scrollTop positioning of the infinite container so that the UI doesn't jump as we 
    //make the replacements
    var infiniteContainer = React.findDOMNode(this.refs.infiniteContainer);
    var visibleRowsContainer = React.findDOMNode(this.refs.visibleRowsContainer);
    var self = this;
    if (this.soonToBeRemovedRowElementHeights) {
      infiniteContainer.scrollTop = infiniteContainer.scrollTop + this.soonToBeRemovedRowElementHeights;
    }
    if (this.numberOfRowsAddedToTop) {
      //we're adding rows to the top, so we're going from 100's to random heights, so we'll calculate the differenece
      //and adjust the infiniteContainer.scrollTop by it
      var adjustmentScroll = 0;

      for (var i = 0; i < this.numberOfRowsAddedToTop; i++) {
        var justAddedElement = visibleRowsContainer.children[i];
        if (justAddedElement) {
          adjustmentScroll += this.props.averageElementHeight - justAddedElement.getBoundingClientRect().height;
          var height = justAddedElement.getBoundingClientRect().height;
        }
      }
      infiniteContainer.scrollTop = infiniteContainer.scrollTop - adjustmentScroll;
    }

    var visibleRowsContainer = React.findDOMNode(this.refs.visibleRowsContainer);
    if (!visibleRowsContainer.childNodes[0]) {
      if (this.props.rowData.length) {
        //we've probably made it here because a bunch of rows have been removed all at once
        //and the visible rows isn't mapping to the row data, so we need to shift the visible rows
        var numberOfRowsToDisplay = this.numberOfRowsToDisplay || 4;
        var newRowStart = this.props.rowData.length - numberOfRowsToDisplay;
        if (!areNonNegativeIntegers([newRowStart])) {
          newRowStart = 0;
        }
        this.prepareVisibleRows(newRowStart , numberOfRowsToDisplay);
        return; //return early because we need to recompute the visible rows
      } else {
        throw new Error('no visible rows!!');
      }
    }
    var adjustInfiniteContainerByThisAmount;

    //check if the visible rows fill up the viewport
    //tnrtodo: maybe put logic in here to reshrink the number of rows to display... maybe...
    if (visibleRowsContainer.getBoundingClientRect().height / 2 <= this.props.containerHeight) {
      //visible rows don't yet fill up the viewport, so we need to add rows
      if (this.rowStart + this.state.visibleRows.length < this.props.rowData.length) {
        //load another row to the bottom
        this.prepareVisibleRows(this.rowStart, this.state.visibleRows.length + 1);
      } else {
        //there aren't more rows that we can load at the bottom so we load more at the top
        if (this.rowStart - 1 > 0) {
          this.prepareVisibleRows(this.rowStart - 1, this.state.visibleRows.length + 1); //don't want to just shift view
        } else if (this.state.visibleRows.length < this.props.rowData.length) {
          this.prepareVisibleRows(0, this.state.visibleRows.length + 1);
        }
      }
    } else if (visibleRowsContainer.getBoundingClientRect().top > infiniteContainer.getBoundingClientRect().top) {
      //scroll to align the tops of the boxes
      adjustInfiniteContainerByThisAmount = visibleRowsContainer.getBoundingClientRect().top - infiniteContainer.getBoundingClientRect().top;
      //   this.adjustmentScroll = true;
      infiniteContainer.scrollTop = infiniteContainer.scrollTop + adjustInfiniteContainerByThisAmount;
    } else if (visibleRowsContainer.getBoundingClientRect().bottom < infiniteContainer.getBoundingClientRect().bottom) {
      //scroll to align the bottoms of the boxes
      adjustInfiniteContainerByThisAmount = visibleRowsContainer.getBoundingClientRect().bottom - infiniteContainer.getBoundingClientRect().bottom;
      //   this.adjustmentScroll = true;
      infiniteContainer.scrollTop = infiniteContainer.scrollTop + adjustInfiniteContainerByThisAmount;
    }
  },

  componentWillMount: function(argument) {
    //this is the only place where we use preloadRowStart
    var newRowStart = 0;
    if (this.props.preloadRowStart < this.props.rowData.length) {
      newRowStart = this.props.preloadRowStart;
    }
    this.prepareVisibleRows(newRowStart, 4);
  },

  componentDidMount: function(argument) {
    //call componentDidUpdate so that the scroll position will be adjusted properly
    //(we may load a random row in the middle of the sequence and not have the infinte container scrolled properly initially, so we scroll to the show the rowContainer)
    this.componentDidUpdate();
  },

  prepareVisibleRows: function(rowStart, newNumberOfRowsToDisplay) { //note, rowEnd is optional
    //setting this property here, but we should try not to use it if possible, it is better to use
    //this.state.visibleRowData.length
    this.numberOfRowsToDisplay = newNumberOfRowsToDisplay;
    var rowData = this.props.rowData;
    if (rowStart + newNumberOfRowsToDisplay > this.props.rowData.length) {
      this.rowEnd = rowData.length - 1;
    } else {
      this.rowEnd = rowStart + newNumberOfRowsToDisplay - 1;
    }
    // var visibleRows = this.state.visibleRowsDataData.slice(rowStart, this.rowEnd + 1);
    // rowData.slice(rowStart, this.rowEnd + 1);
    // setPreloadRowStart(rowStart);
    this.rowStart = rowStart;
    if (!areNonNegativeIntegers([this.rowStart, this.rowEnd])) {
      var e = new Error('Error: row start or end invalid!');
      console.warn('e.trace', e.trace);
      throw e;
    }
    var newVisibleRows = rowData.slice(this.rowStart, this.rowEnd + 1);
    this.setState({
      visibleRows: newVisibleRows
    });
  },
  getVisibleRowsContainerDomNode: function() {
    return this.refs.visibleRowsContainer.getDOMNode();
  },


  render: function() {
    var self = this;
    var rowItems = this.state.visibleRows.map(function(row) {
      return self.props.renderRow(row);
    });

    var rowHeight = this.currentAverageElementHeight ? this.currentAverageElementHeight : this.props.averageElementHeight;
    this.topSpacerHeight = this.rowStart * rowHeight;
    this.bottomSpacerHeight = (this.props.rowData.length - 1 - this.rowEnd) * rowHeight;

    var infiniteContainerStyle = {
      height: this.props.containerHeight,
      overflowY: "scroll",
    };
    return (
      <div
        ref="infiniteContainer"
        className="infiniteContainer"
        style={infiniteContainerStyle}
        onScroll={this.onEditorScroll}
        >
          <div ref="topSpacer" className="topSpacer" style={{height: this.topSpacerHeight}}/>
          <div ref="visibleRowsContainer" className="visibleRowsContainer">
            {rowItems}
          </div>
          <div ref="bottomSpacer" className="bottomSpacer" style={{height: this.bottomSpacerHeight}}/>
      </div>
    );
  }
});

module.exports = InfiniteScoller;

javascript - ReactJS: Modeling Bi-Directional Infinite Scrolling - Sta...

javascript html infinite-scroll reactjs
Rectangle 27 2

You can use this debounce routine for all sort of event calls. Clean and reusable.

// action takes place here. 
function infinite_scrolling(){
    if ($(window).scrollTop() >= $("#home_content").height() - $(window).height()) {
        if (isLastPage) {
            foo();
        } else {
            bar(); // JQuery AJAX call
        }
    }
}

// debounce multiple requests
var _scheduledRequest = null;
function infinite_scroll_debouncer(callback_to_run, debounce_time) {
    debounce_time = typeof debounce_time !== 'undefined' ? debounce_time : 800;
    if (_scheduledRequest) {
        clearTimeout(_scheduledRequest);
    }
    _scheduledRequest = setTimeout(callback_to_run, debounce_time);
}

// usage
$(document).ready(function() {
    $(window).scroll(function() {
        infinite_scroll_debouncer(infinite_scrolling, 1000);
    });
});

javascript - Infinite scroll fired twice on refresh from the bottom of...

javascript jquery ajax
Rectangle 27 2

You can use this debounce routine for all sort of event calls. Clean and reusable.

// action takes place here. 
function infinite_scrolling(){
    if ($(window).scrollTop() >= $("#home_content").height() - $(window).height()) {
        if (isLastPage) {
            foo();
        } else {
            bar(); // JQuery AJAX call
        }
    }
}

// debounce multiple requests
var _scheduledRequest = null;
function infinite_scroll_debouncer(callback_to_run, debounce_time) {
    debounce_time = typeof debounce_time !== 'undefined' ? debounce_time : 800;
    if (_scheduledRequest) {
        clearTimeout(_scheduledRequest);
    }
    _scheduledRequest = setTimeout(callback_to_run, debounce_time);
}

// usage
$(document).ready(function() {
    $(window).scroll(function() {
        infinite_scroll_debouncer(infinite_scrolling, 1000);
    });
});

javascript - Infinite scroll fired twice on refresh from the bottom of...

javascript jquery ajax
Rectangle 27 1

This is just a workaround as we cannot see your complete code, but maybe thats can help:

var timeout;

$(window).scroll(function(){
      clearTimeout(timeout);
  timeout = setTimeout(function(){
      if  ($(window).scrollTop() >= $("#home_content").height() - $(window).height()){
                if (isLastPage){
                    foo();
                }else{
                    bar();//AJAX call
                }
            }
  },0);
});

made a small change. Increased the delay to 100 ms. Now works great in all the browsers. Thanks :) Btw let me know if there is any caveats if I do this.

You solved it man (y) and have bookmarked question so hats off for Question Poster too.

javascript - Infinite scroll fired twice on refresh from the bottom of...

javascript jquery ajax
Rectangle 27 1

This is just a workaround as we cannot see your complete code, but maybe thats can help:

var timeout;

$(window).scroll(function(){
      clearTimeout(timeout);
  timeout = setTimeout(function(){
      if  ($(window).scrollTop() >= $("#home_content").height() - $(window).height()){
                if (isLastPage){
                    foo();
                }else{
                    bar();//AJAX call
                }
            }
  },0);
});

made a small change. Increased the delay to 100 ms. Now works great in all the browsers. Thanks :) Btw let me know if there is any caveats if I do this.

You solved it man (y) and have bookmarked question so hats off for Question Poster too.

javascript - Infinite scroll fired twice on refresh from the bottom of...

javascript jquery ajax
Rectangle 27 1

Basicly, you are updating $scope.userRatings, so I'd use something like that which consist of :

var _loopItems = 10;
 $scope.loadMore = function() {
   var _curLength = $scope.userRatings.length;

   UserService.GetUserRatings($stateParams.id, _curLength  , _curLength  + _loopItems ).success(function (data) {
      $scope.userRatings = angular.merge($scope.userRatings, angular.fromJson(data)); // AS I DON T KNOW YOUR DATAS, IT S HARD TO TELL HOW TO MERGE THOSE VALUES
      }).error(function(error) {
                //do something
    });

    $scope.$broadcast('scroll.infiniteScrollComplete'); 
  };

EDIT : As your response is like that :

[{id:1, userid:1, rating_num:5, rating_text:"foo"},{id:2, userid:2, rating_num:5, rating_text:"foo"}]

I suggest changing the merge with the follwoing :

data = angular.fromJson(data);
for (var i =0; i< data.length; i++){
  $scope.userRatings.push(data[i]);
}

does angular merge work like push?.. like does it add the new objects to the end of the array userRatings array.. i get your logic, my initial index are wrong should be 0 - 9... i was kinda thinking of the same thing (userRatings.length, userRatings.length + 10).. but i was thinking of $scope.userRatings.push for the response data.. how is the push different from the merge

I don't know your response structure. I personnaly loop through my response and do "array.push" but without info on your response structure, it's hard to tell you what to do.

javascript - AngularJS Ionicframework infinite scroll GET REQUEST a li...

javascript angularjs ionic-framework ionic infinite-scroll
Rectangle 27 1

Not an answer but a suggestion: check out (view source) how some other players have done the infinite scroll like Bing's image search or 37 Signals Haystack (which is certainly in Rails as well).

HayStack makes a call to "/listings.js". This means they're probably using a different Rails format in their responds_to call. I considered this, but I don't like it very much because:

  • It depends on having a format (js in this case) at the URL that isn't being used for any other purpose (and never will be).

This is one of the things I hate about AJAX: trying to figure out in which part of the code the magic happens. :-P

jquery - RESTfully getting HTML partial content via AJAX in Rails - St...

jquery ruby-on-rails ajax rest partial-views
Rectangle 27 1

If your goal is to have the user be able to interact with the data as fast as possible, may be you want to consider something like infinite scroll (also called continuous scroll) pattern so you build the grid as needed from the scrolling of the user and not spend the whole time rendering the grid upfront.

c# - Loading large data in jquery - Stack Overflow

c# javascript web-services json data-presentation
Rectangle 27 0

And here is the javascript:

(function($){
    $(document).ready(function(){
        var html = $(".what").html();
        var what = '<div class="what">'+html+'</div>';
        $(window).scrollTop(1);
        $(window).scroll(function() {
            if ( $(window).scrollTop() >= ($('body').height() - $(window).height()) ) {
                $(".what").last().after(what);
                if ($(".what").length > 2) {
                    $(".what").last().prev().remove();
                    $(window).scrollTop($(window).scrollTop() - $(".what").first().height());
                }
            }
            else if ( $(window).scrollTop() == 0 ) {
                $(".what").first().before(what);
                $(window).scrollTop($(".what").first().height());
                if ($(".what").length > 2) {
                    $(".what").last().remove();
                }
            }    
        });
    }); 
})( jQuery );

works like a charm. thank you sir!

just one more question, is it possible to clone the div content and inserting it into the existing div, without its div element?

javascript - Infinite Scroll in both directions - Stack Overflow

javascript jquery
Rectangle 27 0

It looks like a bug in Firefox.

The question: Firefox scrollTop problem has an answer that can be applied here. What it suggests is that you defer the alert() call using setTimeout() to give Firefox a chance to do whatever it needs to do to avoid blanking the page. Applying the workaround to your code, you would get something like this:

window.onscroll = catchScroll;
var timeOutId = 0;
var jitterBuffer = 200;
function catchScroll() {
    if (timeOutId) clearTimeout(timeOutId);
    timeOutId = setTimeout(function () { DoStuffOnScrollEvent() }, jitterBuffer);
}

function DoStuffOnScrollEvent() {
    var scroll_top = $(document).scrollTop();
    alert(scroll_top);
    if (scroll_top <= 70) {
        $('#fixedback').fadeOut(500);
    } else {
        $('#fixedback').fadeIn(500);
    }
};

Or, instead of alert(), you could use console.log(), which will work natively in later versions of IE and Chrome, and Firefox via Firebug.

Love the work around. And the explanation of what is going on, makes great sense. Thank you.

javascript - jQuery: when using the on .scroll event and alert firefox...

javascript jquery firefox alert
Rectangle 27 0

$(window).load(function () {

var $container = $('.products-grid-wrap');

$container.imagesLoaded(function () {
    $container.isotope({
        itemSelector: '.products-grid-block',
        filter: '*:not(.hidden), .products-grid-block',
        animationEngine: 'best-available',
        layoutMode: "perfectMasonry",
        perfectMasonry: {
          columnWidth: 280,
          rowHeight: 310
        }
    });

    $container.infinitescroll({
        navSelector: '#page_nav', // selector for the paged navigation 
        nextSelector: '#page_nav a', // selector for the NEXT link (to page 2)
        itemSelector: '.regular-product-block, .products-grid-block', // selector for all items you'll retrieve
        pixelsFromNavToBottom: Math.round($(window).height() * 2.5),
        bufferPx: Math.round($(window).height() * 2.5),
        loading: {
            finishedMsg: 'No more products to load.',
            img: 'URL/wp-content/uploads/2014/11/ajax-loader-big.gif'
        }
    },
    // call Isotope as a callback
    function (newElements) {
        var $newElems = $(newElements);
        $newElems.imagesLoaded(function () {
            $container.isotope('insert', $newElems);
            $('.products-grid-rollover-block').hide();                 
            setTimeout(function () {
                $('.products-grid-wrap').isotope('reLayout');
                //$('.products-grid-wrap').isotope({
                //sortBy: 'category',
                    //sortAscending: false });
            }, 500);
        });
    $('.products-header-category-select, #products-filter-all-categories').click(function () {
    var selector = $(this).attr('data-filter');
    $container.isotope({
        filter: selector
    });

    return false;
}); 
    });            

});   
});

this hasn't really improved the problem. The filtering was and is working (to a degree), but if the filter results aren't on pages already loaded, then when you initiate a filter, you're greeted with an empty screen until the rest of the pages have loaded in and the relevant blocks are available to filter. If that makes sense?

I see, I thought the filter was not working after infinite scroll. You want to filter items not already loaded on the page?

yes it does work, but obviously as some pages haven't loaded yet, it filters all items out until the relevant page has loaded. So yes, essentially I want to filter items that haven't loaded on the page yet and then load the relevant elements into the page.

Not sure how you can filter items not yet loaded in the DOM but maybe someone does. Your best bet is to try and filter as they load by including the filter you want in the callback on isotope in infinitescroll, so it filters as they load.

javascript - jQuery isotope: Infinite Scroll and filtering - Stack Ove...

javascript jquery jquery-isotope infinite-scroll
Rectangle 27 0

There's a great plugin for scrolling. He has methods such as bund, unbind, destroy and e.t.c.:

ajax - How to handle execution of javascript in infinite scroll? - Sta...

javascript ajax nginfinitescroll