Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site













SourceForge.net Logo

Skylark Tutorial


Table of contents

 

1. SKYLARK handlers and path patterns

2. Witz templates

3. Witz links to handlers

4. Combining Witz templates using #define and #include

5. FORM GET

6. FORM POST

7. Session variables

8. Ajax support

9. Connecting SQL database

10. Advanced SQL

11. Language support

12. Packs

 


1. SKYLARK handlers and path patterns

Skylark application is divided to 'handlers' which provide response to various HTTP requests based on url pattern:

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http << "<!DOCTYPE html><html><body>Hello world!</body></html>";

}

 

SKYLARK(Param, "*/param")

{

    http << "<!DOCTYPE html><html><body>Parameter: " << http[0] << "</html></body>";

}

 

SKYLARK(Params, "params/**")

{

    http << "<!DOCTYPE html><html><body>Parameters: ";

    for(int i = 0; i < http.GetParamCount(); i++)

        http << http[i] << " ";

    http << "</html></body>";

}

 

SKYLARK(CatchAll, "**")

{

    http.Redirect(HomePage);

}

 

SKYLARK(Favicon, "/favicon.ico")

{

    http.ContentType("image/png") << LoadFile(GetDataFile("favicon.png"));

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Handlers are introduced by macro SKYLARK, first parameters is the name of handler (macro creates a function that is named this way and this id is also used in various places like page templates) and path pattern which handler is supposed to respond to.

Handler is represented by C++ function with single parameter Http& http, which is used for request processing. SKYLARK macro creates a header for this function and also registers it with Skylark so that requestes matching the path pattern are dispatched to this function. Important note: as SKYLARK macro uses global constructors to register handlers, handlers should be put into .icpp files (unless they are in the same file with 'main').

Path pattern can contain parameter placeholders '*' (see Param handler), which are then provided by http as parameters (through operator[](int)). Path is always split by '/' characters and each part must either be text or placeholder - patterns like "something*/to*avoid" are not allowed

Special wildcard '**' represents any other number of placeholders (see Params); in special case it is useful to catch all unresolved requests to website and Redirect them to the application homepage (see CatchAll).

The priority of various handlers for particular request is determined by number of text parts matched, which means that for request "foo/bar/32" placeholder "foo/bar/*" has priority over "foo/*/*".

Another concern is setting of root path for application, in code above done by "root = "myapp";". This defines the root path of application, in other words HomePage handler will react to "myapp" path, Param handler to e.g. "myapp/test/param" etc. In situation where this is not desirable, it is possible to exclude root by adding '/' at the start of path, as demonstrated by Favicon handler.

MyApp().Run() starts a HTTP server (and also SCGI server) on default port 8001, so after starting application, you should be able to access it from your browser by entering

127.0.0.1:8001/myapp

We then recommend to try e.g.

127.0.0.1:8001/myapp/anything/param

127.0.0.1:8001/myapp/params/1/2/3

127.0.0.1:8001/myapp/anythingelse

127.0.0.1:8001

Last one should result in error "Page not found", as no matching handler is present.

 


2. Witz templates

To decouple presentation layer from application logic, Skylark features template language 'Witz':

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    ValueArray va;

    va.Add(1);

    va.Add("Hello");

    ValueMap m;

    m.Add("key1", "first value");

    m.Add("key2", "second value");

 

    http("MyValue", "some value")

        ("MyRawValue", Raw("<b>raw <u>html</u></b>"))

        ("MyRawValue2", "<b>another raw <u>html</u></b>")

        ("MyArray", va)

        ("MyMap", m)

        .RenderResult("Skylark02/index");

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

 

Skylark02/index.witz:

 

<!DOCTYPE html>

<html>

<body>

MyValue: $MyValue<br>

MyRawValue: $MyRawValue<br>

MyRawValue2: $raw(MyRawValue2)<br>

<br>MyArray:<br>

$for(i in MyArray)

    $i._index. $i<br>

$endfor

<br>MyMap:<br>

$for(i in MyMap)

    $i._index. $i._key: $i<br>

$endfor

</body>

</html>

 

Witz template language loosely ressembles JavaScript. It is able to evaluate expression, supports "if" and looping through arrays or maps with "for".

Witz templates are compiled at runtime. Settings configuration variable use_caching to false makes them to compile each time they are invoked (otherwise the compilation result is cached) - this is usefull while debugging, as templates can be adjusted while application is running.

The search path for templates is set by another configuration variable path. Default value of path is set to environment variable 'UPP_ASSEMBLY__', which is set by theide to current assembly path - which means that for debugging you do not need to worry about path as long as all templates are in U++ packages and you start the application from theide. Note that path is also used to search for static files.

String values are normally HTML escaped; if you need to pass raw html code as parameter, you have to either use Raw C++ function in application logic or raw function in Witz.

 


3. Witz links to handlers

Skylark handlers are represented in witz templates as function calls or simple variables with the same identifiers as the id of handler; both yield " quoted path that matches the handler path pattern. If pattern contains parameter placeholder, it is passed as argument of the function:

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http.RenderResult("Skylark03/index");

}

 

