Class for outputting HTML with ease

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
SpectreNectar
Forum Newbie
Posts: 4
Joined: Wed May 12, 2010 12:04 pm

Class for outputting HTML with ease

Post by SpectreNectar »

Hi, I wrote this class:

Code: Select all

<?php
	
	/**
	* Hierarchical Tree (in) Massive List
	* http://www.recursive.dk
	**/
	class HTMList {
		
		// ~ FUNCTION LIST ~
		// __construct
		// set
		// get
		// sub
		// remove
		// rename
		// add_before
		// add_after
		// add_around
		// indent
		// outdent
		// add_script_before
		// add_script_after
		// javascript_array
		// add_css_after
		// add_css_before
		// css_array
		// toROWS
		// fromROW
		// isSingleROW
		// insertValue
		// getInsertSafeInt
		// fixIntegerKeys
		// debug
		// translate
		// str
		
		private $debugmode, $debuginfo;
		private $rows;
		
		/**
		* Called when creating your root object, and each time an indent is used
		* Only accepts arrays, and has the ability to enable debug info
		**/
		function __construct($a=array(), $setdebug=false) {
			
			//Show error messages?
			$this -> debugmode = $setdebug;
			$this -> debuginfo = array();
			
			$this -> toROWS($a);
			$this -> rows = $a; //MAIN VAR
			
		}
		
		/**
		* Change an existing row, or the whole tree on blank argument
		**/
		public function set($a, $n="") {
			
			$this -> toROWS($a);
			
			if(trim($n)=="") {
				if($this -> isSingleROW($a)) $a = array($a);
				$this -> rows = $a;
			} else {
				// Constrain withing string if it's a single item array
				$this -> isSingleROW($a);
				if(is_array($a)) {
					$this -> rows[$n] = new HTMList($a);
				} else {
					$this -> rows[$n] = $a;
				}
				
			}
		}
		
		/**
		* Get an existing row, or the whole tree on blank argument
		**/
		public function get($n="") {
			if(trim($n)=="") return $this -> rows;
			else return $this -> rows[$n];
		}
		
		/**
		* Traverse the htm tree
		**/
		public function sub($n) {
			return (is_object($this -> rows[$n])) ? $this -> rows[$n] : null ;
		}
		
		/**
		* Delete row
		**/
		public function remove($n) {
			
			if(!isset($this -> rows[$n])) $this -> debug("Trying to remove a row that doesn't exists.", "warning");
			
			unset($this -> rows[$n]);
		}
		
		/**
		* Give sub another name
		**/
		public function rename($n, $m) {
			
			if(isset($this -> rows[$m])) $this -> debug("Renaming row to one that already exists.", "warning");
			
			$b = array();
			foreach($this -> rows as $key => $val) {
				if($key==$n) $b[$m] = $val;
				else $b[$key] = $val;
			}
			
			$this -> set($b);
			
		}
		
		/**
		* Inject row(s) before another row, possibly overwriting itself if there's a naming conflict
		**/
		public function add_before($a, $oldN="", $newN="") {
			
			$this -> toROWS($a);
			$this -> isSingleROW($a);
			
			//Determine position to add before
			if(trim($oldN)!="") {
				
				//Iterate through all rows
				$b = array();
				foreach($this -> rows as $label => $htm) {
					
					if(strcmp($label, $oldN)==0) {
						
						//Add new rows before choosen row
						if(!is_array($a)) {
							$this -> insertValue($b, $a, $newN);
						} else {
							foreach($a as $label2 => $htm2) {
								$this -> insertValue($b, $htm2, $label2);
							}
						}
						
					}
					
					$b[$label] = $htm;
				}
				
				$this -> rows = $b;
				
			} else {
				//Add at beginning
				
				$b = array();
				if(!is_array($a)) {
					$this -> insertValue($b, $a, $newN);
				} else {
					foreach($a as $label => $htm) {
						$this -> insertValue($b, $htm, $label);
					}
				}
				//Remaining rows come afterwards
				foreach($this -> rows as $label => $htm) {
					$this -> insertValue($b, $htm, $label);
				}
				
				$this -> rows = $b;
				
			}
			//Maintain integer indexes
			$this -> fixIntegerKeys();
		}
		
		
		/**
		* Append row(s) to another row, possibly overwriting itself if there's a naming conflict
		**/
		public function add_after($a, $oldN="", $newN="") {
			
			$this -> toROWS($a);
			$this -> isSingleROW($a);
			
			//Determine position to add after
			if(trim($oldN)!="") {
				
				//Iterate through all rows
				$b = array();
				
				foreach($this -> rows as $label => $htm) {
					
					$this -> insertValue($b, $htm, $label);
					
					if(strcmp($label, $oldN)==0) {
						//Add new rows after choosen row
						if(!is_array($a)) {
							$this -> insertValue($b, $a, $newN);
						} else {
							foreach($a as $label2 => $htm2) {
								$this -> insertValue($b, $htm2, $label2);
							}
						}
						
					}
				}
				
				$this -> rows = $b;
				
			} else {
				//Add at the end
				
				$b = array();
				//Original rows come first
				foreach($this -> rows as $label => $htm) {
					$this -> insertValue($b, $htm, $label);
				}
				//Followed by new rows
				if(!is_array($a)) {
					$this -> insertValue($b, $a, $newN);
				} else {
					foreach($a as $label => $htm) {
						$this -> insertValue($b, $htm, $label);
					}
				}
				
				$this -> rows = $b;
				
			}
			//Maintain integer indexes
			$this -> fixIntegerKeys();
			
		}
		
		
		/**
		* Put row(s) around block of rows, possibly overwriting itself if there's a naming conflict
		**/
		public function add_around($a1, $a2, $oldN="", $newN1="", $newN2="") {
			
			$this -> toROWS($a1);
			$this -> isSingleROW($a1);
			
			$this -> toROWS($a2);
			$this -> isSingleROW($a2);
			
			//Determine position to add around
			if(trim($oldN)!="") {
				
				//Iterate through all rows
				$b = array();
				
				foreach($this -> rows as $label => $htm) {
					
					if(strcmp($label, $oldN)==0) {
						
						//Add new rows before choosen row
						if(!is_array($a1)) {
							$this -> insertValue($b, $a1, $newN1);
						} else {
							foreach($a1 as $label2 => $htm2) {
								$this -> insertValue($b, $htm2, $label2);
							}
						}
						
					}
					
					$this -> insertValue($b, $htm, $label);
					
					if(strcmp($label, $oldN)==0) {
						//Add new rows after choosen row
						if(!is_array($a2)) {
							$this -> insertValue($b, $a2, $newN2);
						} else {
							foreach($a2 as $label2 => $htm2) {
								$this -> insertValue($b, $htm2, $label2);
							}
						}
						
					}
				}
				
				$this -> rows = $b;
				
			} else {
				
				//Add around the whole block
				$b = array();
				if(!is_array($a1)) {
					$this -> insertValue($b, $a1, $newN1);
				} else {
					foreach($a1 as $label => $htm) {
						$this -> insertValue($b, $htm, $label);
					}
				}
				
				//Original rows in the middle
				foreach($this -> rows as $label => $htm) {
					$this -> insertValue($b, $htm, $label);
				}
				
				//Followed by last rows
				if(!is_array($a2)) {
					$this -> insertValue($b, $a2, $newN2);
				} else {
					foreach($a2 as $label => $htm) {
						$this -> insertValue($b, $htm, $label);
					}
				}
				
				
				$this -> rows = $b;
				
			}
			//Maintain integer indexes
			$this -> fixIntegerKeys();
			
		}
		
		
		/**
		* Makes white space on the output by making inN an object
		**/
		public function indent($inN, $newN) {
			
			$htm = $this -> get($inN);
			$temp = new HTMList(array($newN => $htm));
			
			$this -> set($temp, $inN);
			
		}
		
		
		/**
		* Clears white space from the output
		**/
		public function outdent($inN) {
			
			$htm = $this -> get($inN);
			if(is_object($htm)) {
				$htm = $htm -> get();
				if(is_array($htm)) {
					$this -> add_after($htm, $inN);
					$this -> remove($inN);
				} else $this -> set($htm, $inN);
			}
			
		}
		
		
		/**
		* Adds javascript before a html chunk
		**/
		public function add_script_before($a, $oldN="", $newN="", $v="") {
			
			$scr = $this -> javascript_array($a, $v);
			
			$this -> add_before($scr[0], $oldN);
			$this -> add_before($scr[1], $oldN);
			$this -> add_before($scr[2], $oldN);
			
		}
		
		/**
		* Adds javascript after a html chunk
		**/
		public function add_script_after($a, $oldN="", $newN="", $v="") {
			
			$scr = $this -> javascript_array($a, $v);
			
			$this -> add_after($scr[2], $oldN);
			$this -> add_after($scr[1], $oldN);
			$this -> add_after($scr[0], $oldN);
			
		}
		
		
		/**
		* Wraps javascript in an array
		**/
		private function javascript_array($script, $v) {
			
			if(!is_array($script)) $script = array($script);
			
			$version = "";
			if(trim($v)!="") $version = " language=\"javascript".$v."\"";
			
			$starttag = array(
				"<script type=\"text/javascript\"".$version.">",
				"<!--"
			);
			
			$code = array(
				"",
				$script,
				""
			);
			
			$endtag = array(
				"// -->",
				"</script>"
			);
			
			return array($starttag, $code, $endtag);
			
		}
		
		
		/**
		* Adds css before a html chunk
		**/
		public function add_css_before($a, $oldN="", $newN="") {
			
			$style = $this -> css_array($a);
			
			$this -> add_before($style[2], $oldN);
			$this -> add_before($style[1], $oldN);
			$this -> add_before($style[0], $oldN);
			
		}
		
		/**
		* Adds css after a html chunk
		**/
		public function add_css_after($a, $oldN="", $newN="") {
			
			$style = $this -> css_array($a);
			
			$this -> add_after($style[0], $oldN);
			$this -> add_after($style[1], $oldN);
			$this -> add_after($style[2], $oldN);
			
		}
		
		
		/**
		* Wraps css in an array
		**/
		private function css_array($style) {
			
			if(!is_array($style)) $style = array($style);
			
			$starttag = array(
				"<style>"
			);
			
			$sheet = array(
				"",
				$style,
				""
			);
			
			$endtag = array(
				"</style>"
			);
			
			return array($starttag, $sheet, $endtag);
			
		}
		
		
		/**
		* Prepares input for use inside the class, by turning it into an array containing only labeled strings and labeled objects with arrays in them
		* The array is allowed to contain one or more HTMLists
		**/
		private function toROWS(&$x) {
			
			if(is_object($x)) $x = $x -> get();
			if(!is_array($x)) $x = array("".$x);
			else {
				//Turn vals into string
				foreach($x as $label => $htm) {
					if(!is_object($htm) && !is_array($htm)) $x[$label] = "".$htm;
				}
			}
			
			//Check that everything has a text label, -otherwise make a digit one
			//-By making a clone of the array and then digit-labeling that
			$y = array();
			$n = 0;
			$madeN = false;
			foreach($x as $label => $htm) {
				if(!is_string($label)) {
					$y[$n] = $htm;
					$madeN = true;
				} else {
					$y[$label] = $htm;
				}
				$n++;
			}
			if($madeN) {
				$x = $y;
				$this -> debug("Unlabeled rows found.", "notice");
			}
			
			//Convert all arrays and objects into objects
			foreach($x as $label => $htm) {
				if(is_array($htm)) {
					$x[$label] = new HTMList($htm);
				}
			}
			
			return true;
			
		}
		
		
		/**
		* Fetch a row either as string or as object's row array
		**/
		private function fromROW($n="") {
			
			if(trim($n)=="") return $this -> rows;
			
			if(is_object($this -> rows[$n])) return $this -> rows[$n] -> get();
			else return $this -> rows[$n];
			
		}
		
		
		/**
		* Prepares an array for use inside the class
		* The array is allowed to contain one or more HTMLists
		**/
		private function isSingleROW(&$x) {
			
			if(is_array($x)) {
				if(count($x)<=1) {
					$k = array_keys($x);
					foreach($k as $v) {
						if(preg_match("/\d+/", $v)) {
							$x = $x[$v]; // >:D
							return true;
						} else {
							return false;
						}
					}
				}
			}
			return false;
			
		}
		
		
		/**
		* Returns an integer for use in array label that doesn't exist already
		**/
		private function insertValue(&$a, $val, $label) {
			
			if(!is_string($label) || trim($label)=="") $a[$this -> getInsertSafeInt($a)] = $val;
			else {
				if(isset($a[$label])) $this -> debug("Overwriting existing keys.", "warning");
				$a[$label] = $val;
			}
			
		}
		
		
		/**
		* Returns an integer for use in array label that doesn't exist already
		**/
		private function getInsertSafeInt($arr) {
			
			$maxI = 0;
			foreach($arr as $int => $v) {
				if(!is_string($int)) {
					if($int>$maxI) $maxI=$int;
				}
			}
			$maxI++;
			return $maxI;
			
		}
		
		
		/**
		* Anonymous rows have a digit label as array key
		* This function makes sure they match their positions
		**/
		private function fixIntegerKeys() {
			
			$a = $this -> rows;
			
			//Check if things are already as they should be
			$ok = true;
			$n=0;
			foreach($a as $label => $htm) {
				if(!is_string($label)) {
					if($label!=$n) $ok=false;
				}
				$n++;
			}
			if($ok) return false;
			
			$this -> debug("Fixing integer keys.", "notice");
			
			$b = array();
			$n = 0;
			
			//Fix integer keys
			foreach($a as $label => $htm) {
				
				if(!is_string($label)) {
					$b[$n] = $htm;
				} else {
					$b[$label] = $htm;
				}
				
				$n++;
				
			}
			
			$this -> rows = $b;
			
			return true;
		}
		
		
		
		/**
		* Helps you use this class by writing to a string each time something unintended happens, if enabled.
		* Feel free to change debug method :)
		**/
		private function debug($mes, $kind="notice") {
			//echo "<!-- NOTICE: ".$mes." --><br>\n";
			if($this -> debugmode) {
				$this -> debuginfo[] = "<!-- NOTICE: ".$mes." -->\n";
			}
		}
		
		/**
		* Gives each row a line break, indent etc.
		**/
		private function translate($a, $t, $first) {
			$r = "";
			foreach($a as $row) {
				if(is_object($row)) {
					$r .= $this -> translate($row -> get(), $t."\t", false);
				} else {
					$r .= $t.$row."\n";
				}
			}
			
			return $r;
		}
		
		/**
		* Output the final code
		**/
		public function str($output=false) {
			
			$ret = "".$this -> translate($this -> rows, "", false);
			
			if($output) echo $ret;
			return $ret;
		}
		
	}
	
	
