1: <?php
2: namespace Tokamak\Dom;
3: use DOMDocument;
4: use DOMNode;
5: use Closure;
6:
7: /**
8: * Class Component
9: * @package Tokamak\Dom
10: * A component is a reusable/composable template for a DOM subtree.
11: * Could be used to define a widget, page partial, etc. It behaves
12: * slightly differently from an Element in that it can have multiple
13: * elements at its top level; i.e., it has no explicit root node.
14: * When a component is appended to an element, its top-level elements
15: * are all added to the parent element.
16: * If a component is appended to another component,
17: * its top-level elements become siblings to the other component's,
18: * effectively merging the two components and appending them to a common parent.
19: * @todo: add selectors to make it easy to access descendant nodes within a component.
20: */
21: abstract class Component extends Node {
22:
23: /**
24: * @var array the data that is passed into the component
25: */
26: protected $data;
27:
28: /**
29: * @var DOMNode;
30: */
31: protected $parentNode;
32:
33: /**
34: * Accepts array of data/state and builds the component's
35: * DOM structure via the implemented render method.
36: * @param DOMDocument $dom The ancestor DOMDocument instance.
37: * @param array $data Array of arbitrarty data/state passed to the component.
38: */
39: public function __construct(DOMDocument $dom, array $data = null){
40: $this->dom = $dom;
41: $this->data = $data;
42:
43: $this->render();
44: }
45:
46: /**
47: * Since a Component can have multiple top-level elements,
48: * the semantics of "appending" are ambiguous. In this implementation,
49: * calling "append" on a component instance will append a node to
50: * the parent element as a sibling after this component.
51: * @param Node $child
52: * @return Node
53: */
54: public function append(Node $child){
55: while($child->hasDomNodes()){
56: if(isset($this->parentNode)){
57: // Component has already rendered, and append is being called
58: // as part of a chained method call. Append node(s) directly
59: // to parent element's DOMNode.
60: $this->parentNode->appendChild($child->getDomNode());
61: } else {
62: // Add the dom nodes to the queue for the parent element to append.
63: $this->addDomNode($child->getDomNode());
64: }
65: if(isset($callback)){
66: $child->renderCallback($callback);
67: }
68: }
69:
70: return $child;
71: }
72:
73: /**
74: * Set a parent DOMNode.
75: * Once this instance has already been appended by its parent,
76: * any subsequent chained method calls to append must be
77: * appended directly to the parent's underlying DOMNode.
78: * @param DOMNode $parent
79: */
80: public function setParentNode(DOMNode $parent){
81: $this->parentNode = $parent;
82: }
83:
84: }