From 51f4424332dbacf77e565bf1ba749ec3bbfdc09b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 11 May 2013 20:59:14 -0400 Subject: [PATCH] Fixes issue #172: Implemented new usage of widgets. --- apps/bootstrap/protected/views/layouts/main.php | 8 +- apps/bootstrap/protected/views/site/contact.php | 16 ++-- apps/bootstrap/protected/views/site/login.php | 4 +- docs/guide/upgrade-from-v1.md | 24 +++++- yii/base/View.php | 108 ++++-------------------- yii/base/Widget.php | 87 +++++++++++++++++-- yii/web/Pagination.php | 2 +- yii/widgets/Breadcrumbs.php | 12 +-- yii/widgets/Menu.php | 5 +- 9 files changed, 144 insertions(+), 122 deletions(-) diff --git a/apps/bootstrap/protected/views/layouts/main.php b/apps/bootstrap/protected/views/layouts/main.php index a81f983..05f7259 100644 --- a/apps/bootstrap/protected/views/layouts/main.php +++ b/apps/bootstrap/protected/views/layouts/main.php @@ -1,6 +1,8 @@ registerAssetBundle('app'); -widget('yii\debug\Toolbar'); ?> + endPage(); ?> diff --git a/apps/bootstrap/protected/views/site/contact.php b/apps/bootstrap/protected/views/site/contact.php index a632345..4fa0db5 100644 --- a/apps/bootstrap/protected/views/site/contact.php +++ b/apps/bootstrap/protected/views/site/contact.php @@ -23,7 +23,7 @@ $this->params['breadcrumbs'][] = $this->title; If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.

-beginWidget(ActiveForm::className(), array( + array('class' => 'form-horizontal'), 'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')), )); ?> @@ -33,14 +33,14 @@ $this->params['breadcrumbs'][] = $this->title; field($model, 'body')->textArea(array('rows' => 6)); ?> field($model, 'verifyCode'); - echo $field->begin(); - echo $field->label(); - $this->widget(Captcha::className()); - echo Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')); - echo $field->error(); - echo $field->end(); + echo $field->begin() + . $field->label() + . Captcha::widget($this) + . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) + . $field->error() + . $field->end(); ?>
'btn btn-primary')); ?>
-endWidget(); ?> + diff --git a/apps/bootstrap/protected/views/site/login.php b/apps/bootstrap/protected/views/site/login.php index 65dc7d1..7d2fed2 100644 --- a/apps/bootstrap/protected/views/site/login.php +++ b/apps/bootstrap/protected/views/site/login.php @@ -14,11 +14,11 @@ $this->params['breadcrumbs'][] = $this->title;

Please fill out the following fields to login:

-beginWidget(ActiveForm::className(), array('options' => array('class' => 'form-horizontal'))); ?> + array('class' => 'form-horizontal'))); ?> field($model, 'username')->textInput(); ?> field($model, 'password')->passwordInput(); ?> field($model, 'rememberMe')->checkbox(); ?>
'btn btn-primary')); ?>
-endWidget(); ?> + diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index d35e6e0..f02f825 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -209,6 +209,26 @@ if (isset($_POST['Post'])) { ``` +Widgets +------- + +Using a widget is more straightforward in 2.0. You mainly use the `begin()`, `end()` and `widget()` +methods of the `Widget` class. For example, + +```php +// $this refers to the View object +// Note that you have to "echo" the result to display it +echo \yii\widgets\Menu::widget($this, array('items' => $items)); + +// $this refers to the View object +$form = \yii\widgets\ActiveForm::begin($this); +... form inputs here ... +\yii\widgets\ActiveForm::end(); +``` + +Previously in 1.1, you would have to enter the widget class names as strings via the `beginWidget()`, +`endWidget()` and `widget()` methods of `CBaseController`. The approach above gets better IDE support. + Themes ------ @@ -309,13 +329,13 @@ is a container consisting of a label, an input, and an error message. It is repr as an `ActiveField` object. Using fields, you can build a form more cleanly than before: ```php -beginWidget('yii\widgets\ActiveForm'); ?> + field($model, 'username')->textInput(); ?> field($model, 'password')->passwordInput(); ?>
-endWidget(); ?> + ``` diff --git a/yii/base/View.php b/yii/base/View.php index 84a6f11..8482a68 100644 --- a/yii/base/View.php +++ b/yii/base/View.php @@ -11,6 +11,9 @@ use Yii; use yii\base\Application; use yii\helpers\FileHelper; use yii\helpers\Html; +use yii\widgets\Block; +use yii\widgets\ContentDecorator; +use yii\widgets\FragmentCache; /** * View represents a view object in the MVC pattern. @@ -108,12 +111,6 @@ class View extends Component */ public $blocks; /** - * @var Widget[] the widgets that are currently being rendered (not ended). This property - * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly. - * @internal - */ - public $widgetStack = array(); - /** * @var array a list of currently active fragment cache widgets. This property * is used internally to implement the content caching feature. Do not modify it directly. * @internal @@ -364,91 +361,16 @@ class View extends Component } /** - * Creates a widget. - * This method will use [[Yii::createObject()]] to create the widget. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @return Widget the newly created widget instance - */ - public function createWidget($class, $properties = array()) - { - $properties['class'] = $class; - if (!isset($properties['view'])) { - $properties['view'] = $this; - } - return Yii::createObject($properties); - } - - /** - * Creates and runs a widget. - * Compared with [[createWidget()]], this method does one more thing: it will - * run the widget after it is created. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @param boolean $captureOutput whether to capture the output of the widget and return it as a string - * @return string|Widget if $captureOutput is true, the output of the widget will be returned; - * otherwise the widget object will be returned. - */ - public function widget($class, $properties = array(), $captureOutput = false) - { - if ($captureOutput) { - ob_start(); - ob_implicit_flush(false); - $widget = $this->createWidget($class, $properties); - $widget->run(); - return ob_get_clean(); - } else { - $widget = $this->createWidget($class, $properties); - $widget->run(); - return $widget; - } - } - - /** - * Begins a widget. - * This method is similar to [[createWidget()]] except that it will expect a matching - * [[endWidget()]] call after this. - * @param string $class the widget class name or path alias - * @param array $properties the initial property values of the widget. - * @return Widget the widget instance - */ - public function beginWidget($class, $properties = array()) - { - $widget = $this->createWidget($class, $properties); - $this->widgetStack[] = $widget; - return $widget; - } - - /** - * Ends a widget. - * Note that the rendering result of the widget is directly echoed out. - * If you want to capture the rendering result of a widget, you may use - * [[createWidget()]] and [[Widget::run()]]. - * @return Widget the widget instance - * @throws InvalidCallException if [[beginWidget()]] and [[endWidget()]] calls are not properly nested - */ - public function endWidget() - { - $widget = array_pop($this->widgetStack); - if ($widget instanceof Widget) { - $widget->run(); - return $widget; - } else { - throw new InvalidCallException("Unmatched beginWidget() and endWidget() calls."); - } - } - - /** * Begins recording a block. - * This method is a shortcut to beginning [[yii\widgets\Block]] + * This method is a shortcut to beginning [[Block]] * @param string $id the block ID. * @param boolean $renderInPlace whether to render the block content in place. * Defaults to false, meaning the captured block will not be displayed. - * @return \yii\widgets\Block the Block widget instance + * @return Block the Block widget instance */ public function beginBlock($id, $renderInPlace = false) { - return $this->beginWidget('yii\widgets\Block', array( + return Block::begin($this, array( 'id' => $id, 'renderInPlace' => $renderInPlace, )); @@ -459,7 +381,7 @@ class View extends Component */ public function endBlock() { - $this->endWidget(); + Block::end(); } /** @@ -476,12 +398,12 @@ class View extends Component * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget. * This can be specified as either the view file path or path alias. * @param array $params the variables (name => value) to be extracted and made available in the decorative view. - * @return \yii\widgets\ContentDecorator the ContentDecorator widget instance - * @see \yii\widgets\ContentDecorator + * @return ContentDecorator the ContentDecorator widget instance + * @see ContentDecorator */ public function beginContent($viewFile, $params = array()) { - return $this->beginWidget('yii\widgets\ContentDecorator', array( + return ContentDecorator::begin($this, array( 'viewFile' => $viewFile, 'params' => $params, )); @@ -492,7 +414,7 @@ class View extends Component */ public function endContent() { - $this->endWidget(); + ContentDecorator::end(); } /** @@ -510,15 +432,15 @@ class View extends Component * ~~~ * * @param string $id a unique ID identifying the fragment to be cached. - * @param array $properties initial property values for [[\yii\widgets\FragmentCache]] + * @param array $properties initial property values for [[FragmentCache]] * @return boolean whether you should generate the content for caching. * False if the cached version is available. */ public function beginCache($id, $properties = array()) { $properties['id'] = $id; - /** @var $cache \yii\widgets\FragmentCache */ - $cache = $this->beginWidget('yii\widgets\FragmentCache', $properties); + /** @var $cache FragmentCache */ + $cache = FragmentCache::begin($this, $properties); if ($cache->getCachedContent() !== false) { $this->endCache(); return false; @@ -532,7 +454,7 @@ class View extends Component */ public function endCache() { - $this->endWidget(); + FragmentCache::end(); } diff --git a/yii/base/Widget.php b/yii/base/Widget.php index c0c524f..9ec690c 100644 --- a/yii/base/Widget.php +++ b/yii/base/Widget.php @@ -8,7 +8,6 @@ namespace yii\base; use Yii; -use yii\helpers\FileHelper; /** * Widget is the base class for widgets. @@ -19,9 +18,9 @@ use yii\helpers\FileHelper; class Widget extends Component { /** - * @var View the view object that is used to create this widget. - * This property is automatically set by [[View::createWidget()]]. - * This property is required by [[render()]] and [[renderFile()]]. + * @var View the view object that this widget is associated with. + * The widget will use this view object to register any needed assets. + * This property is also required by [[render()]] and [[renderFile()]]. */ public $view; /** @@ -29,9 +28,85 @@ class Widget extends Component */ private $_id; /** - * @var integer a counter used to generate IDs for widgets. + * @var integer a counter used to generate [[id]] for widgets. + * @internal */ - private static $_counter = 0; + public static $_counter = 0; + /** + * @var Widget[] the widgets that are currently being rendered (not ended). This property + * is maintained by [[begin()]] and [[end()]] methods. + * @internal + */ + public static $_stack = array(); + + /** + * Constructor. + * @param View $view the view object that this widget is associated with. + * The widget will use this view object to register any needed assets. + * It is also required by [[render()]] and [[renderFile()]]. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($view, $config = array()) + { + $this->view = $view; + parent::__construct($config); + } + + /** + * Begins a widget. + * This method creates an instance of the calling class. It will apply the configuration + * to the created instance. A matching [[end()]] call should be called later. + * @param View $view the view object that the newly created widget is associated with. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return Widget the newly created widget instance + */ + public static function begin($view, $config = array()) + { + $config['class'] = get_called_class(); + /** @var Widget $widget */ + $widget = Yii::createObject($config, $view); + self::$_stack[] = $widget; + return $widget; + } + + /** + * Ends a widget. + * Note that the rendering result of the widget is directly echoed out. + * @return Widget the widget instance that is ended. + * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested + */ + public static function end() + { + if (!empty(self::$_stack)) { + $widget = array_pop(self::$_stack); + if (get_class($widget) === get_called_class()) { + $widget->run(); + return $widget; + } else { + throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class()); + } + } else { + throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.'); + } + } + + /** + * Creates a widget instance and runs it. + * The widget rendering result is returned by this method. + * @param View $view the view object that the newly created widget is associated with. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return string the rendering result of the widget. + */ + public static function widget($view, $config = array()) + { + ob_start(); + ob_implicit_flush(false); + /** @var Widget $widget */ + $config['class'] = get_called_class(); + $widget = Yii::createObject($config, $view); + $widget->run(); + return ob_get_clean(); + } /** * Returns the ID of the widget. diff --git a/yii/web/Pagination.php b/yii/web/Pagination.php index 20241ef..73c0adb 100644 --- a/yii/web/Pagination.php +++ b/yii/web/Pagination.php @@ -47,7 +47,7 @@ use Yii; * } * * // display pagination - * $this->widget('yii\widgets\LinkPager', array( + * LinkPager::widget($this, array( * 'pages' => $pages, * )); * ~~~ diff --git a/yii/widgets/Breadcrumbs.php b/yii/widgets/Breadcrumbs.php index 22d09b3..35772e0 100644 --- a/yii/widgets/Breadcrumbs.php +++ b/yii/widgets/Breadcrumbs.php @@ -19,10 +19,11 @@ use yii\helpers\Html; * for the "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home" * to return to the homepage. * - * To use Breadcrumbs, you need to configure its [[links]] property, which specifiesthe links to be displayed. For example, + * To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example, * * ~~~ - * $this->widget('yii\widgets\Breadcrumbs', array( + * // $this is the view object currently being used + * echo Breadcrumbs::widget($this, array( * 'links' => array( * array('label' => 'Sample Post', 'url' => array('post/edit', 'id' => 1)), * 'Edit', @@ -30,12 +31,13 @@ use yii\helpers\Html; * )); * ~~~ * - * Because breadcrumbs usually appears in nearly every page of a website, you may consider place it in a layout view. - * You can then use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different + * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view. + * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different * views. In the layout view, you assign this view parameter to the [[links]] property like the following: * * ~~~ - * $this->widget('yii\widgets\Breadcrumbs', array( + * // $this is the view object currently being used + * echo Breadcrumbs::widget($this, array( * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(), * )); * ~~~ diff --git a/yii/widgets/Menu.php b/yii/widgets/Menu.php index b8f69e1..3da0f31 100644 --- a/yii/widgets/Menu.php +++ b/yii/widgets/Menu.php @@ -26,10 +26,11 @@ use yii\helpers\Html; * The following example shows how to use Menu: * * ~~~ - * $this->widget('yii\widgets\Menu', array( + * // $this is the view object currently being used + * echo Menu::widget($this, array( * 'items' => array( * // Important: you need to specify url as 'controller/action', - * // not just as 'controller' even if default acion is used. + * // not just as 'controller' even if default action is used. * array('label' => 'Home', 'url' => array('site/index')), * // 'Products' menu item will be selected as long as the route is 'product/index' * array('label' => 'Products', 'url' => array('product/index'), 'items' => array(