?>
...and would like some constructive feedback if you have any, before I post it somewhere else. More info on how to use it is available here: http://www.recursive.dk

Thank you
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Class for outputting HTML with ease

Post by Christopher »

Please post some examples of using your class.
(#10850)
SpectreNectar
Forum Newbie
Posts: 4
Joined: Wed May 12, 2010 12:04 pm

Re: Class for outputting HTML with ease

Post by SpectreNectar »

Sure,

general usage would be to do something like:

Code: Select all

<?php
	
	require_once("htmlist.php");
	
	//Prepare some HTML content
	$ul = array(
		"<ul>",
		array(
			"<li>One</li>",
			"<li>Two</li>",
			"<li>Three</li>"
		),
		"</ul>"
	);
	
	//Create a new list using the content array
	$a = new HTMList($ul);
	
	//Insert content at the beginning
	$a -> add_before("<span>As easy as:</span><br />");
	
	//Using labeled content
	$a -> add_after(array("footnote"=>array("<i>This goes after everything else.</i>")));
	
	//Add a div around "footnote" and call it wrapper
	$a -> add_around("<div>", "</div>", "footnote", "wrapper");
	
	
	//Prepare some CSS
	$css = array(
		"div {",
		array(
			"background: #ff0080;"
		),
		"}"
	);
	
	//Make a new HTMList with it
	$b = new HTMList();
	$b -> add_css_before($css);
	
	//Add the obj with the CSS to our first obj
	$a -> add_before($b);
	
	//Debug:
	//print_r($a);
	
	//Final output
	$a -> str(true);
	
	/*
		<style>

			div {
				background: #ff0080;
			}

		</style>
		<span>As easy as:</span><br />
		<ul>
			<li>One</li>
			<li>Two</li>
			<li>Three</li>
		</ul>
		<div>
			<i>This goes after everything else.</i>
		</div>
*/
	
	
?>
It has e few more functions but this is basically it.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Class for outputting HTML with ease

Post by Eran »

What are the advantages of this over writing the HTML by hand?
SpectreNectar
Forum Newbie
Posts: 4
Joined: Wed May 12, 2010 12:04 pm

Re: Class for outputting HTML with ease

Post by SpectreNectar »

Well, you can do some stuff with it that you probably wouldn't be able to if you wrote it out by hand.
Like for example having a div for breadcrumbs that you don't have to worry about what is going to contain before you get to the part of the code that decides what page you are on. It just simplifies making content that is dynamic.
In general you can say that this makes is possible to edit any part of the output frmo anywhere. If you want to add a footer you can do that as the first thing. Should you decide to change the title of the website or add an external javascript when you're done with the head section you do that.

To sum it up, this function only makes sense to use if you're making dynamic content in the sense that there's at least a variable or an if statement.
Also, you can do without it but it helps make certain things that I described easier.


I hope that clarifies to some extent :D
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: Class for outputting HTML with ease

Post by AbraCadaver »

After just a quick look, here are a couple of opinions:

1. If I have to write all the HTML anyway then its not very attractive. I would much rather use it like this:

Code: Select all

$ul = array('one', 'two', 'three');
$a = new HTMList($ul);
2. Why "rows"? That sounds like a table. They are <li> "list items", so why not call them items?
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
SpectreNectar
Forum Newbie
Posts: 4
Joined: Wed May 12, 2010 12:04 pm

Re: Class for outputting HTML with ease

Post by SpectreNectar »

I can see how it woul be convenient to omit a lot of html and do like you suggest, but then I'd have to make a way to tell if it's a div/ul/a/img/other, and the whole point isn't to make writing an ordered list easier. That was just a syntax example. The useful thing this class can do is to manage very dynamic content. Here's what I do in my index.php file:

Code: Select all

$o_html = new HTML($html_args);
		
$o_content = new Content($content_args);
$o_layout = new Layout($layout_args);

$o_html -> build_HTML();
$o_layout -> build_layout();
$o_content -> build_content();

$r00t -> str(true);
After that I can do stuff like:

Code: Select all

<?php
	$r00t -> sub("html") -> sub("head") -> sub("css") -> add_css_after("div.mynewclass { background-color: red; }", "css_default", "css_new");
	$r00t -> sub("layout") -> sub("footer") -> set("<div class=\"mynewclass\">Yarrrr</div>");
?>

EDIT
Oh yeah, rows is just rows of chars I guess they should be called lines, heh.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Class for outputting HTML with ease

Post by Benjamin »

This is not a good idea because it would be very difficult for a designer to figure out. The designer would quit. As a programmer, you may find it useful. But as a programmer, you shouldn't be concerned about the design. This is the whole purpose of templates.
phu
Forum Commoner
Posts: 61
Joined: Tue Mar 30, 2010 6:18 pm

Re: Class for outputting HTML with ease

Post by phu »

Benjamin wrote:This is the whole purpose of templates.
I'd agree with this. If you were to remove the actual HTML from the strings you're passing in and define the type of list/element you're creating, it'd be a little more elegant, but you're still removing it from a format that's useful from a design standpoint -- your styling and formatting would be occurring in PHP calls, which is just another version of the logic/presentation mix-up that causes a lot of problems when you embed too much PHP directly into HTML.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Class for outputting HTML with ease

Post by Christopher »

Benjamin wrote:This is not a good idea because it would be very difficult for a designer to figure out. The designer would quit. As a programmer, you may find it useful. But as a programmer, you shouldn't be concerned about the design. This is the whole purpose of templates.
I generally agree. However, though this class may be difficult for designers to figure out, there are some View Helpers that actually make designer's lives easier. I am thinking about generating <select>s that have the options from one data table and/or current value from the database selected. Some of this can be done in Javascript, but having a server side generated fallback it a good idea. Likewise, URL generation can be assisted with a Helper. And even complex Javascript can be packaged in a Helper to make the designer's life easier.
(#10850)
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Class for outputting HTML with ease

Post by Benjamin »

Yes, I've got helpers for generating selects, inputs and radio groups. I integrate these after getting a completed design from the designer though.
Post Reply