summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjavalizard@gmail.com <>2012-10-31 02:35:01 +0000
committerjavalizard@gmail.com <>2012-10-31 02:35:01 +0000
commit9ed8e2fff531f9188dc55a99e3006f94edc59a5e (patch)
tree8218e8929c49a550c4031705b748760e469df735
parentcd88852b2f67529a9071622988d7c4a76429a670 (diff)
MAJOR UPGRADE to TComponent
This adds global 'fx' events, behaviors, class-wide behaviors, intra-object/behavior 'dy' events, and tweaks the raiseEvent to be more advanced (yet backward compatible). A large amount of documentation is in here too. Slight changes to the TApplication object is made to provide events with priorities. TComponent has a massive amount of test cases as well… There are now 512 assertions. Test cases have been added for testing the GetJS and SetJS and ensure functionality of these new getters and setters. The Behaviors are all tested and fully integrated into the TComponent Object. Behaviors are more transparently and widely integrated into TComponent. Class Behaviors rely on the global events to add behaviors to instanced objects. Class behaviors also allow for behaviors to be added during the instancing of new objects of the class. Dynamic events enable the object to send events directly to behaviors as intra-object events. Dynamic events can be chained as multiple behaviors may implement the event. This allows for parameter filtering. RaiseEvent allows for raising global events, capturing results, and feeding results forward into the next handlers parameters should the need arise. This is a forward thinking feature to enable new processes to be designed into the code for PRADO version 3.3 and future version 4.0. This functionality is transparent when unused. We are hoping this is fully backward compatible with the existing code base. It was designed for maximum backward compatibility while expanding the functional base exponentially. This clears the way for a new way of thinking about how objects relate.
-rw-r--r--.gitattributes3
-rw-r--r--framework/Exceptions/messages/messages.txt4
-rw-r--r--framework/TApplication.php4
-rw-r--r--framework/TComponent.php1538
-rw-r--r--framework/Util/TBehavior.php87
-rw-r--r--framework/Util/TCallChain.php147
-rw-r--r--framework/Util/TClassBehavior.php36
-rw-r--r--tests/unit/TComponentTest.php1940
8 files changed, 3654 insertions, 105 deletions
diff --git a/.gitattributes b/.gitattributes
index 6541f9fb..3102dd50 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2706,6 +2706,9 @@ framework/I18N/core/data/zh_TW.dat -text
framework/I18N/schema/mysql.sql -text
framework/I18N/schema/postgresql.sql -text
framework/I18N/schema/sqlite.sql -text
+framework/Util/TBehavior.php -text
+framework/Util/TCallChain.php -text
+framework/Util/TClassBehavior.php -text
framework/Web/Javascripts/source/.htaccess -text
framework/Web/Javascripts/source/prado/activefileupload/ActiveFileUploadBlank.html -text
framework/Web/Javascripts/source/prado/activefileupload/ActiveFileUploadComplete.png -text svneol=unset#image/png
diff --git a/framework/Exceptions/messages/messages.txt b/framework/Exceptions/messages/messages.txt
index 69fd89fc..468ea4e1 100644
--- a/framework/Exceptions/messages/messages.txt
+++ b/framework/Exceptions/messages/messages.txt
@@ -11,6 +11,10 @@ component_event_undefined = Component event '{0}.{1}' is not defined.
component_eventhandler_invalid = Component event '{0}.{1}' is attached with an invalid event handler '{2}'.
component_expression_invalid = Component '{0}' is evaluating an invalid expression '{1}' : {2}.
component_statements_invalid = Component '{0}' is evaluating invalid PHP statements '{1}' : {2}.
+component_class_behavior_defined = Component '{0}' already has a class behavior of '{1}'.
+component_not_a_behavior = Component '{0}' is being added as behavior is not a TBaseBehavior.
+component_no_tcomponent_class_behaviors = TComponent cannot have class behaviors attached due to recursion.
+component_no_class_provided_nor_late_binding = Adding or Removing Class Behaviors must have PHP feature Late Static Binding or a class provided as a parameter
propertyvalue_enumvalue_invalid = Value '{0}' is a not valid enumeration value ({1}).
diff --git a/framework/TApplication.php b/framework/TApplication.php
index 1da70ba5..be43e697 100644
--- a/framework/TApplication.php
+++ b/framework/TApplication.php
@@ -24,8 +24,8 @@ Prado::using('System.TService');
Prado::using('System.Exceptions.TErrorHandler');
Prado::using('System.Caching.TCache');
Prado::using('System.IO.TTextWriter');
-Prado::using('System.Collections.TList');
-Prado::using('System.Collections.TMap');
+Prado::using('System.Collections.TPriorityList');
+Prado::using('System.Collections.TPriorityMap');
Prado::using('System.Collections.TStack');
Prado::using('System.Xml.TXmlDocument');
Prado::using('System.Security.TAuthorizationRule');
diff --git a/framework/TComponent.php b/framework/TComponent.php
index 941f2c50..930afe23 100644
--- a/framework/TComponent.php
+++ b/framework/TComponent.php
@@ -1,10 +1,13 @@
<?php
/**
* TComponent, TPropertyValue classes
- *
* @author Qiang Xue <qiang.xue@gmail.com>
+ *
+ * Global Events, intra-object events, Class behaviors, expanded behaviors
+ * @author Brad Anderson <javalizard@mac.com>
+ *
* @link http://www.pradosoft.com/
- * @copyright Copyright &copy; 2005-2012 PradoSoft
+ * @copyright Copyright &copy; 2005-2011 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Id$
* @package System
@@ -14,7 +17,8 @@
* TComponent class
*
* TComponent is the base class for all PRADO components.
- * TComponent implements the protocol of defining, using properties and events.
+ * TComponent implements the protocol of defining, using properties, behaviors,
+ * and events.
*
* A property is defined by a getter method, and/or a setter method.
* Properties can be accessed in the way like accessing normal object members.
@@ -35,6 +39,8 @@
* in the format of concatenated words, with the first letter of each word
* capitalized (e.g. DisplayMode, ItemStyle).
*
+ * Javascript Get and Set
+ *
* Since Prado 3.2 a new class of javascript-friendly properties have been introduced
* to better deal with potential security problems like cross-site scripting issues.
* All the data that gets sent clientside inside a javascript block is now encoded by default.
@@ -60,6 +66,8 @@
* statement and will not be encoded when rendered inside a javascript block.
* This special handling makes use of the {@link TJavaScriptLiteral} class.
*
+ * Events
+ *
* An event is defined by the presence of a method whose name starts with 'on'.
* The event name is the method name and is thus case-insensitive.
* An event can be attached with one or several methods (called event handlers).
@@ -74,6 +82,7 @@
* To raise an event (assuming named as 'Click') of a component, use
* <code>
* $component->raiseEvent('OnClick');
+ * $component->raiseEvent('OnClick', $this, $param);
* </code>
* To attach an event handler to an event, use one of the following ways,
* <code>
@@ -81,7 +90,7 @@
* $component->attachEventHandler('OnClick',$callback);
* </code>
* The first two ways make use of the fact that $component->OnClick refers to
- * the event handler list {@link TList} for the 'OnClick' event.
+ * the event handler list {@link TPriorityList} for the 'OnClick' event.
* The variable $callback contains the definition of the event handler that can
* be either a string referring to a global function name, or an array whose
* first element refers to an object and second element a method name/path that
@@ -90,11 +99,187 @@
* - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);
* - array($object,'MainContent.SubmitButton.buttonClicked') :
* $object->MainContent->SubmitButton->buttonClicked($sender,$param);
- *
+ *
* @author Qiang Xue <qiang.xue@gmail.com>
+ *
+ * With the addition of behaviors, a more expansive event model is needed. There
+ * are two new event types (global and dynamic events) as well as a more comprehensive
+ * behavior model that includes class wide behaviors.
+ *
+ * A global event is defined by all events whose name starts with 'fx'.
+ * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
+ * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
+ * any global event by defining that event as a method. Global events have priorities
+ * just like 'on' events; so as to be able to order the event execution. Due to the
+ * nature of all events which start with 'fx' being valid, in effect, every object
+ * has every 'fx' global event. It is simply an issue of tapping into the desired
+ * global event.
+ *
+ * A global event that starts with 'fx' can be called even if the object does not
+ * implement the method of the global event. A call to a non-existing 'fx' method
+ * will, at minimal, function and return null. If a method argument list has a first
+ * parameter, it will be returned instead of null. This allows filtering and chaining.
+ * 'fx' methods do not automatically install and uninstall. To install and uninstall an
+ * object's global event listeners, call the object's {@link listen} and
+ * {@link unlisten} methods, respectively. An object may auto-install its global event
+ * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
+ *
+ * As of PHP version 5.3, nulled objects without code references will still continue to persist
+ * in the global event queue because {@link __destruct} is not automatically called. In the common
+ * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
+ * {@link unlisten} is required to be manually called before an object is
+ * left without references if it is currently listening to any global events. This includes
+ * class wide behaviors.
+ *
+ * An object that contains a method that starts with 'fx' will have those functions
+ * automatically receive those events of the same name after {@link listen} is called on the object.
+ *
+ * An object may listen to a global event without defining an 'fx' method of the same name by
+ * adding an object method to the global event list. For example
+ * <code>
+ * $component->fxGlobalCheck=$callback; // or $component->OnClick->add($callback);
+ * $component->attachEventHandler('fxGlobalCheck',array($object, 'someMethod'));
+ * </code>
+ *
+ * Events between Objects and their behaviors, Dynamic Events
+ *
+ * An intra-object/behavior event is defined by methods that start with 'dy'. Just as with
+ * 'fx' global events, every object has every dynamic event. Any call to a method that
+ * starts with 'dy' will be handled, regardless of whether it is implemented. These
+ * events are for communicating with attached behaviors.
+ *
+ * Dynamic events can be used in a variety of ways. They can be used to tell behaviors
+ * when a non-behavior method is called. Dynamic events could be used as data filters.
+ * They could also be used to specify when a piece of code is to be run, eg. should the
+ * loop process be performed on a particular piece of data. In this way, some control
+ * is handed to the behaviors over the process and/or data.
+ *
+ * If there are no handlers for an 'fx' or 'dy' event, it will return the first
+ * parameter of the argument list. If none there are no arguments, these events
+ * will return null. If there are handlers an 'fx' method will be called directly
+ * within the object. Global 'fx' events are triggered by calling {@link raiseEvent}.
+ * For dynamic events where there are behaviors that respond to the dynamic events, a
+ * {@link TCallChain} is developed. A call chain allows the behavior dynamic event
+ * implementations to call further implementing behaviors within a chain.
+ *
+ * If an object implements {@link IDynamicMethods}, all global and object dynamic
+ * events will be sent to {@link __dycall}. In the case of global events, all
+ * global events will trigger this method. In the case of behaviors, all undefined
+ * dynamic events which are called will be passed through to this method.
+ *
+ *
+ * Behaviors
+ *
+ * There are two types of behaviors. There are individual object behaviors and
+ * there are class wide behaviors. Class behaviors depend upon object behaviors.
+ *
+ * When a new class implements {@link IBehavior} or {@link IClassBehavior} or
+ * extends {@link TBehavior} or {@link TClassBehavior}, it may be added to an
+ * object by calling the object's {@link attachBehavior}. The behaviors associated
+ * name can then be used to {@link enableBehavior} or {@link disableBehavior}
+ * the specific behavior.
+ *
+ * All behaviors may be turned on and off via {@link enableBehaviors} and
+ * {@link disableBehaviors}, respectively. To check if behaviors are on or off
+ * a call to {@link getBehaviorsEnabled} will provide the variable.
+ *
+ * Attaching and detaching whole sets of behaviors is done using
+ * {@link attachBehaviors} and {@link detachBehaviors}. {@link clearBehaviors}
+ * removes all of an object's behaviors.
+ *
+ * {@link asa} returns a behavior of a specific name. {@link isa} is the
+ * behavior inclusive function that acts as the PHP operator {@link instanceof}.
+ * A behavior could provide the functionality of a specific class thus causing
+ * the host object to act similarly to a completely different class. A behavior
+ * would then implement {@link IInstanceCheck} to provide the identity of the
+ * different class.
+ *
+ * Class behaviors are similar to object behaviors except that the class behavior
+ * is the implementation for all instances of the class. A class behavior
+ * will have the object upon which is being called be prepended to the parameter
+ * list. This way the object is known across the class behavior implementation.
+ *
+ * Class behaviors are attached using {@link attachClassBehavior} and detached
+ * using {@link detachClassBehavior}. Class behaviors are important in that
+ * they will be applied to all new instances of a particular class. In this way
+ * class behaviors become default behaviors to a new instances of a class in
+ * {@link __construct}. Detaching a class behavior will remove the behavior
+ * from the default set of behaviors created for an object when the object
+ * is instanced.
+ *
+ * Class behaviors are also added to all existing instances via the global 'fx'
+ * event mechanism. When a new class behavior is added, the event
+ * {@link fxAttachClassBehavior} is raised and all existing instances that are
+ * listening to this global event (primarily after {@link listen} is called)
+ * will have this new behavior attached. A similar process is used when
+ * detaching class behaviors. Any objects listening to the global 'fx' event
+ * {@link fxDetachClassBehavior} will have a class behavior removed.
+ *
+ * Dynamic Intra-Object Events
+ *
+ * Dynamic events start with 'dy'. This mechanism is used to allow objects
+ * to communicate with their behaviors directly. The entire 'dy' event space
+ * is valid. All attached, enabled behaviors that implement a dynamic event
+ * are called when the host object calls the dynamic event. If there is no
+ * implementation or behaviors, this returns null when no parameters are
+ * supplied and will return the first parameter when there is at least one
+ * parameter in the dynamic event.
+ * <code>
+ * null == $this->dyBehaviorEvent();
+ * 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
+ * </code>
+ *
+ * Dynamic events can be chained together within behaviors to allow for data
+ * filtering. Dynamic events are implemented within behaviors by defining the
+ * event as a method.
+ * <code>
+ * class TObjectBehavior extends TBehavior {
+ * public function dyBehaviorEvent($param1, $callchain) {
+ * //Do something, eg: $param1 += 13;
+ * return $callchain->dyBehaviorEvent($param1);
+ * }
+ * }
+ * </code>
+ * This implementation of a behavior and dynamic event will flow through to the
+ * next behavior implementing the dynamic event. The first parameter is always
+ * return when it is supplied. Otherwise a dynamic event returns null.
+ *
+ * In the case of a class behavior, the object is also prepended to the dynamic
+ * event.
+ * <code>
+ * class TObjectClassBehavior extends TClassBehavior {
+ * public function dyBehaviorEvent($hostobject, $param1, $callchain) {
+ * //Do something, eg: $param1 += $hostobject->getNumber();
+ * return $callchain->dyBehaviorEvent($param1);
+ * }
+ * }
+ * </code>
+ * When calling a dynamic event, only the parameters are passed. The host object
+ * and the call chain are built into the framework.
+ *
+ * Global Event and Dynamic event catching
+ *
+ * Given that all global 'fx' events and dynamic 'dy' events are valid and
+ * operational, there is a mechanism for catching events called that are not
+ * implemented (similar to the built-in PHP method {@link __call}). When
+ * a dynamic or global event is called but a behavior does not implement it,
+ * yet desires to know when an undefined dynamic event is run, the behavior
+ * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
+ *
+ * In the case of dynamic events, {@link __dycall} is supplied with the method
+ * name and its parameters. When a global event is raised, via {@link raiseEvent},
+ * the method is the event name and the parameters are supplied.
+ *
+ * When implemented, this catch-all mechanism is called for event global event event
+ * when implemented outside of a behavior. Within a behavior, it will also be called
+ * when the object to which the behavior is attached calls any unimplemented dynamic
+ * event. This is the fall-back mechanism for informing a class and/or behavior
+ * of when an global and/or undefined dynamic event is executed.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
* @version $Id$
* @package System
- * @since 3.0
+ * @since 3.1.8
*/
class TComponent
{
@@ -102,6 +287,279 @@ class TComponent
* @var array event handler lists
*/
private $_e=array();
+
+ /**
+ * @var boolean if listening is enabled. Automatically turned on or off in
+ * constructor according to {@link getAutoGlobalListen}. Default false, off
+ */
+ private $_listeningenabled=false;
+
+ /**
+ * @var array static registered global event handler lists
+ */
+ private static $_ue=array();
+
+ /**
+ * @var boolean if object behaviors are on or off. default true, on
+ */
+ private $_behaviorsenabled=true;
+
+ /**
+ * @var TPriorityMap list of object behaviors
+ */
+ private $_m=null;
+
+ /**
+ * @var array static global class behaviors, these behaviors are added upon instantiation of a class
+ */
+ private static $_um=array();
+
+
+ /**
+ * @const string the name of the global {@link raiseEvent} listener
+ */
+ const GLOBAL_RAISE_EVENT_LISTENER='fxGlobalListener';
+
+
+ /**
+ * The common __construct
+ * If desired by the new object, this will auto install and listen to global event functions
+ * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
+ * This function installs all class behaviors in a class hierarchy from the deepest subclass
+ * through each parent to the top most class, TComponent.
+ */
+ public function __construct() {
+ if($this->getAutoGlobalListen())
+ $this->listen();
+
+ $classes=array_reverse($this->getClassHierarchy(true));
+ foreach($classes as $class) {
+ if(isset(self::$_um[$class]))
+ $this->attachBehaviors(self::$_um[$class]);
+ }
+ }
+
+
+ /**
+ * Tells TComponent whether or not to automatically listen to global events.
+ * Defaults to false because PHP variable cleanup is affected if this is true.
+ * When unsetting a variable that is listening to global events, {@link unlisten}
+ * must explicitly be called when cleaning variables allocation or else the global
+ * event registry will contain references to the old object. This is true for PHP 5.4
+ *
+ * Override this method by a subclass to change the setting. When set to true, this
+ * will enable {@link __construct} to call {@link listen}.
+ *
+ * @return boolean whether or not to auto listen to global events during {@link __construct}, default false
+ */
+ public function getAutoGlobalListen() {
+ return false;
+ }
+
+
+ /**
+ * The common __destruct
+ * This unlistens from the global event routines if listening
+ *
+ * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
+ * called must be explicitly called.
+ */
+ public function __destruct() {
+ if($this->_listeningenabled)
+ $this->unlisten();
+ }
+
+
+ /**
+ * This utility function is a private array filter method. The array values
+ * that start with 'fx' are filtered in.
+ */
+ private function filter_prado_fx($name) {
+ return strncasecmp($name,'fx',2)===0;
+ }
+
+
+ /**
+ * This returns an array of the class name and the names of all its parents. The base object first,
+ * {@link TComponent}, and the deepest subclass is last.
+ * @param boolean optional should the names be all lowercase true/false
+ * @return array array of strings being the class hierarchy of $this.
+ */
+ public function getClassHierarchy($lowercase = false)
+ {
+ $class=get_class($this);
+ $classes=array($class);
+ while($class=get_parent_class($class)){array_unshift($classes,$class);}
+ if($lowercase)
+ return array_map('strtolower',$classes);
+ return $classes;
+ }
+
+
+ /**
+ * This adds an object's fx event handlers into the global broadcaster to listen into any
+ * broadcast global events called through {@link raiseEvent}
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyListen($globalEvents[, $chain]) {
+ * $this->listen(); //eg
+ * }
+ * </code>
+ * to be executed when listen is called. All attached behaviors are notified through dyListen.
+ *
+ * @return numeric the number of global events that were registered to the global event registry
+ */
+ public function listen() {
+ if($this->_listeningenabled)
+ return;
+
+ $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
+
+ foreach($fx as $func)
+ $this->attachEventHandler($func,array($this,$func));
+
+ if(is_a($this,'IDynamicMethods')) {
+ $this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
+ array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ }
+
+ $this->_listeningenabled=true;
+
+ $this->dyListen($fx);
+
+ return count($fx);
+ }
+
+ /**
+ * this removes an object's fx events from the global broadcaster
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyUnlisten($globalEvents[, $chain]) {
+ * $this->behaviorUnlisten(); //eg
+ * }
+ * </code>
+ * to be executed when listen is called. All attached behaviors are notified through dyUnlisten.
+ *
+ * @return numeric the number of global events that were unregistered from the global event registry
+ */
+ public function unlisten() {
+ if(!$this->_listeningenabled)
+ return;
+
+ $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
+
+ foreach($fx as $func)
+ $this->detachEventHandler($func,array($this,$func));
+
+ if(is_a($this,'IDynamicMethods')) {
+ $this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
+ array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ }
+
+ $this->_listeningenabled=false;
+
+ $this->dyUnlisten($fx);
+
+ return count($fx);
+ }
+
+ /**
+ * Gets the state of listening to global events
+ * @return boolean is Listening to global broadcast enabled
+ */
+ public function getListeningToGlobalEvents()
+ {
+ return $this->_listeningenabled;
+ }
+
+
+ /**
+ * Calls a method.
+ * Do not call this method directly. This is a PHP magic method that we override
+ * to allow behaviors, dynamic events (intra-object/behavior events),
+ * undefined dynamic and global events, and
+ * to allow using the following syntax to call a property setter or getter.
+ * <code>
+ * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
+ * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
+ * </code>
+ *
+ * Additional object behaviors override class behaviors.
+ * dynamic and global events do not fail even if they aren't implemented.
+ * Any intra-object/behavior dynamic events that are not implemented by the behavior
+ * return the first function paramater or null when no parameters are specified.
+ *
+ * @param string method name that doesn't exist and is being called on the object
+ * @param mixed method parameters
+ * @throws TInvalidOperationException If the property is not defined or read-only or
+ * method is undefined
+ * @return mixed result of the method call, or false if 'fx' or 'dy' function but
+ * is not found in the class, otherwise it runs
+ */
+ public function __call($method, $args)
+ {
+ $getset=substr($method,0,3);
+ if(($getset=='get')||($getset=='set'))
+ {
+ $propname=substr($method,3);
+ $jsmethod=$getset.'js'.$propname;
+ if(method_exists($this,$jsmethod))
+ {
+ if(count($args)>0)
+ if($args[0]&&!($args[0] instanceof TJavaScriptString))
+ $args[0]=new TJavaScriptString($args[0]);
+ return call_user_func_array(array($this,$jsmethod),$args);
+ }
+
+ if (($getset=='set')&&method_exists($this,'getjs'.$propname))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$method);
+ }
+
+ if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ if(strncasecmp($method,'dy',2)===0)
+ {
+ $callchain=new TCallChain($method);
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&(method_exists($behavior,$method)||($behavior instanceof IDynamicMethods)))
+ {
+ $behavior_args=$args;
+ if($behavior instanceof IClassBehavior)
+ array_unshift($behavior_args,$this);
+ $callchain->addCall(array($behavior,$method),$behavior_args);
+ }
+
+ }
+ if($callchain->getCount()>0)
+ return call_user_func_array(array($callchain,'call'),$args);
+ }
+ else
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&method_exists($behavior,$method))
+ {
+ if($behavior instanceof IClassBehavior)
+ array_unshift($args,$this);
+ return call_user_func_array(array($behavior,$method),$args);
+ }
+ }
+ }
+ }
+
+ if(strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($this instanceof IDynamicMethods)
+ return $this->__dycall($method,$args);
+ return isset($args[0])?$args[0]:null;
+ }
+
+ throw new TApplicationException('component_method_undefined',get_class($this),$method);
+ }
+
/**
* Returns a property value or an event handler list by property or event name.
@@ -115,21 +573,27 @@ class TComponent
* <code>
* $eventHandlerList=$component->EventName;
* </code>
+ * This will also return the global event handler list when specifing an 'fx'
+ * event,
+ * <code>
+ * $globalEventHandlerList=$component->fxEventName;
+ * </code>
+ * When behaviors are enabled, this will return the behavior of a specific
+ * name, a property of a behavior, or an object 'on' event defined by the behavior.
* @param string the property name or the event name
- * @return mixed the property value or the event handler list
+ * @return mixed the property value or the event handler list as {@link TPriorityList}
* @throws TInvalidOperationException if the property/event is not defined.
*/
public function __get($name)
{
- $getter='get'.$name; $jsgetter = 'getjs'.$name;
- if(method_exists($this,$getter))
+ if(method_exists($this,$getter='get'.$name))
{
// getting a property
return $this->$getter();
}
- else if(method_exists($this,$jsgetter))
+ else if(method_exists($this,$jsgetter='getjs'.$name))
{
- // getting a property
+ // getting a javascript property
return (string)$this->$jsgetter();
}
else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
@@ -137,13 +601,33 @@ class TComponent
// getting an event (handler list)
$name=strtolower($name);
if(!isset($this->_e[$name]))
- $this->_e[$name]=new TList;
+ $this->_e[$name]=new TPriorityList;
return $this->_e[$name];
}
- else
+ else if(strncasecmp($name,'fx',2)===0)
{
- throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ // getting a global event (handler list)
+ $name=strtolower($name);
+ if(!isset(self::$_ue[$name]))
+ self::$_ue[$name]=new TPriorityList;
+ return self::$_ue[$name];
}
+ else if($this->_behaviorsenabled)
+ {
+ // getting a behavior property/event (handler list)
+ if(isset($this->_m[$name]))
+ return $this->_m[$name];
+ else if($this->_m!==null)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
+ (property_exists($behavior,$name)||$behavior->canGetProperty($name)||$behavior->hasEvent($name)))
+ return $behavior->$name;
+ }
+ }
+ }
+ throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
}
/**
@@ -154,30 +638,47 @@ class TComponent
* $this->PropertyName=$value;
* $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
* $this->EventName=$handler;
+ * $this->fxEventName=$handler; //global event listener
* </code>
+ * When behaviors are enabled, this will also set a behaviors properties and events.
* @param string the property name or event name
* @param mixed the property value or event handler
* @throws TInvalidOperationException If the property is not defined or read-only.
*/
public function __set($name,$value)
{
- if(method_exists($this, $setter='set'.$name))
+ if(method_exists($this,$setter='set'.$name))
{
- if (strncasecmp($name,'js',2)===0 && $value && !($value instanceof TJavaScriptLiteral))
+ if(strncasecmp($name,'js',2)===0&&$value&&!($value instanceof TJavaScriptLiteral))
$value = new TJavaScriptLiteral($value);
- $this->$setter($value);
+ return $this->$setter($value);
}
- else if(method_exists($this, $jssetter = 'setjs'.$name))
+ else if(method_exists($this,$jssetter='setjs'.$name))
{
- if ($value and !($value instanceof TJavaScriptString))
- $value = new TJavaScriptString($value);
- $this->$jssetter($value);
+ if($value&&!($value instanceof TJavaScriptString))
+ $value=new TJavaScriptString($value);
+ return $this->$jssetter($value);
}
- else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
+ else if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0)
{
- $this->attachEventHandler($name,$value);
+ return $this->attachEventHandler($name,$value);
}
- else if(method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name))
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ $sets=0;
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
+ (property_exists($behavior,$name)||$behavior->canSetProperty($name)||$behavior->hasEvent($name))) {
+ $behavior->$name=$value;
+ $sets++;
+ }
+ }
+ if($sets)return $value;
+
+ }
+
+ if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
{
throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
}
@@ -188,37 +689,86 @@ class TComponent
}
/**
- * Calls a method.
+ * Checks if a property value is null, there are no events in the object
+ * event list or global event list registered under the name, and, if
+ * behaviors are enabled,
* Do not call this method. This is a PHP magic method that we override
- * to allow using the following syntax to call a property setter or getter.
- * <code>
- * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
- * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
- * </code>
- * @param string the getter or setter method name
- * @param mixed method call parameters
- * @throws TInvalidOperationException If the property is not defined or read-only.
+ * to allow using isset() to detect if a component property is set or not.
+ * This also works for global events. When behaviors are enabled, it
+ * will check for a behavior of the specified name, and also check
+ * the behavior for events and properties.
+ * @param string the property name or the event name
+ * @since 1.0.1
*/
- public function __call($name,$params)
+ public function __isset($name)
{
- $getset = substr($name,0,3);
- if (($getset=='get') || ($getset=='set'))
+ if(method_exists($this,$getter='get'.$name))
+ return $this->$getter()!==null;
+ else if(method_exists($this,$jsgetter='getjs'.$name))
+ return $this->$jsgetter()!==null;
+ else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
{
- $propname = substr($name,3);
- $jsmethod = $getset.'js'.$propname;
- if (method_exists($this, $jsmethod))
+ $name=strtolower($name);
+ return isset($this->_e[$name])&&$this->_e[$name]->getCount();
+ }
+ else if(strncasecmp($name,'fx',2)===0)
+ {
+ $name=strtolower($name);
+ return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount();
+ }
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ if(isset($this->_m[$name]))
+ return true;
+ foreach($this->_m->toArray() as $behavior)
{
- if (count($params)>0)
- if ($params[0] && !($params[0] instanceof TJavaScriptString))
- $params[0] = new TJavaScriptString($params[0]);
- return call_user_func_array(array($this, $jsmethod), $params);
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled()))
+ return isset($behavior->$name);
}
-
- if (($getset=='set') and method_exists($this, 'getjs'.$propname))
- throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+
}
+ else
+ return false;
+ }
- throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ /**
+ * Sets a component property to be null. Clears the object or global
+ * events. When enabled, loops through all behaviors and unsets the
+ * property or event.
+ * Do not call this method. This is a PHP magic method that we override
+ * to allow using unset() to set a component property to be null.
+ * @param string the property name or the event name
+ * @throws TInvalidOperationException if the property is read only.
+ * @since 3.1.8
+ */
+ public function __unset($name)
+ {
+ if(method_exists($this,$setter='set'.$name))
+ $this->$setter(null);
+ else if(method_exists($this,$jssetter='setjs'.$name))
+ $this->$jssetter(null);
+ else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
+ $this->_e[strtolower($name)]->clear();
+ else if(strncasecmp($name,'fx',2)===0)
+ $this->getEventHandlers($name)->remove(array($this, $name));
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ if(isset($this->_m[$name]))
+ $this->detachBehavior($name);
+ else {
+ $unset=0;
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())) {
+ unset($behavior->$name);
+ $unset++;
+ }
+ }
+ if(!$unset&&method_exists($this,'get'.$name))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+ }
+ } else if(method_exists($this,'get'.$name))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
}
/**
@@ -230,34 +780,55 @@ class TComponent
*/
public function hasProperty($name)
{
- return
- method_exists($this,'get'.$name) || method_exists($this,'set'.$name) ||
- method_exists($this,'getjs'.$name) || method_exists($this,'setjs'.$name)
- ;
+ return $this->canGetProperty($name)||$this->canSetProperty($name);
}
/**
* Determines whether a property can be read.
* A property can be read if the class has a getter method
* for the property name. Note, property name is case-insensitive.
+ * This also checks for getjs. When enabled, it loops through all
+ * active behaviors for the get property when undefined by the object.
* @param string the property name
* @return boolean whether the property can be read
*/
public function canGetProperty($name)
{
- return method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name);
+ if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canGetProperty($name))
+ return true;
+ }
+ }
+ return false;
}
/**
* Determines whether a property can be set.
* A property can be written if the class has a setter method
* for the property name. Note, property name is case-insensitive.
+ * This also checks for setjs. When enabled, it loops through all
+ * active behaviors for the set property when undefined by the object.
* @param string the property name
* @return boolean whether the property can be written
*/
public function canSetProperty($name)
{
- return method_exists($this,'set'.$name) || method_exists($this,'setjs'.$name);
+ if(method_exists($this,'set'.$name)||method_exists($this,'setjs'.$name))
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canSetProperty($name))
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -265,6 +836,8 @@ class TComponent
* A property path is a sequence of property names concatenated by '.' character.
* For example, 'Parent.Page' refers to the 'Page' property of the component's
* 'Parent' property value (which should be a component also).
+ * When a property is not defined by an object, this also loops through all
+ * active behaviors of the object.
* @param string property path
* @return mixed the property path value
*/
@@ -281,6 +854,8 @@ class TComponent
* A property path is a sequence of property names concatenated by '.' character.
* For example, 'Parent.Page' refers to the 'Page' property of the component's
* 'Parent' property value (which should be a component also).
+ * When a property is not defined by an object, this also loops through all
+ * active behaviors of the object.
* @param string property path
* @param mixed the property path value
*/
@@ -299,41 +874,90 @@ class TComponent
/**
* Determines whether an event is defined.
- * An event is defined if the class has a method whose name is the event name prefixed with 'on'.
+ * An event is defined if the class has a method whose name is the event name
+ * prefixed with 'on', 'fx', or 'dy'.
+ * Every object responds to every 'fx' and 'dy' event as they are in a universally
+ * accepted event space. 'on' event must be declared by the object.
+ * When enabled, this will loop through all active behaviors for 'on' events
+ * defined by the behavior.
* Note, event name is case-insensitive.
* @param string the event name
* @return boolean
*/
public function hasEvent($name)
{
- return strncasecmp($name,'on',2)===0 && method_exists($this,$name);
+ if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0||strncasecmp($name,'dy',2)===0)
+ return true;
+
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
+ return true;
+ }
+ }
+ return false;
}
/**
+ * Checks if an event has any handlers. This function also checks through all
+ * the behaviors for 'on' events when behaviors are enabled.
+ * 'dy' dynamic events are not handled by this function.
+ * @param string the event name
* @return boolean whether an event has been attached one or several handlers
*/
public function hasEventHandler($name)
{
$name=strtolower($name);
- return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
+ if(strncasecmp($name,'fx',2)===0)
+ return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount()>0;
+ else
+ {
+ if(isset($this->_e[$name])&&$this->_e[$name]->getCount()>0)
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled) {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEventHandler($name))
+ return true;
+ }
+ }
+ }
+ return false;
}
/**
- * Returns the list of attached event handlers for an event.
- * @return TList list of attached event handlers for an event
+ * Returns the list of attached event handlers for an 'on' or 'fx' event. This function also
+ * checks through all the behaviors for 'on' event lists when behaviors are enabled.
+ * @return TPriorityList list of attached event handlers for an event
* @throws TInvalidOperationException if the event is not defined
*/
public function getEventHandlers($name)
{
- if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
+ if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
{
$name=strtolower($name);
if(!isset($this->_e[$name]))
- $this->_e[$name]=new TList;
+ $this->_e[$name]=new TPriorityList;
return $this->_e[$name];
}
- else
- throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+ else if(strncasecmp($name,'fx',2)===0)
+ {
+ $name=strtolower($name);
+ if(!isset(self::$_ue[$name]))
+ self::$_ue[$name]=new TPriorityList;
+ return self::$_ue[$name];
+ }
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
+ return $behavior->getEventHandlers($name);
+ }
+ }
+ throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
}
/**
@@ -348,16 +972,18 @@ class TComponent
*
* The event handler must be of the following signature,
* <code>
- * function handlerName($sender,$param) {}
+ * function handlerName($sender, $param) {}
+ * function handlerName($sender, $param, $name) {}
* </code>
* where $sender represents the object that raises the event,
- * and $param is the event parameter.
+ * and $param is the event parameter. $name refers to the event name
+ * being handled.
*
* This is a convenient method to add an event handler.
* It is equivalent to {@link getEventHandlers}($name)->add($handler).
* For complete management of event handlers, use {@link getEventHandlers}
* to get the event handler list first, and then do various
- * {@link TList} operations to append, insert or remove
+ * {@link TPriorityList} operations to append, insert or remove
* event handlers. You may also do these operations like
* getting and setting properties, e.g.,
* <code>
@@ -370,29 +996,37 @@ class TComponent
* $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
* </code>
*
+ * Due to the nature of {@link getEventHandlers}, any active behaviors defining
+ * new 'on' events, this method will pass through to the behavior transparently.
+ *
* @param string the event name
* @param callback the event handler
+ * @param numeric|null the priority of the handler, defaults to null which translates into the
+ * default priority of 10.0 within {@link TPriorityList}
* @throws TInvalidOperationException if the event does not exist
*/
- public function attachEventHandler($name,$handler)
+ public function attachEventHandler($name,$handler,$priority=null)
{
- $this->getEventHandlers($name)->add($handler);
+ $this->getEventHandlers($name)->add($handler,$priority);
}
/**
* Detaches an existing event handler.
- * This method is the opposite of {@link attachEventHandler}.
+ * This method is the opposite of {@link attachEventHandler}. It will detach
+ * any 'on' events definedb by an objects active behaviors as well.
* @param string event name
* @param callback the event handler to be removed
+ * @param numeric|false|null the priority of the handler, defaults to false which translates
+ * to an item of any priority within {@link TPriorityList}; null means the default priority
* @return boolean if the removal is successful
*/
- public function detachEventHandler($name,$handler)
+ public function detachEventHandler($name,$handler,$priority=false)
{
if($this->hasEventHandler($name))
{
try
{
- $this->getEventHandlers($name)->remove($handler);
+ $this->getEventHandlers($name)->remove($handler,$priority);
return true;
}
catch(Exception $e)
@@ -403,42 +1037,133 @@ class TComponent
}
/**
- * Raises an event.
+ * Raises an event. This raises both inter-object 'on' events and global 'fx' events.
* This method represents the happening of an event and will
- * invoke all attached event handlers for the event.
+ * invoke all attached event handlers for the event in {@link TPriorityList} order.
+ * This method does not handle intra-object/behavior dynamic 'dy' events.
+ *
+ * There are ways to handle event responses. By defailt {@link EVENT_RESULT_FILTER},
+ * all event responses are stored in an array, filtered for null responses, and returned.
+ * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
+ * with the sender and param in an array
+ * <code>
+ * $result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
+ * </code>
+ *
+ * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
+ * fed forward as the parameters for the next event. This allows for events to filter data
+ * directly by affecting the event parameters
+ *
+ * If a callable function is set in the response type or the post function filter is specified then the
+ * result of each called event handler is post processed by the callable function. Used in
+ * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
+ *
+ * When raising a global 'fx' event, registered handlers in the global event list for
+ * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers. In this way,
+ * these global events are always raised for every global 'fx' event. The registered handlers for global
+ * raiseEvent events have priorities. Any registered global raiseEvent event handlers with a priority less than zero
+ * are added before the main event handlers being raised and any registered global raiseEvent event handlers
+ * with a priority equal or greater than zero are added after the main event handlers being raised. In this way
+ * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
+ *
+ * Behaviors may implement the following functions:
+ * <code>
+ * public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
+ * return $name; //eg, the event name may be filtered/changed
+ * }
+ * public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
+ * return true; //should this particular handler be executed? true/false
+ * }
+ * public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
+ * //contains the per handler response
+ * }
+ * public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
+ * return $responses;
+ * }
+ * </code>
+ * to be executed when raiseEvent is called. The 'intra' dynamic events are called per handler in
+ * the handler loop.
+ *
+ * dyPreRaiseEvent has the effect of being able to change the event being raised. This intra
+ * object/behavior event returns the name of the desired event to be raised. It will pass through
+ * if no dynamic event is specified, or if the original event name is returned.
+ * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
+ * called for a specific raised event (and associated event arguments)
+ * dyIntraRaiseEventPostHandler does not return anything. This allows behaviors to access the results
+ * of an event handler in the per handler loop.
+ * dyPostRaiseEvent returns the responses. This allows for any post processing of the event
+ * results from the sum of all event handlers
+ *
+ * When handling a catch-all {@link __dycall}, the method name is the name of the event
+ * and the parameters are the sender, the param, and then the name of the event.
+ *
* @param string the event name
* @param mixed the event sender object
* @param TEventParameter the event parameter
+ * @param numeric how the results of the event are tabulated. default: {@link EVENT_RESULT_FILTER} The default filters out
+ * null responses. optional
+ * @param function any per handler filtering of the response result needed is passed through
+ * this if not null. default: null. optional
+ * @return mixed the results of the event
* @throws TInvalidOperationException if the event is undefined
* @throws TInvalidDataValueException If an event handler is invalid
*/
- public function raiseEvent($name,$sender,$param)
+ public function raiseEvent($name,$sender,$param,$responsetype=null,$postfunction=null)
{
+ $p=$param;
+ if(is_callable($responsetype))
+ {
+ $postfunction=$responsetype;
+ $responsetype=null;
+ }
+
+ if($responsetype===null)
+ $responsetype=TEventResults::EVENT_RESULT_FILTER;
+
$name=strtolower($name);
- if(isset($this->_e[$name]))
+ $responses=array();
+
+ $name=$this->dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction);
+
+ if($this->hasEventHandler($name)||$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
{
- foreach($this->_e[$name] as $handler)
+ $handlers=$this->getEventHandlers($name);
+ $handlerArray=$handlers->toArray();
+ if(strncasecmp($name,'fx',2)===0&&$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
+ {
+ $globalhandlers=$this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ $handlerArray=array_merge($globalhandlers->toArrayBelowPriority(0),$handlerArray,$globalhandlers->toArrayAbovePriority(0));
+ }
+ $response=null;
+ foreach($handlerArray as $handler)
{
+ if($this->dyIntraRaiseEventTestHandler($handler,$sender,$param,$name)===false)
+ continue;
+
if(is_string($handler))
{
if(($pos=strrpos($handler,'.'))!==false)
{
$object=$this->getSubProperty(substr($handler,0,$pos));
$method=substr($handler,$pos+1);
- if(method_exists($object,$method))
- $object->$method($sender,$param);
+ if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($method=='__dycall')
+ $response=$object->__dycall($name,array($sender,$param,$name));
+ else
+ $response=$object->$method($sender,$param,$name);
+ }
else
throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
}
else
- call_user_func($handler,$sender,$param);
+ $response=call_user_func($handler,$sender,$param,$name);
}
else if(is_callable($handler,true))
{
- // an array: 0 - object, 1 - method name/path
list($object,$method)=$handler;
- if(is_string($object)) // static method call
- call_user_func($handler,$sender,$param);
+ if(is_string($object))
+ $response=call_user_func($handler,$sender,$param,$name);
else
{
if(($pos=strrpos($method,'.'))!==false)
@@ -446,27 +1171,66 @@ class TComponent
$object=$this->getSubProperty(substr($method,0,$pos));
$method=substr($method,$pos+1);
}
- if(method_exists($object,$method))
- $object->$method($sender,$param);
+ if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($method=='__dycall')
+ $response=$object->__dycall($name,array($sender,$param,$name));
+ else
+ $response=$object->$method($sender,$param,$name);
+ }
else
throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);
}
}
else
throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,gettype($handler));
+
+ $this->dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response);
+
+ if($postfunction)
+ $response=call_user_func_array($postfunction,array($sender,$param,$this,$response));
+
+ if($responsetype&TEventResults::EVENT_RESULT_ALL)
+ $responses[]=array('sender'=>$sender,'param'=>$param,'response'=>$response);
+ else
+ $responses[]=$response;
+
+ if($response!==null&&($responsetype&TEventResults::EVENT_RESULT_FEED_FORWARD))
+ $param=$response;
+
}
}
- else if(!$this->hasEvent($name))
+ else if(strncasecmp($name,'on',2)===0&&!$this->hasEvent($name))
throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+
+ if($responsetype&TEventResults::EVENT_RESULT_FILTER)
+ $responses=array_filter($responses);
+
+ $responses=$this->dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction);
+
+ return $responses;
}
/**
* Evaluates a PHP expression in the context of this control.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEvaluateExpressionFilter($expression, $chain) {
+ * return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
+ * }
+ * </code>
+ * to be executed when evaluateExpression is called. All attached behaviors are notified through
+ * dyEvaluateExpressionFilter. The chaining is important in this function due to the filtering
+ * pass-through effect.
+ *
+ * @param string PHP expression
* @return mixed the expression result
* @throws TInvalidOperationException if the expression is invalid
*/
public function evaluateExpression($expression)
{
+ $expression=$this->dyEvaluateExpressionFilter($expression);
try
{
if(eval("\$result=$expression;")===false)
@@ -481,12 +1245,24 @@ class TComponent
/**
* Evaluates a list of PHP statements.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEvaluateStatementsFilter($statements, $chain) {
+ * return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
+ * }
+ * </code>
+ * to be executed when evaluateStatements is called. All attached behaviors are notified through
+ * dyEvaluateStatementsFilter. The chaining is important in this function due to the filtering
+ * pass-through effect.
+ *
* @param string PHP statements
* @return string content echoed or printed by the PHP statements
* @throws TInvalidOperationException if the statements are invalid
*/
public function evaluateStatements($statements)
{
+ $statements=$this->dyEvaluateStatementsFilter($statements);
try
{
ob_start();
@@ -508,11 +1284,22 @@ class TComponent
* The default implementation of this method will invoke
* the potential parent component's {@link addParsedObject}.
* This method can be overridden.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyCreatedOnTemplate($parent, $chain) {
+ * return $chain->dyCreatedOnTemplate($parent); //example
+ * }
+ * </code>
+ * to be executed when createdOnTemplate is called. All attached behaviors are notified through
+ * dyCreatedOnTemplate.
+ *
* @param TComponent potential parent of this control
* @see addParsedObject
*/
public function createdOnTemplate($parent)
{
+ $parent=$this->dyCreatedOnTemplate($parent);
$parent->addParsedObject($this);
}
@@ -521,11 +1308,381 @@ class TComponent
* The object can be either a component or a static text string.
* This method can be overridden to customize the handling of newly created objects in template.
* Only framework developers and control developers should use this method.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyAddParsedObject($object[, $chain]) {
+ * }
+ * </code>
+ * to be executed when addParsedObject is called. All attached behaviors are notified through
+ * dyAddParsedObject.
+ *
* @param string|TComponent text string or component parsed and instantiated in template
* @see createdOnTemplate
*/
public function addParsedObject($object)
{
+ $this->dyAddParsedObject($object);
+ }
+
+
+ /**
+ * This is the method registered for all instanced objects should a class behavior be added after
+ * the class is instanced. Only when the class to which the behavior is being added is in this
+ * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
+ * @param $sender the application
+ * @param $param TClassBehaviorEventParameter
+ */
+ public function fxAttachClassBehavior($sender,$param) {
+ if(in_array($param->getClass(),$this->getClassHierarchy(true)))
+ return $this->attachBehavior($param->getName(),$param->getBehavior(),$param->getPriority());
+ }
+
+
+ /**
+ * This is the method registered for all instanced objects should a class behavior be removed after
+ * the class is instanced. Only when the class to which the behavior is being added is in this
+ * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
+ * @param $sender the application
+ * @param $param TClassBehaviorEventParameter
+ */
+ public function fxDetachClassBehavior($sender,$param) {
+ if(in_array($param->getClass(),$this->getClassHierarchy(true)))
+ return $this->detachBehavior($param->getName(),$param->getPriority());
+ }
+
+
+ /**
+ * This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
+ * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
+ * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
+ * This is done so class behaviors are added last first.
+ * @param string name the key of the class behavior
+ * @param object|string class behavior or name of the object behavior per instance
+ * @param string|class string of class or class on which to attach this behavior. Defaults to null which will error
+ * but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
+ * it should extend.
+ * <code>
+ * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
+ * </code>
+ * @param numeric|null priority of behavior, default: null the default priority of the {@link TPriorityList} Optional.
+ * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
+ * @throws TInvalidOperationException if the class behavior is already defined
+ */
+ public static function attachClassBehavior($name,$behavior,$class=null,$priority=null) {
+ if(!$class&&function_exists('get_called_class'))
+ $class=get_called_class();
+ if(!$class)
+ throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
+
+ if(!is_string($name))
+ $name=get_class($name);
+ $class=strtolower($class);
+ if($class==='tcomponent')
+ throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
+ if(empty(self::$_um[$class]))
+ self::$_um[$class]=array();
+ if(isset(self::$_um[$class][$name]))
+ throw new TInvalidOperationException('component_class_behavior_defined',$class,$name);
+ $param=new TClassBehaviorEventParameter($class,$name,$behavior,$priority);
+ self::$_um[$class]=array($name=>$param)+self::$_um[$class];
+ $behaviorObject=is_string($behavior)?new $behavior:$behavior;
+ return $behaviorObject->raiseEvent('fxAttachClassBehavior',null,$param);
+ }
+
+
+ /**
+ * This will remove a behavior from a class. It unregisters it from future instances and
+ * pulls the changes from all the instances that are listening as well.
+ * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
+ * @param $name the key of the class behavior
+ * @param $class string class on which to attach this behavior. Defaults to null.
+ * @param $priority numeric|null|false priority. false is any priority, null is default
+ * {@link TPriorityList} priority, and numeric is a specific priority.
+ * @throws Exception if the the class cannot be derived from Late Static Binding and is not
+ * not supplied as a parameter.
+ */
+ public static function detachClassBehavior($name,$class=null,$priority=false) {
+ if(!$class&&function_exists('get_called_class'))
+ $class=get_called_class();
+ if(!$class)
+ throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
+
+ $class=strtolower($class);
+ if(!is_string($name))
+ $name=get_class($name);
+ if(empty(self::$_um[$class])||!isset(self::$_um[$class][$name]))
+ return false;
+ $param=self::$_um[$class][$name];
+ $behavior=$param->getBehavior();
+ unset(self::$_um[$class][$name]);
+ $behaviorObject=is_string($behavior)?new $behavior:$behavior;
+ return $behaviorObject->raiseEvent('fxDetachClassBehavior',null,$param);
+ }
+
+ /**
+ * Returns the named behavior object.
+ * The name 'asa' stands for 'as a'.
+ * @param string the behavior name
+ * @return IBehavior the behavior object, or null if the behavior does not exist
+ */
+ public function asa($behaviorname)
+ {
+ return isset($this->_m[$behaviorname])?$this->_m[$behaviorname]:null;
+ }
+
+ /**
+ * Returns whether or not the object or any of the behaviors are of a particular class.
+ * The name 'isa' stands for 'is a'. This first checks if $this is an instanceof the class.
+ * It then checks each Behavior. If a behavior implements {@link IInstanceCheck},
+ * then the behavior can determine what it is an instanceof. If this behavior function returns true,
+ * then this method returns true. If the behavior instance checking function returns false,
+ * then no further checking is performed as it is assumed to be correct.
+ *
+ * If the behavior instance check function returns nothing or null or the behavior
+ * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
+ * The default isa behavior is to check if the behavior is an instanceof the class.
+ *
+ * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
+ * act as a completely different object.
+ *
+ * @param class or string
+ * @return boolean whether or not the object or a behavior is an instance of a particular class
+ */
+ public function isa($class)
+ {
+ if($this instanceof $class)
+ return true;
+ if($this->_m!==null&&$this->_behaviorsenabled)
+ foreach($this->_m->toArray() as $behavior){
+ if(($behavior instanceof IBehavior)&&!$behavior->getEnabled())
+ continue;
+
+ $check = null;
+ if(($behavior->isa('IInstanceCheck'))&&$check=$behavior->isinstanceof($class,$this))
+ return true;
+ if($check===null&&($behavior->isa($class)))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attaches a list of behaviors to the component.
+ * Each behavior is indexed by its name and should be an instance of
+ * {@link IBehavior}, a string specifying the behavior class, or a
+ * {@link TClassBehaviorEventParameter}.
+ * @param array list of behaviors to be attached to the component
+ */
+ public function attachBehaviors($behaviors)
+ {
+ foreach($behaviors as $name=>$behavior)
+ if($behavior instanceof TClassBehaviorEventParameter)
+ $this->attachBehavior($behavior->getName(),$behavior->getBehavior(),$behavior->getPriority());
+ else
+ $this->attachBehavior($name,$behavior);
+ }
+
+ /**
+ * Detaches select behaviors from the component.
+ * Each behavior is indexed by its name and should be an instance of
+ * {@link IBehavior}, a string specifying the behavior class, or a
+ * {@link TClassBehaviorEventParameter}.
+ * @param array list of behaviors to be detached from the component
+ */
+ public function detachBehaviors($behaviors)
+ {
+ if($this->_m!==null)
+ {
+ foreach($behaviors as $name=>$behavior)
+ if($behavior instanceof TClassBehaviorEventParameter)
+ $this->detachBehavior($behavior->getName(),$behavior->getPriority());
+ else
+ $this->detachBehavior(is_string($behavior)?$behavior:$name);
+ }
+ }
+
+ /**
+ * Detaches all behaviors from the component.
+ */
+ public function clearBehaviors()
+ {
+ if($this->_m!==null)
+ {
+ foreach($this->_m->toArray() as $name=>$behavior)
+ $this->detachBehavior($name);
+ $this->_m=null;
+ }
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * This method will create the behavior object based on the given
+ * configuration. After that, the behavior object will be initialized
+ * by calling its {@link IBehavior::attach} method.
+ *
+ * Already attached behaviors may implement the function:
+ * <code>
+ * public function dyAttachBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when attachBehavior is called. All attached behaviors are notified through
+ * dyAttachBehavior.
+ *
+ * @param string the behavior's name. It should uniquely identify this behavior.
+ * @param mixed the behavior configuration. This is passed as the first
+ * parameter to {@link YiiBase::createComponent} to create the behavior object.
+ * @return IBehavior the behavior object
+ */
+ public function attachBehavior($name,$behavior,$priority=null)
+ {
+ if(is_string($behavior))
+ $behavior=Prado::createComponent($behavior);
+ if(!($behavior instanceof IBaseBehavior))
+ throw new TInvalidDataTypeException('component_not_a_behavior',get_class($behavior));
+ if($behavior instanceof IBehavior)
+ $behavior->setEnabled(true);
+ if($this->_m===null)
+ $this->_m=new TPriorityMap;
+ $behavior->attach($this);
+ $this->dyAttachBehavior($name,$behavior);
+ $this->_m->add($name,$behavior,$priority);
+ return $behavior;
+ }
+
+ /**
+ * Detaches a behavior from the component.
+ * The behavior's {@link IBehavior::detach} method will be invoked.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDetachBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when detachBehavior is called. All attached behaviors are notified through
+ * dyDetachBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ * @param numeric the behavior's priority. This defaults to false, aka any priority.
+ * @return IBehavior the detached behavior. Null if the behavior does not exist.
+ */
+ public function detachBehavior($name,$priority=false)
+ {
+ if($this->_m!=null&&isset($this->_m[$name]))
+ {
+ $this->_m[$name]->detach($this);
+ $behavior=$this->_m->itemAt($name);
+ $this->_m->remove($name,$priority);
+ $this->dyDetachBehavior($name,$behavior);
+ return $behavior;
+ }
+ }
+
+ /**
+ * Enables all behaviors attached to this component independent of the behaviors
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEnableBehaviors($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when enableBehaviors is called. All attached behaviors are notified through
+ * dyEnableBehaviors.
+ *
+ */
+ public function enableBehaviors()
+ {
+ if(!$this->_behaviorsenabled)
+ {
+ $this->_behaviorsenabled=true;
+ $this->dyEnableBehaviors();
+ }
+ }
+
+ /**
+ * Disables all behaviors attached to this component independent of the behaviors
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDisableBehaviors($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when disableBehaviors is called. All attached behaviors are notified through
+ * dyDisableBehaviors.
+ *
+ */
+ public function disableBehaviors()
+ {
+ if($this->_behaviorsenabled)
+ {
+ $this->dyDisableBehaviors();
+ $this->_behaviorsenabled=false;
+ }
+ }
+
+
+ /**
+ * Returns if all the behaviors are turned on or off for the object.
+ * @return boolean whether or not all behaviors are enabled (true) or not (false)
+ */
+ public function getBehaviorsEnabled()
+ {
+ return $this->_behaviorsenabled;
+ }
+
+ /**
+ * Enables an attached object behavior. This cannot enable or disable whole class behaviors.
+ * A behavior is only effective when it is enabled.
+ * A behavior is enabled when first attached.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEnableBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when enableBehavior is called. All attached behaviors are notified through
+ * dyEnableBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ */
+ public function enableBehavior($name)
+ {
+ if($this->_m!=null&&isset($this->_m[$name])){
+ if($this->_m[$name] instanceof IBehavior) {
+ $this->_m[$name]->setEnabled(true);
+ $this->dyEnableBehavior($name,$this->_m[$name]);
+ return true;
+ }
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * Disables an attached behavior. This cannot enable or disable whole class behaviors.
+ * A behavior is only effective when it is enabled.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDisableBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when disableBehavior is called. All attached behaviors are notified through
+ * dyDisableBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ */
+ public function disableBehavior($name)
+ {
+ if($this->_m!=null&&isset($this->_m[$name])){
+ if($this->_m[$name] instanceof IBehavior) {
+ $this->_m[$name]->setEnabled(false);
+ $this->dyDisableBehavior($name,$this->_m[$name]);
+ return true;
+ }
+ return false;
+ }
+ return null;
}
/**
@@ -554,6 +1711,91 @@ class TComponent
}
}
+
+/**
+ * IDynamicMethods interface.
+ * IDynamicMethods marks an object to receive undefined global or dynamic events.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IDynamicMethods
+{
+ public function __dycall($method,$args);
+}
+
+
+
+/**
+ * TClassBehaviorEventParameter class.
+ * TClassBehaviorEventParameter is the parameter sent with the class behavior changes.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+class TClassBehaviorEventParameter extends TEventParameter
+{
+ private $_class;
+ private $_name;
+ private $_behavior;
+ private $_priority;
+
+ /**
+ * Holds the parameters for the Class Behavior Events
+ * @param string $class this is the class to get the behavior
+ * @param string $name the name of the behavior
+ * @param object $behavior this is the behavior to implement the class behavior
+ */
+ public function __construct($class,$name,$behavior,$priority)
+ {
+ $this->_class=$class;
+ $this->_name=$name;
+ $this->_behavior=$behavior;
+ $this->_priority=$priority;
+ }
+
+ /**
+ * This is the class to get the behavior
+ * @return string the class to get the behavior
+ */
+ public function getClass()
+ {
+ return $this->_class;
+ }
+
+ /**
+ * name of the behavior
+ * @return string the name to get the behavior
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * This is the behavior which the class is to get
+ * @return object the behavior to implement
+ */
+ public function getBehavior()
+ {
+ return $this->_behavior;
+ }
+
+ /**
+ * This is the priority which the behavior is to get
+ * @return numeric the priority of the behavior
+ */
+ public function getPriority()
+ {
+ return $this->_priority;
+ }
+}
+
+
/**
* TEnumerable class.
* TEnumerable is the base class for all enumerable types.
@@ -578,11 +1820,11 @@ class TComponent
*/
class TEnumerable implements Iterator
{
- private $_enums = array();
+ private $_enums=array();
public function __construct() {
- $reflection = new ReflectionClass($this);
- $this->_enums = $reflection->getConstants();
+ $reflection=new ReflectionClass($this);
+ $this->_enums=$reflection->getConstants();
}
public function current() {
@@ -602,7 +1844,7 @@ class TEnumerable implements Iterator
}
public function valid() {
- return $this->current() !== false;
+ return $this->current()!==false;
}
}
@@ -779,7 +2021,7 @@ class TPropertyValue
*/
public static function ensureNullIfEmpty($value)
{
- return empty($value) ? null : $value;
+ return empty($value)?null:$value;
}
}
@@ -796,6 +2038,12 @@ class TEventParameter extends TComponent
{
}
+class TEventResults extends TEnumerable {
+ const EVENT_RESULT_FEED_FORWARD=1;
+ const EVENT_RESULT_FILTER=2;
+ const EVENT_RESULT_ALL=4;
+}
+
/**
* TComponentReflection class.
*
@@ -978,6 +2226,117 @@ class TComponentReflection extends TComponent
}
}
+
+
+
+
+/**
+ * IBaseBehavior interface is the base behavior class from which all other
+ * behaviors types are derived
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IBaseBehavior {
+ /**
+ * Attaches the behavior object to the component.
+ * @param CComponent the component that this behavior is to be attached to.
+ */
+ public function attach($component);
+ /**
+ * Detaches the behavior object from the component.
+ * @param CComponent the component that this behavior is to be detached from.
+ */
+ public function detach($component);
+}
+
+/**
+ * IBehavior interfaces is implemented by instance behavior classes.
+ *
+ * A behavior is a way to enhance a component with additional methods and
+ * events that are defined in the behavior class and not available in the
+ * class. Objects may signal behaviors through dynamic events.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IBehavior extends IBaseBehavior
+{
+ /**
+ * @return boolean whether this behavior is enabled
+ */
+ public function getEnabled();
+ /**
+ * @param boolean whether this behavior is enabled
+ */
+ public function setEnabled($value);
+}
+
+
+/**
+ * IClassBehavior interface is implements behaviors across all instances of
+ * a particular class
+ *
+ * Any calls to functions not present in the original object but to behaviors
+ * derived from this class, will have inserted as the first argument parameter
+ * the object containing the behavior.
+ *
+ * For example:
+ * <code>
+ * $objWithClassBehavior->MethodOfClassBehavior(1, 20);
+ * </code>
+ * will be acted within the class behavior like this:
+ * <code>
+ * public function MethodOfClassBehavior($object, $firstParam, $secondParam){
+ * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20
+ * }
+ * </code>
+ *
+ * This also holds for 'dy' events as well. For dynamic events, method arguments would be:
+ * <code>
+ * public function dyMethodOfClassBehavior($object, $firstParam, $secondParam, $callchain){
+ * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20, $callchain instanceof {@link TCallChain}
+ * }
+ * </code>
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IClassBehavior extends IBaseBehavior {
+}
+
+
+/**
+ * IInstanceCheck This interface allows objects to determine their own
+ * 'instanceof' results when {@link TComponent::isa} is called. This is
+ * important with behaviors because behaviors may want to look like
+ * particular objects other than themselves.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IInstanceCheck {
+ /**
+ * The method checks $this or, if needed, the parameter $instance is of type
+ * class. In the case of a Class Behavior, the instance to which the behavior
+ * is attached may be important to determine if $this is an instance
+ * of a particular class.
+ * @param class|string the component that this behavior is checking if it is an instanceof.
+ * @param object the object which the behavior is attached to. default: null
+ * @return boolean|null if the this or the instance is of type class. When null, no information could be derived and
+ * the default mechanisms take over.
+ */
+ public function isinstanceof($class,$instance=null);
+}
+
/**
* TJavaScriptLiteral class that encloses string literals that are not
* supposed to be escaped by {@link TJavaScript::encode() }
@@ -1035,3 +2394,4 @@ class TJavaScriptString extends TJavaScriptLiteral
return TJavaScript::jsonEncode((string)$this->_s,JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG);
}
}
+
diff --git a/framework/Util/TBehavior.php b/framework/Util/TBehavior.php
new file mode 100644
index 00000000..465f9673
--- /dev/null
+++ b/framework/Util/TBehavior.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * CBehavior class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2009 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CBehavior is a convenient base class for behavior classes.
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: CBehavior.php 564 2009-01-21 22:07:10Z qiang.xue $
+ * @package system.base
+ * @since 1.0.2
+ */
+class TBehavior extends TComponent implements IBehavior
+{
+ private $_enabled;
+ private $_owner;
+
+ /**
+ * Declares events and the corresponding event handler methods.
+ * The events are defined by the {@link owner} component, while the handler
+ * methods by the behavior class. The handlers will be attached to the corresponding
+ * events when the behavior is attached to the {@link owner} component; and they
+ * will be detached from the events when the behavior is detached from the component.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return array();
+ }
+
+ /**
+ * Attaches the behavior object to the component.
+ * The default implementation will set the {@link owner} property
+ * and attach event handlers as declared in {@link events}.
+ * Make sure you call the parent implementation if you override this method.
+ * @param TComponent the component that this behavior is to be attached to.
+ */
+ public function attach($owner)
+ {
+ $this->_owner=$owner;
+ foreach($this->events() as $event=>$handler)
+ $owner->attachEventHandler($event,array($this,$handler));
+ }
+
+ /**
+ * Detaches the behavior object from the component.
+ * The default implementation will unset the {@link owner} property
+ * and detach event handlers declared in {@link events}.
+ * Make sure you call the parent implementation if you override this method.
+ * @param TComponent the component that this behavior is to be detached from.
+ */
+ public function detach($owner)
+ {
+ foreach($this->events() as $event=>$handler)
+ $owner->detachEventHandler($event,array($this,$handler));
+ $this->_owner=null;
+ }
+
+ /**
+ * @return CComponent the owner component that this behavior is attached to.
+ */
+ public function getOwner()
+ {
+ return $this->_owner;
+ }
+
+ /**
+ * @return boolean whether this behavior is enabled
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * @param boolean whether this behavior is enabled
+ */
+ public function setEnabled($value)
+ {
+ $this->_enabled=$value;
+ }
+} \ No newline at end of file
diff --git a/framework/Util/TCallChain.php b/framework/Util/TCallChain.php
new file mode 100644
index 00000000..ae6e02fc
--- /dev/null
+++ b/framework/Util/TCallChain.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * TCallChain class file.
+ *
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2008-2011 Pradosoft
+ * @license http://www.pradosoft.com/license/
+ */
+
+/**
+ * TCallChain is a recursive event calling mechanism. This class implements
+ * the {@link IDynamicMethods} class so that any 'dy' event calls can be caught
+ * and patched through to the intended recipient
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @version $Id: TCallChain.php 564 2009-01-21 22:07:10Z javalizard $
+ * @package system.base
+ * @since 3.2
+ */
+class TCallChain extends TList implements IDynamicMethods
+{
+ /**
+ * @var {@link TListIterator} for moving through the chained method calls
+ */
+ private $_iterator=null;
+
+ /**
+ * @var string the method name of the call chain
+ */
+ private $_method=null;
+
+ /**
+ * This initializes the list and the name of the method to be called
+ * @param string the name of the function call
+ */
+ public function __construct($method) {
+ $this->_method=$method;
+ parent::__construct();
+ }
+
+
+ /**
+ * This initializes the list and the name of the method to be called
+ * @param string|array this is a callable function as a string or array with
+ * the object and method name as string
+ * @param array The array of arguments to the function call chain
+ */
+ public function addCall($method,$args)
+ {
+ $this->add(array($method,$args));
+ }
+
+ /**
+ * This method calls the next Callable in the list. All of the method arguments
+ * coming into this method are substituted into the original method argument of
+ * call in the chain.
+ *
+ * If the original method call has these parameters
+ * <code>
+ * $originalobject->dyExampleMethod('param1', 'param2', 'param3')
+ * </code>
+ * <code>
+ * $callchain->dyExampleMethod('alt1', 'alt2')
+ * </code>
+ * then the next call in the call chain will recieve the parameters as if this were called
+ * <code>
+ * $behavior->dyExampleMethod('alt1', 'alt2', 'param3', $callchainobject)
+ * </code>
+ *
+ * When dealing with {@link IClassBehaviors}, the first parameter of the stored argument
+ * list in 'dy' event calls is always the object containing the behavior. This modifies
+ * the parameter replacement mechanism slightly to leave the object containing the behavior
+ * alone and only replacing the other parameters in the argument list. As per {@link __call},
+ * any calls to a 'dy' event do not need the object containing the behavior as the addition of
+ * the object to the argument list as the first element is automatic for IClassBehaviors.
+ *
+ * The last parameter of the method parameter list for any callable in the call chain
+ * will be the TCallChain object itself. This is so that any behavior implementing
+ * these calls will have access to the call chain. Each callable should either call
+ * the TCallChain call method internally for direct chaining or call the method being
+ * chained (in which case the dynamic handler will pass through to this call method).
+ *
+ * If the dynamic intra object/behavior event is not called in the behavior implemented
+ * dynamic method, it will return to this method and call the following behavior
+ * implementation so as no behavior with an implementation of the dynamic event is left
+ * uncalled. This does break the call chain though and will not act as a "parameter filter".
+ *
+ * When there are no handlers or no handlers left, it returns the first parameter of the
+ * argument list.
+ *
+ */
+ public function call()
+ {
+ $args=func_get_args();
+ if($this->getCount()===0)
+ return isset($args[0])?$args[0]:null;
+
+ if(!$this->_iterator)
+ {
+ $chain_array=array_reverse($this->toArray());
+ $this->_iterator=new TListIterator($chain_array);
+ }
+ if($this->_iterator->valid())
+ do {
+ $handler=$this->_iterator->current();
+ $this->_iterator->next();
+ if(is_array($handler[0])&&$handler[0][0] instanceof IClassBehavior)
+ array_splice($handler[1],1,count($args),$args);
+ else
+ array_splice($handler[1],0,count($args),$args);
+ $handler[1][]=$this;
+ $result=call_user_func_array($handler[0],$handler[1]);
+ } while($this->_iterator->valid());
+ else
+ $result = $args[0];
+ return $result;
+ }
+
+
+ /**
+ * This catches all the unpatched dynamic events. When the method call matches the
+ * call chain method, it passes the arguments to the original __call (of the dynamic
+ * event being unspecified in TCallChain) and funnels into the method {@link call},
+ * so the next dynamic event handler can be called.
+ * If the original method call has these parameters
+ * <code>
+ * $originalobject->dyExampleMethod('param1', 'param2', 'param3')
+ * </code>
+ * and within the chained dynamic events, this can be called
+ * <code>
+ * class DyBehavior extends TBehavior {
+ * public function dyExampleMethod($param1, $param2, $param3, $callchain)
+ * $callchain->dyExampleMethod($param1, $param2, $param3)
+ * }
+ * {
+ * </code>
+ * to call the next event in the chain.
+ * @param string method name of the unspecified object method
+ * @param array arguments to the unspecified object method
+ */
+ public function __dycall($method,$args)
+ {
+ if($this->_method==$method)
+ return call_user_func_array(array($this,'call'),$args);
+ return null;
+ }
+} \ No newline at end of file
diff --git a/framework/Util/TClassBehavior.php b/framework/Util/TClassBehavior.php
new file mode 100644
index 00000000..807549b7
--- /dev/null
+++ b/framework/Util/TClassBehavior.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * TClassBehavior class file.
+ *
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2008-2011 Pradosoft
+ * @license http://www.pradosoft.com/license/
+ */
+
+/**
+ * TClassBehavior is a convenient base class for whole class behaviors.
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @version $Id: TClassBehavior.php 564 2009-01-21 22:07:10Z javalizard $
+ * @package system.base
+ * @since 3.1.8
+ */
+class TClassBehavior extends TComponent implements IClassBehavior
+{
+
+ /**
+ * Attaches the behavior object to the component.
+ * @param TComponent the component that this behavior is to be attached to.
+ */
+ public function attach($component)
+ {
+ }
+
+ /**
+ * Detaches the behavior object from the component.
+ * @param TComponent the component that this behavior is to be detached from.
+ */
+ public function detach($component)
+ {
+ }
+} \ No newline at end of file
diff --git a/tests/unit/TComponentTest.php b/tests/unit/TComponentTest.php
index 25292c0d..7722f9d6 100644
--- a/tests/unit/TComponentTest.php
+++ b/tests/unit/TComponentTest.php
@@ -1,11 +1,21 @@
<?php
require_once dirname(__FILE__).'/phpunit.php';
+Prado::using('System.Util.*');
+Prado::using('System.Collections.TPriorityList');
+Prado::using('System.Collections.TPriorityMap');
+
class NewComponent extends TComponent {
private $_object = null;
private $_text = 'default';
private $_eventHandled = false;
+ private $_return = null;
+ private $_colorattribute = null;
+ public function getAutoGlobalListen() {
+ return true;
+ }
+
public function getText() {
return $this->_text;
}
@@ -14,6 +24,14 @@ class NewComponent extends TComponent {
$this->_text=$value;
}
+ public function getReadOnlyProperty() {
+ return 'read only';
+ }
+
+ public function getJsReadOnlyJsProperty() {
+ return 'js read only';
+ }
+
public function getObject() {
if(!$this->_object) {
$this->_object=new NewComponent;
@@ -30,10 +48,363 @@ class NewComponent extends TComponent {
$this->_eventHandled=true;
}
+ public function eventReturnValue($sender,$param) {
+ return $param->Return;
+ }
+
public function isEventHandled() {
return $this->_eventHandled;
}
+
+ public function resetEventHandled() {
+ $this->_eventHandled = false;
+ }
+ public function getReturn() {
+ return $this->_return;
+ }
+ public function setReturn($return) {
+ $this->_return = $return;
+ }
+ public function getjsColorAttribute() {
+ return $this->_colorattribute;
+ }
+ public function setjsColorAttribute($colorattribute) {
+ $this->_colorattribute = $colorattribute;
+ }
+}
+
+
+class NewComponentNoListen extends NewComponent {
+ // this object does _not_ auto install global listeners during construction
+ public function getAutoGlobalListen() {
+ return false;
+ }
+}
+
+class DynamicCatchingComponent extends NewComponentNoListen implements IDynamicMethods {
+ public function __dycall($method, $args) {
+
+ }
+}
+
+
+class GlobalRaiseComponent extends NewComponent implements IDynamicMethods {
+ private $_callorder = array();
+
+ public function getCallOrders()
+ {
+ return $this->_callorder;
+ }
+ public function __dycall($method, $args) {
+ if(strncasecmp($method,'fx',2)!==0) return;
+ $this->_callorder[] = 'fxcall';
+ }
+ public function fxGlobalListener($sender,$param,$name) {
+ $this->_callorder[] = 'fxGL';
+ }
+ public function fxPrimaryGlobalEvent($sender,$param,$name) {
+ $this->_callorder[] = 'primary';
+ }
+ public function commonRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'com';
+ }
+ public function postglobalRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'postgl';
+ }
+ public function preglobalRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'pregl';
+ }
+}
+
+
+
+class FooClassBehavior extends TClassBehavior {
+ private $_baseObject = null;
+ public function faaEverMore($object, $laa, $sol) {
+ $this->_baseObject = $object;
+ return $laa * $sol;
+ }
+ public function getLastClassObject() {return $this->_baseObject;}
+}
+
+class FooFooClassBehavior extends FooClassBehavior {
+ public function faafaaEverMore($object, $laa, $sol) {
+
+ }
+}
+
+class BarClassBehavior extends TClassBehavior {
+ public function moreFunction($object, $laa, $sol) {
+ return true;
+ }
+}
+
+
+
+class FooBehavior extends TBehavior {
+ public function faaEverMore($laa, $sol) {
+ return true;
+ }
}
+class FooFooBehavior extends FooBehavior {
+
+ public function faafaaEverMore($laa, $sol) {
+ return sqrt($laa * $laa + $sol * $sol);
+ }
+}
+class FooBarBehavior extends TBehavior {
+ public function moreFunction($laa, $sol) {
+ return $laa * $sol * $sol;
+ }
+}
+
+class PreBarBehavior extends TBehavior {
+}
+
+class BarBehavior extends PreBarBehavior implements IInstanceCheck {
+ private $_instanceReturn = null;
+
+ public function moreFunction($laa, $sol) {
+ return pow($laa+$sol+1, 2);
+ }
+
+ public function isinstanceof($class, $instance=null) {
+ return $this->_instanceReturn;
+ }
+ public function setInstanceReturn($value) {
+ $this->_instanceReturn = $value;
+ }
+}
+class DynamicCallComponent extends NewComponent implements IDynamicMethods {
+ public function __dycall($method, $args) {
+ if($method === 'dyPowerFunction')
+ return pow($args[0], $args[1]);
+ if($method === 'dyDivisionFunction')
+ return $args[0] / $args[1];
+ if($method === 'fxPowerFunction')
+ return 2*pow($args[0], $args[1]);
+ if($method === 'fxDivisionFunction')
+ return 2*$args[0] / $args[1];
+ }
+}
+
+
+
+class BehaviorTestBehavior extends TBehavior {
+ private $excitement = 'faa';
+ public function getExcitement() {
+ return $this->excitement;
+ }
+ public function setExcitement($value) {
+ $this->excitement = $value;
+ }
+ public function getReadOnly() {
+ return true;
+ }
+
+ public function onBehaviorEvent($sender, $param,$responsetype=null,$postfunction=null) {
+ return $this->getOwner()->raiseEvent('onBehaviorEvent',$sender,$param,$responsetype,$postfunction);
+ }
+ public function fxGlobalBehaviorEvent($sender, $param) {
+ }
+}
+
+class dy1TextReplace extends TBehavior {
+ protected $_called = false;
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("..", '__', $callchain->dyTextFilter($text));
+ }
+ public function isCalled() {
+ return $this->_called;
+ }
+ public function dyPowerFunction($x, $y, $callchain)
+ {
+ return pow($x / $callchain->dyPowerFunction($x, $y), $y);
+ }
+}
+class dy2TextReplace extends dy1TextReplace {
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("++", '||', $callchain->dyTextFilter($text));
+ }
+}
+class dy3TextReplace extends dy1TextReplace {
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("!!", '??', $callchain->dyTextFilter($text));
+ }
+}
+
+class dy1ClassTextReplace extends TClassBehavior {
+ protected $_called = false;
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("__", '..', $callchain->dyTextFilter($text));
+ }
+ public function isCalled() {
+ return $this->_called;
+ }
+ public function dyPowerFunction($hostobject, $x, $y, $callchain)
+ {
+ return pow($x / $callchain->dyPowerFunction($x, $y), $y);
+ }
+}
+class dy2ClassTextReplace extends dy1ClassTextReplace {
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("||", '++', $callchain->dyTextFilter($text));
+ }
+}
+class dy3ClassTextReplace extends dy1ClassTextReplace {
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("??", '^_^', $callchain->dyTextFilter($text));
+ }
+}
+
+
+class IntraObjectExtenderBehavior extends TBehavior {
+
+ private $lastCall = null;
+ private $arglist = null;
+
+ public function getLastCall() {
+ $v = $this->lastCall;
+ $this->lastCall = null;
+ return $v;
+ }
+
+ public function getLastArgumentList() {
+ $v = $this->arglist;
+ $this->arglist = null;
+ return $v;
+ }
+
+
+
+ public function dyListen($fx, $chain) {
+ $this->lastCall = 1;
+ $this->arglist = func_get_args();
+
+ return $chain->dyListen($fx); // Calls the next event, within a chain
+ }
+ public function dyUnlisten($fx, $chain) {
+ $this->lastCall = 2;
+ $this->arglist = func_get_args();
+
+ return $chain->dyUnlisten($fx);
+ }
+ public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction, $chain) {
+ $this->lastCall = 3;
+ $this->arglist = func_get_args();
+
+ return $chain->dyPreRaiseEvent($name);// Calls the next event, within a chain, if parameters are left off, they are filled in with
+ // the original parameters passed to the dynamic event. Parameters can be passed if they are changed.
+ }
+ public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name, $chain) {
+ $this->lastCall += 4;
+ $this->arglist = func_get_args();
+ }
+ public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler, $chain) {
+ $this->lastCall += 5;
+ $this->arglist = func_get_args();
+ }
+ public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction, $chain) {
+ $this->lastCall += 6;
+ $this->arglist = func_get_args();
+ }
+ public function dyEvaluateExpressionFilter($expression, $chain) {
+ $this->lastCall = 7;
+ $this->arglist = func_get_args();
+ return $expression;
+ }
+ public function dyEvaluateStatementsFilter($statement, $chain) {
+ $this->lastCall = 8;
+ $this->arglist = func_get_args();
+ return $statement;
+ }
+ public function dyCreatedOnTemplate($parent, $chain) {
+ $this->lastCall = 9;
+ $this->arglist = func_get_args();
+ return $parent;
+ }
+ public function dyAddParsedObject($object, $chain) {
+ $this->lastCall = 10;
+ $this->arglist = func_get_args();
+ }
+ public function dyAttachBehavior($name,$behavior, $chain) {
+ $this->lastCall = 11;
+ $this->arglist = func_get_args();
+ }
+ public function dyDetachBehavior($name,$behavior, $chain) {
+ $this->lastCall = 12;
+ $this->arglist = func_get_args();
+ }
+ public function dyEnableBehaviors($chain) {
+ $this->lastCall += 13;
+ $this->arglist = func_get_args();
+ }
+ public function dyDisableBehaviors($chain) {
+ $this->lastCall = 14;
+ $this->arglist = func_get_args();
+ }
+ public function dyEnableBehavior($name,$behavior, $chain) {
+ $this->lastCall = 15;
+ $this->arglist = func_get_args();
+ }
+ public function dyDisableBehavior($name,$behavior, $chain) {
+ $this->lastCall = 16;
+ $this->arglist = func_get_args();
+ }
+}
+
+
+class IntraClassObjectExtenderBehavior extends TClassBehavior {
+}
+
+
+class TDynamicBehavior extends TBehavior implements IDynamicMethods {
+ private $_dyMethod = null;
+ public function getLastBehaviorDynamicMethodCalled() {
+ return $this->_dyMethod;
+ }
+ public function __dycall($method, $args) {
+ $this->_dyMethod = $method;
+ if($method == 'dyTestDynamicBehaviorMethod')
+ return $args[0] / $args[1];
+ }
+ public function dyTestIntraEvent($param1, $param2, $chain) {
+ return $chain->dyTestIntraEvent($param1*2*$param2, $param2);
+ }
+ public function TestBehaviorMethod($param1, $param2) {
+ return $param1 * $param2;
+ }
+}
+
+
+class TDynamicClassBehavior extends TClassBehavior implements IDynamicMethods {
+ private $_dyMethod = null;
+ public function getLastBehaviorDynamicMethodCalled() {
+ return $this->_dyMethod;
+ }
+ //Dynamic Calls within class behaviors contain the main object as the first parameter within the args
+ public function __dycall($method, $args) {
+ $this->_dyMethod = $method;
+ $object = array_shift($args);
+ if($method == 'dyTestDynamicClassBehaviorMethod')
+ return $args[0] / $args[1];
+ }
+ public function dyTestIntraEvent($object, $param1, $param2, $chain) {
+ return $chain->dyTestIntraEvent($param1*2*$param2, $param2);
+ }
+ public function TestBehaviorMethod($object, $param1, $param2) {
+ return $param1 * $param2;
+ }
+}
+
+
+
/**
* @package System
@@ -46,26 +417,761 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->component = new NewComponent();
}
+
public function tearDown() {
+ // PHP version 5.3.6 doesn't call the __destruct method when unsetting variables;
+ // Thus any object that listens must be explicitly call unlisten in this version of PHP.
+ if($this->component)
+ $this->component->unlisten();
$this->component = null;
}
+
+
+ public function testGetListeningToGlobalEvents() {
+ $this->assertEquals(true, $this->component->getListeningToGlobalEvents());
+ $this->component->unlisten();
+ $this->assertEquals(false, $this->component->getListeningToGlobalEvents());
+ }
+
+
+ public function testConstructorAutoListen() {
+ // the default object auto installs class behavior hooks
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertTrue($this->component->getListeningToGlobalEvents());
+
+ // this object does not auto install class behavior hooks, thus not changing the global event structure.
+ // Creating a new instance should _not_ influence the fxAttachClassBehavior and fxDetachClassBehavior
+ // count.
+ $component_nolisten = new NewComponentNoListen();
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertEquals(1, $component_nolisten->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $component_nolisten->getEventHandlers('fxDetachClassBehavior')->getCount());
+
+ // tests order of class behaviors when a parent and class have class behavior.
+ // The child should override the parent object-oriented programming style
+ $this->component->attachClassBehavior('Bar', 'BarBehavior', 'NewComponentNoListen');
+ $this->component->attachClassBehavior('FooBar', 'FooBarBehavior', 'NewComponent');
+
+ //create new object with new class behaviors built in, defined in the two lines above
+ $component = new NewComponentNoListen;
+
+ $this->assertEquals(25, $component->moreFunction(2, 2));
+
+ $this->assertEquals(25, $component->Bar->moreFunction(2, 2));
+ $this->assertEquals(8, $component->FooBar->moreFunction(2, 2));
+
+ $component->unlisten();// unwind object and class behaviors
+ $this->component->detachClassBehavior('FooBar', 'NewComponent');
+ $this->component->detachClassBehavior('Bar', 'NewComponentNoListen');
+
+ }
+
+
+ public function testListenAndUnlisten() {
+
+ $component = new NewComponentNoListen();
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ $this->assertEquals(2, $component->listen());
+
+ $this->assertEquals(true, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(2, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ $this->assertEquals(2, $component->unlisten());
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ }
+
+
+ public function testListenAndUnlistenWithDynamicEventCatching() {
+
+ $component = new DynamicCatchingComponent();
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(0, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+
+ // this adds the fxAttachClassBehavior, fxDetachClassBehavior, and __dycall of the component
+ $this->assertEquals(3, $component->listen());
+
+ $this->assertEquals(true, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+
+ $this->assertEquals(3, $component->unlisten());
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(0, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+ }
+
+
+
+ //Test Class behaviors
+ public function testAttachClassBehavior() {
+
+ // ensure that the class is listening
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ //Test that the component is not a FooClassBehavior
+ $this->assertNull($this->component->asa('FooClassBehavior'), "Component is already a FooClassBehavior and should not have this behavior");
+
+ //Add the FooClassBehavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Test that the existing listening component can be a FooClassBehavior
+ $this->assertNotNull($this->component->asa('FooClassBehavior'), "Component is does not have the FooClassBehavior and should have this behavior");
+
+ // test if the function modifies new instances of the object
+ $anothercomponent = new NewComponent();
+
+ //The new component should be a FooClassBehavior
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'), "anothercomponent does not have the FooClassBehavior");
+
+ // test when overwriting an existing class behavior, it should throw an TInvalidOperationException
+ try {
+ $this->component->attachClassBehavior('FooClassBehavior', new BarClassBehavior);
+ $this->fail('TInvalidOperationException not raised when overwriting an existing behavior');
+ } catch(TInvalidOperationException $e) {
+ }
+
+
+ // test when overwriting an existing class behavior, it should throw an TInvalidOperationException
+ try {
+ $this->component->attachClassBehavior('FooBarBehavior', 'FooBarBehavior', 'TComponent');
+ $this->fail('TInvalidOperationException not raised when trying to place a behavior on the root object TComponent');
+ } catch(TInvalidOperationException $e) {
+ }
+
+
+ // test if the function does not modify any existing objects that are not listening
+ // The FooClassBehavior is already a part of the class behaviors thus the new instance gets the behavior.
+ $nolistencomponent = new NewComponentNoListen();
+
+ // test if the function modifies all existing objects that are listening
+ // Adding a behavior to the first object, the second instance should automatically get the class behavior.
+ // This is because the second object is listening to the global events of class behaviors
+ $this->component->attachClassBehavior('BarClassBehavior', new BarClassBehavior);
+ $this->assertNotNull($anothercomponent->asa('BarClassBehavior'), "anothercomponent is does not have the BarClassBehavior");
+
+ // The no listen object should not have the BarClassBehavior because it was added as a class behavior after the object was instanced
+ $this->assertNull($nolistencomponent->asa('BarClassBehavior'), "nolistencomponent has the BarClassBehavior and should not");
+
+ // But the no listen object should have the FooClassBehavior because the class behavior was installed before the object was instanced
+ $this->assertNotNull($nolistencomponent->asa('FooClassBehavior'), "nolistencomponent is does not have the FooClassBehavior");
+
+ //Clear out what was done during this test
+ $anothercomponent->unlisten();
+ $this->component->detachClassBehavior('FooClassBehavior');
+ $this->component->detachClassBehavior('BarClassBehavior');
+
+ // Test attaching of single object behaviors as class-wide behaviors
+ $this->component->attachClassBehavior('BarBehaviorObject', 'BarBehavior');
+ $this->assertTrue($this->component->asa('BarBehaviorObject') instanceof BarBehavior);
+ $this->assertEquals($this->component->BarBehaviorObject->Owner, $this->component);
+ $this->component->detachClassBehavior('BarBehaviorObject');
+ }
+
+
+
+
+
+ public function testDetachClassBehavior() {
+ // ensure that the component is listening
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+
+ $prenolistencomponent = new NewComponentNoListen();
+
+ //Attach a class behavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Create new components that listen and don't listen to global events
+ $anothercomponent = new NewComponent();
+ $postnolistencomponent = new NewComponentNoListen();
+
+ //ensures that all the Components are properly initialized
+ $this->assertEquals(2, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertNotNull($this->component->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+ $this->assertNull($prenolistencomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+ $this->assertNotNull($postnolistencomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+
+
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+ $this->assertNull($this->component->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNull($prenolistencomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNotNull($postnolistencomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+
+
+ //tear down function variables
+ $anothercomponent->unlisten();
+ }
+
+ public function testGetClassHierarchy() {
+ $component = new DynamicCatchingComponent;
+ $this->assertEquals(array('TComponent', 'NewComponent', 'NewComponentNoListen', 'DynamicCatchingComponent'), $component->getClassHierarchy());
+ $this->assertEquals(array('TComponent', 'NewComponent', 'NewComponentNoListen', 'DynamicCatchingComponent'), $component->getClassHierarchy(false));
+ $this->assertEquals(array('tcomponent', 'newcomponent', 'newcomponentnolisten', 'dynamiccatchingcomponent'), $component->getClassHierarchy(true));
+ }
+
+
+ public function testAsA() {
+ $anothercomponent = new NewComponent();
+
+ // ensure the component does not have the FooClassBehavior
+ $this->assertNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+ // add the class behavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Check that the component has only the class behavior assigned
+ $this->assertNotNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ //Check that the component has only the class behavior assigned
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+ // remove the class behavior
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+ // Check the function doesn't have the behavior any more
+ $this->assertNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+
+
+
+ $this->component->attachBehavior('BarBehavior', new BarBehavior);
+
+ //Check that the component has only the object behavior assigned
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('NonExistantBehavior'));
+
+ //Check that the component has the behavior assigned
+ $this->assertNull($anothercomponent->asa('FooBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooBehavior'));
+ $this->assertNull($anothercomponent->asa('BarBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantBehavior'));
+
+ $this->component->detachBehavior('BarBehavior');
+
+ //Check that the component has no object behaviors assigned
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('NonExistantBehavior'));
+
+ //Check that the component has no behavior assigned
+ $this->assertNull($anothercomponent->asa('FooBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooBehavior'));
+ $this->assertNull($anothercomponent->asa('BarBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantBehavior'));
+
+ $anothercomponent->unlisten();
+ }
+
+ public function testIsA() {
+ //This doesn't check the IInstanceCheck functionality, separate function
+
+ $this->assertTrue($this->component->isa('TComponent'));
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertFalse($this->component->isa(new FooBehavior));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ //Ensure there is no BarBehavior
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+
+ $this->component->attachBehavior('FooFooBehavior', new FooFooBehavior);
+
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+
+ $this->component->disableBehaviors();
+ // It still has the behavior
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ // But it is not expressed
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+
+ $this->component->enableBehaviors();
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+
+
+
+ $this->component->attachBehavior('FooBarBehavior', new FooBarBehavior);
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+ $this->component->disableBehavior('FooBarBehavior');
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->enableBehavior('FooBarBehavior');
+ $this->component->disableBehavior('FooFooBehavior');
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+ $this->component->disableBehavior('FooBarBehavior');
+ $this->component->disableBehavior('FooFooBehavior');
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->enableBehavior('FooBarBehavior');
+ $this->component->enableBehavior('FooFooBehavior');
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+
+ $this->component->detachBehavior('FooFooBehavior');
+ $this->component->detachBehavior('FooBarBehavior');
+
+ $this->assertFalse($this->component->isa(new FooBehavior));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa(new FooFooBehavior));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa(new FooBarBehavior));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ }
+
+ public function testIsA_with_IInstanceCheck() {
+
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertFalse($this->component->isa('PreBarBehavior'));
+
+ $this->component->attachBehavior('BarBehavior', $behavior = new BarBehavior);
+
+ $behavior->setInstanceReturn(null);
+
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ // This forces the iso on the BarBehavior to respond to any class with false
+ $behavior->setInstanceReturn(false);
+ $this->assertFalse($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ //This forces the isa on the BarBehavior to respond to any class with true
+ $behavior->setInstanceReturn(true);
+ $this->assertTrue($this->component->isa('FooBehavior'));
+
+
+ }
+
+ public function testAttachDetachBehavior() {
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ try {
+ $this->component->attachBehavior('FooBehavior', new TComponent);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TInvalidDataTypeException $e) {}
+
+ $this->component->attachBehavior('FooBehavior', new FooBehavior);
+
+ $this->assertNotNull($this->component->asa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ try {
+ $this->component->noMethodHere(true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->disableBehavior('FooBehavior'));
+
+ //BarBehavior is not a behavior at this time
+ $this->assertNull($this->component->disableBehavior('BarBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->enableBehavior('FooBehavior'));
+
+ //BarBehavior is not a behavior at this time
+ $this->assertNull($this->component->enableBehavior('BarBehavior'));
+
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->component->detachBehavior('FooBehavior');
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ }
+
+ public function testAttachDetachBehaviors() {
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior, 'PreBarBehavior' => new PreBarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('BarBehavior'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->detachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+
+
+
+ // testing if we can detachBehaviors just by the name of the behavior instead of an array of the behavior
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('BarBehavior'));
+
+ $this->component->detachBehaviors(array('FooFooBehavior', 'BarBehavior'));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+ }
+
+
+ public function testClearBehaviors() {
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior, 'PreBarBehavior' => new PreBarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->clearBehaviors();
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+ }
+
+ public function testEnableDisableBehavior() {
+
+ $this->assertNull($this->component->enableBehavior('FooBehavior'));
+ $this->assertNull($this->component->disableBehavior('FooBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->component->attachBehavior('FooBehavior', new FooBehavior);
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->assertTrue($this->component->disableBehavior('FooBehavior'));
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->enableBehavior('FooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+
+
+ $this->assertNull($this->component->enableBehavior('BarClassBehavior'));
+ $this->assertNull($this->component->disableBehavior('BarClassBehavior'));
+
+ try {
+ $this->component->moreFunction(true, true);
+ $this->fail('TApplicationException not raised trying to execute an undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->component->attachClassBehavior('BarClassBehavior', new BarClassBehavior);
+
+ $this->assertFalse($this->component->enableBehavior('BarClassBehavior'));
+ $this->assertFalse($this->component->disableBehavior('BarClassBehavior'));
+
+ try {
+ $this->assertTrue($this->component->moreFunction(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->component->detachClassBehavior('BarClassBehavior');
+ }
+
+
+ public function testBehaviorFunctionCalls() {
+
+ $this->component->attachBehavior('FooBarBehavior', $behavior = new FooBarBehavior);
+ $this->component->attachClassBehavior('FooClassBehavior', $classbehavior = new FooClassBehavior);
+
+ // Test the Class Methods
+ $this->assertEquals(12, $this->component->faaEverMore(3, 4));
+
+ // Check that the called object is shifted in front of the array of a class behavior call
+ $this->assertEquals($this->component, $this->component->getLastClassObject());
+
+
+ //Test the FooBarBehavior
+ $this->assertEquals(27, $this->component->moreFunction(3, 3));
+
+ $this->assertTrue($this->component->disableBehavior('FooBarBehavior'));
+ try {
+ $this->assertNull($this->component->moreFunction(3, 4));
+ $this->fail('TApplicationException not raised trying to execute a disabled behavior');
+ } catch(TApplicationException $e) {}
+ $this->assertTrue($this->component->enableBehavior('FooBarBehavior'));
+
+ // Test the global event space, this should work and return false because no function implements these methods
+ $this->assertNull($this->component->fxSomeUndefinedGlobalEvent());
+ $this->assertNull($this->component->dySomeUndefinedIntraObjectEvent());
+
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+
+
+ // test object instance behaviors implemented through class-wide behaviors
+ $this->component->attachClassBehavior('FooFooBehaviorAsClass', 'FooFooBehavior');
+
+ $component = new NewComponent;
+
+ $this->assertEquals(5, $this->component->faafaaEverMore(3, 4));
+ $this->assertEquals(10, $component->faafaaEverMore(6, 8));
+
+ $this->component->detachClassBehavior('FooFooBehaviorAsClass');
+ $component->unlisten();
+ $component = null;
+
+ try {
+ $this->component->faafaaEverMore(3, 4);
+ $this->fail('TApplicationException not raised trying to execute a disabled behavior');
+ } catch(TApplicationException $e) {}
+
+
+
+ // make a call to an unpatched fx and dy call so that it's passed through to the __dycall function
+ $dynamicComponent = new DynamicCallComponent;
+
+ $this->assertNull($dynamicComponent->fxUndefinedEvent());
+ $this->assertNull($dynamicComponent->dyUndefinedEvent());
+
+ //This tests the dynamic __dycall function
+ $this->assertEquals(1024, $dynamicComponent->dyPowerFunction(2, 10));
+ $this->assertEquals(5, $dynamicComponent->dyDivisionFunction(10, 2));
+
+ $this->assertEquals(2048, $dynamicComponent->fxPowerFunction(2, 10));
+ $this->assertEquals(10, $dynamicComponent->fxDivisionFunction(10, 2));
+
+ $dynamicComponent->unlisten();
+
+ }
+
public function testHasProperty() {
$this->assertTrue($this->component->hasProperty('Text'), "Component hasn't property Text");
$this->assertTrue($this->component->hasProperty('text'), "Component hasn't property text");
- $this->assertFalse($this->component->hasProperty('Caption'), "Component as property Caption");
+ $this->assertFalse($this->component->hasProperty('Caption'), "Component has property Caption");
+
+ $this->assertTrue($this->component->hasProperty('ColorAttribute'), "Component hasn't property JsColorAttribute");
+ $this->assertTrue($this->component->hasProperty('colorattribute'), "Component hasn't property JsColorAttribute");
+ $this->assertFalse($this->component->canGetProperty('PastelAttribute'), "Component has property JsPastelAttribute");
+
+ $this->assertTrue($this->component->hasProperty('JSColorAttribute'), "Component hasn't property JsColorAttribute");
+ $this->assertTrue($this->component->hasProperty('jscolorattribute'), "Component hasn't property JsColorAttribute");
+ $this->assertFalse($this->component->hasProperty('jsPastelAttribute'), "Component has property JsPastelAttribute");
+
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
+
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+
}
public function testCanGetProperty() {
$this->assertTrue($this->component->canGetProperty('Text'));
$this->assertTrue($this->component->canGetProperty('text'));
$this->assertFalse($this->component->canGetProperty('Caption'));
+
+ $this->assertTrue($this->component->canGetProperty('ColorAttribute'));
+ $this->assertTrue($this->component->canGetProperty('colorattribute'));
+ $this->assertFalse($this->component->canGetProperty('PastelAttribute'));
+
+ $this->assertTrue($this->component->canGetProperty('JSColorAttribute'));
+ $this->assertTrue($this->component->canGetProperty('jscolorattribute'));
+ $this->assertFalse($this->component->canGetProperty('jsPastelAttribute'));
+
+
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
+
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
}
public function testCanSetProperty() {
$this->assertTrue($this->component->canSetProperty('Text'));
$this->assertTrue($this->component->canSetProperty('text'));
$this->assertFalse($this->component->canSetProperty('Caption'));
+
+ $this->assertTrue($this->component->canSetProperty('ColorAttribute'));
+ $this->assertTrue($this->component->canSetProperty('colorattribute'));
+ $this->assertFalse($this->component->canSetProperty('PastelAttribute'));
+
+ $this->assertTrue($this->component->canSetProperty('JSColorAttribute'));
+ $this->assertTrue($this->component->canSetProperty('jscolorattribute'));
+ $this->assertFalse($this->component->canSetProperty('jsPastelAttribute'));
+
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
}
public function testGetProperty() {
@@ -75,6 +1181,67 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->fail('exception not raised when getting undefined property');
} catch(TInvalidOperationException $e) {
}
+
+ $this->assertTrue($this->component->OnMyEvent instanceof TPriorityList);
+ try {
+ $value2=$this->component->onUndefinedEvent;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ //Without the function parenthesis, the function is _not_ called but the __get
+ // method is called and the global events (list) are accessed
+ $this->assertTrue($this->component->fxAttachClassBehavior instanceof TPriorityList);
+ $this->assertTrue($this->component->fxDetachClassBehavior instanceof TPriorityList);
+
+ // even undefined global events have a list as every object is able to access every event
+ $this->assertTrue($this->component->fxUndefinedEvent instanceof TPriorityList);
+
+
+ // Test the behaviors within the __get function
+ $this->component->enableBehaviors();
+
+ try {
+ $value2=$this->component->Excitement;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->attachBehavior('BehaviorTestBehavior', $behavior = new BehaviorTestBehavior);
+ $this->assertEquals('faa', $this->component->Excitement);
+
+ $this->component->disableBehaviors();
+
+ try {
+ $this->assertEquals('faa', $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->enableBehaviors();
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+
+ $this->assertEquals($behavior, $this->component->BehaviorTestBehavior);
+ try {
+ $behavior = $this->component->BehaviorTestBehavior2;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->assertEquals('faa', $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+
+ // behaviors allow on and fx events to be passed through.
+ $this->assertTrue($this->component->onBehaviorEvent instanceof TPriorityList);
+
}
public function testSetProperty() {
@@ -87,6 +1254,203 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->fail('exception not raised when setting undefined property');
} catch(TInvalidOperationException $e) {
}
+
+ // Test get only properties is a set function
+ try {
+ $this->component->ReadOnlyProperty = 'setting read only';
+ $this->fail('a property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->component->ReadOnlyJsProperty = 'jssetting read only';
+ $this->fail('a js property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->component->JsReadOnlyJsProperty = 'jssetting read only';
+ $this->fail('a js property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->assertEquals(0, $this->component->getEventHandlers('onMyEvent')->getCount());
+ $this->component->onMyEvent = array($this->component,'myEventHandler');
+ $this->assertEquals(1, $this->component->getEventHandlers('onMyEvent')->getCount());
+ $this->component->onMyEvent[] = array($this->component,'Object.myEventHandler');
+ $this->assertEquals(2, $this->component->getEventHandlers('onMyEvent')->getCount());
+
+ $this->component->getEventHandlers('onMyEvent')->clear();
+
+ // Test the behaviors within the __get function
+ $this->component->enableBehaviors();
+
+ try {
+ $this->component->Excitement = 'laa';
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->attachBehavior('BehaviorTestBehavior', $behavior1 = new BehaviorTestBehavior);
+ $this->component->Excitement = 'laa';
+ $this->assertEquals('laa', $this->component->Excitement);
+ $this->assertEquals('sol', $this->component->Excitement = 'sol');
+
+
+ $this->component->disableBehaviors();
+
+ try {
+ $this->component->Excitement = false;
+ $this->assertEquals(false, $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->enableBehaviors();
+ $this->component->Excitement = 'faa';
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+
+ try {
+ $this->component->Excitement = false;
+ $this->assertEquals(false, $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->component->Excitement = 'sol';
+ $this->assertEquals('sol', $this->component->Excitement);
+
+
+ $this->component->attachBehavior('BehaviorTestBehavior2', $behavior2 = new BehaviorTestBehavior);
+
+ $this->assertEquals('sol', $this->component->Excitement);
+ $this->assertEquals('faa', $behavior2->Excitement);
+
+ // this sets Excitement for both because they are not uniquely named
+ $this->component->Excitement = 'befaad';
+
+ $this->assertEquals('befaad', $this->component->Excitement);
+ $this->assertEquals('befaad', $behavior1->Excitement);
+ $this->assertEquals('befaad', $behavior2->Excitement);
+
+
+ $this->component->detachBehavior('BehaviorTestBehavior2');
+
+ // behaviors allow on and fx events to be passed through.
+ $this->assertTrue($this->component->BehaviorTestBehavior->onBehaviorEvent instanceof TPriorityList);
+
+ $this->assertEquals(0, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+ $this->component->onBehaviorEvent = array($this->component,'myEventHandler');
+ $this->assertEquals(1, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+ $this->component->onBehaviorEvent[] = array($this->component,'Object.myEventHandler');
+ $this->assertEquals(2, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+
+ $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->clear();
+ }
+
+
+ public function testIsSetFunction() {
+ $this->assertTrue(isset($this->component->fxAttachClassBehavior));
+ $this->component->unlisten();
+
+ $this->assertFalse(isset($this->component->onMyEvent));
+ $this->assertFalse(isset($this->component->undefinedEvent));
+ $this->assertFalse(isset($this->component->fxAttachClassBehavior));
+
+ $this->assertFalse(isset($this->component->BehaviorTestBehavior));
+ $this->assertFalse(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->assertTrue(isset($this->component->BehaviorTestBehavior));
+ $this->assertFalse(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachEventHandler('onMyEvent','foo');
+ $this->assertTrue(isset($this->component->onMyEvent));
+
+ $this->assertTrue(isset($this->component->Excitement));
+ $this->component->Excitement = null;
+ $this->assertFalse(isset($this->component->Excitement));
+ $this->assertFalse(isset($this->component->UndefinedBehaviorProperty));
+
+
+ }
+
+
+ public function testUnsetFunction() {
+
+ $this->assertEquals('default', $this->component->getText());
+ unset($this->component->Text);
+ $this->assertNull($this->component->getText());
+
+ unset($this->component->UndefinedProperty);
+
+ // object events
+ $this->assertEquals(0, $this->component->onMyEvent->Count);
+ $this->component->attachEventHandler('onMyEvent','foo');
+ $this->assertEquals(1, $this->component->onMyEvent->Count);
+ unset($this->component->onMyEvent);
+ $this->assertEquals(0, $this->component->onMyEvent->Count);
+
+ //global events
+ $this->assertEquals(1, $this->component->fxAttachClassBehavior->Count);
+ $component = new NewComponent();
+ $this->assertEquals(2, $this->component->fxAttachClassBehavior->Count);
+ unset($this->component->fxAttachClassBehavior);
+ // retain the other object event
+ $this->assertEquals(1, $this->component->fxAttachClassBehavior->Count);
+ $component->unlisten();
+
+ try {
+ unset($this->component->Object);
+ $this->fail('TInvalidOperationException not raised when unsetting get only property');
+ } catch(TInvalidOperationException $e) {}
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->asa('BehaviorTestBehavior') instanceof BehaviorTestBehavior);
+ $this->assertFalse($this->component->asa('BehaviorTestBehavior2') instanceof BehaviorTestBehavior);
+
+ $this->assertEquals('faa', $this->component->Excitement);
+ unset($this->component->Excitement);
+ $this->assertNull($this->component->Excitement);
+ $this->component->Excitement = 'sol';
+ $this->assertEquals('sol', $this->component->Excitement);
+
+ // Test the disabling of unset within behaviors
+ $this->component->disableBehaviors();
+ unset($this->component->Excitement);
+ $this->component->enableBehaviors();
+ // This should still be 'sol' because the unset happened inside behaviors being disabled
+ $this->assertEquals('sol', $this->component->Excitement);
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ unset($this->component->Excitement);
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertEquals('sol', $this->component->Excitement);
+
+ unset($this->component->Excitement);
+ $this->assertNull($this->component->Excitement);
+
+ try {
+ unset($this->component->ReadOnly);
+ $this->fail('TInvalidOperationException not raised when unsetting get only property');
+ } catch(TInvalidOperationException $e) {}
+
+ $this->component->onBehaviorEvent = 'foo';
+ $this->assertEquals(1, count($this->component->onBehaviorEvent));
+ $this->assertEquals(1, count($this->component->BehaviorTestBehavior->onBehaviorEvent));
+ unset($this->component->onBehaviorEvent);
+ $this->assertEquals(0, count($this->component->onBehaviorEvent));
+ $this->assertEquals(0, count($this->component->BehaviorTestBehavior->onBehaviorEvent));
+
+ // Remove behavior via unset
+ unset($this->component->BehaviorTestBehavior);
+ $this->assertFalse($this->component->asa('BehaviorTestBehavior') instanceof BehaviorTestBehavior);
+
}
public function testGetSubProperty() {
@@ -102,46 +1466,225 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->assertTrue($this->component->hasEvent('OnMyEvent'));
$this->assertTrue($this->component->hasEvent('onmyevent'));
$this->assertFalse($this->component->hasEvent('onYourEvent'));
+
+ // fx won't throw an error if any of these fx function are called on an object.
+ // It is a special prefix event designation that every object responds to all events.
+ $this->assertTrue($this->component->hasEvent('fxAttachClassBehavior'));
+ $this->assertTrue($this->component->hasEvent('fxattachclassbehavior'));
+
+ $this->assertTrue($this->component->hasEvent('fxNonExistantGlobalEvent'));
+ $this->assertTrue($this->component->hasEvent('fxnonexistantglobalevent'));
+
+ $this->assertTrue($this->component->hasEvent('dyNonExistantLocalEvent'));
+ $this->assertTrue($this->component->hasEvent('fxnonexistantlocalevent'));
+
+
+ //Test behavior events
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertTrue($this->component->BehaviorTestBehavior->hasEvent('onBehaviorEvent'));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
}
public function testHasEventHandler() {
$this->assertFalse($this->component->hasEventHandler('OnMyEvent'));
$this->component->attachEventHandler('OnMyEvent','foo');
$this->assertTrue($this->component->hasEventHandler('OnMyEvent'));
+
+ $this->assertFalse($this->component->hasEventHandler('fxNonExistantGlobalEvent'));
+ $this->component->attachEventHandler('fxNonExistantGlobalEvent','foo');
+ $this->assertTrue($this->component->hasEventHandler('fxNonExistantGlobalEvent'));
+
+ //Test behavior events
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->assertFalse($this->component->BehaviorTestBehavior->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue($this->component->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertTrue($this->component->hasEventHandler('onBehaviorEvent'));
}
public function testGetEventHandlers() {
$list=$this->component->getEventHandlers('OnMyEvent');
- $this->assertTrue(($list instanceof TList) && ($list->getCount()===0));
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
$this->component->attachEventHandler('OnMyEvent','foo');
- $this->assertTrue(($list instanceof TList) && ($list->getCount()===1));
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
try {
$list=$this->component->getEventHandlers('YourEvent');
$this->fail('exception not raised when getting event handlers for undefined event');
} catch(TInvalidOperationException $e) {
}
+
+ $list=$this->component->getEventHandlers('fxRandomEvent');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
+ $this->component->attachEventHandler('fxRandomEvent','foo');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
+ try {
+ $list=$this->component->getEventHandlers('fxSomeUndefinedGlobalEvent');
+ } catch(TInvalidOperationException $e) {
+ $this->fail('exception raised when getting event handlers for universal global event');
+ }
+
+
+
+ //Test behavior events
+ try {
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ try {
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue(($this->component->getEventHandlers('onBehaviorEvent') instanceof TPriorityList) && ($list->getCount()===1));
+
}
public function testAttachEventHandler() {
+
$this->component->attachEventHandler('OnMyEvent','foo');
- $this->assertTrue($this->component->getEventHandlers('OnMyEvent')->getCount()===1);
+ $this->assertEquals(1, $this->component->getEventHandlers('OnMyEvent')->getCount());
try {
$this->component->attachEventHandler('YourEvent','foo');
$this->fail('exception not raised when attaching event handlers for undefined event');
} catch(TInvalidOperationException $e) {
}
- /*$this->component->MyEvent[]='foo2';
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===2);
- $this->component->getEventHandlers('MyEvent')->add('foo3');
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===3);
- $this->component->MyEvent[0]='foo4';
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===3);
- $this->component->getEventHandlers('MyEvent')->insert(0,'foo5');
- $this->assertTrue($this->component->MyEvent->Count===4 && $this->component->MyEvent[0]==='foo5');
- $this->component->MyEvent='foo6';
- $this->assertTrue($this->component->MyEvent->Count===5 && $this->component->MyEvent[4]==='foo6');*/
+
+ //Testing the priorities of attaching events
+ $this->component->attachEventHandler('OnMyEvent','foopre', 5);
+ $this->component->attachEventHandler('OnMyEvent','foopost', 15);
+ $this->component->attachEventHandler('OnMyEvent','foobar', 10);
+ $this->assertEquals(4, $this->component->getEventHandlers('OnMyEvent')->getCount());
+ $list = $this->component->getEventHandlers('OnMyEvent');
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foo', $list[1]);
+ $this->assertEquals('foobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+
+ //Test attaching behavior events
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+
+ //Testing the priorities of attaching behavior events
+ $this->component->attachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->component->attachEventHandler('onBehaviorEvent','foopost', 15);
+ $this->component->attachEventHandler('onBehaviorEvent','foobar', 10);
+ $this->component->attachEventHandler('onBehaviorEvent','foobarfoobar', 10);
+ $this->assertEquals(5, $this->component->getEventHandlers('onBehaviorEvent')->getCount());
+ $list = $this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foo', $list[1]);
+ $this->assertEquals('foobar', $list[2]);
+ $this->assertEquals('foobarfoobar', $list[3]);
+ $this->assertEquals('foopost', $list[4]);
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','bar');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+
}
+ public function testDetachEventHandler() {
+
+ $this->component->attachEventHandler('OnMyEvent','foo');
+ $this->assertEquals(1, $this->component->getEventHandlers('OnMyEvent')->getCount());
+
+ $this->component->attachEventHandler('OnMyEvent','foopre', 5);
+ $this->component->attachEventHandler('OnMyEvent','foopost', 15);
+ $this->component->attachEventHandler('OnMyEvent','foobar', 10);
+ $this->component->attachEventHandler('OnMyEvent','foobarfoobar', 10);
+
+
+
+ $this->component->detachEventHandler('OnMyEvent','foo');
+ $list = $this->component->getEventHandlers('OnMyEvent');
+ $this->assertEquals(4, $list->getCount());
+
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foobar', $list[1]);
+ $this->assertEquals('foobarfoobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+ $this->component->detachEventHandler('OnMyEvent','foopre', null);
+ $this->assertEquals(4, $list->getCount());
+
+ $this->component->detachEventHandler('OnMyEvent','foopre', 5);
+ $this->assertEquals(3, $list->getCount());
+
+
+ // Now do detaching of behavior on events
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertEquals(1, $this->component->getEventHandlers('onBehaviorEvent')->getCount());
+
+ $this->component->attachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->component->attachEventHandler('onBehaviorEvent','foopost', 15);
+ $this->component->attachEventHandler('onBehaviorEvent','foobar', 10);
+ $this->component->attachEventHandler('onBehaviorEvent','foobarfoobar', 10);
+
+
+
+ $this->component->detachEventHandler('onBehaviorEvent','foo');
+ $list = $this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertEquals(4, $list->getCount());
+
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foobar', $list[1]);
+ $this->assertEquals('foobarfoobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+ $this->component->detachEventHandler('onBehaviorEvent','foopre', null);
+ $this->assertEquals(4, $list->getCount());
+
+ $this->component->detachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->assertEquals(3, $list->getCount());
+ }
+
+
+
+
public function testRaiseEvent() {
$this->component->attachEventHandler('OnMyEvent',array($this->component,'myEventHandler'));
$this->assertFalse($this->component->isEventHandled());
@@ -151,7 +1694,210 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->assertFalse($this->component->Object->isEventHandled());
$this->component->raiseEvent('OnMyEvent',$this,null);
$this->assertTrue($this->component->Object->isEventHandled());
+
+ $this->component->resetEventHandled();
+ $this->component->Object->resetEventHandled();
+
+
+ // Test a behavior on event
+ $this->component->attachBehavior('test', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'myEventHandler'));
+ $this->assertFalse($this->component->isEventHandled());
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->assertTrue($this->component->isEventHandled());
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'Object.myEventHandler'));
+ $this->assertFalse($this->component->Object->isEventHandled());
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->assertTrue($this->component->Object->isEventHandled());
+
+ //test behavior enabled/disabled events
+ $this->component->disableBehavior('test');
+
+ $this->component->resetEventHandled();
+ $this->component->Object->resetEventHandled();
+
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'myEventHandler'));
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {}
+ $this->assertFalse($this->component->isEventHandled());
+ try {
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {}
+ $this->assertFalse($this->component->isEventHandled());
+
+ $this->component->enableBehavior('test');
+
+
+
+ //Test the return types of this function
+
+ $this->assertFalse($this->component->isEventHandled());
+ $this->assertFalse($this->component->Object->isEventHandled());
+ $this->assertEquals(array(), $this->component->onBehaviorEvent($this,$this->component));
+ $this->assertTrue($this->component->isEventHandled());
+ $this->assertTrue($this->component->Object->isEventHandled());
+
+ // This accumulates all the responses from each of the events
+ $arr=$this->component->onBehaviorEvent($this, $this->component, TEventResults::EVENT_RESULT_ALL);
+ $this->assertEquals($this, $arr[0]['sender']);
+ $this->assertEquals($this->component, $arr[0]['param']);
+ $this->assertTrue(null === $arr[0]['response']);
+
+ $this->assertEquals($this, $arr[1]['sender']);
+ $this->assertEquals($this->component, $arr[1]['param']);
+ $this->assertTrue(null === $arr[1]['response']);
+
+ $this->assertEquals(2, count($arr));
+
+ // This tests without the default filtering-out of null
+ $arr=$this->component->onBehaviorEvent($this, $this->component, false);
+ $this->assertEquals(array(null, null), $arr);
+
+
+ unset($this->component->onBehaviorEvent);
+ $this->assertEquals(0, $this->component->onBehaviorEvent->Count);
+
+ $this->component->onBehaviorEvent = array($this, 'returnValue4');
+ $this->component->onBehaviorEvent = array($this, 'returnValue1');
+
+ // Test the per event post processing function
+ $arr=$this->component->onBehaviorEvent($this, $this->component, array($this, 'postEventFunction'));
+ $this->assertEquals(array(exp(4), exp(1)), $arr);
+ $arr=$this->component->onBehaviorEvent($this, $this->component, array($this, 'postEventFunction2'));
+ $this->assertEquals(array(sin(4), sin(1)), $arr);
+
+
+ //Testing Feed-forward functionality
+ unset($this->component->onBehaviorEvent);
+
+ $this->component->onBehaviorEvent = array($this, 'ffValue4');
+ $this->component->onBehaviorEvent = array($this, 'ffValue2');
+ $arr=$this->component->onBehaviorEvent($this, 5, TEventResults::EVENT_RESULT_FEED_FORWARD);
+ $this->assertEquals(array(20, 40), $arr);
+
+
+ unset($this->component->onBehaviorEvent);
+
+ //Order of these events affects the response order in feed forward
+ $this->component->onBehaviorEvent = array($this, 'ffValue2');
+ $this->component->onBehaviorEvent = array($this, 'ffValue4');
+ $arr=$this->component->onBehaviorEvent($this, 5, TEventResults::EVENT_RESULT_FEED_FORWARD);
+ $this->assertEquals(array(10, 40), $arr);
+ }
+
+ public function returnValue1(){return 1;}
+ public function returnValue4(){return 4;}
+ public function postEventFunction($sender, $param, $caller, $response){return exp($response);}
+ public function postEventFunction2($sender, $param, $caller, $response){return sin($response);}
+ public function ffValue2($sender, $param){return $param*2;}
+ public function ffValue4($sender, $param){return $param*4;}
+
+
+ public function testGlobalEventListenerInRaiseEvent() {
+ //TODO Test the Global Event Listener
}
+
+
+ public function testIDynamicMethodsOnBehavior() {
+
+ //Add Behavior with dynamic call
+ $this->component->attachBehavior('TDynamicBehavior', new TDynamicBehavior);
+
+ //Check that the behavior is working as it should
+ $this->assertTrue($this->component->isa('TDynamicBehavior'));
+ $this->assertNull($this->component->getLastBehaviorDynamicMethodCalled());
+
+ // call basic behavior implemented method from object (containing behavior)
+ $this->assertEquals(42, $this->component->TestBehaviorMethod(6, 7));
+
+ //Test out undefined behavior/host object method
+ try {
+ $this->component->objectAndBehaviorUndefinedMethod();
+ $this->fail('exception not raised when evaluating an undefined method by the object and behavior');
+ } catch(TApplicationException $e) {
+ }
+
+ // calling undefined dynamic method, caught by the __dycall method in the behavior and implemented
+ // this behavior catches undefined dynamic event and divides param1 by param 2
+ $this->assertEquals(22, $this->component->dyTestDynamicBehaviorMethod(242, 11));
+ $this->assertEquals('dyTestDynamicBehaviorMethod', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ // calling undefined dynamic method, caught by the __dycall in the behavior and ignored
+ $this->assertNull($this->component->dyUndefinedIntraEvent(242, 11));
+ $this->assertEquals('dyUndefinedIntraEvent', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ //call behavior defined dynamic event
+ // param1 * 2 * param2
+ $this->assertEquals(2420, $this->component->dyTestIntraEvent(121, 10));
+
+ $this->component->detachBehavior('TDynamicBehavior');
+ $this->assertFalse($this->component->isa('TDynamicBehavior'));
+
+
+
+ //Add Class Behavior with dynamic call
+ $this->component->attachBehavior('TDynamicClassBehavior', new TDynamicClassBehavior);
+
+ //Check that the behavior is working as it should
+ $this->assertTrue($this->component->isa('TDynamicClassBehavior'));
+ $this->assertNull($this->component->getLastBehaviorDynamicMethodCalled());
+
+ // call basic behavior implemented method from object (containing behavior)
+ $this->assertEquals(42, $this->component->TestBehaviorMethod(6, 7));
+
+ //Test out undefined behavior/host object method
+ try {
+ $this->component->objectAndBehaviorUndefinedMethod();
+ $this->fail('exception not raised when evaluating an undefined method by the object and behavior');
+ } catch(TApplicationException $e) {
+ }
+
+ // calling undefined dynamic method, caught by the __dycall method in the behavior and implemented
+ // this behavior catches undefined dynamic event and divides param1 by param 2
+ $this->assertEquals(22, $this->component->dyTestDynamicClassBehaviorMethod(242, 11));
+ $this->assertEquals('dyTestDynamicClassBehaviorMethod', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ // calling undefined dynamic method, caught by the __dycall in the behavior and ignored
+ $this->assertNull($this->component->dyUndefinedIntraEvent(242, 11));
+ $this->assertEquals('dyUndefinedIntraEvent', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ //call behavior defined dynamic event
+ // param1 * 2 * param2
+ $this->assertEquals(2420, $this->component->dyTestIntraEvent(121, 10));
+
+ $this->component->detachBehavior('TDynamicClassBehavior');
+ $this->assertFalse($this->component->isa('TDynamicClassBehavior'));
+
+
+ }
+
+ // This also tests the priority of the common global raiseEvent events
+ public function testIDynamicMethodsOnBehaviorGlobalEvents() {
+ $component = new GlobalRaiseComponent();
+
+ // common function has a default priority of 10
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'commonRaiseEventListener'));
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'postglobalRaiseEventListener'), 1);
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'preglobalRaiseEventListener'), -1);
+
+ $this->assertEquals(5, $this->component->fxGlobalListener->getCount());
+ $this->assertEquals(1, $this->component->fxPrimaryGlobalEvent->getCount());
+ $this->assertEquals(1, $this->component->fxPrimaryGlobalEvent->getCount(), 'fxPrimaryGlobalEvent is not installed on test object');
+
+ // call the global event on a different object than the test object
+ $res = $this->component->raiseEvent('fxPrimaryGlobalEvent', $this, null, TEventResults::EVENT_RESULT_ALL);
+
+ $this->assertEquals(6, count($res));
+ $this->assertEquals(array('pregl', 'primary', 'postgl', 'fxGL', 'fxcall', 'com'), $component->getCallOrders());
+
+ $component->unlisten();
+ }
+
+
+
public function testEvaluateExpression() {
$expression="1+2";
@@ -163,6 +1909,9 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
}
}
+
+
+
public function testEvaluateStatements() {
$statements='$a="test string"; echo $a;';
$this->assertEquals('test string',$this->component->evaluateStatements($statements));
@@ -173,6 +1922,169 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
} catch(Exception $e) {
}
}
+
+
+ public function testDynamicFunctionCall() {
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->component->attachBehavior('dy1', new dy1TextReplace);
+ $this->assertFalse($this->component->dy1->isCalled());
+ $this->assertEquals(' aa bb cc __ __ ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy1->isCalled());
+
+ $this->component->attachBehavior('dy2', new dy2TextReplace);
+ $this->assertFalse($this->component->dy2->isCalled());
+ $this->assertEquals(' aa bb cc __ __ || || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy2->isCalled());
+
+ $this->component->attachBehavior('dy3', new dy3TextReplace);
+ $this->assertFalse($this->component->dy3->isCalled());
+ $this->assertEquals(' aa bb cc __ __ || || ?? ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy3->isCalled());
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyUndefinedEvent(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->assertEquals(0.25, $this->component->dyPowerFunction(2,2));
+
+
+ $this->component->detachBehavior('dy1');
+ $this->component->detachBehavior('dy2');
+ $this->component->detachBehavior('dy3');
+
+ //test class behaviors of dynamic events and the argument list order
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->component->attachBehavior('dy1', new dy1ClassTextReplace);
+ $this->assertFalse($this->component->dy1->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy1->isCalled());
+
+ $this->component->attachBehavior('dy2', new dy2ClassTextReplace);
+ $this->assertFalse($this->component->dy2->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ ++ !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy2->isCalled());
+
+ $this->component->attachBehavior('dy3', new dy3ClassTextReplace);
+ $this->assertFalse($this->component->dy3->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ ++ !! ^_^ ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy3->isCalled());
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyUndefinedEvent(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->assertEquals(0.25, $this->component->dyPowerFunction(2,2));
+
+
+ }
+
+
+
+
+ public function testDynamicIntraObjectEvents() {
+
+ $this->component->attachBehavior('IntraEvents', new IntraObjectExtenderBehavior);
+
+ $this->assertNull($this->component->IntraEvents->LastCall);
+
+ //unlisten first, this object listens upon instantiation.
+ $this->component->unlisten();
+ $this->assertEquals(2, $this->component->IntraEvents->LastCall);
+
+ // ensures that IntraEvents nulls the last call variable when calling this getter
+ $this->assertNull($this->component->IntraEvents->LastCall);
+
+ //listen next to undo the unlisten
+ $this->component->listen();
+ $this->assertEquals(1, $this->component->IntraEvents->LastCall);
+
+
+ $this->assertEquals(3, $this->component->evaluateExpression('1+2'));
+ $this->assertEquals(7, $this->component->IntraEvents->LastCall);
+
+ $statements='$a="test string"; echo $a;';
+ $this->assertEquals('test string', $this->component->evaluateStatements($statements));
+ $this->assertEquals(8, $this->component->IntraEvents->LastCall);
+
+ $component2 = new NewComponentNoListen();
+ $this->assertNull($this->component->createdOnTemplate($component2));
+ $this->assertEquals(9, $this->component->IntraEvents->LastCall);
+
+ $this->assertNull($this->component->addParsedObject($component2));
+ $this->assertEquals(10, $this->component->IntraEvents->LastCall);
+
+
+
+ $behavior = new BarBehavior;
+ $this->assertEquals($behavior, $this->component->attachBehavior('BarBehavior', $behavior));
+ $this->assertEquals(11, $this->component->IntraEvents->LastCall);
+
+ $this->assertNull($this->component->disableBehaviors());
+ $this->assertNull($this->component->enableBehaviors());
+ $this->assertEquals(27, $this->component->IntraEvents->LastCall);
+
+ $this->assertTrue($this->component->disableBehavior('BarBehavior'));
+ $this->assertEquals(16, $this->component->IntraEvents->LastCall);
+
+ $this->assertTrue($this->component->enableBehavior('BarBehavior'));
+ $this->assertEquals(15, $this->component->IntraEvents->LastCall);
+
+ $this->assertEquals($behavior, $this->component->detachBehavior('BarBehavior'));
+ $this->assertEquals(12, $this->component->IntraEvents->LastCall);
+
+
+ $this->component->attachEventHandler('OnMyEvent',array($this->component,'myEventHandler'));
+ $this->component->raiseEvent('OnMyEvent',$this,null);
+
+ //3 + 4 + 5 + 6 = 18 (the behavior adds these together when each raiseEvent dynamic intra event is called)
+ $this->assertEquals(18, $this->component->IntraEvents->LastCall);
+ }
+
+
+
+ public function testJavascriptGetterSetter() {
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ $this->component->ColorAttribute = "('#556677', '#abcdef', 503987)";
+ $this->assertEquals("('#556677', '#abcdef', 503987)", $this->component->ColorAttribute);
+
+ $this->assertTrue(isset($this->component->ColorAttribute));
+ $this->assertTrue(isset($this->component->JsColorAttribute));
+
+ $this->component->ColorAttribute = "new Array(1, 2, 3, 4, 5)";
+ $this->assertEquals("new Array(1, 2, 3, 4, 5)", $this->component->JsColorAttribute);
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+ $this->assertEquals("['#112233', '#fedcba', 22009837]", $this->component->ColorAttribute);
+ }
+
+
+ public function testJavascriptIssetUnset() {
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+ $this->assertEquals("['#112233', '#fedcba', 22009837]", $this->component->ColorAttribute);
+
+ unset($this->component->ColorAttribute);
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+
+ $this->assertTrue(isset($this->component->ColorAttribute));
+ $this->assertTrue(isset($this->component->JsColorAttribute));
+
+ unset($this->component->JsColorAttribute);
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ }
+
+
+
}
?> \ No newline at end of file