Page 1 of 1

Class for outputting HTML with ease

Posted: Wed May 12, 2010 12:10 pm
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

Re: Class for outputting HTML with ease

Posted: Wed May 12, 2010 4:40 pm
by Christopher
Please post some examples of using your class.

Re: Class for outputting HTML with ease

Posted: Thu May 13, 2010 10:35 am
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.

Re: Class for outputting HTML with ease

Posted: Thu May 13, 2010 11:16 am
by Eran
What are the advantages of this over writing the HTML by hand?

Re: Class for outputting HTML with ease

Posted: Thu May 13, 2010 3:25 pm
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

Re: Class for outputting HTML with ease

Posted: Thu May 13, 2010 5:12 pm
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?

Re: Class for outputting HTML with ease

Posted: Fri May 14, 2010 10:17 am
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.

Re: Class for outputting HTML with ease

Posted: Sat May 15, 2010 5:55 pm
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.

Re: Class for outputting HTML with ease

Posted: Tue May 18, 2010 1:38 pm
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.

Re: Class for outputting HTML with ease

Posted: Tue May 18, 2010 2:12 pm
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.

Re: Class for outputting HTML with ease

Posted: Tue May 18, 2010 2:29 pm
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.