Commits

Christoffer Niska committed c9328d1

added BootButton, BootButtonGroup, BootDropdown and fixed form errors

  • Participants
  • Parent commits e196cb2
  • Branches bootstrap-2.0.2

Comments (0)

Files changed (11)

File .idea/dictionaries/Crisu.xml

+<component name="ProjectDictionaryState">
+  <dictionary name="Crisu">
+    <words>
+      <w>dropdown</w>
+      <w>navbar</w>
+      <w>scrollspy</w>
+    </words>
+  </dictionary>
+</component>

File components/Bootstrap.php

 		if (!$this->isPluginDisabled(self::PLUGIN_TRANSITION))
 			$this->enableTransitions();
 
-		if (!$this->isPluginDisabled(self::PLUGIN_BUTTON))
-			$this->registerButton();
-
 		if (!$this->isPluginDisabled(self::PLUGIN_TOOLTIP))
 			$this->registerTooltip();
 

File demo/css/styles.css

 @import "../css/highlight.css";
+.clearfix {
+  *zoom: 1;
+}
+.clearfix:before, .clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both;
+}
 body {
   padding-top: 60px;
   padding-bottom: 60px;

File demo/protected/views/site/index.php

 
 <section id="bootAlert">
 
-	<h2>BootAlert</h2>
+	<h2>Alerts</h2>
 
 	<?php
 	Yii::app()->user->setFlash('success', '<strong>Well done!</strong> You successfully read this important alert message.');
 
 <section id="bootCrumb">
 
-	<h2>BootCrumb</h2>
+	<h2>Breadcrumbs</h2>
 
 	<?php $this->widget('bootstrap.widgets.BootCrumb', array(
 		'links'=>array('Library'=>'#', 'Data'),
 
 <section id="bootNavbar">
 
-	<h2>BootNavbar</h2>
+	<h2>Navbar</h2>
 
 	<?php $this->widget('bootstrap.widgets.BootNavbar', array(
 		'fixed'=>false,
 					array('label'=>'Link', 'url'=>'#'),
 					array('label'=>'Link', 'url'=>'#'),
 					array('label'=>'Dropdown', 'url'=>'#', 'items'=>array(
-						array('label'=>'DROPDOWN HEADER', 'itemOptions'=>array('class'=>'nav-header')),
+						array('label'=>'DROPDOWN HEADER', 'header'=>true),
 						array('label'=>'Action', 'url'=>'#'),
 						array('label'=>'Another action', 'url'=>'#'),
 						array('label'=>'Something else here', 'url'=>'#'),
 
 <section id="bootMenu">
 
-	<h2>BootMenu</h2>
+	<h2>Menus</h2>
 
 	<h3>Basic tabs</h3>
 
 
 <section id="bootTabbed">
 
-	<h2>BootTabbed</h2>
+	<h2>Tabs</h2>
 
 	<?php $this->widget('bootstrap.widgets.BootTabbed', array(
 		'htmlOptions'=>array('class'=>'tabbed'),
 
 <section id="bootDetailView">
 
-	<h2>BootDetailView</h2>
+	<h2>Detail views</h2>
 
 	<?php $this->widget('bootstrap.widgets.BootDetailView', array(
 		'data'=>array('id'=>1, 'firstName'=>'Mark', 'lastName'=>'Otto', 'language'=>'CSS'),
 
 <section id="bootGridView">
 
-	<h2>BootGridView</h2>
+	<h2>Grid views</h2>
 
 	<h3>Default</h3>
 
 
 <section id="bootThumbs">
 
-	<h2>BootThumbs</h2>
+	<h2>Thumbnails</h2>
 
 	<?php $this->widget('bootstrap.widgets.BootThumbs', array(
 		'dataProvider'=>$listDataProvider,
 
 <section id="bootTooltip">
 
-	<h2>BootTooltip</h2>
+	<h2>Tooltips</h2>
 
 	<p class="well">
 		Lorem ipsum dolor sit <a href="#" rel="tooltip" title="First tooltip">amet</a>,
 
 <section id="bootPopover">
 
-	<h2>BootPopover</h2>
+	<h2>Popovers</h2>
 
 	<div class="well">
 		<?php echo CHtml::link('Hover me', '#', array(
 
 <section id="bootModal">
 
-	<h2>BootModal</h2>
+	<h2>Modals</h2>
 
 	<?php $this->beginWidget('bootstrap.widgets.BootModal', array(
 		'id'=>'modal',
 
 <section id="bootActiveForm">
 
-	<h2>BootActiveForm</h2>
+	<h2>Forms</h2>
 
 	<h3>Vertical</h3>
 
 
 </section>
 
+<section id="bootButton">
+
+	<h2>Buttons</h2>
+
+	<div class="row">
+		<div class="span4">
+			<h3>Large</h3>
+			<p>
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Primary', 'type'=>'primary', 'size'=>'large',
+				)); ?>
+
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Action', 'size'=>'large',
+				)); ?>
+			</p>
+		</div>
+
+		<div class="span4">
+			<h3>Normal</h3>
+			<p>
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Primary', 'type'=>'primary',
+				)); ?>
+
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Action',
+				)); ?>
+			</p>
+		</div>
+
+		<div class="span4">
+			<h3>Small</h3>
+			<p>
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Primary', 'type'=>'primary', 'size'=>'small',
+				)); ?>
+
+				<?php $this->widget('bootstrap.widgets.BootButton', array(
+					'label'=>'Action', 'size'=>'small',
+				)); ?>
+			</p>
+		</div>
+	</div>
+
+	<h4>Source code</h4>
+
+<?php echo $parser->safeTransform("~~~
+[php]
+<p>
+	<?php \$this->widget('bootstrap.widgets.BootButton', array(
+		'label'=>'Primary',
+		'type'=>'primary', // '', 'primary', 'info', 'success', 'warning', 'danger' or 'inverse'
+		'size'=>'small', // '', 'small' or 'large'
+	)); ?>
+</p>
+~~~"); ?>
+
+	<h3>Button groups</h3>
+
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'Left', 'url'=>'#'),
+				array('label'=>'Middle', 'url'=>'#'),
+				array('label'=>'Right', 'url'=>'#'),
+			),
+		)); ?>
+	</div>
+
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'1', 'url'=>'#'),
+				array('label'=>'2', 'url'=>'#'),
+				array('label'=>'3', 'url'=>'#'),
+				array('label'=>'4', 'url'=>'#'),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'5', 'url'=>'#'),
+				array('label'=>'6', 'url'=>'#'),
+				array('label'=>'7', 'url'=>'#'),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'8', 'url'=>'#'),
+			),
+		)); ?>
+	</div>
+
+	<h3>Dropdowns</h3>
+
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'Action', 'items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'primary',
+			'buttons'=>array(
+				array('label'=>'Action', 'items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'danger',
+			'buttons'=>array(
+				array('label'=>'Danger', 'items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+	</div>
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'success',
+			'buttons'=>array(
+				array('label'=>'Success', 'items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'info',
+			'buttons'=>array(
+				array('label'=>'Info', 'items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+	</div>
+
+	<h4>Source code</h4>
+
+<?php echo $parser->safeTransform("~~~
+[php]
+<div class=\"btn-toolbar\">
+	<?php \$this->widget('bootstrap.widgets.BootButtonGroup', array(
+		'type'=>'primary', // '', 'primary', 'info', 'success', 'warning', 'danger' or 'inverse'
+		'buttons'=>array(
+			array('label'=>'Action', 'items'=>array(
+				array('label'=>'Action'),
+				array('label'=>'Another action'),
+				array('label'=>'Something else'),
+				'---',
+				array('label'=>'Separate link'),
+			)),
+		),
+	)); ?>
+</div>
+~~~"); ?>
+
+	<h3>Split dropdowns</h3>
+
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'buttons'=>array(
+				array('label'=>'Action', 'url'=>'#'),
+				array('items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'primary',
+			'buttons'=>array(
+				array('label'=>'Action', 'url'=>'#'),
+				array('items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'danger',
+			'buttons'=>array(
+				array('label'=>'Danger', 'url'=>'#'),
+				array('items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+	</div>
+	<div class="btn-toolbar">
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'success',
+			'buttons'=>array(
+				array('label'=>'Success', 'url'=>'#'),
+				array('items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+		<?php $this->widget('bootstrap.widgets.BootButtonGroup', array(
+			'type'=>'info',
+			'buttons'=>array(
+				array('label'=>'Info', 'url'=>'#'),
+				array('items'=>array(
+					array('label'=>'Action'),
+					array('label'=>'Another action'),
+					array('label'=>'Something else'),
+					'---',
+					array('label'=>'Separate link'),
+				)),
+			),
+		)); ?>
+	</div>
+
+	<h4>Source code</h4>
+
+<?php echo $parser->safeTransform("~~~
+[php]
+<div class=\"btn-toolbar\">
+	<?php \$this->widget('bootstrap.widgets.BootButtonGroup', array(
+	    'type'=>'primary', // '', 'primary', 'info', 'success', 'warning', 'danger' or 'inverse'
+		'buttons'=>array(
+			array('label'=>'Action', 'url'=>'#'),
+			array('items'=>array(
+				array('label'=>'Action'),
+				array('label'=>'Another action'),
+				array('label'=>'Something else'),
+				'---',
+				array('label'=>'Separate link'),
+			)),
+		),
+	)); ?>
+</div>
+~~~"); ?>
+
+	<h3>Stateful</h3>
+
+	<p>
+		<?php $this->widget('bootstrap.widgets.BootButton', array(
+			'tag'=>'button',
+			'type'=>'primary',
+			'label'=>'Click me',
+			'loadingText'=>'loading...',
+			'htmlOptions'=>array('id'=>'buttonStateful'),
+		)); ?>
+	</p>
+
+	<?php Yii::app()->clientScript->registerScript('buttonStateful', "
+		$('#buttonStateful').click(function() {
+			var btn = $(this);
+			btn.button('loading'); // call the loading function
+			setTimeout(function() {
+				btn.button('reset'); // call the reset function
+			}, 3000);
+		});
+	"); ?>
+
+	<h4>Source code</h4>
+
+<?php echo $parser->safeTransform("~~~
+[php]
+<?php \$this->widget('bootstrap.widgets.BootButton', array(
+	'tag'=>'button',
+	'type'=>'primary',
+	'label'=>'Click me',
+	'loadingText'=>'loading...',
+	'htmlOptions'=>array('id'=>'buttonStateful'),
+)); ?>
+~~~"); ?>
+
+<?php echo $parser->safeTransform("~~~
+[javascript]
+$('#buttonStateful').click(function() {
+	var btn = $(this);
+	btn.button('loading'); // call the loading function
+	setTimeout(function() {
+		btn.button('reset'); // call the reset function
+	}, 3000);
+});
+~~~"); ?>
+
+	<h3>Single state</h3>
+
+	<p>
+		<?php $this->widget('bootstrap.widgets.BootButton', array(
+			'tag'=>'button',
+			'type'=>'primary',
+			'label'=>'Toggle me',
+			'toggle'=>true,
+		)); ?>
+	</p>
+
+	<h4>Source code</h4>
+
+<?php echo $parser->safeTransform("~~~
+[php]
+<?php \$this->widget('bootstrap.widgets.BootButton', array(
+	'tag'=>'button',
+	'type'=>'primary',
+	'label'=>'Toggle me',
+	'toggle'=>true,
+)); ?>
+~~~"); ?>
+
+	<h3>Checkbox and radio</h3>
+
+	<p>@todo</p>
+
+	<a class="top" href="#top">Back to top &uarr;</a>
+
+</section>
+
 <section id="comments">
 
 	<h2>Comments</h2>
 		'type'=>'pills',
 		'scrollspy'=>array('spy'=>'.subnav', 'offset'=>50),
 		'items'=>array(
-			array('label'=>'BootAlert', 'url'=>'#bootAlert'),
-			array('label'=>'BootCrumb', 'url'=>'#bootCrumb'),
-			array('label'=>'BootNavbar', 'url'=>'#bootNavbar'),
-			array('label'=>'BootMenu', 'url'=>'#bootMenu'),
-			array('label'=>'BootTabbed', 'url'=>'#bootTabbed'),
-			array('label'=>'BootDetailView', 'url'=>'#bootDetailView'),
-			array('label'=>'BootGridView', 'url'=>'#bootGridView'),
-			array('label'=>'BootThumbs', 'url'=>'#bootThumbs'),
-			array('label'=>'BootTooltip', 'url'=>'#bootTooltip'),
-			array('label'=>'BootPopover', 'url'=>'#bootPopover'),
-			array('label'=>'BootModal', 'url'=>'#bootModal'),
-			array('label'=>'BootActiveForm', 'url'=>'#bootActiveForm'),
+			array('label'=>'Alert', 'url'=>'#bootAlert'),
+			array('label'=>'Breadcrumb', 'url'=>'#bootCrumb'),
+			array('label'=>'Navbar', 'url'=>'#bootNavbar'),
+			array('label'=>'Menu', 'url'=>'#bootMenu'),
+			array('label'=>'Tabs', 'url'=>'#bootTabbed'),
+			array('label'=>'Detail view', 'url'=>'#bootDetailView'),
+			array('label'=>'Grid view', 'url'=>'#bootGridView'),
+			array('label'=>'Thumbnail', 'url'=>'#bootThumbs'),
+			array('label'=>'Tooltip', 'url'=>'#bootTooltip'),
+			array('label'=>'Popover', 'url'=>'#bootPopover'),
+			array('label'=>'Modal', 'url'=>'#bootModal'),
+			array('label'=>'Form', 'url'=>'#bootActiveForm'),
+			array('label'=>'Button', 'url'=>'#bootButton'),
 		),
 	)); ?>
 

File widgets/BootActiveForm.php

 			'model'=>get_class($model),
 			'name'=>CHtml::resolveName($model, $attribute),
 			'enableAjaxValidation'=>$enableAjaxValidation,
-			'inputContainer'=>'div.clearfix', // Bootstrap requires this
+			'inputContainer'=>'div.control-group', // Bootstrap requires this
 		);
 
 		$optionNames = array(

File widgets/BootButton.php

+<?php
+/**
+ * BootButton class file.
+ * @author Christoffer Niska <ChristofferNiska@gmail.com>
+ * @copyright Copyright &copy; Christoffer Niska 2011-
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @package bootstrap.widgets
+ * @since 0.9.10
+ */
+
+Yii::import('bootstrap.widgets.BootWidget');
+
+class BootButton extends BootWidget
+{
+	const TAG_LINK = 'a';
+	const TAG_BUTTON = 'button';
+
+	const TYPE_NORMAL = '';
+	const TYPE_PRIMARY = 'primary';
+	const TYPE_INFO = 'info';
+	const TYPE_SUCCESS = 'success';
+	const TYPE_WARNING = 'warning';
+	const TYPE_DANGER = 'danger';
+	const TYPE_INVERSE = 'inverse';
+
+	const SIZE_SMALL = 'small';
+	const SIZE_NORMAL = '';
+	const SIZE_LARGE = 'large';
+
+	public $tag = self::TAG_LINK;
+	public $type = self::TYPE_NORMAL;
+	public $size = self::SIZE_NORMAL;
+	public $icon;
+	public $label;
+	public $url;
+	public $items;
+	public $toggle;
+	public $loadingText;
+	public $completeText;
+	public $encodeLabel = true;
+
+	/**
+	 * Initializes the widget.
+	 */
+	public function init()
+	{
+		$class = array('btn');
+
+		$validTypes = array(self::TYPE_PRIMARY, self::TYPE_INFO, self::TYPE_SUCCESS,
+				self::TYPE_WARNING, self::TYPE_DANGER, self::TYPE_INVERSE);
+
+		if (isset($this->type) && in_array($this->type, $validTypes))
+			$class[] = 'btn-'.$this->type;
+
+		$validSizes = array(self::SIZE_SMALL, self::SIZE_LARGE);
+
+		if (isset($this->size) && in_array($this->size, $validSizes))
+			$class[] = 'btn-'.$this->size;
+
+		if ($this->hasDropdown())
+		{
+			$class[] = 'dropdown-toggle';
+			$this->htmlOptions['data-toggle'] = 'dropdown';
+
+			Yii::app()->bootstrap->registerDropdown();
+		}
+
+		$cssClass = implode(' ', $class);
+
+		if (isset($this->htmlOptions['class']))
+			$this->htmlOptions['class'] .= ' '.$cssClass;
+		else
+			$this->htmlOptions['class'] = $cssClass;
+
+		if ($this->encodeLabel)
+			$this->label = CHtml::encode($this->label);
+
+		if (isset($this->icon))
+		{
+			if (strpos($this->icon, 'icon') === false)
+				$this->icon = 'icon-'.implode(' icon-', explode(' ', $this->icon));
+
+			$this->label = '<i class="'.$this->icon.'"></i> '.$this->label;
+		}
+
+		if (!isset($this->url))
+			$this->url = '#';
+
+		if (isset($this->toggle) || isset($this->loadingText) || isset($this->completeText))
+		{
+			if (isset($this->toggle))
+				$this->htmlOptions['data-toggle'] = 'button';
+
+			if (isset($this->loadingText))
+				$this->htmlOptions['data-loading-text'] = $this->loadingText;
+
+			if (isset($this->completeText))
+				$this->htmlOptions['data-complete-text'] = $this->completeText;
+
+			Yii::app()->bootstrap->registerButton();
+		}
+	}
+
+	/**
+	 * Runs the widget.
+	 */
+	public function run()
+	{
+		if ($this->tag === self::TAG_LINK)
+			$this->htmlOptions['href'] = $this->url;
+
+		echo CHtml::openTag($this->tag, $this->htmlOptions);
+		echo $this->label;
+
+		if ($this->hasDropdown())
+			echo ' <span class="caret"></span>';
+
+		echo CHtml::closeTag($this->tag);
+
+		if ($this->hasDropdown())
+		{
+			$this->controller->widget('bootstrap.widgets.BootDropdown', array(
+				'items'=>$this->items,
+			));
+		}
+	}
+
+	/**
+	 * Returns whether the button has a dropdown.
+	 * @return bool the result.
+	 */
+	protected function hasDropdown()
+	{
+		return isset($this->items) && !empty($this->items);
+	}
+}

File widgets/BootButtonGroup.php

+<?php
+/**
+ * BootButtonGroup class file.
+ * @author Christoffer Niska <ChristofferNiska@gmail.com>
+ * @copyright Copyright &copy; Christoffer Niska 2011-
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @package bootstrap.widgets
+ * @since 0.9.10
+ */
+
+Yii::import('bootstrap.widgets.BootButton');
+Yii::import('bootstrap.widgets.BootWidget');
+
+class BootButtonGroup extends BootWidget
+{
+	const TOGGLE_CHECKBOX = 'checkbox';
+	const TOGGLE_RADIO = 'radio';
+
+	public $tag = BootButton::TAG_LINK;
+	public $type = BootButton::TYPE_NORMAL;
+	public $size = BootButton::SIZE_NORMAL;
+	public $encodeLabel = true;
+	public $buttons = array();
+	public $toggle;
+
+	public function init()
+	{
+		$cssClass = 'btn-group';
+		if (isset($this->htmlOptions['class']))
+			$this->htmlOptions['class'] .= ' '.$cssClass;
+		else
+			$this->htmlOptions['class'] = $cssClass;
+
+		$validToggles = array(self::TOGGLE_CHECKBOX, self::TOGGLE_RADIO);
+
+		if (isset($this->toggle) && in_array($this->toggle, $validToggles))
+		{
+			$this->htmlOptions['data-toggle'] = 'buttons-'.$this->toggle;
+			Yii::app()->bootstrap->registerButton();
+		}
+	}
+
+	public function run()
+	{
+		echo CHtml::openTag('div', $this->htmlOptions);
+
+		foreach ($this->buttons as $button)
+		{
+			$this->controller->widget('bootstrap.widgets.BootButton', array(
+				'tag'=>isset($button['tag']) ? $button['tag'] : $this->tag,
+				'type'=>$this->type,
+				'size'=>$this->size,
+				'icon'=>isset($button['icon']) ? $button['icon'] : null,
+				'label'=>isset($button['label']) ? $button['label'] : null,
+				'url'=>isset($button['url']) ? $button['url'] : null,
+				'items'=>isset($button['items']) ? $button['items'] : array(),
+				'htmlOptions'=>isset($button['htmlOptions']) ? $button['htmlOptions'] : array(),
+				'encodeLabel'=>isset($button['encodeLabel']) ? $button['encodeLabel'] : $this->encodeLabel,
+			));
+		}
+
+		echo '</div>';
+	}
+}

File widgets/BootDropdown.php

+<?php
+/**
+ * BootDropdown class file.
+ * @author Christoffer Niska <ChristofferNiska@gmail.com>
+ * @copyright Copyright &copy; Christoffer Niska 2011-
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @package bootstrap.widgets
+ * @since 0.9.10
+ */
+
+Yii::import('bootstrap.widgets.BootMenu');
+
+class BootDropdown extends BootMenu
+{
+	/**
+	 * Initializes the widget.
+	 */
+	public function init()
+	{
+		$route = $this->controller->getRoute();
+		$this->items = $this->normalizeItems($this->items, $route);
+
+		$cssClass = 'dropdown-menu';
+		if (isset($this->htmlOptions['class']))
+			$this->htmlOptions['class'] .= ' '.$cssClass;
+		else
+			$this->htmlOptions['class'] = $cssClass;
+
+		Yii::app()->bootstrap->registerDropdown();
+	}
+
+	/**
+	 * Renders the items in this menu.
+	 * @param array $items the menu items
+	 */
+	protected function renderItems($items)
+	{
+		foreach ($items as $item)
+		{
+			if (!is_array($item))
+				echo '<li class="divider"></li>';
+			else
+			{
+				if (!isset($item['itemOptions']))
+					$item['itemOptions'] = array();
+
+				$class = array();
+				if (isset($item['header']))
+					$class[] = 'nav-header';
+
+				$cssClass = implode(' ', $class);
+				if(isset($item['itemOptions']['class']))
+					$item['itemOptions']['class'] .= $cssClass;
+				else
+					$item['itemOptions']['class'] = $cssClass;
+
+				echo CHtml::openTag('li', $item['itemOptions']);
+				$menu = $this->renderItem($item);
+
+				if (isset($this->itemTemplate) || isset($item['template']))
+				{
+					$template = isset($item['template']) ? $item['template'] : $this->itemTemplate;
+					echo strtr($template, array('{menu}'=>$menu));
+				}
+				else
+					echo $menu;
+
+				echo '</li>';
+			}
+		}
+	}
+
+	/**
+	 * Normalizes the items in this menu.
+	 * @param array $items the items to be normalized
+	 * @param string $route the route of the current request
+	 * @return array the normalized menu items
+	 */
+	protected function normalizeItems($items, $route)
+	{
+		foreach ($items as $i => $item)
+		{
+			if (isset($item['visible']) && !$item['visible'])
+			{
+				unset($items[$i]);
+				continue;
+			}
+
+			if (!isset($item['label']))
+				$item['label'] = '';
+
+			if (isset($item['encodeLabel']) && $item['encodeLabel'])
+				$items[$i]['label'] = CHtml::encode($item['label']);
+
+			if (($this->encodeLabel && !isset($item['encodeLabel']))
+					|| (isset($item['encodeLabel']) && $item['encodeLabel'] !== false))
+				$items[$i]['label'] = CHtml::encode($item['label']);
+		}
+
+		return array_values($items);
+	}
+}

File widgets/BootMenu.php

 	 */
 	public function init()
 	{
-        if (!isset($this->htmlOptions['id']))
-            $this->htmlOptions['id'] = $this->getId();
-
 		$route = $this->controller->getRoute();
 		$this->items = $this->normalizeItems($this->items, $route);
+
+		$class = array('nav');
+
+		$validTypes = array(self::TYPE_UNSTYLED, self::TYPE_TABS, self::TYPE_PILLS, self::TYPE_LIST);
+
+		if (!empty($this->type) && in_array($this->type, $validTypes))
+			$class[] = 'nav-'.$this->type;
+
+		if ($this->type !== self::TYPE_LIST && $this->stacked)
+			$class[] = 'nav-stacked';
+
+		$cssClass = implode(' ', $class);
+		if (isset($this->htmlOptions['class']))
+			$this->htmlOptions['class'] .= ' '.$cssClass;
+		else
+			$this->htmlOptions['class'] = $cssClass;
+
+		if (isset($this->scrollspy) && is_array($this->scrollspy) && isset($this->scrollspy['spy']))
+		{
+			Yii::app()->bootstrap->registerScrollSpy();
+
+			if (!isset($this->scrollspy['subject']))
+				$this->scrollspy['subject'] = 'body';
+
+			if (!isset($this->scrollspy['offset']))
+				$this->scrollspy['offset'] = null;
+
+			Yii::app()->bootstrap->spyOn($this->scrollspy['subject'], $this->scrollspy['spy'], $this->scrollspy['offset']);
+		}
 	}
 
 	/**
 	 */
 	public function run()
 	{
-		if (!empty($this->items))
-		{
-			$cssClass = 'nav';
-
-			if (!empty($this->type))
-				$cssClass .= ' nav-'.$this->type;
-
-			if ($this->type !== self::TYPE_LIST && $this->stacked)
-				$cssClass .= ' nav-stacked';
-
-			if (isset($this->htmlOptions['class']))
-				$this->htmlOptions['class'] .= ' '.$cssClass;
-			else
-				$this->htmlOptions['class'] = $cssClass;
-
-			echo CHtml::openTag('ul', $this->htmlOptions).PHP_EOL;
-			$this->renderItems($this->items);
-			echo '</ul>';
-
-			Yii::app()->bootstrap->registerDropdown();
-
-			if (isset($this->scrollspy) && is_array($this->scrollspy) && isset($this->scrollspy['spy']))
-			{
-				Yii::app()->bootstrap->registerScrollSpy();
-
-				if (!isset($this->scrollspy['subject']))
-					$this->scrollspy['subject'] = 'body';
-
-				if (!isset($this->scrollspy['offset']))
-					$this->scrollspy['offset'] = null;
-
-				Yii::app()->bootstrap->spyOn($this->scrollspy['subject'], $this->scrollspy['spy'], $this->scrollspy['offset']);
-			}
-		}
+		echo CHtml::openTag('ul', $this->htmlOptions);
+		$this->renderItems($this->items);
+		echo '</ul>';
 	}
 
 	/**
 				echo '<li class="divider"></li>';
 			else
 			{
-				$htmlOptions = isset($item['itemOptions']) ? $item['itemOptions'] : array();
+				if (!isset($item['itemOptions']))
+					$item['itemOptions'] = array();
 
-				$cssClass = '';
+				$class = array();
+
+				if (isset($item['header']))
+					$class[] = 'nav-header';
 
 				if ($item['active'] || (isset($item['items']) && $this->isChildActive($item['items'])))
-					$cssClass .= ' active';
+					$class[] = 'active';
 
 				if (isset($item['items']))
-					$cssClass .= ' dropdown';
+					$class[] = 'dropdown';
 
-				if(isset($htmlOptions['class']))
-					$htmlOptions['class'] .= $cssClass;
+				$cssClass = implode(' ', $class);
+				if(isset($item['itemOptions']['class']))
+					$item['itemOptions']['class'] .= $cssClass;
 				else
-					$htmlOptions['class'] = $cssClass;
+					$item['itemOptions']['class'] = $cssClass;
 
-				echo CHtml::openTag('li', $htmlOptions);
-
+				echo CHtml::openTag('li', $item['itemOptions']);
 				$menu = $this->renderItem($item);
 
 				if (isset($this->itemTemplate) || isset($item['template']))
 
 				if(isset($item['items']) && !empty($item['items']))
 				{
-					if (isset($item['dropdownOptions']['class']))
-						$item['dropdownOptions']['class'] .= ' dropdown-menu';
-					else
-						$item['dropdownOptions']['class'] = 'dropdown-menu';
-
-					$dropdownOptions = isset($item['dropdownOptions'])
-							? $item['dropdownOptions'] : $this->dropdownOptions;
-
-					echo CHtml::openTag('ul', $dropdownOptions).PHP_EOL;
-					$this->renderItems($item['items']);
-					echo '</ul>'.PHP_EOL;
+					$this->controller->widget('bootstrap.widgets.BootDropdown', array(
+						'items'=>$item['items'],
+						'htmlOptions'=>isset($this->dropdownOptions) ? $this->dropdownOptions : array(),
+					));
 				}
 
-				echo '</li>'.PHP_EOL;
+				echo '</li>';
 			}
 		}
 	}
 
 	/**
-	 * Renders a single item in this menu.
+	 * Renders a single item in the dropdown.
 	 * @param array $item the item configuration
 	 * @return string the rendered item
 	 */
 	protected function renderItem($item)
 	{
-		if (isset($item['icon'])) {
+		if (!isset($item['linkOptions']))
+			$item['linkOptions'] = array();
+
+		if (isset($item['icon']))
+		{
 			if (strpos($item['icon'], 'icon') === false)
-                $item['icon'] = 'icon-'.implode(' icon-', explode(' ', $item['icon']));
+			{
+				$pieces = explode(' ', $item['icon']);
+                $item['icon'] = 'icon-'.implode(' icon-', $pieces);
+			}
 
 			$item['label'] = '<i class="'.$item['icon'].'"></i> '.$item['label'];
 		}
 
 		if (isset($item['items']))
 		{
-			if (!isset($item['url']))
-				$item['url'] = '#';
-
 			if (isset($item['linkOptions']['class']))
 				$item['linkOptions']['class'] .= ' dropdown-toggle';
 			else
 				$item['linkOptions']['class'] = 'dropdown-toggle';
 
-			$item['label'] .= ' <b class="caret"></b>';
 			$item['linkOptions']['data-toggle'] = 'dropdown';
+			$item['label'] .= ' <span class="caret"></span>';
 		}
 
+		if (!isset($item['header']) && !isset($item['url']))
+			$item['url'] = '#';
+
 		if (isset($item['url']))
-			return CHtml::link($item['label'], $item['url'], isset($item['linkOptions']) ? $item['linkOptions'] : array());
+			return CHtml::link($item['label'], $item['url'], $item['linkOptions']);
 		else
 			return $item['label'];
 	}
 	}
 
 	/**
-	 * Returns whether a child item is activte.
+	 * Returns whether a child item is active.
 	 * @param array $items the items to check
 	 * @return boolean the result
 	 */
 	protected function isChildActive($items)
 	{
 		foreach ($items as $item)
-		{
 			if (isset($item['active']) && $item['active'] === true)
 				return true;
-		}
 
 		return false;
 	}
 		if (isset($item['url']) && is_array($item['url']) && !strcasecmp(trim($item['url'][0], '/'), $route))
 		{
 			if (count($item['url']) > 1)
-			{
 				foreach (array_splice($item['url'], 1) as $name=>$value)
-				{
 					if (!isset($_GET[$name]) || $_GET[$name] != $value)
 						return false;
-				}
-			}
 
 			return true;
 		}

File widgets/input/BootInput.php

 	 */
 	public function run()
 	{
+		$errorCss = $this->model->hasErrors($this->attribute) ? ' '.CHtml::$errorCss : '';
+		echo CHtml::openTag('div', array('class'=>'control-group'.$errorCss));
+
 		switch ($this->type)
 		{
 			case self::TYPE_CHECKBOX:
 			default:
 				throw new CException(__CLASS__.': Failed to run widget! Type is invalid.');
 		}
+
+		echo '</div>';
 	}
 
 	/**

File widgets/input/BootInputHorizontal.php

 class BootInputHorizontal extends BootInput
 {
 	/**
-	 * Runs the widget.
-	 */
-	public function run()
-	{
-		$errorCss = $this->model->hasErrors($this->attribute) ? ' '.CHtml::$errorCss : '';
-		echo CHtml::openTag('div', array('class'=>'control-group'.$errorCss));
-		parent::run();
-		echo '</div>';
-	}
-
-	/**
 	 * Returns the label for this block.
 	 * @param array $htmlOptions additional HTML attributes
 	 * @return string the label