diff --git a/build/commands/AutoloadCommand.php b/build/commands/AutoloadCommand.php index 43d3a0b33c..e4d07afa75 100644 --- a/build/commands/AutoloadCommand.php +++ b/build/commands/AutoloadCommand.php @@ -56,6 +56,7 @@ public function run($args) '/i18n/data', '/utils/mimeTypes.php', '/test', + '/zii', ), ); $files=CFileHelper::findFiles(YII_PATH,$options); diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 5f232ee5f8..a14a4165d4 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -628,6 +628,7 @@ public static function t($category,$message,$params=array(),$source=null,$langua 'CUrlManager' => '/web/CUrlManager.php', 'CWebApplication' => '/web/CWebApplication.php', 'CWebModule' => '/web/CWebModule.php', + 'CWidgetFactory' => '/web/CWidgetFactory.php', 'CAction' => '/web/actions/CAction.php', 'CInlineAction' => '/web/actions/CInlineAction.php', 'CViewAction' => '/web/actions/CViewAction.php', diff --git a/framework/base/interfaces.php b/framework/base/interfaces.php index 4d3575aefd..c1a3dba2f1 100644 --- a/framework/base/interfaces.php +++ b/framework/base/interfaces.php @@ -533,3 +533,26 @@ public function getEnabled(); */ public function setEnabled($value); } + +/** + * IWidgetFactory is the interface that must be implemented by a widget factory class. + * + * When calling {@link CBaseController::createWidget}, if a widget factory is available, + * it will be used for creating the requested widget. + * + * @author Qiang Xue + * @version $Id$ + * @package system.web + * @since 1.1 + */ +interface IWidgetFactory +{ + /** + * Creates a new widget based on the given class name and initial properties. + * @param CBaseController the owner of the new widget + * @param string the class name of the widget. This can also be a path alias (e.g. system.web.widgets.COutputCache) + * @param array the initial property values (name=>value) of the widget. + * @return CWidget the newly created widget whose properties have been initialized with the given values. + */ + public function createWidget($owner,$className,$properties=array()); +} diff --git a/framework/web/CBaseController.php b/framework/web/CBaseController.php index 6fc7adb073..da6ad34d7c 100644 --- a/framework/web/CBaseController.php +++ b/framework/web/CBaseController.php @@ -128,16 +128,23 @@ public function renderInternal($_viewFile_,$_data_=null,$_return_=false) * This method first creates the specified widget instance. * It then configures the widget's properties with the given initial values. * At the end it calls {@link CWidget::init} to initialize the widget. + * Starting from version 1.1, if a {@link CWidgetFactory widget factory} is enabled, + * this method will use the factory to create the widget, instead. * @param string class name (can be in path alias format) * @param array initial property values * @return CWidget the fully initialized widget instance. */ public function createWidget($className,$properties=array()) { - $className=Yii::import($className,true); - $widget=new $className($this); - foreach($properties as $name=>$value) - $widget->$name=$value; + if(($factory=Yii::app()->getWidgetFactory())!==null) + $widget=$factory->createWidget($this,$className,$properties); + else + { + $className=Yii::import($className,true); + $widget=new $className($this); + foreach($properties as $name=>$value) + $widget->$name=$value; + } $widget->init(); return $widget; } diff --git a/framework/web/CTheme.php b/framework/web/CTheme.php index d2c8d7d2f9..b2e7417871 100644 --- a/framework/web/CTheme.php +++ b/framework/web/CTheme.php @@ -75,6 +75,15 @@ public function getSystemViewPath() return $this->getViewPath().DIRECTORY_SEPARATOR.'system'; } + /** + * @return string the path for widget skins. Defaults to 'ThemeRoot/views/skins'. + * @since 1.1 + */ + public function getSkinPath() + { + return $this->getViewPath().DIRECTORY_SEPARATOR.'skins'; + } + /** * Finds the view file for the specified controller's view. * @param CController the controller diff --git a/framework/web/CWebApplication.php b/framework/web/CWebApplication.php index 7fe07e6486..a5c3790258 100644 --- a/framework/web/CWebApplication.php +++ b/framework/web/CWebApplication.php @@ -154,6 +154,10 @@ protected function registerCoreComponents() 'clientScript'=>array( 'class'=>'CClientScript', ), + 'widgetFactory'=>array( + 'class'=>'CWidgetFactory', + 'enabled'=>false, + ), ); $this->setComponents($components); @@ -228,6 +232,16 @@ public function getClientScript() return $this->getComponent('clientScript'); } + /** + * Returns the widget factory. + * @return IWidgetFactory the widget factory + * @since 1.1 + */ + public function getWidgetFactory() + { + return $this->getComponent('widgetFactory'); + } + /** * @return CThemeManager the theme manager. */ diff --git a/framework/web/CWidgetFactory.php b/framework/web/CWidgetFactory.php new file mode 100644 index 0000000000..c55ae9e6a3 --- /dev/null +++ b/framework/web/CWidgetFactory.php @@ -0,0 +1,142 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2009 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CWidgetFactory creates new widgets to be used in views. + * + * CWidgetFactory is used as the default "widgetFactory" application component. + * + * When calling {@link CBaseController::createWidget}, {@link CBaseController::widget} + * or {@link CBaseController::beginWidget}, if the "widgetFactory" component is enabled + * (disabled by default), it will be used to create the requested widget. + * + * CWidgetFactory implements the "skin" feature, which allows a new widget to be created + * and initialized with a set of predefined property values (called skin). + * + * When CWidgetFactory is used to create a new widget, it will first instantiate the + * widget instance. It then checks if there is a skin available for this widget + * according to the widget class name and the widget {@link CWidget::skin} property. + * If a skin is found, it will be merged with the initial properties passed via + * {@link createWidget}. Then the merged initial properties will be used to initialize + * the newly created widget instance. + * + * As aforementioned, a skin is a set of initial property values for a widget. + * It is thus represented as an associative array of name-value pairs. + * Skins are stored in PHP scripts like other configurations. Each script file stores the skins + * for a particular widget type and is named as the wiget class name (e.g. CLinkPager.php). + * Each widget type may have one or several skins, identified by the skin ID set via + * {@link CWidget::skin} property. If the {@link CWidget::skin} property is not set for a given + * widget, it means the default skin would be used. The following shows the possible skins for + * the {@link CLinkPager} widget: + *
+ * return array(
+ *     'default'=>array(
+ *         'nextPageLabel'=>'>>',
+ *         'prevPageLabel'=>'<<',
+ *     ),
+ *     'short'=>array(
+ *         'header'=>'',
+ *         'maxButtonCount'=>5,
+ *     ),
+ * );
+ * 
+ * In the above, there are two skins. The first one is the default skin which is indexed by the string "default". + * Note that {@link CWidget::skin} defaults to "default". Therefore, this is the skin that will be applied + * if we do not explicitly specify the {@link CWidget::skin} property. + * The second one is named as the "short" skin which will be used only when we set {@link CWidget::skin} + * to be "short". + * + * By default, CWidgetFactory looks for the skin of a widget under the "skins" directory + * of the current application's {@link CWebApplication::viewPath} (e.g. protected/views/skins). + * If a theme is being used, it will look for the skin under the "skins" directory of + * the theme's {@link CTheme::viewPath}. In case the specified skin is not found, a widget will still be created + * normally without causing any error. + * + * Note that by default, the "widgetFactory" component is not enabled. It may be enabled + * by configuring the component in the application configuration like the following: + *
+ * return array(
+ *     'components'=>array(
+ *         'widgetFactory'=>array(
+ *             'enabled'=>true,
+ *         ),
+ *     ),
+ * )
+ * 
+ * + * @author Qiang Xue + * @version $Id$ + * @package system.web + * @since 1.1 + */ +class CWidgetFactory extends CApplicationComponent implements IWidgetFactory +{ + /** + * @var string the directory containing all the skin files. Defaults to null, + * meaning using the "skin" directory under the current application's {@link CWebApplication::viewPath}. + */ + public $skinPath; + + private $_skins=array(); // class name, skin name, property name => value + + /** + * Initializes the application component. + * This method overrides the parent implementation by resolving the skin path. + */ + public function init() + { + parent::init(); + + if(($theme=Yii::app()->getTheme())!==null) + $this->skinPath=$theme->getSkinPath(); + else if($this->skinPath===null) + $this->skinPath=Yii::app()->getViewPath().DIRECTORY_SEPARATOR.'skins'; + } + + /** + * Creates a new widget based on the given class name and initial properties. + * @param CBaseController the owner of the new widget + * @param string the class name of the widget. This can also be a path alias (e.g. system.web.widgets.COutputCache) + * @param array the initial property values (name=>value) of the widget. + * @return CWidget the newly created widget whose properties have been initialized with the given values. + */ + public function createWidget($owner,$className,$properties=array()) + { + $className=Yii::import($className,true); + $widget=new $className($this); + $skinName=isset($properties['skin']) ? $properties['skin'] : 'default'; + if(($skin=$this->getSkin($className,$skinName))!==array()) + $properties=$properties===array() ? $skin : CMap::mergeArray($skin,$properties); + foreach($properties as $name=>$value) + $widget->$name=$value; + } + + /** + * Returns the skin for the specified widget class and skin name. + * @param string the widget class name + * @param string the widget skin name + * @return array the skin (name=>value) for the widget + */ + protected function getSkin($className,$skinName) + { + if(!isset($this->_skins[$className][$skinName])) + { + $skinFile=$this->skinPath.DIRECTORY_SEPARATOR.$className.'.php'; + if(is_file($skinFile)) + $this->_skins[$className]=require($skinFile); + else + $this->_skins[$className]=array(); + if(!isset($this->_skins[$className][$skinName])) + $this->_skins[$className][$skinName]=array(); + } + return $this->_skins[$className][$skinName]; + } +} \ No newline at end of file diff --git a/framework/web/widgets/CWidget.php b/framework/web/widgets/CWidget.php index 217ebfe9a1..539b6cc215 100644 --- a/framework/web/widgets/CWidget.php +++ b/framework/web/widgets/CWidget.php @@ -33,6 +33,12 @@ class CWidget extends CBaseController * @since 1.0.1 */ public $actionPrefix; + /** + * @var string the name of the skin to be used by this widget. Defaults to 'default'. + * @see CWidgetFactory + * @since 1.1 + */ + public $skin='default'; /** * @var array view paths for different types of widgets diff --git a/framework/yiilite.php b/framework/yiilite.php index 71532e1743..bc0dd07fb7 100644 --- a/framework/yiilite.php +++ b/framework/yiilite.php @@ -28,9 +28,10 @@ defined('YII_ENABLE_EXCEPTION_HANDLER') or define('YII_ENABLE_EXCEPTION_HANDLER',true); defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER',true); defined('YII_PATH') or define('YII_PATH',dirname(__FILE__)); +defined('YII_ZII_PATH') or define('YII_ZII_PATH',YII_PATH.DIRECTORY_SEPARATOR.'zii'); class YiiBase { - private static $_aliases=array('system'=>YII_PATH); // alias => path + private static $_aliases=array('system'=>YII_PATH,'zii'=>YII_ZII_PATH); // alias => path private static $_imports=array(); // alias => class name or directory private static $_classes=array(); private static $_includePaths; // list of include paths @@ -402,6 +403,7 @@ public static function t($category,$message,$params=array(),$source=null,$langua 'CUrlManager' => '/web/CUrlManager.php', 'CWebApplication' => '/web/CWebApplication.php', 'CWebModule' => '/web/CWebModule.php', + 'CWidgetFactory' => '/web/CWidgetFactory.php', 'CAction' => '/web/actions/CAction.php', 'CInlineAction' => '/web/actions/CInlineAction.php', 'CViewAction' => '/web/actions/CViewAction.php', @@ -1352,6 +1354,10 @@ protected function registerCoreComponents() 'clientScript'=>array( 'class'=>'CClientScript', ), + 'widgetFactory'=>array( + 'class'=>'CWidgetFactory', + 'enabled'=>false, + ), ); $this->setComponents($components); } @@ -1387,6 +1393,10 @@ public function getClientScript() { return $this->getComponent('clientScript'); } + public function getWidgetFactory() + { + return $this->getComponent('widgetFactory'); + } public function getThemeManager() { return $this->getComponent('themeManager'); @@ -2657,10 +2667,15 @@ public function renderInternal($_viewFile_,$_data_=null,$_return_=false) } public function createWidget($className,$properties=array()) { - $className=Yii::import($className,true); - $widget=new $className($this); - foreach($properties as $name=>$value) - $widget->$name=$value; + if(($factory=Yii::app()->getWidgetFactory())!==null) + $widget=$factory->createWidget($this,$className,$properties); + else + { + $className=Yii::import($className,true); + $widget=new $className($this); + foreach($properties as $name=>$value) + $widget->$name=$value; + } $widget->init(); return $widget; } @@ -4473,6 +4488,7 @@ protected static function renderAttributes($htmlOptions) class CWidget extends CBaseController { public $actionPrefix; + public $skin='default'; private static $_viewPaths; private static $_counter=0; private $_id; @@ -7169,4 +7185,8 @@ public function detach($component); public function getEnabled(); public function setEnabled($value); } +interface IWidgetFactory +{ + public function createWidget($owner,$className,$properties=array()); +} ?> \ No newline at end of file