Simple Groupware Solutions Markup Language (sgsML) tutorial

This is a small tutorial intended to help you to understand and develop web applications with sgsML.


Introduction


For many years now I'm writing web applications. Using PHP, Java, HTML, CSS, Javascript in combination with MySQL, Oracle, MsSQL, etc. always struggling with the complexity of web applications. Making changes in one point always influences many other parts of the program. Often applications have several thousand lines of code (or even more). Searching bugs or making daily changes like adding fields takes a lot of time because you need to test the whole application again. Since time is rare and deadline are sharp, it's time to optimize the common web application architecture.

Looking at bigger programs with many web applications, people start writing frameworks for common functions, managing users and parts of the user interface. But these frameworks are often very limited in functionality or require too much time to learn. Using a small framework, you still need to deal with the full complexity of coding PHP, Java, HTML, etc. Moreover when working in bigger teams with a small framework, everyone writes its own specializations for his part. This creates a unmanageable lack of consistency and increases costs for adding new functionality and fixing bugs. Therefore many companies create bigger frameworks to prevent programmers from getting too individual with their code. With Java this is enforced using object-oriented programming (maybe also with PHP in the future). But I think that all these concepts are still too difficult to learn and understand in short time.

That's why I created a new programming language only intended for web applications. You might ask: Hey, are you just creating another language with the intention to replace all the others? The answer is no. The other languages are still used, but in a different way. This is important to understand, because today programming languages are so complex in usage and syntax, and this gets harder every year.


So what is different when working with sgsML?


You no longer need to be the super nerd of a programming language. Now you can create good web applications without having learned programming for ten years. To build web applications with sgsML it is enough if you can write functions with 5 to 10 lines of code. You no longer write functions to store or load data in the database. You no longer write GUI components just like HTML editors, spreadsheets or data selectors. How can this work?
The name sgsML already indicates a relation to XML. To produce applications written in sgsML, XML version 1.0 has been chosen as the syntax for sgsML files. This format is easier to read and interpret than many other formats (e.g. ini, CSV, CSS, etc.). As said before we are talking about sgsML files, meaning that one module (e.g. companies or appointments) is written into one file with the extension ".xml".


Types


With sgsML a field is generally defined by its type. This can be a text, a date, a number, etc. So when you define a field as text with sgsML, Simple Groupware creates this field in the database with the correct type and size in the corresponding table. Furthermore Simple Groupware already knows how to display this field (You only have to decide between a horizontal or a vertical view) including sorting, searching, etc. Additionally, forms to create or edit this field inside a dataset are created automatically for you.

Syntax: Types are declared with simple_type="type" where type is one of int, float, text, password, id, hidden, select, wikiarea, codearea, textarea, htmlarea, checkbox, files, date, time, datetime ... for a complete list, see src/modules/schema/!examples.html.


Validators


To be able to validate inputs made by a user you are able define functions that take care of this. E.g. you define a field as type text and also declare that it has to be validated with the two functions myvalidate1 and myvalidate2. When a user wants to save a new dataset these two functions are called with the value of the field as a parameter and you only have to return a true/false-like answer to tell the program that the input is valid or not. You don't have to care about displaying the form again, mark the field with color red, display the error message to the user, etc. This is all done for you automatically. These 2 functions are normally pretty easy and so small that even beginners can write them very quickly (they don't need any knowledge about the database, or HTML forms with heavy usage of Javascript). To make these functions smaller in size, you can define more than one functions for the validation of a field. This also helps you reuse functions (-> you automatically produce reusable code).
E.g. you want a field to be a positive number: Just write one function "is_numeric" that controls if the value is a number and a second one called "is_positive" that tests if it is positive.
Of course, validating a German, English or ISO formatted date requires some work. Therefore a large number of functions for validating dates, strings and numbers is already shipped with Simple Groupware by default.

Syntax: To collect these functions in a central place, all validators are stored in core/functions_user.php and have a "validate_" prefix, e.g. "validate_is_numeric". To define a validator in a sgsML file, write <validate function="is_numeric"/> between the <field>-tags to validate the field with the function validate_is_numeric.


Filters


