forever scroll loading content dynamically with array - javascript

I have this code, and it works fine, problem is it keeps loading the content over and over again which yes I want but at the same time I don't. I want each time scroll to bottom a new page is loaded. Basically I am trying to create a Forever Scroll for my members list. The urls are the same except an integer which are split by 28s 0-28-56-84 so and so forth.
Code is
function yHandler () {
var wrap = document.getElementById('members_wrapper');
var contentHeight = wrap.offsetHeight;
var yOffset = window.pageYOffset;
var y = yOffset + window.innerHeight;
if (y >= contentHeight) {
$.get('/memberlist?mode=lastvisit&order=DESC&start=28&username',function(data) {
var elems= $(data);
$('#members_wrapper').append(elems.find('#members_wrapper'));
},'html');
}
}
window.onscroll = yHandler;
To be honest I am not quite sure how to go about this... I'm sure I could do the following,
var number = ["28","56","84"];
for (var i = 0; i>number.length; i++) {
var url = '/memberlist?mode=lastvisit&order=DESC&start='+ number[i] +'&username'
}
Then in turn I could post in like so $.get(url, function(data) {
If this would work let me know, because the next issue would be, what if I have more than 84 members and one day go up to 200+ members? Would I then in turn do a mathematical equation, which I never have done so let me explain ...
if number has already passed + 28;
Yes I know that is incorrect since I don't know how to go about this per say. Basically what I need really is if the url has already been passed add 28 to it and then pass the next url once page scroll down.

The window.onscroll handler will fire multiple times before the AJAX request has pulled in the content, which means you will fire multiple AJAX requests.
Keep track of when you are requesting the member list and don't run the onscroll handler if the AJAX request is still processing.
var fetchingContent = false; // tracks if an AJAX request is active
function yHandler () {
var wrap = document.getElementById('members_wrapper');
var contentHeight = wrap.offsetHeight;
var yOffset = window.pageYOffset;
var y = yOffset + window.innerHeight;
if (y >= contentHeight && !fetchingContent) {
// set to TRUE before AJAX request
fetchingContent = true;
$.get('/memberlist?mode=lastvisit&order=DESC&start=28&username',function(data) {
var elems= $(data);
$('#members_wrapper').append(elems.find('#members_wrapper'));
// set to FALSE after AJAX request complete
fetchingContent = false;
},'html');
}
}
window.onscroll = yHandler;
EDIT
To load the appropriate start value in the url you need to keep a reference to it.
var start = 0, limit = 28;
// once the AJAX request has finished you can update the start value
// notice the start variable is dynamically added to the AJAX url
$.get('/memberlist?mode=lastvisit&order=DESC&start='+start+'&username',function(data) {
var elems= $(data);
$('#members_wrapper').append(elems.find('#members_wrapper'));
// set to FALSE after AJAX request complete
fetchingContent = false;
// add to the start value for the next AJAX request
start = start + limit;
},'html');
The problem with doing this client side is that you don't know when the total amount of users has been reached. So you need to check for a false response.
Server Side
$response = array();
if (no members found with given start value) {
$response['success'] = 0;
} else {
$html = {get members and build div}
$response['success'] = 1;
$response['html'] = $html;
}
// send JSON back to the client
echo json_encode($response);
Now you need to check for the success parameter in the AJAX response. Notice I changed the $.get datatype to json. You could also use $.getJSON instead.
$.get('/memberlist?mode=lastvisit&order=DESC&start='+start+'&username',function(response) {
if (response.success === 1) {
var elems= $(response.html);
$('#members_wrapper').append(elems.find('#members_wrapper'));
// set to FALSE after AJAX request complete
fetchingContent = false;
// add to the start value for the next AJAX request
start = start + limit;
}
},'json');

Related

Asynchronous recursive functions in javascript

I am trying to stream mp3 data from my server to the client side. I am doing this using Ajax. The server sends 50 kilobytes per request. I wrote two functions: one that gets the mp3 data and one that plays them. The first function takes the 50 kilobytes, decodes them and stores the decoded data in an array then it calls itself recursively. The second function starts playing as soon as the first element in the array is filled with data. The problem is that this works for the first 50kilobytes only then it fails. What I want to do is keep my get_buffer function running until the server tells it no more data to send, and keep my play() function playing data until there is no more elements in the array.
Here is my two functions:
function buffer_seg() {
// starts a new request
buff_req = new XMLHttpRequest();
// Request attributes
var method = 'GET';
var url = '/buffer.php?request=buffer&offset=' + offset;
var async = true;
// set attributes
buff_req.open(method, url, async);
buff_req.responseType = 'arraybuffer';
// keeps loading until something is recieved
if (!loaded) {
change_icon();
buffering = true;
}
buff_req.onload = function() {
segment = buff_req.response;
// if the whole file was already buffered
if (segment.byteLength == 4) {
return true;
} else if (segment.byteLength == 3) {
return false;
}
// sets the new offset
if (offset == -1) {
offset = BUFFER_SIZE;
} else {
offset += BUFFER_SIZE;
}
//decodes mp3 data and adds it to the array
audioContext.decodeAudioData(segment, function(decoded) {
buffer.push(decoded);
debugger;
if (index == 0) {
play();
}
});
}
buff_req.send();
buff_seg();
}
Second function:
function play() {
// checks if the end of buffer has been reached
if (index == buffer.length) {
loaded = false;
change_icon();
if (buffer_seg == false) {
stop();
change_icon();
return false;
}
}
loaded = true;
change_icon();
// new buffer source
var src = audioContext.createBufferSource();
src.buffer = buffer[index++];
// connects
src.connect(audioContext.destination);
src.start(time);
time += src.buffer.duration;
src.onended = function() {
src.disconnect(audioContext.destination);
play();
}
}
The recursive call to buffer_seg is in the main body of buffer_seg, not in the callback, so it happens immediately - not, as you seem to intend, after a response is received. Second, this also means that the recursive call is unconditional when it should be based on whether the previous response indicated more data would be available. If this isn't just crashing your browser, I'm not sure why. It also means that chunks of streamed audio could be pushed into the buffer out of order.
So to start I'd look at moving the recursive call to the end of the onload handler, after the check for end of stream.
In the 2nd function, what do you intend if (buffer_seg == false) to do? This condition will never be met. Are you thinking this is a way to see the last return value from buffer_seg? That's not how it works. Perhaps you should have a variable that both functions can see, which buffer_seg can set and play can test, or something like that.

Show progress when getting list items from SharePoint List

Now I have a jQuery function for getting the list items from SharePoint List
function getListItems(listTitle, queryText){
var ctx = SP.ClientContext.get_current();
var splist = ctx.get_web().get_lists().getByTitle(listTitle);
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml(queryText);
var listItems = splist.getItems(camlQuery);
ctx.load(listItems);
var d = $.Deferred();
ctx.executeQueryAsync(function() {
var result = listItems.get_data().map(function(i){
return i.get_fieldValues();
});
d.resolve(result);
},
function(sender,args){
d.reject(args);
});
return d.promise();
}
And then I call this function
getListItems(listname , "").done(function(listItems){
//do something here...
}).fail(function(error){console.log(error.get_message());}); // Error message
But one of the list contains quite a large amount of records and I want to show the progress to users so that they know what is going on. Is there a way to do this with just client side scripting? Any help is appreciated. Thank you.
Using the provided example you could display only indeterminate progress bar since the request is submitted once to the server and there is no way to determine current status complete.
But since SharePoint JSOM API supports paged data retrieval, you could consider the below approach that allows to determine current status complete and therefore display determinate progress bar.
function getPagedListItems(list, queryText,itemsCount,position){
itemsCount = itemsCount || 100;
var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle(listTitle);
var ctx = list.get_context();
var camlQuery = new SP.CamlQuery();
if(typeof position != 'undefined')
camlQuery.set_listItemCollectionPosition(position);
var viewXml = String.format("<View>{0}<RowLimit>{1}</RowLimit></View>",queryText,itemsCount);
camlQuery.set_viewXml(viewXml);
var listItems = list.getItems(camlQuery);
ctx.load(list, 'ItemCount');
ctx.load(listItems);
var d = $.Deferred();
ctx.executeQueryAsync(function() {
d.resolve(listItems,list.get_itemCount());
},
function(sender,args){
d.reject(args);
});
return d.promise();
}
function getListItems(listTitle, queryText,itemsCount,position,results){
results = results || [];
return getPagedListItems(listTitle, queryText,itemsCount,position)
.then(function(pagedItems,totalItemCount){
pagedItems.get_data().filter(function(i){
results.push(i.get_fieldValues());
});
var percentLoaded = results.length / totalItemCount * 100;
console.log(String.format('{0}% has been loaded..',percentLoaded));
var pos = pagedItems.get_listItemCollectionPosition();
if(pos != null) {
return getListItems(listTitle, queryText,itemsCount,pos,results);
}
return results;
});
}
Usage
var listTitle = 'Contacts';
getListItems(listTitle , "",20)
.done(function(results){
console.log('Completed');
})
.fail(function(error){
console.log(error.get_message());
});
Results
Short Answer: You can't
Since your query is executed as a single request, there's no way to show "real" progress, although you could fake it by showing a generic "loading" gif.
Long Answer: You can if you really want to
If you were to modify your query to be paged (with a row limit per query), and then execute one request per page until all records are loaded, then you could update something on the page indicating progress.
// Use the RowLimit element to query for only 100 items at a time
camlQuery.set_viewXml("<View>"
+ "<OrderBy><FieldRef Name=\"Created\" /></OrderBy>"
+ "<RowLimit>100</RowLimit>"
+ "</View>");
Now inside the onSuccess function of executeQueryAsync(), you can access the listItemCollectionPosition property of your list item collection and pass that back into your CAML query to get the next page of items.
var itemsCount = listItems.get_count();
// use itemCount to update the current progress as displayed to the user
camlQuery.set_listItemCollectionPosition(listItems.get_listItemCollectionPosition());
// set the query's listItemCollectionPosition so you'll get the next page of results
// reload the items with the updated query
listItems = splist.getItems(camlQuery);
ctx.load(listItems);
ctx.executeQueryAsync(... // rinse and repeat to get the next batch of items
Obviously, this approach would require you to restructure your code to allow an arbitrary number of function calls. You may want to split out your onSuccess function into a named function instead of an anonymous one, so you can execute it somewhat recursively.
When you restructure your code, I also recommend wrapping the entire code block up inside an immediately executing function expression so that your variables can be accessed as needed without polluting the global namespace.
(function(){
//your code here
})();

PHP function fails to return correct result after ~1000 ajax posts

I'm debugging some code and in order to do so I am repeatedly making ajax post to a PHP script on my localhost Apache24 server. In simple terms, the PHP script takes an integer value and returns a different data string depending on the input integer.
I'm stepping through numerous integer values with a for loop on the javascript side, starting at x=1. I've noticed, however, that after ~980 ajax posts, the PHP function stops returning the correct data; it seems to only return the data for x = 980, even as x continues to increment. Console.log confirms that the x value doesn't hang at 980.
I initially thought maybe the script was buggy but then I restarted the loop at x = 980 and, sure enough, the php script worked fine until x = ~1900, when it stopped working again.
Is there a reason the PHP script fails to work after ~980 requests? I have received no errors on either the web side or server side.
function interpretDisplay(input_string) {
var display = input_string.split("?");
for (var x = 0; x < 16; x++) {
document.getElementById("ids").innerHTML = display + " ";
}
}
function runDisplay(x) {
values[1]+= "&seed=" + x;
$.post("test.php", values[1], function(data) {
console.log(x);
if (x % 1 == 0) {
interpretDisplay(data);
}
if (x < 1000) {
setTimeout(function() {
runDisplay(x+1);
}
, 10);
}
});
}
var url = window.location.href;
var values = url.split('?');
runDisplay(1);

Infinite scroll with AngularJs and Firebase

How do you implement infinite scroll on data that you get from firebase. So far I found an angularjs directive, that works really great but I'm having difficulty implementing it with fireable as firebase returns all data in one single request and this is not what I want.
Few weeks ago, I made a JS function that allowed an infinite scrolling in my app.
First, a set of data is displayed when the user visit the website:
// Add a callback that is triggered for each message.
var n = 25; // Step size for messages display.
$(window).load(function() {
lastMessagesQuery = messagesRef.limit(n);
lastMessagesQuery.on('child_added', function (snapshot) {
var message = snapshot.val();
$('<div/>').text(message.text).prependTo($('#messagesDiv'));
$('#messagesDiv')[0].scrollTop = $('#messagesDiv')[0].scrollHeight;
});
$('#messagesDiv').fadeTo(1000, 1);
});
Then, the function that makes possible the infinite scrolling:
// Pagination.
var i = 0; // Record variable.
function moreMessages () {
i += n; // Record pagination updates.
moreMessagesQuery = messagesRef; // Firebase reference.
moreMessagesQuery.on('value', function (snapshot) {
var data = snapshot.exportVal(); // Fetch all data from Firebase as an Object.
var keys = Object.keys(data).reverse(); // Due to the Keys are ordered from the oldest to the newest, it nessesary to change its sequence in order to display Firebase data snapshots properly.
var total_keys = Object.keys(data).length;
var k = keys[i]; // Key from where to start counting. Be careful what Key you pick.
if (i < total_keys) { // Stop displaying messages when it reach the last one.
lastMessagesQuery = messagesRef.endAt(null, k).limit(n); // Messages from a Key to the oldest.
lastMessagesQuery.on('child_added', function (snapshot) {
var message = snapshot.val();
$('<div/>').text(message.text).appendTo($('#messagesDiv')).hide().fadeIn(1000); // Add set of messages (from the oldest to the newest) at the end of #messagesDiv.
});
}
});
}
Finally, the infinite scrolling:
// Load more messages when scroll reach the bottom.
$(window).scroll(function() {
if (window.scrollY == document.body.scrollHeight - window.innerHeight) {
moreMessages();
}
});
It works great with small data sets. I hope this helps you to solve your problem (or gives you more ideas).
UPDATE October 2015
Firebase has growth since my original response, which means now it's pretty easy to achieve an infinite scrolling just using its Javascript API:
First, I recommend to create an Index in your Firebase. For this answer, I create this one:
{
"rules": {
".read": true,
".write": false,
"messages": {
".indexOn": "id"
}
}
}
Then, let's make some magic with Firebase:
// #fb: your Firebase.
// #data: messages, users, products... the dataset you want to do something with.
// #_start: min ID where you want to start fetching your data.
// #_end: max ID where you want to start fetching your data.
// #_n: Step size. In other words, how much data you want to fetch from Firebase.
var fb = new Firebase('https://<YOUR-FIREBASE-APP>.firebaseio.com/');
var data = [];
var _start = 0;
var _end = 9;
var _n = 10;
var getDataset = function() {
fb.orderByChild('id').startAt(_start).endAt(_end).limitToLast(_n).on("child_added", function(dataSnapshot) {
data.push(dataSnapshot.val());
});
_start = _start + _n;
_end = _end + _n;
}
Finally, a better Infinite Scrolling (without jQuery):
window.addEventListener('scroll', function() {
if (window.scrollY === document.body.scrollHeight - window.innerHeight) {
getDataset();
}
});
I'm using this approach with React and it's blazing fast no matter how big your data is.

Why is this JavaScript eating all my memory?

So I have a wee javascript app that receives a pair of numbers from a server every minute (or whatever) and over the course of the minute it updates a div every second (or whatever) so that the value shown in the div gradually increments from the first value to the second. It's only short, I'll post the code at the bottom of the question.
The function contains two sub functions - one of them does the ajax call to update the two values, one of them updates the div contents with a value somewhere between the two values. The ajax function uses setInterval to schedule the div updating function when it has the response from the server, and when the div updating function detects that it's time to update the two values it clears the set interval and calls the ajax function. These two functions thus carry on calling each other for ever.
I declared every variable used by the two sub functions in the outer function, so neither sub function creates any new variables, and both sub-functions are allowed to finish completely each time anyway (thanks to the setInterval in the ajax function).
The memory usage is going up almost every second, which must be every time doDivRefresh is called, but I don't understand what it's doing with new memory each time it's called - it doesn't create any variables.
Help!
/**
*
* Periodically gets the latest values for a stat and changes the contents of a
* div so that is ticks up from the previous value to the latest value
*
* #param divID
* The id of the div to update
* #param URL
* The URL that provides the values
* #param updateDivFrequency
* How often the value displayed in the div is updated, in
* miliseconds
* #param updateDataFrequency
* How often the underlying data is updated from the server, in
* seconds
*
*/
function updateStatOverPeriod(divID, URL, updateDivFrequency, updateDataFrequency)
{
var somethingDoer = new function()
{
};
var request = new XMLHttpRequest();
var currentValue = "";
var previousValue = "";
var latestValue = "";
var it = 0;
var currentTime = new Date();
var endTime = new Date().getTime();
function doDivRefresh(endTime)
{
if (currentValue != null)
{
currentValue = currentValue + it;
document.getElementById(divID).innerHTML = addCommas(parseInt(currentValue));
}
else
{
document.getElementById(divID).innerHTML = "<DIV CLASS=\"error_message\">No data</DIV>";
}
// If it's time to refresh the data end this loop and call the starting
// off method again
currentTime = new Date();
if (currentTime.getTime() >= endTime)
{
clearInterval(somethingDoer);
doAJAX();
}
}
function doAJAX()
{
// If browser supports javascript
if (window.XMLHttpRequest)
{
// Connect to the server and get the new pair of values
request = new XMLHttpRequest();
request.open('get', URL);
request.onreadystatechange = function()
{
// Once...
if (request.readyState == 4)
{
// we've got...
if (request.status == 200)
{
// the response
if (request.responseText)
{
// Parse the response and work out our iterator
previousValue = parseFloat(request.responseText.split("&")[0]);
latestValue = parseFloat(request.responseText.split("&")[1]);
if ((previousValue == 0) || (latestValue == 0))
{
it = parseFloat('0.00');
}
else
{
it = parseFloat((latestValue - previousValue) / (updateDataFrequency / updateDivFrequency));
}
// Set up the doRefreshDiv function in a loop that
// will exit and re-call this function
// once updateDataFrequency has elapsed
currentValue = previousValue;
endTime = new Date().getTime() + updateDataFrequency;
doDivRefresh(endTime);
somethingDoer = setInterval(function()
{
doDivRefresh(endTime);
}, updateDivFrequency);
alert("end of ajax response function()");
}
else
{
document.getElementById(divName).innerHTML = "<DIV CLASS=\"error_message\">Error - no data received from server</DIV>";
}
}
else
{
document.getElementById(divName).innerHTML = "<DIV CLASS=\"error_message\">Error - server returned " + request.status + "</DIV>";
}
}
};
request.send(null);
}
else
{
console.error("No window.XMLHttpRequest, does this browser support AJAX?");
}
alert("end of doAJAX()");
}
// Start
updateDataFrequency = updateDataFrequency * 1000;
doAJAX();
}
These are the first four lines corrected according to jslint.com:
function updateStatOverPeriod(divID, URL, updateDivFrequency, updateDataFrequency) {
"use strict";
var somethingDoer = function() {};
var request = new XMLHttpRequest();
Paste your code there and check for common JS errors. It seems this code was either transformed from an other language to JavaScript or somebody who is not familiar with JavaScript...
See my answer javascript memory leak for dealing with memory leaks. I still think Drip is a very good tool for finding and understanding memory leaks.
I agree that new Date() is always going to be costly. Now here as you just want this code to be executed to refresh values at regular interval can you use setTimeout("doAJAX()",2000); and you can specify the interval in milliseconds instead of checking it using a date function. That should help and in another case you can well use jquery.Ajax if you're interested because there you will be able to concentrate on your area of interest instead of dealing with XMLHttpRequest by yourself.
[head slap]
Thanks for the answers guys, but the real problem was a typo in the element calling the script. Due to a / where I meant to put a * the script was being called about once a millisecond instead of once a minute as intended!

Categories

Resources