Deleting clients
The final part of our CRUD operations is deleting an existing client. Let’s trigger this via a new button on EditClientView. We’ll begin by adding the slot that will be called when the button is pressed to CommandController:
void CommandController::onEditClientDeleteExecuted() { qDebug() << "You executed the Delete command!"; implementation->databaseController->deleteRow(implementation->selectedClient->key(), implementation->selectedClient->id()); implementation->selectedClient = nullptr; qDebug() << "Client deleted."; implementation->clientSearch->search(); }
This follows the same pattern as the other slots, except this time we also clear the selectedClient property as although the client instance still exists in application memory, it has been semantically deleted by the user. We also refresh the search so that the deleted client is removed from the search results. As this method stands, we’ve performed the correct database interaction but the user will be left on editClientView for a client that they have just asked to be deleted. What we want is for the user to be navigated back to the dashboard. In order to do this, we need to add NavigationController as an additional dependency to our CommandController class. Replicate what we did for the DatabaseController dependency so that we can inject it into the constructor. Remember to update MasterController and pass in the navigation controller instance.
With an instance of a database controller available, we can then send the user to the Dashboard View:
void CommandController::onEditClientDeleteExecuted() { ... implementation->navigationController->goDashboardView(); }
Now that we have the navigation controller available, we can also improve the experience when creating new clients. Rather than leaving the user on the new client view, let’s perform a search for the newly created client ID and navigate them to the results. They can then easily select the new client if they wish to view or edit:
void CommandController::onCreateClientSaveExecuted() { ...
implementation->clientSearch->searchText()-
>setValue(implementation->newClient->id()); implementation->clientSearch->search(); implementation->navigationController->goFindClientView(); }
With the deletion slot complete, we can now add a new delete command to the editClientContextCommands list in CommandController:
Command* editClientDeleteCommand = new Command( commandController, QChar( 0xf235 ), "Delete" ); QObject::connect( editClientDeleteCommand, &Command::executed, commandController, &CommandController::onEditClientDeleteExecuted ); editClientViewContextCommands.append( editClientDeleteCommand );
We are now presented with the option to delete an existing client:
If you delete a client, you will see that the row is removed from the database and the user is successfully navigated back to the dashboard. However, you will also see that the Application Output window is full of QML warnings along the lines of qrc:/views/EditClientView:62: TypeError: Cannot read property 'ui_billingAddress' of null.
The reason for this is that the edit view is bound to a client instance that is part of the search results. When we refresh the search, we delete the old search results, which means that the edit view is now bound to nullptr and can no longer access the data. This continues to happen even if you navigate to the dashboard before refreshing the search, because of the asynchronous nature of the signals/slots used to perform the navigation. One way of fixing these warnings is to add null checks on all the bindings in the view and bind to local temporary objects if the main object is null. Consider the following example:
StringEditorSingleLine { property StringDecorator temporaryObject stringDecorator: selectedClient ? selectedClient.ui_reference :
temporaryObject anchors { left: parent.left right: parent.right } }
So, if selectedClient is not null, bind to the ui_reference property of that, otherwise bind to temporaryObject. You can even add a level of indirection to the root Client property and substitute the entire client object:
property Client selectedClient property Client localTemporaryClient property Client clientToBindTo: selectedClient ? selectedClient : localTemporaryClient
Here, selectedClient will be set by the parent as normal; localTemporaryClient will not be set, so a default instance will be created locally. clientToBindTo will then pick the appropriate object to use and all the child controls can bind to that. As these bindings are dynamic, if selectedClient was deleted after loading the view (as in our case), then clientToBindTo will automatically switch over.
As this is just a demonstration project, it is safe for us to ignore the warnings, so we will take no action here to keep things simple.