SKYLARK(Page2, "page2")

{

    http.RenderResult("Skylark03/page2");

}

 

SKYLARK(Param, "paramtest/*")

{

    http("PARAM", http[0]).RenderResult("Skylark03/param");

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Skylark03/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<a href=$Page2>Link to page2</a><br>

$for(i in ["param_test", 123, "param_test3"])

    <a href=$Param(i)>Param test: $i</a><br>

$endfor

</body>

</html>

 

Skylark03/page2.witz:

 

<!DOCTYPE html>

<html>

<body>

<a href=$HomePage()>Back to index</a><br>

</body>

</html>

 

Skylark03/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<a href=$Page2()>Link to page2</a><br>

$for(i in ["param_test", 123, "param_test3"])

    <a href=$Param(i)>Param test: $i</a><br>

$endfor

</body>

</html>

 

 


4. Combining Witz templates using #define and #include

Witz templates can be parametrized using subblock #define id and #id insertion and combined from several files using #include:

Skylark04/base.witz:

 

<!DOCTYPE html>

<html>

<title>#TITLE</title>

<body>

#BODY

</body>

</html>

 

#define TITLE Generic title

 

Skylark04/index.witz:

 

#include Skylark04/base

 

#define TITLE MyApp title

 

#define BODY

This is MyApp body html!

 

 


5. FORM GET

Whenever Skylark founds '?' sign in the request path, it converts attached values into http's shared variable space, which is accessible using operator[].

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http.RenderResult("Skylark05/index");

}

 

SKYLARK(Submit, "submit")

