API Docs for: 1.0.0
Show:

File: Resources/public/js/extensions/ez-loadmorepagination.js

/*
 * Copyright (C) eZ Systems AS. All rights reserved.
 * For full copyright and license information view LICENSE file distributed with this source code.
 */
YUI.add('ez-loadmorepagination', function (Y) {
    "use strict";
    /**
     * Provide the LoadMorePagination extension
     *
     * @module ez-loadmorepagination
     */
    Y.namespace('eZ');

    var IS_PAGE_LOADING = 'is-page-loading';

    /**
     * View extension providing a pagination based on a *load more* button.
     * A *Load More Pagination* view can render and append an item view per new
     * item added in the `items` attribute. The view output should provide an
     * element with the class `ez-loadmorepagination-more`, when the user *taps*
     * on its element, the `offset` attribute is incremented by `limit` value.
     * The view is free to handle that event in its own way but this extension
     * is primarily design to be used with the `eZ.AsynchronousView` extension.
     * It also handles a `loading` state which result in the `is-page-loading`
     * class to be added/removed from the view container.
     * The extension also expects the rendered view to have the following
     * elements:
     * * an element with the class `ez-loadmorepagination-content` where the
     * item view containers are added
     * * an element with the class `ez-loadmorepagination-more-count` where the
     * remaing number of element to load is displayed
     * * an element with the class `ez-loadmorepagination-display-count` where
     * the number of currently displayed element is shown.
     *
     * When a View is extended with this extension, it should define the
     * following properties:
     * * `_getExpectedItemsCount` a function that should return the number of
     * expected items in total
     * * `_ItemViews` a constructor function to build the View items
     * * `_itemViewBaseConfig` the base configuration to pass the item views.
     *   This object will be merged with the item object and will be passed to
     *   the item view.
     *
     * @namespace eZ
     * @class LoadMorePagination
     * @extensionfor Y.View
     */
    Y.eZ.LoadMorePagination = Y.Base.create('loadMorePagination', Y.View, [], {
        events: {
            '.ez-loadmorepagination-more': {
                'tap': '_loadMore',
            },
        },

        initializer: function () {
            /**
             * Holds the grid item view instances for the current grid.
             * This property is deprecated and will be removed in PlatformUI
             * 2.0, please use `this._itemViews` instead.
             *
             * @property _gridItemViews
             * @protected
             * @deprecated
             * @type Array<SubitemGridItemView>
             */
            this._gridItemViews = [];

            /**
             * Holds the item view instances
             *
             * @property _itemViews
             * @protected
             * @type Array<eZ.View>
             */
            this._itemViews = [];

            /**
             * Holds a function returning the number of items the view is supposed
             * to display
             *
             * @property _getExpectedItemsCount
             * @protected
             * @required
             * @type {Function}
             */

            /**
             * Holds the View constructor to use to render each item.
             *
             * @property _ItemView
             * @protected
             * @required
             * @type {Function}
             */

            /**
             * Holds the base configuration object for the item view. This
             * object will be merged with the item object and the resulting
             * object is used as the configuraton for the item view instance.
             *
             * @property _itemViewBaseConfig
             * @protected
             * @required
             * @type {Object}
             */

            this.get('container').addClass('ez-loadmorepagination');
            if ( this.get('loading') ) {
                this._uiPageLoading();
            }
            this.after('loadingChange', function () {
                if ( this.get('loading') ) {
                    this._uiPageLoading();
                } else {
                    this._uiPageEndLoading();
                }
            });

            this.after('offsetChange', function (e) {
                this._set('loading', true);
                this._disableLoadMore();
            });
            this.after('itemsChange', function (e) {
                this._set('loading', false);
                this._appendItems(this._getNewlyAddedItems(e.newVal, e.prevVal));
                this._uiUpdatePagination();
            });
        },

        /**
         * Destroys the item views
         *
         * @private
         * @method _destroyItemViews
         */
        _destroyItemViews: function () {
            this._itemViews.forEach(function (item) {
                item.destroy({remove: true});
            });
            this._itemViews = [];
        },

        destructor: function () {
            this._destroyItemViews();
        },

        /**
         * `tap` event handler on the load more button.
         *
         * @method _loadMore
         * @protected
         */
        _loadMore: function (e) {
            e.preventDefault();
            if ( !e.currentTarget.get('disabled') ) {
                this.set('offset', this.get('offset') + this.get('limit'));
            }
        },

        /**
         * Sets the UI in the loading the state
         *
         * @protected
         * @method _uiPageLoading
         */
        _uiPageLoading: function () {
            this.get('container').addClass(IS_PAGE_LOADING);
        },

        /**
         * Removes the loading state of the UI
         *
         * @method _uiPageEndLoading
         * @protected
         */
        _uiPageEndLoading: function () {
            this.get('container').removeClass(IS_PAGE_LOADING);
        },

        /**
         * Counts the number of loaded and displayed items.
         *
         * @method _countLoadedItems
         * @protected
         * @return {Number}
         */
        _countLoadedItems: function () {
            return this._itemViews.length;
        },

        /**
         * Returns the load more button
         *
         * @method _getLoadMore
         * @protected
         * @return {Y.Node}
         */
        _getLoadMore: function () {
            return this.get('container').one('.ez-loadmorepagination-more');
        },

        /**
         * Disables the load more button
         *
         * @method _disableLoadMore
         * @protected
         */
        _disableLoadMore: function () {
            this._getLoadMore().set('disabled', true);
        },

        /**
         * Enables the load more button
         *
         * @method _enableLoadMore
         * @protected
         */
        _enableLoadMore: function () {
            this._getLoadMore().set('disabled', false);
        },

        /**
         * Updates the pagination displayed to the editor.
         *
         * @method _uiUpdatePagination
         * @protected
         */
        _uiUpdatePagination: function () {
            this._updateDisplayedCount();
            this._updateMoreCount();
            if ( this._countLoadedItems() < this._getExpectedItemsCount() ) {
                this._enableLoadMore();
            } else {
                this._disableLoadMore();
            }
        },

        /**
         * Updates the display count with the number of currently loaded
         * subitems.
         *
         * @method _updateDisplayedCount
         * @protected
         */
        _updateDisplayedCount: function () {
            this.get('container').one('.ez-loadmorepagination-display-count').setContent(
                this._countLoadedItems()
            );
        },

        /**
         * Updates the more count in the load more button.
         *
         * @method _updateMoreCount
         * @protected
         */
        _updateMoreCount: function () {
            var moreCount = Math.min(
                    this.get('limit'),
                    this._getExpectedItemsCount() - this._countLoadedItems()
                );

            if ( !moreCount ) {
                moreCount = this.get('limit');
            }
            this.get('container').one('.ez-loadmorepagination-more-count').setContent(moreCount);
        },

        /**
         * Returns the item view configuration for the given `struct`.
         * It mixes the view configuration, the  base item view configuration
         * and the struct.
         *
         * @private
         * @method _getItemViewConfig
         * @param {Object} struct
         * @return {Object}
         */
        _getItemViewConfig: function (struct) {
            return Y.merge(
                {config: this.get('config')},
                this._itemViewBaseConfig,
                struct
            );
        },

        /**
         * Appends a rendered item view for the last loaded items.
         *
         * @method _appendItems
         * @deprecated
         * @protected
         * @param {Array} newItems
         */
        _appendItems: function (newItems) {
            var content = this.get('container').one('.ez-loadmorepagination-content'),
                ItemView = this._ItemView;

            newItems.forEach(function (struct) {
                var itemView = new ItemView(this._getItemViewConfig(struct));

                itemView.addTarget(this);
                this._itemViews.push(itemView);
                content.append(
                    itemView.render().get('container')
                );
                itemView.set('active', true);
            }, this);
        },

        /**
         * Return an array containing the items that we want to append
         * in the view.
         *
         * @method _getNewlyAddedItems
         * @private
         * @param {Array} itemNewVal an array containing the new items.
         * @param {Array} itemPrevVal an array containing the old items.
         */
        _getNewlyAddedItems: function (itemNewVal, itemPrevVal) {
            var itemPreviousCount = itemPrevVal ? itemPrevVal.length : 0,
                itemNewCount = itemNewVal.length;

            return this.get('items').slice(itemPreviousCount - itemNewCount);
        },
    }, {
        ATTRS: {
            /**
             * Indicates the whether the subitem list is currently in loading.
             *
             * @attribute loading
             * @type {Boolean}
             * @readOnly
             */
            loading: {
                readOnly: true,
            },


            /**
             * The max number of the Locations to display per 'show more'
             * session
             *
             * @attribute limit
             * @default 10
             * @type Number
             */
            limit: {
                value: 10,
            },

            /**
             * The offset in the Location list. A value below zero means no
             * loading has been made yet.
             *
             * @attribute offset
             * @default minus the limit
             * @type Number
             */
            offset: {
                valueFn: function () {
                    return -1 * this.get('limit');
                },
            },

            /**
             * The items to display
             *
             * @attribute items
             * @type Array<Mixed>
             */
            items: {
                setter: function (value, attr, info) {
                    var current = this.get('items'),
                        flag = info || {};

                    if ( flag.reset ) {
                        return value;
                    }
                    if ( current ) {
                        return current.concat(value);
                    }
                    return value;
                },
            },
        },
    });
});