Reading carefully you have read that Simple Groupware automatically displays your fields. But oftentimes you want to display fields differently from the value stored in the database. This is no conflict since you can define filters that change the value of a field before being displayed. This is elementary when working with dates. E.g. your database is configured for English dates, but you want to display them in German. Or you want to crop long texts to only show the first 20 words. Similar to validators you assign a function to the field that performs the filtering operation. This function takes the value of the field as input and returns the filtered value back to the program. This is also very easy for beginners who don't need to care where the field is displayed or where the value comes from. As said Filters are similar to Validators, which means you can make the functions smaller in size by defining more than one function for the filtering process of a field. This also helps you reuse functions (-> you automatically produce reusable code).
E.g. you want a date field to be presented as a German date, take a look at the following code:

<filter views="all" function="dateformat||m/d/Y"/>

This illustrates you the syntax of a filter and also the syntax for applying parameters to your functions (simply add a "|" to separate the parameters). To make working with dates easier for you, the function to format dates is already shipped with Simple Groupware. The first parameter takes a verbal date modification, e.g. now + 1 month which returns the date increased by 1 month. The second parameter defines the format of the date.
An overview of the format parameters can be found at http://www.php.net/date. Examples dealing with modifications of dates are here: http://www.php.net/strtotime.

Syntax: To collect these functions in a central place, all filters are stored in core/functions_user.php and have a "modify_" prefix (because they modify data), e.g. "modify_dateformat". To define a filter in a sgsML file, write <filter views="all" function="truncate|20"/> between the <field>-tags to truncate the field to a maximum of 20 characters with the function modify_truncate. (The "views=all" parameter indicates that this filter is used with every view and will be discussed later) Other functions are included to help you to manage files, URLs, source code highlighting, etc.

Note: for being neutral the date is stored in the database as timestamp (=an integer value).

Store and Restore


Being as simple as possible, Simple Groupware automatically stores/reads data to/from the database. But sometimes you want to store inputs made by the user differently from the original value. E.g. the user types a date with a German formatting, but in the database it has to be a timestamp. Or you want to store URLs without a leading http:// in the database.
Similar to validators and filters, functions used for storing the input get the value from the form as the first parameter and return the modified value back to the program. You don't have to care how the value is transferred from the form to your function. This makes things much easier, e.g. file uploads require big error handling routines that would boost up your code unnecessarily. When modifying data before writing into the database, it is clear that there must be a re-transformation when being read from the database. This re-transformation is called restore. That's why there are restore functions, that are identical to filter functions regarding structure and functionality.
Thus, writing store and restore functions is as easy as validators or filters. To reduce your functions in size you can also define multiple store or restore functions.

Syntax: To collect these functions in a central place, all store and restore functions are kept in core/functions_user.php and have a "modify_" prefix (because they modify data), e.g. "modify_datetime_to_int" converts a date given as string to a timestamp using integer representation. To define a store or restore function in a sgsML file, write <store function="datetime_to_int"/> and <restore function="dateformat||d.m.Y"/> between the <field>-tags to work with a date-field using the functions modify_datetime_to_int and modify_dateformat.
As told before, Simple Groupware already knows a type called "date". Therefore you only need restore/store functions when defining your date-field with type "int". Using type "date" this is done automatically for you.

Note: The store functions are called after those used for validating the input.

Note: The restore functions are called before those used for filtering values from the database.


Data sources


Often it is necessary to build fields that already contain a set of data from which the user can choose the right values. E.g. this is a typical behavior for select-boxes. When writing an address book you might like to implement a field containing the gender of a person. The preferred values are male and female. The type "select" defines a select-box in Simple Groupware. To define the preferred values you use the <data> construct in sgsML, e.g. <data values="male|female"/> Using more than one value, separate each with a "|". You can also define more than one <data> construct inside the <field>-tag.
The method shown before is used for including static data. But in reality, these values are often dynamic and need to be fetched from a special data source. To get these values you can define a function that searches the right values for you and returns them as an array. You don't have to care about transferring these values to forms or to the relevant components, this is done automatically for you. Simple Groupware automatically calls your function when it needs the data. Instead of the values-parameter you use the function parameter to tell it what function to call. E.g. you want to show a list of companies in your address book or predefined values:

<data function="dbselect|simple_companies|companyname,companyname||companyname asc|10"/>
<data values="male|female"/>

