CMemento11

CMemento11 is an application to show the use of Memento and Command design patterns, to implement a undo/redo listView in Qt/C++/QML.

The data model is represented by two classes: CUndoableInventory and CListable

CUndoableInventory class represents the listView and contains a vector of objects extending CListable interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @brief The CUndoableInventory class
*/
class CUndoableInventory
{
protected:
// list of CListable
vector< shared_ptr<CListable> > *inventory_;
vector< shared_ptr<CListable> > *duplicateInventory() const;
void swap(const CUndoableInventory& other);
public:
CUndoableInventory();
CUndoableInventory(const CUndoableInventory& other);
virtual ~CUndoableInventory();
CUndoableInventory & operator= (const CUndoableInventory& other);
// number of listables in the list
int getInventorySize() const;
// get the listable at index 'pos' (unchangeable)
const CListable* getListable(size_t pos) const;
// get the listable at index 'pos' (changeable)
CListable* getListablePtr(size_t pos);
// command list modifiers
void addListable(CListableFactory::listable_t listableType, const std::string& name);
bool insertListable(CListableFactory::listable_t listableType, const std::string& name, size_t pos);
bool removeListable(size_t pos);
bool clearAll();
// print to std::out
void printInventory() const;
// memento methods
CUndoableInventoryMemento* createMemento() const;
void reinstateMemento (CUndoableInventoryMemento* mem);
protected:
void addListablePtr(shared_ptr<CListable> listablePtr);
bool insertListablePtr(shared_ptr<CListable> listablePtr, size_t pos);
};

CListable is a pure virtual class representing an object contained in the listView.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Abstract class CListable
class CListable
{
public:
virtual std::string getTitle() const {
std::string title = getType();
title.append(" - ").append(getName());
return title;
}
virtual std::unique_ptr<CListable> clone() const = 0;
virtual std::string getName() const = 0;
virtual std::string getType() const = 0;
virtual std::string toString() const = 0;
};

This design decouples the CListable abstraction from its implementation (in this case: CBook, CCdRom, CDvd), so that the two can vary independently: it complies to the Bridge Design Pattern.

Undo/Redo: Command and Memento patterns.

Command pattern is an Object behavioral pattern that decouples sender and receiver by encapsulating a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and above all support undo-able operations.
It can also be thought as an object oriented equivalent of call-back method.

Without violating encapsulation the Memento pattern will capture and externalize an object’s internal state so that the object can be restored to this state later.
The Originator (the object to be saved) creates a snap-shot of itself as a Memento object, and passes that reference to the Caretaker object. The Caretaker object keeps the Memento until such a time as the Originator may want to revert to a previous state as recorded in the Memento object.

In my implementation the originator is represented by CUndoableInventory.
CUndoableInventory creates a snap-shot of itself as a CUndoableInventoryMemento object by invoking createMemento(), and can revert to a previous state by invoking reinstateMemento (CUndoableInventoryMemento* mem):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief The CUndoableInventoryMemento class
*/
class CUndoableInventoryMemento
{
private:
CUndoableInventory object_;
public:
CUndoableInventoryMemento(const CUndoableInventory& obj);
// want a snapshot of CUndoableInventory itself because of its many data members
CUndoableInventory snapshot() const;
};

The Caretaker is represented by CUndoableInventoryAction class (Command pattern, where the receiver is CUndoableInventory):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @brief The CUndoableInventoryAction class
*/
class CUndoableInventoryAction
{
private:
CUndoableInventory* receiver_;
// this std::function will contain the reference to receiver's method and all the parameters
function<void(const CUndoableInventory&)> receiverAction_;
static std::vector<CUndoableInventoryAction*> commandList_;
static std::vector<CUndoableInventoryMemento*> mementoList_;
static int numCommands_;
static int maxCommands_;
public:
CUndoableInventoryAction(CUndoableInventory *newReceiver,
function<void(const CUndoableInventory&)> newReceiverAction);
virtual ~CUndoableInventoryAction();
virtual void execute();
static bool isUndoable();
static bool isRedoable();
static void undo();
static void redo();
static void clearAll();
};
#endif // CUNDOABLEINVENTORY_H

An instance of this class represents an undoable operation performed on the CUndoableInventory object:
it keeps a pointer to an instance of the receiver (CUndoableInventory) and the method to be performed on the receiver as a function<void(const CUndoableInventory&)> (the std::function encapsulates both the method of class CUndoableInventory and the parameters to be passed to it).

