Skip to content

Instantly share code, notes, and snippets.

@eissasoubhi
Last active June 8, 2021 19:45
Show Gist options
  • Save eissasoubhi/f4550936c6f515c8876b22c606220459 to your computer and use it in GitHub Desktop.
Save eissasoubhi/f4550936c6f515c8876b22c606220459 to your computer and use it in GitHub Desktop.
DataManager to manage continuous AJAX loading on an event (eg: on scroll to bottom)
// =========================== EventManager ========================
function EventManager () {
// events store
this.events_queue = {};
}
// Register an event
EventManager.prototype.on = function (event_name, closure) {
if (! Array.isArray(this.events_queue[event_name]) ) {
this.events_queue[event_name] = [];
}
this.events_queue[event_name].push(closure);
return this;
}
// Fire an event
EventManager.prototype.trigger = function (event_name, params) {
var events = this.events_queue[event_name] || [];
for (var i = 0; i < events.length; i++) {
events[i].apply(this, params);
}
return this;
}
EventManager.prototype.clearAll = function () {
this.events_queue = {};
return this;
}
// =========================== DataManager ========================
function DataManager(options) {
this.options = $.extend({
// full http url for fetching data
url: null,
// array of objects with 'src' and 'title' keys
data: [],
// the key name that holds the data array
responseDataKey: 'data',
// the key name that holds the next page link
nextPageKey: 'links.next',
}, options);
this.init();
}
DataManager.prototype.init = function (response) {
this.current_page = 0;
this.is_fetching_locked = false;
this.event = new EventManager();
this.fetch_url = this.options.url;
this.fetch_type = this.options.data.length ? 'data' : (this.fetch_url ? 'url' : null);
}
// stop data fetching if neither next page link nor data were found
DataManager.prototype.setNextFetch = function (response) {
if (response.next_link && response.data.length) {
this.fetch_url = response.next_link;
} else {
this.lockFetching();
}
}
DataManager.prototype.lockFetching = function () {
this.is_fetching_locked = true;
}
DataManager.prototype.unlockFetching = function () {
this.is_fetching_locked = false;
}
// get a key from object with dot notation, example: data.key.subkey.
DataManager.prototype.getObjectKeyByString = function (object, dotted_key, default_val) {
var value = dotted_key.split('.').reduce(function (item, i) {
return item ? item[i] : {};
}, object);
if (typeof default_val == 'undefined') {
default_val = value;
}
return value && !$.isEmptyObject(value) ? value : default_val;
}
DataManager.prototype.parseResponse = function (response) {
return {
data: this.getObjectKeyByString(response, this.options.responseDataKey, []),
next_link: this.getObjectKeyByString(response, this.options.nextPageKey, null)
};
}
DataManager.prototype.fetchData = function () {
var _this = this;
if (this.fetch_type === 'data') {
this.event.trigger('beforeFetch');
this.event.trigger('fetch', [_this.options.data]);
this.event.trigger('afterFetch');
} else if (this.fetch_type === 'url') {
// Prevent simultaneous requests.
// Because we need to get the next page link from each request,
// they must be synchronous.
if (this.is_fetching_locked) return;
var current_link = _this.fetch_url;
this.event.trigger('beforeFetch');
this.lockFetching();
$.ajax({
url: current_link,
beforeSend:function(xhr){
// set the request link to get it afterwards in the response
xhr.request_link = current_link;
},
})
.always(function () {
// this is the first callback to be called when the request finishs
_this.unlockFetching();
})
.done(function(response, status_text, xhr){
var parsed_response = _this.parseResponse(response);
_this.current_page++;
//
_this.setNextFetch(parsed_response);
_this.event.trigger('fetch', [
parsed_response.data,
_this.current_page,
xhr.request_link,
parsed_response.next_link
]);
})
.fail(function() {
_this.event.trigger('error', ["problem loading from " + current_link]);
})
.always(function () {
_this.event.trigger('afterFetch');
});
} else {
_this.event.trigger('error', ["options 'data' or 'url' must be set"]);
}
}
DataManager.prototype.fetchNext = function () {
if (this.fetch_type === 'url') {
this.fetchData();
}
}
// The following is the default json structure the DataManager excpect, but it could be overridden in the options object:
// {
// data: [],
// links: {
// next: "www.site.com/api/resources?page=2"
// }
// }
var dataManager = new DataManager({
url: "www.site.com/api/resources?page=1"
});
dataManager.event
.on('beforeFetch', function () {
// show loading... for example
})
.on('fetch', function (data, page, link) {
// get data as json
})
.on('afterFetch', function () {
// hide loading
})
.on('error', function (error) {
// log the error or display on the ui
console.error(error);
});
window.onscroll = function () {
let is_near_bottom = Math.max(
window.pageYOffset,
document.documentElement.scrollTop,
document.body.scrollTop
) + window.innerHeight >= document.documentElement.offsetHeight - 200
if (is_near_bottom) {
// load more content everytome the scroll reaches the bottom,
// more content means the content of the next page if exists.
dataManager.fetchNext();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment