Tutorial

Let's create an TreeFrog application.
Try creating a simple blog system which can be added / edited / deleted texts.

Creating a skeleton of application

First, try creating a skeleton of "blogapp" application, which includes the directory tree and setting files. Enter that from command-line. In Windows, enter on TreeFrog Command Prompt.

 $ tspawn new blogapp
   created  blogapp
   created  blogapp/controllers
   created  blogapp/models
   created  blogapp/models/queries
   created  blogapp/models/sqlobjects
   created  blogapp/views
   :

 
Creating a table

Create a table called blog in the database, which includes title and body fields. Next examples are MySQL and SQLite.
 
MySQL :
 Please check the character-set information set to the database, and setup MySQL tools path beforehand.

$ mysql -u root -p
Enter password:

mysql> CREATE DATABASE blogdb DEFAULT CHARACTER SET utf8;
Query OK, 1 row affected (0.01 sec)

mysql> USE blogdb;
Database changed

mysql> CREATE TABLE blog (id INTEGER AUTO_INCREMENT PRIMARY KEY, title VARCHAR(20), body VARCHAR(200), created_at TIMESTAMP DEFAULT 0, updated_at TIMESTAMP DEFAULT 0, lock_revision INTEGER) DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (0.02 sec)

mysql> DESC blog;
+---------------+--------------+------+-----+---------------------+------
| Field         | Type         | Null | Key | Default             | Extra
+---------------+--------------+------+-----+---------------------+------
| id            | int(11)      | NO   | PRI | NULL                | auto_     
| title         | varchar(20)  | YES  |     | NULL                |      
| body          | varchar(200) | YES  |     | NULL                |      
| created_at    | timestamp    | NO   |     | 0000-00-00 00:00:00 | 
| updated_at    | timestamp    | NO   |     | 0000-00-00 00:00:00 |      
| lock_revision | int(11)      | YES  |     | NULL                |      
+---------------+--------------+------+-----+---------------------+------
6 rows in set (0.01 sec)

mysql> quit
Bye

  
SQLite :
 Place the database file in the db directory.

 $ cd blogapp
 $ sqlite db/blogdb
 SQLite version 3.6.12
 sqlite>
CREATE TABLE blog (id INTEGER PRIMARY KEY, title VARCHAR(20), body VARCHAR(200), created_at TIMESTAMP, updated_at TIMESTAMP, lock_revision INTEGER);
 sqlite> .quit

 
So, the blog table with the fields of id, title, body, created_at, updated_at and lock_revision was created.
If with the field(s) of created_at or updated_at, TreeFrog automatically inserts creation date/time and update date/time, respectively. And, create the lock_revision field as integer type to lock the record optimistic.

Optimistic Lock
Mechanism to update a record safely without locking. When updating a record, the system checks whether others have changed it or not. If changed already, the system cancels the update process itself.

Setting database information

Set the database information to config/database.ini file. Open the file by editor program, and enter proper values into the items of dev section.
Example of MySQL :

[dev]
DriverType=QMYSQL
DatabaseName=blogdb
HostName=
Port=
UserName=root
Password=root
ConnectOptions=

 
Example of SQLite :

[dev]
DriverType=QSQLITE
DatabaseName=db/blogdb
HostName=
Port=
UserName=
Password=
ConnectOptions=

  
To check whether the settings is correct, try to access to the database and show available tables.

 $ cd blogapp 
 $ tspawn --show-tables
 DriverType:   QSQLITE
 DatabaseName: db\blogdb 
 HostName:
 Database opened successfully
 -----
 Available tables:
  blog

 If this, it's OK.

If no database driver, displays an error message like that:
 "QSqlDatabase: QMYSQL driver not loaded"

In this case, download database drivers in Download Page and install it referring the install file. 
 
To check database drivers installed, execute the following command.

 $ tspawn --show-drivers
 Available database drivers for Qt:
  QSQLITE
  QMYSQL3
  QMYSQL
  QODBC3
  QODBC

 

Setting the template system

In TreeFrog Framework, you can choose either the ERB or Otama as the template system. Set to the development.ini file.

TemplateSystem=ERB
  or
TemplateSystem=Otama

  

Auto generating source codes from the table

Run the generator command, tspawn, from command-line to generate source codes of its base. The following is generating a controller, a model and a view. The parameter is the table name, lower-case.

  $ cd blogapp
  $ tspawn scaffold blog
   created  controllers/blogcontroller.h
   created  controllers/blogcontroller.cpp
   created  controllers/controllers.pro
   created  models/sqlobjects/blogobject.h
   created  models/blog.h
   created  models/blog.cpp
   created  models/models.pro
   created  views/blog/index.html
   :

You can change to generate only a controller or only a model by the option of tspawn.
If the SQL driver doesn't exist in Qt SDK, the command would be error. In this case, build the SQL driver. See FAQ.
Or, you can download the package of SQL database drivers from this page, and install it.
 