Moreover, the CUndoableInventoryAction class is the Caretaker, because it keeps two static lists:

  • 1) the list of actions performed;
  • 2) the list of the inner states of the undoable inventory (CUndoableInventoryMemento object).

The undo/redo functions have been implemented as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CUndoableInventoryAction::undo()
{
if(numCommands_ == 0) {
std::cout << "There is nothing to undo at this point." << std::endl;
return;
}
commandList_[numCommands_ - 1]->receiver_->reinstateMemento(mementoList_[numCommands_ - 1]);
numCommands_--;
}
void CUndoableInventoryAction::redo()
{
if (numCommands_ > maxCommands_) {
std::cout << "There is nothing to redo at this point." << std::endl;
return;
}
CUndoableInventoryAction* commandRedo = commandList_[numCommands_];
commandRedo->receiverAction_(*(commandRedo->receiver_));
numCommands_++;
}

Undoable Operations

The class in charge of performing undoable operations on a CUndoableInventory object, is CUndoableInventoryManager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CUndoableInventoryManager
{
protected:
CUndoableInventory undoableInventory_;
public:
CUndoableInventoryManager();
virtual ~CUndoableInventoryManager();
protected:
void executeNonUndoableMethod(function<void(const CUndoableInventory&)> method);
void executeUndoableMethod(function<void(const CUndoableInventory&)> method);
public:
const CUndoableInventory& undoableInventory() const;
void setUndoableInventory(unique_ptr<CUndoableInventory> undoableInventory);
int getInventorySize() const;
const CListable* getInventoryListable(size_t pos) const;
CListable* getInventoryListablePtr(size_t pos);
bool isLastInventoryActionUndoable();
bool isLastInventoryActionRedoable();
void undoLastInventoryAction();
void redoLastInventoryAction();
void inventoryAddListable(CListableFactory::listable_t listableType, const std::string& name);
void inventoryInsertListable(CListableFactory::listable_t listableType, const std::string& name, size_t pos);
void inventoryRemoveListable(size_t pos);
void inventoryClear();
};

The magic happens inside executeUndoableMethod

1
2
3
4
5
void CUndoableInventoryManager::executeUndoableMethod(function<void(const CUndoableInventory&)> method)
{
CUndoableInventoryAction* myInventoryAction = new CUndoableInventoryAction(&undoableInventory_, method);
myInventoryAction->execute();
}

which receives a function<void(const CUndoableInventory&)> as parameter, which encapsulates the method of CUndoableInventory to be invoked and the parameters.
This method creates an action (Command pattern) by creating an object of type CUndoableInventoryAction, and then invokes its execute method myInventoryAction->execute();.

In this way, any function willing to be undoable, will just have to wrap the CUndoableInventory method and the parameters, inside a std::function

For example, let’s have a look at inventoryAddListable(CListableFactory::listable_t listableType, const std::string& name):

1
2
3
4
5
void CUndoableInventoryManager::inventoryAddListable(CListableFactory::listable_t listableType, const string &name)
{
std::function<void(const CUndoableInventory&)> inventoryMethod = std::bind(&CUndoableInventory::addListable, &undoableInventory_, listableType, name);
executeUndoableMethod(inventoryMethod);
}

C++ <—–> QML

In order to “bind” the QML undoable listView with the data model represented by CUndoableInventoryManager object, I set up a MVC architecture.

  • Model: CUndoableInventoryManager
  • View: QML undoable listView
  • Controller: an object of type CInventoryModel extending QAbstractListModel

The class CInventoryModel receives commands from Javascript functions used in QML undoable listView, and then it performs modifications on the data model and the listView itself.
For example, let’s have a look at the addListable method:

1
2
3
4
5
6
7
8
9
10
11
/**
* C++
*/
void CInventoryModel::addListable(int listableType, const QString& name)
{
CListableFactory::listable_t listableFactoryType = static_cast<CListableFactory::listable_t>(listableType);
beginInsertRows(QModelIndex(), myInventoryManager_->getInventorySize(), myInventoryManager_->getInventorySize());
myInventoryManager_->inventoryAddListable(listableFactoryType, name.toStdString());
endInsertRows();
emit modelModified();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* QML/Javascript
*/
function execAddListable() {
var listableType = listableTypeComboBox.currentIndex + 1;
var listableName = listableNameTextField.text;
inventoryModel.addListable(listableType, listableName);
inventoryList.positionViewAtEnd();
}
...
Button {
x: 10
y: 100
id: cmdButtonAdd
Layout.alignment: Qt.AlignLeft
anchors.topMargin: 10
text: qsTr("Add Item")
style: ButtonStyle {
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
opacity: enabled ? 1 : 0.3
border.color: cmdButtonAdd.down ? selectedColor : normalColor
border.width: 1
radius: 2
}
label: Text {
text: cmdButtonAdd.text
opacity: enabled ? 1.0 : 0.3
color: cmdButtonAdd.down ? selectedColor : normalColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
action: Action {
enabled: true
onTriggered: {
execAddListable();
console.log("Add detected!")
}
}
}
...

In this way I was able to implement all of the methods to add, insert, delete items on the list, and the undo/redo actions.

DanJsonp Class

JSONP is a method for sending JSON data without worrying about cross-domain issues.

It does not use the XMLHttpRequest object, but the script tag instead.

Requesting an external script from another domain does not have cross-domain policy problems.

DanJsonp is a pure Javascript ES6 class which implements JSONP protocol inside a web page.

It contains a static method which receives two parameters: an uri and a callbackName

1
static invokeJsonp(uri, callbackName = null) {

So this methods considers two modalities:

  1. The first one is when an external callback name is provided.
  2. The second one is when no callback name is provided.

The method creates a script element first

1
let s = document.createElement("script");

Then, if a callback name is provided, it executes:

1
2
3
4
5
6
7
if(callbackName) {
finalAddress += separator + "callback=" + callbackName;
s.src = finalAddress;
// append script node to document body
document.body.appendChild(s);
}

and does not return anything.

Else, if a callback name is provided, it executes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
else {
// create the promise to return
var willPromiseFinish = new Promise(
(resolve, reject) => {
// assign static method danJsonpCallback to class DanJsonp
Object.assign(DanJsonp, {
danJsonpCallback(myObj) {
// resolve promise after callback invocation
if(myObj) {
resolve(myObj);
}
else {
reject("No value returned by server");
}
}
});
}
);
finalAddress += separator + "callback=DanJsonp.danJsonpCallback";
s.src = finalAddress;
// append script node to document body
document.body.appendChild(s);
return willPromiseFinish;
}

The very interesting part here is the creation of a Promise object, which will be returned at method completion.

A Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

A function that is passed with the arguments resolve and reject to the Promise contructor is called executor. The executor function is executed immediately by the Promise implementation, passing resolve and reject functions (the executor is called before the Promise constructor even returns the created object). The resolve and reject functions, when called, resolve or reject the promise, respectively.

In the invokeJsonp method, the executor creates a static method named danJsonpCallback inside DanJsonp class, by using Object.assign function.

This static method (DanJsonp.danJsonpCallback) will be invoked at the end of API execution, and only then it will “resolve” the promise, so to make the JSON data available to client.

In order to do that, a query parameter finalAddress += separator + "callback=DanJsonp.danJsonpCallback"; is concatenated to the final uri, and the script element is appended to the document body.

Example

Let’s consider a simple PHP/Apache server, which provides a simple API, and is running on http://localhost/php-jsonp/getJsonpData.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$phpData = (object) [
'lang' => 'php',
'name' => 'phpJsonp',
'payload' => "Let's test these data coming from the PHP/Apache server"
];
$callbackName = filter_input(INPUT_GET, 'callback', FILTER_SANITIZE_SPECIAL_CHARS);
// JSONP modality
if(($callbackName !== false) && ($callbackName !== null)) {
echo("/**/ typeof ".$callbackName." === 'function' && ".$callbackName.'('.json_encode($phpData).');');
}
// JSON modality
else {
echo(json_encode($phpData));
}

To get the simple JSON object provided by the PHP/Apache server, by using DanJsonp class, the client should simply invoke:

1
2
3
4
5
6
7
DanJsonp.invokeJsonp("http://localhost/php-jsonp/getJsonpData.php")
.then((myObj) => {
console.log(myObj);
})
.catch((reason) => {
console.log('Promises catch: ' + reason);
});