Tutorial

For this demo, we will create a very simple application that will be used to manage the books from a tiny bookstore (this is why we will call our application “Tiny Bookstore”). The administrator will be able to manage publishers, authors, categories and books. Each book is represented by several details: title, author (foreign key to the `authors` table), publisher (foreign key to the `publishers` table), category (foreign key to the `categories` table), ISBN, price, description and a flag specifying whether this book is recommended or not.

The administrator will have the possibility to perform basic CRUD operations: create (insert), update, delete and list data stored into the database. Also, filtering and paging capabilities will be provided. In order to do this, several steps must be taken:
  • satisfy the prerequisites
  • creating the database
  • writing server-side code
  • writing client-side code
  • compiling the client-side code
  • deploying the application
Satisfy the Prerequisites
First of all, you need to download the latest version of Dynamize from http://www.dynamize.net (“Download” section). This archive contains the skeleton code needed to create your first application. Unpack this archive to any directory you want.

In order to build the client-side code, you will need a Java compiler, preferably the one from Sun Microsystems (http://java.sun.com/javase/). Any version newer then Java SE 5 will do just fine. The only thing left to do is to add the Java bin directory to the system path (so the framework will be able to find and call the Java compiler – javac).

In order to test the code locally, you will need to download and install Apache Web Server, PHP and MySQL Database Server. An all-in-one solution (for example: WAMP Server that can be downloaded from http://www.wampserver.com/en/) is recommended. I will not enter into details, as it is presumed that the user of this framework has, at least once, configured and used a WAMP/LAMP solution.
Create the Database
The script used to create the database is presented in listing 4-1. This will create four tables for storing authors, publishers, categories and books. The fifth table is for internal use and will contain the default password for the administrator (set to “administrator” – without quotes). Passwords are stored encrypted.
CREATE TABLE IF NOT EXISTS `authors` (
  `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(120) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
 
CREATE TABLE IF NOT EXISTS `books` (
  `id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
  `author` int(3) UNSIGNED NOT NULL,
  `category` int(3) UNSIGNED NOT NULL,
  `publisher` int(3) UNSIGNED NOT NULL,
  `title` varchar(120) collate utf8_bin NOT NULL,
  `isbn` varchar(22) collate utf8_bin NOT NULL,
  `price` int(3) UNSIGNED NOT NULL,
  `description` text collate utf8_bin NOT NULL,
  `recommended` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY  (`id`),
  KEY `mainidx` (`category`,`publisher`,`author`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
 
CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT,
  `parent` int(3) NOT NULL DEFAULT '0',
  `name` varchar(120) collate utf8_bin NOT NULL,
  `color` varchar(7) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `parent` (`parent`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
 
CREATE TABLE IF NOT EXISTS `publishers` (
  `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(120) collate utf8_bin NOT NULL,
  `url` varchar(120) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
 
CREATE TABLE IF NOT EXISTS `settings` (
  `k` varchar(100) collate utf8_bin NOT NULL,
  `v` varchar(200) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`k`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;

INSERT INTO `settings` (`k`, `v`) VALUES
  ('admin_password', '4f208e87dbf1f6ded475ec7a7c8dea87');
Write Server-Side Code
As mentioned before, the server side code must be written in PHP. First, we need to configure the database connection string. We need to set the host for the MySQL server, the user and the password used to connect to the database and the database name. This is done by modifying the Config.php file as follows:
	class Config {
		public static $db = 
			array(
				'driver' => 'Pdo_Mysql', 
				'connection_data' => 
					array(
					    'host'     => '127.0.0.1',
					    'username' => 'dbuser',
					    'password' => 'dbpass',
					    'dbname'   => 'db'
					));
	}
Now we need to create a class for each database table in order to provide data stores for the client-side application. Thus, we will create four classes (four PHP files):

_authors.php: serves as a data store for the `authors` table. Also, this class will provide data for all the controls that need data regarding authors and implement hierarchical or plain structures (COLUMN_TYPE_DATATREE or COLUMN_TYPE_COMBOLIST). This is done by invoking the setTreeDataSql() method. The data store will also enforce uniqueness based on the `name` field.
include('dbo/BaseDBO.php');
 
class Authors extends BaseDBObject {
	public FUNCTION __construct() {
		parent::__construct('authors');
 
		$fields = new FieldCollection();
 
		$fields->addField(new FIELD('id', FIELD::FIELD_TYPE_INTEGER));
		$fields->addField(new FIELD('name', FIELD::FIELD_TYPE_STRING));
 
		parent::setFields($fields);
		parent::setPrimaryKeyField('id');
		parent::setSecondKeyField('name');
		parent::setDefaultSortField('name');
 
		parent::setTreeDataSql('SELECT id, name, 0 as parent FROM authors');
 
		parent::initBDO();
	}
}
 
new Authors();
_publishers.php: serves as a data store for the `publishers` table. Also, this class will provide data for all the controls that need data regarding publishers and implement hierarchical or plain structures (COLUMN_TYPE_DATATREE or COLUMN_TYPE_COMBOLIST). This is done by invoking the setTreeDataSql() method. The data store will also enforce uniqueness based on the `name` field.
include('dbo/BaseDBO.php');
 
class Publishers extends BaseDBObject {
	public FUNCTION __construct() {
		parent::__construct('publishers');
 
		$fields = new FieldCollection();
 
		$fields->addField(new FIELD('id', FIELD::FIELD_TYPE_INTEGER));
		$fields->addField(new FIELD('name', FIELD::FIELD_TYPE_STRING));
		$fields->addField(new FIELD('url', FIELD::FIELD_TYPE_STRING));
 
		parent::setFields($fields);
		parent::setPrimaryKeyField('id');
		parent::setSecondKeyField('name');
		parent::setDefaultSortField('name');
 
		parent::setTreeDataSql(
		  'SELECT id, name, 0 as parent FROM publishers');
 
		parent::initBDO();
	}
}

new Publishers();
_categories.php: serves as a data store for the `publishers` table. Also, this class will provide data for all the controls that need data regarding publishers and implement hierarchical or plain structures (COLUMN_TYPE_DATATREE or COLUMN_TYPE_COMBOLIST). This is done by invoking the setTreeDataSql() method. The trickiest part here is to define the hierarchical relation (tree-like structure). This is done by joining the `categories` table to itself and setting the needed foreign key and foreign data fields using the setInjectedFields(), setForeignKeyField() and setForeignDataField() methods. The data store will also enforce uniqueness based on the `name` field.
include('dbo/BaseDBO.php');
 
class Categories extends BaseDBObject {
	public FUNCTION __construct() {
		parent::__construct('categories');
 
		$fields = new FieldCollection();
 
		$fields->addField(new FIELD('id', FIELD::FIELD_TYPE_INTEGER));
 
		$fParent = new FIELD('parent', FIELD::FIELD_TYPE_COMPOSITE);
		$fParent->setInjectedFields(' c2.id as pid, c2.name as pname ');
		$fParent->setInjectedFrom(
  		  ' left join categories as c2 on categories.parent = c2.id ');
		$fParent->setForeignKeyField("pid");
		$fParent->setForeignDataField("pname");
		$fields->addField($fParent);
 
		$fields->addField(new FIELD('name', FIELD::FIELD_TYPE_STRING));
		$fields->addField(new FIELD('color', FIELD::FIELD_TYPE_STRING));
 
		parent::setFields($fields);
		parent::setPrimaryKeyField('id');
		parent::setSecondKeyField('name');
		parent::setDefaultSortField('name');
 
		parent::setTreeDataSql('SELECT id, name, parent FROM categories');
 
		parent::initBDO();
	}
}
 
new Categories();
_books.php: serves as a data store for the `books` table. The trickiest part here is to define the foreign relations for the category, publisher and author fields. This is done by joining the `books` table to the `categories`, `publishers` and `authors` tables and setting the needed foreign keys and foreign data fields using the setInjectedFields(), setForeignKeyField() and setForeignDataField() methods. The data store will also enforce uniqueness based on the `title` field.
include('dbo/BaseDBO.php');
 
class Books extends BaseDBObject {
	public FUNCTION __construct() {
		parent::__construct('books');
 
		$fields = new FieldCollection();
 
		$fields->addField(new FIELD('id', FIELD::FIELD_TYPE_INTEGER));
 
		$fCategory = new FIELD('category', FIELD::FIELD_TYPE_COMPOSITE);
		$fCategory->setInjectedFields(' c2.id as cid, c2.name as cname ');
		$fCategory->setInjectedFrom(
		  ' left join categories as c2 on books.category = c2.id ');
		$fCategory->setForeignKeyField("cid");
		$fCategory->setForeignDataField("cname");
		$fields->addField($fCategory);
 
		$fPublisher = new FIELD('publisher', FIELD::FIELD_TYPE_COMPOSITE);
		$fPublisher->setInjectedFields(' c3.id as pid, c3.name as pname ');
		$fPublisher->setInjectedFrom(
		  ' left join publishers as c3 on books.publisher = c3.id ');
		$fPublisher->setForeignKeyField("pid");
		$fPublisher->setForeignDataField("pname");
		$fields->addField($fPublisher);
 
		$fPublisher = new FIELD('author', FIELD::FIELD_TYPE_COMPOSITE);
		$fPublisher->setInjectedFields(' c4.id as aid, c4.name as aname ');
		$fPublisher->setInjectedFrom(
		  ' left join authors as c4 on books.author = c4.id ');
		$fPublisher->setForeignKeyField("aid");
		$fPublisher->setForeignDataField("aname");
		$fields->addField($fPublisher);
 
		$fields->addField(new FIELD('title', FIELD::FIELD_TYPE_STRING));
		$fields->addField(new FIELD('isbn', FIELD::FIELD_TYPE_STRING));
		$fields->addField(new FIELD('price', FIELD::FIELD_TYPE_INTEGER));
		$fields->addField(new FIELD('description',
		  FIELD::FIELD_TYPE_STRING));
		$fields->addField(new FIELD('recommended',
		  FIELD::FIELD_TYPE_INTEGER));
 
		parent::setFields($fields);
		parent::setPrimaryKeyField('id');
		parent::setSecondKeyField('title');
		parent::setDefaultSortField('title');
 
		parent::initBDO();
	}
}
 
new Books();
Write Client-Side Code
Similarly to the server-side code, the client-side code consists of four classes, each class representing one of the entities described above. The Java files needed to run the application are the following:

ApplicationLayout.java – existing file / method createToolbar(): this is the actual entry point for the user interface. Actual buttons for user actions (open publishers list, open authors list, open categories list, open books list) are to be added here. The holder for these buttons is the application toolbar, called toolbar. The event listeners needed to handle events for these four buttons need to be created. In order to create the buttons and assign them with the corresponding event listeners, the following code needs to be inserted:
TextToolItem btnPublishers = new TextToolItem("Edituri", "icon-evenimente");
btnPublishers.addSelectionListener(new PublishersListener());
toolBar.add(btnPublishers);
 
toolBar.add(new SeparatorToolItem());
 
TextToolItem btnAuthors = new TextToolItem("Autori", "icon-evenimente");
btnAuthors.addSelectionListener(new AuthorsListener());
toolBar.add(btnAuthors);
 
toolBar.add(new SeparatorToolItem());
 
TextToolItem btnCategories = new TextToolItem("Categorii", "icon-stiri");
btnCategories.addSelectionListener(new CategoriesListener());
toolBar.add(btnCategories);
 
toolBar.add(new SeparatorToolItem());
 
TextToolItem btnBooks = new TextToolItem("Cărţi"i", "icon-pagini");
btnBooks.addSelectionListener(new BooksListener());
toolBar.add(btnBooks);
As mentioned above, each of the buttons has its own event listener. Each of these listeners needs to create a CompleteGridWidget object and embed the needed columns into it.

AuthorsListener.java: serves as the action listener for the ‘Open authors list’ button. It defines only the two columns needed in order to handle authors. It specifies the URL of the data store and issues the render action for the grid.
package ro.dizertatie.client.listeners;
public class AuthorsListener extends ItemBaseListener {
 
	protected void onNewTab() {
		currentTab.setLayout(new BorderLayout());
 
		CompleteGridWidget grid = new CompleteGridWidget();
 
		grid.setDataUrl(AdminConstants.WEBMODE_DATA_URL + "_authors.php");

		CustomColumnConfig column = new CustomColumnConfig("id", "ID", 30);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_INT);
		column.setEditable(false);
		column.setPrimaryKey(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("name", "Nume", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT);
		column.setRequired(true);
		column.setEditable(true);
		grid.addColumn(column);
 
		try {
			grid.render();
		} catch (CompleteGridException e) {
			e.printStackTrace();
		}
 
		currentTab.add(grid, new BorderLayoutData(LayoutRegion.CENTER));
	}
 
	protected void onExistingTab() {}
}
PublishersListener.java: serves as the action listener for the ‘Open publishers list’ button. It defines the three columns needed in order to handle publishers. It specifies the URL of the data store and issues the render action for the grid.
package ro.dizertatie.client.listeners;
public class PublishersListener extends ItemBaseListener {
 
	protected void onNewTab() {
		currentTab.setLayout(new BorderLayout());
 
		CompleteGridWidget grid = new CompleteGridWidget();
 
		grid.setDataUrl(AdminConstants.WEBMODE_DATA_URL + "_publishers.php");
		
		CustomColumnConfig column = new CustomColumnConfig("id", "ID", 30);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_INT);
		column.setEditable(false);
		column.setPrimaryKey(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("name", "Nume", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT);
		column.setRequired(true);
		column.setEditable(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("url", "Url", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT);
		column.setRequired(false);
		column.setRegexp("http://[a-zA-Z0-9-_.]+");
		column.setEditable(true);
		grid.addColumn(column);
 
		try {
			grid.render();
		} catch (CompleteGridException e) {
			e.printStackTrace();
		}
 
		currentTab.add(grid, new BorderLayoutData(LayoutRegion.CENTER));
	}
 
	protected void onExistingTab() {}
}
CategoriesListener.java: serves as the action listener for the ‘Open categories list’ button. It defines the three columns needed in order to handle categories. It specifies the URL of the data store and issues the render action for the grid. The trickiest part here is to set up the column which is used to handle the parent of the category. We need to specify the URL of the associated data store.
package ro.dizertatie.client.listeners;
public class CategoriesListener extends ItemBaseListener {
 
	protected void onNewTab() {
		currentTab.setLayout(new BorderLayout());
 
		CompleteGridWidget grid = new CompleteGridWidget();
 
		grid.setDataUrl(AdminConstants.WEBMODE_DATA_URL + "_categories.php");
 
		CustomColumnConfig column = new CustomColumnConfig("id", "ID", 30);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_INT);
		column.setEditable(false);
		column.setPrimaryKey(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("name", "Nume", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT);
		column.setRequired(true);
		column.setEditable(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("parent", "Părinte (id)"", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_DATATREE);
		column.setRootSelectable(true);
		column.setRequired(true);
 
		column.setAssociatedControlDataUrl(AdminConstants.WEBMODE_DATA_URL +
		  "_categories.php");
		
		column.setEditable(true);
		grid.addColumn(column);
 
		column = new CustomColumnConfig("color", "Culoare", 100);
		column.setColumnType(CompleteGridConstants.COLUMN_TYPE_COLOR);
		column.setRequired(false);
		column.setEditable(true);
		grid.addColumn(column);
 
		try {
			grid.render();
			grid.getStore().groupBy("parent");
		} catch (CompleteGridException e) {
			e.printStackTrace();
		}
 
		currentTab.add(grid, new BorderLayoutData(LayoutRegion.CENTER));
	}
 
	protected void onExistingTab() {}
    }
BooksListener.java: serves as the action listener for the ‘Open books list’ button. It defines only the two columns needed in order to handle books. It specifies the URL of the data store and issues the render action for the grid. We are also using a RowExpander. As seen in the bellow listing, the RowExpander uses a template for rendering its content. The template is composed of HTML code augmented with placeholders who will be replaced with information from the grid. In this case, we are displaying the value of the description associated the row being expanded. Also, the data in the grid will be grouped using the `category` field.
package ro.dizertatie.client.listeners;
public class BooksListener extends ItemBaseListener {
 
	protected void onNewTab() {
		currentTab.setLayout(new BorderLayout());
 
		CompleteGridWidget grid = new CompleteGridWidget();
 
		grid.setDataUrl(AdminConstants.WEBMODE_DATA_URL + "_books.php");
 
		XTemplate tpl = XTemplate.create(
"
Descriere:
"+ "
{[values.description.replace(/&/g,\"&\").replace(//g, \">\").replace(/\\\"/g, \""\")]} 
"); RowExpander expander = new RowExpander(); expander.setTemplate(tpl); grid.addColumn(expander); CustomColumnConfig column = new CustomColumnConfig("id", "ID", 30); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_INT); column.setEditable(false); column.setPrimaryKey(true); grid.addColumn(column); column = new CustomColumnConfig("title", "Titlu", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT); column.setRequired(true); column.setEditable(true); grid.addColumn(column); column = new CustomColumnConfig("category", "Categorie (id)", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_DATATREE); column.setRootSelectable(false); column.setRequired(true); column.setAssociatedControlDataUrl(AdminConstants.WEBMODE_DATA_URL + "_categories.php"); column.setEditable(true); grid.addColumn(column); column = new CustomColumnConfig("publisher", "Editura (id)", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_COMBOLIST); column.setRootSelectable(false); column.setRequired(true); column.setAssociatedControlDataUrl(AdminConstants.WEBMODE_DATA_URL + "_publishers.php"); column.setEditable(true); grid.addColumn(column); column = new CustomColumnConfig("author", "Autor (id)", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_DATATREE); column.setRootSelectable(false); column.setRequired(true); column.setAssociatedControlDataUrl(AdminConstants.WEBMODE_DATA_URL + "_authors.php"); column.setEditable(true); grid.addColumn(column); column = new CustomColumnConfig("isbn", "ISBN", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_TEXT); column.setRequired(true); column.setEditable(true); column.setHidden(true); grid.addColumn(column); column = new CustomColumnConfig("price", "Pret", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_INT); column.setEditable(true); column.setHidden(true); grid.addColumn(column); column = new CustomColumnConfig("description", "Descriere", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_RICHTEXT); column.setEditable(true); column.setHidden(true); grid.addColumn(column); column = new CustomColumnConfig("recommended", "Recomandare", 100); column.setColumnType(CompleteGridConstants.COLUMN_TYPE_CHECKBOX); column.setEditable(true); column.setHidden(true); grid.addColumn(column); try { grid.render(); grid.getStore().groupBy("category"); } catch (CompleteGridException e) { e.printStackTrace(); } currentTab.add(grid, new BorderLayoutData(LayoutRegion.CENTER)); } protected void onExistingTab() {} }
Compile the Client-Side Code
The client-side code will be interpreted by a browser. Thus, it needs to be in a browser-readable format. GWT provides the mechanism needed to translate the Java code into JavaScript code that can be loaded and interpreted by any compatible browser. In order to compile the client-code, the following command must be issued from a command-prompt, from within the directory in which the application was created:

C:> @java -Xmx256M -cp "%~dp0\src;%~dp0\bin;./lib/gwt/gwt-user.jar;./lib/gwt/gwt-dev-windows.jar;./lib/gxt/gxt.jar" com.google.gwt.dev.GWTCompiler -logLevel DEBUG -out "%~dp0\www" %* ro.dizertatie.Admin

The translated JavaScript code is placed inside the www directory. Additional information about the compiling process will be available in the console, due to the log level which is set to the DEBUG value. The generated code is also obfuscated. If you would like to generate clean JavaScript code, you could specify an extra argument in the command line: -style DETAILED.
Deploy the Application
Deploying the application actually means copying the generated resources into a folder accessible for the web server. Let’s consider that we are deploying the application into a directory called dizertatie/admin/. All the server-side code needs to be copied inside the dizertatie/admin/server/ directory. The client-side code must be copied into the specified directory (dizertatie/admin/).
We will also create a .htaccess file that we instruct Apache Http Server to load the Admin.html file as the default file when calling an URL like http://localhost/my-app/admin/. Also, the directives inside the .htaccess file would prohibit directory listing (for security reasons). The .htaccess file content is:
Options -indexes
DirectoryIndex Admin.html
Tips and Tricks
I mentioned earlier (when presenting the API) that you can create and use columns of type COLUMN_TYPE_CUSTOM for displaying custom data-selecting widgets; for instance, it can be used to display an interactive map (e.g.: Google Maps); the custom widget must be enclosed within a HTML page.
  • in order to have access to the custom data, you must use the following pattern: $(window.parent.getEditorId()).value, $() is a shorthand for window.parent.document.getElementById() DOM method
  • in order to set your data, you must use the window.parent.setContent() method and provide it with the value retrieved from your control
I will now present a simple way to integrate a third-party editor into the application. I will show you how we have been able to integrate InnovaStudio WYSIWYG Editor into the application. InnovaStudio WYSIWYG Editor is a web-based HTML Editor that easily integrates into your Web/HTML Forms as an ordinary <textarea> replacement. It is a cross-browser, 100% JavaScript code/object and requires only a few lines of code to include in your web page.
The way the framework works is that it loads a file called editor.html into an IFRAME element. From this element, the custom code has access to the element that needs to be populated with data. All that happens here is simple JavaScript code: the third party editor is initialized after the body has finished loading; a timer is set to ensure the consistency of data in the editor and inside the application. All that needs to be done inside to populate the control inside the application is to call:
window.parent.setContent(__editor.getHTMLBody());
Once this line has been executed, the actual content of the editor is set inside the custom field of the application. The complete code of the file editor.html is listed below:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=windows-1250">
<title>Editor</title>
<style type="text/css">
	* {
		margin: 0;
		padding; 0;
	}
</style>
<script language="JavaScript" 
	type="text/javascript" src='./scripts/innovaeditor.js'></script>
<script language="JavaScript" type="text/javascript">
	var __editor;
	function _initEditor() {
		document.getElementById("txtContent").value = 
			window.parent.document.getElementById(
				window.parent.getEditorId()).value;
 
		__editor = new InnovaEditor("__editor");
		__editor.width = "100%";
		__editor.height = "100%";
		__editor.mode = "XHTMLBody";
		__editor.REPLACE("txtContent");
	}
 
	function setCustomTimer() {
		setInterval("pushContent()", 500);
	}
 
	function pushContent() {
		window.parent.setContent(__editor.getHTMLBody());
	}
</script>
</head>

<body onload="setCustomTimer()">
	<textarea id="txtContent" name="txtContent" style="display: none;" 
		rows="10" cols="50">
	</textarea>

<script language="JavaScript" type="text/javascript">
	_initEditor();
</script>
</body>
</html>
Now, when we try to access a custom field, the InnovaStudio WYSIWYG editor is loaded and the rich content can be edited using a "what you see is what you get" editor:
In the same way presented above we can embed any custom control that respects the basic rules that the framework imposes. As a final example, we present you a simple map embedded into a Dynamize application for managing store locations inside a mall. The map has been developed using Adobe Flash and, similar to the control presented above, uses a simple API call to set the content of the control it represents. Again, the heart of the needed code is composed only of setting the application field to the desired value. This is achieved by executing window.parent.setContent(zone).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		
		<script src="/js/swfobject.js" type="text/javascript"></script>
		
		<script type="text/javascript">
			function setShop(zone, linkUrl) {
				window.parent.setContent(zone);
			}
		</script>
		
		<script type="text/javascript">
			var flashvars = {};
			flashvars.xmlFile = "/xml-categorii.xml";

			var params = {};
			params.wmode = "opaque";
			params.allowScriptAccess = "always";
			
			var attributes = {};
			
			// FLASH
			swfobject.embedSWF("/harta/harta.swf", "flash-map", "998",
			"500", "9.0.0", false, flashvars, params, attributes);
		</script>
	</head>
	
	<body style="background-color: #fff;">
		<div><div id="flash-map"></div></div>
	</body>
</html>