component-search-page-selector.js

/**
 * Facet page selector component functionality
 * @module searchPageSelector
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with component page selector
*/
XA.component.search.pageSelector = (function ($, document) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:searchPageSelector
    */
    var api = {},
        queryModel,
        initialized = false;
    facetName = "e";
    /**
    * @name module:searchPageSelector.SearchPageSelectorModel
    * @constructor
    * @augments Backbone.Model
    */
    var SearchPageSelectorModel = Backbone.Model.extend(
        /** @lends module:searchPageSelector.SearchPageSelectorModel.prototype **/
        {
            /**
            * Default model options
            * @default
            */
            defaults: {
                dataProperties: {},
                resultsCount: 0,
                offset: 0,
                selectedValue: 1,
                pageSize: 0,
                repeatRequest: false,
                template: "<ul class='page-selector-list'> " +
                    "<li class='page-selector-item-first'><a href='#'><%= data.first %></a></li>" +
                    "<li class='page-selector-item-previous'><a href='#'><%= data.previous %></a></li>" +
                    "<% var beforePage = 0; %>" +
                    "<% _.each(data.pages, function(page){ %>" +
                    "<% if((beforePage+1) != page.number){ %>" +
                    "<li><span class='page-selector-more'>...</span></li>" +
                    "<% } %>" +
                    "<% beforePage = page.number; %>" +
                    "<% if(data.selectedValue === page.number){ %>" +
                    "<% active = 'active'; %>" +
                    "<% }else { active = '' } %>" +
                    "<li><a class='page-selector-item-link <%= active %>' data-offset='<%= page.offset %>' data-itemNumber='<%= page.number %>' href='#'><%= page.number %></a></li>" +
                    "<% }); %>" +
                    "<li class='page-selector-item-next'><a href='#'><%= data.next %></a></li>" +
                    "<li class='page-selector-item-last'><a href='#'><%= data.last %></a></li>" +
                    "</ul>",
                sig: [],
                timeStamp: new Date().getTime()
            }
        });
    /**
     * @name module:searchPageSelector.SearchPageSelectorView
     * @constructor
     * @augments Backbone.View
     */
    var SearchPageSelectorView = XA.component.search.baseView.extend(
        /** @lends module:searchPageSelector.SearchPageSelectorView **/
        {
            /**
           * Initially sets data to model and watches events on which
           * view should be updated
           * @listens module:searchPageSelector.SearchPageSelectorView~event:change
           * @listens module:XA.component.search.vent~event:results-loaded
           * @memberof module:searchPageSelector.SearchPageSelectorView
           * @alias module:searchPageSelector.SearchPageSelectorView#initialize
           */
            initialize: function () {
                var dataProp = this.$el.data();

                if (dataProp.properties.searchResultsSignature === null) {
                    dataProp.properties.searchResultsSignature = "";
                }

                this.model.set("dataProperties", dataProp);
                this.model.set("sig", this.translateSignatures(dataProp.properties.searchResultsSignature, facetName));
                this.model.on("change", this.render, this);

                XA.component.search.vent.on("results-loaded", this.handleLoadedData.bind(this));

                //check whether page is opened from disc - if yes then we are in Creative Exchange mode and lets mock some content
                if (window.location.href.startsWith("file://")) {
                    this.model.set({
                        "resultsCount": 10,
                        "pageSize": 2,
                        "selectedValue": 2
                    });
                }
            },
            /**
             * list of events for Backbone View
             * @memberof module:searchPageSelector.SearchPageSelectorView
             * @alias module:searchPageSelector.SearchPageSelectorView#events
             */
            events: {
                'click .page-selector-item-link': "updateSelectedValue",
                'click .page-selector-item-first a': "showFirstPage",
                'click .page-selector-item-last a': "showLastPage",
                'click .page-selector-item-previous a': "showPrevPage",
                'click .page-selector-item-next a': "showNextPage"
            },
            /**
            * Updates model values with selectedValue
            * and data object from server response
            * @param {Object} data Data that contains component properties
            * @param {Number} selectedValue number of selected page 
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateModelAfterSearch
            */
            updateModelAfterSearch: function (data, selectedValue) {
                this.model.set({
                    "pageSize": parseInt(data.pageSize),
                    "resultsCount": parseInt(data.dataCount),
                    "offset": parseInt(data.offset),
                    "selectedValue": parseInt(selectedValue)
                });
                this.model.set("timeStamp", new Date().getTime());
                this.updateElementCssClass(data);
            },
            /**
            * Function that based on selected component values sets css class
            * page-selector-empty or page-selector-single-page
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateElementCssClass
            */
            updateElementCssClass: function (data) {
                this.el.classList.remove("page-selector-empty")
                this.el.classList.remove("page-selector-single-page")
                if (data.dataCount === 0) {
                    this.el.classList.add("page-selector-empty")
                } else if (data.pageSize > data.dataCount || data.offset > data.dataCount) {
                    this.el.classList.add("page-selector-single-page")
                }
            },
            /**
            * Updates hash according to changed component value
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#updateSelectedValue
            */
            updateSelectedValue: function (event) {
                event.preventDefault();

                var sig = this.model.get("sig"),
                    dataProp = $(event.target).data();

                queryModel.updateHash(this.updateSignaturesHash(sig, dataProp.offset, {}));
            },
            /**
            * Switches page selector to the first page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showFirstPage
            */
            showFirstPage: function (event) {
                event.preventDefault();

                var sig = this.model.get("sig"),
                    dataProp = $(event.target).data();

                queryModel.updateHash(this.updateSignaturesHash(sig, 0, {}));
            },
            /**
            * Switches page selector to the last page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showLastPage
            */
            showLastPage: function (event) {
                event.preventDefault();
                var lastPageItems = this.model.get("resultsCount") % this.model.get("pageSize"),
                    offset = this.model.get("resultsCount") - ((lastPageItems === 0) ? this.model.get("pageSize") : lastPageItems),
                    sig = this.model.get("sig");

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
            },
            /**
            * Switches page selector to the next page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showNextPage
            */
            showNextPage: function (event) {
                event.preventDefault();

                var offset = this.model.get("offset"),
                    sig = this.model.get("sig");

                if ((offset + this.model.get("pageSize")) < this.model.get("resultsCount")) {
                    offset += this.model.get("pageSize");
                }

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
            },
            /**
            * Switches page selector to the precious page and updates hash parameters
            * @param {Event} event Event object that contains target with selected component value
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#showPrevPage
            */
            showPrevPage: function (event) {
                event.preventDefault();

                var offset = this.model.get("offset"),
                    dataProp = $(event.target).data(),
                    sig = this.model.get("sig");

                if ((offset - this.model.get("pageSize")) >= 0) {
                    offset -= this.model.get("pageSize");
                }

                queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
            },
            /**
            * Renders view
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#render
            */
            render: function () {
                var inst = this,
                    dataProp = this.model.get("dataProperties").properties,
                    resultsCount = this.model.get("resultsCount"),
                    pageSize = this.model.get("pageSize"),
                    selectedValue = this.model.get("selectedValue"),
                    pagesCount = Math.ceil(resultsCount / pageSize),
                    pages = [],
                    rangeStart = selectedValue - dataProp.treshold / 2,
                    rangeEnd = selectedValue + dataProp.treshold / 2,
                    templateObj;

                if (rangeStart < 0) {
                    rangeEnd += Math.abs(rangeStart);
                }

                if (rangeEnd > pagesCount) {
                    rangeStart -= (rangeEnd - pagesCount);
                }

                if (dataProp.treshold >= pagesCount) {
                    for (var i = 0; i < pagesCount; i++) {
                        pages.push({ number: i + 1, offset: i * pageSize });
                    }
                }
                else {
                    for (var i = 1; i <= pagesCount; i++) {
                        if ((i === 1) || (i === pagesCount)) {
                            pages.push({ number: i, offset: (i - 1) * pageSize });
                        }
                        else if ((i >= rangeStart) && (i <= rangeEnd)) {
                            pages.push({ number: i, offset: (i - 1) * pageSize });
                        }
                    }
                }

                templateObj = {
                    previous: dataProp.previous,
                    first: dataProp.first,
                    next: dataProp.next,
                    last: dataProp.last,
                    pages: pages,
                    selectedValue: selectedValue
                };
                var template = _.template(inst.model.get("template"));
                var templateResult = template({ data: templateObj });
                this.$el.html(templateResult);

                this.handleButtonState(selectedValue, pagesCount);
            },
            /**
            * Sets up page selectors buttons state depending on selected values
            * @param {Number} selectedPage number of selected page
            * @param {Number} pageCount number of pages
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#handleButtonState
            */
            handleButtonState: function (selectedPage, pageCount) {
                this.$el.find(".page-selector-item-last, .page-selector-item-next").removeClass("inactive");
                this.$el.find(".page-selector-item-first, .page-selector-item-previous").removeClass("inactive");


                if (pageCount == 0) {
                    this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
                    this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
                }
                else {
                    if (selectedPage == 1) {
                        this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
                    }
                    if (selectedPage == pageCount) {
                        this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
                    }
                }
            },
            /**
            * Updates hash and model based on data object
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#handleLoadedData
            */
            handleLoadedData: function (data) {
                var that = this,
                    sig = this.model.get("dataProperties").properties.searchResultsSignature.split(','),
                    hash = queryModel.parseHashParameters(window.location.hash),
                    newSelectedValue,
                    param,
                    hashObj,
                    i;

                if (typeof data.offset === 'undefined') {
                    data.offset = 0;
                }

                for (i = 0; i < sig.length; i++) {
                    if (encodeURIComponent(sig[i]) === data.searchResultsSignature) {
                        if (data.pageSize > data.dataCount || data.offset > data.dataCount) {
                            //When we have less results then page size then show first page
                            this.updateModelAfterSearch(data, 1);
                            //Wait till all requests are done and go to first page
                            setTimeout(function () {
                                //queryModel.updateHash({e: 0}); //- this will work, but doesn't give us proper browser history
                                hashObj = queryModel.parseHashParameters(window.location.hash);
                                param = data.searchResultsSignature !== "" ? data.searchResultsSignature + "_e" : "e";
                                if (hash[param] !== "0") {
                                    hashObj[param] = 0;
                                    Backbone.history.navigate(that.createFirstPageUrlHash(hashObj), { trigger: true, replace: true });
                                }
                            }, 100);
                        } else {
                            newSelectedValue = Math.ceil(data.offset / data.pageSize) + 1;
                            this.updateModelAfterSearch(data, newSelectedValue);
                        }
                    }
                }
            },
            /**
            * Calculates hash parameters for first page
            * @param {Object} data Data that contains component properties
            * @memberOf module:searchPageSelector.SearchPageSelectorView
            * @alias module:searchPageSelector.SearchPageSelectorView#createFirstPageUrlHash
            * @returns {String} hash parameter for first page
            */
            createFirstPageUrlHash: function (hashObj) {
                var hashStr = "";

                var i = 0;
                _.each(hashObj, function (item, key) {
                    if (i > 0) {
                        hashStr += "&";
                    }
                    i++;
                    hashStr += key + "=" + item;
                });

                return hashStr;
            }
        });

    /**
    * For each page selector on a page creates instance of 
    * ["SearchPageSelectorModel"]{@link module:searchPageSelector.SearchPageSelectorModel} and 
    * ["SearchPageSelectorView"]{@link module:searchPageSelector.SearchPageSelectorView} 
    * @memberOf module:searchPageSelector
    * @alias module:searchPageSelector.SearchPageSelectorView.init
    */
    api.init = function () {
        if ($("body").hasClass("on-page-editor") || initialized) {
            return;
        }

        queryModel = XA.component.search.query;

        var searchPageSelector = $(".page-selector");
        _.each(searchPageSelector, function (elem) {
            new SearchPageSelectorView({ el: $(elem), model: new SearchPageSelectorModel });
        });

        initialized = true;
    };

    return api;

}(jQuery, document));

XA.register('searchPageSelector', XA.component.search.pageSelector);