SQLite driver is built in Qt SDK always, therefore it might be better to use, if you just try a little.

Building source codes

Only once run the following command to generate a Makefile.

 $ qmake -r "CONFIG+=debug"

 
Run make command in app directory to compile all of controllers, models, views and helpers.

 $ make     (On Windows, run 'mingw32-make' command instead)

 
When the build succeeds, four shared libraries will be generated in lib directory.
On Windows, shared libraries of debug mode will be generated as default. If you make libraries of release mode, execute a next command. See Qt doc for details of CONFIG variable.

 $ qmake -r "CONFIG+=release"

 

Starting up the application server

Start up the application server in the root directory of blogapp. The server will be proceed assuming the current directory as the application root.
Press Ctrl+c button to stop the server.

 treefrog -e dev

  
On Windows, use treefrogd command.

treefrogd -e dev

  
On Windows, use treefrogd command if the Web application is built in debug mode. Use treefrog command if built in release mode.
Mixing objects release and debug modes, it does not work properly.

Specify -d option to execute the server in background:

 treefrog -d -e dev

 
Stop command:

 $ treefrog -k stop

 
Forced termination command:

 $ treefrog -k abort

 
Restart command:

 $ treefrog -k restart

 
If the firewall is enabled, open the port (default:8800).

Access by web browser

Open the URL, http://localhost:8800/Blog/index, by web browser. The following screen will be displayed (like Rails..). First, no entry.

Two entries was registered. You can register, edit, delete.
No problem showing multi-bytes characters, Japanese.
It's easy!

TreeFrog has the URL routing system as of other frameworks, which is mechanism of calling the corresponding controller's method (action) from the requested URL.
The developed source codes can work on other platform, if compile again.

Published this sample web application. Access here!
Fast just like a desktop application. Use it freely.

Controller source code

Try checking out the source codes of the generated controller.
Header file, first. The actions equivalent to CRUD are defined. Don't mind magic codes, here.

class T_CONTROLLER_EXPORT BlogController : public ApplicationController
{
    Q_OBJECT
public:
    BlogController() { }
    BlogController(const BlogController &other);

public slots:
    void index();                     // Lists all
    void show(const QString &pk);     // Shows 1 entry
    void entry();                     // Displays registration screen
    void create();                    // New
registration
    void edit(const QString &pk);     // Displays edit screen
    void save(const QString &pk);     // Updates (save)
    void remove(const QString &pk);   // Deletes 1 entry
};

T_DECLARE_CONTROLLER(BlogController, blogcontroller)     // magic code

 
Next, source file.
100 lines, so go together.

#include "blogcontroller.h"
#include "blog.h"

BlogController::BlogController(const BlogController &)
    : ApplicationController()
{ }

void BlogController::index()
{
    QList<Blog> blogList = Blog::getAll();     // Gets all Blog objects
    texport(blogList);             // Pass the value to view
    render();                      // Render the view (template)
}

void BlogController::show(const QString &pk)
{
    Blog blog = Blog::get(pk.toInt()) ;  // Gets Blog model by primary key
    texport(blog);                         
    render();
}

void BlogController::entry()
{
    render();
}

void BlogController::create()
{
    if (httpRequest().method() != Tf::Post) {   // Checks the request is POST method
        return;
    }
   
    Blog blog = Blog::create( httpRequest().parameters("blog") );  // Creates the object from posted information
    if (!blog.isNull()) {
        QString notice = "Created successfully.";
        tflash(notice);                           // Sets flash message
        redirect(urla("show", blog.id()));        // redirect to show action
    } else {
        QString error = "Failed to create.";     // Failed creating the object
        texport(error);                        
        render("entry");
    }
}

void BlogController::edit(const QString &pk)
{
    Blog blog = Blog::get(pk.toInt());  // Gets Blog object to edit
    if (!blog.isNull()) {
        texport(blog);   

       session().insert("blog_lockRevision", blog.lockRevision());  // Saves the lock-revision into session
        render();
    } else {
        redirect(urla("entry"));
    }
}

void BlogController::save(const QString &pk)
{
    if (httpRequest().method() != Tf::Post) {
        return;
    }

    QString error;
    int rev = session().value("blog_lockRevision").toInt();  // Gets the lock-revision
    Blog blog = Blog::get(pk.toInt(), rev);  // Gest the Blog object to update
    if (blog.isNull()) {
        error = "Original data not found. It may have been updated/removed by another transaction.";
        tflash(error);
        redirect(urla("edit", pk));
        return;
    }
   
    blog.setProperties( httpRequest().parameters("blog") );  // Sets
the requested values
    if (blog.save()) {                   // Saves the object
        QString notice = "Updated successfully.";
        tflash(notice);
    } else {
        error = "Failed to update.";
        tflash(error);
    }
    redirect(urla("show", pk));      // Redirects to show action
}

