3
\$\begingroup\$

I'm creating a generic pagination helper class. Personally I use this class for navigating through Records which are loaded into any sequential containers e.g QVector, QList etc.

The idea is that you load your data from any source such as a database, cloud, file, etc.

You specify how many results you want to load per page and it generates
a pagination list or page numbers which you can wire up with any model on QML side.

Is this is a suitable approach, or is there a better way to do it? Any guides, suggestions or remarks are welcome.

This is best of my understanding; I could be completely wrong or there might be solutions out there which are more effective.

Just to sum it up:

IPaginationHandler is an interface class.

PaginationHandler class is an actual implementation. This class has an opaque pointer to actual data members.

In PaginationHandler.cpp

There is an Implementation class which has the data members.

Key Members are:

1.QHash<int,int> page indexes which will hold the value of which page number would have how many results. e.g for source container of 47 Size it generates values (1,10) (2,10) (3,10) (4,10) (5,7). This member is mutated in populatePageIndex() method.

2.QVector<int> which will contain actual page numbers like 1,2,3....15 etc this gets mutated in populatePageNumberContainer() method

3.int processRecords is like ref counter for records that has been processed by navigation, which in turn sets whether you are at the start of Data source or at the end of it; it also helps on UI part for navigation controls visibility.

// IPaginationHandler.h
class IPaginationHandler : public QObject
{
    Q_OBJECT
    // This is for navigation Control on UI side so you can set visibility or 
    // other visual changes 
    Q_PROPERTY(bool canMoveAhead READ canMoveAhead WRITE setCanMoveAhead NOTIFY canMoveAheadChanged)
    Q_PROPERTY(bool canMoveBack READ canMoveBack WRITE setCanMoveBack NOTIFY canMoveBackChanged)
public:
    explicit IPaginationHandler(QObject *parent=nullptr):QObject(parent){}
    virtual ~IPaginationHandler(){}

    enum class CursorDirection{
        MoveForward=0x001,
        MoveBackward,
        MoveLast,
        MoveFirst
    };Q_ENUM(CursorDirection)

public:
    virtual QHash<int,int> pageIndex()const=0;

    virtual void populatePageIndex(const int &sourceContainerSize,const int &resultPerPage)=0;

    virtual QVector<int> pageNumber()const=0;
    // QML was giving me error for c++ enums so I know there is way to do that 
   // but I couldn't figure it out so here we are.
    virtual void populatePageNumberContainer(const int &direction,const int &totalRecords)=0;

    virtual void setNavigationControl(const int &totalRecords,const int &sourceContainerSize,const int &direction)=0;

    virtual bool canMoveAhead()const=0;
    virtual void setCanMoveAhead(bool val)=0;

    virtual bool canMoveBack()const=0;
    virtual void setCanMoveBack(bool val)=0;


signals:
    void canMoveAheadChanged();
    void canMoveBackChanged();

    void prePageNumbersChanged();
    void postPageNumbersChanged();
};

Now the actual implementation:

PaginationHandler.h

 // PaginationHandler.h

class PaginationHandler : public IPaginationHandler
{
    Q_OBJECT
public:
    PaginationHandler(QObject *parent=nullptr);
    ~PaginationHandler()override;

     QHash<int, int> pageIndex() const override;
     void populatePageIndex(const int &sourceContainerSize, const int &resultPerPage) override;
     QVector<int> pageNumber() const override;
     void populatePageNumberContainer(const int &direction, const int &totalRecords) override;
     void setNavigationControl(const int &totalRecords, const int &sourceContainerSize, const int &direction) override;
     bool canMoveAhead() const override;
     void setCanMoveAhead(bool val) override;
     bool canMoveBack() const override;
     void setCanMoveBack(bool val) override;
private:
     class Implementation;
     std::unique_ptr<Implementation> opaque;

};

PaginationHandler.cpp

#include "Paginationhandler.h"
#include <QHash>
#include <QVector>
#include <cmath> 

const int TEN{10};
const int ONE{1};
const int ZERO{0};
const int FIVE{5};

PaginationHandler::PaginationHandler(QObject *parent):IPaginationHandler(parent)
{
    opaque = std::make_unique<Implementation>(this);
}

PaginationHandler::~PaginationHandler()
{

}


class PaginationHandler::Implementation{

public:
    PaginationHandler *handler{nullptr};
    QHash<int,int> pageIndexs; // I fixed this hash to 5 Pages.
    /* For example source Container with 100 size and result per page is 10
     * this hash would hold (0,10)... (4,10)
     * but it won't exceed more than 5 Pages.
    */
    QVector<int> pageNumbers; // this is the root container which will hold page 
                              // numbers

    int resultsPerPage;
    int totalPages;
    int currentPage;

    bool canMoveAhead{false};
    bool canMoveBack{false};

    int processedRecords;

public:
    Implementation(PaginationHandler *mInstance);
};

