_childNodes = array(); $this->_attributes = array(); $this->_parentNode = $parentNode; if ($parentNode) { $parentNode->appendChild($this); } $this->setID($id ? $id : uniqid('id')); } /** * Compute, if necessary, and return the fully qualified path of * the element within the containment structured defined via * the parentnode * * @return string */ public function getIDPath() { if ($this->_idPath == null) { $this->_computeIDPath(); } return($this->_idPath); } /** * Return simple ID of the element * * @return string */ public function getID() { return($this->getAttribute("ID")); } /** * Searchs the parent chain defined by the _parentNode reference * until the top of the tree is reached. If the element has no * parents then the element is the 'root' * * @return ZFormElement */ public function getRoot() { if ($this->_parentNode === null) { return $this; } return($this->_parentNode->getRoot()); } /** * Return the list (array) of children contained by the element * * @return array */ public function getChildNodes() { return $this->_childNodes; } /** * Sets the ID of the element. The ID is checked for validitiy within * the parent naming space. NOTE: IDs must be unique within the parent * name space. If the name collides a ZFormElementException is thrown * Additionally, the id path is recalcuated when the ID is changed * @see _computeIDPath() * @param string $id * @return void */ public function setID($id) { $this->_isLegalName($id); $this->setAttribute("ID", $id); $this->_computeIDPath(); } /** * Returns the value of the $name attribute associated with the element * * @param string $name * @return string */ public function getAttribute($name) { return $this->_attributes[$name]; } /** * Set the value of the $name attribute within the element * * @param string $name * @return void */ public function setAttribute($name, $value) { $this->_attributes[$name] = $value; } /** * Return the associative array of attribute maintained by the element. * * @return array */ public function getAttributes() { return($this->_attributes); } /** * Return the parent node reference maintained by the element * * @return ZFormElement */ public function getParentNode() { return($this->_parentNode); } /** * Changes the parent node associated with the element to the * new $parent specified in the parameter. * * @param ZFormElement $parent */ protected function setParentNode(ZFormElement $parent) { if ($this->_parentNode && $this->_parentNode != $parent) { $parent->removeChild($this); } $this->_parentNode = $parent; $this->_computeIDPath(); } /** * Does the element contain children? * * @return boolean */ public function hasChildNodes() { return $this->_childNodes && count($this->_childNodes) > 0; } /** * Adds the $child to the list of children associated with the element * The parent of the $child is set to the element * * @param ZFormElement $child * @return ZFormElement The newly added $child (the one passed in). */ public function appendChild(ZFormElement $child) { // Set the parent of the new $child $child->setParentNode($this); // Remove child just in case $this->removeChild($child); $this->_childNodes[] = $child; return($child); } /** * Remove the given $child from the list (array) of children * associated with the element * * @param ZFormElement $child * @return boolean true if removed, false otherwise */ public function removeChild(ZFormElement $child) { // @todo can be made more efficient(?) $index = array_search($child, $this->_childNodes); if ($index) { unset($this->_childNodes[$index]); $child->setParentNode(null); return(true); } return(false); } /** * Abstract method used to retrive the value associated with the * element. Each element maintains an abstract value which can be * changed which triggers a value change event. This method is * abstract so subclasses can define the contents of that value * * @return mixed */ abstract public function getValue(); /** * Implementation of the setValue which ONLY generates a * ONVALUECHANGE event if the new $value is different from the current * value. This means the subclasses MUST call this implementation and * store the value. * * @param mixed $value * */ public function setValue($value) { $current = $this->getValue(); if ($value != $current) { $this->fireEvent(new ZFormElementEvent(ZFormElementEvent::ONVALUECHANGE, $this, array('old' => $current, 'new' => $value))); } } /** * This method (process) does much of the work associated with the element. * In general, ZFormElement enables the processing of input data(request and * persistent) to be loaded/validated and committed to the application * model. ZFormElements can be wired-up to each other so that events can * be triggered in one element and consumed in another. Processing of an * element and its children does not render the element tree, it simply * gives the controls a chance to interact with each other and the * controller of the application. Rendering is defined by subclasses * and/or templating engines. * Processing consists of: * 1) If the element is persistent restore the saved state @see * restoreState() * 2) Allow events to be process after restoring the state * 3) Ask the elements to load state from the request parameter. * 4) Allow events to be process after loading data from request * 5) Validate the data loaded * 6) Allow events to be process after loading data from request * 7) Invoke the application to allow the elements to communicate with the * model * 8) One last chance to process events * NOTE: Events are queued during steps 1, 3, 5, 7 until the whole child * tree has been visited. * */ public function process() { do { $this->_process = true; if (!$this->restoreState() || !$this->_process) { break; } if (!$this->_processEvents() || !$this->_process) { break; } if (!$this->loadRequestData() || !$this->_process) { break; } if (!$this->_processEvents() || !$this->_process) { break; } if (!$this->validate() || !$this->_process) { break; } if (!$this->_processEvents() || !$this->_process) { break; } if (!$this->invokeApplication() || !$this->_process) { break; } $this->_processEvents(); break; } while (true); } /** * Sets the processing flag, at any point during the process loop an * element can terminate futher process by setting this flag to false * * @param boolean * @return void */ public function setProcess($process) { $this->_process = ($process ? true : false); } /** * Returns the status of the processing flag * * @return boolean true of the element is currently in a processing * loop, false otherwise */ public function getIsProcessing() { return $this->_process; } /** * Returns the error message originated by the $child * * @param ZFormElement $child * @return string The error message if the child generated * an error message or '' */ public function getErrorMessage($child) { if ($this->_errors && ($validator = $this->_errors[$child->getIDPath()])) { return $validator->getErrorMessage(); } return ''; } /** * Returns the array of validation errors associated with the element. * The array is an assoc array whose key is the ID path of the child * element and the value which is an array of validators that reported * the error * @return array */ public function getValidationErrors() { return $this->_errors; } /** * Validates the element and all its children by applying the validators * associated with the element and then recursively invoking validate on the * children. * * @return array an assoc array whose key is the ID path of the child * element and the value which is an array of validators the reported * the error. */ public function validate() { $this->_errors = array(); $this->_performValidation($this->_errors); return($this->_errors); } /** * Iterates over the validators associated with the elements and inokes * the validator on the element. Errors are collected in the $errors array * * @param array $errors * @return void */ protected function _performValidation(&$errors = null) { if ($this->_validators) { foreach ($this->_validators as $validator) { if ($validator instanceof ZFormElementValidator) { if (! $validator->performValidation($this)) { $idPath = $this->getIDPath(); $curVal = $errors[$idPath]; if ($curVal) { if (is_array($curVal)) { $curVal[] = $validator; } else { $curVal = array($curVal); } $errors[$idPath] = $curVal; } else { $errors[$idPath] = $validator; } } } } } if ($this->hasChildNodes()) { foreach ($this->_childNodes as $child) { $child->_performValidation($errors); } } } /** * Abstract implementation of invoking the application component associated * with the element. This method simply recurs over the children of the * element calling the invokeApplication of each child. Subclasses should * call this method (e.g. parent::invokeApplication) to invoke application * logic that is associated with its children * */ public function invokeApplication() { if ($this->hasChildNodes()) { foreach ($this->_childNodes as $child) { $child->invokeApplication(); } } } /** * Abstract implementation that interates of the children of the element * invoking loadRequestData. During this phase of the processing cycle * elements should retrieve input data from the ZRequest object * * @return boolean true if all children were processed, false otherwise */ public function loadRequestData() { if ($this->hasChildNodes()) { foreach ($this->_childNodes as $child) { if (! $child->loadRequestData()) { return(false); } } } return true; } /** * An element can persist itself between requests (@see persist()), if so * it is during this stage of the processing cycle that elements restore * their state. Persistent data is retrieved from the session object * under the elements idPath. * * @return boolean */ public function restoreState() { $mementos = ZRequest::session($this->getIDPath()); if ($mementos) { $this->_restoreMementos($this, $mementos); } return true; } /** * Persists the elements to the session for subsequent restoration and * presentation to the user (this is useful with multi-page forms). * Persisting an element consists of asking itself and each of its * children for a memento which is place into the session object. * Upon restoreState the memento is passed to the object for * reconstitution. */ public function persist() { $mementos = array(); $this->_gatherMementos($this, $mementos); if (count($mementos) > 0) { ZRequest::setSession($this->getIDPath(), $mementos); } return($mementos); } /** * Fire the given event into the element tree. Elements are not * delivered right away, they are queued for delivery at the * appropriate time of the process cycle (@see process()) */ public function fireEvent(ZFormElementEvent $event) { if ($this->_eatEvents) { return; } if (! $this->_eventListeners) { $this->_eventListeners = array(); } $listeners = $this->_eventListeners[$event->getType()]; if ($listeners) { $this->_events[] = $event; } } /** * Remove the event listener associated with the given event type * from the list of listeners. If $listener is not specified * all events of the given type will be removed from the listeners * * @param string $type (@see ZFormElementEvent for event types) * @param ZFormElementEventListenerInterface $listener */ public function removeEventListener($type, $listener = '') { if (! $this->_eventListeners) { return; } if ($listener === '') { unset($this->_eventListeners[$type]); } else if (isset($this->_eventListeners[$type])) { $index = array_search($this->_eventListeners[$type]); if ($index) { unset($this->_eventListeners[$type][$index]); } } } /** * Adds an event listener of the element for the given $type * * @param string $type (@see ZFormElementEvent for event types) * @param mixed $listener */ public function addEventListener($type, $listener) { if (! $this->_eventListeners) { $this->_eventListeners = array(); } $this->removeEventListener($type, $listener); if (! $this->_eventListeners[$type]) { $this->_eventListeners[$type] = array(); } $this->_eventListeners[$type][] = $listener; } /** * Turns of processing events, that is events delivered to the * fireEvent method (@see fireEvent()) are not process, they are * ignored * * @param boolean $allow true - allow events, otherwise ignore them */ public function setAllowEvents($allow) { $this->_eatEvents = ! $allow; } /** * Simple getter for the event listeners associated with the web element * * @return array All the events and listeners associated with the web * elements * Format: array(n) { ["submit"] => array(m) { [0] => eventlistener, * [m] = eventlistener}, * ["click"] => array(m) ....} * */ public function getEventListeners($type = null) { return ($this->_eventListeners && $type) ? $this->_eventListeners[$type] : $this->_eventListeners; } /** * Add the given validator to the list of validators associated with the * element. If the validator is already contained in the list no action is * taken. Elements support multiple validators on a FIFO basis. When a * validator fails the remaining validators (if any) are not called. * * @param ZFormElementValidator $validator */ public function addValidator(ZFormElementValidator $validator) { if (! $this->_validators) { $this->_validators = array(); } $this->_appendToList($validator, $this->_validators); } /** * Removes validator from the list of validators associated with the * element * * @param ZFormElementValidator $validator The validator instance to remove * @return boolean true if removed false otherwise */ public function removedValidator(ZFormElementValidator $validator) { return $this->_removeFromList($validator, $this->_validators); } /** * Returns the list of validators associated with the element * * @return array */ public function getValidators() { return $this->_validators; } /** * Add the given behavior to the list of behaviors associated with the * element. If the behavior is already contained in the list no action is * taken. Elements support multiple behaviors on a FIFO basis. When a * behaviors fails the remaining behaviors (if any) are not called. * * @param ZFormElementBehavior $behavior */ public function addBehavior(ZFormElementBehaviorAbstract $behavior) { if (! $this->_behaviors) { $this->_behaviors = array(); } $this->_appendToList($behavior, $this->_behaviors); } /** * Removes behavior from the list of behaviors associated with the * element * * @param ZFormElementBehavior $behavior The validator instance to remove * @return boolean true if removed false otherwise */ public function removedBehavior($behavior) { return $this->_removeFromList($behavior, $this->_behaviors); } /** * Returns the list (array) of behaviors associated with the element * * @return array */ public function getBehaviors() { return $this->_behaviors; } /** * Returns the childs whose ID is $id. If the $recursive = true, continue * search depth first for the child * * @param string $id * @param boolean true - recursively search child, false - child search * the children * @return ZFormElement If found, null otherwise */ public function getElementById($id, $recursive = false) { if ($this->hasChildNodes()) { foreach ($this->_childNodes as $child) { if ($child->getID() == $id) { return($child); } if ($recursive) { $result = $child->getElementById($id, true); if ($result) { return($result); } } } } return(null); } /** * Default implementation of retriving the memento associated with the element * that will be used during persistent (@see persist()) * The default implementation does not persist anything, we implement it here * so subclasses are not required to * * @return mixed null for the default implementation, subclasses should * override. */ abstract public function getMemento(); /** * The bookend implementation to @see getMemento(). This function is a void * implementation of the protocol to simplify the task of subclassing * */ abstract public function setMemento($memento); /** * Default implementation of the ZFormElementEventListenerInterface * interface which simply does nothing. Intended to make subclassing * more easy. */ public function handleEvent(ZFormElementEvent $event) { // Void implementation eat the event } /** * Helper array management routines to add/remove and item if it doesn't * already exist to an array * * @param mixed $item * @param array &$list * @return boolean true if added, false otherwise */ protected function _appendToList($item, &$list) { if (! array_search($item, $list)) { $list[] = $item; return true; } return false; } /** * Helper array management that removes a given element for a list * * @param mixed $element * @param array &$list * @return boolean true if removed false otherwise */ protected function _removeFromList($element, &$list) { // @todo can be optimized? $index = array_search($element, $list); if ($index) { unset($list[$index]); return true; } return false; } /** * Generates the unique ID path for the element which * consists of its ancestor IDs concatenated with the element's * ID. * * @return string */ protected function _computeIDPath() { $result = $this->getID(); if ($result) { $temp = $this->_parentNode; while ($temp) { $result = $temp->getID().self::PATH_SEPARATOR.$result; $temp = $temp->_parentNode; } $this->_idPath = $result; } return $result; } /** * Determines if a given $id is unique within the element children * names. * * @param string $id * @throws ZFormElementException * @return boolean true or throws ZFormElementException */ protected function _isLegalName($id) { if ($this->_parentNode) { $children = $this->_parentNode->_childNodes; if ($children != null) { foreach ($children as $child) { if ($child->getID() == $id) { throw new ZFormElementException ("Illegal ID: $id, IDs must be uniqued within parent!"); } } } } return true; } /** * Process the list (array) of queued events ready for delivery * (@see process(), @see fireEvent()). * * @return boolean true of all child events were processed */ protected function _processEvents() { if ($this->_events) { foreach ($this->_events as $event) { if (! $event->fire()) { return false; } } $this->_events = array(); } if ($this->hasChildNodes()) { foreach ($this->_childNodes as $child) { if (! $child->_processEvents()) { return false; } } } return true; } /** * Support methods for gathering and redistributing mementos to th * children of the element (@see persist(), @see restoreState()) * * @param ZFormElement $visiting * @param array $mementos */ protected function _gatherMementos($visiting, &$mementos) { $memento = $visiting->getMemento(); if ($memento) { $mementos[$visiting->getIDPath()] = $memento; } if ($visiting->hasChildNodes()) { foreach ($visiting->_childNodes as $child) { $this->_gatherMementos($child, $mementos); } } } /** * Support methods for gathering and redistributing mementos to th * children of the element (@see persist(), @see restoreState()) * * @param ZFormElement $visiting * @param array $mementos */ protected function _restoreMementos($visiting, &$mementos) { $memento = $mementos[$visiting->getIDPath()]; $visiting->setAllowEvents(false); if ($memento) { $visiting->setMemento($memento); } if ($visiting->hasChildNodes()) { foreach ($visiting->_childNodes as $child) { $this->_restoreMementos($child, $mementos); } } $visiting->setAllowEvents(true); } } ?>