void BlogController::remove(const QString &pk)
{
    if (httpRequest().method() != Tf::Post) {
        return;
    }
   
    Blog blog = Blog::get(pk.toInt());   // Gets the Blog object

    blog.remove();                       // Removes it
    redirect(urla("index"));
}

// Don't remove below this line
T_REGISTER_CONTROLLER(blogcontroller)       // magic code

- The lock-revision exists for Optimistic Lock.
 
As you see, use the texport method to pass values to the view. The parameter of this method is a object of QVariant class, which can be any object such as int, QString, QList, QHash and etc. See details of Qt document.

Mechanism of View

Two template systems, ERB and original system named Otama, are adopted in TreeFrog framework.

Generator command generates view files of ERB as default. In ERB, write C++ code into the part of "<% .. %>". If render() method is called in index action, the system returns the contents of index.erb as a response.

<!DOCTYPE HTML>
<%#include "blog.h" %>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title><%= controller()->name() + ": " + controller()->activeAction() %></title>
</head>
<body>
<h1>Listing Blog</h1>
<%== linkTo("New entry", urla("entry")) %><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
  <tr>
    <th>ID</th>
    <th>Title</th>
    <th>Body</th>
  </tr>
<% tfetch(QList<Blog>, blogList); %>
<% for (QListIterator<Blog> it(blogList); it.hasNext(); ) {
     const Blog &i = it.next(); %>
  <tr>
    <td><%= i.id() %></td>
    <td><%= i.title() %></td>
    <td><%= i.body() %></td>
    <td>
      <%== linkTo("Show", urla("show", i.id())) %>
      <%== linkTo("Edit", urla("edit", i.id())) %>
      <%== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')") %>
    </td>
  </tr>
<% } %>
</table>

 

The next is Otama template system.

Otama template system is entirely separate templates and presentation logic. Templates are written in perfect HTML. Mark the element (start-tag) of the part that you want to re-write dynamically, and write down logic (C++ code) associated with the mark to the presentation logic file. Therefore, it is physically possible to divide the labor between designers and programmers.

Look into index.html file generated by the command.
If render() method is called in index action, the system returns the contents of index.html as a response. As you see, this file conforms to HTML version 5. If open by the today's web browser, the design doesn't break.

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title data-tf="@head_title"></title>
</head>
<body>
<h1>Listing Blog</h1>
<a href="#" data-tf="@link_to_entry">New entry</a><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
  <tr>
    <th>ID</th>
    <th>Title</th>
    <th>Body</th>
    <th></th>
  </tr>
  <tr data-tf="@for">               ← mark as @for
    <td data-tf="@id"></td>
    <td data-tf="@title"></td>
    <td data-tf="@body"></td>
    <td>
      <a data-tf="@linkToShow">Show</a>
      <a data-tf="@linkToEdit">Edit</a>
      <a data-tf="@linkToRemove">Remove</a>
    </td>
  </tr>
</table>
</body>
</html>

Then, look into index.olg corresponding to the presentation logic. This file associates above marks with C++ logic, but #include is reserved word. A part from the mark to the empty line is one set. Logic parts are codes of C++ language almost. The association sets include operators such as ~= or :==, by which the behavior changes.It uses Custem Data Attibute of HTML5 named data-tf to mark elements each. The string starting with "@" is the mark of Otama.

#include "blog.h"  /*  just C++ code, include the blog.h */

@head_title ~= controller()->controllerName() + ": " + controller()->actionName()

@for :
tfetch(QList<Blog>, blogList);  /* declaration to use data passed from controller */
for (QListIterator<Blog> it(blogList); it.hasNext(); ) {
    const Blog &i = it.next();        /* reference to Blog object */
    %%      /* pattern in loop sentence. repeats the element and child elements */
}

@id ~= i.id()   /* assigns the results of i.id()  to the content of the element marked with @id */

@title ~= i.title()

@body ~= i.body()

@linkToShow :== linkTo("Show", urla("show", i.id()))  /* replaces the element and child elements with the results of linkTo() */

@linkToEdit :== linkTo("Edit", urla("edit", i.id()))

@linkToRemove :== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')")

@linkToEntry :== linkTo("New entry", urla("entry"))

 
Briefly describes Otama operator.
~ (tilde) operator sets the result of the right side to the content of the marked element.
= (equals sign)  operator converts to HTML entities, that is HTML-escape. Therefore, ~= operator converts the result of the right side to HTML entities and sets to the content of the marked element. If you don't want HTML-escape, use ~== operator instead.
Moreover, : (colon) operator replaces the element and child elements with the result of the right side. Therefore, :== operator replaces it with the raw value, not converted.

