Correcting time entry slowness
[wrms.git] / inc / MenuClass.php
blob43f4c7775ef45df52c9d96febde3cbb181cdf4bc
1 <?php
2 /**
3 * Some intelligence and standardisation around presenting a menu hierarchy.
5 * See the MenuSet class for examples as that is the primary interface.
6 * @see MenuSet
8 * @package awl
9 * @subpackage MenuSet
10 * @author Andrew McMillan <andrew@catalyst.net.nz>
11 * @copyright Catalyst IT Ltd
12 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
15 /**
16 * Each menu option is an object.
17 * @package awl
19 class MenuOption {
20 /**#@+
21 * @access private
23 /**
24 * The label for the menu item
25 * @var string
27 var $label;
29 /**
30 * The target URL for the menu
31 * @var string
33 var $target;
35 /**
36 * The title for the item when moused over, which should be displayed as a tooltip.
37 * @var string
39 var $title;
41 /**
42 * Whether the menu option is active
43 * @var string
45 var $active;
47 /**
48 * For sorting menu options
49 * @var string
51 var $sortkey;
53 /**
54 * Style to render the menu option with.
55 * @var string
57 var $style;
59 /**
60 * The MenuSet that this menu is a parent of
61 * @var string
63 var $submenu_set;
64 /**#@-*/
66 /**#@+
67 * @access public
69 /**
70 * The rendered HTML fragment (once it has been).
71 * @var string
73 var $rendered;
74 /**#@-*/
76 /**
77 * The thing we click
78 * @param string $label The label to display for this option.
79 * @param string $target The URL to target for this option.
80 * @param string $title Some tooltip help for the title tag.
81 * @param string $style A base class name for this option.
82 * @param int $sortkey An (optional) value to allow option ordering.
84 function MenuOption( $label, $target, $title="", $style="menu", $sortkey=0 ) {
85 $this->label = $label;
86 $this->target = $target;
87 $this->title = $title;
88 $this->style = $style;
89 $this->attributes = array();
90 $this->active = false;
91 $this->sortkey = $sortkey;
93 $this->rendered = "";
96 /**
97 * Convert the menu option into an HTML string
98 * @return string The HTML fragment for the menu option.
100 function Render( ) {
101 $r = sprintf('<span class="%s_left"></span><a href="%s" class="%s" title="%s"%s>%s</a><span class="%s_right"></span>',
102 $this->style, $this->target, $this->style, htmlentities($this->title), "%%attributes%%",
103 htmlentities($this->label), $this->style );
105 // Now process the generic attributes
106 $attribute_values = "";
107 foreach( $this->attributes AS $k => $v ) {
108 if ( substr($k, 0, 1) == '_' ) continue;
109 $attribute_values .= " $k=\"".htmlentities($v)."\"";
111 $r = str_replace( '%%attributes%%', $attribute_values, $r );
113 $this->rendered = $r;
114 return "$r\n";
118 * Set arbitrary attributes of the menu option
119 * @param string $attribute An arbitrary attribute to be set in the hyperlink.
120 * @param string $value A value for this attribute.
122 function Set( $attribute, $value ) {
123 $this->attributes[$attribute] = $value;
127 * Mark it as active, with a fancy style to distinguish that
128 * @param string $style A style used to highlight that the option is active.
130 function Active( $style ) {
131 $this->active = true;
132 $this->style = $style;
136 * This menu option is now promoted to the head of a tree
138 function AddSubmenu( &$submenu_set ) {
139 $this->submenu_set = &$submenu_set;
143 * Whether this option is currently active.
144 * @return boolean The value of the active flag.
146 function IsActive( ) {
147 return ( $this->active );
152 * _CompareMenuSequence is used in sorting the menu options into the sequence order
154 * @param objectref $a The first menu option
155 * @param objectref $b The second menu option
156 * @return int ( $a == b ? 0 ( $a > b ? 1 : -1 ))
158 function _CompareMenuSequence( $a, $b ) {
159 global $session;
160 $session->Dbg("MenuSet", "Comparing %d with %d", $a->sortkey, $b->sortkey);
161 return ($a->sortkey - $b->sortkey);
167 * A MenuSet is a hierarchy of MenuOptions, some of which might be
168 * MenuSet objects themselves.
170 * The menu options are presented in HTML span tags, and the menus
171 * themselves are presented inside HTML div tags. All layout and
172 * styling is expected to be provide by CSS.
174 * A non-trivial example would look something like this:
175 *<code>
176 *require_once("MenuSet.php");
177 *$main_menu = new MenuSet('menu', 'menu', 'menu_active');
178 * ...
179 *$other_menu = new MenuSet('submenu', 'submenu', 'submenu_active');
180 *$other_menu->AddOption("Extra Other","/extraother.php","Submenu option to do extra things.");
181 *$other_menu->AddOption("Super Other","/superother.php","Submenu option to do super things.");
182 *$other_menu->AddOption("Meta Other","/metaother.php","Submenu option to do meta things.");
183 * ...
184 *$main_menu->AddOption("Do This","/dothis.php","Option to do this thing.");
185 *$main_menu->AddOption("Do That","/dothat.php","Option to do all of that.");
186 *$main_menu->AddSubMenu( $other_menu, "Do The Other","/dotheother.php","Submenu to do all of the other things.", true);
187 * ...
188 *if ( isset($main_menu) && is_object($main_menu) ) {
189 * $main_menu->AddOption("Home","/","Go back to the home page");
190 * echo $main_menu->Render();
192 *</code>
193 * In a hierarchical menu tree, like the example above, only one sub-menu will be
194 * shown, which will be the first one that is found to have active menu options.
196 * The menu display will generally recognise the current URL and mark as active the
197 * menu option that matches it, but in some cases it might be desirable to force one
198 * or another option to be marked as active using the appropriate parameter to the
199 * AddOption or AddSubMenu call.
200 * @package awl
202 class MenuSet {
203 /**#@+
204 * @access private
207 * CSS style to use for the div around the options
208 * @var string
210 var $div_id;
213 * CSS style to use for normal menu option
214 * @var string
216 var $main_class;
219 * CSS style to use for active menu option
220 * @var string
222 var $active_class;
225 * An array of MenuOption objects
226 * @var array
228 var $options;
231 * Any menu option that happens to parent this set
232 * @var reference
234 var $parent;
237 * Will be set to true or false when we link active sub-menus, but will be
238 * unset until we do that.
239 * @var boolean
241 var $has_active_options;
244 * An incrementing value to use for default sort options so they all get
245 * different values.
246 * @var integer
248 var $default_sortkey;
249 /**#@-*/
252 * Start a new MenuSet with no options.
253 * @param string $div_id An ID for the HTML div that the menu will be presented in.
254 * @param string $main_class A CSS class for most menu options.
255 * @param string $active_class A CSS class for active menu options.
257 function MenuSet( $div_id, $main_class = '', $active_class = 'active' ) {
258 $this->options = array();
259 $this->main_class = $main_class;
260 $this->active_class = $active_class;
261 $this->div_id = $div_id;
262 $this->default_sortkey = 91625;
266 * Add an option, which is a link.
267 * The call will attempt to work out whether the option should be marked as
268 * active, and will sometimes get it wrong.
269 * @param string $label A Label for the new menu option
270 * @param string $target The URL to target for this option.
271 * @param string $title Some tooltip help for the title tag.
272 * @param string $active Whether this option should be marked as Active.
273 * @param int $sortkey An (optional) value to allow option ordering.
274 * @return mixed A reference to the MenuOption that was added, or false if none were added.
276 function &AddOption( $label, $target, $title="", $active=false, $sortkey=107965 ) {
277 $new_option = false;
278 if ( $this->_OptionExists( $label ) ) return $new_option;
280 if ( $sortkey == 107965 ) $sortkey = $this->default_sortkey++;
282 $new_option =& new MenuOption( $label, $target, $title, $this->main_class, $sortkey );
283 array_push( $this->options, &$new_option );
284 if ( is_bool($active) && $active == false && $_SERVER['REQUEST_URI'] == $target ) {
285 // If $active is not set, then we look for an exact match to the current URL
286 $new_option->Active( $this->active_class );
288 else if ( is_bool($active) && $active ) {
289 // When active is specified as a boolean, the recognition has been done externally
290 $new_option->Active( $this->active_class );
292 else if ( is_string($active) && preg_match($active,$_SERVER['REQUEST_URI']) ) {
293 // If $active is a string, then we match the current URL to that as a Perl regex
294 $new_option->Active( $this->active_class );
296 return $new_option ;
300 * Add an option, which is a submenu
301 * @param object &$submenu_set A reference to a menu tree
302 * @param string $label A Label for the new menu option
303 * @param string $target The URL to target for this option.
304 * @param string $title Some tooltip help for the title tag.
305 * @param string $active Whether this option should be marked as Active.
306 * @param int $sortkey An (optional) value to allow option ordering.
307 * @return mixed A reference to the MenuOption that was added, or false if none were added.
309 function &AddSubMenu( &$submenu_set, $label, $target, $title="", $active=false, $sortkey=107965 ) {
310 if ( $sortkey == 107965 ) $sortkey = $this->default_sortkey++;
311 $new_option =& $this->AddOption( $label, $target, $title, $active, $sortkey );
312 $submenu_set->parent = &$new_option ;
313 $new_option->AddSubmenu( &$submenu_set );
314 return $new_option ;
318 * Does the menu have any options that are active.
319 * Most likely used so that we can then set the parent menu as active.
320 * @param string $label A Label for the new menu option
321 * @return boolean Whether the menu has options that are active.
323 function _HasActive( ) {
324 if ( isset($this->has_active_options) ) {
325 return $this->has_active_options;
327 foreach( $this->options AS $k => $v ) {
328 if ( $v->IsActive() ) {
329 $rc = true;
330 return $rc;
333 $rc = false;
334 return $rc;
338 * Find out how many options the menu has.
339 * @return int The number of options in the menu.
341 function Size( ) {
342 return count($this->options);
346 * See if a menu already has this option
347 * @return boolean Whether the option already exists in the menu.
349 function _OptionExists( $newlabel ) {
350 $rc = false;
351 foreach( $this->options AS $k => $v ) {
352 if ( $newlabel == $v->label ) {
353 $rc = true;
354 return $rc;
357 return $rc;
361 * Mark each MenuOption as active that has an active sub-menu entry.
363 * Currently needs to be called manually before rendering but
364 * really should probably be called as part of the render now,
365 * and then this could be a private routine.
367 function LinkActiveSubMenus( ) {
368 $this->has_active_options = false;
369 foreach( $this->options AS $k => $v ) {
370 if ( isset($v->submenu_set) && $v->submenu_set->_HasActive() ) {
371 // Note that we need to do it this way, since $v is a copy, not a reference
372 $this->options[$k]->Active( $this->active_class );
373 $this->has_active_options = true;
380 * Render the menu tree to an HTML fragment.
382 * @param boolean $submenus_inline Indicate whether to render the sub-menus within
383 * the menus, or render them entirely separately after we finish rendering the
384 * top level ones.
385 * @return string The HTML fragment.
387 function Render( $submenus_inline = false ) {
388 if ( !isset($this->has_active_options) ) {
389 $this->LinkActiveSubMenus();
391 $options = $this->options;
392 usort($options,"_CompareMenuSequence");
393 $render_sub_menus = false;
394 $r = "<div id=\"$this->div_id\" class=\"$this->div_id\">\n";
395 foreach( $options AS $k => $v ) {
396 $r .= $v->Render();
397 if ( $v->IsActive() && isset($v->submenu_set) ) {
398 $render_sub_menus = $v->submenu_set;
399 if ( $submenus_inline )
400 $render_sub_menus->Render();
403 $r .="</div>\n";
404 if ( !$submenus_inline && $render_sub_menus != false ) {
405 $r .= $render_sub_menus->Render();
407 return $r;