1
votes

My backend is Parse's BAAS. In order to fetch latest records first and paginate through previous records, I'll need to order descending by created timestamp, and use "skip" certain number of records in conjunction with "limit" number of records per fetch. Both skip and limit are +ve numbers.

I have a list in ST that needs to have the most recent record at the bottom. When a user enters a record, the record needs to append to the bottom. When the user scrolls to the top, the list needs to fetch previous records using pagination.

1st question: In order to show the most recent message at the bottom, I'm sorting the list on on ST in ascending order of created timestamp – is there a way to achieve this without the sort?

2nd question: I have a custom plugin (a slight variant of the list paging plugin) that fetches the previous records when scrolled to top, but even though I am dynamically setting the scrollToTop to false, once the store loads, the list keeps scrolling to the top (not with animation). How can I avoid this?

Any help is much appreciated! Thanks!

Here is my plugin code:

/**
 * Adds a Load More button at the bottom of the list. When the user presses this button,
 * the next page of data will be loaded into the store and appended to the List.
 *
 * By specifying `{@link #autoPaging}: true`, an 'infinite scroll' effect can be achieved,
 * i.e., the next page of content will load automatically when the user scrolls to the
 * bottom of the list.
 *
 * ## Example
 *
 *     Ext.create('Ext.dataview.List', {
 *
 *         store: Ext.create('TweetStore'),
 *
 *         plugins: [
 *             {
 *                 xclass: 'Ext.plugin.ListPaging',
 *                 autoPaging: true
 *             }
 *         ],
 *
 *         itemTpl: [
 *             '<img src="{profile_image_url}" />',
 *             '<div class="tweet">{text}</div>'
 *         ]
 *     });
 */