To get these values you call the function dbselect which selects data from the database. To reduce this list we only want 10 items (parameter 5), if there are more, a search box is used to select relevant values. All companies have to be listed sorted by the name of the company (=companyname) alphabetically (asc=ascending) (parameter 4). As the table "simple_companies" (parameter 1) can have more than one field we tell the function to use the field "companyname" (parameter 2). Parameter 3 is not used in this case. With parameter 3 you can reduce the list of companies with an sql-where clause, e.g. "companyname like 'a%'" to get all companies starting with "a".

Syntax: To collect these functions in a central place, all data functions are kept in core/functions_user.php and have a "select_" prefix (because they select data), e.g. "select_dbselect" can be used to get data from the database.
To define a data construct in a sgsML file, write <data function="getmydata"/> between the <field>-tags to call the function "select_getmydata". Here dbselect is illustrated because it is the most common function to get data from the database.

Note: To reduce the size of your functions you can define multiple <data function=> constructs between the <field>-tags.


Tables


When looking at tables we see rows, columns. Our rows are assets, every column is a field (similar to database design). <table></table> is the main tag used in sgsML and includes all the others (e.g. fields, views, etc.). With the table tag you define the name of the module, e.g.

<table modulename="Contacts"></table>

With a table you also define how the fields should be treated:
What is the column the list or assets should be sorted by?
What order should be used?
How many items should be displayed on one page?
Should the results be groups by a special column?
e.g.:

<table modulename="Contacts" orderby="lastname" order="asc" groupby="categories" limit="20"></table>

Of course, these are only the default values you suggest to the user. He can later choose on his own if he needs different settings.


Fields


All fields are defined inside the <table>-tag. A field can be seen as a column in a database, e.g. Last name, Street, Country in an address book. Every field has a type, in sgsML the type is called "simple_type". A type can be a normal string (simple_type is "text") or a collection of lines (simple_type is "textarea"). There are even exotic types like "htmlarea" which represents lines formatted with HTML, or different types for using passwords, dates, times, datetimes, files, wikis, etc.
Every field has a name to be identified by the program. This is similar to column definitions in databases. Besides the internal name used by the program every column can contain a displayname which is the name displayed to the user.
E.g. the name of a field can be "lastname", the displayname is "Last name". To separate these two has a lot of advantages:
The name used by the program is limited to numbers and characters, whereas displayname can contain all kinds of special characters or it can be translated into many languages without changing the internal name used by the program. This creates consistency when using internationalization with Simple Groupware.
Fields can be declared as unique, meaning that there can be no other asset having the same value within this field. To decide between required and optional fields, every field can be defined as "required", but is "optional" by default.
As written before, fields can be validated using validators, their data can be defined by some data sources and they can be modified before / after storing them in the database.

Some examples:

contacts.xml:

<field name="lastname" displayname="Last name" simple_type="text" required="true" />

<field name="zipcode" displayname="Zipcode" db_size="6" simple_type="text">
<validate function="numeric"/>
</field>

<field name="description" displayname="Description" simple_type="textarea" simple_size="4" />


Views


The next element inside the sgsML language is a "view". At first "views" are groups of fields. E.g. you show "first name" and "last name" of a person in the view "display" and the field "address" and "description" in the view "details". But views can do a lot more: views are also used to edit and create assets. Since defining forms (for editing and creating assets) is not always easy and oftentimes requires Javascript usage, Simple Groupware creates all the forms automatically for you. So you only define a field as an html-editor (simple_type "htmlarea") and you're done.
To present several fields, sgsML differs between the orientation in which a group of fields is presented: The horizontal view (sgsML "display") is a list where every field has one column (this is how Windows Explorer lists files). The second one is called "details" (vertical orientation) and presents every field as one line, meaning the first column is the name of the field and the second column contains the value of it. Both orientations have advantages and disadvantages, so you can decide on your own which one you use for what case: define template="display" or template="details" in the view-tag and you're done. Note: When naming a view "foobar", the template "asset_foobar.tpl" will be used. Using the attribute template="display" in the view tag overrides this automatic mapping.

