====== Creating new site objects from the scratch ======
We hope you've already read the basic LIMB 2.x principles and got the main idea.
So it's time to implement your very first custom site objects, isn't it? :-)
Please note we're going to describe very simple custom site objects implementation, the main goal is to shed some light on LIMB 2.x internals. If you need to create something way more complex, you'd probably want to browse the [[http://limb-project.com/root/download|LIMB source]] and [[http://limb-project.com/forum|consult developers]] about any problems you might face. You can also take a look at the [[https://sourceforge.net/project/showfiles.php?group_id=109345&package_id=118079&release_id=249852|demo site source]] if anything goes wrong or may seem to be unclear.
In order to create the new site object you're usually required to accomplish the following steps:
- Plan your new site object: whether it's a site object or a content object, probable attributes and required functionality
- Create a class for it(inherit from **site_object** or **content_object** base class)
- Create a db_table class if you are creating a content object
- Create a site object controller class for it.
- Create action classes to implement functionality
- Create template files for actions with GUI (good candidates are display, edit, create actions templates).
- Register the new site object in the object tree using admin_page's register_new_object action.
- Assign appropriate access permissions
- Create ini-files with localized strings (optional)
==== Planning ====
Let's imagine we need a sort of very specific **catalog**. Suppose our catalog has some form of hierarchically linked folders. Every folder contains //catalog folders// as well as //catalog objects//.
To implement this we need 2 types of new site objects: **some_catalog_object** and **some_catalog_folder**.
Below we'll try to create all necessary classes and templates to implement our simple catalog functionality step by step.
**some_catalog_folder** will be a common site_object as far as it doesn't have any additional attributes other than 'identifier' and 'title'.
**some_catalog_object** is a content object since it would be nice for it to support versioning, it has the following attributes:
* 'identifier' - VARCHAR
* 'title' - VARCHAR
* 'description' - VARCHAR
* 'price' - FLOAT
* 'image_id' - INTEGER, id of the image from the LIMB media repository
==== Creating site object classes ====
Site object classes are used to define the fetching and saving behaviour. Usually the only thing you need is to inherit your custom site object class from one of the 2 base classes: **site_object** or **content_object**. We won't go into advanced details in this tutorial. Let's just keep it simple for a while.
//In fact you have ability not to create a class for catalog_folder since there is no need to specify controller class name in Limb 2.3+ for every site object. (in Limb 2.2+ it was neccessary to specify controller in site_object class). Any way we will continue since we want to specify special icon for our catalog folders.//
Let's create a file for our **some_catalog_folder** class in site_objects folder of our project. The path to the file will look like this: PROJECT_DIR/core/model/site_objects/some_catalog_folder.class.php:
And the contents:
1, //was used for sorting in the tree, e.g. folders usually have this one set to 0,
//it was used to for 'folders first' sorting functionality, currently not implemented
'can_be_parent' => 1, // since it's a catalog folder
'icon' => '/shared/images/folder.gif' // icon to display in tree structure
);
}
}
?>
Next step is a class for **some_catalog_object**. The file with the class will has a path like this: PROJECT_DIR/core/model/site_objects/some_catalog_object.class.php
array('search' => true, 'search_weight' => 1),
// tells to the LIMB search engine to index some_catalog_object by 'description' attribute
//and use 'search_weight' for relevant sorting
));
}
function _define_class_properties()
{
return array(
'class_ordr' => 1,
'can_be_parent' => 0,
'auto_identifier' => true //there's no need to set identifier manually for this object
);
}
}
?>
==== Creating db_table classes ====
LIMB uses db_table mechanism to abstract db calls to tables. This mechanism allows to emulate nested deletes and simplify basic operations. At the moment you have to duplicate your db tables with corresponding db_table classes. There's no db schema auto generation, yet it's planned to appear some day in the future. So first you have to create tables in database and then describe them with db_tables.
You have only to create db_table for **some_catalog_object**, there's no need to create one for **some_catalog_folder**, since it doesn't have versions thus the default sys_site_object_db_table is used(this behaviour is defined in the base **site_object** class).
Since **some_catalog_object** is inherited from the **content_object** base class it keeps all attributes in one db table by default. Appropriate db_table class is used for carrying-out fetching/creating/deleting. **some_catalog_object_db_table** will be used by default(the base class tries to guess which db_table to use if not explicitly specified).
//You can override db_table class name for the object by db_table_name property (do not append '_db_table' postfix), e.g. //
class some_site_object ...
{
....
function _get_db_table_name()
{
return 'some_table_name';
}
}
or like this
class some_site_object ...
{
....
function _define_class_properties()
{
return array(
[...]
'db_table_name' => 'some_table_name'
);
}
}
**some_catalog_object_db_table** has the following code (PROJECT_DIR/core/db_tables/some_catalog_object_db_table.class.php):
array('type' => 'numeric'),
'description' => '', //similar to array('type' => 'string') used for convenience of typing
'price' => array('type' => 'numeric'),
);
}
}
?>
Columns definitions are useful in type casting and filtering.
String type used if the definition for a column is empty. The list of
supported types at the moment:
* 'numeric'
* 'string'
* 'boolean'
* 'date'
* 'datetime'
**some_catalog_object** - is a default name of data base table **some_catalog_object_db_table** class will work with.
You can override name of the table, placing some_catalog_object_db_table :: _define_db_table_name() method:
function _define_db_table_name()
{
return 'some_other_name';
}
DB schema for this table is as follows:
some_catalog_object CREATE TABLE `some_catalog_object` (
`id` int(11) unsigned NOT NULL auto_increment,
`object_id` int(11) unsigned NOT NULL default '0',
`version` int(11) unsigned NOT NULL default '0',
`identifier` varchar(50) NOT NULL default '',
`title` varchar(50) NOT NULL default '',
`description` varchar(255) NOT NULL default '',
`price` float default NULL,
`image_id` int(11) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ov` (`object_id`,`version`),
KEY `object_id` (`object_id`)
) TYPE=InnoDB
==== Creating controllers ====
The controller defines some site object functionality. In Limb 2.3+ it's possible to assign any controller to a site object.
Our **some_catalog_folder** is able to display itself in 2 lists (admin_display + display),
create child objects and folders, edit and delete itself.
Ok, here goes **some_catalog_folder_controller** source (PROJECT_DIR/core/controllers/some_catalog_folder_controller.class.php). There will be a detailed explanation for every attribute below:
array(
'template_path' => '/some_catalog_folder/display.html'
),
'admin_display' => array(
'template_path' => '/some_catalog_folder/admin_display.html'
),
'create_some_catalog_folder' => array(
'template_path' => '/some_catalog_folder/create.html',
'action_path' => '/some_catalog_folder/create_some_catalog_folder_action',
'JIP' => true,
'popup' => true,
'img_src' => '/shared/images/new.folder.gif',
'action_name' => strings :: get('create_catalog_folder', 'some_catalog'),
'can_have_access_template' => true,
),
'edit' => array(
'popup' => true,
'JIP' => true,
'action_name' => strings :: get('edit_catalog_folder', 'some_catalog'),
'action_path' => '/site_object/edit_action',
'template_path' => '/site_object/edit.html',
'img_src' => '/shared/images/edit.gif'
),
'create_some_catalog_object' => array(
'template_path' => '/some_catalog_object/create.html',
'action_path' => '/some_catalog_object/create_some_catalog_object_action',
'JIP' => true,
'popup' => true,
'img_src' => '/shared/images/new.generic.gif',
'action_name' => strings :: get('create_catalog_object', 'some_catalog'),
'can_have_access_template' => true,
),
'delete' => array(
'JIP' => true,
'popup' => true,
'action_name' => strings :: get('delete_catalog_folde', 'some_catalog'),
'action_path' => 'form_delete_site_object_action',
'template_path' => '/site_object/delete.html',
'img_src' => '/shared/images/rem.gif'
)
);
}
}
?>
Let's clarify some attributes:
* **JIP** flag means whether the action is to be displayed in the template Just In Place controls.
* **action_name** holds the localized full name of this action.
* **action_path** option tells the relative path to the action object. It's important to know how LIMB resolves relative files paths to understand the principle. Note that we don't append '.class.php' - this done automatically just to save your typing time.
* **template_path** points to the relative template file to display the action result. The same resolving technique]] is applied to it too.
* **popup** flag means that action should be performed in the popup window.
* **img_src** is used to display an icon for this action.
//For Limb 2.2 users. Note: **permissions_required** option was removed from controller specification due to access policy changes. Limb 2.3+ access policy was greately simplified so there is no need for //permissions_required// attribute any more.//
It's a turn for the **some_catalog_object** controller (PROJECT_DIR/core/controllers/some_catalog_object_controller.class.php):
array(
'template_path' => '/some_catalog_object/display.html',
),
'admin_display' => array(
'template_path' => '/some_catalog_object/admin_display.html'
),
'edit' => array(
'popup' => true,
'JIP' => true,
'action_name' => strings :: get('edit_catalog_object', 'some_catalog'),
'action_path' => '/some_catalog_object/edit_some_catalog_object_action',
'template_path' => '/some_catalog_object/edit.html',
'img_src' => '/shared/images/edit.gif'
),
'delete' => array(
'JIP' => true,
'popup' => true,
'action_name' => strings :: get('delete_catalog_object', 'some_catalog'),
'action_path' => 'form_delete_site_object_action',
'template_path' => '/site_object/delete.html',
'img_src' => '/shared/images/rem.gif'
)
);
}
}
?>
==== Localized *.ini files ====
As you may have noted from controllers definition we use strings :: get([...]) calls to get the localized values.
You'll definitely want to read more on LIMB I18N and L10N conceptions.
Our **some_catalog_en.ini** file is as follows:
[constants]
create_catalog_folder = Create catalog folder
create_catalog_object = Create catalog object
catalog_object = Catalog object
catalog_folder=Folder
catalog_object_creation = Catalog object creation
catalog_object_edition = Catalog object edition
catalog_folder_creation = Catalog folder creation
catalog_folder_edition = Catalog folder edition
catalog_image_creation = Image creation
catalog_image_edition = Image edition
image = Image
edit_catalog_object = Edit catalog object
edit_catalog_folder = Edit catalog folder
edit_catalog_image = Edit catalog image
delete_catalog_object = Delete catalog object
delete_catalog_folder = Delete catalog folder
description = Description
price = Price
//For Limb 2.2+ users. Extends section was removed in Limb 2.3 for simplification.//
[extends]
filename = common
==== Creating action classes ====
Let's implement some actions defined in our **some_catalog_folder** controller.
First goes the 'create_some_catalog_folder' action (PROJECT_DIR/core/actions/some_catalog_folder/create_some_catalog_folder_action.class.php).
This action should create a nested catalog folder under the current catalog folder. Here's the code:
Simple, isn't it? :-) We just specified controller name that should be assigned to new catalog folder by default.
There is no need to create a special action class for "edit" action since the standart /site_object/edit_action.class.php will be used. See some_catalog_folder controller class above.
That's it for the **some_catalog_folder**. You may ask where're the rest of actions? Well, they're already coded for you in LIMB and there's no need to override them in standart situations.
Then comes 'create_some_catalog_object' action (PROJECT_DIR/core/actions/some_catalog_object/create_some_catalog_object_action.class.php):
'price',
'description' => ' description ',
'image_id' => 'image_id'
)
);
}
function _init_validator()
{
parent :: _init_validator();
$v = array();
$this->validator->add_rule($v[] = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'title'));
$this->validator->add_rule($v[] = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'description'));
$this->validator->add_rule($v[] = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'price'));
}
}
?>
* **_define_site_object_class_name()** - defines the class of the site object.
* **_define_dataspace_name()** - defines the dataspace to be used by this action. It returns the value of the 'name' attribute of the form component in the WACT template assigned to the action. There will be created a common dataspace shared by the action and the template.
* Note also how we validate values coming from the request: we define rules in **_init_validator()** method. To avoid the overhead of parsing PHP code before real execution we use lazy load technique similiar to [[http://wact.sourceforge.net/index.php/ResolveHandle'>this one]].
You can find a bunch of different rules in LIMB_DIR/core/lib/validators/rules directory.
The code above should be self-explanatory yet you'll definitely want to dig the **form_create_site_object_action** base class :)
* Please note that with **_define_datamap()** method we define how request variables should be mapped into site object's attributes.
Well, it's time for the **some_catalog_object_controller** actions implementaion. The only one we have to code here is 'edit'action (PROJECT_DIR/core/actions/some_catalog_object/edit_some_catalog_object_action.class.php), since the rest of them are already coded for you in LIMB.
'price',
'description' => ' description ',
'image_id' => 'image_id'
)
);
}
function _init_validator()
{
parent :: _init_validator();
$this->validator->add_rule($v1 = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'title'));
$this->validator->add_rule($v2 = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'description'));
$this->validator->add_rule($v3 = array(LIMB_DIR . 'core/lib/validators/rules/required_rule', 'price'));
}
}
?>
Well that should be it for actions!
Some actions may seem to be very similar with only several different pieces of code, yep, we know about it. It could be very nice to have some auto code generation for actions based on meta information. At the moment all we can say is that it's planned :-)
==== Creating template files ====
In this section we'll create a couple of templates for site objects actions. Why only a couple? Well, because some action re-use the default templates bundled with LIMB and some action don't need a template at all. Say, 'delete' action of the **some_catalog_object** - it makes use of the LIMB_DIR/design/default/templates/site_object/delete.html default template, take a look at action declaration in **some_catalog_object_controller** above. And sometimes there's no template at all, since its a non-interactive action(not covered in this tutorial though).
LIMB templates are active. Usually all db fetching operations are performed by
templates datasources(not a mandatory behaviour though).
Here is the template for the 'admin_display' action of some_catalog_folder site object, we place it in PROJECT_DIR/design/main/templates/some_catalog_folder/admin_display.html:
This template contains 2 tables to display child folders and child catalog objects.
* **** gets a bunch of child some_catalog_folders at depth=1 by default and
assigns them to target 'folders' component.
* **** is used to display tabular data. and ****
make output only if the data set has at least one record (**** tag
can be in case of empty data set).
Let's implement template for the 'create' action of the some_catalog_folder site object, we place it in PROJECT_DIR/design/main/templates/some_catalog_folder/create.html:
And the last is one is the template for the 'create' action of the some_catalog_object site object, we place it in PROJECT_DIR/design/main/templates/some_catalog_object/create.html:
As long as the form fields are similar for creating and editing it's convenient to share them, that's why we include them with <core:INCLUDE file="/some_catalog_object/form_fields.html"> tag.
Here's the contents of PROJECT_DIR/design/main/templates/some_catalog_object/form_fields.html:
==== Registering site objects ====
Please take a look at [[register_new_site_object|register new site objects documentation page]] for more details.
The only site object we have to register is **some_catalog_folder**, we don't need to register **some_catalog_object** since some_catalog_folder //knows// how and where to place its children some_catalog_object//s//.
First you have to be logged in! Go to the **/root/admin** page then and find there 'register new site object' action. Click it and enter the following values for your new some_catalog_folder:
* class_name = **some_catalog_folder**
* controller_name = **some_catalog_folder_controller**
* identifier = **catalog**
* title = **My new catalog**
* path = **/root**
However your catalog folder is not accessible yet. Go to the **/root/admin/controllers** (/root/admin/classes in LIMB 2.2.x), find your newly installed **some_catalog_folder_controller** class and set access for groups for it. For more information take a look at the access policy documentation page.
I guess that should be it, finally! :) You should be able to find your site object in the site tree **/root/admin/site_structure**. Try browsing it in the 'front' at **/root/catalog** and in the 'back' **/root/catalog?action=admin_display**(as you can see this separation is very thin and transparent)