Ext.define('MyApp.ux.plugin.ListReversePaging', {
    extend: 'Ext.Component',
    alias: 'plugin.listreversepaging',
    config: {
        /**
         * @cfg {Boolean} autoPaging
         * True to automatically load the next page when you scroll to the bottom of the list.
         */
        autoPaging: false,
        /**
         * @cfg {String} loadMoreText The text used as the label of the Load More button.
         */
        loadMoreText: '',
        /**
         * @cfg {String} noMoreRecordsText The text used as the label of the Load More button when the Store's
         * {@link Ext.data.Store#totalCount totalCount} indicates that all of the records available on the server are
         * already loaded
         */
        noMoreRecordsText: '',
        /**
         * @private
         * @cfg {String} loadTpl The template used to render the load more text
         */
        loadTpl: [
            '<div class="{cssPrefix}loading-spinner" style="font-size: 180%; margin: 10px auto;">',
            '<span class="{cssPrefix}loading-top"></span>',
            '<span class="{cssPrefix}loading-right"></span>',
            '<span class="{cssPrefix}loading-bottom"></span>',
            '<span class="{cssPrefix}loading-left"></span>',
            '</div>',
            '<div class="{cssPrefix}list-paging-msg">{message}</div>'
        ].join(''),
        /**
         * @cfg {Object} loadMoreCmp
         * @private
         */
        loadMoreCmp: {
            xtype: 'component',
            baseCls: Ext.baseCSSPrefix + 'list-paging',
            scrollDock: 'top',
            hidden: true
        },
        /**
         * @private
         * @cfg {Boolean} loadMoreCmpAdded Indicates whether or not the load more component has been added to the List
         * yet.
         */
        loadMoreCmpAdded: false,
        /**
         * @private
         * @cfg {String} loadingCls The CSS class that is added to the {@link #loadMoreCmp} while the Store is loading
         */
        loadingCls: Ext.baseCSSPrefix + 'loading',
        /**
         * @private
         * @cfg {Ext.List} list Local reference to the List this plugin is bound to
         */
        list: null,
        /**
         * @private
         * @cfg {Ext.scroll.Scroller} scroller Local reference to the List's Scroller
         */
        scroller: null,
        /**
         * @private
         * @cfg {Boolean} loading True if the plugin has initiated a Store load that has not yet completed
         */
        loading: false
    },
    /**
     * @private
     * Sets up all of the references the plugin needs
     */
    init: function(list) {
        var scroller = list.getScrollable().
                getScroller(),
                store = list.getStore();


        this.setList(list);
        this.setScroller(scroller);
        this.bindStore(list.getStore());


        this.addLoadMoreCmp();


        // The List's Store could change at any time so make sure we are informed when that happens
        list.updateStore = Ext.Function.createInterceptor(list.updateStore, this.bindStore, this);


        if (this.getAutoPaging()) {
            scroller.on({
                scrollend: this.onScrollEnd,
                scope: this
            });
        }
    },
    /**
     * @private
     */
    bindStore: function(newStore, oldStore) {
        if (oldStore) {
            oldStore.un({
                beforeload: this.onStoreBeforeLoad,
                load: this.onStoreLoad,
                filter: this.onFilter,
                scope: this
            });
        }


        if (newStore) {
            newStore.on({
                beforeload: this.onStoreBeforeLoad,
                load: this.onStoreLoad,
                filter: this.onFilter,
                scope: this
            });
        }
    },
    /**
     * @private
     * Removes the List/DataView's loading mask because we show our own in the plugin. The logic here disables the
     * loading mask immediately if the store is autoloading. If it's not autoloading, allow the mask to show the first
     * time the Store loads, then disable it and use the plugin's loading spinner.
     * @param {Ext.data.Store} store The store that is bound to the DataView
     */
    disableDataViewMask: function() {
        var list = this.getList();
        this._listMask = list.getLoadingText();


        list.setLoadingText(null);
    },
    enableDataViewMask: function() {
        if (this._listMask) {
            var list = this.getList();
            list.setLoadingText(this._listMask);
            delete this._listMask;
        }
    },
    /**
     * @private
     */
    applyLoadTpl: function(config) {
        return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
    },
    /**
     * @private
     */
    applyLoadMoreCmp: function(config) {
        config = Ext.merge(config, {
            html: this.getLoadTpl().
                    apply({
                        cssPrefix: Ext.baseCSSPrefix,
                        message: this.getLoadMoreText()
                    }),
            scrollDock: 'bottom',
            listeners: {
                tap: {
                    fn: this.loadNextPage,
                    scope: this,
                    element: 'element'
                }
            }
        });


        return Ext.factory(config, Ext.Component, this.getLoadMoreCmp());
    },
    /**
     * @private
     * If we're using autoPaging and detect that the user has scrolled to the bottom, kick off loading of the next page
     */
    onScrollEnd: function(scroller, x, y) {

        var list = this.getList();


        if (!this.getLoading() && y <= 50) {

            this.currentScrollToTopOnRefresh = list.getScrollToTopOnRefresh();
            list.setScrollToTopOnRefresh(false);

            this.loadNextPage();
        }
    },
    /**
     * @private
     * Makes sure we add/remove the loading CSS class while the Store is loading
     */
    updateLoading: function(isLoading) {
        var loadMoreCmp = this.getLoadMoreCmp(),
                loadMoreCls = this.getLoadingCls();


        if (isLoading) {
            loadMoreCmp.addCls(loadMoreCls);
        } else {
            loadMoreCmp.removeCls(loadMoreCls);
        }
    },
    /**
     * @private
     * If the Store is just about to load but it's currently empty, we hide the load more button because this is
     * usually an outcome of setting a new Store on the List so we don't want the load more button to flash while
     * the new Store loads
     */
    onStoreBeforeLoad: function(store) {
        if (store.getCount() === 0) {
            this.getLoadMoreCmp().
                    hide();
        }
    },
    /**
     * @private
     */
    onStoreLoad: function(store) {
        var loadCmp = this.getLoadMoreCmp(),
                template = this.getLoadTpl(),
                message = this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText();


        if (store.getCount()) {
            loadCmp.show();
        }
        this.setLoading(false);


        //if we've reached the end of the data set, switch to the noMoreRecordsText
        loadCmp.setHtml(template.apply({
            cssPrefix: Ext.baseCSSPrefix,
            message: message
        }));


        if (this.currentScrollToTopOnRefresh !== undefined) {

            this.getList().
                    setScrollToTopOnRefresh(false);
            delete this.currentScrollToTopOnRefresh;
        }


        this.enableDataViewMask();
    },
    onFilter: function(store) {
        if (store.getCount() === 0) {
            this.getLoadMoreCmp().
                    hide();
        } else {
            this.getLoadMoreCmp().
                    show();
        }
    },
    /**
     * @private
     * Because the attached List's inner list element is rendered after our init function is called,
     * we need to dynamically add the loadMoreCmp later. This does this once and caches the result.
     */
    addLoadMoreCmp: function() {
        var list = this.getList(),
                cmp = this.getLoadMoreCmp();


        if (!this.getLoadMoreCmpAdded()) {
            list.add(cmp);


            /**
             * @event loadmorecmpadded  Fired when the Load More component is added to the list. Fires on the List.
             * @param {Ext.plugin.ListPaging} this The list paging plugin
             * @param {Ext.List} list The list
             */
            list.fireEvent('loadmorecmpadded', this, list);
            this.setLoadMoreCmpAdded(true);
        }


        return cmp;
    },
    /**
     * @private
     * Returns true if the Store is detected as being fully loaded, or the server did not return a total count, which
     * means we're in 'infinite' mode
     * @return {Boolean}
     */
    storeFullyLoaded: function() {
        var store = this.getList().
                getStore(),
                total = store.getTotalCount();


        return total !== null ? store.getTotalCount() <= (store.currentPage * store.getPageSize()) : false;
    },
    /**
     * @private
     */
    loadNextPage: function() {
        var me = this;
        if (!me.storeFullyLoaded()) {
            me.disableDataViewMask();
            me.setLoading(true);
            me.getList().
                    getStore().
                    nextPage({addRecords: true});
        }
    }
});
1

1 Answers

0
votes

For question 1

Sure: use insert( index, records ) http://docs.sencha.com/touch/2.3.1/#!/api/Ext.data.Store-method-insert

you could see how many items you have to know the index you should use to insert:

var totalCount = myStore.getTotalCount();  //lets say there are 10 elements
var recordMsg  = {title : "User message", content, "Hello"}; //create new model /record  

myStore.insert(totalCount, recordMsg); // so since there are 10 elements indexes 0 -9 are already taken, lets use index 10 and put it at the end