PaginationHandler::Implementation::Implementation(PaginationHandler *mInstance):handler(mInstance)
{
    resultsPerPage = TEN;
    totalPages = FIVE;
    currentPage = ONE;
    processedRecords = ZERO;
}


QHash<int, int> PaginationHandler::pageIndex() const
{
    return opaque->pageIndexs;
}

void PaginationHandler::populatePageIndex(const int &sourceContainerSize, const int &resultPerPage)
{
    int loadedRecords = sourceContainerSize;
    int minumumPages {ONE};

    opaque->resultsPerPage = resultPerPage;
    if(loadedRecords <= opaque->resultsPerPage){
        opaque->pageIndexs.clear();
        opaque->pageIndexs[minumumPages] = loadedRecords;
    }else if((loadedRecords > ZERO && loadedRecords <= opaque->resultsPerPage)){
        opaque->pageIndexs.clear();
        opaque->pageIndexs[minumumPages] = loadedRecords;
    }
    else{
        opaque->pageIndexs.clear();
        while(loadedRecords > opaque->resultsPerPage){
            opaque->pageIndexs[minumumPages] = opaque->resultsPerPage;
            loadedRecords -= opaque->resultsPerPage;
            minumumPages++;
        }

        if(loadedRecords!=ZERO){
            opaque->pageIndexs[minumumPages]= loadedRecords;
        }
    }

}

QVector<int> PaginationHandler::pageNumber() const
{
    return opaque->pageNumbers;
}

void PaginationHandler::populatePageNumberContainer(const int &direction, const int &totalRecords)
{
    if(opaque->pageIndexs.isEmpty())
        return;
    int loopCtr{ZERO};
    int pageNumber{ONE};
    int maximumPages{ONE};
    if(opaque->pageNumbers.isEmpty()){
        emit prePageNumberChanged();
        while (loopCtr < opaque->pageIndexs.size()) {
            opaque->pageNumbers.append(pageNumber);
            ++pageNumber;
            ++loopCtr;
        }
        emit postPageNumberChanged();
    }else{
        emit prePageNumberChanged();
        CursorDirection dir = static_cast<CursorDirection>(direction);

        switch (dir) {
        case CursorDirection::MoveForward:
            pageNumber = opaque->pageNumbers.last();
            opaque->pageNumbers.clear();
            while (loopCtr<opaque->pageIndexs.size()) {
                opaque->pageNumbers.append(pageNumber+ONE);
                ++pageNumber;
                ++loopCtr;
            }
            break;
        case CursorDirection::MoveBackward:
            pageNumber = opaque->pageNumbers.first();
            opaque->pageNumbers.clear();
            while (loopCtr < opaque->pageIndexs.size()) {
                opaque->pageNumbers.append(pageNumber-ONE);
                --pageNumber;
                ++loopCtr;
            }
            std::reverse(opaque->pageNumbers.begin(),opaque->pageNumbers.end());
            break;
        case CursorDirection::MoveFirst:
            opaque->pageNumbers.clear();
            while (loopCtr < opaque->pageIndexs.size()) {
                opaque->pageNumbers.append(pageNumber);
                ++pageNumber;
                ++loopCtr;
            }
            break;
        case CursorDirection::MoveLast:
            opaque->pageNumbers.clear();
            maximumPages = int(ceil(double(totalRecords)/double(opaque->resultsPerPage)));
            while (loopCtr < opaque->pageIndexs.size()) {
                opaque->pageNumbers.append(maximumPages);
                --maximumPages;
                ++loopCtr;
            }
            std::reverse(opaque->pageNumbers.begin(),opaque->pageNumbers.end());
            break;
        }
        emit postPageNumberChanged();

    }

}

void PaginationHandler::setNavigationControl(const int &totalRecords, const int &sourceContainerSize, const int &direction)
{
    CursorDirection dir = static_cast<CursorDirection>(direction);

    switch (dir) {
    case CursorDirection::MoveForward:
        opaque->processedRecords += sourceContainerSize;
        break;
    case CursorDirection::MoveBackward:
        opaque->processedRecords -= sourceContainerSize;
        break;
    case CursorDirection::MoveFirst:
        opaque->processedRecords = totalRecords;
        break;
    case CursorDirection::MoveLast:
        opaque->processedRecords = sourceContainerSize;
        break;
    }
}

bool PaginationHandler::canMoveAhead() const
{
    return opaque->canMoveBack;
}

void PaginationHandler::setCanMoveAhead(bool val)
{
    opaque->canMoveAhead = val;
    emit canMoveAheadChanged();
}

bool PaginationHandler::canMoveBack() const
{
    return opaque->canMoveBack;
}

void PaginationHandler::setCanMoveBack(bool val)
{
    opaque->canMoveBack = val;
    emit canMoveBackChanged();
}

PaginationModel.h:

class IPaginationHandler;

class MetraPaginationModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(IPaginationHandler *handler READ getHandler WRITE setHandler NOTIFY handlerChanged)
public:
    enum {PageNumber = Qt::UserRole};
    explicit MetraPaginationModel(QObject *parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QHash<int,QByteArray> roleNames()const override;

    IPaginationHandler *getHandler();
    void setHandler(IPaginationHandler *handlerInstance);

signals:
    void handlerChanged();
private:
    IPaginationHandler *handler;
};

PaginationModel.cpp

#include "paginationmodel.h"
#include "ipaginationhandler.h"
const int ZERO{0};

PaginationModel::PaginationModel(QObject *parent)
    : QAbstractListModel(parent),handler(nullptr)
{
}

int PaginationModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid() || !handler)
        return ZERO;
    return handler->pageNumber().size();
}

QVariant PaginationModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || !handler)
        return QVariant();
    switch (role) {
    case PageNumber:
        return QVariant(handler->pageNumber().at(index.row()));
    }
    return QVariant();
}

QHash<int, QByteArray> PaginationModel::roleNames() const
{
    QHash<int,QByteArray>roles;
    roles[PageNumber] = "page_number";
    return roles;
}

IPaginationHandler *PaginationModel::getHandler()
{
    return this->handler;
}

void MetraPaginationModel::setHandler(IPaginationHandler *handlerInstance)
{
    beginResetModel();
    if(handler)
        handler->disconnect(this);
    this->handler=handlerInstance;
    emit handlerChanged();

    if(handler){
        connect(handler,&IPaginationHandler::prePageNumberChanged,this,[=](){
            beginResetModel();
        });
        connect(handler,&IPaginationHandler::postPageNumberChanged,this,[=](){
            endResetModel();
        });
    }
    endResetModel();

}

Pagination.qml
This is just a snippet to get the idea how it would work on UI side

          ListView{
                id:paginationView
                implicitWidth: parent.width
                implicitHeight: parent.height * 0.15
                anchors.top: categoryList.bottom
                anchors.topMargin:Geometry.spacingVerticle * 3

                model: PaginationModel{
                    list:categoryHandler.pageList
                }
                delegate:Component{
                    PageIndicatorDelegate{
                        onPageIndexClicked:categoryHandler.getPageData(index)
                    }
                }
                spacing: Geometry.spacingHorizontal * 0.90
                orientation:ListView.Horizontal
                highlight: Component{IndicatorHighlightDelegate{}}
                highlightFollowsCurrentItem: true
                interactive:false
                header:Component{
                    ListViewNavigationControl{
                        onFirstIndicatorClicked: {
                            categoryHandler.moveBatch(Enums.MoveBackward)
                            paginationView.currentIndex = 0
                        }
                        onSecondIndicatorClicked:{
                            categoryHandler.moveBatch(Enums.MoveFirst)
                            paginationView.currentIndex = 0
                        }
                        secondIndicatorEnumeration: Qt.RightToLeft
                        firstControlVisible: categoryHandler.pageList.canMoveBack
                        secondControlVisible: categoryHandler.pageList.canMoveBack
                    }
                }
                footer:Component{
                    ListViewNavigationControl{
                        onFirstIndicatorClicked:{
                            categoryHandler.moveBatch(Enums.MoveForward)
                            console.log(MetraEnums.MoveForward)
                            paginationView.currentIndex = 0
                        }
                        onSecondIndicatorClicked: {
                            categoryHandler.moveBatch(Enums.MoveLast)
                            paginationView.currentIndex = 0
                        }
                        controlFirstIcon:  Icons.rightarrowThick
                        controlSecondIcon: Icons.rightarrowThick
                        firstControlVisible: categoryHandler.pageList.canMoveAhead
                        secondControlVisible:categoryHandler.pageList.canMoveAhead
                    }
                }
            }

        }

This is Delegate Item

// This is a snippet so 
Item {
    id: number
    implicitWidth: Geometry.screenHeight * 0.03
    implicitHeight: Geometry.screenHeight * 0.03

    signal pageIndexClicked(var index)

    MouseArea{

        anchors.fill: parent
        onClicked:{
            name.ListView.view.currentIndex = index
            pageIndexClicked(index+1)

        }
        hoverEnabled:name.ListView.view.currentIndex === index ? false:true
        onEntered:{pageNumber.color=ThemeLight.tertiarycolorLight}
        onExited: {pageNumber.color=ThemeLight.textcolor}
    }

    Label{
        id:pageNumber
        text:model.page_number ? model.page_number : index
        font.pixelSize:MetraGeometry.helpTextSize*1.2
        anchors.centerIn: parent
    }


}
\$\endgroup\$
2
  • \$\begingroup\$ (I see a lot of erratic capital letters - do you use a spelling checker?) There are more code blocks than fit on my screen at once: could you please give an overview before the first one? \$\endgroup\$
    – greybeard
    Commented Dec 21, 2019 at 13:09
  • \$\begingroup\$ I know its bit too much code but its my first post and i am trying to get the hang of it I can share code on git hub if that helps in this regard. \$\endgroup\$
    – k03rdt6
    Commented Dec 21, 2019 at 19:45

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.