When looking with web applications you often see a list of assets (horizontal orientation) and a "Details" button at the right side to switch to the vertical one for a selected row. If you need this behavior depends on you own, simply add a showinsingleview="true" as attribute to the details-view and you're done (sgsML defines the rows of a table as single view, the headline above is the "normal" view, thus "singlebuttons" are buttons displayed at the right side of every asset).
Similar to tables, views can have their own "order", "orderby", "limit" statements which override those defined by the table.
Other tags are "image_width" and "image_height" (both or only one of them) which enable you to automatically resize images (simple_type "files", images are detected automatically when extension is gif/jpeg/png). This is very useful when bandwidth is low.
Another way views can be used is to reduce the number of assets by a "where" clause, e.g. "lastname like 'a%'" to get all persons starting with "a" (this functionality is similar to views in databases using SQL-Views).
To group fields to special views, you define constructs like "notin" (exclusion method) or "onlyin" (inclusion method) inside the <field>-tags.

Some examples:

contacts.xml:

<view name="display" displayname="{t}Display{/t}" groupby="category" />
<view name="details" displayname="{t}Details{/t}" showinsingleview="true" />

users.xml:

<view name="display" displayname="{t}Active{/t}" where="activated=1" />
<view name="details" displayname="{t}Details{/t}" image_width="200" image_height="100" showinsingleview="true" />


Tabs


The last construct used by sgsML are tabs. Tabs are similar to views: They group fields and enable you to quickly switch from one group to another without overloading the screen. A view is static: This means when you click on a view, the program takes the required fields from the databases and presents the results to you. In contrast, tabs are subordinated to views. A view can contain several tabs, every tab is visible in every view (sounds a bit difficult at the first shot, but gets clearer when writing the first application).
Tabs are dynamic: E.g. you click on one view containing 5 tabs, all fields for the 5 tabs are loaded from the database and put into the (HTML-)output. To avoid overloading the screen, tabs use Javascript to make sure that only one tab is visible at once. When clicking another tab, the old one is hidden and the other is made visible. To assign fields to tabs, simply use <field ... simple_tab="mytab">.

Here is an example:

<tab name="general" displayname="{t}General{/t}" />
<tab name="details" displayname="{t}Details{/t}" />

<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4" simple_tab="details" />


Deploy your applications with sgsML


