From 04a825f462941540e54f405c48341dc6380eeb31 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 20 May 2013 16:39:13 -0400 Subject: [PATCH] Finished bootstrap Widget and Modal. --- yii/base/View.php | 2 +- yii/bootstrap/Modal.php | 354 ++++++++++++++++++++--------------------------- yii/bootstrap/Widget.php | 127 ++++++++--------- 3 files changed, 202 insertions(+), 281 deletions(-) diff --git a/yii/base/View.php b/yii/base/View.php index cf92741..8bede3d 100644 --- a/yii/base/View.php +++ b/yii/base/View.php @@ -741,7 +741,7 @@ class View extends Component $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), array('type' => 'text/javascript')); } if (!empty($this->js[self::POS_READY])) { - $js = "jQuery(document).ready(function(){\n{" . implode("\n", $this->js[self::POS_READY]) . "}\n});"; + $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; $lines[] = Html::script($js, array('type' => 'text/javascript')); } return empty($lines) ? '' : implode("\n", $lines) . "\n"; diff --git a/yii/bootstrap/Modal.php b/yii/bootstrap/Modal.php index 5e287c5..b3b13b3 100644 --- a/yii/bootstrap/Modal.php +++ b/yii/bootstrap/Modal.php @@ -8,279 +8,219 @@ namespace yii\bootstrap; use Yii; -use yii\helpers\Html; use yii\helpers\ArrayHelper; +use yii\helpers\Html; /** - * Modal renders a bootstrap modal on the page for its use on your application. + * Modal renders a modal window that can be toggled by clicking on a button. * - * Basic usage: + * For example, * - * ```php - * $this->widget(Modal::className(), array( - * 'id' => 'myModal', - * 'header' => 'Modal Heading', - * 'content' => '

One fine body...

', - * 'footer' => 'Modal Footer', - * // if we wish to display a modal button - * 'buttonOptions' => array( - * 'label' => 'Show Modal', - * 'class' => 'btn btn-primary' - * ) + * ~~~php + * echo Modal::widget(array( + * 'header' => '

Hello world

', + * 'body' => 'Say hello...', + * 'toggleButton' => array( + * 'label' => 'click me', + * ), * )); - * ``` + * ~~~ + * + * The following example will show the content enclosed between the [[begin()]] + * and [[end()]] calls within the modal window: + * + * ~~~php + * Modal::begin(array( + * 'header' => '

Hello world

', + * 'toggleButton' => array( + * 'label' => 'click me', + * ), + * )); + * + * echo 'Say hello...'; + * + * Modal::end(); + * ~~~ + * * @see http://twitter.github.io/bootstrap/javascript.html#modals * @author Antonio Ramirez + * @author Qiang Xue * @since 2.0 */ class Modal extends Widget { /** - * @var array The additional HTML attributes of the button that will show the modal. If empty array, only - * the markup of the modal will be rendered on the page, so users can easily call the modal manually with their own - * scripts. The following special attributes are available: - *
    - *
  • label: string, the label of the button
  • - *
- * - * For available options of the button trigger, see http://twitter.github.com/bootstrap/javascript.html#modals. - */ - public $buttonOptions = array(); - - /** - * @var boolean indicates whether the modal should use transitions. Defaults to 'true'. + * @var string the header content in the modal window. */ - public $fade = true; - - /** - * @var bool $keyboard, closes the modal when escape key is pressed. - */ - public $keyboard = true; - - /** - * @var bool $show, shows the modal when initialized. - */ - public $show = false; - - /** - * @var mixed includes a modal-backdrop element. Alternatively, specify `static` for a backdrop which doesn't close - * the modal on click. - */ - public $backdrop = true; - - /** - * @var mixed the remote url. If a remote url is provided, content will be loaded via jQuery's load method and - * injected into the .modal-body of the modal. - */ - public $remote; - - /** - * @var string a javascript function that will be invoked immediately when the `show` instance method is called. - */ - public $onShow; - - /** - * @var string a javascript function that will be invoked when the modal has been made visible to the user - * (will wait for css transitions to complete). - */ - public $onShown; - - /** - * @var string a javascript function that will be invoked immediately when the hide instance method has been called. - */ - public $onHide; - - /** - * @var string a javascript function that will be invoked when the modal has finished being hidden from the user - * (will wait for css transitions to complete). - */ - public $onHidden; - - /** - * @var string[] the Javascript event handlers. - */ - protected $events = array(); - + public $header; /** - * @var array $pluginOptions the plugin options. + * @var string the body content in the modal window. Note that anything between + * the [[begin()]] and [[end()]] calls of the Modal widget will also be treated + * as the body content, and will be rendered before this. */ - protected $pluginOptions = array(); - + public $body; /** - * @var string + * @var string the footer content in the modal window. */ - public $closeText = '×'; - + public $footer; /** - * @var string header content. Header can also be a path to a view file. + * @var array the options for rendering the close button tag. + * The close button is displayed in the header of the modal window. Clicking + * on the button will hide the modal window. If this is null, no close button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to '×'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals) + * for the supported HTML attributes. */ - public $header; - + public $closeButton = array(); /** - * @var string body of modal. Body can also be a path to a view file. + * @var array the options for rendering the toggle button tag. + * The toggle button is used to toggle the visibility of the modal window. + * If this property is null, no toggle button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to 'Show'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals) + * for the supported HTML attributes. */ - public $content; + public $toggleButton; - /** - * @var string footer content. Content can also be a path to a view file. - */ - public $footer; /** - * Widget's init method + * Initializes the widget. */ public function init() { parent::init(); - $this->name = 'modal'; - - $this->defaultOption('id', $this->getId()); - - $this->defaultOption('role', 'dialog'); - $this->defaultOption('tabindex', '-1'); - - $this->addClassName('modal'); - $this->addClassName('hide'); - - if ($this->fade) - $this->addClassName('fade'); - - $this->initPluginOptions(); - $this->initPluginEvents(); - } + $this->options = array_merge(array( + 'class' => 'modal hide', + ), $this->options); + $this->addCssClass($this->options, 'modal'); + + $this->pluginOptions = array_merge(array( + 'show' => false, + ), $this->pluginOptions); + + if ($this->closeButton !== null) { + $this->closeButton = array_merge(array( + 'data-dismiss' => 'modal', + 'aria-hidden' => 'true', + 'class' => 'close', + ), $this->closeButton); + } - /** - * Initialize plugin events if any - */ - public function initPluginEvents() - { - foreach (array('onShow', 'onShown', 'onHide', 'onHidden') as $event) { - if ($this->{$event} !== null) { - $modalEvent = strtolower(substr($event, 2)); - if ($this->{$event} instanceof JsExpression) - $this->events[$modalEvent] = $this->$event; - else - $this->events[$modalEvent] = new JsExpression($this->{$event}); + if ($this->toggleButton !== null) { + $this->toggleButton = array_merge(array( + 'data-toggle' => 'modal', + ), $this->toggleButton); + if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { + $this->toggleButton['data-target'] = '#' . $this->options['id']; } } - } - - /** - * Initialize plugin options. - * ***Important***: The display of the button overrides the initialization of the modal bootstrap widget. - */ - public function initPluginOptions() - { - if (null !== $this->remote) - $this->pluginOptions['remote'] = Html::url($this->remote); - foreach (array('backdrop', 'keyboard', 'show') as $option) { - $this->pluginOptions[$option] = isset($this->pluginOptions[$option]) - ? $this->pluginOptions[$option] - : $this->{$option}; - } + ob_start(); + ob_implicit_flush(false); } /** - * Widget's run method + * Renders the widget. */ public function run() { - $this->renderModal(); - $this->renderButton(); - $this->registerScript(); - } + $this->body = ob_get_clean() . $this->body; - /** - * Renders the button that will open the modal if its options have been configured - */ - public function renderButton() - { - if (!empty($this->buttonOptions)) { - - $this->buttonOptions['data-toggle'] = isset($this->buttonOptions['data-toggle']) - ? $this->buttonOptions['data-toggle'] - : 'modal'; - - if ($this->remote !== null && !isset($this->buttonOptions['data-remote'])) - $this->buttonOptions['data-remote'] = Html::url($this->remote); + echo $this->renderToggleButton(); - $label = ArrayHelper::remove($this->buttonOptions, 'label', 'Button'); - $name = ArrayHelper::remove($this->buttonOptions, 'name'); - $value = ArrayHelper::remove($this->buttonOptions, 'value'); + $html = $this->renderHeader() . "\n" + . $this->renderBody() . "\n" + . $this->renderFooter(); + echo Html::tag('div', "\n" . $html . "\n", $this->options); - $attr = isset($this->buttonOptions['data-remote']) - ? 'data-target' - : 'href'; - - $this->buttonOptions[$attr] = isset($this->buttonOptions[$attr]) - ? $this->buttonOptions[$attr] - : '#' . ArrayHelper::getValue($this->options, 'id'); - - echo Html::button($label, $name, $value, $this->buttonOptions); - } + $this->registerPlugin('modal'); } /** - * Renders the modal markup + * Renders the header HTML markup of the modal + * @return string the rendering result */ - public function renderModal() + protected function renderHeader() { - echo Html::beginTag('div', $this->options); - - $this->renderModalHeader(); - $this->renderModalBody(); - $this->renderModalFooter(); - - echo Html::endTag('div'); + $button = $this->renderCloseButton(); + if ($button !== null) { + $this->header = $button . "\n" . $this->header; + } + if ($this->header !== null) { + return Html::tag('div', "\n" . $this->header . "\n", array('class' => 'modal-header')); + } else { + return null; + } } /** - * Renders the header HTML markup of the modal + * Renders the HTML markup for the body of the modal + * @return string the rendering result */ - public function renderModalHeader() + protected function renderBody() { - echo Html::beginTag('div', array('class'=>'modal-header')); - if ($this->closeText) - echo Html::button($this->closeText, null, null, array('data-dismiss' => 'modal', 'class'=>'close')); - echo $this->header; - echo Html::endTag('div'); + return Html::tag('div', $this->body, array('class' => 'modal-body')); } /** - * Renders the HTML markup for the body of the modal + * Renders the HTML markup for the footer of the modal + * @return string the rendering result */ - public function renderModalBody() + protected function renderFooter() { - echo Html::beginTag('div', array('class'=>'modal-body')); - echo $this->content; - echo Html::endTag('div'); + if ($this->footer !== null) { + return Html::tag('div', $this->footer, array('class' => 'modal-footer')); + } else { + return null; + } } /** - * Renders the HTML markup for the footer of the modal + * Renders the toggle button. + * @return string the rendering result */ - public function renderModalFooter() + protected function renderToggleButton() { - - echo Html::beginTag('div', array('class'=>'modal-footer')); - echo $this->footer; - echo Html::endTag('div'); + if ($this->toggleButton !== null) { + $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show'); + if ($tag === 'button' && !isset($this->toggleButton['type'])) { + $this->toggleButton['type'] = 'button'; + } + return Html::tag($tag, $label, $this->toggleButton) . "\n"; + } else { + return null; + } } /** - * Registers client scripts + * Renders the close button. + * @return string the rendering result */ - public function registerScript() + protected function renderCloseButton() { - // do we render a button? If so, bootstrap will handle its behavior through its - // mark-up, otherwise, register the plugin. - if(empty($this->buttonOptions)) - $this->registerPlugin('modal', $this->pluginOptions); - - // register events - $this->registerEvents($this->events); + if ($this->closeButton !== null) { + $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->closeButton, 'label', '×'); + if ($tag === 'button' && !isset($this->closeButton['type'])) { + $this->closeButton['type'] = 'button'; + } + return Html::tag($tag, $label, $this->closeButton); + } else { + return null; + } } - -} \ No newline at end of file +} diff --git a/yii/bootstrap/Widget.php b/yii/bootstrap/Widget.php index c50349f..405cd19 100644 --- a/yii/bootstrap/Widget.php +++ b/yii/bootstrap/Widget.php @@ -9,19 +9,20 @@ namespace yii\bootstrap; use Yii; use yii\base\View; +use yii\helpers\Json; /** - * Bootstrap is the base class for bootstrap widgets. + * \yii\bootstrap\Widget is the base class for all bootstrap widgets. * * @author Antonio Ramirez + * @author Qiang Xue * @since 2.0 */ class Widget extends \yii\base\Widget { - /** - * @var bool whether to register the asset + * @var boolean whether to use the responsive version of Bootstrap. */ public static $responsive = true; @@ -29,95 +30,75 @@ class Widget extends \yii\base\Widget * @var array the HTML attributes for the widget container tag. */ public $options = array(); - /** - * Initializes the widget. + * @var array the options for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible options. + * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows + * how to use the "Modal" plugin and the supported options (e.g. "remote"). */ - public function init() - { - // ensure bundle - $this->registerBundle(static::$responsive); - } - + public $pluginOptions = array(); /** - * Registers plugin events with the API. - * @param string $selector the CSS selector. - * @param string[] $events the JavaScript event configuration (name=>handler). - * @return boolean whether the events were registered. - * @todo To be discussed + * @var array the event handlers for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible events. + * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows + * how to use the "Modal" plugin and the supported events (e.g. "shown"). */ - protected function registerEvents($selector, $events = array()) - { - if (empty($events)) - return; - - $script = ''; - foreach ($events as $name => $handler) { - $handler = ($handler instanceof JsExpression) - ? $handler - : new JsExpression($handler); + public $pluginEvents = array(); - $script .= ";jQuery('{$selector}').on('{$name}', {$handler});"; - } - if (!empty($script)) - $this->view->registerJs($script); - } /** - * Registers a specific Bootstrap plugin using the given selector and options. - * - * @param string $name the name of the javascript widget to initialize - * @param array $options the Javascript options for the plugin + * Initializes the widget. + * This method will register the bootstrap asset bundle. If you override this method, + * make sure you call the parent implementation first. */ - public function registerPlugin($name, $options = array()) + public function init() { - $selector = '#' . ArrayHelper::getValue($this->options, 'id'); - $options = !empty($options) ? Json::encode($options) : ''; - $script = ";jQuery('{$selector}').{$name}({$options});"; - $this->view->registerJs($script); + parent::init(); + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } } /** - * Registers bootstrap bundle - * @param bool $responsive + * Registers a specific Bootstrap plugin and the related events + * @param string $name the name of the Bootstrap plugin */ - public function registerBundle($responsive = false) + protected function registerPlugin($name) { - $bundle = $responsive ? 'yii/bootstrap-responsive' : 'yii/bootstrap'; - $this->view->registerAssetBundle($bundle); - } + $id = $this->options['id']; + $view = $this->getView(); + $bundle = static::$responsive ? 'yii/bootstrap-responsive' : 'yii/bootstrap'; + $view->registerAssetBundle($bundle); - /** - * Adds a new class to options. If the class key does not exists, it will create one, if it exists it will append - * the value and also makes sure the uniqueness of them. - * - * @param string $class - * @return array - */ - protected function addClassName($class) - { - if (isset($this->options['class'])) { - if (!is_array($this->options['class'])) - $this->options['class'] = explode(' ', $this->options['class']); - $this->options['class'][] = $class; - $this->options['class'] = array_unique($this->options['class']); - $this->options['class'] = implode(' ', $this->options['class']); - } else - $this->options['class'] = $class; - return $this->options; + if ($this->pluginOptions !== false) { + $options = empty($this->pluginOptions) ? '' : Json::encode($this->pluginOptions); + $js = "jQuery('#$id').$name($options);"; + $view->registerJs($js); + } + + if (!empty($this->pluginEvents)) { + $js = array(); + foreach ($this->pluginEvents as $event => $handler) { + $js[] = "jQuery('#$id').on('$event', $handler);"; + } + $view->registerJs(implode("\n", $js)); + } } /** - * Sets the default value for an item if not set. - * @param string $key the name of the item. - * @param mixed $value the default value. - * @return array + * Adds a CSS class to the specified options. + * This method will ensure that the CSS class is unique and the "class" option is properly formatted. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added */ - protected function defaultOption($key, $value) + protected function addCssClass(&$options, $class) { - if (!isset($this->options[$key])) - $this->options[$key] = $value; - return $this->options; + if (isset($options['class'])) { + $classes = preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY); + $options['class'] = implode(' ', array_unique($classes)); + } else { + $options['class'] = $class; + } } -} \ No newline at end of file +}