Overview

Namespaces

  • Tokamak
    • Dom

Classes

  • Tokamak\Dom\Component
  • Tokamak\Dom\Document
  • Tokamak\Dom\Element
  • Tokamak\Dom\HTMLDocument
  • Tokamak\Dom\Node
  • Overview
  • Namespace
  • Class
  1: <?php
  2: namespace Tokamak\Dom;
  3: 
  4: use DOMDocument;
  5: use DOMNode;
  6: use SplQueue;
  7: use Closure;
  8: 
  9: /**
 10:  * Class Node
 11:  * @package Tokamak\Dom
 12:  * An abstraction over a DOMNode, providing a simple API for
 13:  * building document and component templates by implementing
 14:  * the render method.
 15:  * @throws \RuntimeException
 16:  */
 17: abstract class Node
 18: {
 19:     /**
 20:      * @var string Tokamak will look for components in this namespace if they are not
 21:      * defined elsewhere.
 22:      */
 23:     protected static $COMPONENT_NAMESPACE = '\Tokamak\Dom\Components\\';
 24: 
 25:     /**
 26:      * @var DOMDocument The underlying DOMDocument instance.
 27:      * Normally created by Tokamak\Dom\Document instance.
 28:      */
 29:     protected $dom;
 30: 
 31:     /**
 32:      * @var SplQueue<DOMNode> Queue of dom nodes to be appended to the parent node.
 33:      */
 34:     protected $domNodes;
 35: 
 36:     /**
 37:      * Append one Node instance as a child of another.
 38:      * Must be implemented differently for Element and Component,
 39:      * because the former is a single element that can have children,
 40:      * whereas the latter can represent a list of multiple
 41:      * top-level elements and components. Meanwhile, Document
 42:      * appends children to the top-level DOMDocument instance.
 43:      * @param Node $node An Element or Component to be appended.
 44:      * @return Node Returns the child node for method chaining.
 45:      */
 46:     abstract public function append(Node $node);
 47: 
 48:     /**
 49:      * Syntactic sugar for constructing and then appending a new Element.
 50:      * Abstracts away the need to pass the ancestor DOMDocument to the child.
 51:      * @param string $name The element name ('div', 'a', 'body', etc.)
 52:      * @param array $attributes Associative array of the element's attributes and their values.
 53:      *                          For "class", the value can also be an array of class names.
 54:      * @param string $content The text content of the element.
 55:      * @param Closure $callback A callback closure to be executed within the context of the child node.
 56:      *                          Allows a callback-chaining style for building the DOM tree.
 57:      * @return Element Returns the child Element for method chaining.
 58:      */
 59:     public function appendElement($name, array $attributes = null, $content = '',  Closure $callback = null){
 60:         $child = $this->append(new Element($this->dom, $name, $attributes, $content));
 61:         if(isset($callback)){
 62:             $boundCallback = $callback->bindTo($child, $child);
 63:             $boundCallback();
 64:         }
 65:         return $child;
 66:     }
 67: 
 68:     /**
 69:      * Syntactic sugar for constructing and then appending a new Component.
 70:      * Abstracts away the need to pass the ancestor DOMDocument to the child.
 71:      * If a user-defined Component class of the requested name is not found,
 72:      * will look in the Tokamak\Dom\Components namespace for a built-in component
 73:      * class.
 74:      * @param string $name  The fully-qualified class name of a user-defined Component,
 75:      *                      or the name of a Component class in the Tokamak\Dom\Components namespace.
 76:      * @param array $data   Array of arbitrary data/state to be passed to the component
 77:      * @return Component    Returns the child Element for method chaining.
 78:      * @param Closure $callback A callback closure to be executed within the context of the child node.
 79:      *                          Allows a callback-chaining style for building the DOM tree.
 80:      * @throws \RuntimeException Thrown if $name does not refer to a defined Component class.
 81:      */
 82:     public function appendComponent($name, array $data = null, Closure $callback = null)
 83:     {
 84:         if (is_a($name, 'Tokamak\Dom\Component', true)){
 85:             // The specified $name is a subclass of Component
 86:             $component = new $name($this->dom, $data);
 87:         } else if (is_a(self::$COMPONENT_NAMESPACE . $name, 'Tokamak\Dom\Component', true)){
 88:             // The requested component name is a built-in Component
 89:             $qualifiedName = self::$COMPONENT_NAMESPACE . $name;
 90:             $component =  new $qualifiedName($this->dom, $data);
 91:         } else {
 92:             throw new \RuntimeException("Component $name not defined.");
 93:         }
 94:         $child =  $this->append($component);
 95: 
 96:         if(isset($callback)){
 97:             $boundCallback = $callback->bindTo($child, $child);
 98:             $boundCallback();
 99:         }
100: 
101:         return $child;
102:     }
103: 
104:     /**
105:      * Inject an DOMDocument element as the ancestor of this node.
106:      * @param DOMDocument $dom
107:      */
108:     public function setDom(DOMDocument $dom)
109:     {
110:         $this->dom = $dom;
111:     }
112: 
113:     /**
114:      * Get the underlying DOMDocument ancestor instance.
115:      * @return DOMDocument
116:      */
117:     public function getDom()
118:     {
119:         return $this->dom;
120:     }
121: 
122:     /**
123:      * Add a DOMNode to the queue of nodes that will
124:      * be appended to this node's parent.
125:      * @param DOMNode $node
126:      */
127:     protected function addDomNode(DOMNode $node)
128:     {
129:         if(!isset($this->domNodes)){
130:             $this->domNodes = new SplQueue();
131:         }
132:         $this->domNodes->enqueue($node);
133:     }
134: 
135:     /**
136:      * Remove a DOMNode from the queue of nodes
137:      * to be appended to the parent and return it.
138:      * @return DOMNode
139:      */
140:     public function getDomNode()
141:     {
142:         if($this->hasDomNodes()){
143:             $node = $this->domNodes->dequeue();
144:             return $node;
145:         } else {
146:             return null;
147:         }
148:     }
149: 
150:     /**
151:      * Does this instance have remaining DOMNode instances
152:      * to be appended to the parent?
153:      * @return bool
154:      */
155:     public function hasDomNodes()
156:     {
157:         if(!isset($this->domNodes)){
158:             return false;
159:         }
160:         return !$this->domNodes->isEmpty();
161:     }
162: 
163:     /**
164:      * Called by constructor.
165:      * Must be implemented to define the DOM structure
166:      * of an element or component. Works by adding
167:      * nodes to the domNodes queue, either explicitly
168:      * or via calls to Node::append.
169:      * @return void
170:      */
171:     abstract protected function render();
172: }
API documentation generated by ApiGen