{

    http("RESULT", ToUpper((String)http["id"]))

        .RenderResult("Skylark05/submit");

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Skylark05/index.witz

 

<!DOCTYPE html>

<html>

<body>

<form action=$Submit method="get" accept-charset="utf-8" enctype="multipart/form-data">

  <P>

   <INPUT type="text" name="id">

   <INPUT type="submit" value="Submit">

  </P>

</form>

</body>

</html>

 

Skylark05/submit.witz

 

<!DOCTYPE html>

<html>

<body>

Result: $RESULT<br>

<a href=$HomePage>Back to homepage</a>

</body>

</html>

 

 


6. FORM POST

If HTTP method is POST and content-type field of header contains standard values (either "application/x-www-form-urlencoded" or starts with "multipart/", Skylark parses the posted content and assigns posted values into shared variable space.

This makes handling FORM POSTs similar to GET with one exception: To avoid CSRF attacks, it is mandatory to include hidden field into dialog definition provided by function $post_identity() - Skylark manages everything required using session subsystem.

Request method is also part of handler path pattern (GET is default if not specified).

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http.RenderResult("Skylark06/index");

}

 

SKYLARK(Submit, "submit:POST")

{

    http("RESULT", ToUpper((String)http["id"]))

        .RenderResult("Skylark06/submit");

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Skylark06/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<form action=$Submit method="post" accept-charset="utf-8" enctype="multipart/form-data">

  <P>

   $post_identity()

   <INPUT type="text" name="id">

   <INPUT type="submit" value="Submit">

  </P>

</form>

</body>

</html>

 

 

Skylark06/submit.witz:

 

<!DOCTYPE html>

<html>

<body>

Result: $RESULT<br>

<a href=$HomePage>Back to homepage</a>

</body>

</html>

 

 

Note: There also exists alternative POST_RAW method marker in SKYLARK handler definition - such handler also reacts to POST requests, but it avoids identity checks to prevent CSRF attacks.

 


7. Session variables

Http::SessioSet method can be used to store 'session variables' that are persistent for specific browser across requests. Implementation is based on cookie, session variables are stored either in filesystem or in database (see Skylark configuration for details). Session variables are reflected in shared variable space (means its values are accessible using Http::operator[]) and are distinguished with '.' as the first character.

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http.RenderResult("Skylark07/index");

}

 

SKYLARK(Submit, "submit:POST")

{

    Value h = http[".LIST"];

    ValueArray va;

    if(IsValueArray(h))

        va = h;

    va.Add(http["id"]);

    http.SessionSet(".LIST", va);

    http.Redirect(HomePage);

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Skylark07/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<form action=$Submit method="post" accept-charset="utf-8" enctype="multipart/form-data">

  <P>

   $post_identity()

   <INPUT type="text" name="id">

   <INPUT type="submit" value="Add to list">

  </P>

</form>

List ($count(.LIST)):<br>

$for(i in .LIST)

    "$i"

$endfor

</body>

</html>

 

 


8. Ajax support

Skylark provides optional direct support for handling Ajax requests. On client side, this support is implemented using tiny JavaScript library "skylark.js".

 

#include <Skylark/Skylark.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http.RenderResult("Skylark08/index");

}

 

SKYLARK(Add, "add:POST")

{

    String r = AsString(Nvl(http.Int("number1")) + Nvl(http.Int("number2")));

    http.Ux("result", "The result is: " + r)

        .UxSet("number1", r);

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    MyApp().Run();    

}

 

Skylark08/index.witz:

 

<!DOCTYPE html>

<html>

<script type="text/javascript" src="$static/Skylark/skylark.js"></script>

<body>

$js_identity()

<div id="numbers">

   <INPUT type="text" id="number1">

   <INPUT type="text" id="number2">

   <INPUT type="button" value="Add" onclick='UxPost($Add, "numbers")'><br>

</div>

<div id="result"/>

</body>

</html>

 

$static variable is set by Skylark to the static_dir configuration parameter, if missing it is set to root/static, which makes Skylark to handle requests for static files (we expect that for production environment, serving static files will be off-loaded to webserver). JavaScript function UxPost recursively scans through html elements provided as second, third etc.. parameters and gathers all ids and values of input elements, packs them into POST format and sends to Skylark handler specified as first parameter. In this case it basically means that is sends number1 and number2 values to server handler Add. Note the use of $js_identity() call - this plays the same role in prevention of CSRF attacks as $post_identity for FORM POSTs (alternative UxGet function uses GET requests and does not require $js_identity). Also note the use of single quotes ' for onclick handler - this is because links to handlers are expanded as double-quoted strings.

The response to UxPost or UxGet requests is Skylark arbitrary format that can be used to directly alter current web page. Ux replaces innerHTML of matching (by id) element, UxSet sets the value attribute of matching element. Not shown here, UxRender is similar to Ux, but renders the text from witz template and finally UxRun can be used to run any JavaScript code in the client.

 


9. Connecting SQL database

Skylark naturally uses U++ SQL support when dealing with persistent storage, using "session per-thread" mode. Because of this, database session needs to be connected when starting each thread - this is done by overloading WorkThread virtual method of SkylarkApp:

 

#include <Skylark/Skylark.h>

#include <plugin/sqlite3/Sqlite3.h>

 

using namespace Upp;

 

#define  MODEL <Skylark09/myapp.sch>

#define  SCHEMADIALECT <plugin/sqlite3/Sqlite3Schema.h>

#include <Sql/sch_header.h>

#include <Sql/sch_schema.h>

#include <Sql/sch_source.h>

 

SKYLARK(HomePage, "")

{

    Sql sql;

    sql * Select(ID, NAME, LASTNAME)

          .From(PERSON)

          .OrderBy(LASTNAME, NAME);

    ValueArray person;

    ValueMap vm;

    while(sql.Fetch(vm))

        person.Add(vm);

    http("PERSON", person)

        .RenderResult("Skylark09/index");

}

 

struct MyApp : SkylarkApp {

    virtual void WorkThread();

 

    MyApp() {

        root = "myapp";

        threads = 1; // Sqlite3 does not like threads...

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

void InitModel()

{

#ifdef _DEBUG

    SqlSchema sch(SQLITE3);

    All_Tables(sch);

    SqlPerformScript(sch.Upgrade());

    SqlPerformScript(sch.Attributes());

    sch.SaveNormal();

#endif

}

 

void OpenSQL(Sqlite3Session& sqlite3)

{

    if(!sqlite3.Open(ConfigFile("db"))) {

        LOG("Can't create or open database file\n");

        Exit(1);

    }

#ifdef _DEBUG

    sqlite3.LogErrors();

    sqlite3.SetTrace();

#endif

    SQL = sqlite3;

}

 

void MyApp::WorkThread()

{

    Sqlite3Session sqlite3;

    OpenSQL(sqlite3);

    RunThread();

}

 

void InitDB()

{

    Sqlite3Session sqlsession;

    OpenSQL(sqlsession);

    SqlSchema sch(SQLITE3);

    All_Tables(sch);

    SqlPerformScript(sch.Upgrade());

    SqlPerformScript(sch.Attributes());

 

    SQL * Insert(PERSON)(NAME,"Joe")(LASTNAME,"Smith");

    SQL * Insert(PERSON)(NAME,"Mike")(LASTNAME,"Carpenter");

    SQL * Insert(PERSON)(NAME,"Jon")(LASTNAME,"Goober");

}

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    DeleteFile(ConfigFile("db")); // for this example, always create a new DB

    InitDB();

 

    MyApp().Run();    

}

 

Skylark09/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<table border="1">

<tr>

 <th>No.</th>

 <th>First Name</th>

 <th>Last Name</th>

</tr>

$for(i in PERSON)

    <tr>

      <td>$i._index.</td>

      <td>$i.NAME</td>

      <td>$i.LASTNAME</td>

    </tr>    

$/

</table>

</body>

</html>

 

Skylark09/myapp.sch:

 

TABLE_(PERSON)

    INT_    (ID)      PRIMARY_KEY AUTO_INCREMENT

    STRING_ (NAME, 200)

    STRING_ (LASTNAME, 200)

END_TABLE

 

 


10. Advanced SQL

Http class contains some advanced convenience support for dealing with SQL databases:

It is possible to fill result set of SqlSelect into witz variable as demonstrated by HomePage handler

Http::Insert inserts a row to database, reading values of columns based on known (from .sch file) names of columns from equally named variables of shared variable space (which come from POST or GET request), as demonstrated by SubmitNew.

Http::Update updates a row in database, again based on names of columns and shared variable space, as demonstrated by SubmitEdit.

It is also possible to add shared variables based on single row select, names being then same as names of columns, as demonstrated by Edit.

 

#include <Skylark/Skylark.h>

#include <plugin/sqlite3/Sqlite3.h>

 

using namespace Upp;

 

#define  MODEL <Skylark09/myapp.sch>

#define  SCHEMADIALECT <plugin/sqlite3/Sqlite3Schema.h>

#include <Sql/sch_header.h>

#include <Sql/sch_schema.h>

#include <Sql/sch_source.h>

 

SKYLARK(HomePage, "")

{

/*

    Sql sql;

    sql * Select(ID, NAME, LASTNAME)

          .From(PERSON)

          .OrderBy(LASTNAME, NAME);

    ValueArray person;

    ValueMap vm;

    while(sql.Fetch(vm))

        person.Add(vm);

*/

    http("PERSON", Select(SqlAll()).From(PERSON).OrderBy(LASTNAME, NAME))

    .RenderResult("Skylark10/index");

}

 

SKYLARK(SubmitNew, "create/submit:POST")

{

/*

    SQL * Insert(PERSON)(NAME, http["name"])(LASTNAME, http["lastname"]);

*/

    SQL * http.Insert(PERSON);

    http.Redirect(HomePage);

}

 

SKYLARK(New, "create")

{

    http("ACTION", SubmitNew)

    .RenderResult("Skylark10/Dialog");

}

 

SKYLARK(SubmitEdit, "edit/submit/*:POST")

{

/*

    SQL * Update(PERSON)

            (NAME, http["name"])

            (LASTNAME, http["lastname"])

            .Where(ID == http.Int(0));

    ;

*/

    SQL * http.Update(PERSON).Where(ID == http.Int(0));

    http.Redirect(HomePage);

}

 

SKYLARK(Edit, "edit/*")

{

    int id = http.Int(0);

/*

    Sql sql;

    sql * Select(NAME, LASTNAME).From(PERSON).Where(ID == id);

    if(!sql.Fetch()) {

        http.Redirect(HomePage);

        return;

    }

    http("NAME", sql[NAME])("LASTNAME", sql[LASTNAME]);

*/

    http

        (Select(SqlAll()).From(PERSON).Where(ID == id))

        ("ID", id)

        ("ACTION", SubmitEdit, id)

    .RenderResult("Skylark10/Dialog");

}

 

struct MyApp : SkylarkApp {

    virtual void WorkThread();

 

    MyApp() {

        root = "myapp";

        threads = 1; // Sqlite3 does not like threads...

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

void InitModel()

{

#ifdef _DEBUG

    SqlSchema sch(SQLITE3);

    All_Tables(sch);

    SqlPerformScript(sch.Upgrade());

    SqlPerformScript(sch.Attributes());

    sch.SaveNormal();

#endif

}

 

void OpenSQL(Sqlite3Session& sqlite3)

{

    if(!sqlite3.Open(ConfigFile("db"))) {

        LOG("Can't create or open database file\n");

        Exit(1);

    }

#ifdef _DEBUG

    sqlite3.LogErrors();

    sqlite3.SetTrace();

#endif

    SQL = sqlite3;

}

 

void MyApp::WorkThread()

{

    Sqlite3Session sqlite3;

    OpenSQL(sqlite3);

    RunThread();

}

 

void InitDB()

{

    Sqlite3Session sqlsession;

    OpenSQL(sqlsession);

    SqlSchema sch(SQLITE3);

    All_Tables(sch);

    SqlPerformScript(sch.Upgrade());

    SqlPerformScript(sch.Attributes());

}

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

 

    InitDB();

 

    MyApp().Run();    

}

 

Skylark10/index.witz:

 

<!DOCTYPE html>

<html>

<body>

<table border="1">

<tr>

 <th>No.</th>

 <th>First Name</th>

 <th>Last Name</th>

</tr>

$for(i in PERSON)

    <tr>

      <td>$(i._index + 1).</td>

      <td>$i.NAME</td>

      <td>$i.LASTNAME</td>

      <td><a href=$Edit(i.ID)>Edit</a></td>

    </tr>    

$/

</table>

<a href=$New>Create new</a>

</body>

</html>

 

Skylark10/dialog.witz:

 

<!DOCTYPE html>

<html>

<body>

<FORM action=$ACTION method="post" accept-charset="utf-8" enctype="multipart/form-data">

$post_identity()

<P>

   First name: <INPUT type="text" name="name" value="$NAME"><BR>

   Last name: <INPUT type="text" name="lastname" value="$LASTNAME"><BR>

   <INPUT type="submit" value="Send" name="OK"/><BR>

</P>

</FORM>

</table>

</body>

</html>

 

Skylark10/myapp.sch:

 

TABLE_(PERSON)

    INT_    (ID)      PRIMARY_KEY AUTO_INCREMENT

    STRING_ (NAME, 200)

    STRING_ (LASTNAME, 200)

END_TABLE

 

 


11. Language support

Skylark language support is based on session variables:

".__lang__" contains integer with U++ language identifier

".language" contains the string form of this identifier (e.g. "cs-cz")

Http::SetLanguage method sets both variables and also SetLanguage (U++ Core function to switch U++ language). When loading .witz templates, Skylark first searches for templates for specific language - language can be a part of template name, e.g. index.cs-cz.witz, only if not found, 'generic' version (like  index.witz) is used. Also, after loading the session, U++ Core SetLanguage is invoked with current ".__lang__" setting, that way U++ i18n system can be used in Skylark handlers.

 

#include <Skylark/Skylark.h>

#include <plugin/sqlite3/Sqlite3.h>

 

using namespace Upp;

 

SKYLARK(HomePage, "")

{

    http("VAR", t_("Aborted by user."))

        .RenderResult("Skylark11/index");

}

 

SKYLARK(SetLanguage, "setlanguage/*")

{

    int lang = LNGFromText(http[0]);

    if(lang)

        http.SetLanguage(lang);

    http.Redirect(HomePage);

}

 

struct MyApp : SkylarkApp {

    MyApp() {

        root = "myapp";

        threads = 1; // Sqlite3 does not like threads...

    #ifdef _DEBUG

        prefork = 0;

        use_caching = false;

    #endif

    }

};

 

CONSOLE_APP_MAIN

{

#ifdef _DEBUG

    StdLogSetup(LOG_FILE|LOG_COUT);

    Ini::skylark_log = true;

#endif

    MyApp().Run();    

}

 

Skylark11/lang.witz:

 

<a href=$SetLanguage("en-us")>EN</a>

<a href=$SetLanguage("cs-cz")>CZ</a>

<br>

Current language: $.language<br>

 

Skylark11/index.witz:

 

<!DOCTYPE html>

<html>

<body>

#include lang

English version<br>

Variable value: $VAR

</body>

</html>

 

Skylark11/index.cs-cz.witz:

 

<!DOCTYPE html>

<html>

<body>

#include lang

Česká verze<br>

Hodnota proměnné: $VAR

</body>

</html>

 

 


12. Packs

Sometimes it is useful to parametrize a group of handlers so that they can be used in different contexts. For example, the Create/Edit/Delete operations in chapter 10 share many elements (e.g. table name or dialog) and by parametrizing them, it would be possible to use them with different table. Such thing is possible by creating "pack" class. Such class has to be derived from SkylarkPack base class, must have CLASSNAME typedef and must have Use method.

Pack handlers are normal methods of such class and are registred in Use method by SKYLARK_METHOD macro. To reference other method of the same class (e.g. for Redirect or as witz parameter), use THISLINK macro.

The instance of pack is created with SKYLARK_USE. This effectively creates a global variable of pack class type and register handlers for this instance, combining variable name and method name as name of handler and provided urls to get the handler url. SKYLARK_USE has "body" that gets executed during registration phase and can be used to setup parameters of pack instance.

Url links to pack methods are then expressed as instance:method in witz templates and as LINK(instance, method) in C++ code.

Note also that HandlerId is a special simple type able to store either LINK or THISLINK originated reference to pack method or pointer to handler function.

 

struct CreateEditDelete : SkylarkPack {

    HandlerId   back;

    SqlId       table;

    SqlId       key;

    SqlSet      columns;

    String      dialog;

 

    void Create(Http& http);

    void SubmitCreate(Http& http);

    void Edit(Http& http);

    void SubmitEdit(Http& http);

    void Delete(Http& http);

 

    typedef CreateEditDelete CLASSNAME;

 

    void Use();

    

    CreateEditDelete() { key = SqlId("ID"); columns = SqlSet(SqlAll()); }

};

 

Skylark12/Cud.cpp:

 

#include "Skylark12.h"

 

void CreateEditDelete::Create(Http& http)

{

    http("ACTION", THISLINK(SubmitCreate))

    .RenderResult(dialog);

}

 

void CreateEditDelete::SubmitCreate(Http& http)

{

    SQL * http.Insert(table);

    http.Redirect(back);

}

 

void CreateEditDelete::Edit(Http& http)

{

    int id = http.Int(0);

    http

        (Select(columns).From(table).Where(key == id))

        ("ID", id)

        ("ACTION", THISLINK(SubmitEdit), id)

    .RenderResult(dialog);

}

 

void CreateEditDelete::SubmitEdit(Http& http)

{

    SQL * http.Update(table).Where(key == http.Int(0));

    http.Redirect(back);

}

 

void CreateEditDelete::Delete(Http& http)

{

    SQL * SqlDelete(table).Where(key == atoi(http[0]));

    http.Redirect(back);

}

 

void CreateEditDelete::Use()

{

    SKYLARK_METHOD(Create, "create");

    SKYLARK_METHOD(SubmitCreate, "create_submit:POST");

    SKYLARK_METHOD(Edit, "edit/*");

    SKYLARK_METHOD(SubmitEdit, "submit_edit/*:POST");

    SKYLARK_METHOD(Delete, "delete/*");

}

 

Skylark12/Handlers.icpp:

 

#include "Skylark12.h"

 

void HomePage(Http&);

 

SKYLARK_USE(CreateEditDelete, Person, "person")

{

    Person.back = HomePage;

    Person.table = PERSON;

    Person.dialog = "Skylark12/dialog";

}

 

SKYLARK(HomePage, "")

{

    http("PERSON", Select(ID, FIRSTNAME, LASTNAME, EMAIL).From(PERSON)

                   .OrderBy(LASTNAME, FIRSTNAME))

    ("CREATE", LINK(Person, Create))

    .RenderResult("Skylark12/index");

}

 

SKYLARK(CatchAll, "**")

{

    http.Redirect(HomePage);

}

 

Skylark12/index.witz:

 

#include Skylark/Base

 

#define BODY

 

<table border="1" id="persons">

<tr>

 <th>First Name</th>

 <th>Last Name</th>

 <th>Email</th>

</tr>

$for(i in PERSON)

    <tr>

      <td>$i.FIRSTNAME</td>

      <td>$i.LASTNAME</td>

      <td>$i.EMAIL</td>

      <td>

          <a href=$Person:Edit(i.ID)>Edit</a>

          <a href=$Person:Delete(i.ID)>Delete</a>

      </td>

    </tr>    

$/

</table>

 

<p/>

<a href=$Person:Create>Insert new person</a>

<a href=$CREATE>Insert new person using LINK</a>

 

 


Recommended tutorials:

If you want to learn more, we have several tutorials that you can find useful:

SQL tutorial - everything you should know to work efficiency with databases within U++ framework.

Last edit by klugier on 11/20/2020. Do you want to contribute?. T++