diff --git a/etemplate/doc/etemplate.html b/etemplate/doc/etemplate.html index f296b021fd..ceae0b12da 100644 --- a/etemplate/doc/etemplate.html +++ b/etemplate/doc/etemplate.html @@ -3,10 +3,17 @@
A developers tutorial how to write an application with the new eTemplates.
It is also an introduction how to write a phpgw- and setup(3)-compatible app.
Each app need a name, eg. 'et_media'. We now need to create the following directory structur above the phpgroupware dir:
-et_media (that has to be identical to our app-name) - + setup (files necessary for the setup Programm, give the webserver write-permission to that dir) - + inc (class-files) - + templates (templates, still need to store the images and get around a lot of complains from the api) +et_media that has to be identical to our app-name + + setup files necessary for the setup Programm, give the webserver write-permission to that dir + + inc class-files + + templates templates, still needed to store the images and get around a lot of complains from the api + default - + images (here goes our images / icons) + + images here goes our images / icons
To enable setup to create a db-table for us, we need to define the fields we want.
+
To enable setup to create a db-table for us and to supply the so_sql-class with the necessary information, we need to define
+the type and size of the fields / columns in our db-table.
We can use the db-Tools from the etemplate app to create the file for us:
Now we need a nice edit dialog and use the eTemplate editor to set it up:
+ +
The index page is only used if someone clicks on the navbar icon (or on the black cross as we haven't supplied one so far).
Create the file /et_media/index.php with the following content:
<?php $GLOBALS['phpgw_info']['flags'] = array( @@ -140,118 +151,187 @@ Create the file /et_media/index.php with the following content:7. the code of class.et_media.inc.php
As a first step, we only save new entries. The code of the app is in /et_media/inc/class.et_media.inc.php:
+<?php - /**************************************************************************\ - * phpGroupWare - eTemplates - Tutoria Example - a simple MediaDB * - * http://www.phpgroupware.org * - * Written by Ralf Becker+ +* - * -------------------------------------------- * - * This program is free software; you can redistribute it and/or modify it * - * under the terms of the GNU General Public License as published by the * - * Free Software Foundation; either version 2 of the License, or (at your * - * option) any later version. * - \**************************************************************************/ +/**************************************************************************\ +* phpGroupWare - eTemplates - Tutoria Example - a simple MediaDB * +* http://www.phpgroupware.org * +* Written by Ralf Becker <RalfBecker@outdoor-training.de> * +* -------------------------------------------- * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation; either version 2 of the License, or (at your * +* option) any later version. * +\**************************************************************************/ - /* $Id$ */ +/* $Id$ */ - if(!isset($GLOBALS['phpgw_info']['flags']['included_classes']['so_sql'])) +if(!isset($GLOBALS['phpgw_info']['flags']['included_classes']['so_sql'])) +{ + include(PHPGW_API_INC . '/../../etemplate/inc/class.so_sql.inc.php'); + $GLOBALS['phpgw_info']['flags']['included_classes']['so_sql'] = True; +} + This loads the class so_sql, in a way that more than one class may use it (would be nice if we had an api-function for that).
+ ++class et_media extends so_sql +{ + var $messages = array( + 'nothing_found' => 'Nothing matched search criteria !!!', + 'anz_found' => '%d matches on search criteria', + 'saved' => 'Entry saved', + 'error_writeing' => 'Error: writeing !!!' + ); + var $types = array( + '' => 'Select one ...', + 'cd' => 'Compact Disc', + 'dvd' => 'DVD', + 'book' => 'Book', + 'video' => 'Video Tape' + ); ++ +These are a few messages to show the user what happend, we show it via 'msg' in content in the first Label-field after the app-title.
+ +
+As one of the messages contain a %s to be used with sprintf, we have to run them manualy through lang().+ function et_media($lang_on_messages = True) { - include(PHPGW_API_INC . '/../../etemplate/inc/class.so_sql.inc.php'); - $GLOBALS['phpgw_info']['flags']['included_classes']['so_sql'] = True; - } + $this->tmpl = CreateObject('etemplate.etemplate','et_media.edit'); - class et_media extends so_sql - { - var $messages = array( - 'nothing_found' => 'Nothing matched search criteria !!!', - 'anz_found' => '%d matches on search criteria', - 'saved' => 'Entry saved', - 'error_writeing' => 'Error: writeing !!!' - ); - var $types = array( - '' => 'Select one ...', - 'cd' => 'Compact Disc', - 'dvd' => 'DVD', - 'book' => 'Book', - 'video' => 'Video Tape' + $this->so_sql('et_media','phpgw_et_media'); // sets up our storage layer using the table 'phpgw_et_media' + $this->empty_on_write = "''"; // what to write in the db, if a column is empty, the default is NULL + + $this->public_functions += array( // this function can be called external, eg. by /index.php?menuaction=... + 'edit' => True, + 'writeLangFile' => True ); - function et_media($lang_on_messages = True) + if ($lang_on_messages) // run all our messages throug lang { - $this->tmpl = CreateObject('etemplate.etemplate','et_media.edit'); - - $this->so_sql('et_media','phpgw_et_media'); // sets up our storage layer using the table 'phpgw_et_media' - $this->empty_on_write = "''"; // that means if a column is empty how to write in the db, the default is NULL - - $this->public_functions += array( - 'edit' => True, - 'writeLangFile' => True - ); - - if ($lang_on_messages) - { - reset($this->messages); - while (list($key,$msg) = each($this->messages)) - $this->messages[$key] = lang($msg); - } - } - - function edit($content='',$msg = '') - { - if (is_array($content)) // not first call from index - { - if ($content['id'] > 0) - { - $this->read($content); - } - $this->data_merge($content); - - if (isset($content['save'])) - { - $msg .= $this->messages[!$this->save() ? 'saved' : 'error_writeing']; - } - elseif (isset($content['read'])) - { - unset($content['id']); - $found = $this->search($content,False,'name,author'); - - if (!$found) - { - $msg .= $this->messages['nothing_found']; - } - else - { - $this->init($found[0]); - } - } - } - - // now we filling the content array for the next call to etemplate.exec - - $content = $this->data + array( - 'msg' => $msg - ); - $sel_options = array( - 'type' => $this->types - ); - $no_button = array( - 'delete' => !$this->data[$this->db_key_cols[$this->autoinc_id]] - ); - $this->tmpl->exec('et_media.et_media.edit',$content,$sel_options,$no_button,array( - 'id' => $this->data['id'] - )); + reset($this->messages); + while (list($key,$msg) = each($this->messages)) + $this->messages[$key] = lang($msg); } }-8. adding the search-function and a list-dialog
-As saveing and reading of entries is working now we want to search for an entry and show a list of the result (if it's more than one).
+
-First we need to create an other eTemplate to show the list: 'et_media.show'This is the contructor of the class, it does the following for us: +
+
+ +- creates a eTemplate object and load direct our 'et_media.show' template via the constructor +
- set up our storage-layer, this is the so_sql class we are extending.
+ so_sql provides basic functions to read, write, delete and search records in a sql-database. + It get's the information it needs about the structure of our table from the tables_current-file we created with the db-tools. +- set up / extends from so_sql the public_functions array, all functions called which should be called by links or as methode, + like our callbacks, need to be listed here. +
- run our messages through lang() if we are not called with False (see later) +
+ function edit($content='',$msg = '') + { + if (is_array($content)) // we are called as callback for the dialog / form + { + if ($content['id'] > 0) // if we have an id --> read the entry + { + $this->read($content); + } + $this->data_merge($content); // merge content with our internal data-array ($this->data) + + if (isset($content['save'])) // save the entry ($this->data) + { + $msg .= $this->messages[!$this->save() ? 'saved' : 'error_writeing']; + } + elseif (isset($content['read'])) + { + unset($content['id']); // not set by user, so dont use for seach + $found = $this->search($content,False,'name,author'); // searches by using the no-empty fields + + if (!$found) // search returned empty + { + $msg .= $this->messages['nothing_found']; + } + else + { + $this->init($found[0]); // set data-array with the content of the first match + } + } + } + + // now we filling the content array for the next call to etemplate.exec + + $content = $this->data + array( // the content to be merged in the template + 'msg' => $msg + ); + $sel_options = array( // the options for our type selectbox + 'type' => $this->types + ); + $no_button = array( // button not to show + ); + $preserv = array( // this data is preserved over the exec-call (like a hidden input-field in form) + 'id' => $this->data['id'] + ); + $this->tmpl->exec( + 'et_media.et_media.edit', // setting this function as callback for the dialog + $content,$sel_options,$no_button,$preserv + ); + } +} ++ +The edit function is called from our index.php file or as callback for this form / dialog. In that case $content is an array +with the content the user put into the fields of the dialog.
+Let first have a look what happend if we called the first time (or what we do to show the dialog again with the changed data):
++
+- the $content array is set up with our internal data-array (which is empty on the first call) and the message +
- $sel_options has the options for our selectbox: the options are an array where the keys are the values returned by the selectbox + and the values are what the selectbox shows to the user. As we can have more than one selectbox in a dialog, the key in + $sel_options need to be the same as the name of the selectbox. +
- $readonlys: if a fieldname is set in $readonlys to True, its content is showed readonly (for regular fields like type Text) + or left out for buttons (we use this later to show the delete-button only when an entry is loaded) +
- the array $preserv is preserved, which means its stored in the app's session-data and is delivered back like the content of the + fields to the callback-function. We use it here to store the id of the entry. This is similar to use a hidden input-field in a + form, but it does not need to be serialized by the app and is NOT transmitted to the user and back. +
- at last we call etemplate::exec to show the template with the content from $content and set the function itself as callback + for the dialog / form. +
Now let's have a look what happens if the user submits the form and our callback is called: +
+
+ +- the callback (this function) is not the submit-address of the form, the form get's always submitted to the function + process_exec of the etemplate class. This function changes for some field-types the content (eg. a date-field consists + of 3 single fields, process_exec takes care that it is delivered back as timestamp, as we set it in content before). It can + even submit the form back to the user if for a address-selection a search for a pattern has to be performed and the matches + are shown to the user. In this case the callback is NOT called. The same is true if an int field contains letters or is not + within the minimum or maximum set. Not all of the is allready working, it will follow in the next days/weeks.
+ For the specialist process_exec uses $GLOBALS['HTTP_POST_VARS'] and ignores HTTP_GET_VARS set as query in the url. +- the so_sql function data_merge, copies all values from $content, which are columns in the db-table, in our internal data array. + Values which are not real data, like buttons pressed are not copied (!). +
- if $content['save'] is set, the [Save] button has been pressed ('save' is the name NOT the label of the save button), in that + case we use so_sql's save function to save the content of our internal data-array to the db. +
- the same check is used for the [Read]: we uses the content of all fields to search db for matching entries. The user can use + wildcards to perform a search on all field. The wildcards are '*' and '?', so_sql translates them into sql-wildcards. +
- if the search return False we just set our message var. +
- if something is found we use so_sql's init-function to set the data of the first match. Lateron we will show a list if + more than one entry is found. +
- after that the content array is filled again as discriped above. +
Now we are able to store entries in the db and retrive them by searching the database for patterns in the different fields.
+ +
+We are only lacking some way to show if we get more than one match on a search, that's what we are going to implement next:8. adding a list-dialog for the search-function
+First we need to create an other eTemplate to show the list: 'et_media.show'
+ +As you see the templates includes an other template: 'et_media.show.rows'
+ -We need some code in the class to call the template and fill the content:
+ +We need some code / a function in the class to call the template and fill the content:
function show($found) @@ -261,68 +341,99 @@ First we need to create an other eTemplate to show the list: 'et_media.show'< $this->edit(); return; } - reset($found); + reset($found); // create array with all matches, indexes starting with 1 for ($row=1; list($key,$data) = each($found); ++$row) { $entry[$row] = $data; } $content = array( 'msg' => sprintf($this->messages['anz_found'],count($found)), - 'entry' => $entry + 'entry' => $entry // et_media.show.rows uses this, as we put 'entry' in the size-field ); - $this->tmpl->read('et_media.show'); + $this->tmpl->read('et_media.show'); // read the show-template - $this->tmpl->exec('et_media.et_media.edit',$content); + $this->tmpl->exec('et_media.et_media.edit',$content); // exec it with the edit-function as callback }+This function is called by edit with the matches of a search:
++
+- We build an array with all the matches, the index in that array is the row-number starting with 1 (!).
+ $entry = array('empty') + $found; would do the same. +- $content contains again 'msg' which we set to the number of entris found and the above array with the data of all rows under + the key 'entry', as we put that in Size for the field loading the sub-template 'et_media.show.rows'. It not necessary to + put something in Size-field / use a sub-array for a sub-template, but it can be very helpful to organize a complex content-array. + (As an exercice you can remove 'entry' from the Size-field and change the function arrcordingly). +
- we now explizitly read the template 'et_media.show' (the constructor reed 'et_media.edit') and execute it again with + the edit function as callback (because of that, show does NOT need to be listed in public_functions) +
To call the show function, we need to make some changes to the edit-function too:
- elseif (isset($content['read'])) - { - unset($content['id']); - $found = $this->search($content,False,'name,author'); + elseif (isset($content['read'])) + { + unset($content['id']); // not set by user, so dont use for seach + $found = $this->search($content,False,'name,author'); // searches by using the no-empty fields - if (!$found) - { - $msg .= $this->messages['nothing_found']; - } - elseif (count($found) == 1) - { - $this->init($found[0]); - } - else - { - $this->show($found); - return; - } - } - elseif (isset($content['entry']['edit'])) + if (!$found) // search returned empty { - list($id) = each($content['entry']['edit']); - if ($id > 0) - { - $this->read(array('id' => $id)); - } + $msg .= $this->messages['nothing_found']; } + elseif (count($found) == 1) // only one match --> show it in the editor + { + $this->init($found[0]); + } + else // multiple matches --> use the show function/template + { + $this->show($found); + return; + } + } + elseif (isset($content['entry']['edit'])) // the callback from for the show function/template + { // the id is set via the button name of '$row_cont[id]' + list($id) = each($content['entry']['edit']); // note its not only ['edit'] !!! + if ($id > 0) + { + $this->read(array('id' => $id)); + } + }++
+- the first part should be self-explaining, we call show with $found if it contain more than one entry. +
- The show function uses edit as callback, the [Edit] buttons in each row has 'edit[$row_cont[id]]' as name. If an [Edit] button is + pressed $content['entry']['edit'] is set to the id of the entry of that row. We use that id to read the whole entry. +
While makeing this changes we can add a [Cancel] and [Delete] button too:
- elseif (isset($content['cancel'])) - { - $this->init(); - } - elseif (isset($content['delete'])) - { - $this->delete(); - $this->init(); - } + elseif (isset($content['cancel'])) + { + $this->init(); + } + elseif (isset($content['delete'])) + { + $this->delete(); + $this->init(); + } + + + $no_button = array( // no delete button if id == 0 --> entry not saved + 'delete' => !$this->data[$this->db_key_cols[$this->autoinc_id]] + );
-Of course we have to add this buttons to the template 'et_media.edit'
++
+ +- on cancel we just clear the internal data-array with so_sql's init function. +
- on delete we have to call so_sql's delete before (it deletes the db-row coresponding with our internal data-array) +
- the last block checks if the id field is set (it can only be set by a read or save) and disables the [Delete] button if not + ($this->db_key_cols[$this->autoinc_id] == 'id'). +
Of course we have to add this buttons to the template 'et_media.edit'. I trust you can add 2 Submitbuttons with the names +'cancel' and 'delete', a Label and a nice helpmessages by now without looking at a screenshot ;-).
9. creating the english lang-file
To get rid of the stars '*' behind each Label and to be able to translate the app in other languages we need to create a lang-file
http://ourDomain/phpgroupware/index.php?menuaction=et_media.et_media.writeLangFile (the errormsg can be savely ignored)
@@ -335,14 +446,36 @@ There are 2 possibilties to create it automaticaly:
This is the function (don't forget to add it like the edit-function to public_functions):
function writeLangFile()
{
- $etm = new et_media(False); // no lang on messages
+ $etm = new et_media(False); // no lang() on messages
$this->tmpl->writeLangFile('et_media','en',$etm->messages);
}
+To be able to put the eTemplates in CVS and to ship them with your app, you need to dump them in a file first. +
This is done in the eTemplate editor by putting the app-name or an template-name in the Name field and clicking on the button +[Dump4Setup]. This creates the file et_media/setup/etemplates.inc.php. The etemplate-class loads this file whenever it finds +a new version automaticaly.
+ +