eduinaf-ajax-filter-posts/assets/js/ajax-filter-posts.js

328 lines
9.2 KiB
JavaScript
Executable File

(function (){
var queryParams = null;
var container = document.querySelector('.js-container-async');
if (container) {
var filterTogglers = container.getElementsByClassName('js-toggle-filters');
var content = container.querySelector('.ajax-posts__posts');
var status = container.querySelector('.ajax-posts__status');
}
function init() {
if (container) {
addEventListeners();
setDefaults();
}
}
/**
* Add event listeners
*/
function addEventListeners() {
// Add event listeners with event propagination
on(container,'click', 'a[data-filter]', function(event) {
handleFilterEvent(event.target);
event.preventDefault();
return true;
});
on(container, 'click', '.js-load-more', function(event) {
handleLoadMoreEvent(event.target);
event.preventDefault();
return true;
});
on(container, 'click', '.js-reset-filters', function() {
resetFilters();
event.preventDefault();
return true;
});
// Add event listner on all filter toggle buttons
[].slice.call(filterTogglers).forEach(function(button){
button.addEventListener('click', toggleFilters)
});
}
/**
* Set default query variables
*/
function setDefaults() {
queryParams = {
'page' : null,
'tax' : {},
'quantity': parseInt(container.dataset.quantity, 10) || 0,
'postType': container.dataset.postType || 'post',
'language': filterPosts.language || null,
};
}
/**
* Update query and get posts after filter change
*
* @param NodeElement filter Clicked filter
*/
function handleFilterEvent(filter) {
if (!filter.classList.contains('is-active')) {
filter.classList.add('is-active');
updateQueryParams({
page: 1,
filter: filter.dataset.filter,
term: filter.dataset.term,
});
} else {
filter.classList.remove('is-active');
removeQueryParam(filter.dataset.filter, filter.dataset.term);
}
getAJAXPosts({reset: true});
}
/**
* Get next page of posts
*
* @param NodeElement button Clicked load more button
*/
function handleLoadMoreEvent(button){
updateQueryParams({ page: parseInt(button.dataset.page, 10) })
getAJAXPosts({reset: false});
}
/**
* Reset filters to empty values
*/
function resetFilters() {
// Convert nodeList to array to prevent fail on for each
var activeFilters = Array.prototype.slice.call(container.querySelectorAll('a[data-filter].is-active'));
// Remove active classes
activeFilters.forEach(function(filter){
filter.classList.remove('is-active');
});
// Empty taxonomy from query
queryParams.tax = {};
queryParams.page = 1;
getAJAXPosts({reset: true});
}
/**
* Toggle the filter class.
* Called via event listeners
*/
function toggleFilters() {
container.classList.toggle('is-expanded-filters');
}
/**
* Update te query parameters based on the filter change
*
* @param Array params params that will be changed
*/
function updateQueryParams(params) {
queryParams.page = params.page;
// If we're also updating the taxonomy
if (params.filter) {
if (queryParams.tax.hasOwnProperty(params.filter)) {
queryParams.tax[params.filter].push(params.term);
} else {
queryParams.tax[params.filter] = [params.term];
}
}
}
/**
* Remove a term from the set of query params
*
* @param string tax taxonomy of the term to remove
* @param {tring term term to remove
*/
function removeQueryParam(tax, term) {
if (queryParams.tax.hasOwnProperty(tax)) {
if (queryParams.tax[tax].indexOf(term) > -1) {
queryParams.tax[tax].splice( queryParams.tax[tax].indexOf(term) , 1 );
}
}
}
/**
* Show the Error Reponse div
*/
function showResponseMessage() {
status.style.display = 'block';
status.scrollIntoView({behavior: "smooth"});
}
/**
* Show the Error Reponse div
*/
function hideResponseMessage() {
status.style.display = 'none';
}
/**
* Get new posts via Ajax
*
* Retrieve a new set of posts based on the created query
*
* @return string server side generated HTML
*/
function getAJAXPosts(args) {
// Set status to querying
container.classList.add('is-waiting');
var request = new XMLHttpRequest();
request.open('POST', filterPosts.ajaxUrl, true);
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.timeout = 4000; // time in milliseconds
request.onload = function() {
console.log(this.response);
//remove load more button
var loadMoreButton = content.querySelector('.js-load-more');
if (loadMoreButton) {
content.removeChild(loadMoreButton.parentNode);
}
var response = JSON.parse(this.response);
if (this.status === 200) {
// If we have a succesfull query
if (response.success) {
// Hide error status button
hideResponseMessage();
// If we have to remove the show more button
if (args.reset) {
content.innerHTML = response.data.content;
} else {
content.innerHTML += response.data.content;
}
} else {
status.innerHTML = response.data;
showResponseMessage();
}
} else {
status.innerHTML = filterPosts.serverErrorMessage;
showResponseMessage();
}
// Resolve status
container.classList.remove('is-waiting');
};
request.ontimeout = function() {
status.innerHTML = filterPosts.timeoutMessage;
showResponseMessage();
container.classList.remove('is-waiting');
}
request.send(objectToQueryString({
action: 'process_filter_change',
nonce: filterPosts.nonce,
params: queryParams,
}));
}
/**
* Helper function for event delegation
*
* To add event listeners on dynamic content, you can add a listener
* on thewrapping container, find the dom-node that triggered
* the event and check if that node mach our
*
* @param NodeElement el wrapping element for the dynamic content
* @param string eventName type of event, e.g. click, mouseenter, etc
* @param string selector selector criteria of the element where the action should be on
* @param Function fn callback funciton
* @return Function The callback
*/
function on(el, eventName, selector, fn) {
var element = el;
element.addEventListener(eventName, function(event) {
var possibleTargets = element.querySelectorAll(selector);
var target = event.target;
for (var i = 0, l = possibleTargets.length; i < l; i++) {
var el = target;
var p = possibleTargets[i];
while(el && el !== element) {
if (el === p) {
return fn.call(p, event);
}
el = el.parentNode;
}
}
});
}
/**
* Convert an deep object to a url parameter list
*
* Boiled down from jQuery
*
* WordPress Ajax post request doesn't accept JSON, only form-urlencoded!
* Took me a while to get...
*
* Although seems not to be totally true:
* http://wordpress.stackexchange.com/questions/177554/allowing-admin-ajax-php-to-receive-application-json-instead-of-x-www-form-url
*
* But in this case we just convert the params object to a url encoded string, like our friend and foe jQuery does.
*
*/
function objectToQueryString(a) {
var prefix, s, add, name, r20, output;
s = [];
r20 = /%20/g;
add = function (key, value) {
// If value is a function, invoke it and return its value
value = ( typeof value == 'function' ) ? value() : ( value == null ? "" : value );
s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
};
if (a instanceof Array) {
for (name in a) {
add(name, a[name]);
}
} else {
for (prefix in a) {
buildParams(prefix, a[ prefix ], add);
}
}
output = s.join("&").replace(r20, "+");
return output;
};
/**
* Helper function to create URL parameters of deep object
*
* Boiled down from jQuery
*/
function buildParams(prefix, obj, add) {
var name, i, l, rbracket;
rbracket = /\[\]$/;
if (obj instanceof Array) {
for (i = 0, l = obj.length; i < l; i++) {
if (rbracket.test(prefix)) {
add(prefix, obj[i]);
} else {
buildParams(prefix + "[" + ( typeof obj[i] === "object" ? i : "" ) + "]", obj[i], add);
}
}
} else if (typeof obj == "object") {
// Serialize object item.
for (name in obj) {
buildParams(prefix + "[" + name + "]", obj[ name ], add);
}
} else {
// Serialize scalar item.
add(prefix, obj);
}
}
init();
}());