The data model for components is fairly simple. Ideally, all data needed for rendering should be stored in component properties. This is not always the case. For example, TKSelect stores the options in an internal array, which is not accessible through the property interface. Whether or not to use properties depends on how accessible the data should be to the outside world. If the data is intended to be freely modified by the user of the component, without requiring additional processing, then properties are ideal. Sometimes, however, adding or modifying data requires other operations to be performed. In this case, you can register a property change listener (see Advanced Considerations below), or just keep track of the data using member variables.
Rendering requires gathering all relevant data and generating HTML from it. There is no general rule for how to do this part, except that, to the extent possible, the relevant id, class and style tags be set in the right places. Especially important is the id tag, since it is what allows the JavaScript actions to operate on Toolkit components.
Components should also provide a createFromXML()
method, which is called when TKXML is used to create the object. See Advanced Considerations for more on that.
The tutorial will walk you through the process of creating a component. Most additional complexities will be dealt with in Advanced Considerations.
<?php class TKNiceButton extends TKComponent { public function __construct() { parent::__construct(); } protected function renderComponent($class, $style, $events, $id) { } public static function createFromXML($attrs, $contents, $node) { } } ?>
The renderComponent()
method is called by the Toolkit when the component needs to be converted to HTML. It takes four parameters, the first is a string that consists of the " class="..." " tag, or an empty string if there are no CSS classes set. The $style
and $events
parameters are the same. The $id
parameter is always " id="idXXX"" as all components have an ID. These can be embedded directly into the attribute space of the outermost tag you generate. All but $id
may be empty. It is up to you whether you even generate an outermost tag if all of the parameters but $id
are empty.
The createFromXML()
method creates a TKNiceButton
based on XML input. See TKXML for more on the XML format for TKXML. We will leave this function empty for now.
The next step is to decide what kind of data we need to store about our button. Obviously a caption or title would be necessary. We also need to consider if there are additional CSS classes or styles that the user might want to be able to set. And to do that, we need to know exactly what the HTML that we generate is going to look like. We could use a single div that has a thick outset or inset border to make our button. We might, however, want to have multiple divs (for drop shadows), or an image of some sort. If we chose the latter route, we would perhaps want to have CSS classes for the additional divs. All of this data can be stored in properties since it should be freely modifiable by the user of the component. To add these properties, put the following lines in the constructor:
$this->addProperty('caption', self::PROP_STRING); # if we chose to use multiple divs, we might want the following additional properties # NOTE: class and style properties are always of type PROP_ARRAY and will be automatically # converted to strings by TKComponent::render() $this->addProperty('drop_shadow_class', self::PROP_ARRAY); $this->addProperty('drop_shadow_style', self::PROP_ARRAY);
Properties must be named in all lowercase with words separated by underscores. While this isn't enforced by the Toolkit directly, if you fail to follow this pattern, things may break in strange ways.
Additionally, it would make sense to be able to pass in the caption in the constructor and then assign that to the caption
property inside the constructor. To set the caption
property, do this: $this->set('caption', $caption);
where $caption
is the argument passed in to the constructor.
The class now looks like this:
<?php class TKNiceButton extends TKComponent { public function __construct($caption) { parent::__construct(); $this->addProperty('caption', self::PROP_STRING); $this->set('caption', $caption); } protected function renderComponent($class, $style, $events, $id) { } public static function createFromXML($attrs, $contents, $node) { Toolkit::globalSetError("Cannot create TKNiceButtons from XML yet!"); return null; } } ?>
The last part is to render the button. Since we have chosen to go the simple route and only have a single div, our render function should be fairly straightforward. For a button with a title of "Click!" and a user-set CSS class of 'myButton', the following HTML should be generated:
<div id="id0" class="myButton" onclick="alert('You clicked me!')">Click!</div>
We have a problem, however. By default, this won't look or act like a button. We spoke earlier of having the button have thick outset borders. We could manually modify the style so that there are thick outset borders. But then the user couldn't theme or style the button to fit the needs of the web application. So we must leave that information out. The right place to put that is in the default theme's initialize()
method (TKDefaultTheme). We would add the following lines to that method:
# if we want to use a CSS class in toolkit.css, just put in this line: $this->setThemeDefault("TKNiceButton", "class", array("tkNiceButton")); # if we want to use hardcoded styles, put in these lines: $this->setThemeDefault("TKNiceButton", "style", array( 'border' => '3px outset black', 'cursor' => 'pointer', 'background-color' => 'Khaki' ));
If we simply set the "class" field, then we need to define a "tkNiceButton" CSS class somewhere. If TKNiceButton is intended to be included as part of the Toolkit itself (as opposed to a project-specific component), then we should modify /ui/tk/toolkit.css and add the class there. Otherwise, the class can be put in a project's CSS files. And in that case, it is not necessary to modify TKDefaultTheme. Instead, a project-specific theme class can be used.
Now that the CSS and HTML bit is taken care of, we can convert the above to PHP inside renderComponent()
public function renderComponent($class, $style, $events, $id) { $html = "<div$id$class[0]$style[0]$events>" . $this->get('caption') . "</div>\n"; return $html; }
That wasn't so bad. There are a few gotchas, however. Notice that we use "$class[0]" and "$style[0]". The first element of the "$class" and "$style" arrays is the string conversion of the class
and style
properties, respectively. If we had added the drop_shadow_class
and drop_shadow_style
properties, we could access those with "$class[drop_shadow]" and "$style[drop_shadow]", respectively. Notice also that there are no spaces between the variables inside the string. That's because those variables already have a leading space in them, if they have any content. If they don't, then it's no big deal because it's as if the variable wasn't there at all. In other words, trust the Toolkit to make everything work out right.
Now let's tackle the TKXML horror. Fortunately, the Toolkit has methods that will do most of the work for you. The first step is to create a new TKNiceButton object, and then call those functions on that object, and then return the object. For most components, this is usually all that needs to be done:
public static function createFromXML($attrs, $contents, $node) { # create the button object; $contents contains everything inside <TKNiceButton>...</TKNiceButton> $obj = new TKNiceButton("$contents"); # these functions will initialize properties and events automatically self::convertStdAttributes($obj, $attrs); self::convertStdEvents($obj, $node); # we're done, so return the object return $obj; }
The final class looks like this:
<?php class TKNiceButton extends TKComponent { public function __construct($caption) { parent::__construct(); $this->addProperty('caption', self::PROP_STRING); $this->set('caption', $caption); } protected function renderComponent($class, $style, $events, $id) { $html = "<div$id$class[0]$style[0]$events>" . $this->get('caption') . "</div>\n"; return $html; } public static function createFromXML($attrs, $contents, $node) { # create the button object; $contents contains everything inside <TKNiceButton>...</TKNiceButton> $obj = new TKNiceButton("$contents"); # these functions will initialize properties and events automatically self::convertStdAttributes($obj, $attrs); self::convertStdEvents($obj, $node); # we're done, so return the object return $obj; } } ?>
Remember that we also modified TKDefaultTheme and /ui/tk/toolkit.css, or potentially other files depending on the intended usage of TKNiceButton.
renderComponent()
method. As such, it's probably best to write your own components from scratch, perhaps using the Composition design pattern to make use of existing components. Containers, however, are more suited to being extended. See the relevant section in the tutorial on creating new containers.