- Enginio Manual
- Enginio C++ Classes and Examples
- Enginio C++ Examples - Todos
- Enginio 1.2.1 Reference Documentation

# Enginio C++ Examples - Todos

In this example, a list of objects is displayed in a QTreeView. Each item in the list is a `Todo` object, which can be marked `Done` or `Not done`. A `Todo` can be added, removed, or altered. We will be using the classes and concepts duscussed in Model/View Programming.

In this simple schema, each `ToDo` object will have two properties: a `string` named `title` and a `bool` named `completed`. These properties are added to the default properties e.g.: creation date, which always exist.

A `Todo` object will look like this in JSON:

    {
      "title": "Buy Milk",
      "completed": false
    }

A ToDo object can be created and appended to an EnginioModel using append(), as shown in MainWindow::appendItem(). Alternatively, if many ToDo objects are to be added to the model all at once, they can be inserted directly into the server database via the model's Enginio client. The client is obtained from the model with client(). Then the ToDo objects are inserted into the server database with the client's create() function. The model can then be reloaded from the server with reload().

But first we must create some infrastructure. Our `TodosModel` class is declared in todos-cpp/todosmodel.h. It inherits EnginioModel, which is a list model. We add two roles to the Enginio::Role enum, chosen for the new properties, one for the ToDo `title` and one for marking a ToDo as `completed`. We assign enum values to the new roles, equating `TitleRole` to Enginio::CustomPropertyRole, which is itself equated to Qt::UserRole + 10.

    class TodosModel : public EnginioModel
    {
        Q_OBJECT

    public:
        enum Role
        {
            TitleRole = Enginio::CustomPropertyRole,
            CompletedRole
        };

The new roles can be used for displaying and editing the values of the new properties. Views of the model also use roles from in Qt::ItemDataRole enum. The example's view is a QTreeView, as shown in the MainWindow class, which holds a pointer to the view, a pointer to the client, and a pointer to the model.

A new EnginioModel is empty. It automatically populates itself from the server, when its query and client properties have both been set. In the example, these properties are set in the constructor for the main window. The EnginioClient is created first. Then an instance of the `ToDosModel` is created, and its client is set using EnginioModel::setClient(). Then the query is created as a QJsonObject, and the model's query is set using EnginioModel::setQuery().

Once the ToDo data has been downloaded, the model resets itself, and sets up the internal data cache and roles names. EnginioModel guesses the role names based on heuristics. It may be wrong if not all objects received from the backend have exactly the same structure. For example, a property can be missing in certain objects. To protect against such cases, we overload roleNames(). Overriding roleNames() can also be used to match default Qt roles to the named ones.

    QHash<int, QByteArray> TodosModel::roleNames() const
    {
        QHash<int, QByteArray> roles = EnginioModel::roleNames();
        roles.insert(TitleRole, "title");
        roles.insert(Qt::DisplayRole, "title");
        roles.insert(Qt::EditRole, "title");
        roles.insert(CompletedRole, "completed");
        return roles;
    }

In this example, we map the Qt::DisplayRole and Qt::EditRole to the `title` property in the JSON. This way the right string is shown by default and editing works as expected.

Remember to always call the base class implementation to avoid situations in which the internal cache is not in sync.

By default EnginioModel operates on QJsonValue, and that is what the data() function returns inside the QVariant, but standard views, such as QListView, use predefined roles which do not map directly to our roles. That is why we need to write a mapping between them:

    QVariant TodosModel::data(const QModelIndex &index, int role) const
    {
        if (role == Qt::FontRole) {
            bool completed = EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();
            QFont font;
            font.setPointSize(20);
            font.setStrikeOut(completed);
            return font;
        }

        if (role == Qt::TextColorRole) {
            bool completed = EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();
            return completed ? QColor("#999") : QColor("#333");
        }

        if (role == CompletedRole)
            return EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();

        // fallback to base class
        return EnginioModel::data(index, role);
    }

As we have our model defined, we need to create an instance of EnginioClient:

    m_client = new EnginioClient(this);
    m_client->setBackendId(EnginioBackendId);

It is used by the model to connect to the Enginio backend. Next we need to construct and configure our model too. The configuration is based on two steps, assigning an EnginioClient instance and by creating a query.

    m_model = new TodosModel(this);
    m_model->setClient(m_client);

    QJsonObject query;
    query["objectType"] = QString::fromUtf8("objects.todos");
    m_model->setQuery(query);

The model has to be assigned to a view. In this example it is a QTreeView.

    m_view->setModel(m_model);

To make the application fully functional, a way to add and remove a Todo is needed. To do so, we need to connect the correct buttons to slots for adding a new item:

    void MainWindow::appendItem()
    {
        bool ok;
        QString text = QInputDialog::getText(this, tr("Create a new To Do")
                                             , tr("Title:"), QLineEdit::Normal
                                             , "", &ok);
        if (ok && !text.isEmpty()){
            QJsonObject object;
            object["title"] = text;
            object["completed"] = false; // By default a new To Do is not completed
            EnginioReply *reply = m_model->append(object);
            QObject::connect(reply, &EnginioReply::finished, reply, &EnginioReply::deleteLater);
        }
    }

and for removing it:

    void MainWindow::removeItem()
    {
        QModelIndex index = m_view->currentIndex();
        EnginioReply *reply = m_model->remove(index.row());
        QObject::connect(reply, &EnginioReply::finished, reply, &EnginioReply::deleteLater);
    }

Files:

- todos-cpp/mainwindow.cpp
- todos-cpp/mainwindow.h
- todos-cpp/todosmodel.cpp
- todos-cpp/todosmodel.h
- todos-cpp/main.cpp
- todos-cpp/todos-cpp.pro