Tigermouse – User Documentation

Last updated on 21 Jul 2007.

 

1. Design, concepts and technologies.

 

1.1. Model-View-Controller

 

1.2. Asynchronous Javascript and XML

 

1.3. Contemporary GUI toolkits

 

1.4. What is it good for

 

1.5. What is it not good for

 

2. Working with Views

View is an object that can be serialized to string as piece of HTML code.

 

2.1. Displaying a View

To display a View all you need to do is to return it from controller method.

class MainCtrl extends Ctrl {

	public function show() {
		$label = new Label();
		$label->text = 'Hello World';
		return $label;
	}

}

This displays a text message.

 

2.2. Building composite Views using containers

You can have composite views easily using containers. Containers are Views that can contain other Views. To build a composite view using container you need to add one or more Views to it.

class MainCtrl extends Ctrl {

	public function show() {
		$label1 = new Label();
		$label1->text = 'Hello ';

		$label2 = new Label();
		$label2->text = 'World';

		$container = new View('compView');
		$container->add($label1);
		$container->add($label2);
		return $container;
	}

}

You can use one of bundled containers such as HBox, VBox, Menu, Tree, etc. or write your own using template file.

 

2.3. Building composite Views using templates

Templates are written in Smarty template language. Use $self variable to refer to current view in your template file.

<div id="{$id}">
	{foreach from=$self item=child}
		{$child}
		<hr/>
	{/foreach}
</div>

Save it in views/MyCompositeView.tpl and add the following line before return in show() method from previous chapter:

		$container->template = 'MyCompositeView.tpl';

Each View added to your container will be displayed and followed by horizontal line. Views implement Iterator interface, therefore can be handled directly using {foreach} Smarty instruction. Views implement magical PHP 5 __toString() method, therefore can be echoed straight away.

Another thing you will notice here is {$id} variable. It is View's unique identifier. It is not mandatory for many Views. If a View is anonymous it cannot be replaced or updated later, unless a non-anonymous container is replaced or updated.

You can add template variables just by assigning values to respective property. For example:

		$composite->title = 'This is embedded view!';

or equivalent:

		$l = new Label();
		$l->text = 'This is embedded view!';
		$composite->title = $l;

Can be used in template file:

<div id="{$id}">
	<h1>{$title}</h1>
	{foreach from=$self item=child}
		{$child}
		<hr/>
	{/foreach}
</div>
 

2.4. Using View aliases

When building an application you need to take care of identifier uniqueness for yourself. This is because there cannot be duplicate identifiers in HTML document. It may be difficult to remember all the identifiers you have used before in your program. So, you are advised to prefix identifiers with container identifier as a convention. This may make identifiers very long and not particular practical for use in templates. In such case you may use Views aliases.

class MianCtrl extends Ctrl {

	public function show() {
		$label1 = new Label('MainModule_RootContainer_Label1');
		$label2 = new Label('MainModule_RootContainer_Label2');
		$container = new View('MainModule_RootContainer');
		$container->add($label1, 'label1');
		$container->add($label2, 'label2');
		return $container;
	}

}

In the example above Labels will be registered as label1 and label2 for use in template.

<div id="{$id}">
{$label1}
{$label2}
</div>

You will learn more about aliases while working with Forms.

 

2.5. Accessing sub-Views

If you use a helper method to provide composite view and you need to access sub-views you can use property accessor to reach it. Refer to sub-view using its alias (or identifier when there is no alias given).

class MianCtrl extends Ctrl {

	private function getPreparedView() {
		$label1 = new Label('MainModule_RootContainer_Label1');
		$label2 = new Label('MainModule_RootContainer_Label2');
		$container = new View('MainModule_RootContainer');
		$container->add($label1, 'label1');
		$container->add($label2, 'label2');
		return $container;
	}

	public function show() {
		$container = $this->getPreparedView();
		$container->label1->text = 'Composite ';
		$container->label2->text = 'View!';
		return $container;
	}

}

This way you are accessing sub-views using property access operator. This method works only for views nested directly in composite view. When you need to access view in composite view which is nested in another composite view you need to use View getChildById() method supplying identifier.

		$label1 = $container->getChildById('label1');

This method works good for simple one level composition too.

 

2.6. Displaying multiple Views

Use Views' update() method to update many of them at the same time.

		$label1 = new Label('myLabel1');
		$label1->text = time();

		$label2 = new Label('myLabel2');
		$label2->text = rand(0, 1000);

		$label1->update();
		$label2->update();
 

3. Working with events

 

3.1. Action string, Action and ActionService

Action string is a string that identifies certain Controller and its method. It can also contain path to Controller class file. The most basic way Action string looks like is

MainCtrl/show

It contains Controller class name, which is MainCtrl and its method name, show. It also tells that the Controller class is placed directly in ctrl/ directory.

LoginService/LoginForm/LoginCtrl/authenticate

This Action string points to method authenticate of Controller class LoginCtrl, which is paced in ctrl/LoginService/LoginForm directory.

Action is a request from Controller method execution. Define an Action passing Action string for desired Controller method to be invoked. You can optionaly provide second parameter, which is a value or array of values passed to that Controller method. See examples:

$action1 = new Action('MainCtrl/show');
$action2 = new Action('MainCtrl/displayDocument', 42);
$action2 = new Action('LoginService/LoginForm/LoginCtrl/authenticate', array($login, $password));

In the first case MainCtrl Controller will be instantiated and method show called. In the second case method displayDocument will receive one argument. In third example two arguments will be passed to method authenticate of LoginCtrl Controller saved in ctrl/LoginService/LoginForm/ directory.

Use method run() of Action to execute it immediately.

ActionService is a class that has one static method execAction. Calling that method staticaly let you execute an Action just without instantiating Action object. Pass the same arguments as in Action constructor.

ActionService::execAction('LoginService/LoginForm/LoginCtrl/authenticate', array($login, $password));

Both Action::run() and ActionService::execAction return the result of Controller method.

 

3.2. Defining event Listeners

Listener is an observer that triggers defined action when event occurs. Use listeners to react on user clicks, key press, mouseovers and other well known Javascript events. Define Listener in conjunction with Action or Behaviour.

class MainCtrl extends Ctrl {

	public function show() {
		$button = new Button('InteractiveWidget');
		$button->text = 'Click me!';

		$button->addListener(
			new ClickListener(
				new Action('MainCtrl/click')
			)
		);

		return $button;
	}

	public function click() {
		$button = new Button('InteractiveWidget');
		$button->text = 'You clicked me!';
		return $button;
	}

}
 

3.3. Drag and Drop

 

3.4. Multiple listeners and ActionAggregate

 

3.5. Defining global keyboard shortcuts

 

4. Working with Forms

 

4.1. Building forms

Build a Form using template and sub-views. To pass form data you need to define Listener that performs Action with a parameter. That parameter is a special value reader and id provided by Form via valueReader() method.

class MainCtrl extends Ctrl {

	public function show() {
		$f = new Form();
		$f->add(new Input('login'));
		$f->add(new Input('password'));
		$ok = new Button('ok');
		$ok->addListener(
			new ClickListener(
				new Action('MainCtrl/login', $f->valueReader())
			)
		);
		$f->add($ok);
		return $f;

	}

	public function login() {

	}

}

Value reader is a piece of Javascript that reads form fields values. Those values will be sent to Controller method then.

 

4.2. Passing and receiving form data

Values gathered with Form value reader are passed as FormContext object to Controller. See what should login() method from example above look like:

	public function login(FormContext $cx) {
		$cx->login; // value of login input
		$cx->password; // value of password input
	}

Values are passed in FormContext object. Access values using form fields identifiers. If you used alias to add form field to Form view, use alias to access the value respectively.

 

4.3. Validation and error messages

Setting and clearing error messages for form elements can be done using errorMessage() and clearError() methods respectively. It is convenient to obtain prepared Form object.

class MainCtrl extends Ctrl {

	public function show() {
		$f = new Form();
		$f->add(new Input('login'));
		$f->add(new Input('password'));
		$ok = new Button('ok');
		$ok->addListener(
			new ClickListener(
				new Action('MainCtrl/login', $f->valueReader())
			)
		);
		$f->add($ok);
		return $f;

	}

	public function login(FormContext $cx) {
		$f = $this->show();
		$f->clearError(); // clear all previous errors first on all form fields
		if (!$cx->login) {
			$f->login->errorMessage('Enter your username'); // set new error message
			return;
		}
		if (!$cx->password) {
			$f->password->errorMessage('Enter your password'); // set new error message
			return;
		}
		// more processing
	}

}

Using clearError() on Form will clear errors on all form fields.

 

5. Working with data representation objects

 

5.1. Choosing the right DB library adapter and SQL dialect

There are many database abstraction layers out there. Tigermouse is flexible enough to support most of them. It is done through database adapters. By default adapters for PDO, PEAR MDB2 and ADOdb are provided.

In the same manner there are many database engines out there. Each of them is more or less compatible to SQL language standard. To handle the differences Tigermouse incorporates SQLDialect objects.

To connect to database you need these two: adapter and dialect objects. Here is an example for connecting to PostgreSQL database using PDO abstraction layer.

$dsn = 'pgsql:dbname=yourdbname;user=yourloginname';
$pdo = new PDO($dsn);
$dialect = new SQL92Dialect();
$adapter = new PDOAdapter($pdo, $dialect);

DSN (Data Source Name) is a way of telling PDO which database to connect. SQL92Dialect is the dialect class that is appropriate for PostgreSQL engine. The same can be written in more terse manner:

$adapter = new PDOAdapter(new PDO('pgsql:dbname=yourdbname;user=yourloginname'), new SQL92Dialect());

It is a good idea to have factory class that produces database adapter. Such class is provided with Tigermouse in model/ directory. Alter its buildAdapter() method to produce a desired DBAdapter.

	protected static function buildAdapter() {
		return new PDOAdapter(
			new PDO('sqlite:var/sqlite/mydatabase.sqlite'),
			new SQL92Dialect()
		);
	}

The example above shows how to configure PDO backend with SQLite database. Make sure that var/sqlite/ is writable for your web server. Otherwise SQLite will complain it could not create temporary transaction file.

Use DBFactory::getAdapter() method when you need to obtain an instance of configured DBAdapter.

 

5.2. Using ActiveRecord

Active Record is a design pattern closely related to database. It represents single database table record and provides interface for reading, writing and removing to/from that table.

Create the simplest Active Record by providing information about table name and primary key name. If you skip primary key name it will be set to 'id'.

class Person extends ActiveRecord {

	protected $tableName = 'people';
	protected $primaryKey = 'id';

}

Load a record into Active Record object using primary key value (42 in this example):

$p = new Person();
$p->loadById(42);

Note that database adapter is required for Active Record to work. DBAdapter will be obtained automaticaly from DBFactory::getAdapter() method, unless you overwrite ActiveRecord::buildDBAdapter method.

If you need multiple selection using non primary key value, do it this way:

$p = new Person();
$females = $p->loadByFieldValue('gender', 'female');

The example above shows how to fetch a collection of Active Records. Array $females is filled with all Active Records of class Person matching given condition. In addition the first object of that array is the current object. In other words, varriable $p is the first item in returned array.

Modifying Active Record properties will result in insertion or update on database table after saving.

$p->name = 'Random J. Hacker';
$p->gender = 'male';
$p->save();
 

5.3. Defining one-to-many relations

One-to-many relations are the most often used ones in most database schemes. Tigermouse Active Record implementation makes using such relations really easy. Consider the following database scheme:

CREATE TABLE people (
	id		SERIAL PRIMARY KEY,
	name		TEXT NOT NULL,
	gender		TEXT
);
CREATE TABLE emails (
	id		SERIAL PRIMARY KEY
	email		TEXT NOT NULL,
	person_id	INT REFERENCES people(id)
);

Define Active Record classes reflecting the scheme.

class Person extends ActiveRecord {

	protected $tableName = 'people';
	protected $hasMany = array(
		'emails' => array('Email', 'person_id')
	);

}

class Email exteds ActiveRecord {

	protected $tableName = 'emails';
	protected $belongsTo = array(
		'person' => array('Person', 'person_id')
	);

}

Having it defined this way you can access related objects directly just by calling $p->emails[$i] (where $p is instance of Person and $i is valid array index) or $e->person (where $e is instance of Email.

$e = new Email();
$e->email = 'rjhacker@gmail.com';

$p = new Person();
$p->name = 'Random J. Hacker';
$p->gender = 'male';
$p->email->add($e);

$p->save();
$e->save();

To remove Active Record from collection use:

$p->email->remove($e);
 

5.4. Defining one-to-one relations

One-to-one relation is simple case of one-to-many relations. Accessing Active Record returns only one instance instead of collection. Use hasOne property to define such relation.

 

5.5. Defining many-to-many relations

Many-to-many relations are often avoided because of their complexity level. From relational point of view extra database table is needed to link entities from two tables. Managing such connections may be troublesome.

Define many-to-many relation between users and groups. Use external table to link each other.

CREATE TABLE users (
	id		SERIAL PRIMARY KEY,
	name		TEXT NOT NULL
);
CREATE TABLE groups (
	id		SERIAL PRIMARY KEY
	name		TEXT NOT NULL,
);
CREATE TABLE users_link_groups (
	user_id		INT REFERENCES users(id),
	group_id	INT REFERENCES groups(id)
);

Create Active Record definitions reflecting the relation.

class User exteds ActiveRecord {

	protected $tableName = 'users';
	protected $manyToMany = array(
		'groups' => array('Group', 'users_link_groups', 'user_id', 'group_id')
	);

}

class Group exteds ActiveRecord {

	protected $tableName = 'group';
	protected $manyToMany = array(
		'users' => array('User', 'users_link_groups', 'group_id', 'user_id')
	);

}

Accessing collection of linked Active Records is done the same way as in one-to-many relation. Use add() and remove() methods to manage objects.

$user = new User();
$group = new Group();
$user->groups->add($group);
$group->users->remove($users);
 

5.6. Using DataSources

 

Internaltionalization (i18n)

Tigermouse provides internationalization framework with flexible translations storage backends.

 

Configuration

Setting up i18n subsystem requires you to define default language and translation service backend. The best place to do it is index.php file.

i18n::setDefaultLang('en_EN');
i18n::setBackend(new i18nFileTranslationService());

Later on language can be changed:

i18n::setLang('pl_PL');

i18nFileTranslationService is the basic translation service backend. It requires no additional configuration. It uses translation files from i18n/ directory. File names must correspond to language codes. Use string=translation format for translation files. Leading and trailing white spaces will be ignored.

Once you setup i18n sybsystem __() function will return translations for string given as argument.

	echo __('Click here to resume');

When translation for given string cannot be found, original string will be returned.

Note that __() returns a translation object instead of old-fashion string. The object is automaticaly converted to string when used in string context.

> 6. Advanced topics
> 6.1. Class autoloader
> 6.2.


> 6.2. Errors and pop-up windows
> 6.3. Requests log
> 6.4. Debugging Javascript
> 6.5. Deploying application on production server