Passing data from Controller to View
If the data (object) texport-ed  in controllers used in the view, it must be declared by tfetch method (Actually, macro). The arguments are the variable type and the variable name. Then, you can use it as a normal variable, that is the exactly value texport-ed in the controller. See above presentation logic file.

Example :

Controller side :
 int hoge;
 hoge = ...
 texport(hoge);

 
View side :
 tfetch(int, hoge);

 
Otama system generates C++ codes from these template files and presentation logic. Internally, tmake command is processes it. The codes are compiled  to a shared library of view. Therefore, it can work very fast.

HTML Glossary:
An Element consists of a start-tag, a content and a end-tag. For example, in case of "<p>Hello</p>" element, <p> is the start-tag, Hello is the content, </p> is the end-tag.

Model and ORM

TreeFrog framework has the concept Model includes ORM object, that is has-a relationship. Most other frameworks have the concept that Model is equals to ORM object as default, which is different from that of TreeFrog. That said, its ability is same. Here, understand that Model is small wrapper of ORM object.

O/R mapping module (O/R mapper) named SqlObject is built in TreeFrog as default. Check the generated SqlObject file, blogobject.h
Because C++ is a statically typed language, type declarations are required. Fields in the table are declared as public member variables. It's almost a struct. Only in this way, you can use methods equivalent to CURD, create, update, findFirst and remove. These methods are defined  in TSqlObject class and  TSqlORMapper class.

class T_MODEL_EXPORT BlogObject : public TSqlObject, public QSharedData
{
public:
    int id;
    QString title;
    QString body;
    QDateTime created_at;
    QDateTime updated_at;
    int lock_revision;
 
    enum PropertyIndex {
        Id = 0,
        Title,
        Body,
        CreatedAt,
        UpdatedAt,
        LockRevision,
    };

    int primaryKeyIndex() const { return Id; }

    /*** Don't modify below this line ***/
    Q_OBJECT
                            // magic macros, from here
    Q_PROPERTY(int id READ getid WRITE setid)
    T_DEFINE_PROPERTY(int, id)
    Q_PROPERTY(QString title READ gettitle WRITE settitle)
    T_DEFINE_PROPERTY(QString, title)
    Q_PROPERTY(QString body READ getbody WRITE setbody)
    T_DEFINE_PROPERTY(QString, body)
    Q_PROPERTY(QDateTime created_at READ getcreated_at WRITE setcreated_at)
    T_DEFINE_PROPERTY(QDateTime, created_at)
    Q_PROPERTY(QDateTime updated_at READ getupdated_at WRITE setupdated_at)
    T_DEFINE_PROPERTY(QDateTime, updated_at)
    Q_PROPERTY(int lock_revision READ getlock_revision WRITE setlock_revision)
    T_DEFINE_PROPERTY(int, lock_revision)
};

Although O/R mapper includes methods of inquiry or update by primary key, SqlObject can have only one primary key as a return value of primaryKeyIndex method. Therefore, in the table with multiple primary keys, please modify this value if necessary. By using TCriteria class, it is possible to inquiry by more complex conditions. See the section.

Then, let's look into the Model header file.
Setter/getter of each property and static methods of object creation and getter are defined. Because methods of save and remove are defined in TAbstractModel class that is the parent, Blog class has methods equivalent to CURD, create, get, save and remove.

class T_MODEL_EXPORT Blog : public TAbstractModel
{
public:
    Blog();
    Blog(const Blog &other);
    Blog(const BlogObject &object);  // constructor made from ORM object
    ~Blog();
 
    int id() const;    // Setters and getters, from here
    void setId(int id);
    QString title() const;
    void setTitle(const QString &title);
    QString body() const;
    void setBody(const QString &body);
    QDateTime createdAt() const;
    QDateTime updatedAt() const;
    int lockRevision() const;
 
    static Blog create(int id, const QString &title, const QString &body);  // object creation
    static Blog create(const QHash<QString, QString> &values);   
// object creation from Hash
    static Blog get(int id);        // Gets object specified by ID

    static Blog get(int id, int lockRevision);
    static QList<Blog> getAll();      // Gets all objects

private:
    QSharedDataPointer<BlogObject> d;   // pointer to ORM object

    TSqlObject *data();
    const TSqlObject *data() const;
};

Q_DECLARE_METATYPE(Blog)        // magic code, from here
Q_DECLARE_METATYPE(QList<Blog>)

 
Although codes automatically generated by the generator are not so many, basic functions are working. Of course, the generated codes are not perfect, and since the actual application process is more complex, it might not be enough. Modifying would be necessary. Understand that generator command is the tool to save effort just a bit. In the background of codes described above, various functions are working such as cookie tampering checking, optimistic locking, SQL injection protection, CSRF protection with authentication token, etc.

If interested, take a look to the source codes.

Create Blog App Demo

  

Comments are closed.