HoinP means HTML objects in PHP. The easy spelling is HoP.
It's free software under LGPL
The purpose of this library is to offer a structured way to generate XHTML
markup from PHP, without the traditional interleaving of PHP chunks, HTML
chunks and echo
statements all over.
It is an intermediate concept between templating systems (I don't like the paradigm) and PHP DOM extensions (over complicated for the purpose). It will fit well in MVC designed applications and is close to the definition of helpers in the Zend Framework. In fact, the first draft of this library is older than the ZF 1.0 release, while it shares some conceptual similarities with these helpers.
If you hesitate to use it due to server load, consider caching methods and get the flexibility at low cost.
HoinP requires PHP 5
In the controler code :
<?php require_once 'hop/hop.php' ; // the single include required, classes are autoloaded $a_select = $hop->select('choice_1') //create a select object with id 'choice_1' ->auto_name() //the name attribute will be from the id ->add($hop->option()) //empty option object ->add($hop->option('date') //create and add an option object ->value(date('Y'))) //set the value of this option : a PHP fonction result ->add($hop->option('time') //second option ->value(time())) //its value ->onchange('this.form.submit()') // add an onchange attribute to the a_select ->class('a_class') // and a class ; ?>
In the view (or theme) file :
<form action=""> <p><?php $a_select->render() ?></p> </form>
A cooler syntax, using Hop_node_from_file
class (see below) :
<form action=""> <p>{$a_select->render()}</p> </form>
And you get :
Yes, this is useless for such a simple case…
Hop_node
and
Hop_element
Hop
Hop_form_controller
Hop_node_from_file
and
Hop_node_from_buffer
Hop_page
and Hop_menu
.
This section is the most likely to grow as useful widgets will be
added.General rule : every method returns $this, the object it pertains to, unless it is obviously inappropriate, as for getter methods.
Hover the cursor on a class name to unfold relevant chapter. Slide to left margin to fold in. Disable this crazy feature ! (Works badly on IE6, the hover syndrome, hehe)
Hop_node
This the base class. You will instantiate it, each time you need an abstract container (i.e. with no rendering by itself), for example to return a single node from a method or function, with html tree inside.
item parameter can be Hop_node or string. In this latter case this item is a terminal leaf. This feature can be used to add raw html in an otherwise structured tree.
$a_node->add('this is normal text')->add('<b>bold text</b>');
Additionally, setContent() method accepts an array as argument. It must be an array of Hop_nodes or strings and then becomes the new content array.
All three methods manage tree linking, from child to parent through the protected container property, and also from parent to named child, if the node has an id (see below Hop_element) :
$a_node->add($an_element); $b_node = $an_element->get_container(); // $a_node and $b_node reference the same object
$branch = $here->a_branch->cut(); //cut a branch here $there->add($branch); //graft it there $there->add($here->a_branch->cut()); //direct equivalence
These methods return first child, last child or container node, respectively.
Returns a string. It must be output by the caller, or used otherwise, cached, etc.
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 84 $) $Date : 2009-05-14 21:24:32 +0200 (Jeu, 14 mai 2009) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This simple class define a base container, which has no proper output rendered, only contained nodes (or elements). It is the base of the Hop_element class. It must be used per see, each time one need an abstract container to hold contents. */ class Hop_node { public $container ; //for nesting management public $content = array() ; // the content of the object, rendered by the render() method public function __clone() { $this->container = null; foreach ($this->content as $i => $el) if ( $el instanceof Hop_node ) { $this->content[$i] = clone $el; $this->content[$i]->set_container($this); } } protected function set_container($node) { //this function can be redefined in descendant class to supplement the linking task (e.g. from container to child) $this->container = $node; } function cut() { if ($this->container) { foreach($this->container->content as $i => $o) if ($o === $this) unset($this->container->content[$i]); $this->container = null; } return $this ; } //setter //these three functions add element to content function set_content($el = null) { if ( is_array($el) ) $this->content = $el; elseif ($el !== null) $this->content = array($el); foreach ($this->content as $el) if ( $el instanceof Hop_node ) $el->set_container($this); return $this ; } function add($el = null) { if ($el !== null) $this->content[] = $el; //allow any type, only string and Hop_node are rendered, everything else is casted to string if ( $el instanceof Hop_node ) $el->set_container($this); return $this ; } function insert($el = null) { if ($el !== null) array_unshift($this->content, $el); if ( $el instanceof Hop_node ) $el->set_container($this); return $this ; } function insert_at($pos, $el = null) { if ($el !== null) array_splice ( $this->content , $pos , 0 , array($el) ); if ( $el instanceof Hop_node ) $el->set_container($this); return $this ; } public function insert_before($el = null) { $i = $this->index(); $this->container->insert_at($i, $el); return $this ; } public function insert_after($el = null) { $i = $this->index() + 1; if (!$i) $i = count($this->container); $this->container->insert_at($i, $el); return $this ; } //getter //commodity method. return the last element of content array function last() { return end($this->content); } //commodity method. return the first element of content array function first() { return reset($this->content); } function container() { return $this->container; } //index of this element public function index() { if ($this->container) foreach ($this->container->content as $i=>$el) if ($el === $this) return $i; return 0 ; } //render this element, recursively function render($indent = '') { $html_string = '' ; if ($this->content and !is_array($this->content)) $this->content = (array) $this->content; //in case the content has been overwritten with a string foreach ($this->content as $el) { if ( $el instanceof Hop_node ) $html_string .= $el->render($indent); elseif (is_string($el)) $html_string .= (string) $el ; else $html_string .= print_r($el, true) ; //debug purpose don't rely on it } return $html_string ; } } ?>
Hop_element
This is the core class of the package. You will probably not instantiate it directly very often, as the Hop class below offer a useful wrapping factory. Nevertheless it's important to grab some key points of its design.
First, every Hop_element must have an id. If none is provided, it is generated by concatenating its type string and the current number of element in the pool (i.e. p_43). It is important to note that this id is in the PHP space, not the html one, even if it's possible to have the html id reflect the object id by setting the auto_id property. Alternatively setting the auto_name property will reflect the id to the name html attribute.
This id is very useful to access an element after it has been created, using a path notation :
$container->add(new Hop_element('div', 'div_id')); //create a div element inside an existing container object $container->div_id->auto_id(); //access the new div as a container property, using its id
There is also a static property (i.e. unique and global to the class) named pool to access any element by its id, wherever it is in the tree :
Hop_element::pool->div_id; //access through the class static property $hop->pool('div_id'); //access through the $hop object getter (see below) $hop->pool()->div_id; //alternate syntax
This implies that every id must be unique. If it's not it will be rejected and an automatic id generated. Beware, while using pool access, to address the right object. Path access is safer in this respect : a rejected id will be inaccessible. HTML also enforce unique id so it is an early warning.
The second parameter is important and will also be used by the
factory.
It can be a string, and thus is the id of the new object, or an array
or object, for structured options setting.
Compare the flow syntax and the options syntax, both having specific
usages :
//flow syntax $a_div = new Hop_element('div', 'unique_div_id') ; // see below for $hop factory use $a_div->auto_id() // auto_id setter ->class('div_class') //class ->title('div title'); //title
//options array syntax $a_div = new Hop_element('div', array( 'id' => 'unique_div_id', 'attributes' => array( 'auto_id' => true, 'class' => 'div_class', 'title' => 'div title' ))) ;
The attribute property itself can be a string, array or object. A
string will be treated as raw attribute string
'title="something"' and cast to object to allow subsequent
attribute setting.
There is an important distinction between using an attribute setter (see
below) and using an attributes array. In the latter case, there is no
automatic htmlspecialchars() call
Rendering uses empty_el and nl properties to
select the right markup and formating. See Hop
factory
comment and source for details.
Indentation is automatic and increase as recursing the node tree. This ensure nice looking readable markup.
First three are classical setters/getter, while the last is the most interesting : it's a fallback allowing to set/get/remove any attribute, so the two following statements are equivalents :
Revert to setAttr() fo attributes with charset != [a-zA-Z0-9_]+ : setAttr('xml:lang', 'en')
$an_element->title('the title'); $an_element->setAttr('title', 'the title');
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 87 $) $Date : 2017-12-10 15:23:11 +0100 (Dim, 10 déc 2017) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ //XHTML element. class Hop_element extends Hop_node { const INDENT = "\t"; //define the indentation string public static $comment_tag = true ; //optional comment at closing tag for readability. Global to the whole rendering public static $pack = false ; //pack the html //singleton protected static $n_el = 0 ; //element count in the pool added for time optimization public static $pool ; //a "flat" container object for all Hop_elements. //Use it to direct access to an object : Hop_element::$pool->object_id //See also the commodity function in Hop class //mandatory properties values public $type ; //string, element type eg. 'div','p' public $id ; //id of this object, automatic generation if not given //optional properties public $attributes = null ; //stdclass object : html attributes container public $auto_id = false ; //should 'id' html attribute be reflecting the object id public $auto_name = false ; //should 'name' html attribute be reflecting the object id (eg. for input element) public $empty_el = false ; //is this element type empty (otional empty should be set to false) public $nl = 1 ; //newlines when rendering : 0 for inline (e.g. <span>), // 1 for newline before the opening tag (one line elements : <p> or <legend>), // 2 for newline before the closing tag (container elements : <body>, <div>) //constructor function __construct($type, $options = null) { if (!isset(self::$pool)) self::$pool = new stdClass() ; $this->type = $type; // set the element type //process options if (is_string($options)) $options = array('id' => $options) ; // a string option default to the object id elseif ($options and !is_object($options)) $options = (object) $options ; //cast to object for uniform processing if (isset($options->content) ) { //content should be handled by setter for correct parent linking $this->set_content($options->content); unset ($options->content); } if ($options) foreach ($options as $k => $v) $this->$k = $v; //make other options properies of the element //if (isset($this->$k)) //cast attributes to object for easy access (might be array or simple string) if ($this->attributes and !is_object($this->attributes)) $this->attributes = (object) $this->attributes; // if ($this->attributes and !is_object($this->attributes)) settype($this->attributes, 'object'); self::$n_el ++ ; //setting id : if id is specified it's used else a new one is generated in set_id() // a side effect is that an existing element with same id becomes unreachable from the pool (in a loop typically). // One should previoulsy re-set_id() on such element to be able to reach it. // the three line below solve the problem but force to avoid looping on same id. // Not enforced yet for compatibility. // $id = $this->id; // $this->id = null; // $this->set_id($id); $this->set_id($this->id); } function __clone() { parent::__clone() ; self::$n_el ++ ; $id = "{$this->id}_clone_" . self::$n_el ; $this->id = $this->container = null; $this->set_id($id); if ($this->attributes) $this->attributes = clone $this->attributes; } //id setter. useful for cloned element or changing an automatic id function set_id($id = null) { if ($this->id) { unset(self::$pool->{$this->id}); if ($this->container) unset($this->container->{$this->id}); } $this->id = $id; //if no id generate one, for the safeness of the pool. The element will then be difficult to address. if (!$this->id or !is_string($this->id)) $this->id = $this->type; if (isset(self::$pool->{$this->id})) $this->id = "{$this->type}_" . self::$n_el ; //add self to the pool self::$pool->{$this->id} = $this; if ($this->container) $this->container->{$this->id} = $this; return $this ; } protected function set_container($node) { //redefined to add a reverse link from parent to child through an id named property parent::set_container($node) ; //=> $this->container = $node; // if (is_a($node, __CLASS__)) $node->{$this->id} = $this; //cannot be done at node level becaus nodes have no id } function cut() { unset($this->container->{$this->id}); return parent::cut() ; } //rendering function. function render($indent = '') { $nl = self::$pack ? 0 : $this->nl; $html_string = '' ; if ($this->auto_id and empty($this->attributes->id) ) $this->set_attr('id', $this->id); if ($this->auto_name and empty($this->attributes->name) ) $this->set_attr('name', $this->id); if ($nl) $html_string .= "\n$indent"; $html_string .= "<$this->type"; if ($this->attributes) { //cast in case it has been set to a string if (!is_object($this->attributes)) $this->attributes = (object) $this->attributes; foreach ($this->attributes as $k => $value) { if ($k == 'raw' or $k == 'scalar') $html_string .= " $value"; //for raw formatted attributes else $html_string .= " $k=\"$value\""; } } if ($this->empty_el) { $html_string .= ' />'; } else { $html_string .= '>'; $html_string .= parent::render(self::INDENT . $indent) ; if ($nl > 1) $html_string .= "\n$indent"; $html_string .= "</$this->type>"; //optional, for code documentation if ($nl > 1 and self::$comment_tag and isset($this->attributes->id)) $html_string .= "<!-- {$this->attributes->id} -->"; } return $html_string ; } //properties setter function auto_id($val=true) { $this->auto_id = $val; return $this ; } function auto_name($val=true) { $this->auto_name = $val; return $this ; } function empty_el($val=true) { $this->empty_el = $val; return $this ; } function nl($val=1) { $this->nl = $val; return $this ; } //commodity setter for html id function id($value = null) { if ($value) $this->set_attr('id', $value); //set html id attribute, no incidence on hop id else $this->auto_id = true; // shortcut to use hop id return $this ; } //commodity setter for html name function name($value = null) { if ($value) $this->set_attr('name', $value); //set html name attribute else $this->auto_name = true; // shortcut to <auto_name></auto_name> return $this ; } //commodity function to set an attribute function set_attr($attr, $value=null) { if (!isset($this->attributes)) $this->attributes = new stdClass(); if (isset($value)) $this->attributes->$attr = htmlspecialchars($value); //htmlspecialchars htmlentities return $this ; } //commodity function to get an attribute function get_attr($attr) { return @$this->attributes->$attr; } //remove an attribute function remove_attr($attr) { unset ($this->attributes->$attr); return $this ; } //generic attibute setter public function __call($setter, $params) { if (mb_ereg('(set|get|remove)_(.*)', $setter, $e_res)) { switch ($e_res[1]) { case 'set' : return $this->set_attr($e_res[2], $params[0]); case 'get' : return $this->get_attr($e_res[2]); case 'remove' : return $this->remove_attr($e_res[2]); } } //shortcut setter : $el->class('a_class') <=> $el->set_class('a_class') <=> $el->set_attr('class', 'a_class') else return $this->set_attr($setter, $params[0]); // else throw new Exception("invalide function call : $setter"); } //specific class methods public function add_class($class = null) { if ($class) $this->set_attr('class', (($c = $this->get_attr('class')) ? "$c " : '') . "$class"); return $this ; } public function remove_class($class = null) { if ($class) $this->set_attr('class', trim(mb_ereg_replace(" *$class", '', $this->get_attr('class')))); return $this ; } } ?>
Hop
The file containing this class is the only one you should include to use HoinP. It sets the include path and the autoload function to load other classes on the fly.
This class is a singleton. You can't instantiate object with the new operator. You must do it through the single method which always return the same single instance. The global $hop object is created by default when the file is included. If you don't like this identifier or don't want it at global level, unset it and get another reference :
unset($hop) ; $my_prefered_id_in_any_name_scope = Hop::single() ;
The main purpose of this class is to define a factory for
Hop_element
, easing the library usage.
This is done through a fallback method, allowing creation of elements of any
kind :
$hop->canvas(); $hop->whatever_you_want();
Nevertheless, this feature is controlled by mean of three properties arrays, defining collections of elements : containers, single_line, in_flow. They are mutually exclusive, an element type should be listed in only one array. By the default setting, single_line is empty, and thus any element type not listed elsewhere is considered of this category. These categories, which do not map exactly to block and inline html notions, are used to set up the way element are rendered. This is a formating issue, with no functional consequence. See the source comments for detail on output formatting. They are also used to define the parameter ordering and this can be confusing at first.
A last collection, empy_el lists empty elements and is not exclusive : an empty element can be single_line, the default, or in_flow.
Redefine these arrays (with properties assignment, not source editing) to adapt formatting to your liking, or to exclude exotic elements.
Hop fallback factory method accepts 2 parameters : options and
content.
As containers are more likely to have id, be subtree root and have content
added subsequently, options/id is the first parameter.
Single_line and in_flow elements are most often used to add immediate
content, thus it is the first parameter.
$branch = $hop->div('branch_id') // a div with an id, no content ->auto_id() // html id ->add($hop->p('this the text content of the paragraph'))//add a p to the div with content, no id nor options ; $div_op = $hop->div(null, $hop->p('blah')); //a div with no id and immediate content $div_op->add($hop->p(null, 'p_id')); //add another p with no content and an id, to the div $div_op->p_id->add('eventually a content'); //add content to the p latter in the process
All examples use simple strings (id) as options parameter, but it can be structured, as seen before in hop_element chapter.
pool()
method
This is a convenient alternate method to access the global pool. Not much shorter but easy for flow typing.
$hop->pool('div_id'); //access through the $hop object getter $hop->pool()->div_id; //alternate syntax Hop_element::pool->div_id; //equivalent access through the class static property
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 86 $) $Date : 2014-12-14 21:18:10 +0100 (Dim, 14 déc 2014) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* ================================== library include and autoload ================================== */ set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . PATH_SEPARATOR . dirname(__FILE__) . '/widgets') ; function __autoload($class_name) { require_once strtolower($class_name) . '.php' ; } // function __autoload_hop($class_name) { require_once strtolower($class_name) . '.php' ; } // spl_autoload_register('__autoload_hop') ; /* This class is a commodity one, which encapsulate any useful method not pertaining to nodes classes. It is designed have only one instance, created after the class declaration as the global variable $hop. This is the only identifier of the HOP library in the global namespace. The most useful feature is a trap made with the __call magic method. This offers an automatic html objects creation for every type : $hop->div() to instantiate a div, and so on. Default setting allow creation of any arbitrary element, even invalid like $hop->menu() etc. To restrict the element creation to valid types, set the $hop->single_line property to the relevant list o types. ================================== container elements ================================== Element of this kind are rendered with a newline before both opening and closing tag : <div> <div> </div> </div> As content is more likely to be added with add() or insert methods, it is the second (optional) parameter. $d = $hop->div('div1','initial content')->add('added content'); The first parameter can be either a string and then interpreted as the object id or an array or object of properties of the new object. ================================== One line elements (default) ================================== Rendered on one line : <p>text</p> The content parameter it the first : $p = $hop->p('text', $options). Content adding methods are still usable : $p->add('another text')->add(span('special text')->set_class('special')); ================================== Inflow elements ================================== Rendered in flow, without any newline : text before<span>special text</span>text after The content parameter it the first : $sp = $hop->span('special text', $options). Content adding methods are still usable : $sp->add('another text')->set_class('special')); ================================== Empty elements ================================== There is no content so the only, optional parameter is id/options : $inp = $hop->input('txt1')->set_type('text')->set_value('default value'); //flow way $inp = $hop->input(array('id'=>'txt1', 'attributes' => array('type' => 'text', 'value' => 'default text'))) ; // options way */ class Hop { private static $single_hop ; private function __construct() {} static function single() { $c = __CLASS__ ; if (!self::$single_hop) self::$single_hop = new $c ; return self::$single_hop ; } // Following properties can be set to different tag list to adapt rendering to your preference. // The first three are exclusives to each other public $container = array('html', 'head', 'body', 'div', 'ul', 'ol', 'dl', 'pre', 'blockquote', 'object', 'table', 'caption', 'tbody', 'tr', 'form', 'fieldset', 'select') ; public $single_line = null ; public $in_flow = array('span', 'a', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'sub', 'sup', 'ins', 'del') ; // This property define empty elements and should not be changed unless set to another markup. public $empty_el = array('br', 'hr', 'base', 'meta', 'link', 'param', 'area', 'img', 'input', 'col') ; //auto named elements public $auto_name = array('input', 'select', 'textarea') ; //auto id elements (useful for label to form elements) public $auto_id = array() ; //'input select textarea' ; public function __call($element_type, $params) { $params[] = null ; $params[] = null ; //add null values to ensure the minimum params number //default settings $nl = 1 ; $options = $params[1] ; $content = $params[0] ; $check_valid = false ; if (in_array($element_type, $this->container)) { $options = $params[0] ; // id/options is first parameter for container $content = $params[1] ; $nl = 2 ; $check_valid = true ; } elseif (in_array($element_type, $this->empty_el)) { if (is_string($params[0])) { $options = new stdClass() ; $options->id = $params[0]; } else $options = (object) $params[0] ; // id/options is the sole parameter for empty elements $options->empty_el = true; $content = null ; $check_valid = true ; } if (in_array($element_type, $this->in_flow)) { $nl = 0 ; $check_valid = true ; } if ($check_valid or !$this->single_line or in_array($element_type, $this->single_line)) { $el = new Hop_element($element_type, $options) ; $el->set_content($content)->nl($nl); if (in_array($element_type, $this->auto_name)) $el->auto_name(true); if (in_array($element_type, $this->auto_id)) $el->auto_id(true); return $el ; } else throw new Exception("undefined tag : $element_type"); } /* This function return a hop_element object given its id. Or the global pool if none. $o = $hop->pool('anID'); $o = $hop->pool()->anId; $o = Hop_element::$pool->$id; */ function pool($id = null) { if ($id) return @Hop_element::$pool->$id; // isset(Hop_element::$pool->$id) ? Hop_element::$pool->$id :null; return Hop_element::$pool ; } function clone_el($el) { if (is_string($el)) $el = $this->pool($el); return clone $el ; } } $hop = Hop::single() ; ?>
Hop_form_controller
This class works at the interface level between controllers and views. It's a wrapper for creating html form elements and, moreover set and get values of these elements from request or object. It also acts as a container for values, before the elements are generated.
$fc = new Hop_form_controller( array( array('txt1', "input()->type('text')", 'default text 1'), //object has automatic id, name is set to 'txt1' by controller array('txt2', "input(\$id)->type('text')->value('default text 2')"), //object has id. Default value is set regardless of control value, null here array('txt3', "input(\$id)->type('text')->auto_id()", 'default text 3'), //object has id, and it's propagated to html element. Default value is overwritten by values_from_* methods array('check1', "input()->type('checkbox')->value(\$id)"), array('check2', "input()->type('checkbox')->value(\$id)", 'check2'), array('radio', null, 'rad1'), array('rad1', "input()->type('radio')->value(\$id)->name('radio')"), array('rad2', "input()->type('radio')->value(\$id)->name('radio')"), array('ta1', "textarea(null, \$id)->auto_id()", 'default text area'), //beware, textarea in not an empty element so the id, if needed, must be the second parameter array('sel1', "select() ->add(\$hop->option('option1')->value('op1')) ->add(\$hop->option('option2')->value('op2')) ->add(\$hop->option('option3')->value('op3'))", 'op2'), array('ok', "input()->type('submit')->value('Send')") //declaration of button in controller has not utility beside grouping of controls ) ) ; $fc->values_from_request('ok'); // get values if submitted $fc->values_from_data(array('radio'=>'rad2')); //set values from array (object is ok too and less memory consuming) $ul = $fc->hop_all('li()', 'ul()'); // get form elements wrapped in a list $fld = $fc->hop_all('p()', 'fieldset()'); // get form elements in a fieldset $fc->txt3 = 'late modif'; // control value is modified after the hop object are generated $fc->map_global(); //progagate the modif of every controls with id set $fc->map($ul->content[0]->last(), 'txt3'); // set this control value to an arbitrary element, with no id, accessed through its "path"
The controller constructor has only one parameter, a definition array. Elements of this array are arrays, one for each control (form element). Each has three parameters, the last two being optional :
Notice the special case of the radio group which needs a control property to store the group value, and the way the name attribute is set on radio elements to group them with the control property.
After an element has been instantiated, change to the value at the control level is not reflected to the element object, unless you do it with one map method. In "normal" MVC flow, this should not be necessary.
Fonction to add a control, alternative to definition array, and to remove one control given its id.
Use them to set all values in one shot from a database object, or array or request values.
In the latter case, a "trigger" element id must be specified as the first parameter. If it is not set, nothing happens.
The second optional parameter specifies from which global array values are
fetched. Default is $_REQUEST. This function returns the
trigger value or false if not set and thus can be used to test if and why
values were set.
This method does the reverse of values_from_data(), it sets values form the form controller to the array element (associative keys) or object properties.
Use it to reflect the values got from the request into controller data.
Instantiate one element, given a control id, or all of them. This second method is the most productive. Optional parameters define methods for wrapping the element. The first will be used to wrap each element, the second as the global element container. Example is more explicit.
The last method is global, that is, is use the pool to reach elements and set values.
As mentioned above, this is only needed in special cases.
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 87 $) $Date : 2017-12-10 15:23:11 +0100 (Dim, 10 déc 2017) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Hop_form_controller { protected $controls = array() ; //array of unstantiation functions public $trigger = null ; //value of trigger control function __construct($definition = array()) { // definition array must be of the form : array( // array('id', "input(\$id)->set_type('text')", 'value'), // array('ok', "input(\$id)->set_type('submit')", 'Submit data') // ) // the second parameter is the hop function to instantiate the hop_element $id will be substituted at intantiation time foreach ($definition as $c) { $this->add_control($c); } } public function add_control($cont_def = array()) { if ($cont_def) { $cont_def[] = null ; $cont_def[] = null ; // in case not all parameters have been specified $this->controls[$cont_def[0]] = $cont_def[1]; //$this->controls['id'] = "function" $this->{$cont_def[0]} = $cont_def[2]; // $this->control_id = control_value } return $this ; } public function remove_control($id) { //remove a control, given its id. This function must be used BEFORE the HoP elements are instantiated. It will NOT remove an already instantiated control. unset($this->controls[$id]); unset($this->$id); return $this ; } public function values_from_request($trigger , &$mode = null, $index = null) { //$_REQUEST, $_POST, $_GET if (!isset($mode)) $mode =& $_REQUEST ; $this->trigger = isset($mode[$trigger]); if ($this->trigger) { $this->trigger = $mode[$trigger]; foreach ($this->controls as $id => $f) { // echo "$id\n"; // attention l'index d'un control a deux utilisations : pour les formulaire multiples (utilisation du parametre $index) ou pour les select/radio/checkbox. par combinaison, ces derniers sont de second niveaux. $this->$id = ((isset($mode[$id]) and $mode[$id] != 'hop_null') ? (is_array($mode[$id]) && $index !== null ? $mode[$id][$index] : $mode[$id]) : null) ; // $this->$id = ((isset($mode[$id]) and $mode[$id] != 'hop_null') ? (is_array($mode[$id]) ? @$mode[$id][$index !== null ? $index : 0] : $mode[$id]) : null); } } return $this ; } // public function trigger() { // return $this->trigger; // } // public function values_from_data($obj_or_array) { if (is_array($obj_or_array)) $obj_or_array = (object) $obj_or_array ; foreach ($this->controls as $id => $f) $this->$id = (isset($obj_or_array->$id) ? $obj_or_array->$id : null); return $this ; } public function values_to_data(& $obj_or_array) { foreach ($obj_or_array as $id => $val) if (isset($this->$id)) { if (is_array($obj_or_array)) $obj_or_array[$id] = $this->$id; elseif ((is_object($obj_or_array))) $obj_or_array->$id = $this->$id; } return $this ; } public function values($reg_exp = null, $def = null, $adslsh = false) { //returns on array of values, if no default value is given, only non null values are returned. Pass the empty string to get all values ; $values = array() ; foreach ($this->controls as $id => $f) { if ((is_null($reg_exp) or preg_match($reg_exp, $id)) and (isset($def) or isset($this->$id)) ) { $values[$id] = (isset($this->$id) ? (is_array($this->$id) ? implode(',', $this->$id) : $this->$id) : $def); if ($adslsh) $values[$id] = addslashes($values[$id]) ; } } return $values ; } public function map($ho, $id) { // map the control value to the existing hop_element oject switch ($ho->type) { case 'input' : switch ($ho->attributes->type) { case 'checkbox' : $ho->remove_checked(); // $groupe = str_replace('[]', '', $ho->get_name()); $groupe = mb_ereg_replace('[[0-9]*]', '', $ho->get_name()); if (@$this->$groupe) { $values = (is_array($this->$groupe) ? $this->$groupe : explode(',', $this->$groupe)); if (in_array($ho->get_value(), $values) ) $ho->set_checked('checked'); } else if ($this->$id == $ho->get_value()) $ho->set_checked('checked'); break ; case 'radio' : $groupe = mb_ereg_replace('[[0-9]*]', '', $ho->get_name()); if ($this->$groupe == $ho->get_value()) $ho->set_checked('checked'); else $ho->remove_checked(); // echo "$groupe : $id / {$this->$groupe} == {$ho->get_value()} = {$ho->get_checked()}\n"; break ; case 'submit' : case 'reset' : case 'image' : case 'button' : // break ; default : if (isset($this->$id)) $ho->set_value($this->$id); break ; } break ; case 'select' : $values = (is_array($this->$id) ? $this->$id : explode(',', $this->$id)); foreach($ho->content as $op) { if (in_array($op->get_value(), $values)) $op->set_selected('selected'); else $op->remove_selected(); } break ; case 'textarea' : $ho->set_content(htmlspecialchars($this->$id)); break ; default : if (isset($this->$id)) $ho->set_value($this->$id); break ; } return $this ; } public function hop($id) { $hop = Hop::single() ; //get the global hop factory // instantiate and return the hop_element for this id if ($f = $this->controls[$id]) eval ("\$hop_el = \$hop->$f;"); else throw new Exception("No instantiation function definition for $id"); if ( ! $hop_el instanceof Hop_element) return "Invalid Hop_element : $id"; // throw new Exception("Invalid element : $id"); if (!$hop_el->get_name()) $hop_el->set_name($id); $this->map($hop_el, $id); return $hop_el ; } public function hop_all($wrapper = null, $f_container = null, $reg_exp = null) { $hop = Hop::single() ; //get the global hop factory if ($f_container) eval("\$container = \$hop->$f_container;"); else $container = new Hop_node ; foreach ($this->controls as $id => $f) { if ($f and (is_null($reg_exp) or preg_match($reg_exp, $id))) { if ($wrapper) $container->add(eval("return \$hop->{$wrapper}->add(\$this->hop(\$id));")); else $container->add($this->hop($id)); } } return $container ; } public function hop_range($wrapper = null, $f_container = null, $start = null, $end = null) { $hop = Hop::single() ; //get the global hop factory if ($f_container) eval("\$container = \$hop->$f_container;"); else $container = new Hop_node ; $range = !($start and $end) ; foreach ($this->controls as $id => $f) { if (!$range and $id == $start) $range = !$range ; if ($f and $range) { if ($wrapper) $container->add(eval("return \$hop->{$wrapper}->add(\$this->hop(\$id));")); else $container->add($this->hop($id)); } if ($range and $id == $end) $range = !$range ; } return $container ; } public function map_global() { foreach ($this->controls as $id => $f) { if (isset(Hop_element::$pool->$id)) $this->map(Hop_element::$pool->$id, $id); } return $this ; } } /* Example usage : $fc = new Hop_form_controller( array( array('txt1', "input()->set_type('text')", 'default text 1'), //object has automatic id, name is set to 'txt1' by controler array('txt2', "input(\$id)->set_type('text')->set_value('default text 2')"), //object has id. Default value is set regardless of control value, null here array('txt3', "input(\$id)->set_type('text')->auto_id()", 'default text 3'), //object has id, and it's propagated to html element. Default value is overwritten by values_from_* methods array('check1', "input()->set_type('checkbox')->set_value(\$id)"), array('check2', "input()->set_type('checkbox')->set_value(\$id)", 'check2'), array('radio', null, 'rad1'), array('rad1', "input()->set_type('radio')->set_value(\$id)->set_name('radio')"), array('rad2', "input()->set_type('radio')->set_value(\$id)->set_name('radio')"), array('ta1', "textarea(null, \$id)->auto_id()", 'default text area'), //beware, textarea in not an empty element so the id, if needed, must be the second parameter array('sel1', "select() ->add(\$hop->option('option1')->set_value('op1')) ->add(\$hop->option('option2')->set_value('op2')) ->add(\$hop->option('option3')->set_value('op3'))", 'op2'), array('defered_select', "pool('model_select')"), //This select wil be linked at instantiation time to a prgram built model. array('clone_select', "clone_el(\$hop->pool('model_select'))", 'atelier'), //This select wil be cloned at instantiation time from a prgram built model. This is useful if you need several element with same base content. Attributes are cloned too, so they can differ. array('ok', "input()->set_type('submit')->set_value('Send')") //declaration of button in controler has not utility beside grouping of controls ) ) ; $fc->values_from_request('ok'); // get values if submitted $fc->values_from_data(array('radio'=>'rad2')); //set values from array (object is ok too and less memory consuming) $ul = $fc->hop_all('li()', 'ul()'); // get form elements wrapped in a list $fld = $fc->hop_all('p()', 'fieldset()'); // get form elements in a fieldset $fc->txt3 = 'late modif'; // control value is modified after the hop object are generated $fc->map_global(); //progagate the modif of every controls with id set $fc->map($ul->content[0]->last(), 'txt3'); // set this control value to an arbitrary element, whith no id, accessed through its "path" */ ?>
Hop_node_from_file
This special class is designed to simplify integration with html template files. See the relevant chapter for a general discussion on this topic.
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 86 $) $Date : 2014-12-14 21:18:10 +0100 (Dim, 14 déc 2014) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Hop_node_from_file extends Hop_node { public $file ; function __construct($file) { $this->file = $file; $text = file_get_contents($this->file); extract($GLOBALS) ; //for correct substitution of php variable inside the file eval ('$text = "' . addcslashes($text, '"') . '";'); // suppress global html to allow insertion of wysiwyg edited html as a chunk $text = preg_replace('/^.*< *body[^>]*>/is', '', $text); //from start to body tag $text = preg_replace('/< *\/body[^>]*>.*$/is', '', $text); //from /body to end $this->text = $text; // echo $this->text; } public function render($indent = '') { // $text = file_get_contents($this->file); $text = $this->text; $text = preg_replace(array( '/^\t?/m' //indent the chunk to the current level , '/[ ]*([;:?!»])(?=\s|<)/u' //pour fine insecable , '/([« ])[ ]*/u' //pour fine insecable , '/([0-9])\s([0-9])/u' //fine sur nombres , '/&(.*?);/u' // correction effet indésirable ci-dessus ), array( $indent ,' $1' // fine insecable entre crochets une fine insécable[ ] ,'$1 ' // fine insecable   ,'$1 $2' , '&$1;' //rétablissement entitées ) , trim($text)) ; if (@$this->base_img) $text = str_replace('src="img/', "src=\"{$this->base_img}img/", $text); // extract($GLOBALS) ; //for correct substitution of php variable inside the file // eval ('$text = "' . addcslashes($text, '"') . '";'); return "\n$text"; } public static function static_render($file) { extract($GLOBALS) ; // eval ('return "' . addcslashes(file_get_contents($file), '"') . '";'); //évolution pour expression avec "" eval ('$text = "' . addcslashes(preg_replace('/\{(.*)"([^"]*)"(.*)\}/U',"{\$1'\$2'\$3}", @file_get_contents($file)), '"') . '";'); return $text ; // eval ("return \"$text\""); } } ?>
Hop_node_from_buffer
A very simple class to help migrating to HoinP.
When you need to mix, in a view, parts generated from HoinP and parts from the "echo" side, for example a forum module or whatever, this is it.
$debug = new Hop_node_from_buffer ; $debug->start_buffering(); echo 'I hate the echo method!' ; $debug->stop_buffering();
By buffering the standard output, its content is captured and stored in this special node for subsequent rendering, at the right place in your view.
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 47 $) $Date : 2007-09-09 15:43:54 +0200 (Dim, 09 sep 2007) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Hop_node_from_buffer extends Hop_node { function start_buffering() { ob_start() ; } function stop_buffering() { $this->set_content(ob_get_clean()); } } ?>
Hop_page
A simple widget to build a base XHTML page. Source is self-explanatory. <html> and <meta /> elements are full HoP elements, this way you can change default settings.
//notice the use of factory method for direct fluid method chaining. $page = Hop_page::factory('this is the title')->html->setAttr('xml:lang', $session->lang)->lang($session->lang);
A commodity trick is that head and body element are cross referenced from the page level for simpler access :
$page->head === $page->html->head; //true
<?php /* « Copyright 2007 Max Barel a_x@ac-mb.info » Release : 0.4 ($Rev : 67 $) $Date : 2008-03-09 22:02:56 +0100 (Sun, 09 Mar 2008) $ This file is part of HoP. HoP is free software ; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation ; either version 2.1 of the License, or (at your option) any later version. HoP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with HoP ; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Hop_page extends Hop_node { public $id ; //page id is needed to avoid conflict in the gobal pool when several pages are built, i.e. for html messages function __construct($title_or_options = array()) { //options_array_or_object = array('title' => string, 'lang' => string, 'id' => string) //default options $title = 'Webmaster forgot to set this title!' ; $lang = 'en' ; $id = 'main' ; if (is_string($title_or_options)) $title = $title_or_options ; else extract((array) $title_or_options) ; $hop = Hop::single() ; //get the global hop factory $this->doctype = $this->add('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . (Hop_element::$pack ? "\n" : ''))->last(); $this->html = $hop->html("{$id}_html")->xmlns('http://www.w3.org/1999/xhtml')->set_attr('xml:lang', $lang)->lang($lang); $this->add($this->html); $this->head = $hop->head("{$id}_head"); $this->html->add($this->head); $this->head->add($hop->meta("{$id}_content_type")->set_attr('http-equiv', 'Content-Type')->set_attr('content', 'text/html; charset=utf-8')) ; $this->title = $hop->title($title); $this->head->add($this->title); $this->body = $hop->body("{$id}_body"); $this->html->add($this->body); } public static function factory($title = 'Webmaster forgot to set this title!') { return new self($title) ; } } ?>
Hop_menu
This widget build a list based, CSS popup menu. More about CSS and IE in the CSS menus page. It can automatically add a link element in the page header to reference the built-in CSS.
This example shows the add_item() use. As it gives no page parameter the css will not be included automatically.
$menu = Hop_menu::factory() ->add_item('first') ->sub_menu(Hop_menu::factory() ->add_item('sub_item_1_1') ->add_item('sub_item_1_2') ) ->add_item('second') ;
See below, the php code for this site menu. Note that using automatic translation in menu item text is much simpler if item content is hop generated than if raw text :
$menu = Hop_menu::factory(array( 'id' => 'menu_g', 'page' => $page, 'css' => '../hop/widgets/hop_menu_IE6.css', 'items' => array( 'accueil' => $hop->a($tr->_("accueil"))->href('accueil.html'), 'contributions' => $tr->_("contributions"), 'web2' => $hop->a("Ajax, web 2")->href('web2.html'), // 'web2' => '<a href="web2.html">Ajax, web 2</a>', //same as preceding with raw html content 'xhtml' => $hop->a("XHTML")->href('xhtml.html'), 'css' => $hop->a("CSS")->href('css.html'), 'javascript' => $hop->a("JavaScript")->href('javascript.html'), 'php' => $hop->a("PHP")->href('php.html'), 'zend' => $hop->a("Zend framework")->href('zend.html'), 'mvc' => $hop->a("MVC")->href('mvc.html'), 'mysql' => $hop->a("MySQL")->href('mysql.html'), 'i18n' => $hop->a("i18n")->href('i18n.html'), 'spip' => $hop->a("CMS, SPIP")->href('spip.html') ))) ->sub_menu(Hop_menu::factory(array( 'id'=>'logiciels', 'items' => array( 'hop' => $hop->a("Ho<sub>in</sub>P")->href('hop.html'), 'menus' => $hop->a($tr->_("menus en CSS"))->href('menus.html') ))) , 'contributions') ->auto_id() ; $hop->pool()->$sujet->add_class('actif'); //pool access to reach submenu items $page->head->hop_menu->auto_id(); //for javascript vivibility
In most pages, you'll need to mix markup from HoinP and raw markup, edited in you favorite editor, either WYSIWYG or not. According to your design, they will fall in one of two categories :
Main means that the whole page is HTML an some part of it will come from HoP objects rendering. Here is a simple, partial example :
<body> <?php echo $hop_object->render() ?> </body>
Not much to say, the usual include of such a file from your controller, if any, will do.
However, if there are many places in your file where you have objects to render, this syntax become cumbersome and, moreover, is not friendly to WYSIWYG editors.
In such cases you can use a static method from an HoP class : Hop_node_from_file::static_render(filename) in place of the include. It's much like an include, in fact an eval of the included file. The example above thus becomes :
<body> {$hop_object->render()} </body>
This is much easier to type, WYSIWYG friendly and syntactically close to some other templating systems. Just keep in mind that eval'ing the file does not allow for PHP statements or expressions. Only variables substitutions. Nevertheless, object methods are OK, thus opening for rather powerful chained HoP expressions.
In this case, overall page structure is made from HoP objects, and you need to insert chunks of HTML markup when there is no benefit to use HoP, specially where editors do a better job.
The same Hop_node_from_file class does just this : return the html markup from a given file, at render time of the whole HoP tree.
If this file has been made with a WYSIWYG editor, it will have DOCTYPE, html, head and body elements which are not desired there. They are automatically stripped from the output at rendering time.
A typical case would be :
<?php $page = new Hop_page('page title') ; //add stuff $page->body->add(new Hop_node_from_file('HTML_file_to_include')); //add other stuff echo $page->render(); ?>
The code is eval'ed when rendered, as by static_render() method. Thus you can have pre-built HoP objects rendered in the markup. This page has numerous in-place rendering for source files display, while it is included as a big chunk in the general HoP structure of the site. Most code can be seen on the MVC page.
If there is enough interest on this library, I might set-up a forum. In the meantime, my email is on the home page.