diff --git a/docs/_includes/header.html b/docs/_includes/header.html index cd937bdd7..96a465dc5 100644 --- a/docs/_includes/header.html +++ b/docs/_includes/header.html @@ -4,6 +4,8 @@ {% assign url = 'ja' %} {% elsif page.url contains '/ch/' %} {% assign url = 'ch' %} + {% elsif page.url contains '/de/' %} + {% assign url = 'de' %} {% else %} {% assign url = '' %} {% endif %} diff --git a/docs/_includes/nav.html b/docs/_includes/nav.html index 74a61ae2b..0dce9e0d3 100644 --- a/docs/_includes/nav.html +++ b/docs/_includes/nav.html @@ -16,6 +16,14 @@
  • FAQ
  • Blog
  • Development
  • + {% elsif page.url contains '/de/' %} +
  • Startseite
  • +
  • Herunterladen
  • +
  • Dokumente
  • +
  • Schnittstellenreferenz (API)
  • +
  • FAQ
  • +
  • Blog
  • +
  • Entwicklung
  • {% else %}
  • Home
  • Download
  • diff --git a/docs/_includes/navigation.html b/docs/_includes/navigation.html index cd178617c..a41f5d585 100644 --- a/docs/_includes/navigation.html +++ b/docs/_includes/navigation.html @@ -8,6 +8,10 @@

    Search<
    +{% elsif page.url contains '/de/' %} + + + {% else %} @@ -22,24 +26,39 @@

    Language
  • English
  • {% assign ja_url = url | replace: '/en/', '/ja/' %} {% assign ch_url = url | replace: '/en/', '/ch/' %} + {% assign ch_url = url | replace: '/en/', '/de/' %}
  • 日本語 (Japanese)
  • 中文(简体) (Chinese-Simplified)
  • +
  • Deutsch
  • {% elsif page.url contains '/ja/' %}
  • 日本語 (Japanese)
  • {% assign en_url = url | replace: '/ja/', '/en/' %} {% assign ch_url = url | replace: '/ja/', '/ch/' %} + {% assign ch_url = url | replace: '/ja/', '/de/' %}
  • English
  • 中文(简体) (Chinese-Simplified)
  • +
  • Deutsch
  • {% elsif page.url contains '/ch/' %}
  • 中文(简体) (Chinese-Simplified)
  • {% assign en_url = url | replace: '/ch/', '/en/' %} {% assign ja_url = url | replace: '/ch/', '/ja/' %} + {% assign ch_url = url | replace: '/ch/', '/de/' %} +
  • English
  • +
  • 日本語 (Japanese)
  • +
  • Deutsch
  • +{% elsif page.url contains '/de/' %} +
  • Deutsch
  • + {% assign en_url = url | replace: '/de/', '/en/' %} + {% assign ja_url = url | replace: '/de/', '/ja/' %} + {% assign ja_url = url | replace: '/de/', '/ch/' %}
  • English
  • 日本語 (Japanese)
  • +
  • 中文(简体) (Chinese-Simplified)
  • {% else %}
  • English
  • 日本語 (Japanese)
  • 中文(简体) (Chinese-Simplified)
  • +
  • Deutsch
  • {% endif %} @@ -51,6 +70,8 @@

    Users Gui {% include generateDynamicMenu.html root="/ja/user-guide/" %} {% elsif page.url contains '/ch/' %} {% include generateDynamicMenu.html root="/ch/user-guide/" %} +{% elsif page.url contains '/de/' %} + {% include generateDynamicMenu.html root="/de/user-guide/" %} {% else %} {% include generateDynamicMenu.html root="/en/user-guide/" %} {% endif %} diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 8127d566c..d8fe740ab 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -3,6 +3,8 @@ {% elsif page.url contains '/ch/' %} +{% elsif page.url contains '/de/' %} + {% else %} {% endif %} diff --git a/docs/_layouts/docindex.html b/docs/_layouts/docindex.html index 8f06099ab..97425149f 100644 --- a/docs/_layouts/docindex.html +++ b/docs/_layouts/docindex.html @@ -32,6 +32,8 @@

    {{page.title}}

    {% include generateDynamicMenu.html root="/ja/user-guide/" %} {% elsif page.url contains '/ch/' %} {% include generateDynamicMenu.html root="/ch/user-guide/" %} + {% elsif page.url contains '/de/' %} + {% include generateDynamicMenu.html root="/de/user-guide/" %} {% else %} {% include generateDynamicMenu.html root="/en/user-guide/" %} {% endif %} diff --git a/docs/de/api-reference/index.md b/docs/de/api-reference/index.md new file mode 100644 index 000000000..17adc23aa --- /dev/null +++ b/docs/de/api-reference/index.md @@ -0,0 +1,10 @@ +--- +title: API Reference +page_id: "api-reference.00" +--- + +## API Reference + +Published documents generated by Doxygen. + +[TreeFrog Framework API Reference >>](http://api-reference.treefrogframework.org/annotated.html){:target="_blank"} diff --git a/docs/de/blog/index.md b/docs/de/blog/index.md new file mode 100644 index 000000000..7aafd33f0 --- /dev/null +++ b/docs/de/blog/index.md @@ -0,0 +1,37 @@ +--- +title: Blog +page_id: "blog.00" +--- + +## Web Framework Benchmarks - Runde 15 + +{:target="_blank"} + + +## Web Framework Benchmarks - Runde 13 + +{:target="_blank"} + +Nach dem Hinzufügen der MongoDB-Anbindung wurde diese Kombination auf dem Prüfstand getestet. Ergänzungen zu den Benchmark-Ergebnissen: + + - treefrog: TreeFrog (MPM:thread) + MySQL + - treefrog-postgres: TreeFrog (MPM:thread) + PostgreSQL + - treefrog-mongo: TreeFrog (MPM:thread) + MongoDB + - thread-hybrid: treefrog (MPM:hybrid) + MySQL + + +## Minimalistischer C++-Interpreter + +Ich habe einen minimalistischen C++11-Interpreter erstellt, der nützlich ist, wenn Sie Ihren C++11-Code überprüfen möchten. + + + + +## Chat-App auf WebSocket-Basis + +Chat-Beispielapplikation auf WebSocket-Basis. Probier es aus! +Ich werde eine entsprechende Anleitung zum Erstellen dieser Applikation hochladen. + + + +Der Server wurde mit dem TreeFrog-Framework erstellt. \ No newline at end of file diff --git a/docs/de/development/index.md b/docs/de/development/index.md new file mode 100644 index 000000000..1733bc654 --- /dev/null +++ b/docs/de/development/index.md @@ -0,0 +1,17 @@ +--- +title: development +page_id: "development.00" +--- + +## Entwicklung + +Das TreeFrog-Framework - Repository steht auf GitHub bereit: + + [ https://github.com/treefrogframework](https://github.com/treefrogframework){:target="_blank"} + +Falls Sie einen Patch erstellt haben, senden Sie bitte einen Pull-Request mit Ihren Kommentaren zum Repository. + + +### Forum + +Diskussionsgruppe für das TreeFrog-Framework: [https://groups.google.com/forum/#!forum/treefrogframework](https://groups.google.com/forum/#!forum/treefrogframework){:target="_blank"} diff --git a/docs/de/download/index.md b/docs/de/download/index.md new file mode 100644 index 000000000..68d4665e9 --- /dev/null +++ b/docs/de/download/index.md @@ -0,0 +1,69 @@ +--- +title: download +page_id: "download.00" +--- + +## Download + +### Installationsassistent für Windows + +Wir stellen einen Installationsassistenten für Qt6 zur Verfügung. Nach der Einrichtung verfügen Sie im Handumdrehen über eine TreeFrog-Framework-Entwicklungsumgebung. Es ist nicht erforderlich, den Quellcode zu bauen und zu installieren, daher ist dieser Installationsassistent für diejenigen geeignet, die schnell eine Umgebung erstellen möchten. Vor der Ausführung des Installationsassistenten muss Qt6 für Windows installiert werden. + +
    + +| Version | Datei | +|-------------------------------------|--------------------------------------| +| 2.7.1 für Visual Studio 2019 (Qt 6.4 or 6.3)| [ treefrog-2.7.1-msvc_64-setup.exe](https://github.com/treefrogframework/treefrog-framework/releases/download/v2.7.1/treefrog-2.7.1-msvc_64-setup.exe) | + +
    + +## Quellcode + +Das Quellcode-Paket des TreeFrog-Frameworks ist ebenfalls verfügbar. + +
    + +| Source | Datei | +|----------------|----------------------------------| +| Version 2.7.1 | [ treefrog-framework-2.7.1.tar.gz](https://github.com/treefrogframework/treefrog-framework/archive/v2.7.1.tar.gz) | + +
    + +[Alle vorhergehenden Versionen ](https://github.com/treefrogframework/treefrog-framework/releases) + +Der Letztstand des Quellcodes befindet sich auf [GitHub](https://github.com/treefrogframework/). + +## Homebrew + +Siehe die Homebrew-[Seite](https://formulae.brew.sh/formula/treefrog) von TreeFrog. Dieser kann mittels Homebrew auf macOS-basierten Geräten wie folgt installiert werden: + +``` + $ brew install treefrog +``` + +Wenn der SQL-Treiber für Qt korrekt installiert wurde, dann werden die folgenden Ausgaben angezeigt: + +``` + $ tspawn --show-drivers + Available database drivers for Qt: + QSQLITE + QMARIADB + QMYSQL + QPSQL +``` + +Wenn `QMARIADB` oder `QPSQL` nicht angezeigt werden sollten, dann sind die jeweiligen Treiber nicht am dafür vorgesehenen Speicherort abgelegt. Führen Sie folgende Befehle aus um den entsprechenden Treiber-Pfad zu überprüfen und kopieren Sie den Treiber manuell in den dafür vorgesehenen Ordner: + +``` +Example: + $ cd $(tspawn --show-driver-path) + $ pwd + (your_brew_path)/Cellar/qt/6.2.3_1/share/qt/plugins/sqldrivers +``` + +Nach dem Kopieren des Treibers sollten die folgenden Ausgaben mittels des `ls`-Befehls angezeigt werden: + +``` +$ ls $(tspawn --show-driver-path) +libqsqlite.dylib libqsqlmysql.dylib libqsqlpsql.dylib +``` diff --git a/docs/de/faq/index.md b/docs/de/faq/index.md new file mode 100644 index 000000000..f7ad20d2f --- /dev/null +++ b/docs/de/faq/index.md @@ -0,0 +1,15 @@ +--- +title: faq +page_id: "faq.00" +--- + +## FAQ + +Frage: Gibt es ein Docker-Image für das TreeFrog-Framework? + +Antwort: Ja, die Images dafür wurden auf Docker Hub veröffentlicht: [https://hub.docker.com/r/treefrogframework/](https://hub.docker.com/r/treefrogframework/){:target="_blank"} + + +Frage: Ich erhalte folgende Fehlermeldung: `Incorrect string value: '\xE3\x81\x82' for column 'xxxx' at row 1 QMYSQL: Unable to execute query` bei MySQL. Weswegen erhalte ich diese Fehlermeldung? + +Antwort: Es handelt sich dabei um ein Zeichensatzproblem, bitte überprüfen Sie den entsprechenden Zeichensatz Ihrer Tabelle. Zur Vermeidung solcher Fehler sollten Sie den Default-Zeichensatz beim Erstellen der Tabelle mittels `DEFAULT CHARSET=UTF8`` gleich mitangeben. \ No newline at end of file diff --git a/docs/de/index.md b/docs/de/index.md new file mode 100644 index 000000000..3f2a83af4 --- /dev/null +++ b/docs/de/index.md @@ -0,0 +1,114 @@ +--- +title: Startseite +page_id: "home.00" +--- + +## Klein, aber leistungsstark und effizient + +Das TreeFrog Framework ist ein Hochperformanz- und Full-Stack-C++-Framework für die Entwicklung von Webanwendungen, welches die HTTP- und WebSocket-Protokolle unterstützt. + +Webanwendungen können schneller ausgeführt werden als Skriptsprachen, da das serverseitige Framework in C++/Qt geschrieben wurde. Bei der Anwendungsentwicklung stellt es ein objektrelationales Mapping-System und ein Templating-System auf einer MVC-Architektur bereit und zielt darauf ab höchste Produktivität, durch Konvention über Konfiguration, zu erreichen. + + +## Kennzeichnungsmerkmale + + 1. Hochperformanz - hoch-optimierte Anwendungsserver-Engine auf C++-Basis. + 2. Objektrelationales Mapping - kapselt komplexe und umständliche Datenbankzugriffe + 3. Templating-System - Adapation einer ERB-ähnlichen Template-Engine + 4. Unterstützte Datenbanken - MySQL, MariaDB, PostgreSQL, ODBC, MongoDB, Redis, Memcached, etc. + 5. WebSocket-Unterstützung - stellt vollduplex Kommunikationskanäle bereit + 6. Generator - generiert Codegerüst, Makefile und vue.js - Vorlagen + 7. Unterstützung für unterschiedliches Rückgabetypen - JSON, XML und CBOR + 8. Multiplatform-Fähigkeit - derselbe Quellcode kann somit auf Windows, macOS und Linux funktionieren + 9. OSS - quelloffene Software, mittels New-BSD-Lizenz + + +## Weswegen das TreeFrog-Framework? + +Es wird gesagt, dass es bei der Entwicklung von Webanwendungen einen Kompromiss zwischen Entwicklungseffizienz und Betriebsgeschwindigkeit gibt. Stimmt das wirklich? + +So etwas gibt es nicht. Durch die Bereitstellung praktischer Entwicklungswerkzeuge und hervorragender Bibliotheken aus dem Framework, sowie durch die Angabe von Spezifikationen zur Minimierung der Konfigurationsdatei, ist eine effiziente Entwicklung möglich. + +In den letzten Jahren ist das Cloud-Computing aufgekommen, die Bedeutung von Webanwendungen nimmt von Jahr zu Jahr zu. Obwohl bekannt ist, dass die Ausführungsgeschwindigkeit von Skriptsprachen mit zunehmender Codemenge abnimmt, kann die Sprache C++ mit der höchsten Geschwindigkeit und einem geringen Speicherbedarf arbeiten ohne dabei die Ausführungsgeschwindigkeit zu vermindern, selbst wenn die Codemenge zunimmt. + +Mehrere Anwendungsserver, die Skriptsprache ausführen, können ohne Leistungseinbußen zu einem zusammengefasst werden. Probiere das TreeFrog-Framework aus, das hohe Produktivität und Hochgeschwindigkeitsbetrieb kombiniert! + + +## Neuigkeiten + +### 2023-03-26 TreeFrog-Framework Version 2.7.1 (stable-release) Neu! + + - Behebung eines Fehlers beim Öffnen des Shared-Memory-KVS. + - Behebung des Fehlers, dass "NotFound" zurückgegeben wurde, wenn eine Action nicht aufgerufen werden konnte. + + [ Lade diese Version herunter](/de/download/) + +### 2023-02-25 TreeFrog-Framework Version 2.7.0 (stable-release) + + - Behebung der Möglichkeit von Thread-Konflikten beim Empfang von Paketen. + - Änderung des Hash-Algorithmus auf SHA3-HMAC. + - Hinzufügen des memcached als Session-Store. + - Aktualisierung des malloc-Algorithmuses der TSharedMemoryAllocator-Klasse. + - Aktualisierung des System-Loggers. + - Geschwindigkeitsoptimierung des Poolingsverfahrens für Datenbankverbindungen. + +### 2023-01-21 TreeFrog-Framework Version 2.6.1 (stable-release) + + - Behebung eines Access-Log-Ausgabe-Bugs. + - Hinzufügen einer Link-Option für die LZ4-Shared-Library auf Linux oder macOS. + +### 2023-01-02 TreeFrog-Framework Version 2.6.0 (stable-release) + + - Implementierung eines in-memory KVS für das Cache-System. + - Hinzufügen einer Link-Option für die Glog-Shared-Library. + - Behebung eines Makro-Fehlers für das Kommandozeileninterface. + - Aktualisierung von LZ4 auf v1.9.4. + +### 2022-11-01 TreeFrog-Framework Version 2.5.0 (stable-release) + + - Implementierung der flushResponse()-Funktion um den Prozess nach dem Senden einer Response weiterlaufen zu lassen. + - Aktualisierung von glog auf v0.6.0. + - Geschwindigkeitsverbesserungen für den Redis-Client. + - Implementierung eines memcached-Client. [experimentell] + - Implementierung eines Cache-Store für memcached, die TCacheMemcachedStore-Klasse. + +### 2022-08-13 TreeFrog-Framework Version 2.4.0 (stable-release) + + - Implementierung eines Memory-Store für den Cache. + - Aktualisierung des MongoDB C-Treibers auf v1.21.2. + +### 2022-05-28 TreeFrog-Framework Version 2.3.1 (stable-release) + + - Behebung eines Kompilierungsfehlers beim Einsatz von Qt 6.3. + + [ Alle Änderungsprotokolle](https://github.com/treefrogframework/treefrog-framework/blob/master/CHANGELOG.md) + + +## Unterstütze die Entwicklung mit deiner Spende + +Das TreeFrog-Framework ist ein New-BSD-lizenziertes Open-Source-Projekt und kann völlig kostenlos verwendet werden. Allerdings ist der Aufwand, der für die Wartung und Entwicklung neuer Funktionen für das Projekt erforderlich ist, ohne angemessene finanzielle Unterstützung nicht tragbar. Wir nehmen Spenden von Sponsoren und Einzelspendern über die folgenden Methoden entgegen: + + - Spende mittels [PayPal ](https://www.paypal.me/aoyamakazuharu) + - werde ein [Sponsor in GitHub](https://github.com/sponsors/treefrogframework) + - Bitcoin-Spendenadresse: [12C69oSYQLJmA4yB5QynEAoppJpUSDdcZZ]({{ site.baseurl }}/assets/images/btc_address.png "Bitcoin-Spendenadresse") + +Ich würde mich freuen, wenn Sie sich überlegen würden mit ihrer Spende das TreeFrog-Framework-Projekt zu unterstützen. Danke schön! + + +## Wir suchen ... + + - Entwickler, Tester, Übersetzer. + +Da diese Website mit [GitHub Pages](https://pages.github.com/) erstellt wurde, können Übersetzungen auch per Pull-Request übermittelt werden. +Besuchen Sie [GitHub](https://github.com/treefrogframework/treefrog-framework){:target="_blank"}. Sie sind herzlich willkommen! + + +## Weiterführende Informationen + +[TreeFrog-Forum ](https://groups.google.com/forum/#!forum/treefrogframework){:target="_blank"} + +Twitter (Japanisch) [@TreeFrog_ja ](https://twitter.com/TreeFrog_ja){:target="_blank"} + +[Docker Images ](https://hub.docker.com/r/treefrogframework/treefrog/){:target="_blank"} + +[Benchmarks ](https://www.techempower.com/benchmarks/){:target="_blank"} diff --git a/docs/de/search.html b/docs/de/search.html new file mode 100644 index 000000000..8949fc98e --- /dev/null +++ b/docs/de/search.html @@ -0,0 +1,20 @@ +--- +title: Suche +layout: default +--- + + + + + + + + + + + + +
    +{% include search_json.html %} diff --git a/docs/de/user-guide/configuration/index.md b/docs/de/user-guide/configuration/index.md new file mode 100644 index 000000000..5c219a5ba --- /dev/null +++ b/docs/de/user-guide/configuration/index.md @@ -0,0 +1,420 @@ +--- +title: Configuration +page_id: "085.0" +--- + +## Configuration + +Here are sample files of configuration. + +#### application.ini + +``` +## +## Application settings file +## +[General] + +# Listens for incoming connections on the specified port. +ListenPort=80 + +# Listens for incoming connections on the specified IP address. If this value +# is empty, equivalent to "0.0.0.0". +ListenAddress= + +# Sets the codec used by 'QObject::tr()' and 'toLocal8Bit()' to the +# QTextCodec for the specified encoding. See QTextCodec class reference. +InternalEncoding=UTF-8 + +# Sets the codec for http output stream to the QTextCodec for the +# specified encoding. See QTextCodec class reference. +HttpOutputEncoding=UTF-8 + +# Sets a language/country pair, such as en_US, ja_JP, etc. +# If this value is empty, the system's locale is used. +Locale= + +# Specify the multiprocessing module, such as thread or epoll. +# thread: multithreading assigned to each socket, available for all platforms +# epoll: scalable I/O event notification (epoll) in single thread, Linux only +MultiProcessingModule=thread + +# Specify the absolute or relative path of the temporary directory +# for HTTP uploaded files. Uses system default if not specified. +UploadTemporaryDirectory=tmp + +# Specify setting files for SQL databases. +SqlDatabaseSettingsFiles=database.ini + +# Specify the setting file for MongoDB. +# To access MongoDB server, uncomment the following line. +#MongoDbSettingsFile=mongodb.ini + +# Specify the setting file for Redis. +# To access Redis server, uncomment the following line. +#RedisSettingsFile=redis.ini + +# Specify the setting file for Memcached. +# To access Memcached server, uncomment the following line. +#MemcachedSettingsFile=memcached.ini + +# Specify the directory path to store SQL query files. +SqlQueriesStoredDirectory=sql/ + +# Determines whether it renders views without controllers directly +# like PHP or not, which views are stored in the directory of +# app/views/direct. By default, this parameter is false. +DirectViewRenderMode=false + +# Specify a file path for SQL query log. +# If it's empty or the line is commented out, output to SQL query log +# is disabled. +SqlQueryLog.FilePath=log/query.log + +# Specify the layout of SQL query log. +# %d : date-time +# %p : priority (lowercase) +# %P : priority (uppercase) +# %t : thread ID (dec) +# %T : thread ID (hex) +# %i : PID (dec) +# %I : PID (hex) +# %e : elapsed processing time in milliseconds +# %m : log message +# %n : newline code +SqlQueryLog.Layout="%d [%t] (%e) %m%n" + +# Specify the date-time format of SQL query log, see also QDateTime +# class reference. +SqlQueryLog.DateTimeFormat="yyyy-MM-dd hh:mm:ss" + +# Determines whether the application aborts (to create a core dump +# on Unix systems) or not when it output a fatal message by tFatal() +# method. +ApplicationAbortOnFatal=false + +# This directive specifies the number of bytes that are allowed in +# a request body. 0 means unlimited. +LimitRequestBody=0 + +# If false is specified, the protective function against cross-site request +# forgery never work; otherwise it's enabled. +EnableCsrfProtectionModule=false + +# Enables HTTP method override if true. The following are priorities of +# override. +# - Value of query parameter named '_method' +# - Value of X-HTTP-Method-Override header +# - Value of X-HTTP-Method header +# - Value of X-METHOD-OVERRIDE header +EnableHttpMethodOverride=false + +# Enables the value of X-Forwarded-For header as originating IP address of +# the client, if true. +EnableForwardedForHeader=false + +# Specify IP addresses of the proxy servers to work the feature of +# X-Forwarded-For header. +TrustedProxyServers= + +# Sets the timeout in seconds during which a keep-alive HTTP connection +# will stay open on the server side. The zero value disables keep-alive +# client connections. +HttpKeepAliveTimeout=10 + +# Forces some libraries to be loaded before all others. It means to set +# the LD_PRELOAD environment variable for the application server, Linux +# only. The paths to shared objects, jemalloc or TCMalloc, can be +# specified. +LDPreload= + +# Searches those paths for JavaScript modules if they are not found elsewhere, +# sets to a quoted semicolon-delimited list of relative or absolute paths. +JavaScriptPath="script;node_modules" + +## +## Session section +## +Session.Name=TFSESSION + +# Specify the session store type, such as 'sqlobject', 'file', 'cookie', +# 'mongodb', 'redis', 'cachedb' or plugin module name. +# For 'sqlobject', the settings specified in SqlDatabaseSettingsFiles are used. +# For 'mongodb', the settings specified in MongoDbSettingsFile are used. +# For 'redis', the settings specified in RedisSettingsFile are used. +# For 'memcached', the settings specified in MemcachedSettingsFile are used. +Session.StoreType=cookie + +# Replaces the session ID with a new one each time one connects, and +# keeps the current session information. +Session.AutoIdRegeneration=false + +# Specifies a Max-Age attribute of the session cookie in seconds. The value 0 +# means "until the browser is closed." +Session.CookieMaxAge=0 + +# Specifies a domain attribute to set in the session cookie. +Session.CookieDomain= + +# Specifies a path attribute to set in the session cookie. Defaults to /. +Session.CookiePath=/ + +# Specifies a value to assert that a cookie must not be sent with cross-origin +# requests; Strict, Lax or None. +Session.CookieSameSite=Lax + +# Probability that the garbage collection starts. +# If 100 specified, the GC of sessions starts at the rate of once per 100 +# accesses. If 0 specified, the GC never starts. +Session.GcProbability=100 + +# Specifies the number of seconds after which session data will be seen as +# 'garbage' and potentially cleaned up. +Session.GcMaxLifeTime=1800 + +# Secret key for verifying cookie session data integrity. +# Enter at least 30 characters and all random. +Session.Secret=z2tAnYAQ8Ouk9ZgLRL0THFN5hDvAXo + +# Specify CSRF protection key. +# Uses it in case of cookie session. +Session.CsrfProtectionKey=_csrfId + +## +## MPM thread section +## + +# Number of application server processes to be started. +MPM.thread.MaxAppServers=1 + +# Maximum number of action threads allowed to start simultaneously +# per server process. Set max_connections parameter of the DBMS +# to (MaxAppServers * MaxThreadsPerAppServer) or more. +MPM.thread.MaxThreadsPerAppServer=128 + +## +## MPM epoll section +## + +# Number of application server processes to be started. +MPM.epoll.MaxAppServers=1 + +## +## SystemLog settings +## + +# Specify the system log file name. +SystemLog.FilePath=log/treefrog.log + +# Specify the layout of the system log +# %d : Date-time +# %p : Priority (lowercase) +# %P : Priority (uppercase) +# %t : Thread ID (dec) +# %T : Thread ID (hex) +# %i : PID (dec) +# %I : PID (hex) +# %m : Log message +# %n : Newline code +SystemLog.Layout="%d %5P [%t] %m%n" + +# Specify the date-time format of the system log +SystemLog.DateTimeFormat="yyyy-MM-dd hh:mm:ss" + +## +## AccessLog settings +## + +# Specify the access log file name. +AccessLog.FilePath=log/access.log + +# Specify the layout of the access log. +# %h : Remote host +# %d : Date-time the request was received +# %r : First line of request +# %s : Status code +# %O : Bytes sent, including headers, cannot be zero +# %e : elapsed processing time in milliseconds +# %n : Newline code +AccessLog.Layout="%h %d \"%r\" %s %O%n" + +# Specify the date-time format of the access log +AccessLog.DateTimeFormat="yyyy-MM-dd hh:mm:ss" + +## +## ActionMailer section +## + +# Specify the delivery method such as "smtp" or "sendmail". +# If empty, the mail is not sent. +ActionMailer.DeliveryMethod=smtp + +# Specify the character set of email. The system encodes with this codec, +# and sends the encoded mail. +ActionMailer.CharacterSet=UTF-8 + +# Enables the delayed delivery of email if true. If enabled, deliver() method +# only adds the email to the queue and therefore the method doesn't block. +ActionMailer.DelayedDelivery=false + +## +## ActionMailer SMTP section +## + +# Specify the connection's host name or IP address. +ActionMailer.smtp.HostName= + +# Specify the connection's port number. +ActionMailer.smtp.Port= + +# Enables SMTP authentication if true; disables SMTP +# authentication if false. +ActionMailer.smtp.Authentication=false + +# Requires TLS encrypted communication to SMTP server if true. +ActionMailer.smtp.RequireTLS=false + +# Specify the user name for SMTP authentication. +ActionMailer.smtp.UserName= + +# Specify the password for SMTP authentication. +ActionMailer.smtp.Password= + +# Enables POP before SMTP authentication if true. +ActionMailer.smtp.EnablePopBeforeSmtp=false + +# Specify the POP host name for POP before SMTP. +ActionMailer.smtp.PopServer.HostName= + +# Specify the port number for POP. +ActionMailer.smtp.PopServer.Port=110 + +# Enables APOP authentication for the POP server if true. +ActionMailer.smtp.PopServer.EnableApop=false + +## +## ActionMailer Sendmail section +## + +ActionMailer.sendmail.CommandLocation=/usr/sbin/sendmail + +## +## Cache section +## + +# Specify the settings file to enable the cache module, +# which can be used through Tf::cache() function. +# See https://api-reference.treefrogframework.org/classTCache.html. +# Uncomment the following line and write connection information +# to the file. +#Cache.SettingsFile=cache.ini + +# Specify the cache backend, such as 'sqlite', 'mongodb', 'redis', +# 'memcached' or 'memory'. +Cache.Backend=memory + +# Probability of starting garbage collection (GC) for cache. +# If 1000 is specified, GC will be started at a rate of once per 1000 +# sets. If 0 is specified, the GC never starts. +Cache.GcProbability=1000 + +# If true, enable LZ4 compression when storing data. +Cache.EnableCompression=true +``` + +#### database.ini + +``` +# +# Database settings file +# + +# The currently available driver types are: +# [Driver Type] [Description] +# QDB2 IBM DB2 +# QIBASE Borland InterBase Driver +# QMYSQL MySQL Driver +# QOCI Oracle Call Interface Driver +# QODBC ODBC Driver (includes Microsoft SQL Server) +# QPSQL PostgreSQL Driver +# QSQLITE SQLite version 3 or above +# QSQLITE2 SQLite version 2 +# +# In case of SQLite, specify the DB file path to DatabaseName as follows; +# DatabaseName=db/dbfile + +[dev] +DriverType=QSQLITE +DatabaseName=db/dbfile +HostName= +Port= +UserName= +Password= +ConnectOptions= +PostOpenStatements="PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;" +EnableUpsert=false + +[test] +DriverType=QMYSQL +DatabaseName= +HostName= +Port= +UserName= +Password= +ConnectOptions= +PostOpenStatements= +EnableUpsert=false + +[product] +DriverType=QMYSQL +DatabaseName= +HostName= +Port= +UserName= +Password= +ConnectOptions= +PostOpenStatements= +EnableUpsert=false +``` + +#### logger.ini + +``` +## +## Logger settings file +## +[General] + +# Specify loggers +Loggers=FileLogger + +# Specify the default log text encoding. If not specified, +# the codec will be based on a system locale. +DefaultTextEncoding= + +## +## FileLogger section +## + +# Specify the application log file name. +FileLogger.Target=log/app.log + +# Specify the layout of FileLogger. +# %d : date-time +# %p : priority (lowercase) +# %P : priority (uppercase) +# %t : thread ID (dec) +# %T : thread ID (hex) +# %i : PID (dec) +# %I : PID (hex) +# %m : log message +# %n : newline code +FileLogger.Layout="%d %5P [%t] %m%n" + +# Specify the date-time format of FileLogger, see also QDateTime +# class reference. +FileLogger.DateTimeFormat="yyyy-MM-dd hh:mm:ss" + +# Outputs the logs of equal or higher priority than this. +FileLogger.Threshold=debug +``` \ No newline at end of file diff --git a/docs/de/user-guide/controller/access-routing.md b/docs/de/user-guide/controller/access-routing.md new file mode 100644 index 000000000..17756f8c3 --- /dev/null +++ b/docs/de/user-guide/controller/access-routing.md @@ -0,0 +1,62 @@ +--- +title: Access Routing +page_id: "050.040" +--- + +## Access Control + +Access control in a website can be done in two ways: + +* by user authenticiation and +* by the connecting host (IP address). + +For access control by the host, please set for the Web server (Apache/nginx) as required. + +First of all, we will talk about how to control user access for Web sites. + +## User Access Control + +On some Web sites, there is a fixed amount pages that anyone can access, and also pages that can only be accessed by users. For example an Admin page would only be accessible to persons with the right authority. In such cases, you can deny access in the following ways: + +First, refer to the section on [authentication]({{ site.baseurl }}/en/user-guide/helper-reference/authentication.html){:target="_blank"} and create a user model class.
    +For the page to which you want to restrict access, login authentication should be required. By doing so, an instance of the user model will be obtained. + +Then override setAccessRules of the controller() method. Set the access rules for any the action by group or user ID to *allow* or *deny*. Both *User ID* and *Group* point to the user model class. The identityKey() method and the groupKey() method in the model class each return the value that represents granted or denied access when the user is performing an action. + +```c++ +void FooController::setAccessRules() +{ + setDenyDefault(true); + QStringList allowed; + allowed << "index" << "show" << "entry" << "create"; + setAllowUser("user1", allowed); + : +} +``` + +Use allowUser() and allowGroup() to allow access, and define denyUser() and denyGroup() to deny access. The first argument specifies the group or user ID; the second argument specifies the list or action name (QStringList). + +For permissions/denials from the user that do not define the access rules, the default settings will be used. Use setAllowDefault() method or setDenyDefault() method for this. + +```c++ +setDenyDefault(true); +``` + +Here is the logic to validate users with access. The following method here overrides the preFilter() method of the controller and returns *false* when the user access has been denied. The controller preFilter() method returns *false* if user access is not granted. + +```c++ +bool FooController::preFilter() +{ + ApplicationController::preFilter(); + : + : // Get an instance of the user model + : + if (!validateAccess(&loginUser)) { // to verify the access rules you have defined + renderErrorResponse(403); + return false; + } + return true; +} +``` + +If the preFilter() method returns false, the action will not be executed. You will therefore want to visualize that the access hs been denied. For this purpose, you can then use the renderErrorResponse() method to display a static error page (*public/403.html* in this example). \ No newline at end of file diff --git a/docs/de/user-guide/controller/cookie.md b/docs/de/user-guide/controller/cookie.md new file mode 100644 index 000000000..1b3b46c3d --- /dev/null +++ b/docs/de/user-guide/controller/cookie.md @@ -0,0 +1,31 @@ +--- +title: Cookie +page_id: "050.020" +--- + +## Cookie + +Cookies are information stored in the browser (PC) containing key-value pairs which are supposed to be shared between the browser and the online Web application. There are basically some restrictions, but the functions of cookies are supported by almost all browsers. Cookies are essential for establishing sessions as described in the previous section. + +The Cookie system was originally proposed by Netscape Communications Corporation and published as an RFC before becoming standardized. + +## Save a String in a Cookie + +Cookies are created by the Web application which the user has access to. In accordance with the specifications cookies, they are saved as key-value pairs for the HTTP response. + +When the relevant action occurs, the following functions are called and the key-value pairs will be saved inside a cookie. + +```c++ +addCookie("key1", "Hello world."); +``` + +An expiration date can be set on a cookie as well. The cookie rides on the HTTP request, and it is sent between server and browser. Once it reaches the expiration date the cookie will be automatically erased from the browser and eventually disappears. The expiration date is specified in the third argument of the addCookie() function. For more information, refer to the [API reference](http://treefrogframework.org/tf_doxygen/classes.html){:target="_blank"}. + +## Reading a String from a Cookie + +By using the keys, values can be retrieved from the cookies present in the HTTP request in the following way. + +```c++ +QByteArray text = httpRequest().cookie("key1"); +// text = "Hello world." +``` \ No newline at end of file diff --git a/docs/de/user-guide/controller/index.md b/docs/de/user-guide/controller/index.md new file mode 100644 index 000000000..0efa3c155 --- /dev/null +++ b/docs/de/user-guide/controller/index.md @@ -0,0 +1,307 @@ +--- +title: Controller +page_id: "050.0" +--- + +## Controller + +The Controller is the central class of a Web application. It receives a request from the browser, call the business logic with focus on the model, generates HTML to the view on the basis of the results, and returns the response. + +## Defining Actions + +An **action** is called on the basis of the requested URL which decides about the method to be called defined in the controller. + +Let's add some actions to the "Scaffolding" as it is being generated. First, the part of the 'public slots' is declared in the header file action. Please note that when you declare any other parts, they (by normal methods) will not be recognized. If you want to add arguments to the action, these should be declared in the QString type. Up to 10 arguments may be specified there. + +```c++ +class T_CONTROLLER_EXPORT FooController : public ApplicationController +{ + Q_OBJECT + : +public slots: // The action is defined here + void bar(); + void baz(const QString &str); + : +``` + +After that, carry on implementing those actions in the same way as always. + +## Determination of an Action + +When the URL string is requested, the correct action to be invoked will be determined. By default, the following rules will be used. + +``` + /controller-name/action-name/argument1/argument2/ ... +``` + +These arguments will be referred as the "Action Arguments". + +If more than 10 are specified, only 10 arguments will be taken into account. If you want to use 11 or more arguments, please use the post or data URL arguments as described below. + +Here are some concrete examples. Actions from the BlogController class are called in each case. + +``` + /blog/show → show(); + /blog/show/2 → show(QString("2")); + /blog/show/foo/5 → show(QString("foo"), QString("5")); +``` + +If the action name is omitted, then the action with the name *index* will be called by default. This could look like as follow: + +``` + /blog → index(); +``` + +If the action corresponding to the requested URL is not defined, status code 500 (Internal Server Error) is returned to the browser. + +For each action that is called, the following process will be carried out in most cases: + +* scrutiny of the request +* access the cookies and the session +* access the upload file +* call the model (business logic) +* passing variable(s) to a view +* request the creation of the HTML response + +This usually means a lot of work and code to be written for the programmer. Therefore, as a result, some people may end up with a large and complex controllers. To combat this, try to keep the implementation of the model side business logic only. You should always try to keep the controller as simple and as small as possible. + +Once you have learned the mechanism on how an action is invoked, it is possible to customize the action that is determined by the URL. Please see the section on [URL routing](http://www.treefrogframework.org/documents/controller/url-routing){:target="_blank"} for more details. + +## Acquisition of the Request + +An HTTP request is represented by the THttpRequest class. +In the controller, you can obtain ThttpRequest object from the httpRequest() method. + +```c++ +const THttpRequest & TActionController::httpRequest () const; +``` + +You can retrieve various data from here. + +## Receiving the Request Data + +An HTTP request sent from the client (browser) consists of a method, a header, and a body. The incoming data, which arrives the server, is referred to it in the following terms. + +* post data - data submitted from a form using the POST method +* URL argument (query parameter) - data assigned to a URL argument after "?" as (format of key = value & …) +* Action argument - data that has been granted after action (the 3 part of "/blog/edit/3") ←see above. + +The following example shows how to obtain post data in the controller. +Let's consider that you've made an \ tag in the view. + +``` + +``` + +To get the value that has been sent from the browser to the controller (server-side), use the following code: + +```c++ + QString val = httpRequest().formItemValue("title") +``` + +By the way, if you want to convert a value into *int*, you can use the Qt included toInt() method. + +If you have a large number of data items sent, getting them one by one can be a bit cumbersome. For this, there is also a method available to obtain the values all at once.
    +For example, you could write your tags as follows. + +``` + + +``` + +In the controller, data can be obtained as follows: + +```c++ + QVariantMap blog = httpRequest().formItems("blog"); + QVariant t = blog["title"]; + QVariant b = blog["body"]; + : +``` +Request data can be represented by the hash format. + +How to get the URL argument (query parameters)? Here is an example: + +``` + http://example.com/blog/index?mode=normal +``` + +You can use the following method to the value named *mode* here in the *index* action of the *blog* controller. + +``` + QString val = httpRequest().queryItemValue("mode"); + // val = "normal" +``` + +By the way, if you want to get the data without specifying the URL argument and post data, you can use the methods allParameters() and methods(). + +In order to verify whether requested data is in the form required by the application side, a validation function is provided, too. For more information, visit the chapter on [validation](/en/user-guide/helper-reference/validation.html){:target="_blank"}. + +## Passing Variables to a View + +To pass variables to a view, use the macros* texport(variable)* or *T_EXPORT(variable)*. You can specify the arguments as *QVariant*, *int*, *QString*, *QList*, or *QHash*. Or you can, of course, also specify the instance of a user-defined model. These are used as follows: + +```c++ + QString foo = "Hello world"; + texport(foo); + + // Alternatively + + int bar; + bar = … + texport(bar); +``` + +**Note:** the variable must be specified as an argument to texport. You can't directly specify a string ("Hello world") or numbers (such as 100). + +To use a variable inside a view, you must first declare the variable in *tfetch (Type, variable)*. Please see the [view]({{ site.baseurl }}/en/user-guide/view/index.html){:target="_blank"} chapter for more information. + +##### In brief: Pass the object to the view by using tfetch(). + +**In case of an user-defined class:** + +When you make the new class passing to the view (user-defined class), please add the following charm at the end of the header class. For more information about this, see the section on ["Creating original model"](/en/user-guide/model/index/html){:target="_blank"}. + +```c++ + Q_DECLARE_METATYPE(ClassName) // ← Please replace the class name +``` + +You don't need to declare a class that is provided in Qt's Q_DECLARE_METATYPE. However, declaration is required if you want to pass an object of a template class to the view, such as QHash and QList. In that case, please add the following at the end of the *helpers/applicationhelper.h*. + +```c++ + : + Q_DECLARE_METATYPE(QList) +``` + +A Q_DECLARE_METATYPE macro argument is the name of a class. But if you include a comma (such as QHash \), a compile error will occur. In this case, the name used should be declared as typedef QHash \. + +```c++ + typedef QHash BarHash; + Q_DECLARE_METATYPE(BarHash) +``` + +#### Export object + +We refer to an object passed to the view (object that set in the texport() method) as an "export object". + +## Requesting the Creation of a Response + +After processing the business logic, a result of the process as an HTML response will be returned. If the BlogController *show* action is executed, the response is generated by the template name (extension due to the template system) as in *views/blog/show.xxx*. To request a response, you can use the render() method. + +```c++ + bool render(const QString &action = QString(), const QString &layout = QString()); +``` + +If you want to return a response with a different template, specify the action name as an argument. The layout file name can be specified as a layout argument. + +The method **render()** means to "request the view/template to create a response". This is also called "drawing". + +##### In brief: To render the template, use the render() method. + +### Layout + +When you create a site, the header and footer and some other parts are usually common to all pages, but with different content. The term layout is used for these common parts on which the template is based. Layout files should be placed in the views/layouts directory. + +### About the Template System + +TreeFrog, so far, has adopted two template systems: **Otama**, and **ERB**. In ERB, code will be embedded by using <% .. %>. The Otama system has a little different treatment in TreeFrog, because it completely separates the logic (.otm) and the presentation templates (.html). + +* ERB uses file extension: xxx.erb +* Otama uses file extensions: xxx.otm and xxx.html
    (where 'xxx' is the action name) + +## Direct Rendering of a String + +To render a string directly, use the renderText() method. + +```c++ + // "Hello world" render the string + renderText("Hello world"); +``` + +By default, the layout is not applied. If you want to apply a layout, you must specify *true* as the second argument. + +```c++ + // rendering the string "Hello world" while applying a layout + renderText("Hello world", true); +``` + +For more information about layouts, please refer to the [view](/en/user-guide/view/index/html){:target="_blank"} chapter. + +## Redirect + +In order to redirect the browser to another URL, you can use the redirect method. For the first argument, specify the instance of the *QUrl* class. + +```c++ + // redirecting to www.example.org + redirect(QUrl("www.example.org")); +``` + +You are then able to redirect to other actions on the same host. + +```c++ + / redirecting to the incdex action of the Blog controller + redirect( url("blog", "index") ); +``` + +Here is another useful url method. It returns the appropriate QUrl instance if you pass the controller and action names. + +To redirect to other actions inside the same controller, you can omit the controller name. + +```c++ + // redirecting to the show action of the same controller + redirect( urla("show") ); +``` + +It is important to use the urla() method. + +## To Display Messages in the Redirect Destination + +A redirection leads to another URL. This is because, from the point of view of the server, the URL receives a new destination, thus it has the effect of another action being called. + +In the TreeFrog Framework, there is a mechanism to pass a message (via a variable) to the controller to which you have transferred in the redirect. See the following example which passes variables on the tflash() or T_FLASH() method. + +```c++ + // foo - the flash object + QString foo = "successfully"; + tflash( foo ); +``` + +The variable that was passed here is given the name "Flash object". + +The flash object is converted to an export object in the view of the redirect. Because of this, it can be output in the echo() or eh() method to display the message. + +### The Good Thing About Using the Flash Object + +In fact, you can create a web application without using any flash objects. However, if you do use them, when certain conditions are met, code can become an easy-to-understand manner (once you get used to it). + +Personally, I think it's better and easier to understand, to have each action completely independent in order to reduce the dependence of actions on each other. I would advise against implementing a call to more than one action in a single request. Let's keep the relationship of one action to one request as much as possible. + +If the action creates separate independent displays of almost the same content, the code becomes simpler when using the flash object. + +The way the *blogapp* (the subject of the [tutorial chapter](/en/user-guide/tutorial/index/html){:target="_blank"}) uses the *create* and *show* action are good examples. But processing of these actions is different, but both only display the contents of the blog 1 as a result of this processing. In the create action, after the successful data registration, the data will be displayed by redirecting to the show action. At the same time, the message "Created successfully." is displayed by using the flash object. + +Actually, an application is not always this simple, because it isn't always necessarily possible to capture and use the flash object. Therefore, please try to use it to strike a balance. + +With that being said, in practice most actions come down to either using the *redirect()* or *render()* method. + +**By the way …**
    +In the manner described above, when a redirect occurs, the processing is cut once it goes to the Web application. Objects that survive in spite of this can make other contexts (actions), whereas the flash object is implemented using the session. + +## staticInitialize() Method + +When starting up, there is only one process in the application. You may want to keep reading the initial data from the DB in advance. + +In this case, write the processing as *ApplicationController#staticInitialize()*." + +```c++ + void ApplicationController::staticInitialize() + { + // do something.. + } +``` + +This staticInitialize() method, is called only once when the server process is started. + +## Lifespan of a controller instance + +An instance of a controller is created just before the action is invoked, and destroyed as soon as the action is finished. It means that the creation and the destruction of a controller are associated with each HTTP request.
    +Because of this specification, the controller doesn't often have instance variables. Please implement the *ApplicationController* the same way. \ No newline at end of file diff --git a/docs/de/user-guide/controller/json-xml-response.md b/docs/de/user-guide/controller/json-xml-response.md new file mode 100644 index 000000000..76126ebd6 --- /dev/null +++ b/docs/de/user-guide/controller/json-xml-response.md @@ -0,0 +1,57 @@ +--- +title: JSON/XML Response +page_id: "050.050" +--- + +## JSON/XML Response + +There are cases where, in order to provide information to an external application, data needs to be output in XML format or JSON. A case would be the Ajax applications. + +## Send the Contents of the Model in JSON Format + +If external applications use JavaScript, the JSON (JavaScript Object Notation) format is easy to handle. To deal with JSON in this framework, Qt version 5 or later is required. + +Here is an example of sending a JSON object from the controller. + +```c++ + Blog blog = Blog::get(10); + renderJson(blog.toVariantMap()); +``` + +That's all. The model that is generated by the generator, is provided by the toVariantMap() method to convert QVariantMap object itself. It's important to note that this method will convert all the properties a model has, therefore the method should be implemented separately for separate properties if there is information to be concealed. + +Sending All Blog Objects in List Format + +``` + renderJson( Blog::getAllJson() ); +``` + +This is also simple! However, please note that if there are a large number of records in the blog database, results could be unexpected. + +In addition to this, the following methods are available. Please check the [API reference](http://treefrogframework.org/tf_doxygen/classes.html){:target="_blank"} for this. + +```c++ + bool renderJson(const QJsonDocument &document); + bool renderJson(const QJsonObject &object); + bool renderJson(const QJsonArray &array); + bool renderJson(const QVariantMap &map); + bool renderJson(const QVariantList &list); + bool renderJson(const QStringList &list); +``` + +## Send the Contents of the Model in XML Format + +The way to send the content of the model in XML format is not much different from the way we send in JSON format. Call one of the following methods in order to achieve this: + +```c++ + bool renderXml(const QDomDocument &document); + bool renderXml(const QVariantMap &map); + bool renderXml(const QVariantList &list); + bool renderXml(const QStringList &list); +``` + +If the input from the output of these does not suit your requirements, you can implement a new template. How to implement it is described in the [view chapter]({{ site.baseurl }}/en/user-guide/view/index.html){:target="_blank"} section, but don't forget that you only set the content type of the response in the controller. + +```c++ + setContentType("text/xml") +``` \ No newline at end of file diff --git a/docs/de/user-guide/controller/session.md b/docs/de/user-guide/controller/session.md new file mode 100644 index 000000000..7e41677b2 --- /dev/null +++ b/docs/de/user-guide/controller/session.md @@ -0,0 +1,104 @@ +--- +title: Session +page_id: "050.010" +--- + +## Session + +On a Web site, a variety of information will be carried over from one page to another. A familiar example is the shopping cart in which the selected product numbers are carried across the page. + +The HTTP protocol doesn't retain any information (it is said to be stateless). Therefore, some kind of mechanism is necessary in order to retain the information. The mechanism for this is called a **session**; it will be provided by the framework (or language). + +- In addition, in another sense, there is also the case where the Web site visitor refers to a "series of communications" to leave the site. + +## Adding Data to the Session + +To add data to a session, do the following: + +``` + session().insert("name", "foo"); + or + session().insert("index", 123); +``` + +Access to the session in the action. Alternatively, we could also write as follows: + +``` + session()["name"] = "foo"; + or + session()["index"] = 123; +``` + +## Reading the Data from the Session + +Read the data from the session as follows: + +``` + QString name = session().value("name").toString(); + or + int index = session().value("index").toInt(); +``` + +Alternatively, we could also write as follows: + +``` + QString name = session()["name"].toString(); + or + int index = session()["index"].toInt(); +``` + +## Set the Session Destination + +As you have seen so far, the session can be considered an "associative array (hash)" with the key-value pairs, where the data is represented as strings. The session itself is a single object, but somewhere we need to save (persisting) information in order to be able to carry it over between pages. + +In TreeFrog Framework, you can select one file, RDB (SqlObject), from cookies as the storage location for a session. This is achieved by using the Session.StoreType setting in the *application.ini*. + +## Set a Cookie to a Location to Save the Session + +If you would like to set a cookie destination, you can simply write: + +``` + Session.StoreType=cookie +``` + +By saving cookies, so that the contents of the session will be saved to the client (browser) side, the contents will be available for the users to view if you wish to allow it. Information that should not be shown to the user should be saved on the server (such as RDB). As a rule, you should try to put in the session only a minimum amount of necessary information. + +## Session Save File Destination + +If you would like to set a destination for your cookie files, you can simply write the following (Session files will be continuously made in the *tmp* directory of the application root directory): + +``` + Session.StoreType=file +``` + +This method isn't much used if the AP server is parallelized on multiple machines. Because the request from the user doesn't always reach the same AP server, a shared file server is required as the destination of the session file. Furthermore, in order to operate properly and safely, a file locking mechanism is needed. + +## Set the RDB where to Save the Session + +As a prerequisite, the database information has been set in the *database.ini* file.
    +In order to save the session to RDB, a table is required. Therefore, let's create a 'session' table in the database with an SQL statement such as the following: +Example in MySQL : + +```sql + > CREATE TABLE session (id VARCHAR(50) PRIMARY KEY, data BLOB, updated_at TIMESTAMP); +``` + +The *application.ini* file should also be edited. + +``` + Session.StoreType=sqlobject +``` + +That's all we need to do. If necessary, the system can be allowed to save the rest of the session to the DB. + +## Session Lifetime + +The validity period (measured in seconds) of a session is set to *Session.LifeTime* in the configuration file. If the expiration date has passed, the session is erased or destroyed leaving nothing behind. In addition, you can specify 0, which means that the session will be valid only while the browser is running. In this case, the session is discarded when the browser is closed. + +### Column + +One session is assigned for each browser. Each different PC has a different session, and the session is also different if a different browser is used on the same PC. + +The Framework keeps track of the sessions by allocating each one with a number. + +A unique ID (hard to guess) will be allocated to each session. The ID is stored in the PC browser as a cookie and sent again aboard to the HTTP request, unless the expiration date has been exceeded. The Framework then finds the session from the storage location corresponding to the ID. \ No newline at end of file diff --git a/docs/de/user-guide/controller/url-routing.md b/docs/de/user-guide/controller/url-routing.md new file mode 100644 index 000000000..b14281237 --- /dev/null +++ b/docs/de/user-guide/controller/url-routing.md @@ -0,0 +1,68 @@ +--- +title: URL Routing +page_id: "050.030" +--- + +## URL Routing + +The URL Routing is the mechanism that determines the action to call for a requested URL. When a request is received from a browser, the URL checks for correspondence with the routing rules and, where applicable, the defined action will be called. If no action has been specifically determined, action by the default rule is invoked. + +Here is a little reminder of those default rules: + +``` + /controller-name/action-name/argument1/argument2/... +``` + +Let's look at customizing the routing rules.
    +The routing definition is written in the *config/routes.cfg* file. For each entry, the directives, path and an action are written side by side on a single line. The directive is to select *match*, *get*, *post*, *put* or *delete*. +In addition, a line that begins with '#' is considered to be a comment line. + +Here's an example: + +``` + match /index Merge.index +``` + +In this case, if the browser requests '/index' either by POST method or GET method, the controller will respond with the *index* action of the *Merge* controller. + +The next case is where the get directives have been defined: + +``` + get /index Merge.index +``` + +In this case, routing will be carried out only when the '/index' is requested with the GET method. If the request is made by the POST method, it will be rejected. + +Similarly, if you specify a post directive, it is only valid for POST method requests. Request in GET method will be rejected. + +``` + post /index Merge.index +``` + +The following is about how to pass arguments to the action. Suppose you have defined the following entries as routing rules: + +``` + get /search/:params Searcher.search +``` + +It's important to use the keyword ':params'.
    +In the case of */search/foo*, when the request is made with the GET method, a search action with one argument is called to the *Searcher* controller. The "foo" in the argument is passed. +Similarly with /search/foo/bar. Following this request, a search action with two arguments ("foo" and "bar") is called. + +``` + /search/foo -> Call search("foo") of SearcherController + /search/foo/bar -> Call search("foo", "bar") of SearcherController +``` + +## Show Routing + +After building the app, the following command shows the current routing information. +``` + $ treefrog --show-routes + Available controllers: + match /blog/index -> blogcontroller.index() + match /blog/show/:param -> blogcontroller.show(id) + match /blog/create -> blogcontroller.create() + match /blog/save/:param -> blogcontroller.save(id) + match /blog/remove/:param -> blogcontroller.remove(id) +``` diff --git a/docs/de/user-guide/controller/webapi.md b/docs/de/user-guide/controller/webapi.md new file mode 100644 index 000000000..46433d2b0 --- /dev/null +++ b/docs/de/user-guide/controller/webapi.md @@ -0,0 +1,185 @@ +--- +title: Web API +page_id: "050.060" +--- + +## Web API + +Web API is an interface between systems on the Web, i.e., a protocol for passing data between them. By implementing this Web API, data can be passed to and from browsers and other softwares via HTTP or HTTPS. + +Web APIs are freely implemented by service providers, and many cloud services offer Web APIs with a REST-style design concept and JSON data format. + +## Scaffolding Web API + +In TreeFrog, it's easy to generate Web API scaffolds to do CRUD data from a single table. +For example, the following table exists. + +``` +sqlite> .schema blog +CREATE TABLE blog (id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(20), body VARCHAR(200), created_at TIMESTAMP, updated_at TIMESTAMP, lock_revision INTEGER); +``` + +The following command creates a Web API scaffold for this table. + +``` +$ tspawn api blog +DriverType: QSQLITE +DatabaseName: db/dbfile +HostName: +Database opened successfully + created models/sqlobjects/blogobject.h + created models/objects/blog.h + created models/objects/blog.cpp + updated models/models.pro + created models/apiblogservice.h + created models/apiblogservice.cpp + updated models/models.pro + created controllers/apiblogcontroller.h + created controllers/apiblogcontroller.cpp + updated controllers/controllers.pro +``` + +Source files such as controller and service classes have been generated. The data format of the Web API created here is JSON. + +Entry points of the Web API are defined in the controller `apiblogcontroller.h` as follows. + +``` +class T_CONTROLLER_EXPORT ApiBlogController : public ApplicationController { + Q_OBJECT +public slots: + void index(); // Gets all entries + void get(const QString &id); // Gets one entry + void create(); // New registration + void save(const QString &id); // Saves (updates) + void remove(const QString &id); // Deletes one entry +}; +``` + +These entry points are as follows. + +``` +/apiblog/index +/apiblog/get/(id) +/apiblog/create +/apiblog/save/(id) +/apiblog/remove/(id) +``` + +## Check Web API + +Build the source and start the server. + +``` + $ make + $ treefrog -e dev -d +``` + +Check the operation of the Web API with the curl command. + +``` +$ curl -sS http://localhost:8800/apiblog/index +{"data":[]} +``` + +Since no data is registered, empty JSON data is retrieved. + +## Add entry point + +In addition to the default entry points, additional entry points can be added by writing entries in `config/routes.cfg`. For example, write the followings. + +``` +# Method Entry-point Function + +get /api/blog/index ApiBlog.index +get /api/blog/get/:param ApiBlog.get +post /api/blog/create ApiBlog.create +post /api/blog/save/:param ApiBlog.save +post /api/blog/remove/:param ApiBlog.remove +``` + +Check that it has been added correctly. + +``` +$ treefrog --show-routes +Available routes: + get /api/blog/index -> apiblogcontroller.index() + get /api/blog/get/:param -> apiblogcontroller.get(id) + post /api/blog/create -> apiblogcontroller.create() + post /api/blog/save/:param -> apiblogcontroller.save(id) + post /api/blog/remove/:param -> apiblogcontroller.remove(id) +``` + +In addition to GET and POST, PUT and DELETE can be added. See the [URL Routing](/en/user-guide/controller/url-routing.html) page for more information. + +Check the operation of the entry point added by the curl command. + +``` +$ curl -sS http://localhost:8800/api/blog/index ← Added entry point +{"data":[]} +``` + +Similarly, empty JSON data could be retrieved. + +## Web API for registration + +Next, register one item of data. Post JSON data with curl command. + +``` +$ curl -sS -X POST -H "Content-Type: application/json" -d '{"title":"Hello","body":"hello world"}' http://localhost:8800/api/blog/create +``` + +Try to get the list again. + +``` +$ curl -sS http://localhost:8800/api/blog/index +{"data":[{"body":"hello world","createdAt":"2022-05-25T16:39:02.142","id":1,"lockRevision":1,"title":"Hello","updatedAt":"2022-05-25T16:39:02.142"}]} +``` + +This time, JSON list data could be retrieved. + +Try to get the JSON data by specifying the ID. + +``` +$ curl -sS http://localhost:8800/api/blog/get/1 +{"data":{"body":"hello world","createdAt":"2022-05-25T16:39:02.142","id":1,"lockRevision":1,"title":"Hello","updatedAt":"2022-05-25T16:39:02.142"}} +``` + +The JSON data could be retrieved successfully. + +## Restrict properties to be returned + +In the source code after scaffolding, all data in the DB record columns were returned, but here try to restrict the returned data (properties). + +Edit the service class `apiblogservice.cpp`. + +``` +QJsonObject ApiBlogService::index() +{ + auto blogList = Blog::getAll(); + QJsonObject json = { {"data", tfConvertToJsonArray(blogList, {"id", "title", "body"})} }; // ← here + return json; +} + +QJsonObject ApiBlogService::get(int id) +{ + auto blog = Blog::get(id); + QJsonObject json = { {"data", blog.toJsonObject({"id", "title", "body"})} }; // ← here + return json; +} +``` + +After the build, a curl command confirms that only the specified properties were returned. + +``` +$ curl -sS http://localhost:8800/api/blog/index +{"data":[{"body":"hello world","id":1,"title":"Hello"}]} + +$ curl -sS http://localhost:8800/api/blog/get/1 +{"data":{"body":"hello world","id":1,"title":"Hello"}} +``` + +## Summary + +We were able to generate simple Web API by scaffolding. + +In real cases, this scaffolding-built implementation will not be sufficient. For example, it may retrieve data from two or more tables and return hierarchical JSON data. In such a case, please modify the service class and other classes and implement accordingly. diff --git a/docs/de/user-guide/cooperation-with-the-reverse-proxy-server/index.md b/docs/de/user-guide/cooperation-with-the-reverse-proxy-server/index.md new file mode 100644 index 000000000..645607059 --- /dev/null +++ b/docs/de/user-guide/cooperation-with-the-reverse-proxy-server/index.md @@ -0,0 +1,60 @@ +--- +title: Cooperation with the Reserve Proxy Server +page_id: "140.0" +--- + +## Cooperation with the Reverse Proxy Server + +The TreeFrog Framework provides an application server (AP server) to send back a dynamic created content for an inidvidual request. As previously mentioned, the application server also functions as a Web server, so you can also process static content. As for the Web server role, we have specialized it in one function only for an high speed respond of static content. + +As it stands, this is sufficient for small and medium-sized Web sites. However, when making a large Web site system, in order to achieve stable operation and load balance it becomes necessary to use a Web server (reverse proxy) system such as *nginx* or* Apache*.
    +Also, if you want to work with compression response and SSL, you must set up a Web server separately. + +##### In brief: For large scale sites, set up a Web server. + +
    + +**Server-side Roles** + +
    + +
    + +| Server | Role | +|----------------------------|-----------------------------------------------------------------------------------| +| Web server (Reverse proxy) | Load balancing, encryption, compression, caching,sending static contents and etc. | +| AP server (TreeFrog) | Generating and sending dynamic contents (static contents) | +| DB server (RDB) | Data store, persistence | + +

    + +I'll not go into detail about the reverse proxy configuration for Apache and nginx, since a great deal of information is readily available on the internet.
    +The basic idea is that reverse proxy listens to port 80 with requests being transferred to the application server as they are received. Of course, the application server should be assigned a port number that does not duplicate other services. Use the *ListenPort* parameter in the *application.ini* file to set the port number. + +If you run your application server and reverse proxy on the same host, you can use the UNIX domain socket connection to them. The advantages of using the UNIX domain socket are that its overhead is less than the TCP socket and cannot be connected to an external host. This can make you feel a little bit more secure. + +For settings corresponding to the UNIX domain socket server application, perform the ListenPort parameters as follows: + +``` + ListenPort=unix:/tmp/foo +``` + +- Please change the file name as necessary. + +For example, in order to make a reverse proxy to a UNIX domain socket in nginx, add the following entry: + +``` +upstream backend { + server unix:/tmp/foo; +} +server { + listen 80; + server_name localhost; + location / { + proxy_pass http://backend; + } +} +``` + +Then all you should need to do is to save the setting.
    +Start the AP server and then the Web server, then try to visit it from the browser to be sure if it is working correctly. If it doesn't work properly, try looking for the reason and checking the access log. \ No newline at end of file diff --git a/docs/de/user-guide/debug/index.md b/docs/de/user-guide/debug/index.md new file mode 100644 index 000000000..a2cd7445a --- /dev/null +++ b/docs/de/user-guide/debug/index.md @@ -0,0 +1,147 @@ +--- +title: Debug +page_id: "110.0" +--- + +## Debug + +When you build the source code that you have created, four shared libraries are generated. These are the basement of your Web application. The TreeFrog application server (AP server) reads them during the startup, then waits for access from a browser. + +Debugging the Web application is equivalent to debugging the shared common libraries. First of all, let's compile the source code in debug mode.
    +In the application root directory, you can run the following command: + +``` + $ qmake -r "CONFIG+=debug" + $ make clean + $ make +``` + +When debugging, the following settings are used according to the platforms: + +
    + +**In the case of Linux/macOS:** + +
    + +
    + +| Option | Value | +|-------------------------------------------------------|------------------------------------------------| +| Command | tadpole | +| Command argument | \--debug -e dev (Absolute path of the app root) | +| LD_LIBRARY_PATH env variable
    (not needed on macOS) | Specify the *lib* directory of web application. | + +

    + +
    + +**In the case of Windows:** + +
    +
    + +| Option | Value | +|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Command | tadpole**d**.exe | +| Command argument | \--debug -e dev (Absolute path of the app root) | +| PATH variable | Add TreeFrog's bin directory C:\TreeFrog\x.x.x\bin at the beginning. Also, if you use something like MySQL or PostgreSQL, the directoryincluding the client DLL should also be added. | +| TFDIR variable | The TreeFrog directory is set, c:\TreeFrog\x.x.x. | + +

    + +- x.x.x stands for the version of TreeFrog. + +Next we are going to configure these items. + +## Debugging with Qt Creator + +Let's introduce *debugging* using the *Qt Creator*, I think the way you debug is basically the same with other debuggers. + +First, make a thread in the [MPM]({{ site.baseurl }}/en/user-guide/performance/index.html){:target="_blank"} application configuration file. + +``` + MultiProcessingModule=thread +``` + +Import the source code of the application file to the Qt Creator. Then click *[File] – [Open File or Project...]* and then choose the project file on the file selection screen. Click the *[Configure Project]* button, and then import the project. The following screen is seen when the *blogapp* project has been successfully imported. + +
    + +![QtCreator Import]({{ site.baseurl }}/assets/images/documentation/QtCreator-import.png "QtCreator Import") + +
    + +Now we will run the settings screen for debugging.
    +The last of the tadpole command arguments, specifies the *-e* option and the absolute path of application root. You may remember that the -e option is the option for switching the DB environment. Let's assume you choose *dev*. + +In the case of Linux:
    +In the next screen we choose /var/tmp/blogapp as the application root. + +
    + +![QtCreator runenv]({{ site.baseurl }}/assets/images/documentation/QtCreator-runenv(1).png "QtCreator runenv") + +
    + +In Windows:
    +We can set the content in two ways by building the configuration screen and by implementing the configuration screen. + +Example of a build configuration: (sorry for only having Japanese images for this demonstration...) + +
    + +![QtCreator build settings window 1]({{ site.baseurl }}/assets/images/documentation/QtCreator-build-settings-win.png "QtCreator build settings window 1") + +
    + +And an example of run configuration: + +
    + +![QtCreator build settings window 2]({{ site.baseurl }}/assets/images/documentation/QtCreator-run-settings-win.png "QtCreator build settings window 2") + +
    + +That is all about the configuration settings.
    +When adding a breakpoint to the source code, always try to access it from your Web browser. + +Now check out whether the browser stops at the previously placed breakpoint. Does it stop at the there? + +## Debugging WebSocket with Qt Creator + +To debug TreeFrog application containing WebSocket, modify Run Configuration of your project: + +
    + +**In the case of Linux/macOS:** + +
    +
    + +| Option | Value | +|--------------------|---------------------------------------------| +| Command | treefrog | +| Command argument | -e dev (Absolute path of the app root) | +| Working directory | %{buildDir} | + +

    + +
    + +**In the case of Windows:** + +
    +
    + +| Option | Value | +|--------------------|---------------------------------------------| +| Command | treefrog**d**.exe | +| Command argument | -e dev (Absolute path of the app root) | +| Working directory | %{buildDir} | + +

    + +Now run the application *[Build] – [Run]* and then attach to tadpole process (tadpole**d** on Windows) + +To attach to tadpole process in QtCreator click *[Debug] – [Start Debugging] - [Attach to Running Application...]*. It is possible to create a keyboard shortcut for this operation. diff --git a/docs/de/user-guide/deployment/index.md b/docs/de/user-guide/deployment/index.md new file mode 100644 index 000000000..1fb09c577 --- /dev/null +++ b/docs/de/user-guide/deployment/index.md @@ -0,0 +1,137 @@ +--- +title: Deployment +page_id: "130.0" +--- + +## Deployment + +A developed application is deployed in the production environment (or test environment) and it is about to run there. + +Although it's easier if the source code is built in the production environment, in general the production environment and the build machine are separate. For building, the computer needs to have the same OS/Library installed as in the production environment. The binary for the release can then be built. The binary and its related files, which all will be created, are then transferred from the archive to the production environment. + +### Release Mode Building + +In order to build the source code in release mode, the following command should be used in the application root: + +``` + $ qmake -r "CONFIG+=release" + $ make clean + $ make +``` + +A binary, that is optimized to the environment, should have been generated then. + +### Deploying to the Production Environment + +First, check the settings of the production environment. You should check the user *name/password* in the [*product*] section in the *database.ini* settings file and the number of the listening port in the *application.ini* file. Please ensure all these settings are correct for your environment. + +The following list is a summary of folder and files needed for your application to work correctly. All the folders and files will be archived in the following directory: + +* config +* db ← not necessary if sqlite is not used +* lib +* plugin +* public +* sql + +Examples of the *tar* command: + +``` + $ tar cvfz app.tar.gz config/ db/ lib/ plugin/ public/ sql/ +``` + +- Please change the tar file name accordingly. + +This is then the setup of the production environment. Building and configuration of the database systems and installation of TreeFrog Framework/Qt have been completed in advance. + +The tar file is copied to the production environment. Once copied, it can then be expanded by creating a directory. + +``` + $ mkdir app_name + $ cd app_name + $ tar xvfz (directory-name)/app.tar.gz +``` + +To start, use the following command by specifying the application root directory (must be an absolute path): + +``` + $ sudo treefrog -d [application_root_path] +``` + +Some distributions may require you to have root privileges if you want to open port 80. In this example, I started the service with the sudo command. + +In addition, in Linux, you will be able to activate the app automatically by making the *init.d* script. In Windows, this is possible by registering the startup. Because there are many articles on the internet about how to start the service automatically at OS boot time, I won't need to go into detail here. + +The next statement shows the Stop Command for stopping the TreeFrog service. + +``` + $ sudo treefrog -k stop [application_root_path] +``` + +### Release Mode Building on CI tools +Using CI tools to build the source code on release mode or test as close to the production environment as possible is one of the most effective ways to do. + +An example usage: +```yaml +# GitHub Action +# .github/workflows/c-cpp.yml +name: C/C++ CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + + # build treefrog + - name: dependency install + run: sudo apt-get install -y qt5-default qt5-qmake libqt5sql5-mysql libqt5sql5-psql libqt5sql5-odbc libqt5sql5-sqlite libqt5core5a libqt5qml5 libqt5xml5 qtbase5-dev qtdeclarative5-dev qtbase5-dev-tools gcc g++ make cmake + - name: dependency db install + run: sudo apt-get install -y libmysqlclient-dev libpq5 libodbc1 libmongoc-dev libbson-dev + - name: download treefrog source archive + run: wget https://github.com/treefrogframework/treefrog-framework/archive/v1.30.0.tar.gz + - name: expand treefrog archive + run: tar zxvf v*.*.*.tar.gz + - name: configure treefrog + run: cd treefrog-framework-*.* && ./configure --prefix=/usr/local + - name: make treefrog + run: cd treefrog-framework-*.* && make -j4 -C src + - name: make install treefrog + run: cd treefrog-framework-*.* && sudo make install -C src + - name: make treefrog tools + run: cd treefrog-framework-*.* && make -j4 -C tools + - name: make install treefrog tools + run: cd treefrog-framework-*.* && sudo make install -C tools + - name: update share library dependency info + run: sudo ldconfig + - name: check treefrog version + run: treefrog -v + + # build project files + - name: execute qmake. + run: qmake -r "CONFIG+=release" + - name: makeing directory for build + run: mkdir build + - name: cmake + run: cd build && cmake ../ + - name: execute make + run: cd build && make + + # package project files. + - name: compressive archive + run: tar cvfz app.tar.gz config/ db/ lib/ plugin/ public/ sql/ + - name: staging + run: mkdir staging && mv app.tar.gz staging/ + - uses: actions/upload-artifact@v2 + with: + name: Package + path: staging +``` \ No newline at end of file diff --git a/docs/de/user-guide/generator/index.md b/docs/de/user-guide/generator/index.md new file mode 100644 index 000000000..e4025a250 --- /dev/null +++ b/docs/de/user-guide/generator/index.md @@ -0,0 +1,280 @@ +--- +title: Generator +page_id: "040.0" +--- + +## Generator + +In this chapter we'll take a look at the generator command with the short name: _tspawn_. + +## Generate a Skeleton + +First, we must create a skeleton of the application before we can do anything else. We'll use the name _blogapp_ again for our creation. Enter the following command from the command line (in Windows, run it from the TreeFrog Command Prompt): + +``` + $ tspawn new blogapp +``` + +When you run this command, the directory tree will then include the application root directory at the top. The configuration file (_ini_) and the project files (_pro_) will be generated, too. The directory is just one of the names you have already seen before.
    +The following items will be generated as a directory. + +- controllers +- models +- views +- helpers +- config – configuration files +- db - database file storage (SQLite) +- lib +- log – log files +- plugin +- public – static HTML files, images and JavaScript files +- script +- test +- tmp – temparary directory, such as a file upload + +## Generate a Scaffold + +The scaffold contains a basic implementation allowing CRUD operations to take place. The scaffold includes the following components: controller, model, source files for _views_, and project files (pro). Therefore, the scaffold forms a good basement on which you can start to establish your full-scale development. + +In order to generate a scaffold with the generator command _tspawn_, you need to define a table in the database in advance and to set the database information in the configuration file (_database.ini_).
    +Now, let's define a table. See the following example: + +```sql +> CREATE TABLE blog (id INTEGER PRIMARY KEY, title VARCHAR(20), body VARCHAR(200)); +``` + +If you want to use SQLite for your database, you should make the database file in the application root directory. You can set the database information in the configuration file. The generator command refers to the information that is set in the _dev_ section. + +```ini +[dev] +driverType=QMYSQL +databaseName=blogdb +hostName= +port= +userName=root +password=root +connectOptions= +``` + +
    + +**Settings List** + +
    + +
    + +| Item | Meaning | Remarks | +| -------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| driverType | Driver name | Choices are as follows:
    - QDB2: IBM DB2
    - QIBASE: Borland InterBase Driver
    - QMYSQL: MySQL Driver
    - QOCI: Oracle Call Interface Driver
    - QODBC: ODBC Driver
    - QPSQL: PostgreSQL Driver
    - QSQLITE: SQLite version 3 or above | +| databaseName | Database name | In the case of SQLite a file path must be specified.
    Example: db/blogdb | +| hostName | Host name | _localhost_ in the case of blank | +| port | Port number | The default port if blank | +| userName | User name | | +| password | Password | | +| connectOptions | Connection options | For more information see Qt documents:
    [QSqlDatabase::setConnectOptions()](http://doc.qt.io/qt-5/qsqldatabase.html){:target="\_blank"} | + +

    + +If the database driver is not included in the Qt SDK, you won't be able to access the database. If you haven't built yet, you should setup the driver. Alternatively, you can download the database driver from the [download page](http://www.treefrogframework.org/download){:target="\_blank"}, and then install it. + +When you run the generator command (after the above mentioned steps), the scaffolding will be generated. Every command should be running from the application root directory. + +``` +$ cd blogapp +$ tspawn scaffold blog +driverType: QMYSQL +databaseName: blogdb +hostName: +Database open successfully + created controllers/blogcontroller.h + created controllers/blogcontroller.cpp + : +``` + +
    +##### In brief: Define the schema in the database and make us the generator command for the scaffolding. + +Lists files and directories. + +``` +$ tree +. +├── CMakeLists.txt +├── appbase.pri +├── blogapp10.pro +├── cmake +│   ├── CacheClean.cmake +│   └── TargetCmake.cmake +├── config +│   ├── application.ini +│   ├── cache.ini +│   ├── database.ini +│   ├── development.ini +│   ├── internet_media_types.ini +│   ├── logger.ini +│   ├── mongodb.ini +│   ├── redis.ini +│   ├── routes.cfg +│   └── validation.ini +├── controllers +│   ├── CMakeLists.txt +│   ├── applicationcontroller.cpp +│   ├── applicationcontroller.h +│   ├── blogcontroller.cpp +│   ├── blogcontroller.h +│   └── controllers.pro +├── db +│   └── dbfile +├── helpers +│   ├── CMakeLists.txt +│   ├── applicationhelper.cpp +│   ├── applicationhelper.h +│   └── helpers.pro +├── lib +├── log +├── models +│   ├── CMakeLists.txt +│   ├── blogservice.cpp +│   ├── blogservice.h +│   ├── models.pro +│   ├── mongoobjects +│   ├── objects +│   │   ├── blog.cpp +│   │   └── blog.h +│   └── sqlobjects +│   └── blogobject.h +├── plugin +├── public +│   ├── css +│   ├── images +│   └── js +├── script +├── sql +├── test +├── tmp +└── views + ├── CMakeLists.txt + ├── _src + │   └── _src.pro + ├── blog + │   ├── create.erb + │   ├── index.erb + │   ├── save.erb + │   └── show.erb + ├── layouts + ├── mailer + ├── partial + └── views.pro +``` + +### Relationship of Model-Name/Controller-Name and Table Name + +The generator will create class names determined on the basis of the table name. The rules are as follows: + +``` + Table name Model name Controller name Sql object name + blog_entry → BlogEntry BlogEntryController BlogEntryObject +``` + +Notice that when the underscore is removed, the next character is capitalized. You may completely ignore any distinction between singular and plural word forms. + +## Generator Sub-Commands + +Here are the usage rules for the tspawn command: + +``` +$ tspawn -h +usage: tspawn [args] + +Type 'tspawn --show-drivers' to show all the available database drivers for Qt. +Type 'tspawn --show-driver-path' to show the path of database drivers for Qt. +Type 'tspawn --show-tables' to show all tables to user in the setting of 'dev'. +Type 'tspawn --show-collections' to show all collections in the MongoDB. + +Available subcommands: + new (n) + scaffold (s) [model-name] + controller (c) action [action ...] + model (m) [model-name] + helper (h) + usermodel (u) [username password [model-name]] + sqlobject (o) [model-name] + mongoscaffold (ms) + mongomodel (mm) + websocket (w) + api (a) + validator (v) + mailer (l) action [action ...] + delete (d) +``` + +If you specify "controller", "model", or "sqlobject" as a sub-command, you can generate ONLY "controller", "model" and "SqlObject". + +### Column + +TreeFrog has no migration feature or other mechanism for making changes to and differential management of the DB schema. Therefore, using a [migration](https://en.wikipedia.org/wiki/Schema_migration) tool for DB schema is recommended if necessary. + +## Naming Conventions + +TreeFrog has a class naming and file naming convention. With the generator, class or file names are generated under the following terms and conditions. + +#### Convention for Naming of Controllers + +The class name of the controller is "table name + Controller". The controller's class name always begins with an upper-case letter, do not use the underscore ('\_') to separate words, but capitalize the first letter (_camelcase_) after where the separator would be.
    +The following class names are good examples to understand the here described convention: + +- BlogController +- EntryCommentController + +These files are stored in the controller's directory. File names inside the that folder will be all in lowercase; the class name plus the relevant extension (.cpp or .h). + +#### Conventions for Naming Models + +In the same manner as with the controller, model names should always begin with a capital letter, erase the underscore ('\_') to separate words but capitalize the first letter after where the separator would be. For example, class names such as the following: + +- Blog +- EntryComment + +These files are stored in the models directory. As well as the controller, the file name will be all in lowercase. The model name is used plus the file extension (.cpp or .h). +Unlike in Rails, we don't use convertion of singular and plural form of words here. + +#### View Naming Conventions + +Template files are generated with the file name "action name + extension" all in lower case, in the 'views/controller name" directory. The extension, which is used here, depends on the template system. +Also, when you build the view and then output the source file in _views/\_src_ directory, you will notice that these files have been all converted to C++ code templates. When these files are compiled, a shared library view will be created, too. + +#### CRUD + +CRUD covers the four major functions found in a Web application. The name comes from taking the initial letters of "Create (generate)," "Read (Read)", "Update (update)", and "Delete (Delete)". +When you create a scaffolding, the generator command generates the naming code as follows: + +
    + +**CRUD Correspondence Table** + +
    + +
    + +| | Action | Model | ORM | SQL | +| --- | ------------- | ----------------------------------- | -------- | ------ | +| C | create | create() [static]
    create() | create() | INSERT | +| R | index
    show | get() [static]
    getAll() [static] | find() | SELECT | +| U | save | save()
    update() | update() | UPDATE | +| D | remove | remove() | remove() | DELETE | + +

    + +## About the T_CONTROLLER_EXPORT Macro + +The controller class that you have created in the generator, will have added a macro called T_CONTROLLER_EXPORT. + +In Windows, the controller is a single DLL file, but in order to the classes and functions of these available from outside, we need to define it with a keyword \_\_declspec called (dllexport). The T_CONTROLLER_EXPORT macro is then replaced with this keyword.
    +However, nothing is defined in the T_CONTROLLER_EXPORT in Linux and Mac OS X installations, because a keyword is unnecessary. + +``` + #define T_CONTROLLER_EXPORT +``` + +In this way, the same source code is used and you are thus able to support multiple platforms. diff --git a/docs/de/user-guide/helper-reference/authentication.md b/docs/de/user-guide/helper-reference/authentication.md new file mode 100644 index 000000000..935b78724 --- /dev/null +++ b/docs/de/user-guide/helper-reference/authentication.md @@ -0,0 +1,194 @@ +--- +title: Authentication +page_id: "080.020" +--- + +## Authentication + +TreeFrog offers a concise authentication mechanism.
    +In order to implement the functionality of authentication, you need to create a model class that represents the "user" first. Here, we will try to make a user class containing the properties *username* and *password*. +We'll define a table as follows: + +```sql + > CREATE TABLE user ( username VARCHAR(128) PRIMARY KEY, password VARCHAR(128) ); +``` + +Then navigate to the application root directory and create a model class by using the generator command: + +``` + $ tspawn usermodel user + created models/sqlobjects/userobject.h + created models/user.h + created models/user.cpp + created models/models.pro +``` + +By specifying the 'usermodel' option (or you can use 'u' option), the user model class, that inherits from TAbstractUser class, will be created. + +The field names of user name and password of the user model class are 'username' and 'password' and they are created by default, but, of course, you can change them. For example, in case you want to define a schema using the field names 'user_id' and 'pass' instead, use the generator command as follows: + +``` + $ tspawn usermodel user user_id pass +``` + +- You just simply need to add those field names at the end of the command. + +Unlike a normal class model, an authentication method, such as the following, has been added to the user model class. This method is used to authenticate the user's name. For this purpose, the user name is set the key for retriving the user data object. Then the authentication method reads the user object, compares the password, and only if it matches, it returns the correct model object. + +```c++ +User User::authenticate(const QString &username, const QString &password) +{ + if (username.isEmpty() || password.isEmpty()) + return User(); + + TSqlORMapper mapper; + UserObject obj = mapper.findFirst(TCriteria(UserObject::Username, username)); + if (obj.isNull() || obj.password != password) { + obj.clear(); + } + return User(obj); +} +``` + +Please ensure that any modifications are based on the code above.
    +There may be cases where the authentication process is left to an external system. Also there may be cases when a password values need to be saved in md5. + +## Login + +Let's create a controller that does the login/logout process. In this example, we will create an AccountController class with three actions: *form*, *login*, and *logout*.
    +The following code shows how to achieve this: + +``` + > tspawn controller account form login logout + created controllers/accountcontroller.h + created controllers/accountcontroller.cpp + created controllers/controllers.pro +``` + +As a result, a skeleton code is generated.
    +In the form action, we can display the login form in the view. + +```c++ +void AccountController::form() +{ + userLogout(); // forcibly logged out + render(); // shows form view +} +``` + +In this example, we simply display the form, but if you have already logged in, it is possible/necessary to redirect to a different screen. The response can be tailored to your requirements. + +Now, we'll create a view of the login form using the view file *views/account/form.erb*. Here, login action is the place for the login form to be posted. + +``` + + + + + + +

    Login Form

    +
    <%==$message %>
    + <%== formTag(urla("login")); %> +
    + User Name: +
    +
    + Password: +
    +
    + +
    + + + +``` + +In the login action, you can write the authentication process that is executed when a user name and a password are posted. Once the authentication was successful, call the userLogin() method and then let the user login to the system. + +```c++ +void AccountController::login() +{ + QString username = httpRequest().formItemValue("username"); + QString password = httpRequest().formItemValue("password"); + + User user = User::authenticate(username, password); + if (!user.isNull()) { + userLogin(&user); + redirect(QUrl(...)); + } else { + QString message = "Login failed"; + texport(message); + render("form"); + } +} +``` + +- If you do not include the *user.h* file it would cause a compile-time error. + +That completes the login process.
    +Although not included in the code above, it is recommended to call the userLogin() method once the user is logged in order to check for any duplicate login(s). Checking the return value (bool) is therefore advised. + +After calling the userLogin() method, the return value of identityKey() method of user model is stored into the session. By default, a user name is stored. + +```c++ + QString identityKey() const { return username(); } +``` + +You can modify the return value, which should be unique in your system. For example, you can let return a primary key or ID and then can get the value by calling the identityKeyOfLoginUser() method. + +## Logout + +To log out, all you need to do is simply to call the userLogout() method in the action. + +```c++ +void AccountController::logout() +{ + userLogout(); + redirect(url("Account", "form")); // redirect to a login form +} +``` + +## Checking Logging In + +If you want to prevent access from users who are not logged, you can override the preFilter() of controller. Write that process there. + +```c++ +bool HogeController::preFilter() +{ + if (!isUserLoggedIn()) { + redirect( ... ); + return false; + } + return true; +} +``` + +When the preFilter() method returns *false*, the action will not be processed after this.
    +If you would like to restrict access in many controllers, you can set it to preFilter() of the ApplicationController class. + +## Getting the Logged-in User + +First of all, we need to get an instance of the logged-in user. You can get the identity information of the logged-in user by the identityKeyOfLoginUser() method. If the return value is empty, it indicates that nobody is logging in the session; otherwise the value is a user name from type string by default. + +Next, define a getter method in user model class that returns the key. + +```c++ +User User::getByIdentityKey(const QString &username) +{ + TSqlORMapper mapper; + TCriteria cri(UserObject::Username, username); + return User(mapper.findFirst(cri)); +} +``` + +In the controller, use the following code: + +```c++ +QString username = identityKeyOfLoginUser(); +User loginUser = User::getByIdentityKey(username); +``` + +### Additional Comment + +The implementations of login that are described here in this chapter all using the session. Therefore, the lifetime of the session will be simultaneously the lifetime of the login. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/cache.md b/docs/de/user-guide/helper-reference/cache.md new file mode 100644 index 000000000..c0ebe432b --- /dev/null +++ b/docs/de/user-guide/helper-reference/cache.md @@ -0,0 +1,71 @@ +--- +title: Cache +page_id: "080.035" +--- + +## Cache + +In a Web application, every time a user requests, a process such as querying the database and rendering a template is performed to generate HTML data. Many requests have little overhead, but large sites receiving a large number of requests might not be able to ignore them. You don't want to do a lot of work that takes a few seconds, even on a small site. In such a case, the processed data is cached, and the cached data can be reused when the same request is received. Caching leads to a reduction in overhead, and you can expect a quick response. + +However, there is also the danger that old data will continue to be showed by reusing cached data. When data is updated, you need to consider how to handle its associated cached data. Do you need to update the cached data immediately, or is it OK to keep showing old data until the timeout expires? Consider the implementation depending on the nature of the data showed. + +Be careful when using cache on sites that have different pages for each user, for example, pages that display private information. If you cache page data, cache the data using a different string for each user as a key. If the key strings are not set uniquely, pages of other unrelated users will be displayed. Alternatively, when caching in such cases, it may be safe and effective to limit the data to common data for all users. + + +## Enable cache module + +Uncomment the Cache.SettingsFile in the application.ini file, and set the value of the cache backend parameter 'Cache.Backend'. +``` +Cache.SettingsFile=cache.ini + +Cache.Backend=sqlite +``` +In this example, set up SQLite. You can also configure MongoDB or Redis as other usable backends. + +Next, edit cache.ini and set the connection information to the database. By default, it looks like this. Edit as necessary. +``` +[sqlite] +DatabaseName=tmp/cachedb +HostName= +Port= +UserName= +Password= +ConnectOptions= +PostOpenStatements=PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL; VACUUM; +``` + +If you set up MongoDB or Redis on the backend, set the connection information as appropriate. + +## Page caching + +Generated HTML data can be cached. + +To generate HTML data, the action name was specified for the render () function. To use the page cache, specify two parameters of a key and a time for the renderOnCache () function. To send cached HTML data, use the renderOnCache () function. + +As an example, to cache the HTML data of the "index" view with the key "index" for 10 seconds, and to do the cached HTML data, do the following: +``` + if (! renderOnCache("index")) { + : + : // get data.. + : + renderAndCache("index", 10, "index"); + } +``` + +If executed by the index action, the third argument "index" can be omitted. +See the [API Reference](http://api-reference.treefrogframework.org/classTActionController.html){:target="_blank"} for details. + +## Data caching + +You may not want to cache only pages. Binaries and text can be cached. +Use the TCache class to store, retrieve, and delete data. + +``` + Tf::cache()->set("key", "value", 10); + : + : + auto data = Tf::cache()->get("key"); + : +``` + +See [API Reference](http://api-reference.treefrogframework.org/classTCache.html){:target="_blank"} for other methods. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/file-upload.md b/docs/de/user-guide/helper-reference/file-upload.md new file mode 100644 index 000000000..1da31d48f --- /dev/null +++ b/docs/de/user-guide/helper-reference/file-upload.md @@ -0,0 +1,75 @@ +--- +title: File Upload +page_id: "080.010" +--- + +## File Upload + +In this chapter we will create a simple form for a file upload. The code example below is using ERB. By specifying *true* for the third argument of the formTag() method, a form will be generated as multipart/form-data. + +``` +<%== formTag(urla("upload"), Tf::Post, true) %> +

    + File: +

    +

    + +

    + +``` + +In this example, the destination of the file upload is the upload action in the same controller. + +The upload file that is being received in action can be renamed as well by using the following method: + +```c++ +TMultipartFormData &formdata = httpRequest().multipartFormData(); +formdata.renameUploadedFile("picture", "dest_path"); +``` + +The uploaded file is treated as a temporary file. If you do rename the upload file, the file is automatically deleted after the action ends.
    +The original file name can be obtained using the following method: + +```c++ +QString origname = formdata.originalFileName("picture"); +``` + +It may not be much used, but you can get a temporary file name just after uploading by using the uploadedFilePath() method. Random file names have been attached to make sure that they do not overlap. + +```c++ +QString upfile = formdata.uploadedFilePath("picture"); +``` + +## Upload a variable number of files + +TreeFrog Framework also supports uploading a variable number of files. You can upload a variable number files provided that you use the Javascript library. Here, I'll explain an easier way of uploading two files (or more). + +First we create a form as follows: + +``` +<%== formTag(urla("upload"), Tf::Post, true) %> +

    + File1: + File2: +

    +

    + +

    + +``` + +When creating an input tag with JavaScript dynamically, it is important to add "[]" at the end of the 'name' like name = "picture []". + +To receive the uploaded files in the upload action, you can access the two files through the TMimeEntity object as follows: + +```c++ +QList lst = httpRequest().multipartFormData().entityList( "picture[]" ); +for (QListIterator it(lst); it.hasNext(); ) { + TMimeEntity e = it.next(); + QString oname = e.originalFileName(); // original name + e.renameUploadedFile("dst_path.."); // rename file + : +} +``` + +Don't forget to use the Iterator here for that. diff --git a/docs/de/user-guide/helper-reference/image-manipulation.md b/docs/de/user-guide/helper-reference/image-manipulation.md new file mode 100644 index 000000000..4318f47b1 --- /dev/null +++ b/docs/de/user-guide/helper-reference/image-manipulation.md @@ -0,0 +1,85 @@ +--- +title: Image Manipulation +page_id: "080.070" +--- + +## Image Manipulation + +There are various libraries that are capable of image processing. If you need to do complicated image processing, you may need to use [OpenCV](http://opencv.org/){:target="_blank"}, but in this chapter I would like to explain about image manipulation using the Qt library. It's easy to use. Since Qt is the base library of the TreeFrog Framework, it is ready to provide a GUI tool kit including many useful functions for image processing. + +At first, it is necessary to activate the QtGUI module of the TreeFrog framework.
    +For this purpose, let's recompile the framework. + +In the case of Linux / Mac OS X : + +``` + $ ./configure --enable-gui-mod + $ cd src + $ make + $ sudo make install + $ cd ../tools + $ make + $ sudo make install +``` + +In the case of Windows : + +``` + > configure --enable-debug --enable-gui-mod + > cd src + > nmake install + > cd ..\tools + > nmake install + > cd .. + > configure --enable-gui-mod + > cd src + > nmake install + > cd ..\tools + > nmake install +``` + +Next, this setting is also required for the Web application side. Edit the project file (.pro), add "gui" in the variable with the name *QT*. + +``` + : + QT += network sql gui + : +``` + +In this setting, the app is built as a GUI application. For Linux, particularly, X Window System is required to implement the environment.If you cannot meet this requirement, it is recommended that you use OpenCV as your image processing library. + +## Resize the Image + +The following code is an example of how to save a JPEG image by converting to the QVGA size while keeping the aspect ratio: + +```c++ +QImage img = QImage("src.jpg").scaled(320, 240, Qt::KeepAspectRatio); +img.save("qvga.jpg"); +``` + +- In reality, use the absolute path as a the file path. + +Using this QImage class, you can convert while ignoring aspect ratio. Also you can convert to a different image format. Please see [Qt Document](https://doc.qt.io/qt-5/){:target="_blank"} for detail. + +## Rotation of the Image + +The following code is an example of rotating an image clockwise through 90 degrees. + +```c++ +QImage img("src.jpg"); +QImage rotatedImg = img.transformed(QMatrix().rotate(90.0)); +rotatedImg.save("rotated.jpg"); +``` + +## Image Synthesis + +Let's superimpose two images. You can align the small image to the coordinates of the upper right corner in the big picture. + +```c++ +QImage background("back.jpg"); +QPainter painter(&background); +painter.drawImage(0, 0, QImage("small.jpg")); +background.save("composition.jpg"); +``` + +You can then prepare a painter to the original image, and draw a different picture there. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/index.md b/docs/de/user-guide/helper-reference/index.md new file mode 100644 index 000000000..4a7d25ea7 --- /dev/null +++ b/docs/de/user-guide/helper-reference/index.md @@ -0,0 +1,19 @@ +--- +title: Helper Reference +page_id: "080.0" +--- + +## Helper Reference + +An helper is a helping function or class to complement the treatment. In TreeFrog Framwork there are many helpers, so that by using them, code can be simple and easy to understand. The following helpers are examples of their kind: + +* User authentication +* Form validation +* Mailer (Sending mail) +* Access to the upload file +* Logging +* Access control of users + +If you make a helper as a class, it can be basically a class without a state (as per object-oriented). In a class with a state, the general case is defined as a model since it persists in the DB. + +In addition, for a similar piece of logic which appears several times in the controller and the view, you should consider whether it is worth cutting it out as a helper. Please see [this chapter](/en/user-guide/helper-reference/making-original-helper.html){:target="_target"} if you want to create a helper on your own. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/logging.md b/docs/de/user-guide/helper-reference/logging.md new file mode 100644 index 000000000..aba3f16cd --- /dev/null +++ b/docs/de/user-guide/helper-reference/logging.md @@ -0,0 +1,84 @@ +--- +title: Logging +page_id: "080.050" +--- + +## Logging + +Your Web application will log four outputs as follow: + +
    + +| Log | File Name | Content | +|--------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| App log | app.log | Logging of Web application. Developers output will be logged here. See below about the output method. | +| Access log | access.log | Logging access from the browser. Including access to a static file. | +| TreeFrog log | treefrog.log | Logging of the TreeFrog system. System outputs, such as errors, are logged here. | +| Query log | query.log | Query log issued to the database. Specify the file name in the file value of SqlQueryLog in the configuration file. When stopping the output, flush it. Because there is overhead when the log is outputting. It is a good idea to stop the output when you operate a formal Web application. | + +

    + +## Output of the Application Log + +The application log is used for logging your Web application. There are several types of methods that you can use to output the application log: + +* tFatal() +* tError() +* tWarn() +* tInfo() +* tDebug() +* tTrace() + +Arguments that can be passed here are the same as the printf-format of format string and a variable number. For example, like this: + +```c++ +tError("Invalid Parameter : value : %d", value); +``` + +Then, the following log will be output to the *log/app.log* file: + +``` + 2011-04-01 21:06:04 ERROR [12345678] Invalid Parameter : value : -1 +``` + +Line feed code is not required at the end of the format string. + +## Changing Log Layout + +It is possible to change the layout of the log output, by setting FileLogger.Layout parameters in the configuration file *logger.ini*. + +```ini +# Specify the layout of FileLogger. +# %d : date-time +# %p : priority (lowercase) +# %P : priority (uppercase) +# %t : thread ID (dec) +# %T : thread ID (hex) +# %i : PID (dec) +# %I : PID (hex) +# %m : log message +# %n : newline code +FileLogger.Layout="%d %5P [%t] %m%n" +``` + +When a log was generated, date and time will be inserted there and tagged with '%d' in the log layout.
    +The date format is specified in the FileLogger.DateTimeFormat parameter. The format that can be specified is the same value as the argument of QDateTime::toString(). Please refer to the [Qt document](http://doc.qt.io/qt-5/qdatetime.html){:target="_blank"} for further detail. + +```ini +# Specify the date-time format of FileLogger, see also QDateTime +# class reference. +FileLogger.DateTimeFormat="yyyy-MM-dd hh:mm:ss" +``` + +## Changing the Logging Level + +You can set the log output level using the following parameter in *logger.ini*: + +```ini +# Outputs the logs of equal or higher priority than this. +FileLogger.Threshold=debug +``` + +In this example, the log level is higher than debug. + +##### In brief: Using the tDebug() function to output the debug log (necessary for development). diff --git a/docs/de/user-guide/helper-reference/mailer.md b/docs/de/user-guide/helper-reference/mailer.md new file mode 100644 index 000000000..ea50c57d1 --- /dev/null +++ b/docs/de/user-guide/helper-reference/mailer.md @@ -0,0 +1,107 @@ +--- +title: Mailer +page_id: "080.040" +--- + +## Mailer + +TreeFrog Framework incorporates a mailer (mail client) which makes it possible to send mails by SMTP. For now (v1.0), only SMTP message sending is possible. To create mail messages, an ERB template is used. + +First, let's create a mail skeleton with the following command: + +``` + $ tspawn mailer information send + created controllers/informationmailer.h + created controllers/informationmailer.cpp + created controllers/controllers.pro + created views/mailer/mail.erb +``` + +The class of InformationMailer is created in the controller directory, and the template with the name *mail.erb* is created in the views directory. + +Next, open the *mail.erb* that was previously created and then change its content to the following: + +``` + Subject: Test Mail + To: <%==$ to %> + From: foo@example.com + + Hi, + This is a test mail. +``` + +Above the blank line is the mail header, and below is the content of the body. Specify subject and destination in the header. It is possible to add any field of header. However, Content-Type and Date field are automatically added, so that you don't need to write them there. + +If you are using multi-byte characters, such as Japanese, save the file by setting the encoding (default UTF-8) in the settings file of InternalEncoding. + +Call the deliver() method at the end of the send() method of the InformationMailer class. + +```c++ +void InformationMailer::send() +{ + QString to = "sample@example.com"; + texport(to); + deliver("mail"); // ← mail.erb Mail sent by template +} +``` + +You are now able to call from outside of the class. By writing the following code in the action, the process of mail sending will be executed: + +```c++ +InformationMailer().send(); +``` + +- When you actually send a mail, please see the following "SMTP Settings" section. + +When you need to send a mail directly without using a template, you can use the TSmtpMailer::send() method. + +## SMTP Settings + +There is no configuration information for SMTP in the code above. It becomes necessary to set information about SMTP in the *application.ini* file. + +```ini +# Specify the connection's host name or IP address. +ActionMailer.smtp.HostName=smtp.example.com + +# Specify the connection's port number. +ActionMailer.smtp.Port=25 + +# Enables SMTP authentication if true; disables SMTP +# authentication if false. +ActionMailer.smtp.Authentication=false + +# Specify the user name for SMTP authentication. +ActionMailer.smtp.UserName= + +# Specify the password for SMTP authentication. +ActionMailer.smtp.Password= + +# Enables the delayed delivery of email if true. If enabled, deliver() method +# only adds the email to the queue and therefore the method doesn't block. +ActionMailer.smtp.DelayedDelivery=false +``` + +If you use SMTP authentication, you need to set this: + +```ini +ActionMailer.smtp.Authentication=true +``` + +As for the authentication method, CRAM-MD5, LOGIN and PLAIN (using this priority) are mounted in a way, so that the authentication process is performed automatically. + +In this framework, SMTPS email sending is not supported. + +## Delay sending the mail + +Since mail sending by SMTP needs to pass the data through an external server, it requires time compared to the process. You can return an HTTP response before the mail sending process is executed. + +Edit the *application.ini* file as follows: + +```ini +ActionMailer.smtp.DelayedDelivery=true +``` + +By doing this, the deliver() method will be a non-blocking function of merely queuing data. The mail sending process will occur after returning an HTTP response. + +**Additional note:** +If you don't set the delay sending (in case of *false*), the deliver() method would keep blocking until the SMTP processing ends, or all ends up in an error. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/making-original-helper.md b/docs/de/user-guide/helper-reference/making-original-helper.md new file mode 100644 index 000000000..bf3de536c --- /dev/null +++ b/docs/de/user-guide/helper-reference/making-original-helper.md @@ -0,0 +1,26 @@ +--- +title: Making Original Helper +page_id: "080.090" +--- + +## Making Original Helper + +When you create a helper, it can be accessible from the model/controller/view. Please try to consider making a helper whenever similar code appears several times. + +## How to Make the helper + +You can implement the class as you like in the *helpers* directory, remember to put the charm of T_HELPER_EXPORT. Other than that you can do anything. + +```c++ +#include +class T_HELPER_EXPORT SampleHelper +{ + // Write as you like. +}; +``` + +Add the header and the source file to project files, *helpers.pro*, then run make. + +## How to Use the Helper + +Use it the same way as a normal class by including a header file. There is no particular difference compared to the way the normal class is used. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/pagination.md b/docs/de/user-guide/helper-reference/pagination.md new file mode 100644 index 000000000..478dd1406 --- /dev/null +++ b/docs/de/user-guide/helper-reference/pagination.md @@ -0,0 +1,75 @@ +--- +title: Pagination +page_id: "080.080" +--- + +## Pagination + +When data cannot be displayed on one single Web page, you might have already heard about **pagination** or **paging**. TreeFrog provides such a function which divides into multiple Web pages. This technique is very common for many web applications on the internet. + +TreeFrog provides a class for pagination with very basic functions. In order to use TreeFrog's pagination ability you need to use the *TPaginator* class. The following example - written in ERB, but similar to Otama - will show you how to use pagination. + +First of all, in the action of the *controller* class we will retrieve information about the current displaying page number by using query arguments. Then we get a list of models that correspond to that page number and pass them to the view. + +```c++ +int current = httpRequest().queryItemValue("page", "1").toInt(); +int totalCount = Blog::count(); + +// Display up to ten items per page. The argument '5' specifies the number of pages +// to show ‘around’ the current page on a pagination bar, and should be an odd number +TPaginator pager(totalCount, 10, 5); +pager.setCurrentPage(current); // Set the page number that is supposed to be displayed +texport(pager); + +// Obtain the corresponding items and add them to the view +QList blogList = Blog::getBlogs( pager.itemCountPerPage(), pager.offset() ); +texport(blogList); +render(); +``` + +Now, let's have a look at the view.
    +It is a good technique to use a partial template when you want to display page numbers.
    +In the following example, we used the passed *TPaginator* object to draw the page numbers and their respective links. For each link, the *urlq()* method creates the URL and the specified query arguments to the current action for you. + +Using template: views/partial/pagination.erb + +``` +<%#include %> +<% tfetch(TPaginator, pager); %> + + +``` + +Rendering a partial template is rather simple. Use the following *renderPartial()* method to achieve this: + +``` +<%== renderPartial("pagination") %> +``` + +The passing argument "pagination" is the name of the template you want to include. Furthermore, this template draws a list of models passed from the controller. For more details about rendering templates, please refer to the [generator](/en//user-guide/generator/index.html){:target="_blank"} which generates templates such as *index*, *show* etc. + +Next is the acquisition of the model.
    +To obtain a list of applicable models, you can issue a query with the LIMIT and OFFSET parameters from the database. Depending on your requirements, you may need to specify a WHERE or to sort the obtaining models. + +Because such SQL queries are commonly used, the following TreeFrog Framework utility function requires not much code. + +```c++ +QList Blog::getBlogs(int limit, int offset) +{ + return tfGetModelListByCriteria(TCriteria(), limit, offset); +} +``` + +Please refer the [API Reference](http://treefrogframework.org/tf_doxygen/tmodelutil_8h.html){:target="_blank"} page for more details about the model utility. + +**Note:**
    +Since pagination is not that difficult, you can also try to challenge the implementation without using the TPaginator class at all. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/plugin.md b/docs/de/user-guide/helper-reference/plugin.md new file mode 100644 index 000000000..10f289b81 --- /dev/null +++ b/docs/de/user-guide/helper-reference/plugin.md @@ -0,0 +1,132 @@ +--- +title: Plugin +page_id: "080.060" +--- + +## Plugin + +In TreeFrog, the plug-in refers to dynamic libraries (shared library, DLL) that are added to extend the functionality. Since TreeFrog has the Qt plug-in system, you can make a new plug-in, if the standard function is insufficient. At this point, the categories of possible plug-ins that can be made are as follows: + +* Logger plug-in (for log output) +* The session store plug-in (for Saving and reading sessions) + +## How to Create a Plug-in + +Create a plug-in is exactly the same as how to create a plug-in for Qt. To demonstrate this, let's create a logger plug-in. First, we'll create a working directory in the *plugin* directory + +``` + > cd plugin + > mkdir sample +``` + +- Basically, you can choose any place for the working directory, but when thinking about the path solution, it is a good idea to create the working directory as just previously mentioned above. + +We'll create a plug-in wrapper class (TLoggerPlugin in this example). We do this by inheriting the class as an interface and then we override some of the virtual functions. + +```c++ +sampleplugin.h +--- +class SamplePlugin : public TLoggerPlugin +{ + QStringList keys() const; + TLogger *create(const QString &key); +}; +``` + +In the keys() method, the string that can be the key for supporting the plug-in, is returned as a list. In the create() method, the instance of the logger that corresponds to the key is created and implemented in a way to be returned as a pointer. + +After including the QtPlugin in the source file, you can register the plug-in by putting a macro, such as the following: + +* The first argument can be any string such as the name of the plug-in +* The second argument is a plug-in class name. + +```c++ +sampleplugin.cpp +--- +#include + : + : +Q_EXPORT_PLUGIN2(samplePlugin, SamplePlugin) +``` + +Next, we create a function to extend the plug-in (class). We'll make a logger that outputs a log here. As above, we do this by inheriting the TLogger class as an interface from logger and then override some virtual functions again. + +```c++ +samplelogger.h +--- +class SampleLogger : public TLogger +{ +public: + QString key() const { return "Sample"; } + bool isMultiProcessSafe() const; + bool open(); + void close(); + bool isOpen() const; + void log(const TLog &log); + ... +}; +``` + +Our next target is the project file (.pro). Do not forget to add the value "plugin" to the CONFIG parameter in this file! + +``` +TARGET = sampleplugin +TEMPLATE = lib +CONFIG += plugin +DESTDIR = .. +include(../../appbase.pri) +HEADERS = sampleplugin.h \ + samplelogger.h +SOURCES = sampleplugin.cpp \ + samplelogger.cpp +``` + +- It is important to include the *appbase.pri* file by using the include function. + +After this, when you build you can make plug-ins that are dynamically loadable. Save the plug-in to the plugin directory every time without fault, because the application server (AP server) loads the plug-ins from this directory.
    +Please see the Qt documentation for more details of the [plug-in system](http://doc.qt.io/qt-5/plugins-howto.html){:target="_blank"}. + +## Logger Plug-in + +FileLogger is a basic logger that outputs the log inside a file. However, it may be insufficient depending on the requirements. For example, if a log is used to save as a database or as a log file that you want to keep by rotation, you may want to extend the functionality using the mechanism of the plug-in. + +As described above, after creating a logger plug-in, place the plug-in in the plug-in directory. Furthermore, update the configuration information inside the *logger.ini* file in order to be loaded into the application server. Arrange the keys of the logger in the *loggers* parameter with spaces. The following example shows how this may look like: + +``` + Loggers=FileLogger Sample +``` + +In this way, the plug-in will be loaded when you start the application server. + +Once again, the plug-in interface for the logger is a class as shown next: + +* Plug-in interface: TLoggerPlugin +* Logger interface: TLogger + +### About the Logger Methods + +In order to implement the logger, you can override the following methods in the class TLogger: + +* key(): returns the name of the logger. +* open(): open of the log called by the plug-in immediately after loading. +* close(): close the log. +* log(): output the log. This method may be called from multiple threads, make this as **thread-safe**. +* isMultiProcessSafe(): indicates whether it is safe for you to output a log in a multi-process. When it is safe, it returns true. I not, it returns as false. + +About MultiProcessSafe method: when it returns *false* (meaning it is not safe) and the application server is running in a multiprocess mode as well, the isMultiProcessSafe() method calls open()/close() each time before and after it is logging output (leads into increasing overhead).
    +By the way, this system has lock/unlock around this by semaphore, so that there is no conflict. And when you return *true*, the system will only call the open() method first. + +## Session Store Plug-in + +The session store that is standard on TreeFrog is as follows: + +* Cookies Session: save to cookies +* DB session: save to DB. You need to make the table only for this purpose. +* File session: save to file + +If these are insufficient, you can create a plug-in that inherits the interface class. + +* Plug-in interface: TSessionStorePlugin +* Session Store interface: TSessionStore + +In the same manner as described above, by overriding the virtual function by inheriting these classes, you can create a plug-in. Then in order to load this plug-in, you can set only one key at *Session.StoreType* parameter in the *application.ini* file (you can choose only one). The default is set as cookie. \ No newline at end of file diff --git a/docs/de/user-guide/helper-reference/validation.md b/docs/de/user-guide/helper-reference/validation.md new file mode 100644 index 000000000..0d149db19 --- /dev/null +++ b/docs/de/user-guide/helper-reference/validation.md @@ -0,0 +1,115 @@ +--- +title: Validation +page_id: "080.030" +--- + +## Validation + +Sometimes, data that is sent as a request may not have the format that the developer has specified. For example, some users might put letters where numbers are required. Even if you have implemented Javascript to validate on the client side, tampering with the content of a request is not complicated, so a server side system for validating content is essential. + +As mentioned in the [controller chapter]({{ site.baseurl }}/en/user-guide/controller/index.html){:target="_blank"}, data in received requests is expressed as hash format. Usually before sending the request data to the model, each value should be validated the value's format. + +First, we will generate a validation class skeleton for validating request data (hash) for *blog*. Navigate to the application root directory and then execute the following commands. + +``` + $ tspawn validator blog + created helpers/blogvalidator.h + created helpers/blogvalidator.cpp + updated helpers/helpers.pro +``` + +The validation rules are set in the construction of the generated BlogValidator class. The following example shows the implementation of a the rule for the title variable which must be made of at least 4 or more letters and must not have more than 20. + +```c++ +BlogValidator::BlogValidator() : TFormValidator() +{ + setRule("title", Tf::MinLength, 4); + setRule("title", Tf::MaxLength, 20); +} +``` + +The enum value is the second argument. You can specify mandatory input, maximum/minimum string length, integer of maximum/minimum value, date format, e-mail address format, user-defined rules (regular expression), and so on (defined in tfnamespace.h). + +There is also a fourth argument: setRule(). This sets the validation error message. If you don't specify a message (what we have done here), the message defined in *config/validation.ini* file is used. + +Rules are implicitly set for "mandatory input". If you do NOT want an input to be "mandatory", describe the validation rule as follows: + +```c++ +setRule("title", Tf::Required, false); +``` + +
    + +**Rules** + +
    + +
    + +| enum | Meaning | +|--------------|-------------------------| +| Required | Input required | +| MaxLength | Maximum length | +| MinLength | Minimum length | +| IntMax | Maximum value (integer) | +| IntMin | Minimum value (integer) | +| DoubleMax | Maximum value (double) | +| DoubleMin | Minimum value (double) | +| EmailAddress | Email address format | +| Url | URL format | +| Date | Date format | +| Time | Time format | +| DateTime | DateTime format | +| Pattern | Regular Expressions | + +

    + +Once you have set the rules, you can use them in the controller. Include the header file relating to this.
    +The following code example validates the request data that is retrieved from the form. If you get a validation error, you get the error message. + +```c++ +QVariantMap blog = httpRequest().formItems("blog"); +BlogValidator validator; +if (!validator.validate(blog)) { + // Retrieve message rules for validation error + QStringList errs = validator.errorMessages(); + : +} +``` + +Normally, since there are sets of multiple rules, there will also be multiple error messages. One-by-one processing is a little cumbersome. However, if you use the following method, you can export the all validation error messages at once (to be passed to the view): + +```c++ +exportValidationErrors(valid, "err_"); +``` + +In the second argument, specify a prefix as the variable name for the export object. + +##### In brief: Set rules for the form data and validate them by using validate(). + +## Custom validation + +The explanation above is about static validation. It cannot be used in a dynamic case where the allowable range of the value changes according to what the value is. In this case, you may override the validate() method and then you can write any code of validation whatsoever. + +The following code is an example on how a customized validation could look like: + +```c++ +bool FooValidator::validate(const QVariantMap &hash) +{ + bool ret = THashValidator::validate(hash); // ←Validation of static rules + if (ret) { + QDate startDate = hash.value("startDate").toDate(); + QDate endDate = hash.value("endDate").toDate(); + + if (endDate < startDate) { + setValidationError("error"); + return false; + } + : + : + } + return ret; +} +``` + +It compares the value of *endData* and the value of *startDate*. If the endDate is smaller than the startDate, an validation error will be thrown. \ No newline at end of file diff --git a/docs/de/user-guide/index.md b/docs/de/user-guide/index.md new file mode 100644 index 000000000..aa7d8750e --- /dev/null +++ b/docs/de/user-guide/index.md @@ -0,0 +1,5 @@ +--- +title: Document Index +layout: docindex +--- + diff --git a/docs/de/user-guide/install/index.md b/docs/de/user-guide/install/index.md new file mode 100644 index 000000000..62e1a33be --- /dev/null +++ b/docs/de/user-guide/install/index.md @@ -0,0 +1,166 @@ +--- +title: Install +page_id: "020.0" +--- + +## Install + +First of all, we need to install the Qt library in advance. + +On Windows and macOS, download the package from the [Qt site](http://qt-project.org/downloads){:target="_blank"} and install it. +On Linux, you can install a package for each distribution. + +In case of Ubuntu: +Install the Qt libraries and dev tools: + +``` + $ sudo apt-get install -y qt5-default qt5-qmake libqt5sql5-mysql libqt5sql5-psql + libqt5sql5-odbc libqt5sql5-sqlite libqt5core5a libqt5qml5 libqt5xml5 qtbase5-dev + qtdeclarative5-dev qtbase5-dev-tools gcc g++ make cmake +``` + +Now install the DB client libraries: + +``` + $ sudo apt-get install -y libmysqlclient-dev libpq5 libodbc1 libmongoc-dev libbson-dev +``` + +### Installation Instructions + +1. Extract the file you just have [downloaded](http://www.treefrogframework.org/en/download/){:target="_blank"}. + + The following command applies to version 'x.x.x'. Please update it appropriately. + + ``` + $ tar xvfz treefrog-x.x.x.tar.gz + ``` + +2. Run build commands. + + In Windows: + Please create a binary of two types for *release* and for *debugging*. + Start the Qt Command Prompt and then build with the following commands. The configuration batch should be run twice. + + ``` + > cd treefrog-x.x.x + > configure --enable-debug + > cd src + > nmake install + > cd ..\tools + > nmake install + > cd .. + > configure + > cd src + > nmake install + > cd ..\tools + > nmake install + ``` + + In UNIX-based OS Linux, and macOS: + Enter the following command from the command line: + + ``` + $ cd treefrog-x.x.x + $ ./configure + $ cd src + $ make + $ sudo make install + $ cd ../tools + $ make + $ sudo make install + ``` + + **Note:** + In order to debug the TreeFrog Framework itself, please use the *configure* option. + Now please run this command: + + ``` + ./configure --enable-debug + ``` + + The next command updates the dynamic linker runtime bindings in Linux only. + + ``` + $ sudo ldconfig + ``` + +3. Create a shortcut of TreeFrog Command Prompt (Windows only). + Right-click on the folder on which you want to create a shortcut and select "New" and then click the "Shortcut". Set the links as follows: + + ``` + C:\Windows\System32\cmd.exe /K C:\TreeFrog\x.x.x\bin\tfenv.bat + ``` + + ('x.x.x' represents the current version you use) + +
    + + ![Create Shortcut]({{ site.baseurl }}/assets/images/documentation/shortcut_en.png "Create Shortcut") + +
    + + Set the Shortcut name to 'TreeFrog Command Prompt'. + +
    + + ![Shortcut name]({{ site.baseurl }}/assets/images/documentation/shortcut-name_en.png "Shortcut name") + +
    + +### Configure Option + +By specifying various options, you can customize to suit your environment. + +Options available on Windows using "Configure option": + +``` + > configure --help + Usage: configure [OPTION]... [VAR=VALUE]... + Configuration: + -h, --help display this help and exit + --enable-debug compile with debugging information + --enable-gui-mod compile and link with QtGui module + --enable-mongo compile with MongoDB driver library + + Installation directories: + --prefix=PREFIX install files in PREFIX [C:\TreeFrog\x.x.x] +``` + +Options available on Linux, and UNIX-like OS: + +``` + $ ./configure --help + Usage: ./configure [OPTION]... [VAR=VALUE]... + Configuration: + -h, --help display this help and exit + --enable-debug compile with debugging information + --enable-gui-mod compile and link with QtGui module + --enable-mongo compile with MongoDB driver library + --spec=SPEC use SPEC as QMAKESPEC + + Installation directories: + --prefix=PREFIX install files in PREFIX [/usr] + + Fine tuning of the installation directories: + --bindir=DIR user executables [/usr/bin] + --libdir=DIR object code libraries [/usr/lib] + --includedir=DIR C header files [/usr/include/treefrog] + --datadir=DIR read-only architecture-independent data [/usr/share/treefrog] +``` + +Options available in macOS: + +``` + $ ./configure --help + Usage: ./configure [OPTION]... [VAR=VALUE]... + Configuration: + -h, --help display this help and exit + --enable-debug compile with debugging information + --enable-gui-mod compile and link with QtGui module + --enable-mongo compile with MongoDB driver library + + Fine tuning of the installation directories: + --framework=PREFIX install framework files in PREFIX [/Library/Frameworks] + --bindir=DIR user executables [/usr/bin] + --datadir=DIR read-only architecture-independent data [/usr/share/treefrog +``` diff --git a/docs/de/user-guide/introduction/index.md b/docs/de/user-guide/introduction/index.md new file mode 100644 index 000000000..83d82066c --- /dev/null +++ b/docs/de/user-guide/introduction/index.md @@ -0,0 +1,40 @@ +--- +title: Introduction +page_id: "010.0" +--- + +## Introduction + +### What is the TreeFrog Framework + +TreeFrog Framework is a full-stack Web application framework. Written in C++, it is lightweight (low resource demands), and allows extremely fast working. + +With the aim of reducing development costs while producing a C++ framework, a policy of "convention over configuration" has been followed. The configuration file has been made as small as possible. Because it provides help in automatic generation of code for template systems (scaffolding), O/R mapping and ORM, developers are free to focus on logic. + +### Cross-platform + +TreeFrog Framework is cross-platform. It runs on Windows, of course, but also on UNIX-like Operating Systems, macOS, and Linux. Using Windows open-source coding, it is possible to support Linux. Web applications that run on multiple platforms are also possible, simply by recompiling the source code. + +### Controller + +Developers will be easily able to obtain the data representing HTTP request/response, and the session. In addition to this, it has already provided same useful rough features such as login authentication, form validation, and access control. +Also, because it provides a mechanism (routing system) for call methods by the appropriate controller from the requested URL to required actions, there is no need to write rules one-by-one in the configuration file, or to distribute any action requests. + +### View + +In the view layer, ERB format descriptions, well known in Rails, may be used. The C++ code <% …%> can be embedded in HTML files. This means that coding can be written in a way very similar to a scripting language. +It also offers a new template system (Otama) that completely separates the presentation logic from the templates. Being written in pure HTML, the template is able to describe the C++ coded logic file. This enables programmers and designers to collaborate. + +### Model + +In the model layer, called SqlObject, an O/R mapper (O/R mapping system) is provided. App developers will therefore be able to focus on developing business logic without having to write too much because of this SQL. +However, that being said, if data needs to be used in complex conditions, SQL can be readily utilized. Through the use of placeholders, SQL queries may be run easily and safely. +In addition, this framework is compatible with all major database programs such as MySQL, PostgreSQL, SQLite, DB2, and Oracle. + +### Qt-based + +Qt and the TreeFrog Framework are linked. Not only is the Qt GUI framework powerful, but the non-GUI functionality is also very good. By combining Web applications for the core module container class, network, SQL, JSON, unit test, and meta-object, app developers are provided with very convenient usability features. + +### Open source + +TreeFrog Framework is open-source software, under the new BSD license (3-clause BSD License). \ No newline at end of file diff --git a/docs/de/user-guide/introduction/license.md b/docs/de/user-guide/introduction/license.md new file mode 100644 index 000000000..174a79a3e --- /dev/null +++ b/docs/de/user-guide/introduction/license.md @@ -0,0 +1,31 @@ +--- +title: License +page_id: "010.020" +--- + +## License + +### Modified BSD license (New BSD License) + +Modification, distribution or copying of source code is allowed in accordance with the license conditions below. If you modify the source code, you don't need to publish it; you can select "not publish" if you wish. + +Copyright (c) 2010-2022, AOYAMA Kazuharu +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the TreeFrog Framework Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/de/user-guide/introduction/requirements.md b/docs/de/user-guide/introduction/requirements.md new file mode 100644 index 000000000..d200ab082 --- /dev/null +++ b/docs/de/user-guide/introduction/requirements.md @@ -0,0 +1,21 @@ +--- +title: Requirements +page_id: "010.010" +--- + +## Requirements + +### Execution environment + +* Windows, Linux, UNIX-like OS or maxOS +* Qt version 5.4 or later – [The Qt Company](https://www.qt.io/) + +### Development environment + +In order to develop a Web application in TreeFrog Framework, you must have C++ development tools appropriate to the execution environment as lsited above. For the time being, development is possible using the g++ compiler to make and edit commands. + +Visual Studio is available for Windows but MinGW is not supported. Using the Qt Creator IDE is also a good idea. + +On maxOS, if you install Xcode, various commands such as a compiler are installed. For Linux, I recommend installing the packages that are provided with the distribution. + +For debugging, using the Qt Creator is a safe choice, because it is a framework based on Qt. diff --git a/docs/de/user-guide/model/access-mongodb.md b/docs/de/user-guide/model/access-mongodb.md new file mode 100644 index 000000000..fa412dd53 --- /dev/null +++ b/docs/de/user-guide/model/access-mongodb.md @@ -0,0 +1,206 @@ +--- +title: Access MongoDB +page_id: "060.050" +--- + +## Access MongoDB + +MongoDB is a document-oriented open source database. It is one of the so-called NoSQL systems. + +To manage the data in the RDB, you need to define a table (schema) in advance, but you will most likely not need to do that with MongoDB. In MongoDB, data is represented by a like JSON format (BSON) called "Documents", and the set is managed as a "collection". Each document has assigned a unique ID (ObjectID) by the system. + +Here is a comparison between the layered structures of an RDB and MongoDB. + +
    + +| MongoDB | RDB | Remarks | +|------------|----------|---------------| +| Database | Database | The same term | +| Collection | Table | | +| Document | Record | | + +

    + +## Installation + +Re-install the framework with the following command: + +``` + $ tar xvzf treefrog-x.x.x.tar.gz + $ cd treefrog-x.x.x + $ ./configure --enable-mongo + $ cd src + $ make + $ sudo make install + $ cd ../tools + $ make + $ sudo make install +``` + +- x.x.x represents the current version you have downloaded. + +## Setting Credentials + +Assume that MongoDB has being installed and the server is running. Then you can make a skeleton of the application in the generator. + +Let's set the connection information in order to communicate with the MongoDB server. First, edit the following line in *config/application.ini*. + +```ini + MongoDbSettingsFile=mongodb.ini +``` + +Then edit the *config/mongodb.ini*, specifying the host name and database name. As for the configuration file for the SQL database, this file is divided into three sections, *dev*, *test*, and *product*. + +```ini + [dev] + DatabaseName=foodb # Database name + HostName=192.168.x.x # Host name or IP address + Port= + UserName= + Password= + ConnectOptions= # unused +``` + +Now, let's check the settings by execute the following command in the application root directory *when MongoDB is running*: + +``` + $ tspawn --show-collections + DatabaseName: foodb + HostName: localhost + MongoDB opened successfully + ----------------- + Existing collections: +``` + +If it succeeds, it will be displayed like above. + +Web applications can access both MongoDB and SQL databases. This enables the Web application to respond in a flexible way in case of an increased load on the Web system. + +## Creating New Document + +To access the MongoDB server, use the *TMongoQuery* object. Specify the collection name as an argument to the constructor to create an instance. + +MongoDB document is represented by a QVariantMap object. Set the key-value pair for the object and then insert it by using the insert() method into the MongoDB at the end. + +```c++ +#include +--- +TMongoQuery mongo("blog"); // Operations on a blog collection +QVariantMap doc; + +doc["title"] = "Hello"; +doc["body"] = "Hello world."; +mongo.insert(doc); // Inserts new +``` + +Internally, a unique ObjectID is allocated when the insert() method is being called. + +### Supplement + +As this example shows, there is no need for developers to worry at all about the process of connect/disconnect with MongoDB, because the management of the connection is handled by the framework itself. Through the mechanism of re-using connections, the overhead caused by the number of connections/disconnections is kept low. + +## Reading the Document + +When you search for documents and some of them (or all) match the previously set criteria, it is necessary to pass the returned documents (if any) one by one into a QVariantMap. Please be ware, that we have to use QVariantMap here, because the search criteria is expressed as QVariantMap, too. + +The following example creates an Criteria object which contains two criteria sets and then being passed as an argument to the find() method. Assuming that there is more than one document that matches the search criteria we use the *while* statement to loop through the list of available documents. + +```c++ +TMongoQuery mongo("blog"); +QVariantMap criteria; + +criteria["title"] = "foo"; // Set the search criteria +criteria["body"] = "bar"; // Set the search criteria + +mongo.find(criteria); // Run the search +while (mongo.next()) { + QVariantMap doc = mongo.value(); // Get a document + // Do something +} +``` + +- Two criteria are joined by the AND operator. + +If you are looking for only one documents that matches the criteria, you can use the findOne() method. + +```c++ +QVariantMap doc = mongo.findOne(criteria); +``` + +The following example sets an criteria for a 'num'. Only documents those value 'num' is greater than 10 will match. In order to achieve this, use **$gt** as the comparison operator for your criteria object. + +```c++ +QVariantMap criteria; +QVariantMap gt; +gt.insert("$gt", 10); +criteria.insert("num", gt); // Set the search criteria +mongo.find(criteria); // Run the search + : +``` + +**Comparison Operators:** + +* **$gt**: Greater than +* **$gte**: Greater than or equal to +* **$lt**: Less than +* **$lte**: Less than or equal to +* **$ne**: Not equal +* **$in**: In +* **$nin**: Not in + +### OR Operator + +Joins query clauses with a logical OR **$or** operator. + +```c++ +QVariantMap criteria; +QVariantList orlst; +orlst << c1 << c2 << c3; // Three criteria +criteria.insert("$or", orlst); + : +``` + +As described above, the search condition in *TMongoQuery* is represented by an object from type *QVariantMap*. In MongoDB, the search condition is represented by JSON, so when executing a query, the QVariantMap object will be converted to a JSON object. Therefore you can specify all the operators supported by MongoDB provided that you have described them properly according to their rules. An efficient search will be then possible. + +There are more operators provided by MongoDB. Please have a look at the [MongoDB documents](http://docs.mongodb.org/manual/reference/operator/nav-query/){:target="_blank"} for an insight view. + +## Updating a Document + +We will read a document from the MongoDB server and then update it. As indicated by the update() method, we will update one document that matches the criteria. + +```c++ +TMongoQuery mongo("blog"); +QVariantMap criteria; +criteria["title"] = "foo"; // Set the search criteria +QVariantMap doc = mongo.findOne(criteria); // Get one +doc["body"] = "bar baz"; // Change the contents of the document + +criteria["_id"] = doc["_id"]; // Set ObjectID to the search criteria +mongo.update(criteria, doc); +``` + +It is important to note here, that even if there are several documents matching the search criteria, but in order to be sure the the document can be updated, add the *ObjectID* to the search criteria. + +In addition, if you want to update all documents that match the criteria, you can use the updateMulti() method. + +```c++ +mongo.updateMulti(criteria, doc); +``` + +## Removing a Document + +Specify the object ID as a condition If you want to delete one document. + +```c++ +criteria["_id"] = "517b4909c6efa89aed288706"; // Removes by ObjectID +mongo.remove(criteria); +``` + +You can also remove all documents that match the criteria. + +```c++ +TMongoQuery mongo("blog"); +QVariantMap criteria; +criteria["foo"] = "bar"; +mongo.remove(criteria); // Remove +``` diff --git a/docs/de/user-guide/model/index.md b/docs/de/user-guide/model/index.md new file mode 100644 index 000000000..80adc7a05 --- /dev/null +++ b/docs/de/user-guide/model/index.md @@ -0,0 +1,113 @@ +--- +title: Model +page_id: "060.0" +--- + +## Model + +The model is an object that represents the (abstract) information that should be returned to the browser. In fact it is not so simple in terms of business logic. So, let's try to understand it. + +Models are stored in a system or in an external database in a persistent state. Looking from the controller side, regardless of the model, you are accessing a database for information wanted for the HTTP response. Once the information has been left in a persistent state in the database, you will be able to get a model with information that you can pass to the view. + +When you write directly to SQL to access the database, coding tends to become complicated in some cases and therefore more difficult to read. This is even truer if the DB schema becomes complicated. In order to mitigate this is difficulty, TreeFrog is equipped with an O/R mapping system (named SqlObject). + +In a Web application, CRUD (create, read, update, delete) is a set of functions with minimum requirements; the SQL statement you write is almost routine. For this part, the O/R mapping system will be working here very effective. + +I personally think, as for the Web frameworks, it is recommended to have the model object itself related to an object for O/R mapping (these objects will be referred as ORM objects). By default, each model object in TreeFrog Framework includes ORM objects. + + "A model has ORM object(s)." + +Doing it this way is beneficial in a number of ways: + +* Intuitive structure of the ORM class. +* Possible to conceal the information that is not required to show on the controller side. +* Can absorb both logical delete and physical delete of the record as a model. + → Use this page to create a deletion flag column, it is then only necessary to update it in the model remove() method. +* Since there is no need for a relationship table since the model is one-to-one, the degree of freedom in design model class is greater. + → Business logic can be added to the model in a natural way. + +The disadvantage is that the amount of code increases a bit. + +##### In brief: Hide unnecessary information to the controller and the view. + +## API of Model + +In general, when classes are more independent they are also more reusable. It would therefore be desirable to let dependency of the model to be as small as possible. + +In Web applications, the DB is often used for store data in a persisting state, so that "model class" relates to "table". Web applications that must handle many kinds of data have to create many tables (models) accordingly. Because it is common when designing a DB schema to conduct data normalization, models will have a relationship with each other through their properties (fields). + +When coding the class of models, the following conventions are in place. These should be learnt. + +Use the texport() method to pass arguments to the view. +This is equal to set to a variable from type QVariant (by using the setValue() method) in the following classes:  + + - public default constructor + - public copy constructor + - public destructor + - Declaration in Q_DECLARE_METATYPE macro (At the end of the header per file) + +Please read the [Qt documentation](http://doc.qt.io/qt-5/qmetatype.html){:target="_blank"} if you want to learn more about it. + +##### ★ A model that is created by the generator command already meets the minimum requirements to work. + +The model class that is created in the generator is inherited from the TAbstractModel class. I have inherited it in order to take advantage of its features. Additionally, convenient methods for handling ORM objects will be available, too. Basically, the inheritance is merely for reusability. Those models that don't access the database at all, don't need to deal with inheritance though. + +##### In brief: if you want to use an ORM object, you should inherit from the TAbstractModel class regardlessly. + +When the model is created by the generator, the getter/setter of each property and the class methods, that are equivalent to "create" and "read", are defined. The following example is an excerpt of the Blog class which we made in the [tutorial chapter]({{ site.baseurl }}/en/user-guide/tutorial/index.html){:target="_blank"}. + +```c++ +static Blog create(const QString &title, const QString &body); +static Blog create(const QVariantMap &values); +static Blog get(int id); // Get the model object with a specified ID +static Blog get(int id, int lockRevision); +static QList getAll(); // Get all model objects +``` + +When you run the create() method, the content of the object is stored in the database during its creation. + +Let's also look at the methods defined in TAbstractModel class: + +```c++ +virtual bool create(); // New +virtual bool save(); // Save (New or Updated) +virtual bool update(); // Update +virtual bool remove(); // Remove +virtual bool isNull() const; // Whether present in the DB +virtual bool isNew() const; // Whether before saving to DB +virtual bool isSaved() const; // Whether stored in the DB +void setProperties(const QVariantMap &properties); +``` + +The save() method internally calls the create() method if the ORM object doesn't already exist, or the update() method if it does exist. So, if you don't want to distinguish between the create() and update() method, then you can simply use the save() method to call the model. + +The code which is generated here is only the tip of the iceberg. You can add or modify the property by shifting, for example, from *protected* to *private* or whatever you like. + +## Creating a Model with a Different Name to the Table Name + +When you create a model using the generator command, the model name are derived from the table name in this format '_' (underscore).
    +If you want to give an individual model a different name with this format, you can deal with it using the command with a string at the end such as follows: + +``` + $ tspawn model blog_entries BlogEntry ← only model created + $ tspawn s blog_entries BlogEntry ← model-view-controller created +``` + +## Creating an Original Model + +You don't necessarily have to associate a model with the table. It can also be used to summarize relevant data in the case of passing information to the view. + +If you want to create a model on its own without the use of a generator, you should declare a class as shown in the following example: + +```c++ +class T_MODEL_EXPORT Post +{ + public: + // include default constructor, copy constructor, destructor + // write the code freely afterward. +}; +Q_DECLARE_METATYPE(Post) // charm to pass to view +Q_DECLARE_METATYPE(QList) // charm to pass the list to view +``` + +Save it in the models directory, add files to the project (*models.pro*), and specify the file name of the source and header. diff --git a/docs/de/user-guide/model/object-document-mapping-on-mongodb.md b/docs/de/user-guide/model/object-document-mapping-on-mongodb.md new file mode 100644 index 000000000..57f055f20 --- /dev/null +++ b/docs/de/user-guide/model/object-document-mapping-on-mongodb.md @@ -0,0 +1,156 @@ +--- +title: Object-Document Mapping in MongoDB +page_id: "060.060" +--- + +## Object-Document Mapping in MongoDB + +MongoDB expresses data to be saved in a JSON-like format and saves it as a document. The function of associating such a document with an object in a programming language is called **object-document mapping (O/D mapping)**. + +As in the [O/R mapping](/en/user-guide/model/or-mapping.html){:target="_blank"} chapter described, one document here is also associated with one object in the O/D mapping. + +Since MongoDB documents are JSON-like formats, it is possible to have a hierarchical (nested) structure, but in O/D mapping, an object doesn't correspond to two or more levels of documents. For example, the following example shows the only supported simple form in O/D mapping: + +```json +{ + "name": "John Smith", + "age": 20, + "email": "foo@example.com" +} +``` + +## Setup + +If you are not sure anymore how to setup a MongoDB connection properly, please refer to the [Access MongoDB](/en/user-guide/model/access-mongodb.html){:target="_blank"} chapter. + +For generating the class for O/D mapping, execute the following command in the application root directory. In this example, we create a collection named *foo*. The name of the model will be *Foo*. + +``` + $ tspawn mm foo + created models/mongoobjects/fooobject.h + created models/foo.h + created models/foo.cpp + updated models/models.pro +``` + +The next step is to define the data to be stored in the document. Then we edit the c++ header file *models/mongoobjects/fooobject.h* by adding the QString variables *title* and *body*. + +```c++ +class T_MODEL_EXPORT FooObject : public TMongoObject, public QSharedData +{ +public: + QString title; // ← Add here + QString body; // ← Add here + QString _id; + QDateTime createdAt; + QDateTime updatedAt; + int lockRevision; + enum PropertyIndex { + Id = 0, + CreatedAt, + UpdatedAt, + LockRevision, + }; + : +``` + +Variables other than **_id** are not mandatory, so you can delete them. The variable *_id* is equivalent to the ObjectID in MongoDB, therefore please **don't** delete it. + +- This object is responsible for accessing MongoDB, which is why we call it "Mongo object" from the time being. + +Execute the following command again in order to reflect the added contents to other files. + +``` + $ tspawn mm foo +``` + +Type 'Y' for all files with changes.
    +This completes the model with [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete){:target="_blank"}. + +If you want to generate scaffolds including *controllers* and *views*, you can execute the following command instead of 'tspawn mm foo'. + +``` + $ tspawn ms foo +``` + +Now, scaffolding has been generated. After compiling, try running the AP server. + +``` + $ treefrog -d -e dev +``` + +By accessing *http://localhost:8800/foo/* in the browser, a screen which contains a list will be displayed. Use this screen as a starting point for registering, editing and deleting data. + +As you can see, this file is similar to the migration file (in Rails), because you automatically define/change the layout of the Mongo document by editing the class of the Mongo object. + +## Read a Mongo object + +Let's see how a Mongo object, which is referring to the class created by scaffolding, can be read. We do this by loading a Mongo object by using the object ID as a key. + +```c++ +QString id; +id = ... +TMongoODMapper mapper; +FooObject foo = mapper.findByObjectId(id)); +``` + +## Create a Mongo object + +Make it the same way as instantiating ordinary objects by setting properties. After this is done, calling the *create()* method will create a new document in the MongoDB for you. + +```c++ +FooObject foo; +foo.title = ... +foo.body = ... +foo.create(); +``` + +Since the object ID is generated automatically, please don't set anything. + +## Update a Mongo object + +Obtain the Mongo object, for example, by calling its object ID and set a new value. When this is done, the *update()* method will eventually update the object in the MongoDB internally. + +```c++ +TMongoODMapper mapper; +FooObject foo = mapper.findByObjectId(id)); +foo.title = ... +foo.update(); +``` + +There is also a *save()* method for saving the document.
    +This calls *create()* method **if the corresponding document does not exist** in the MongoDB, and then the *update()* method **if it exists**. + +## Delete a Mongo object + +Deleting a Mongo object deletes the document. Use the *remove()* method to remove the object. + +```c++ +TMongoODMapper mapper; +FooObject foo = mapper.findByObjectId(id)); +foo.remove(); +``` + +##### Supplement + +As mentioned above, Mongo objects can be used in the same way as ORM objects (O/R mapper objects). Looking from the controller's point of view, since the functions provided by the model class are the same, there is no difference in their use. These objects are hidden in the 'private' area of the model. + +In other words, if you define model class names which do not overlap, you can even have access to MongoDB and RDB simultaneously, which enables you to easily distribute data to multiple DB systems. If implemented correctly, you can reduce the load of RDB that sometimes may tend to be a bottleneck of the system.
    +However, when distributing it, you should consider whether the data is supposed to be saved in RDB or in MongoDB depending on the nature of the data. Probably, the question is whether you really want to use transactions or not? + +In this way, you can easily access to database systems with different mechanisms which gives you the opportunity to build highly scalable systems as a Web application. + +**Differences between the Mongo object class and the ORM object class:** + +In a Mongo object class, you can define **QStringList**s as an instance variable like the following sequence of code visualizes: + +```c++ +class T_MODEL_EXPORT FooObject : public TMongoObject, public QSharedData +{ +public: + QString _id; + QStringList texts; + : +``` + +* Please consider: **QStringList** cannot be defined in ORM object classes. \ No newline at end of file diff --git a/docs/de/user-guide/model/or-mapping.md b/docs/de/user-guide/model/or-mapping.md new file mode 100644 index 000000000..989ed22f2 --- /dev/null +++ b/docs/de/user-guide/model/or-mapping.md @@ -0,0 +1,240 @@ +--- +title: O/R Mapping +page_id: "060.010" +--- + +## O/R Mapping + +The model accesses the database internally through O/R mapping objects. This object will basically be a one-to-one relationship and will be referred to as an ORM object. It can be represented by a diagram such as the following:
    + +
    + +![ORM]({{ site.baseurl }}/assets/images/documentation/orm.png "ORM") + +
    + +One record is related to one object. However, there are often cases of one-to-many relationships.
    +Because the model is a collection of information to be returned to the browser, you need to understand how to access and manipulate the RDB ORM object. + +The following DBMS (driver) are supported (this is equivalent to Qt supports): + +* MySQL +* PostgreSQL +* SQLite +* ODBC +* Oracle +* DB2 +* InterBase + +As we proceed with the description, let's check the correspondence of terms between RDB and object-oriented language. + +
    + +**Term correspondence table** + +
    + +
    + +| Object-orientation | RDB | +|--------------------|--------| +| Class | Table | +| Object | Record | +| Property | Field | + +

    + +## Database Connection Information + +The connection information for the database is specified in the configuration file (*config/database.ini*). The content of the configuration file is divided into three sections: *product*, *test*, and *dev*. By specifying options (-e) in the Web application startup command string (treefrog), you can switch the database. In addition, you can do so by adding a section. + +Parameters that can be set are the following: + +
    + +| Parameters | Description | +|----------------|--------------------------------------------------------------------------------| +| driverType | Driver typeChoose from:
    QMYSQL, QPSQL, QSQLITE, QODBC, QOCI, QDB2, QIBASE | +| databaseName | Database name Specify the file path in the case of SQLite, e.g. *db/dbfile* | +| hostName | Host name | +| port | Port | +| userName | User name | +| password | Password | +| connectOptions | Connect options
    Refer to Qt documentation [QSqlDatabase::setConnectOptions()](http://doc.qt.io/qt-5/qsqldatabase.html){:target="_blank"} | + +

    + +In this way, when you start a Web application, the system will manage the database connection automatically. From developer side, you don't need to deal with the process of opening or closing the database. + +After you create a table in the database, set the connection information in the dev section of the configuration file. + +The following sections will be using the *BlogObject* class as an example which I made in the [tutorial chapter]({{ site.baseurl }}/en/user-guide/tutorial/index.html){:target="_blank"}. + +## Reading ORM Object + +This is the most basic operation. It deals with finding documents in a given table. The passed *id* in the findByPrimaryKey() method here is supposed to find a document inside the table which matches with one of the ids (set as primary key). If a match has been decected, the content of the record will be then read. + +```c++ +int id; +id = ... +TSqlORMapper mapper; +BlogObject blog = mapper.findByPrimaryKey(id); +``` + +You can also read all records by using the findAll() method. + +```c++ +TSqlORMapper mapper; +QList list = mapper.findAll(); +``` + +You must be careful with this; there is a possibility when the number of record is large, using read all would consume the memory. You can set an upper boundary using the setLimit() method. + +The SQL statement is generated inside the ORM. To see what query was issued, please check the [query log]({{ site.baseurl }}/en/user-guide/helper-reference/logging/html){:target="_blank"}. + +## Iterator + +If you want to deal with the search results one by one, you can use the iterator. + +```c++ +TSqlORMapper mapper; +mapper.find(); // execute queries +TSqlORMapperIterator i(mapper); +while (i.hasNext()) { // Itaration + BlogObject obj = i.next(); + // do something .. +} +``` + +## Reading ORM Object by Specifying the Search Criteria + +Search criterias are specified in the TCriteria class. Importing just a single record such as "Hello world" into the *Title* folder can be done in the following manner: + +```c++ +TCriteria crt(BlogObject::Title, "Hello world"); +BlogObject blog = mapper.findFirst(crt); +if ( !blog.isNull() ) { + // If the record exists +} else { + // If the record does not exist +} +``` + +You can also combine and apply multiple conditions. + +```c++ +// WHERE title = "Hello World" AND create_at > "2011-01-25T13:30:00" +TCriteria crt(BlogObject::Title, tr("Hello World")); +QDateTime dt = QDateTime::fromString("2011-01-25T13:30:00", Qt::ISODate); +crt.add(BlogObject::CreatedAt, TSql::GreaterThan, dt); // AND add to the end operator + +TSqlORMapper mapper; +BlogObject blog = mapper.findFirst(crt); + : +``` + +If you want to create conditions connect by the OR operator, use the addOr() method. + +```c++ +// WHERE title = "Hello World" OR create_at > "2011-01-25T13:30:00" ) +TCriteria crt(BlogObject::Title, tr("Hello World")); +QDateTime dt = QDateTime::fromString("2011-01-25T13:30:00", Qt::ISODate); +crt.addOr(BlogObject::CreatedAt, TSql::GreaterThan, dt); // OR add to the end operator + : +``` + +If you add a condition in the addOr() method, the condition clause is enclosed in parentheses. If you use a combination of add() and addOr() methods, take care about the order in which they are called. + +##### NOTE + +Remember, that when using AND and OR operators together, the AND operator has priority during its evaluation. That means, when expressions mix AND and OR operators, the AND operator is evaluated from the expression first, while the OR operator is evaluated in the second place. If you don't wish an operator to be evaluated in this order, you have to parantheses. + +## Create an ORM Object + +Create an ORM object in the same way as an ordinary object and then set its properties. Use the create() method to insert it in the database. + +```c++ +BlogObject blog; +blog.id = ... +blog.title = ... +blog.body = ... +blog.create(); // Inserts to DB +``` + +## Update an ORM Object + +In order to update an ORM object, you need to create an ORM object first, before its record can be read. Once you have fetched an ORM object from the database, you can set the properties and save its new state with the update() method. + +```c++ +TSqlORMapper mapper; +BlogObject blog = mapper.findByPrimaryKey(id); +blog.title = ... +blog.update(); +``` + +## Delete an ORM Object + +Removing ORM object means removing its record as well.
    +Reads the ORM object and then deletes it by the remove() method. + +```c++ +TSqlORMapper mapper; +BlogObject blog = mapper.findByPrimaryKey(id); +blog.remove(); +``` + +Like other methods, you can remove the records that match the criteria directly, without creating an ORM object. + +```c++ +// Deletes records that the Title field is "Hello" +TSqlORMapper mapper; +mapper.removeAll( TCriteria(BlogObject::Title, tr("Hello")) ); +``` + +## Automatic ID Serial Numbering + +In some database systems, there is an automatic numbering function for fields. For example, in MySQL, the AUTO_INCREMENT attribute, with the equivalent in PostgreSQL being a field of serial type. + +The TreeFrog Framework is also equipped with this mechanism. That means, in the examples below numbers are assigned automatically. There is no need to update or to register a new value model.
    +First, create a table with a field for the automatically sequenced number. Then, when the model is created by the generator command, we don't to manually apply updating the field (here 'id') anymore. + +Example in MySQL: + +```sql + CREATE TABLE animal ( id INT PRIMARY KEY AUTO_INCREMENT, ... +``` + +Example in PostgreSQL: + +```sql + CREATE TABLE animal ( id SERIAL PRIMARY KEY, ... +``` + +## Automatically Saving og the Date and Time while Creation and Update + +There are often cases where you want to store information such as date and time or the date and time when a record has been once created. Since this is a routine implementation, the field names can be set up in accordance with the rules in advance, so that the framework takes care of it by itself automatically. + +For saving date and time in case of a record creation, you can use the field name called *created_at*. Use the field name *updated_at* instead for saving date and time when updating a record. We use the TIMESTAMP type here for our purpose. If there is such a field, the ORM object sets a time stamp. + +
    + +| Item | Field Name | +|------------------------------------------|---------------------------| +| Saving the date and time of creation | created_at | +| Saving the date and time of modification | updated_at OR modified_at | + +

    + +The tools for storing date and time automatically are handeled by the database itself as well. My recommendation is that, even though it can be done quite well in the database, it's better to do it in the framework. + +It doesn't really matter either way, I think either that we do not care, but by using the framework, you can define elaborate field names as you wish and leave the database side to do the rest. + +## Optimistic Locking + +The optimistic locking is a way to save data while verifying that it is not updated by others, without doing "record locking" while updating is taking place. The update is abandoned if another update is already taking place. + +In advance, prepare a field named *lock_revision* as an integer to record also using the auto increment ability. Lock revision is then incremented with each update. When reading the value and it is found to be different from that in the update, it means that it has been updated from elsewhere. Only if the values are the same, the update proceeds. In this way, we are able to securely proceeds updates. Since lock is not used, saving of DB system memory and an improvement in processing speeds, even if these are slight, can be expected. + +To take advantage of optimistic locking in the SqlObject, add an integer type field named *lock_revision* to the table. It creates a class using the generator. With this alone, optimistic locking is activated when you call TSqlObject::remove() method and TSqlObject::update() method. + +##### In brief: Create a field named lock_revision from type integer in the table. diff --git a/docs/de/user-guide/model/partitioning.md b/docs/de/user-guide/model/partitioning.md new file mode 100644 index 000000000..e6a492754 --- /dev/null +++ b/docs/de/user-guide/model/partitioning.md @@ -0,0 +1,75 @@ +--- +title: Partitioning +page_id: "060.040" +--- + +## Partitioning + +Partitioning is used to put table A and table B on different servers to balance the load on the DB. On a Web system, the database is often the bottleneck of the whole system. + +As the amount of data in the table rests in the memory of the DB server (equivalent to disk I/O), it creates an increasing a loss of performance. The simplest answer may be to expand the memory (because memory is cheap these days). However, there are various problems with this solution, therefore it may be not as simple as expected first. + +It may be possible to increase the DB server itself, and it is worth considering partitioning of the server load. However, this is a difficult issue, server scaling depends on the size of the DB. Many helpful books have been published on this subject. For this reason, I will not discuss this here any further. + +The TreeFrog Framework provides a mechanism for partitioning, giving easy access data on a different DB server. + +## Partitioning by SqlObject + +As a prerequisite, we use table A for host A, and table B for host B. + +First, make separate database configuration files describing the hosts connection information. The file names must be *databaseA.ini* and *databaseB.ini* respectively. They should be located in the config directory. For the contents of the file, write the appropriate values. Here is an example of the way the *dev* section might be defined. + +```ini +[dev] +DriverType=QMYSQL +DatabaseName=foodb +HostName=192.168.xxx.xxx +Port=3306 +UserName=root +Password=xxxx +ConnectOptions= +``` + +Next define the file names of the database configuration files in the application configuration file (*application.ini*). Use *DatabaseSettingsFiles* with the values written side by side and seperate them by spaces. + +```ini +# Specify setting files for databases. +DatabaseSettingsFiles=databaseA.ini databaseB.ini +``` + +The database IDs follow the sequence 0, 1, 2, … as per following the example: + +* The database ID of the host A : 0 +* The database ID of the host B : 1 + +Then, set the header file of SqlObject with this database ID.
    +Edit the header file that is created by the generator. You can also override the databaseId() method by the returned database ID. + +```c++ +class T_MODEL_EXPORT BlogObject : public TSqlObject, public QSharedData +{ + : + int databaseId() const { return 1; } // returns ID 1 + : +}; +``` + +By doing this, queries for BlogObject will be issued to host B. After that, we can just use the SqlObject as in the past. + +The example here was made with two DB servers, but is also compatible with three or more DB servers. Simply add the *DatabaseSettingsFiles* in the configuration file. + +Thus, partitioning can be applied without changing the logic of the model and controller. + +## Querying Partitioned Tables + +As discussed in the [SQL query]({{ site.baseurl }}/en/user-guide/model/sql-query.html){:target="_blank"}, the TSqlQuery is not only used to issue a query on its own, or it doesn't need to extract all values, but you can use it to apply to the partitioning. + +As follows, the database ID is specified in the second argument to the constructor: + +```c++ +TSqlQuery query("SELECT * FROM foo WHERE ...", 1); // specifies database ID 1 +query.exec(); + : +``` + +This query is then therefore executed to host B. \ No newline at end of file diff --git a/docs/de/user-guide/model/sql-query.md b/docs/de/user-guide/model/sql-query.md new file mode 100644 index 000000000..5895fbdc8 --- /dev/null +++ b/docs/de/user-guide/model/sql-query.md @@ -0,0 +1,96 @@ +--- +title: SQL Query +page_id: "060.020" +--- + +## SQL Query + +SqlObject function is sufficient when you need to read a simple record, but when the processing is complex such as when relating to multiple tables, you may want to publish an SQL query directly. In this framework, it is possible to generate a query safely by using a placeholder. + +The following code is an example of issuing a query using placeholders for ODBC format: + +```c++ +TSqlQuery query; +query.prepare("INSERT INTO blog (id, title, body) VALUES (?, ?, ?)"); +query.addBind(100).addBind(tr("Hello")).addBind(tr("Hello world")); +query.exec(); // Query execution +``` + +Here is an example using a named placeholder: + +```c++ +TSqlQuery query; +query.prepare("INSERT INTO blog (id, title, body) VALUES (:id, :title, :body)"); +query.bind(":id", 100).bind(":title", tr("Hello")).bind(":body", tr("Hello world")); +query.exec(); // Query execution +``` + +How to retrieve the data from the query result is the same as the QSqlQuery class of Qt: + +```c++ +TSqlQuery query; +query.exec("SELECT id, title FROM blog"); // Query execution +while (query.next()) { + int id = query.value(0).toInt(); // Convert the field first to int type + QString str = query.value(1).toString(); // Convert the second field to QString type + // do something +} +``` + +The same method can be used for the TSqlQuery class, because it inherits the [QSqlQuery class](https://doc.qt.io/qt-5/qsqlquery.html){:target="_blank"} of Qt. + +##### In brief: queries can be created using placeholders in all cases. + +In fact, you can see any query that has been executed in the [query log]({{ site.baseurl }}/en/user-guide/helper-reference/logging.html){:target="_blank"} file. + +## Reading a Query From a File + +Because it's necessary to compile after every time you write or modify a query statement in the source code, you might find it a bit of a hassle during the application development. To alleviate this, there is a mechanism for writing only query statements to a separate file to be loaded at runtime. + +The file is placed in the sql directory (however, the directory can be changed through the *application.ini*). The *insert_blog.sql* is temporary, I'll describe the contents below. + +```sql +INSERT INTO blog (id, title, body) VALUES (?, ?, ?) +``` + +The following is the source code. We will read the *insert_blog.sql* file with the load() method. + +```c++ +TSqlQuery query; +query.load("insert_blog.sql") +query.addBind(100).addBind(tr("Hello")).addBind(tr("Hello world")); +query.exec(); // Query execution +``` + +The cache works inside the load() method (but only when thread module is applied in [MPM]({{ site.baseurl }}/en/user-guide/performance/index.html){:target="_blank"}. The query is read from the file only on the first occasion, after that it is used from the cache memory, so it then works at high speed.
    +After the file has been updated, we need to restart the server in order to read the query statement. + +``` + $ treefrog -k abort ; treefrog -d -e dev +``` + +Or like the following: + +```c++ + $ treefrog -k restart +``` + +## Get an ORM Object from the Result of a Query + +In the above method, it is necessary to retrieve the value of every field from the results of the query; however, single records can be extracted as ORM objects in the following manner. + +Run the query using the TSqlQueryMapper object. Then extract the ORM object from the results using an iterator. It's important to specify the 'blog. *' in the SELECT statement in order to select and target all fields. + +```c++ +TSqlQueryORMapper mapper; +mapper.prepare("SELECT blog.* FROM blog WHERE ..."); +mapper.exec(); // Query execution +TSqlQueryORMapperIterator it(mapper); +while (it.hasNext()) { + BlogObject obj = it.next(); + // do something + : +} +``` + +If you need to extract only one ORM object and you can get the results using the execFirst() method. \ No newline at end of file diff --git a/docs/de/user-guide/model/transaction.md b/docs/de/user-guide/model/transaction.md new file mode 100644 index 000000000..d32886905 --- /dev/null +++ b/docs/de/user-guide/model/transaction.md @@ -0,0 +1,39 @@ +--- +title: Transaction +page_id: "060.030" +--- + +## Transaction + +If the DBMS supports transaction, the transaction will work by default; then, when you launch the app, it is already working. + +The framework starts the transaction just before the action is called, and after being called, it commits the transaction. + +If something abnormal occurs and you want to roll back the transaction, you can throw an exception or call rollbackTransaction() method in the controller. A Rollback should be implemented after completing the action. + +```c++ +// in an action + : +if (...) { + rollbackTransaction(); + : +} +``` + +If you don't want to activate the transaction itself deliberately, you can override the transactionEnabled() method of the controller and then return *false*. + +```c++ +bool FooController::transactionEnabled() const +{ + return false; +} +``` + +You can set each controller, as in this example, or, if you don't want to use it all, you can override in ApplicationController. + +```c++ +bool ApplicationController::transactionEnabled() const +{ + return false; +} +``` \ No newline at end of file diff --git a/docs/de/user-guide/performance/index.md b/docs/de/user-guide/performance/index.md new file mode 100644 index 000000000..707a52874 --- /dev/null +++ b/docs/de/user-guide/performance/index.md @@ -0,0 +1,88 @@ +--- +title: Performance +page_id: "160.0" +--- + +## Performance + +### Creating the Application Server + +The TreeFrog Application server (AP server) is created by a server process that handles HTTP (*tadpole*) and a process that monitors its life and death (*treefrog*). + +If a segmentation fault causes the *tadpole* process to go down, the *treefrog* process will detect it and then restart the *tadpole* process. This kind of fault-tolerant mechanism makes it possible for the service itself to be provided continuously. + +## Multi-Processing Module – MPM + +There are two MPMs (Multi-Processing Modules) to create multiprocessing modules for the application server (AP server): *prefork* and *thread*. These are similar to Apache. You need to choose one of them and then specify it in setting file. The default is 'thread'. + +* **prefork:** Create the process "fork" in advance, and then create socket as "listen". The action is performed during the process, and when the request has been operated and the response returned, the process disappears. Do not reuse it! If the action is down by fault or illegal operation, it wouldn't then affect any actions. **[Deprecated]** +* **thread:** Thread is created every time when there is a request. Action runs on the thread and when the request has been processed and the response sent, the thread disappears. The performance is good. +* **hybrid:** Sockets are monitored by epoll system call and an HTTP request is processed on a thread. It's available on Linux only. It is implemented for the C10K problem that can maintain many sockets. + +If you use the thread module, performance can be very high (about 14x of 'prefork'). If you use the hybrid module on Linux, performance can be much better at high level load. However, when, for example, a segmentation fault occurs, each process can go down, therefore all threads can be down. If a certain action has a fault, other parallel running actions could also be affected. That would cause problems. In the case of the prefork module the process is divided into individual actions, so that this kind of concern can be avoided. + +In addition, in case of using the thread or hybrid module setting, when a web application has a memory leak fault, by continuing operation, sooner or later the memory will be used up. Of course, the memory leak bug should be fixed, but if you cannot solve it, you can use the prefork module. Each time a process is exited you can avoid the memory being eaten up. However, it is annoying!
    +Be sure to consider the mentioned things above when choosing an MPM. + +## Benchmark + +The following comparisons use a sample application (blogapp) and the benchmark software *httperf*. + +**Test environment:** + +* PC: MacBook Pro [ 2.4GHz Intel Core 2 Duo, 4GB 1067MHz DDR3 ] +* OS: Mac OS X 10.6 +* DB: SQLite (no record) +* Qt: 4.7 (Qt SDK 2010.05) +* TreeFrog: 0.54 (compiled by -O2 option) + +**Test Method** + +* The performance here is measured by sending huge amount of requests to */blog/index* in localhost in one connection. Httperf is used. + +The framework, individual requests, controller, model, DB, and view are all comprehensively checked. Since the DB cache system is not implemented (in the case of 0.54), an SQL query is called to the DB each time. + +In the case of the thread module:
    +**Parameter: MPM.thread.MaxServers=20** + +``` + $ httperf –server=localhost –port=8800 –uri=/Blog/index/ –num-conns=10000 –num-calls=1 + httperf –client=0/1 –server=localhost –port=8800 –uri=/Blog/index/ –send-buffer=4096 + –recv-buffer=16384 –num-conns=10000 –num-calls=1 + httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE + Maximum connect burst length: 1 + Total: connections 10000 requests 10000 replies 10000 test-duration 17.800 s + + Connection rate: 561.8 conn/s (1.8 ms/conn, <=1 concurrent connections) + Connection time [ms]: min 0.3 avg 1.8 max 17.5 median 1.5 stddev 0.5 + Connection time [ms]: connect 0.1 + Connection length [replies/conn]: 1.000 + + Request rate: 561.8 req/s (1.8 ms/req) + Request size [B]: 73.0 + + Reply rate [replies/s]: min 555.4 avg 562.3 max 566.8 stddev 6.1 (3 samples) + Reply time [ms]: response 1.7 transfer 0.0 + Reply size [B]: header 313.0 content 421.0 footer 0.0 (total 734.0) + Reply status: 1xx=0 2xx=10000 3xx=0 4xx=0 5xx=0 + + CPU time [s]: user 3.13 system 13.73 (user 17.6% system 77.1% total 94.7%) + Net I/O: 442.7 KB/s (3.6*10^6 bps) + + Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0 + Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0 +``` + +* By running the test several times, I was able to find an intermediate result, as posted above ↑ + +About 562 times requests are executed per second. This is the number when the record is 0, so that it would represents the highest performance figure of which TreeFrog is capable. I think it indicates top class performance in the web application framework. What do you think? + +In real web applications, the logic of controller and model is more complicated, and there should would be a large number of records, so performance would be less than the above. These figures should therefore be taken as a reference. + +##### In brief: Use 'thread' as your MPM. If using the WebSocket protocol, consider setting 'hybrid' as well. + +### Benchmarks Site + +This following site published benchmarks for web application frameworks. + +- [Web Framework Benchmarks](https://www.techempower.com/benchmarks) diff --git a/docs/de/user-guide/security/index.md b/docs/de/user-guide/security/index.md new file mode 100644 index 000000000..c92cebc26 --- /dev/null +++ b/docs/de/user-guide/security/index.md @@ -0,0 +1,59 @@ +--- +title: Security +page_id: "100.0" +--- + +## Security + +Once you publish the website, you have to understand the threats it faces and set security measures. The application developer has to program carefully in order to avoid vulnerability on the web. You can find detailed articles on the internet, that's why I'll not go to deept into detail here. + +Since TreeFrog has an inbuilt security system, you can establish a safe website when using it properly. + +## SQL Injection Prevention + +As you will know, SQL injection means that due to the problems of SQL sentence construction, the database can be attacked or misused. TreeFrog advises observance of the following SQL injection measures: + +* Use ORM object => you can make safe SQL query sentences inside. +* Use TSqlQuery place holder when constructing SQL sentences => the values are processed by escape automatically. +* When constructing SQL sentences by string concatenation, use TSqlQuery::escapeIdentifier() for field name, and TSqlQuery::formatValue() for value. => escape processing is done for each. + +## Cross-site Scripting Prevention (CSRF) + +If there is a deficiency in the process of generating websites, it can allow the inclusion of malicious scripts, and if that happens, a cross-site scripting attack can be established. In order to guard against this, you should implement a policy of using escape for all values and attribute outputs which output dynamically into the view area. In the TreeFrog, the following is done: + +* The eh() method or '<%= .. %>' notation should be used for outputting values. + +You have to be very careful if you output the area WITHOUT using the escape processing, that is the echo() method or '<%= .. %>' notation. + +Basically, for the content of any \ .. \ element, I recommend you to not dynamically output any information dependent on outside input. + +## CSRF Protection + +Any site that accepts user requests without any verification, has a possible vulnerability to CSRF (Cross-Site Request Forgery) exists. If an HTTP request has been trumped by a third party, you have to discard it. + +To prevent this, perform the followings both. + + * As for the information on the form, ONLY accept requests in the POST method. + * Put a row of letters that is difficult to predict as a hidden parameter on one of the information items within the form, and validate it when receiving it. => by generating a form tag with the formTag() method, the hidden parameter is automatically granted. + +If this hidden parameter is easily predictable, this CSRF protection is insufficient. This parameter is a string that has been converted by the hash function, using the Session.Secret parameter, in the *application.ini* configuration file. It is therefore sufficiently difficult for the string to be guessed by anyone not knowing the *Session.Secret* value. + +In order to turn on the CSRF protection, you need to set the following in the *application.ini* file: + +``` + EnableCsrfProtectionModule=true +``` + +I would recommend that you turn this feature off during the development of the application. + +##### In brief: Generate a form tag using the formTag() method. + +## Session Hijacking Prevention + +When a session ID is easily guessed or stolen, spoof-like acts are possible. This kind of spoof-like conduct is called session hijacking. + +In the TreeFrog Framework, as a defense against session hijacking, a new session ID of sufficient length to avoid being guessed is generated using random number and a hash function each time the site is accessed. In that way the session ID is extremely difficult to guess and, for a general site, provides what is thought to be adequate protection. + +Although guessing a session ID is difficult, a more serious threat is possible eavesdropping on the network. To counter this, it is possible to encrypt (SSL) the communication channel by reverse proxy (such as nginx or Apache). + +##### In brief: Encrypt by SSL all sites where important information is being dealt with. diff --git a/docs/de/user-guide/test/index.md b/docs/de/user-guide/test/index.md new file mode 100644 index 000000000..5838d48ef --- /dev/null +++ b/docs/de/user-guide/test/index.md @@ -0,0 +1,175 @@ +--- +title: Test +page_id: "120.0" +--- + +## Test + +In the process of application development, testing is very important. Testing requires checking by repeating, and it is a boring process. For this reason, it might be very useful to automate this process. + +## Unit Test of the Model + +In this session, we will try to check if the model works the right way. The test framework follows TestLib attached by Qt (please check out the [documentation](http://qt-project.org/doc/qt-5.0/qttestlib/qtest-overview.html){:target="_blank"} for more details). + +Let's test the Blog model code that we made in the [tutorial](/en/user-guide/tutorial/index.html){:target="_blank"}. Make a common library for the model in advance. At first, we will create a working directory in the *test* directory. + +``` + $ cd test + $ mkdir blog + $ cd blog +``` + +We will try to create the test case for creating and reading of the Blog model.
    +For example, let's set the name of the implementing test as follows: *TestBlog*. The source code with the following content is saved as a file named *testblog.cpp*. + +```c++ +#include +#include "models/blog.h" // include the model class + +class TestBlog : public QObject +{ + Q_OBJECT +private slots: + void create_data(); + void create(); +}; + +void TestBlog::create_data() +{ + // definition of test data + QTest::addColumn("title"); + QTest::addColumn("body"); + + // adding to test data + QTest::newRow("No1") << "Hello" << "Hello world."; +} + +void TestBlog::create() +{ + // acquisition of test data + QFETCH(QString, title); + QFETCH(QString, body); + + // logic of the test + Blog created = Blog::create(title, body); + int id = created.id(); + Blog blog = Blog::get(id); // Getting model ID + + // verification of result execution + QCOMPARE(blog.title(), title); + QCOMPARE(blog.body(), body); +} + +TF_TEST_MAIN(TestBlog) // specify the class name you created +#include "testblog.moc" // charm. Make the extension .moc +``` + +As supplemental comment, among this, create() method can do the test, and QCOMPARE macro can check the real returning value. The create_data() method works as passing test data to the create_data() method. +The rule is always to put '_data' at the end of the method name. + +In this example, I am doing the following in create_data() method. + +* QTest::addColumn() function: Define the name and type of test data. +* QTest::newRow() function: Add test data. + +The following is done in the create() method. + +* Fetch the test data. +* Run the test logic. +* Verify that the result is correct. + +Next, create a project file to make the *Makefile*. The file name is *testblog.pro*, saving the contents as the following. + +``` + TARGET = testblog + TEMPLATE = app + CONFIG += console debug c++14 + CONFIG -= app_bundle + QT += network sql testlib + QT -= gui + DEFINES += TF_DLL + INCLUDEPATH += ../.. + LIBS += -L../../lib -lmodel + include(../../appbase.pri) + SOURCES = testblog.cpp # Specifying the file name +``` + +After you save the project file, you can create a binary by running the following command in its directory: + +``` + $ qmake + $ make +``` + +Next, some little configuration needs to be done for the testing process.
    +Because of the need to refer to the various configuration files, the test command requires a symbolic link to the config directory. Its location should be directly below of the test command. When SQLite is used for the database, we need to make a symbolic link to the *db* directory as well. + +``` + $ ln -s ../../config config + $ ln -s ../../db db +``` + +If you use Windows, an exe file of the test is created in the *debug* directory, so that a symbolic link is created there. Please be careful: it is NOT a shortcut! +To create a symbolic link, you must run the command from the command prompt launched with administrator privileges. + +``` + > cd debug + > mklink /D config ..\..\..\config + > mklink /D db ..\..\..\db +``` + +Furthermore, take the path to the common library including the Blog model.
    +In the case of Linux, set the environment variable as follows: + +``` + $ export LD_LIBRARY_PATH=/path/to/blogapp/lib +``` + +If you use Windows, add the setting to PATH variables like this: + +``` + > set PATH=C:\path\to\blogapp\lib;%PATH% +``` + +Then check the connection information for the database. In the unit test, the connection information in the test section in the database configuration file (*database.ini*) is used. + +``` +[test] +DriverType=QMYSQL +DatabaseName=blogdb +HostName= +Port= +UserName=root +Password= +ConnectOptions= +``` + +The configuration is now complete. Next, the test needs to be executed. If the test was a throughout success, you can see the following message on the screen: + +``` +$ ./testblog +Config: Using QtTest library 5.5.1, Qt 5.5.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.4.0 20160609) +PASS : TestBlog::initTestCase() +PASS : TestBlog::create(No1) +PASS : TestBlog::cleanupTestCase() +Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted +********* Finished testing of TestBlog ********* +``` + +In the case of Windows, please execute the test on the TreeFrog Command Prompt.
    +If, however, the result is not what was expected, you will see the following message. + +``` +********* Start testing of TestBlog ********* +Config: Using QtTest library 5.5.1, Qt 5.5.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.4.0 20160609) +PASS : TestBlog::initTestCase() +FAIL! : TestBlog::create(No1) Compared values are not the same + Actual (blog.body()): "foo." + Expected (body): "Hello world." + Loc: [testblog.cpp(35)] +PASS : TestBlog::cleanupTestCase() +Totals: 2 passed, 1 failed, 0 skipped, 0 blacklisted +********* Finished testing of TestBlog ******* +``` + +Make a test case for each each model. Then please do the test. The key to a good Web application development is to be sure that the model is working properly. diff --git a/docs/de/user-guide/tutorial/index.md b/docs/de/user-guide/tutorial/index.md new file mode 100644 index 000000000..d8b4160ec --- /dev/null +++ b/docs/de/user-guide/tutorial/index.md @@ -0,0 +1,797 @@ +--- +title: Tutorial +page_id: "030.0" +--- + +## Tutorial + +Let's create a TreeFrog Application.
    +We'll try to make a simple blog system which can list, view, and add/edit/delete text. + +### Generate the Application Skeleton + +First we will need to make a skeleton (various settings files and a directory tree). We'll use the name "blogapp". Run the following command from the command line. (In Windows, please run from the TreeFrog Command Prompt). + +``` + $ tspawn new blogapp + created blogapp + created blogapp/controllers + created blogapp/models + created blogapp/models/sqlobjects + created blogapp/views + created blogapp/views/layouts + created blogapp/views/mailer + created blogapp/views/partial + : +``` + +### Create a Table + +Now we need to create a table in the database. We'll create the field title and content (body). Here are examples in MySQL and SQLite' + +Example in MySQL:
    +Set the character set to UTF-8. You can also specify this when generating the database (do ensure that it is being set correctly, see FAQ). You can specify the configuration file for the database, as described below. Also, make the path through into MySQL using the command line tool. + +``` + $ mysql -u root -p + Enter password: + + mysql> CREATE DATABASE blogdb DEFAULT CHARACTER SET utf8mb4; + 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 DATETIME, updated_at DATETIME, 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_increment | + | title | varchar(20) | YES | | NULL | | + | body | varchar(200) | YES | | NULL | | + | created_at | datetime | YES | | NULL | | + | updated_at | datetime | YES | | NULL | | + | lock_revision | int(11) | YES | | NULL | | + +---------------+--------------+------+-----+---------+----------------+ + 6 rows in set (0.01 sec) + + mysql> quit + Bye +``` + +**Example in SQLite:**
    +We are going to put the database files in the DB directory. + +``` + $ cd blogapp + $ sqlite3 db/blogdb + SQLite version 3.6.12 + sqlite> CREATE TABLE blog (id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(20), body VARCHAR(200), created_at TIMESTAMP, updated_at TIMESTAMP, lock_revision INTEGER); + sqlite> .quit +``` + +A blog table is created with the fields: id, title, body, created_at, updated_at, and lock_revision. + +With the fields updated_at and created_at, TreeFrog will automatically insert the date and time of creation and of each update. The lock_revision field, which is intended for use with optimistic locking, needs to be created as an integer type. + +#### Optimistic Locking + +The optimistic locking is used to store data while verifying that information is not locked by being updated by another user. Since there is no actual write lock, you can expect processing to be a little faster. +See the section on O/R mapping for more information. + +## Set the Database Information + +Use _config/database.ini_ to set information about the database.
    +Open the file in the editor, enter the appropriate values for your environment to each item in the [dev] section, and then click Save. + +Example in MySQL: + +``` + [dev] + DriverType=QMYSQL + DatabaseName=blogdb + HostName= + Port= + UserName=root + Password=pass + ConnectOptions= +``` + +Example in SQLite: + +``` + [dev] + DriverType=QSQLITE + DatabaseName=db/blogdb + HostName= + Port= + UserName= + Password= + ConnectOptions= +``` + +Once you have correctly set these details, it's time to display the table to access the DB.
    +If everything is setup properly, it will display a message like this: + +``` + $ cd blogapp + $ tspawn --show-tables + DriverType: QSQLITE + DatabaseName: db\blogdb + HostName: + Database opened successfully + ----- + Available tables: + blog +``` + +If a required SQL driver is not included in the Qt SDK, the following error message will appear: + +``` + QSqlDatabase: QMYSQL driver not loaded +``` + +If you receive this message, the Qt SQL driver may not be installed. Install the driver for the RDBM. + +You can check which SQL drivers are installed with the following command; + +``` + $ tspawn --show-drivers + Available database drivers for Qt: + QSQLITE + QMYSQL3 + QMYSQL + QODBC3 + QODBC +``` + +The pre-built SQL driver can be used for SQLite, although the SQLite driver can also be used with a little effort. + +## Specifying a Template System + +In TreeFrog Framework, we can specify either Otama or ERB as a template system. We will set the TemplateSystem parameter in the _development.ini_ file. + +``` +TemplateSystem=ERB + or +TemplateSystem=Otama +``` + +ERB is specified by default. + +## Automatic Generation of Code Created from the Table + +From the command line, run the command generator (tspawn) which will generate the underlying code. The example below shows the production of controller, model, and view. The table name is specified as the command argument. + +``` + $ tspawn scaffold blog + DriverType: QSQLITE + DatabaseName: db/blogdb + HostName: + Database open successfully + created controllers/blogcontroller.h + created controllers/blogcontroller.cpp + updated controllers/controllers.pro + created models/sqlobjects/blogobject.h + created models/blog.h + created models/blog.cpp + updated models/models.pro + created views/blog +   : +``` + +Depending on the tspawn option, only controllers or models can be generated. + +### Vue.js support + +In version 2 and later, it is possible to choose to generate views using [vue.js](https://vuejs.org/). + +``` + : + Create sources for vue.js? [y/n] y ← Enter 'y' +``` + +### Reference: Help for tspawn command + +``` + $ tspawn --help + usage: tspawn [args] + + Type 'tspawn --show-drivers' to show all the available database drivers for Qt. + Type 'tspawn --show-driver-path' to show the path of database drivers for Qt. + Type 'tspawn --show-tables' to show all tables to user in the setting of 'dev'. + Type 'tspawn --show-collections' to show all collections in the MongoDB. + + Available subcommands: + new (n) + scaffold (s) [model-name] + controller (c) action [action ...] + model (m) [model-name] + helper (h) + usermodel (u) [username password [model-name]] + sqlobject (o) [model-name] + mongoscaffold (ms) + mongomodel (mm) + websocket (w) + api (a) + validator (v) + mailer (l) action [action ...] + delete (d) +``` + +## Build the Source Code + +To start the build process, run the following command only once; it will generate a Makefile. + +``` + $ qmake -r "CONFIG+=debug" +``` + +A WARNING message will be displayed, but there is actually no problem. Next, run the make command to compile the controller, model, view, and helper. + +``` + $ make (On MSVC run 'nmake' command instead) +``` + +If the build succeeds, four shared libraries (controller, model, view, helper) will be created in the lib directory. By default, the library is generated in debug mode; however, you can regenerate the Makefile, using the following command, to create a library in release mode. + +Creating a Makefile in release mode: + +``` + $ qmake -r "CONFIG+=release" +``` + +## To Start the Application Server + +Change to the root directory of the application before starting the application server (AP server). The server will start to process what it regards as the application root directory to the directory where the command is run. To stop the server, press Ctrl+c. + +``` + $ treefrog -e dev +``` + +In Windows, start by using _treefrog**d**.exe_. + +``` +> treefrogd.exe -e dev +``` + +In Windows, start by using treefroge**d**.exe when you build web applications in debug mode, and start by using treefrog.exe when you want to build a web application in release mode. + +##### Release and debug modes should not be mixed, as the result will not work properly. + +If you want it to run in the background, use the option -d together with any other required options. + +``` + $ treefrog -d -e dev +``` + +The command option '-e' appears in the above examples. When this is followed by a **section name** that you have specified in database.ini before, it can be used to change the database settings. If no section name is specified it is assumed that the command refers to a product (when the project is being made, the following three sections are predefined). + +
    + +| Section | Description | +| ------- | ---------------------------------------- | +| dev | For generator, development | +| test | For test | +| product | For official version, production version | + +
    + +'-e' comes from the initials letter of "environment". + +Stop command: + +``` + $ treefrog -k stop +``` + +Abort command (forced termination): + +``` + $ treefrog -k abort +``` + +Restart command: + +``` + $ treefrog -k restart +``` + +If the firewall is in place, make sure that the correct port is open (the default is port 8800). + +The following command shows the current URL routing information. + +``` + $ treefrog --show-routes + Available controllers: + match /blog/index -> blogcontroller.index() + match /blog/show/:param -> blogcontroller.show(id) + match /blog/create -> blogcontroller.create() + match /blog/save/:param -> blogcontroller.save(id) + match /blog/remove/:param -> blogcontroller.remove(id) +``` + +### Reference: Help for treefrog command + +``` +$ treefrog -h +Usage: treefrog [-d] [-p port] [-e environment] [-r] [app-directory] +Usage: treefrog -k [stop|abort|restart|status] [app-directory] +Usage: treefrog -m [app-directory] +Options: + -d : run as a daemon process + -p port : run server on specified port + -e environment : specify an environment of the database settings + -k : send signal to a manager process + -m : show the process ID of a running main program + -r : reload app automatically when updated (for development) + +Type 'treefrog --show-routes [app-directory]' to show routing information. +Type 'treefrog --settings [app-directory]' to show application settings. +Type 'treefrog -l' to show your running applications. +Type 'treefrog -h' to show this information. +Type 'treefrog -v' to show the program version. +``` + +## Browser Access + +We will now use your web browser to access http://localhost:8800/Blog. A list screen, such as the following should be displayed. + +Initially, there is nothing registered. + +
    + +![Listing Blog 1]({{ site.baseurl }}/assets/images/documentation/ListingBlog-300x216.png "Listing Blog 1") + +
    + +When two items are registered the options show, edit, and remove become visible. As you can see, there is even no problem in displaying Japanese text. + +
    + +![Listing Blog 2]({{ site.baseurl }}/assets/images/documentation/ListingBlog2-300x216.png "Listing Blog 2") + +
    + +TreeFrog is equipped with a call method mechanism (Routing system) for the appropriate controller from the requested URL to the action (as well as other frameworks).
    +Developed source code can work on other platforms, if it is re-built. + +To see a sample Web application. You can watch it [here](http://blogapp.treefrogframework.org/Blog){:target="\_blank"}.
    +You can play with this and it will respond at the same speed as the average desktop application. + +## Source Code of Controller + +Let's take a look at the contents of the controller which is generated.
    +First, the header file. There are several charm codes, but these are required for sending by URL. + +The purpose of public slots is to declare the actions (methods) you want to send. Actions corresponding to the [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete){:target="\_blank"} are defined. Incidentally, the slots keyword is a feature of the Qt extension. Please see the Qt documentation for more details. + +```c++ +class T_CONTROLLER_EXPORT BlogController : public ApplicationController { +public slots: + void index(); // Lists all entries + void show(const QString &id); // Shows one entry + void create(); // New registration + void save(const QString &id); // Updates (save) + void remove(const QString &id); // Deletes one entry +}; +``` + +Next, explain the source file. The controller is responsible for invoking the view on request. Calls the service and depending on the result, it calls the template logic with the render() function or redirects with the redirect() function. +It is important to **write the main processing in the service class and keep the controller logic simple**. + +```c++ +static BlogService service; + +void BlogController::index() +{ + service.index(); // Calls the service + render(); // Renders the view, index.erb +} + +void BlogController::show(const QString &id) +{ + service.show(id.toInt()); // Calls the service + render(); // Renders the view, show.erb +} + +void BlogController::create() +{ + int id; + + switch (request().method()) { // Checks the incoming httpRequest method type + case Tf::Get: // GET Method + render(); + break; + case Tf::Post: // POST Method + id = service.create(request()); // Calls the service + if (id > 0) { + redirect(urla("show", id)); // Redirects + } else { + render(); // Renders the view, create.erb + } + break; + + default: + renderErrorResponse(Tf::NotFound); + break; + } +} + +void BlogController::save(const QString &id) +{ + int res; + + switch (request().method()) { + case Tf::Get: + service.edit(session(), id.toInt()); // Calls the service + render(); + break; + case Tf::Post: + res = service.save(request(), session(), id.toInt()); // Calls the service + if (res > 0) { + // Save completed + redirect(urla("show", id)); // Redirects to /blog/show + } else if (res < 0) { + // Failed + render(); // Renders the view, save.erb + } else { + // Retry + redirect(urla("save", id)); // Redirects to /blog/save + } + break; + default: + renderErrorResponse(Tf::NotFound); + break; + } +} + +void BlogController::remove(const QString &id) +{ + switch (request().method()) { + case Tf::Post: + service.remove(id.toInt()); // Calls the service + redirect(urla("index")); // Redirects to /blog/index + break; + default: + renderErrorResponse(Tf::NotFound); + break; + } +} + +// Don't remove below this line +T_DEFINE_CONTROLLER(BlogController) +``` + +In the service class, write the original logic that should be processed in the request, so business logic. +It can process the model object retrieved from the database and send it to the view, or it can save the data retrieved from the request to the database via the model object. You can also validate the form data. + +```c++ +void BlogService::index() +{ + auto blogList = Blog::getAll(); // Gets a list of all Blog objects + texport(blogList); // Sends the data to the view +} + +void BlogService::show(int id) +{ + auto blog = Blog::get(id); // Gets the Blog object by primary key + texport(blog); // Sends the data to the view +} + +int BlogService::create(THttpRequest &request) +{ + auto items = request.formItems("blog"); // Gets the incoming form data + auto model = Blog::create(items); // Creates the Blog object + + if (model.isNull()) { + QString error = "Failed to create."; // Error message + texport(error); + return -1; + } + + QString notice = "Created successfully."; + tflash(notice); // Sets a flash message + return model.id(); +} + +void BlogService::edit(TSession& session, int id) +{ + auto model = Blog::get(id); // Gets the Blog object + if (!model.isNull()) { + session.insert("blog_lockRevision", model.lockRevision()); // Stores the lock revision to the session + auto blog = model.toVariantMap(); + texport(blog); // Sends to the view + } +} + +int BlogService::save(THttpRequest &request, TSession &session, int id) +{ + int rev = session.value("blog_lockRevision").toInt(); // Gets the lock revision + auto model = Blog::get(id, rev); // Gets a Blog object + + if (model.isNull()) { + QString error = "Original data not found. It may have been updated/removed by another transaction."; + tflash(error); + return 0; + } + + auto blog = request.formItems("blog"); // Gets the form data + model.setProperties(blog); // Sets the form data + if (!model.save()) { // Saves the object to DB + texport(blog); + QString error = "Failed to update."; + texport(error); + return -1; + } + + QString notice = "Updated successfully."; + tflash(notice); + return 1; +} + +bool BlogService::remove(int id) +{ + auto blog = Blog::get(id); // Gets a Blog object + return blog.remove(); // Removes it from DB +} +``` + +Lock revision is used to realize the optimistic locking. See "model", which comes later in this chapter, for more information. + +As you can see, you can use the texport method to pass data to the view (template). The argument for this texport method is a QVariant object. QVariant can be any type, so int, QString, QList, and QHash can pass any object. For more details on QVariant, please refer to the Qt documentation. + +## View Mechanism + +Two template systems have been incorporated into TreeFrog so far. These are the proprietary system (called Otama) and ERB. As is familiar from Rails, ERB is used for embedding into HTML. + +The default view that is automatically generated by the generator is an ERB file. So, let's take a look at the contents of index.erb. As you can see, the C++ code is surrounded by <% … %>. When the render method is called from the index action, the content of index.erb is returned as the response. + +``` + +<%#include "blog.h" %> + + + + <%= controller()->name() + ": " + controller()->activeAction() %> + + +

    Listing Blog

    + +<%== linkTo("New entry", urla("entry")) %>
    +
    + + + + + + +<% tfetch(QList, blogList); %> +<% for (const auto &i : blogList) { %> + + + + + + +<% } %> +
    IDTitleBody
    <%= i.id() %><%= i.title() %><%= i.body() %> + <%== linkTo("Show", urla("show", i.id())) %> + <%== linkTo("Edit", urla("save", i.id())) %> + <%== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')") %> +
    +``` + +**Another template system** + +Otama is a template system that completely separates the presentation logic from the templates. The template is written in HTML and a "mark" element is inserted as the start tag of the section to be rewritten dynamically. The presentation logic file, written in C++ code, provides the logic in relation to the "mark". + +The following example is a file, _index.html_, that is generated by the generator when it is specified in the Otama template system. This can include the file data, but you will see, if you open it in your browser as it is, because it uses HTML5, the design does not collapse at all without the data. + +``` + + + + + + + +

    Listing Blog

    +New entry
    +
    + + + + + + + + ← mark '@for' + + + + + +
    IDTitleBody
    + Show + Edit + Remove +
    + + +``` + +A custom attribute called 'data-tf' is used to turn on the "mark". This is a Custom Data Attribute as defined in HTML5. A string beginning with "@" is used as the value for the "mark". + +Next, let's look at the index.otm corresponding to the presentation logic.
    +The mark, which links to the associated logic, is declared in the above template, and continues in effect until a blank line is encountered. The logic is contained in the C++ part of the code. + +Operators (such as == ~ =) are also used. The operators control different behaviors (for more information see the following chapters). + +```c++ +#include "blog.h" ← This is as it is C++ code to include the blog.h +@head_title ~= controller()->controllerName() + ": " + controller()->actionName() + +@for : +tfetch(QList, blogList); /* Declaration to use the data passed from the controller */ +for (QListIterator it(blogList); it.hasNext(); ) { + const Blog &i = it.next(); /* reference to Blog object */ + %% /* usually, for loop statements, to repeat the child and 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?')") +``` + +The Otama operators, (and their combinations) are fairly simple:
    +\~ (tilde) sets the content of marked elements to the result of the right-hand side, +\= output the HTML escape, therefore ~= sets the content of the element to the results of the right-hand side then HTML-escape, if you don't want to escape HTML, you can use ~==. + +\: (colon) replaces the result of the right-hand child elements and the elements that are marked, therefore :== replaces the element without HTML escape. + +### Passing Data from the Service or the Controller to the View + +In order to use the exported data (objects) in the view, you need to declare its variables by the tfetch() function. For the argument, specify type of the variable and the variable name. The variables are the same state as immediately before the specified variables are exported, and can be used exactly the same way as a normal variable of C++. + +Here is an example in use : + +``` +Service side : + int hoge; + hoge = ... + texport(hoge); + +View side : + tfetch(int, hoge); +``` + +The Otama system, generates the C++ code based on the presentation file and the template file. Internally, tmake is responsible for processing it. After that the code is compiled, with the shared library as one view, so, the operation is very fast. + +#### HTML Glossary + +An HTML element consists of three components, a start tag, the content, end an tag. For example, in the typical HTML element, +"\Hello\", \ is the start tag, Hello is the content, and \ is the end tag. + +## Model and ORM + +In TreeFrog, a model object is **a data entity that can be persist and represents a concept**, and is a small wrapper around an ORM object. The model object contains the ORM object, which is a has-a relationship (naturally you can create such a model with two or more ORM objects). Since it uses "ORM = Object Model" by default in other framework, in this respect TreeFrog differs from the others. + +An O/R mapper named SqlObject is included by default in TreeFrog. Since C++ is a statically typed language, type declaration is required. Let's take a look at the SqlObject file generated by blogobject.h. + +There is charm codes, but the field in the table is declared as a public member variable. It is close to the actual structure, but can be used by CRUD or an equivalent method, (create, findFirst, update, remove). These methods are defined in the TSqlORMapper class and in the TSqlObject class. + +```c++ +class T_MODEL_EXPORT BlogObject : public TSqlObject, public QSharedData +{ +public: + int id {0}; + QString title; + QString body; + QDateTime created_at; + QDateTime updated_at; + int lock_revision {0}; + + enum PropertyIndex { + Id = 0, + Title, + Body, + CreatedAt, + UpdatedAt, + LockRevision, + }; + + int primaryKeyIndex() const override { return Id; } + int autoValueIndex() const override { return Id; } + QString tableName() const override { return QLatin1String("blog"); } + +private: /*** Don't modify below this line ***/ + Q_OBJECT + 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) +}; +``` + +There are methods to query and update the primary key in the TreeFrog's O/R mapper, but the primary key SqlObject can have only one return primaryKeyIndex() method. Therefore, any table with multiple primary keys should be corrected to return one only. It is also possible to issue more complex queries by using the TCriteria class condition. Please see following chapters for details. + +Next, let's look at the model.
    +The setter/getter for each property and static method of generation/acquisition of the object are defined. The parent class TAbstractModel defines the methods to save and to remove, because of this, the Blog class is equipped with the CRUD methods (_create, get, save, remove_) . + +```c++ +class T_MODEL_EXPORT Blog : public TAbstractModel +{ +public: + Blog(); + Blog(const Blog &other); + Blog(const BlogObject &object); // constructor made from the ORM object + ~Blog(); + + int id() const; // The following lines are the setter/getter + 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; + Blog &operator=(const Blog &other); + + bool create() { return TAbstractModel::create(); } + bool update() { return TAbstractModel::update(); } + bool save() { return TAbstractModel::save(); } + bool remove() { return TAbstractModel::remove(); } + + static Blog create(const QString &title, const QString &body); // object creation + static Blog create(const QVariantMap &values); // object creation from Hash + static Blog get(int id); // Gets object specified by ID + static Blog get(int id, int lockRevision); // Gets object specified by ID and lockRevision + static int count(); // Returns the amount of blog data items + static QList getAll(); // Gets all model objects + static QJsonArray getAllJson(); // Gets all model objects in JSON style + +private: + QSharedDataPointer d; // Holds the pointer of the ORM object + + TModelObject *modelData(); + const TModelObject *modelData() const; +}; + +Q_DECLARE_METATYPE(Blog) +Q_DECLARE_METATYPE(QList) +``` + +Despite the fact that the number of code steps automatically generated by the generator is not high, all the basic functions are covered. + +Of course, automatically generated code is not perfect. Real life applications may need to be more complex. The code may not be sufficient as generated, thus some reworking may be necessary. Nevertheless, the generator will save a little time and effort in writing code. + +In the background, the code as described above also functions to provide; CSRF measures with cookie tampering check, optimistic locking, and token authentication against SQL Injection. If you are interested, please look into the source. + +## Video Demo – Sample Blog Application Creation + +
    + +[![Video Demo - Sample blog Application Creation](http://img.youtube.com/vi/M_ZUPZzi9V8/0.jpg)](https://www.youtube.com/watch?v=M_ZUPZzi9V8) + +
    diff --git a/docs/de/user-guide/view/erb.md b/docs/de/user-guide/view/erb.md new file mode 100644 index 000000000..25be898b8 --- /dev/null +++ b/docs/de/user-guide/view/erb.md @@ -0,0 +1,339 @@ +--- +title: ERB +page_id: "070.010" +--- + +## ERB + +Originally, ERB was a library for embedding Ruby script into text documents. It has been adopted as (one of the) template engines, such as Rails, that you can embed code in HTML between the tags likes these: <% … %>. + +In the same way, TreeFrog Framework uses tags <% … %> as well to embed C++ code. For convenience, I'll refer to this implementation as ERB as well. + +First, I'll make sure that the items in the configuration file *development.ini* are as follows. Unless you've changed from the default, they should be. + +``` + TemplateSystem=ERB +``` + +Then, when you generate a view in the command generator, the ERB template format will be generated. File name of the template that is generated must follow the rule that it should all be in lower case. + +``` + views/Controller-name/Action-name.erb (In lower case) +``` + +If you want to add a new template, please follow the same naming convention. + + +## Relationship of View and Action + +I would like to review the relationship between view and action. As an example, when the bar action of the Foo controller calls the render() method without arguments, the content of the following template will be output. + +``` + views/foo/bar.erb +``` + +If you call the render() method with an argument, the contents of the appropriate template will be output. + +You can add template files as you like. But consider, once you have added a new one (or several), you must run the following command once in the view directory: + +``` + $ cd views + $ make qmake +``` + +That's all that was needed to do in order to add the new template which will has been added to the makefile entry of the build.
    +Remember to reflect the new template that you've added in the shared library. + +##### In brief: After you have added a template file, use the "make qmake" command. + +## To Output a String + +We are going to the output the string "Hello world". There are two ways to do this. First, we can use the following method: + +``` + <% eh("Hello world"); %> +``` + +This eh() method prints the string using the HTML escape for cross-site scripting protection. If you do not want to use escape processing, use the echo() method instead. + +Another way is to use the notation <%= … %>. This produces exactly the same results as using the eh() method does. + +``` + <%= "Hello world" %> +``` + +Again if you do not want to use escape, you can use <%== … %> which gives exactly the same results as using the echo() method. + +``` + <%== "

    Hello world

    " %> +``` + +**Note:**
    +When you write <%= … %> in the original (eRuby) specification, it outputs a string WITHOUT HTML escape. In TreeFrog, the idea is that by using the shorter code for indicating HTML escape, safety is increased, based on the possibility that extra bits of code can easily be omitted in error. It seems to be becoming the mainstream in recent years. + +## Display of a Default Value + +If a variable is an empty string, let's display the default value as an alternative.
    +By writing as follows, if the variable *str* is empty, then the string "none" is displayed. + +``` + <%= str %|% "none" %> +``` + +## To Use the Object Passed from the Controller + +In order to display the object that is exported from the controller by the texport() method, first declare type (class) and variable names using the the tfetch() macro or T_FETCH macro. We'll refer to this operation as 'fetch'. + +``` + <% tfetch(Blog, blog); %> + <%= blog.title %> ← output tha value of blog.title +``` + +The variable (object) that has been fetched, can be accessed later in the same file. In other words, it becomes a local variable. + +You might ask if fetch processing has to be processed each time of using, but you could see that the fetch processing does not have to be run every time you want to use the variable. + +The fetched object is a local variable in a function. Therefore, if we fetched the same object twice, because the variables are the same we would have defined twice, giving an error at compile time. Of course, it would not be a compile-time error if we divided the block, but it probably would not make much sense to do so. + +##### In brief: Use the fetch case only once. + +If you export objects of the type int and QString type, then you can only output in the tehex() function. Fetch processing eliminates the need for it to be this way. + +``` + <% tehex(foo); %> +``` + +Understand that tehex() function as a combination of the eh() and the fetch() method. There is a difference in defining between fetching and local variables, as you can see in here, function variable (*foo* in this example) outputs the value without defining. + +If you do not want to escape the HTML processing, use the techoex() function, which is a combination of echo() and fetch() methods. In the same way, the variable is not defined. + +In addition, there is another way to export the output objects. You can use the code <=$ .. %>. Note that there must be no space between '$' (dollar) and '=' (equal). +This produces exactly the same result. + +``` + <%=$ foo %> +``` + +Codes have been considerably simplified. +This means that you can replace the tehex() method with the "=$" code. +Similarly, <% techoex(..); %> can be rewritten in the notation <==$ .. %>. + +To sum up, to export an object of int type or QString type, I think it's better to output using the notation <=$ .. %>, unless you want to output just once (in which case, use the fetch process ). + +**In brief: Use \<=\$ .. %> to export objects that do not output only once.** + +## How to Write Comments + +The following is an example of how to write a comment. Nothing will be output as HTML. However, its contents will remain in the C++ code. + +``` + <%# comment area %> +``` + +After the C++ code is placed in the views/_src directory, it is compiled. Look here if you want to refer to the C++ code. + +## Include Files + +If you use a class, such as a model, in the ERB template, you will need to include the header file in the same way as with C++. Note that it is not automatically included. +Include these next: + +``` + <%#include "blog.h" %> +``` + +Note that there must be no space between '#' and 'include'. In this example, the blog.h file is to be included. + +Remember that the template is converted to C++ code as it is, so don't forget to include the template file. + +## Loop + +Let's write a loop to use on a list. For example, take the list blogList which is made up of objects called Blog, it looks like this (if it is exporting an object, do the fetch processing in advance): + +``` + <% QListIterator i(blogList); + while ( i.hasNext() ) { + const Blog &b = i.next(); %> + ... + <% } %> +``` + +You can use the foreach statement from Qt which makes coding shorter: + +``` + <% foreach (Blog b, blogList) { %> + ... + <% } %> +``` + +This looks more like C++. + +## Creating an \ Tag + +To create an \ tag, use the linkTo() method: + +``` + <%== linkTo("Back", QUrl("/Blog/index")) %> + ↓ + Back +``` + +In the linkTo() method, other arguments can be specified; see the [API reference](http://treefrogframework.org/tf_doxygen/classes.html){:target="_blank"} for more information. + +You could also use the url() method to specify a URL. Specify the controller name, with the url as the first argument, and the action name as the second argument. + +``` + <%== linkTo("Back", url("Blog", "index")) %> + ↓ + Back +``` + +If the template is on the same controller, you use the urla() method and specify only the action name: + +``` + <%== linkTo("Back", urla("index")) %> + ↓ + Back +``` + +Using JavaScript, the link and confirmation dialog can be written as follows: + +``` + <%== linkTo(tr("Delete"), urla("remove", 1), Tf::Post, "confirm('Are you sure?')") %> + ↓ + Delete +``` + +Now let's add an attribute to the tag, using the THtmlAttribute class: + +``` + <%== linkTo("Back", urla("index"), Tf::Get, "", THtmlAttribute("class", "menu")) %> + ↓ + Back +``` + +You can use the short a() method code to generate the same THtmlAttribute output: + +``` + <%== linkTo("Back", urla("index"), Tf::Get, "", a("class", "menu")) %> +``` + +If there is more than one attribute, use '\|' operator: + +``` + a("class", "menu") | a("title", "hello") + ↓ + class="menu" title="hello" +``` + +By the way, there is the anchor() method, that aliases the linkTo() method; they can be used in exactly the same way. + +In addition, many other methods are available, see the [API Document](http://treefrogframework.org/tf_doxygen/classes.html){:target="_blank"} + +## Form + +We are going to post the data to the server from a browser form. In the following example, we use the formTag() method to post data to create action on the same controller. + +``` + <%== formTag(urla("create"), Tf::Post) %> + ... + ... + +``` + +You may think that you can do the same thing by describing the form tag, rather than the formTag() method, but when you do use the method it means that the framework can do CSRF measures. From a security point of view, we should do so. To enable this CSRF measures, please set the items as follows in the configuration file application.ini. + +``` + EnableCsrfProtectionModule=true +``` + +This framework makes it illegal to guard post data. However, following various repeated tests during development, of course you can disable the protection temporarily. + +For more information on CSRF measures, see also the chapter [security]({{ site.baseurl }}/en/user-guide/security/index.html){:target="_blank"}. + +## Layout + +The layout is a template on which to outline the design commonly used in the site. It is not possible to place an HTML element freely in the layout, except in the header part, menu section, and footer section. + +There are four ways to interact with the layout when the controller requests view, as follows: + +1. Set the layout for each action. +2. Set the layout for each controller. +3. Use the default layout. +4. Do not use layout. + +Only one layout is used when drawing the view, but a different layout can be used if the above list gives it a higher priority. So, for example, this means that rather than "layout that is set for each controller," being used, the rule "layout is set for each action" takes precedence. + +Let's take the example of a very simple layout (as per the following). It is saved with the extension .erb. The location of the layout is the view/layouts directory. + +``` + + + + + Blog Title + + ... +<%== yield() %> + ... + +``` + +The important part here is the line; <%== yield(); %>. This line outputs the contents of the template. In other words, when the render() method is called, the content of the template will be merged into the layout. + +By using a layout like this, you can put together a common design, such as headers and footers, for the site. Changing the design of the site then becomes much easier, because all you need to change is the layout. + +##### In brief: Layout is the overall outline for the site design. + +Now, I will describe each method to set the layout. + +**1.Set the layout for each action** + +You can set the layout name as the second argument of the render() method when it is called in the action. +This example layout simplelayout.erb is used here. + +```c++ +render("show", "simplelayout"); +``` + +Now, the contents of the show template, merged into the layout of simplelayout, will be returned as a response. + +**2.Set the layout for each controller** + +In the constructor of the controller, call the setLayout() method, and specify the layout name as an argument. + +```c++ +setLayout("basiclayout"); // basiclayout.erb the layout used +``` + +**3.Use the default layout** + +The file application.erb is the default layout file. If you do not specify a particular layout, this default layout is used. + + +**4.Do not use layout** + +If none of the above three conditions are met, no layout is used. In addition, if you want to specify that a layout is not used, use an action to call the function setLayoutDisabled(true). + +## Partial Template + +If you viewing a website, you will often notice an area of the page which is constant, that is it shows the same content on multiple pages. Perhaps it's an advertising area, or some kind of toolbar. + +To work on a Web application in cases like this, besides the methods discussed above for including such an area in the layout, there is also a way to share content by cutting the area into a "partial" templates. + +First, cut out the part you want to use on multiple pages, then save it to the views/partial directory as a template, with the .erb extension. We can then draw it with the renderPartial() method. + +``` + <%== renderPartial("content") %> +``` + +The contents of *content.erb* are embedded into the original template and then output. In the partial template, you can output the value of export objects as well as the original templates. + +From the fact that both are types of merging, the layout and partial template should seem very familiar. But what can you do is to use them in a different way. + +My opinion is that it is good way to define the layout for sections such as footer and header that are always present on the page, and to use additional partial templates to display the parts that are often but not always displayed on the page. + +##### In brief: Define areas of your layout that have persistent and not persistent content and put them into partial templates. diff --git a/docs/de/user-guide/view/index.md b/docs/de/user-guide/view/index.md new file mode 100644 index 000000000..dc3e97c15 --- /dev/null +++ b/docs/de/user-guide/view/index.md @@ -0,0 +1,19 @@ +--- +title: View +page_id: "070.0" +--- + +## View + +The role of view in a Web application is to generate an HTML document from a response. The developer creates each template for an HTML document to be embedded in a predetermined portion of the variable passed from the controller (model). + +There are two template systems so far adopted into the TreeFrog Framework. These are Otama and ERB. In both systems you can output the value dynamically using the template, and perform loops and conditional branching. + +The ERB system, such as in Ruby, is used to embed the template code in the program (library). While there are benefits in that it is easy to understand as a mechanism, it is difficult to check or change the Web design. + +Otama is a template system that completely separates the presentation logic and the templates. The advantage is that checking or changing the Web Design is easy, and programmers and designers are better able to collaboration. The disadvantage is that a little more complex mechanism is required, and that there will be a slight learning cost. + +Performance is the same in either. When the codes are built, the templates are compiled after it is converted to C++ code. One shared library (dynamic link library) is created, so both run faster. After that, it depends on user's description of the code. + +* [To the chapter of ERB template system >>]({{ site.baseurl }}/en/user-guide/view/erb.html){:target="_blank"} +* [To the chapter of Otama template system >>]({{ site.baseurl }}/en/user-guide/view/otama-template-system.html){:target="_blank"} \ No newline at end of file diff --git a/docs/de/user-guide/view/otama-template-system.md b/docs/de/user-guide/view/otama-template-system.md new file mode 100644 index 000000000..568a2808c --- /dev/null +++ b/docs/de/user-guide/view/otama-template-system.md @@ -0,0 +1,402 @@ +--- +title: Otama Template System +page_id: "070.020" +--- + +## Otama Template System + +Otama is a template system that completely separates the presentation logic from the templates. It is a system made especially for the TreeFrog Framework. + +Views are created for the Otama system when the configuration file (development.ini) is edited as the following and then the generator makes a scaffolding. + +``` + TemplateSystem=Otama +``` + +The template is written in full HTML (with the .html file extension). A "mark" is used for the tag elements where logic code is to be inserted. The presentation logic file (.otm) is written with the associated C++ code, and the mark. This will be then automatically converted to C++ code when the shared view library is built. + +
    + +![View Convention]({{ site.baseurl }}/assets/images/documentation/views_conv.png "View Convention") + +
    + +Basically, a set of presentation logic and template are made for every action. The file names are [action name].html and [action name].otm (case-sensitive). The files are placed in the "views/controller-name/" directory. + +Once you have created a new template, in order for this to be reflected in the view shared library, you will need to run "make qmake" in the view directory + +``` + $ cd views + $ make qmake +``` + +If you have not already done so, it is recommended that you read the ERB chapter before continuing with this chapter, since there is much in common between the two template systems. Also, there is so much to learn about the Otama system that knowing ERB in advance will make it easier to understand. + +## Output a String + +We are going to the output of the statement "Hello world".
    +On the template page, written in HTML , use a custom attribute called data-tf to put a "mark" for the element. The attribute name must start with "@". For example, we write as follows: + +``` +

    +``` + +We've used paragraph tags (\ \) around the @hello mark.
    +In the mark you may only use alphanumeric characters and the underscore '_'. Do not use anything else. + +Next, we'll look at the presentation logic file in C++ code. We need to associate the C++ code with the mark made above. We write this as follows: + +``` + @hello ~ eh("Hello world"); +``` + +We then build, run the app, and then the view will output the following results: + +``` +

    Hello world

    +``` + +The tilde (~) that connects the C++ code with the mark that was intended for the presentation logic means effectively "substitute the contents of the right side for the content of the element marked". We remember that the eh() method outputs the value that is passed. + +In other words, the content between the p tags (blank in this case) is replaced with "Hello world". The data-tf attribute will then disappear entirely. + +In addition, as an alternative way of outputting the same result, it's also possible to write as follows: + +``` + @hello ~= "Hello world" +``` + +As in ERB; The combination of ~ and eh() method can be rewritten as '~='; similarly, the combination of ~ and echo() method can be rewritten as '~=='. + +Although we've output a static string here, in order to simplify the explanation, it's also possible to output a variable in the same way. Of course, it is also possible to output an object that is passed from the controller. + +##### In brief: Place a mark where you want to output a variable. Then connect the mark to the code. + +## Otama Operator + +The symbols that are sandwiched between the C++ code and the mark are called the Otama operator. + +Associate C++ code and elements using the Otama operator, and then decide how these should function. In the presentation logic, note that there must be a space on each side of the Otama operator. + +This time, we'll use a different Otama operator. Let's assume that presentation logic is written as the following (colon). + +``` + @hello : eh("Hello world"); +``` + +The Result of View is as follows: + +``` + Hello world +``` + +The p tag has been removed. This is because the colon has the effect of "replace the whole element marked", with this result. Similar to the above, this could also be written as follows: + +``` + @hello := "Hello world" +``` + +## Using an Object Passed from the Controller + +In order to display the export object passed from the controller, as with ERB, you can use it after fetching by tfetch() macro or T_FETCH() macro. When msg can export an object of QString type, you can describe as follows: + +``` + @hello : tfetch(QString, msg); eh(msg); +``` + +As with ERB, objects fetched are defined as a local variable. + +Typically, C++ code will not fit in one instruction line. To write a C++ code of multiple rows for one mark, write side by side as normal but put a blank line at the end. The blank line is considered to be one set of the parts of the mark. Thus, between one mark and the next a blank line (including a line with only blank characters) acts as a separator in the presentation logic. + +##### In brief: logic is delimited by an empty line. + +Next, we look at the case of wanting to display an export object in two different locations. In this case, if you describe it at #init, it will be called first (fetched). After that, it can be used freely in the presentation logic. It should look similar to the following: + +``` + #init : tfetch(QString, msg); + + @foo1 := msg + + @foo2 ~= QString("message is ") + msg +``` + +With that said, for exporting objects that are referenced more than once, use the fetch processing at *#init*. + +Here is yet another way to export output objects.
    +Place "$" after the Otama operator. For example, you could write the following to export the output object called *obj1*. + +``` + @foo1 :=$ obj1 +``` + +This is, output the value using the eh() method while fetch() processing for obj1. However, this process is only an equivalent to fetch processing, the local variable is not actually defined. + +To obtain output using the echo() method, you can write as follows: + +``` + @foo1 :==$ obj1 +``` + +Just like ERB. + +##### In brief: for export objects, output using =$ or ~=$. + +## Loop + +Next, I will explain how to use loop processing for repeatedly displaying the numbers in a list.
    +In the template, we want a text description. + +``` + + + + + +``` + +That is exported as an object in the list of Blog class named blogList. We want to write a loop using a for statement. The while statement will also be similar. + +``` + @foreach : + tfetch(QList, blogList); /* Fetch processing */ + for (auto &b, blogList) { + %% + } + + @id ~= b.id() + + @title ~= b.title() + + @body ~= b.body() +``` +  +The %% sign is important, because it refers to the entire element (*@foreach*) of the mark. In other words, in this case, it refers to the element from \ up to \. Therefore, by repeating the \ tags, the foreach statement which sets the value of each content element with *@id*, *@title*, and *@body*, results in the view output being something like the following: + +``` + + 100 + Hello + Hello world! + + 101 + Good morning + This morning ... + + : (← Repeat the partial number of the list) +``` + +The data-tf attribute will disappear, the same as before. + +## Adding an Attribute + +Let's use the Otama operator to add an attribute to the element.
    +Suppose you have marked such as the following in the template: + +``` +Message +``` + +Now, suppose you wrote the following in the presentation logic: + +``` + @spancolor + echo("class=\"c1\" title=\"foo\""); +``` + +As a result, the following is output: + +``` + Message +``` + +In this way, by using the + operator, you can add only the attribute.
    +As a side note, you cannot use the eh() method instead of the echo() method, because this will take on a different meaning when the double quotes are escaped. + +Another method that we could also use would be written as follows in the presentation logic: + +``` + @spancolor +== "class=\"c1\" title=\"foo\"" +``` + +echo() method can be rewritten to '=='. + +In addition, for the same output result, the following alternative method could be also written like: + +``` + @spancolor +== a("class", "c1") | a("title", "foo") +``` + +The a() method creates a THtmlAttribute object that represents the HTML attribute, using \| (vertical bar) to concatenate these. It is not an THtmlAttribute object after concatenation but, if you output with the echo() method, they are converted to a string of *key1="val1", key2="val2"…*, means that attributes are added as a result. + +You may use more if you wish. + +## Rewriting the \ Tag + +The \ tag can be rewritten using the colon ':' operator. It acts as described above.
    +To recap a little; the \ tag is to be marked on the template as follows: + +``` +Back +``` + +As an example; we can write the presentation logic of the view (of the Blog) as follows: + +``` + @foo :== linkTo("Back", urla("index")) +``` + +As a result, the view outputs the following: + +``` +Back +``` + +Since the linkTo() method generates the \ tag, we can get this result. Unfortunately, the class attribute that was originally located has disappeared. The reason is that this operator has the effect of replacing the whole element. + +If you want to set the attribute you can add it as an argument to the linkTo() method: + +``` + @foo :== linkTo("Back", urla("index"), Tf::Get, "", a("class", "c1")) +``` + +The class attribute will also be output as a result like the same as above. + +Although attribute information could be output, you wouldn't really want to bother to write such information in the presentation logic.
    +As a solution there is the \|== operator. This has the effect of merging the contents while leaving the information of the attributes attached to the tag. + +So, let's rewrite the presentation logic as follows: + +``` + @foo |== linkTo("Back", urla("index")) +``` + +As a result, the view outputs the following: + +``` +Back +``` + +The class attribute that existed originally remains; it does NOT disappear. + +The \|== operator has a condition to merge the elements. That is the elements must be the same tags. In addition, if the same attribute is present in both, the value of the presentation logic takes precedence. + +By using this operator, the information for the design (HTML attributes) can be transferred to the template side. + +##### In brief: Leave the attribute related to the design of the template and merge it by using the \|== operator. + +**Note:**
    +The \|== operator is only available in this format (i.e. \|== ), neither '\|' on its own, nor '\|=' will work. + +## Form Tag + +Do not use the form tag \ to POST data unless you have enabled the CSRF measures. It does not accept POST data but only describes the form tag in the template. We need to embed the secret information as a hidden parameter. + +We use the \ tag in the template. After putting the mark to the \ tag of the template, merge it with the content of what the formTag() method is outputting + +Template: + +``` + : +
    + : +``` + +Presentation logic: + +``` + @form |== formTag( ... ) +``` + +You'll be able to POST the data normally. + +For those who have enabled CSRF measures and want to have more details about security, please check out the chapter [security]({{ site.baseurl }}/en/user-guide/security/index.html){:target="_blank"}. + +## Erasing the Element + +If you mark *@dummy* elements in the template, it is not output as a view. Suppose you wrote the following to the template. + +``` +
    +

    Hello

    +

    message ..

    +
    +``` +Then, the view will make the following results. + +``` +
    +

    Hello

    +
    +``` + +## Erasing the Tag + +You can keep a content and erase a start-tag and an end-tag only.
    +For example, when using a layout, the \ tag is outputted by the layout file side, so you don't need to output it anymore on the template side, but leave the \ tag on the template side if you want your layout have based on HTML.
    +Suppose you wrote the following to the template. + +``` + +

    Hello

    + +``` + +Then, the view will make the following results. + +``` +

    Hello

    +``` + +You use these when you want to keep it in the Web design, but erase it from the view. + +## Including the Header File + +We talked about the presentation logic template being converted to C++ code. The header and user-defined files will not be included automatically and you must write them by yourself. However, basic TreeFrog header files can be included. + +For example, if you want to include *user.h* and *blog.h* files, you would write these in at the top of the presentation logic. + +``` + #include "blog.h" + #include "user.h" +``` + +All the same as the C++ code!
    +Lines beginning with an #include string are moved directly to the code view. + +## Otama Operator + +The following table describes the Otama operator which we've been discussing. + +
    + +| Operator | Description | Remarks | +|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| : | **Element replacement**
    The components and subcomponents that are markedwill be completely replaced and output by the string inside the eh() or echo() method which are on the right-hand side of this operator. | %% means the elements themselves that can be replaced. +| ~ | **Content replacement**
    The content of marked elementswill be replaced and output by the string inside the eh() or echo() method which are on the right-hand side of this operator. | | +| + | **Attribute addition**
    If you want to add an attribute to an element that is marked, use this operator plus a string inside the echo() method, which is supposed to be output on the right-hand side of this operator. | += is HTML escaping, perhaps not used that much. | +| \|== | **Element merger**
    Based on the marked elements, the specified strings will be merged on the right-hand side of this operator. | '\|' and '\|=' are disabled. | + +

    + +Extended versions of these four operators are as follows. +With the echo() statement and eh() statement no longer being needed, you'll be able to write shorter code. + +
    + +| Operator | Description | +|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| :=
    :==
    :=$
    :==$ | Element replaced by an HTML escaped variable.
    Element replaced by a variable.
    Element replaced by an HTML escaped export object.
    Element replaced by an export object. | +| ~=
    ~==
    ~=$
    ~==$ | Content replaced by an HTML escaped variable.
    Content replaced by a variable.
    Content replaced by an HTML escaped export object.
    Content replaced by an export object. | +| +=
    +==
    +=$
    +==$ | Add an HTML escaped variable to an attribute.
    Add a variable to an attribute.
    Add an HTML escaped export object to an attribute.
    Add an export object to an attribute. | +| \|==$ | Element merged with an export object. | + +

    + +## Comment + +Please write in the form of /*.. */, if you want to write a comment in the presentation logic. + +``` + @foo ~= bar /* This is a comment */ +``` + +**Note:** In C++ the format used is "// .." but this can NOT be used in the presentation logic. \ No newline at end of file diff --git a/docs/de/user-guide/view/static-contents.md b/docs/de/user-guide/view/static-contents.md new file mode 100644 index 000000000..30aa16c94 --- /dev/null +++ b/docs/de/user-guide/view/static-contents.md @@ -0,0 +1,97 @@ +--- +title: Static Contents +page_id: "070.030" +--- + +## Static Contents + +Place static contents that is accessible from the browser in the *public* directory. Only save published files here. + +For example, assuming that an HTML file is in *public/sample.html*. When you access *http://URL:PORT/sample.html* from the browser, in case the application server (AP server) is running, its content will be displayed. + +After creating the skeleton of the application by using the generator command, the following subdirectories will be created. + +
    + +| Directory | File Type | URL path | +|----------------|------------------------------|-------------| +| public/images/ | Image files | /images/... | +| public/js/ | Javascript files | /js/... | +| public/css/ | Cascading Style Sheets (CSS) | /css/... | + +

    + +You can make subdirectories freely within the public directory. + +## Internet Media Type (MIME type) + +When the web server returns static content, the rule is to set the internet media type (MIME type) in the response's content-type header field. These are the strings such as "text/html" and "image/jpg". Using this information, the browser can determine the format in which the data has been sent. + +TreeFrog Framework returns the media type by using the file extension and referring to the defined content in the *config/initializers/internet_media_types.ini* file. In that file, line by line, file extensions and internet media types are defined and connected with "=". It is as in the following table. + +``` + pdf=application/pdf + js=application/javascript + zip=application/zip + : +``` + +If the Internet media types don't cover your needs, you can add other types in this file. After doing so, you should restart the AP server to reflect the definition information that you added. + +## Error Display + +The AP server is always required to return some response even if some error or exception occurs. In these cases, the status codes for the error responses are defined in [RFC](http://www.ietf.org/rfc/rfc2616.txt){:target="_blank"}.
    +In this framework, the contents of the following files will be returned as the response when an error or exception occurs. + +
    + +| Cause | Static File | +|--------------------------|-----------------| +| Not Found | public/404.html | +| Request Entity Too Large | public/413.html | +| Internal Server Error | public/500.html | + +

    + +By editing these static files, you can change what to display. + +By calling the function from the action as follows, you will be able to return the static file to indicate the error. In this way, when 401 is set as the status code of the response, the contents of *public/401.html* would be returned. + +```c++ +renderErrorResponse(401); +``` + +Furthermore, as another method of displaying an error screen into the browser, it is redirecting to the given URL. + +```c++ +redirect(QUrl("/401.html")); +``` + +## Send a File + +If you want to send a file from the controller, use the sendFile() method. As the first argument, specify the file path, and as the second argument, specify the content type. The file sent is not required to be in the *public* directory. + +```c++ +sendFile("filepath.jpg", "image/jpeg"); +``` + +If you send a file in this function, then the file download process is implemented on the Web browser side. A dialog is displayed, asking if the user wants to open or to save the file. This function sends the file as HTTP response which it is the same treatment as the render() method. Therefore, the controller can no longer output the template by the render() method. + +By the way, as for the file path here, if you specify an absolute path, it would ensure that it will be found. If you use the Tf::app()->webRootPath() function, you can obtain the absolute path to the application directory route, so you can easily create the absolute path to the file. In order to use this function, please include TWebApplication in the header file. + +```c++ +#include +``` + +## Send Data + +To send the data into the memory, instead to a file, you can use the sendData() method as follows. + +```c++ +QByteArray data; +data = ... +sendFile(data, "text/plain"); +``` + +You can omit the access process to reduce the overhead compared to the sendFile() method.
    +Similarly, it means the file download operation is executed on the Web browser side, after that you cannot call the render() method (it would not work if you did). \ No newline at end of file diff --git a/docs/de/user-guide/websocket/index.md b/docs/de/user-guide/websocket/index.md new file mode 100644 index 000000000..3c02b80bb --- /dev/null +++ b/docs/de/user-guide/websocket/index.md @@ -0,0 +1,265 @@ +--- +title: WebSocket +page_id: "090.0" +--- + +## WebSocket + +WebSocket is a communication standard that performs bidirectional communication between server and client, and is widely supported by browsers. + +HTTP repeatedly establishes connections and disconnections for every request that is made and it is not supposed to establish connections for a long time.
    +WebSocket, on the other hand, maintains the TCP connection once it has been established successfully. During a persisting connection, messages can be sent from either side. Because it is assumed that a connection will be long-lasting, so-called **Ping/Pong** frames are defined to confirm the other side's life and death. + +Additionally, since the connection is stateful (maintaining an established connection), you don't need to return the session ID by using a cookie. + +##### In short: WebSocket is a stateful and bidirectional communication + +In case the client is a browser, the connection will be lost, if you move to another page or if the WebSocket connection established for a page is closed. + +Since WebSocket in the browser is implemented with JavaScript, it is natural that the Websocket connection will be lost if its context (object) disappears, therefore it can be considered that the timing of the disconnect is associated with the page transition. In practice it is considered that there are not many cases in which a connection is actually long-lasting. + +Now, let's try to prepare a WebSocket that is compatible with the browser and write some JavaScript. + +First, we will generate a WebSocket object. As for the parameters, pass in the URL starting with *ws://* or *wss://*.
    +The connection processing is performed at the time of creation. + +The following event handlers can be registered for the WebSocket object: + +
    + +| Name | Description | +|-----------|----------------| +| onopen | open handler | +| onclose | close handler | +| onmessage | message handler | +| onerror | error handler | + +

    + +The methods of the WebSocket object are as follows: + +
    + +| Method | Description | +|---------------|--------------| +| send(msg) | send message | +| close(code) | disconnect | + +

    + +For more insight details, please visit [http://www.w3.org/TR/websockets/](http://www.w3.org/TR/websockets/){:target="_blank"}. + +## Make a chat application + +Let's make a chat application based on these handlers.
    +The main function of this application is as follow: the user can enter his name and a message into a very basic HTML form. After the user has clicked the "send" button, the application will send the data to a server and stores it inside a database. Whenever any visitor of this application accesses the Web page, for example, the most recent 30 messages will be delivered to the visitor and displayed in the chat window. + +We will also talk about how to send messages only to persons who have *subscribed* to a specific *topic*. + +Now let's start with the implementation, starting with the client side. **Note:** the following example uses [jQuery](https://jquery.com/){:target="_blank"}. + +**HTML (excerpt)**
    +Save it as public/index.html. + +``` + +
    + +Name +
    + +``` + +Here is an example made with JavaScript. + +```js +$(function(){ + // create WebSocket to 'chat' endpoint + ws = new WebSocket("ws://" + location.host + "/chat"); + + // message received + ws.onmessage = function(message){ + var msg = escapeHtml(message.data); + $("#log").append("

    " + msg + "

    "); + } + + // error event + ws.onerror = function(){ + $("#log").append("[ Error occurred. Try reloading. ]"); + } + + // onclose event + ws.onclose = function(){ + $("#log").append("[ Connection closed. Try reloading. ]"); + } +}); +// Sending as one message containing 'Name' and 'Time' +function sendMessage() { + if ($('#msg').val() != '') { + var name = $.trim($('#name').val()); + if (name == '') { + name = "(I'm John Doe)"; + } + name += ' : ' + (new Date()).toISOString() + '\n'; + ws.send(name + $('#msg').val()); + $('#msg').val(''); // clear + } +} +``` + +Next, let's implement the server side by using scaffold. + +``` + $ tspawn new chatapp + created chatapp + created chatapp/controllers + created chatapp/models + : +``` + +Create an endpoint for the WebSocket interaction.
    +This is made with the name ('chat' in this example) set as the path of URL passed when generating the JavaScript WebSocket object. Otherwise it will not work! + +``` + $ cd chatapp + $ tspawn websocket chat + created controllers/applicationendpoint.h + updated controllers/controllers.pro + created controllers/applicationendpoint.cpp + : +``` + +The generated *chatendpoint.h* looks then as follows.
    +There is no need to modify it in particular. + +```c++ +class T_CONTROLLER_EXPORT ChatEndpoint : public ApplicationEndpoint +{ +public: + ChatEndpoint() { } + ChatEndpoint(const ChatEndpoint &other); +protected: + bool onOpen(const TSession &httpSession); // open handler + void onClose(int closeCode); // close handler + void onTextReceived(const QString &text); // text receive handler + void onBinaryReceived(const QByteArray &binary); // binary receive handler +}; +``` + +**Explaining the onOpen() handler:**
    +The HTTP session object at that time is passed in the *httpSession* argument. The endpoint is read only and its content cannot be changed (I may deal with this in the future). + +Instead, let's save the information here using the *WebSocketSession* object. Within each method of the endpoint class, the information can be retrieved using the *session()* method. By the way, since the information is stored in the memory, if you store data of a large size, memory will be compressed if the connection load increases. + +Furthermore, the WebSocket connection can be rejected if the return value of *onOpen()* is *false*. If you don't want to accept all connection requests, it is possible to implement some sort secret values stored in HTTP sessions, for example, accepting them only if they are correct. + +Next is the *chatendpoint.cpp*.
    +We want to send the received text to all the subscribers. This can be done by using the *publication/subscription* (Pub/Sub) method. + +First, the recipient needs to subscribe to a certain "topic". When someone is sending a message to that "topic", that message will delivered to all its subscribers. + +The code for this behaviour looks like this: + +```c++ +#define TOPIC_NAME "foo" +ChatEndpoint::ChatEndpoint(const ChatEndpoint &) + : ApplicationEndpoint() +{ } + +bool ChatEndpoint::onOpen(const TSession &) +{ + subscribe(TOPIC_NAME); // Start subscription + publish(TOPIC_NAME, QString(" [ New person joined ]\n")); + return true; +} + +void ChatEndpoint::onClose(int) +{ + unsubscribe(TOPIC_NAME); // Stop subscription + publish(TOPIC_NAME, QString(" [ A person left ]\n")); +} + +void ChatEndpoint::onTextReceived(const QString &text) +{ + publish(TOPIC_NAME, text); // Send message +} + +void ChatEndpoint::onBinaryReceived(const QByteArray &) +{ } +``` + +### Build + +In this case, I will not use **VIEW**, so I will remove it from the build. +Edit *chatapp.pro* as follows and save it. + +``` + TEMPLATE = subdirs + CONFIG += ordered + SUBDIRS = helpers models controllers +``` + +Build command: + +``` + $ qmake -r + $ make (nmake on Windows) + $ treefrog -d + + (stop command) + $ treefrog -k stop +``` + +Let's start the browser and access *http://(host):8800/index.html*. + +Did it work properly? + +We are now publishing what we just have implemented, so the result should look like the following: [http://chatsample.treefrogframework.org/](http://chatsample.treefrogframework.org/){:target="_blank"} + +The following functions are added from the sample above: + +* 30 most recent messages are stored in DB +* the message is sent immediately after connection (*onOpen()*) +* adding some stylish CSS makes your application look good + +## Keep alive + +When the non-communication state lasts for a long time in a TCP session, communication devices, such as routers, will stop routing. In this case, even if you send a message from the server, it will not reach the client anymore. + +In order to avoid this, it is necessary to keep connection alive by periodically communicating. *Keep alive* in WebSocket is achieved by sending and receiving *Ping/Pong* frames. + +In TreeFrog, it is set by the return value of the *keepAliveInterval()* of the endpoint class. The time unit is in seconds. + +```c++ +int keepAliveInterval() const { return 300; } +``` + +If the value is 0, the keep-alive function will not work. The default is 0 (not keepalive). + +By keeping alive, you can check not only whether the communication path is valid, but also you can check that the host software is not down. However, the API that detects it is currently available (2015/6), but not implemented in TreeFrog yet. + +**Reference**
    +tspawn HELP + +``` + $ tspawn -h + usage: tspawn [args] + Type 'tspawn --show-drivers' to show all the available database drivers for Qt. + Type 'tspawn --show-driver-path' to show the path of database drivers for Qt. + Type 'tspawn --show-tables' to show all tables to user in the setting of 'dev'. + Type 'tspawn --show-collections' to show all collections in the MongoDB. + + Available subcommands: + new (n) + scaffold (s) [model-name] + controller (c) action [action ...] + model (m) [model-name] + usermodel (u) [username password [model-name]] + sqlobject (o) [model-name] + mongoscaffold (ms) + mongomodel (mm) + websocket (w) + validator (v) + mailer (l) action [action ...] + delete (d) +``` \ No newline at end of file