File: development/modules/ajaxapp/js/ajaxapp.js
import H from 'handlebars';
import {pipe} from '../../../js/utils/func';
import Channels from '../../../js/globals/channels';
import DOMModule from '../../__base/dommodule';
import Pagination from '../../fke-pagination/js/pagination';
/**
* @module Modules
*/
const handleSubmit = Symbol('_handleSubmit');
const handleResize = Symbol('_handleResize');
function fieldsToHashMap (xs) {
return xs.reduce((hashmap, x) => {
hashmap[x.name] = x.value;
return hashmap;
}, {});
}
function childsHeight (node) {
return $(node).children().toArray().
reduce((acc, child) => {
return $(child).outerHeight(true) + acc;
}, 0);
}
/**
* AjaxApp module
* @class Modules.AjaxApp
* @constructor
* @extends Modules._DOMModule
*/
class AjaxApp extends DOMModule {
constructor(options) {
super(options);
this._action = this.$rootNode.prop('action');
this._method = this.$rootNode.attr('method') || 'POST';
}
/**
* Creates a new AjaxApp instance without the "new" keyword but does not
* initialize it
* @method of
* @static
* @for Modules.AjaxApp
* @param {node|string|configs} node Node, CSS selector or configuration object
* @return {AjaxApp} A new instance
*
* @example
* import AjaxApp from './modules/ajaxapp/js/ajaxapp';
*
* const myAjaxApp = AjaxApp.of('[data-widget=AjaxApp]');
* myAjaxApp.render().startUp();
*/
static of (node) {
return new AjaxApp(node);
}
/**
* Finds and initializes all AjaxApps on a page at once
* @method findAll
* @static
* @for Modules.AjaxApp
* @param {string} selector CSS selector to match nodes against
* @return {array} A list of instances
*
* @example
* import AjaxApp from './modules/ajaxapp/js/ajaxapp';
*
* AjaxApp.findAll('[data-widget=AjaxApp]');
*/
static findAll(selector) {
return $(selector).toArray().filter((node) => {
return !$(node).data('defer');
}).map((node) => {
const ajaxapp = AjaxApp.of(node);
ajaxapp.render().startUp();
return ajaxapp;
});
}
/**
* Renders missing nodes into the instance and returns it
* @method render
* @for Modules.AjaxApp
* @return {this} The instance
*
* @example
* import AjaxApp from './modules/ajaxapp/js/ajaxapp';
*
* const myAjaxApp = AjaxApp.of('[data-widget=AjaxApp]');
* myAjaxApp.render();
*/
render() {
var $results = $(this.$data('ajaxapp-result-container'));
var $spinner = $('<div class="loader">Loading...</div>');
this.$resultContainer = $results.after($spinner);
return this;
}
/**
* Initializes sub-components, attaches all eventlisteners to the instance.
* Initiates initial Ajax-Request if configured that way
* (data-ajaxapp-initcall)
* @method startUp
* @for Modules.AjaxApp
* @return {undefined} Nothing
*
* @example
* import AjaxApp from './modules/ajaxapp/js/ajaxapp';
*
* const myAjaxApp = AjaxApp.of('[data-widget=AjaxApp]');
* myAjaxApp.render().startUp();
*/
startUp() {
this._initResultTmpl();
this._initPagination();
this.onSubmit(handleSubmit);
this.onResize(handleResize)
this.$findChilds('input[type="radio"]').on('change', this[handleSubmit].bind(this))
this.$findChilds('select').on('change', this[handleSubmit].bind(this));
this.$findChilds('input.datepicker').on('change', this[handleSubmit].bind(this));
if (this.$data('ajaxapp-initcall')) {
this.request({ desiredPage: 1}).
done(this._requestSuccess.bind(this));
// .fail(); // TODO
}
}
/**
* Initiates ajax request to server.
* Initiates initial Ajax-Request if configured that way
* (data-ajaxapp-initcall)
* @method request
* @param {object} info information that have to be stored before requests
* at the moment: { desiredPage: 1 } for requested page.
* @for Modules.AjaxApp
* @return {jqXHR} pending request
*
* @example
* import AjaxApp from './modules/ajaxapp/js/ajaxapp';
*
* const myAjaxApp = AjaxApp.of('[data-widget=AjaxApp]');
* myAjaxApp.render().startUp();
*/
request(info = { desiredPage: 1 }) {
if (this._requestInFlight) {
this._requestInFlight.abort();
}
this.$findChilds('input[data-ajaxapp-pagination]').val(info.desiredPage);
this.$resultContainer.
addClass('is-loading').
css({ 'height': this.$resultContainer.height() });
this._requestInFlight = $.ajax({
url: this._action,
method: this._method,
data: fieldsToHashMap(this.$rootNode.serializeArray()),
dataType: 'json'
});
return this._requestInFlight;
}
// === PRIVATE STUFF ===
_initResultTmpl() {
var $resultTmpl = $(this.$data('ajaxapp-result-tmpl')).remove();
this._tmpl = H.compile($resultTmpl.first().html());
}
_initPagination() {
var $pagination = $(this.$data('ajaxapp-pagination'));
if ($pagination.length) {
this.$$pagination = Pagination.of($pagination[0]);
this.$$pagination.render().startUp();
this.$$pagination.addObserver({ update: this._handlePaginationChange.bind(this) });
}
}
_requestSuccess(data) {
// i know piping doesn't make any sense here, but i'm not sure if i'm
// gonna need it some time.
pipe(
this._updatePagination.bind(this),
this._renderResults.bind(this),
this._showResults.bind(this)
)(data);
setTimeout(() => {
this.publish(Channels.CONTENT_HEIGHTCHANGED)
}, 300); // cause there's a transition on the height of <ul>
}
_renderResults(data) {
this.$resultContainer.html(this._tmpl(data));
return data;
}
_showResults(data) {
this.$resultContainer.
removeClass('is-loading').
css({ 'height': childsHeight(this.$resultContainer) });
return data;
}
_updatePagination(data) {
if (this.$$pagination) {
this.$$pagination.setCurrentPage(1);
this.$$pagination.setPageCount(data.pages);
}
return data;
}
// === OBSERVER HANDLERS ===
_handlePaginationChange(data) {
this.request({ desiredPage: data.page }).
done(pipe(
this._renderResults.bind(this),
this._showResults.bind(this)
));
// done((data) => {
// setTimeout(() => {
// this._renderResults(data),
// this._showResults(data)
// }, 2000)
// });
}
// === EVENT HANDLERS ===
[handleSubmit](event) {
event.preventDefault();
this.request({ desiredPage: 1 }).
done(this._requestSuccess.bind(this));
// done((data) => {
// setTimeout(() => {
// this._requestSuccess(data);
// }, 2000)
// });
}
[handleResize](event) {
event.preventDefault();
this.$resultContainer.
css({ 'height': childsHeight(this.$resultContainer) });
}
}
export default AjaxApp;