Last active
June 8, 2021 19:45
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// =========================== 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