Finally after writing your first sgsML application you will surely want to deploy it to your Simple Groupware installation. Therefore every module is a web application and stored in one .xml file. Simple Groupware distinguishes between two types of modules: Normal modules and system modules.
System modules are very special modules providing access to other data sources (e.g. the filesystem, other databases, IMAP, iCalendar, etc.) or providing system functionality (folders, statistics, events, search functionalities, session handling, etc.). A module is a system module if Simple Groupware cannot run without it. (There are even some modules like users and groups where this border cannot be clearly pulled, these modules remain system modules since you can't work without it). System modules are kept under "modules/schema_sys". If a module doesn't represent a schema from a table with data in the database than it carries a "nosql_" prefix in the filename (e.g. the filesystem has a schema, but it is not a schema from a table with data in the database). A module that is marked as "nosql_" needs to get its data from another data source. Therefore it uses its own functions for getting and setting data. These functions are very simple and are automatically called by the sql-handler of Simple Groupware. These functions are stored in modules/lib/* and are all following the Simple Groupware API. When learning sgsML you should try to understand system modules at last.
The Simple Groupware folder structure has two branches: src/ and bin/. If you install with language English, German, etc. all files from src/* are translated and written to bin/* during the setup process. Therefore if you installed with English or German, place your .xml file in bin/modules/schema/. If you installed with Developer as language, place your .xml file in src/modules/schema/.
Note: To be able to select a module in the list when creating a new folder, you need to add the module to "modules/schema/modules.txt". Every line in this file contains the module's name and the string that is displayed in the web-interface.
Note: Every .xml file defines a structure of a table in the database. The name of the table is automatically set by the filename and "simple_" as prefix. E.g. the table name for schema/tasks.xml is set to simple_tasks. If the module is a system module, the prefix will be "simple_sys_". E.g. the table name for schema_sys/users.xml will be simple_sys_users. The "modulename" attribute is only the name displayed above the tree on every page.


Translations


With Simple Groupware you can translate everything: It doesn't matter if the strings that should translated are inside XML, PHP, Html, sgsML, etc. Every string that needs to be translated is covered with {t} and {/t}. E.g. to make "username" translatable, use it as "{t}username{/t}". That's the same using sgsML. Examples for translations inside sgsML are display-names for fields, date formats as validation / store parameters, etc. The translation process is done during the setup. So you select a language and all files from the src/ directory are translated into the bin/ directory. When installing with Developer as language, the translation isn't done. All files reside in src/ and when viewing the output, {t} and {/t} are automatically removed. So using Simple Groupware as "Developer" you always get untranslated English values in the output.
Note: If you don't need translations, you won't need to add "{t}" and "{/t}".

Examples


blank.xml:

<table modulename="{t}Blank{/t}" default_view="display" default_sql="no_select">
<view name="display" displayname="{t}Display{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" />
</table>

This module has the name blank. It will be displayed as {t}Blank{/t} where "{t}Blank{/t}" will be translated using the language files. As we defined one view (display), the default view is also display. The view "display" will be displayed as the translation of {t}Display{/t}. In this example we have one field called "id" with the simple_type of "id" meaning this field will be used as a primary key for the table. Also there is a default_sql statement: This overrides normal (automatic) SQL select statements. When using no_select, there will be no select statement being done. In this example we want the SQL statement to be done automatically, so we strip the default_sql attribute.

Now, let's make a second field called "Last name". Since every person should have a last name we want it to be required every time a new person is created or edited. Last name is a string and therefore we use simple_type "text" which is used for all strings that don't require line breaks. Last name should be named "lastname" in the program and displayed as the translation of {t}Last name{/t}.

blank.xml:

<table modulename="{t}Blank{/t}" default_view="display">
<view name="display" displayname="{t}Display{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" />
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
</table>

Adding "first name" is done the same way:

<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />

To help you finding your entries we want to sort them by lastname (default is Id). Therefore we expand the table statement with two attributes:

orderby="lastname" order="asc"

Orderby defines the field we want to sort by and asc means that the entries should be sorted ascending (instead of desc for descending). Having more than 100 entries makes the list very long, so we want to distribute the entries to several pages. To do this, only set a limit to the table:

limit="20"

To help you to remember the relationships to these assets we add a new field called description:

<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4" />

The field description will be displayed as the translation of {t}Description{/t}. The type used here is "textarea" which is similar to the "textarea" component used in HTML. Thus we have a text which can be longer than one line. Here simple_size indicates that textarea has a height of 4 rows.

Now we have:

blank.xml:

<table modulename="{t}Blank{/t}" default_view="display" orderby="lastname" order="asc" limit="20">
<view name="display" displayname="{t}Display{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" />
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4" />
</table>

To rename our module from blank to "My Addresses", use ...

myaddresses.xml:

<table modulename="{t}My Addresses{/t}" default_view="display" orderby="lastname" order="asc" limit="20">
<view name="display" displayname="{t}Display{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" />
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4" />
</table>

... and rename the file from "src/modules/schema/blank.xml" to "src/modules/schema/myaddresses.xml" if you installed Simple Groupware with language "Developer". If you installed it with English or German, rename the file from "bin/modules/schema/blank.xml" to "bin/modules/schema/myaddresses.xml".

Now we only have a simple view that can display entries. To enable you to create new entries, add the following attribute to the table tag:

enable_new="true"

This automatically adds the "New" view with the rights attributes.
To allow you to edit and delete existing entries, add these attributes to the table tag:

enable_edit="true" enable_delete="true"

This automatically adds the "Edit" view and the "Delete" buttons.
With Simple Groupware you assign one module to every folder. Therefore you may want to be able to delete all entries in the folder. To do this we use the "empty" attribute:

enable_empty="true"

This automatically adds the "Empty" button.

myaddresses.xml:

<table modulename="{t}My Addresses{/t}" default_view="display" orderby="lastname" order="asc" limit="20" enable_new="true" enable_edit="true" enable_delete="true" enable_empty="true">
<view name="display" displayname="{t}Display{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" />
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4" />
</table>

To deploy your new web application, make sure that the module is stored in "src/modules/schema/myaddresses.xml" (Developer language) or "bin/modules/schema/myaddresses.xml" (English, German, etc. language).
Inside Simple Groupware, take a look at the tree. Go to a folder where you want to place the new application, e.g. click "Workspace". Below the tree, click "Options", enter a new folder name to the "New folder" form, choose "myaddresses" in the list and click Ok. This is all: The new module is running now!
Note: The modules in the list contain the filenames without the extension (.xml), not the modulename-attribute. Therefore you see "myaddresses" in the list instead of "{t}My Addresses{/t}".

To improve "My Addresses" we add some more fields:

<field name="street" displayname="{t}Street{/t}" simple_type="text" />
<field name="zipcode" displayname="{t}Zipcode{/t}" db_size="6" simple_type="text">
<validate function="numeric" />
</field>
<field name="city" displayname="{t}City{/t}" simple_type="text" />

All three are of type "text", but the zip code should be validated by some constraints: The first validation is done with db_size=6 which means that the field may not be longer than 6 characters. Since zip codes are numeric (in Germany) we add the validator "numeric" (when creating or editing a new asset the function "validate_numeric" is called). If your zip codes are not numeric, leave this step out.

But the "Display" quickly gets overloaded with fields. Thus we create a new view called "Details".

<view name="details" displayname="{t}Details{/t}" showinsingleview="true" tfield_1="firstname" tfield_2="lastname" />

We decide that Id and description are less important than the other fields and should be displayed in the second view "Details". Also the fields "lastname" and "firstname" should be displayed as a caption in the first line of the details view (indicated with the attributes "tfield_1" and "tfield_2").

This means we add new limitations to the fields:

<notin views="display" />

Notin tells the program not to display the field in the view "display". Notin uses "views" as attribute which means that you can add several views here using views="view1|view2" separated with "|".


Summarizing our activities


myaddresses.xml:

<table modulename="{t}My Addresses{/t}" default_view="display" orderby="lastname" order="asc" limit="20" enable_new="true" enable_edit="true" enable_delete="true" enable_empty="true">
<view name="display" displayname="{t}Display{/t}" />
<view name="details" displayname="{t}Details{/t}" showinsingleview="true" tfield_1="firstname" tfield_2="lastname" />
<field name="id" simple_type="id" displayname="{t}Id{/t}">
<notin views="display" />
</field>
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="textarea" simple_size="4">
<notin views="display" />
</field>
<field name="street" displayname="{t}Street{/t}" simple_type="text" />
<field name="zipcode" displayname="{t}Zipcode{/t}" db_size="6" simple_type="text">
<validate function="numeric"/>
</field>
<field name="city" displayname="{t}City{/t}" simple_type="text" />
</table>

When saving changes to the xml file you only need to hit the "Refresh" button in your browser to make Simple Groupware apply your changes.

Examples (continued)


To make the description field more usable we change simple_type from textarea to htmlarea:

simple_type="htmlarea"

Hit refresh and you have a complete HTML editor!

Another way to avoid overloading of the display view is using simple_tabs.

So we define two tabs, one for general information, the other one for the details (both displayed with the translation from the language file):

<tab name="general" displayname="{t}General{/t}" />
<tab name="details" displayname="{t}Details{/t}" />

The difference between "views" and "tabs" is the time when the information is loaded. With views, only the information for the current view is loaded. Using tabs all the information is loaded, but hided using Javascript until you hit the tab. Switching between tabs is much faster because you have already loaded all the information in your browser, but loading the page takes longer at the beginning.

The same example using tabs instead of views looks like this:

myaddresses.xml:

<table modulename="{t}My Addresses{/t}" default_view="display" orderby="lastname" order="asc" limit="20" enable_new="true" enable_edit="true" enable_delete="true" enable_empty="true">
<view name="display" displayname="{t}Display{/t}" />
<tab name="general" displayname="{t}General{/t}" />
<tab name="details" displayname="{t}Details{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" simple_tab="details" />
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="htmlarea" simple_size="4" simple_tab="details" />
<field name="street" displayname="{t}Street{/t}" simple_type="text" />
<field name="zipcode" displayname="{t}Zipcode{/t}" db_size="6" simple_type="text">
<validate function="numeric" />
</field>
<field name="city" displayname="{t}City{/t}" simple_type="text" />
</table>

To add a gender selectbox with the values male, female, use:

<field name="gender" displayname="{t}Gender{/t}" db_size="10" simple_type="select" simple_size="1" simple_tab="details">
<data values="{t}male{/t}|{t}female{/t}"/>
</field>

The field gender has a maximum of 10 characters, is displayed as a selectbox (simple_type select) and the user can select 1 item (instead of multiple items).

Since every address has relations to other addresses we finally add a field to store these relationships:

<field name="isrelatedto" displayname="{t}Is related to{/t}" simple_type="select" simple_size="3" is_linked="simple_myaddresses|details|lastname" multiple="&lt;br&gt;" simple_tab="details" allow_custom="true">
<data function="dbselect|simple_myaddresses|lastname,lastname||lastname asc|10"/>
</field>

The field isrelatedto is displayed with the translation of {t}Is related to{/t}. The simple_type is a selectbox with a height of 3 lines, allowing the user to select multiple items. In the display these items are separated by "<br>" (= new line). The user can also type in his own items which is declared by allow_custom=true. To get the data from the selectbox we use the select_dbselect function which does something like "select lastname from simple_myaddresses order by lastname asc limit 10". If there are more than 10 items, the user can select them by using a search box. This field is also shown in the details tab. Furthermore when displaying the items of this field we want these items to be links. When a user clicks them the referenced address should be displayed. This behavior is achieved by using the "is_linked" attribute. The first parameter names the table, the second the view we want to see in the popup and the third defines the field to choose from.


The complete code of the example


myaddresses.xml:

<table modulename="{t}My Addresses{/t}" default_view="display" orderby="lastname" order="asc" limit="20" enable_new="true" enable_edit="true" enable_delete="true" enable_empty="true">
<view name="display" displayname="{t}Display{/t}" />
<tab name="general" displayname="{t}General{/t}" />
<tab name="details" displayname="{t}Details{/t}" />
<field name="id" simple_type="id" displayname="{t}Id{/t}" simple_tab="details" />
<field name="gender" displayname="{t}Gender{/t}" db_size="10" simple_type="select" simple_size="1" simple_tab="details">
<data values="{t}male{/t}|{t}female{/t}"/>
</field>
<field name="lastname" displayname="{t}Last name{/t}" simple_type="text" required="true" />
<field name="firstname" displayname="{t}First name{/t}" simple_type="text" required="true" />
<field name="description" displayname="{t}Description{/t}" simple_type="htmlarea" simple_size="4" simple_tab="details" />
<field name="street" displayname="{t}Street{/t}" simple_type="text" />
<field name="zipcode" displayname="{t}Zipcode{/t}" db_size="6" simple_type="text">
<validate function="numeric"/>
</field>
<field name="city" displayname="{t}City{/t}" simple_type="text" />
<field name="isrelatedto" displayname="{t}Is related to{/t}" simple_type="select" simple_size="3" is_linked="simple_myaddresses|details|lastname" multiple="&lt;br&gt;" simple_tab="details" allow_custom="true">
<data function="dbselect|simple_myaddresses|lastname,lastname||lastname asc|10"/>
</field>
</table>

Note: Using translations normally takes more time, so it is on you to use them or not.


Screenshots of myaddresses.xml



Search functionality


All your data will automatically be searchable. If simple_type is files, each file will be indexed using the binary tools (catdoc, xlhtml, ImageMagick, Xpdf, UnZip, gzip, tar). These tools are included as windows binaries, if using Linux/Unix you need to install these packages from your distribution (see Installation).


Summary


To demonstrate the efficiency of Simple Groupware and sgsML:

  • We have written a complete web application including 9 fields with only 26 lines of code.
  • You can create / edit / delete assets. With the built-in tree of Simple Groupware you can create different folders for special groups of addresses.
  • All your changes are logged in the history.
  • And maybe the most important benefit all the values are already searchable via the global search function. (You can even try using phonetic searches!)

I hope you got a first introduction into the world of sgsML and already understood most parts of it.
For more examples, see the "src/modules/schema" directory (including examples using files, dates, codeareas, wikiareas etc.).
To go deeper into sgsML there is "src/modules/schema_sys" (containing the Simple Groupware system modules and handlers for external data sources).
This is only a small tutorial describing the basic ideas and elements behind sgsML. If you write your own modules, and there is something not working or not clear, feel free to mail me.

Enjoy Simple Groupware!