From edf983f4d76545b7ba38c1d79028f73c29932330 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Fri, 24 May 2013 12:45:42 +0200 Subject: [PATCH 01/70] added tabs --- framework/yii/bootstrap/Tabs.php | 171 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 framework/yii/bootstrap/Tabs.php diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php new file mode 100644 index 0000000..454eb48 --- /dev/null +++ b/framework/yii/bootstrap/Tabs.php @@ -0,0 +1,171 @@ + array('class'=>'nav-tabs'), + * 'items' => array( + * array( + * 'header' => 'One', + * 'content' => 'Anim pariatur cliche...', + * ), + * array( + * 'header' => 'Two', + * 'headerOptions' => array(...), + * 'content' => 'Anim pariatur cliche...', + * 'options' => array('id'=>'myveryownID'), + * ), + * array( + * 'header' => 'Dropdown', + * 'items' => array( + * array( + * 'header' => '@Dropdown1', + * 'content' => 'Anim pariatur cliche...', + * ), + * ), + * ), + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/javascript.html#tabs + * @author Antonio Ramirez + * @since 2.0 + */ +class Tabs extends Widget +{ + /** + * @var array list of tabs in the tabs widget. Each array element represents a single + * tab with the following structure: + * + * ```php + * array( + * // required, the header (HTML) of the tab + * 'header' => 'Tab label', + * // optional the HTML attributes of the tab header `LI` tag container + * 'headerOptions'=> array(...), + * // required, the content (HTML) of the tab + * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', + * // optional the HTML attributes of the tab content container + * 'options'=> array(...), + * // optional, an array of items so to dipslay a dropdown menu on the tab header + * // ***Important*** if `items` is set, then `content` will be ignored + * 'items'=> array(...) + * ) + * ``` + */ + public $items = array(); + /** + * @var int keeps track of the tabs count so to provide a correct id in case it has not been specified. + */ + protected $counter = 0; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderHeaders($this->items, $this->options) . "\n"; + $this->counter = 0; // reset tab counter + echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; + echo $this->renderContents($this->items) . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('tab'); + } + + /** + * @param array $items the items to render in the header. + * @param array $options the HTML attributes of the menu container. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderHeaders($items, $options = array()) + { + $headers = array(); + + for ($i = 0, $count = count($items); $i < $count; $i++) { + $item = $items[$i]; + if (!isset($item['header'])) { + throw new InvalidConfigException("The 'header' option is required."); + } + $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + if ($this->counter === 0) { + $this->addCssClass($headerOptions, 'active'); + } + if (isset($item['items'])) { + $this->getView()->registerAssetBundle("yii/bootstrap/dropdown"); + $this->addCssClass($headerOptions, 'dropdown'); + $headers[] = Html::tag( + 'li', + Html::a($item['header'] . ' ', "#", array( + 'class' => 'dropdown-toggle', + 'data-toggle' => 'dropdown' + )) . + $this->renderHeaders($item['items'], array('class' => 'dropdown-menu')), + $headerOptions + ); + } else { + $contentOptions = ArrayHelper::getValue($item, 'options', array()); + $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $this->counter++); + $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); + } + } + + return Html::tag('ul', implode("\n", $headers), $options); + } + + /** + * Renders tabs contents as specified on [[items]]. + * @param array $items the items to get the contents from. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderContents($items) + { + $contents = array(); + foreach ($items as $item) { + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $options = ArrayHelper::getValue($item, 'options', array()); + $this->addCssClass($options, 'tab-pane'); + + if ($this->counter === 0) { + $this->addCssClass($options, 'active'); + } + if (isset($item['items'])) { + $contents[] = $this->renderContents($item['items']); + } else { + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $this->counter++); + $contents[] = Html::tag('div', $item['content'], $options); + } + } + + return implode("\n", $contents); + } +} \ No newline at end of file From 6316463ad00dd30748fe6e20b41335970f9c3f56 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Fri, 24 May 2013 14:17:46 +0200 Subject: [PATCH 02/70] add fixes --- framework/yii/bootstrap/Tabs.php | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 454eb48..8bc61da 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -70,10 +70,6 @@ class Tabs extends Widget * ``` */ public $items = array(); - /** - * @var int keeps track of the tabs count so to provide a correct id in case it has not been specified. - */ - protected $counter = 0; /** @@ -91,7 +87,6 @@ class Tabs extends Widget public function run() { echo $this->renderHeaders($this->items, $this->options) . "\n"; - $this->counter = 0; // reset tab counter echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; echo $this->renderContents($this->items) . "\n"; echo Html::endTag('div') . "\n"; @@ -101,20 +96,20 @@ class Tabs extends Widget /** * @param array $items the items to render in the header. * @param array $options the HTML attributes of the menu container. + * @param integer $index the starting index of header item. Used to set ids. * @return string the rendering result. * @throws InvalidConfigException */ - protected function renderHeaders($items, $options = array()) + protected function renderHeaders($items, $options = array(), $index = 0) { $headers = array(); - for ($i = 0, $count = count($items); $i < $count; $i++) { - $item = $items[$i]; + foreach ($items as $item) { if (!isset($item['header'])) { throw new InvalidConfigException("The 'header' option is required."); } $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($this->counter === 0) { + if ($index === 0) { $this->addCssClass($headerOptions, 'active'); } if (isset($item['items'])) { @@ -126,12 +121,13 @@ class Tabs extends Widget 'class' => 'dropdown-toggle', 'data-toggle' => 'dropdown' )) . - $this->renderHeaders($item['items'], array('class' => 'dropdown-menu')), + $this->renderHeaders($item['items'], array('class' => 'dropdown-menu'), $index++), $headerOptions ); } else { + $contentOptions = ArrayHelper::getValue($item, 'options', array()); - $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $this->counter++); + $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $index++); $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); } } @@ -142,10 +138,11 @@ class Tabs extends Widget /** * Renders tabs contents as specified on [[items]]. * @param array $items the items to get the contents from. + * @param integer $index the starting index (for recursion) * @return string the rendering result. * @throws InvalidConfigException */ - protected function renderContents($items) + protected function renderContents($items, $index = 0) { $contents = array(); foreach ($items as $item) { @@ -155,13 +152,13 @@ class Tabs extends Widget $options = ArrayHelper::getValue($item, 'options', array()); $this->addCssClass($options, 'tab-pane'); - if ($this->counter === 0) { - $this->addCssClass($options, 'active'); - } if (isset($item['items'])) { - $contents[] = $this->renderContents($item['items']); + $contents[] = $this->renderContents($item['items'], $index++); } else { - $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $this->counter++); + if ($index === 0) { + $this->addCssClass($options, 'active'); + } + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $index++); $contents[] = Html::tag('div', $item['content'], $options); } } From 39c2107599b9ef67651095b4bfd88e26fa09971a Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Sun, 26 May 2013 21:09:12 +0400 Subject: [PATCH 03/70] Refactoring --- framework/yii/jui/Tabs.php | 77 +++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index 052ffe7..7b9d4d8 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -20,16 +20,32 @@ use yii\helpers\Html; * echo Tabs::widget(array( * 'items' => array( * array( - * 'header' => 'One', + * 'header' => 'Tab one', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * ), * array( - * 'header' => 'Two', - * 'headerOptions' => array(...), + * 'header' => 'Tab two', + * 'headerOptions' => array( + * 'tag' => 'li', + * ), * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => array(...), + * 'options' => array( + * 'tag' => 'div', + * ), * ), * ), + * 'options' => array( + * 'tag' => 'div', + * ), + * 'itemOptions' => array( + * 'tag' => 'div', + * ), + * 'headerOptions' => array( + * 'tag' => 'li', + * ), + * 'clientOptions' => array( + * 'collapsible' => false, + * ), * )); * ``` * @@ -39,24 +55,10 @@ use yii\helpers\Html; */ class Tabs extends Widget { - /** - * @var array list of tabs in the tabs widget. Each array element represents a single - * tab with the following structure: - * - * ```php - * array( - * // required, the header (HTML) of the tab - * 'header' => 'Tab label', - * // required, the content (HTML) of the tab - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * // optional the HTML attributes of the tab content container - * 'options'=> array(...), - * // optional the HTML attributes of the tab header container - * 'headerOptions'=> array(...), - * ) - * ``` - */ + public $options = array(); public $items = array(); + public $itemOptions = array(); + public $headerOptions = array(); /** @@ -65,42 +67,24 @@ class Tabs extends Widget public function run() { echo Html::beginTag('div', $this->options) . "\n"; - echo $this->renderHeaders() . "\n"; - echo $this->renderContents() . "\n"; + echo $this->renderItems() . "\n"; echo Html::endTag('div') . "\n"; $this->registerWidget('tabs'); } /** - * Renders tabs headers as specified on [[items]]. + * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. */ - protected function renderHeaders() + protected function renderItems() { $headers = array(); + $items = array(); foreach ($this->items as $n => $item) { if (!isset($item['header'])) { throw new InvalidConfigException("The 'header' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - $id = isset($options['id']) ? $options['id'] : $this->options['id'] . '-tab' . $n; - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - $headers[] = Html::tag('li', Html::a($item['header'], "#$id"), $headerOptions); - } - - return Html::tag('ul', implode("\n", $headers)); - } - - /** - * Renders tabs contents as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - protected function renderContents() - { - $contents = array(); - foreach ($this->items as $n => $item) { if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } @@ -108,9 +92,10 @@ class Tabs extends Widget if (!isset($options['id'])) { $options['id'] = $this->options['id'] . '-tab' . $n; } - $contents[] = Html::tag('div', $item['content'], $options); + $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + $headers[] = Html::tag('li', Html::a($item['header'], '#' . $options['id']), $headerOptions); + $items[] = Html::tag('div', $item['content'], $options); } - - return implode("\n", $contents); + return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); } } From 73900b4bd10d8343e87a79ebbe83af6cca6e9764 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Sun, 26 May 2013 22:03:51 +0400 Subject: [PATCH 04/70] jQuery UI tabs rework --- framework/yii/jui/Tabs.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index 7b9d4d8..c4ef3c5 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -20,19 +20,27 @@ use yii\helpers\Html; * echo Tabs::widget(array( * 'items' => array( * array( - * 'header' => 'Tab one', + * 'label' => 'Tab one', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * ), * array( - * 'header' => 'Tab two', - * 'headerOptions' => array( - * 'tag' => 'li', - * ), + * 'label' => 'Tab two', * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', * 'options' => array( * 'tag' => 'div', * ), * ), + * array( + * 'label' => 'Tab three', + * 'content' => 'Specific content...', + * 'options' => array( + * 'id' => 'my-tab', + * ), + * ), + * array( + * 'label' => 'Ajax tab', + * 'url' => 'http://www.yiiframework.com', + * ), * ), * 'options' => array( * 'tag' => 'div', @@ -40,9 +48,6 @@ use yii\helpers\Html; * 'itemOptions' => array( * 'tag' => 'div', * ), - * 'headerOptions' => array( - * 'tag' => 'li', - * ), * 'clientOptions' => array( * 'collapsible' => false, * ), @@ -58,7 +63,7 @@ class Tabs extends Widget public $options = array(); public $items = array(); public $itemOptions = array(); - public $headerOptions = array(); + public $headerTemplate = '
  • {label}
  • '; /** @@ -76,6 +81,7 @@ class Tabs extends Widget * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. + * @todo rework */ protected function renderItems() { From e84f5649e6c6ef18cf4ec0207485b56ee20cee2c Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Mon, 27 May 2013 00:26:23 +0400 Subject: [PATCH 05/70] jQuery UI tabs full rework --- framework/yii/jui/Tabs.php | 95 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index c4ef3c5..b0f54d9 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -29,17 +29,20 @@ use yii\helpers\Html; * 'options' => array( * 'tag' => 'div', * ), + * 'headerOptions' => array( + * 'class' => 'my-class', + * ), * ), * array( - * 'label' => 'Tab three', - * 'content' => 'Specific content...', + * 'label' => 'Tab with custom id', + * 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...', * 'options' => array( - * 'id' => 'my-tab', + * 'id' => 'my-tab', * ), * ), * array( * 'label' => 'Ajax tab', - * 'url' => 'http://www.yiiframework.com', + * 'ajax' => array('ajax/content'), * ), * ), * 'options' => array( @@ -48,6 +51,9 @@ use yii\helpers\Html; * 'itemOptions' => array( * 'tag' => 'div', * ), + * 'headerOptions' => array( + * 'class' => 'my-class', + * ), * 'clientOptions' => array( * 'collapsible' => false, * ), @@ -60,10 +66,52 @@ use yii\helpers\Html; */ class Tabs extends Widget { + /** + * @var array the HTML attributes for the widget container tag. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the container tag of this widget + */ public $options = array(); + /** + * @var array list of tab items. Each item can be an array of the following structure: + * + * ~~~ + * array( + * 'label' => 'Tab header label', + * 'content' => 'Tab item content', + * 'ajax' => 'http://www.yiiframework.com', //or array('ajax/action'), + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => array(), + * // the HTML attributes of the header container tag. This will overwrite "headerOptions". + * 'headerOptions' = array(), + * // @todo comment. + * 'template' + * ) + * ~~~ + */ public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the item container tags. + */ public $itemOptions = array(); - public $headerTemplate = '
  • {label}
  • '; + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = array(); + /** + * @var string + * @todo comment. + */ + public $linkTemplate = '{label}'; + /** + * @var boolean + * @todo comment. + */ + public $encodeLabels = true; /** @@ -71,9 +119,11 @@ class Tabs extends Widget */ public function run() { - echo Html::beginTag('div', $this->options) . "\n"; + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + echo Html::beginTag($tag, $options) . "\n"; echo $this->renderItems() . "\n"; - echo Html::endTag('div') . "\n"; + echo Html::endTag($tag) . "\n"; $this->registerWidget('tabs'); } @@ -81,26 +131,35 @@ class Tabs extends Widget * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. - * @todo rework */ protected function renderItems() { $headers = array(); $items = array(); foreach ($this->items as $n => $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); - } - if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' option is required."); + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - if (!isset($options['id'])) { - $options['id'] = $this->options['id'] . '-tab' . $n; + if (isset($item['ajax'])) { + $url = $item['ajax']; + } else { + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' or 'ajax' option is required."); + } + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + if (!isset($options['id'])) { + $options['id'] = $this->options['id'] . '-tab' . $n; + } + $url = '#' . $options['id']; + $items[] = Html::tag($tag, $item['content'], $options); } $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - $headers[] = Html::tag('li', Html::a($item['header'], '#' . $options['id']), $headerOptions); - $items[] = Html::tag('div', $item['content'], $options); + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); + $headers[] = Html::tag('li', strtr($template, array( + '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], + '{url}' => Html::url($url), + )), $headerOptions); } return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); } From 5038c35f6dd8031d20b2da11cc9f0e9b622ed8d4 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Mon, 27 May 2013 01:45:36 +0400 Subject: [PATCH 06/70] Refactoring --- framework/yii/jui/Tabs.php | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index b0f54d9..bbc3534 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -42,7 +42,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Ajax tab', - * 'ajax' => array('ajax/content'), + * 'url' => array('ajax/content'), * ), * ), * 'options' => array( @@ -75,19 +75,13 @@ class Tabs extends Widget /** * @var array list of tab items. Each item can be an array of the following structure: * - * ~~~ - * array( - * 'label' => 'Tab header label', - * 'content' => 'Tab item content', - * 'ajax' => 'http://www.yiiframework.com', //or array('ajax/action'), - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => array(), - * // the HTML attributes of the header container tag. This will overwrite "headerOptions". - * 'headerOptions' = array(), - * // @todo comment. - * 'template' - * ) - * ~~~ + * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label + * will be HTML-encoded. + * - content: string, @todo comment + * - url: mixed, @todo comment + * - template: string, optional, @todo comment + * - options: array, optional, @todo comment + * - headerOptions: array, optional, @todo comment */ public $items = array(); /** @@ -103,13 +97,11 @@ class Tabs extends Widget */ public $headerOptions = array(); /** - * @var string - * @todo comment. + * @var string @todo comment */ public $linkTemplate = '{label}'; /** - * @var boolean - * @todo comment. + * @var boolean whether the labels for header items should be HTML-encoded. */ public $encodeLabels = true; @@ -140,11 +132,11 @@ class Tabs extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - if (isset($item['ajax'])) { - $url = $item['ajax']; + if (isset($item['url'])) { + $url = Html::url($item['url']); } else { if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' or 'ajax' option is required."); + throw new InvalidConfigException("The 'content' or 'url' option is required."); } $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); $tag = ArrayHelper::remove($options, 'tag', 'div'); @@ -158,7 +150,7 @@ class Tabs extends Widget $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); $headers[] = Html::tag('li', strtr($template, array( '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], - '{url}' => Html::url($url), + '{url}' => $url, )), $headerOptions); } return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); From 5fe6d9910f7bafad098d362f07498e1d3683d00a Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 11:50:12 +0200 Subject: [PATCH 07/70] Added dropdown widget --- framework/yii/bootstrap/Dropdown.php | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 framework/yii/bootstrap/Dropdown.php diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php new file mode 100644 index 0000000..fa966d3 --- /dev/null +++ b/framework/yii/bootstrap/Dropdown.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +class Dropdown extends Widget +{ + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * ```php + * array( + * // required, the label of the item link + * 'label' => 'Menu label', + * // optional, url of the item link + * 'url' => '', + * // optional the HTML attributes of the item link + * 'urlOptions'=> array(...), + * // optional the HTML attributes of the item + * 'options'=> array(...), + * // optional, an array of items that configure a sub menu of the item + * // note: if `items` is set, then `url` of the parent item will be ignored and automatically set to "#" + * 'items'=> array(...) + * ) + * ``` + * If you wish to display a `divider`, use any string. The widget will render a bootstrap dropdown divider: + * + * ```html + *
  • + * ``` + */ + public $items = array(); + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'dropdown-menu'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('ul', $this->options) . "\n"; + echo $this->renderContents() . "\n"; + echo Html::endTag('ul') . "\n"; + $this->registerPlugin('dropdown'); + } + + /** + * Renders dropdown contents as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderContents() + { + $contents = array(); + foreach ($this->items as $item) { + if (is_string($item)) { + $contents[] = Html::tag('li', '', array('class' => 'divider')); + continue; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + + $options = ArrayHelper::getValue($item, 'options', array()); + $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); + $urlOptions['tabindex'] = '-1'; + + if (isset($item['items'])) { + $this->addCssClass($options, 'dropdown-submenu'); + $content = Html::a($item['label'], '#', $urlOptions) . $this->dropdown($item['items']); + } else { + $content = Html::a($item['label'], ArrayHelper::getValue($item, 'url', '#'), $urlOptions); + } + $contents[] = Html::tag('li', $content , $options); + } + + return implode("\n", $contents); + } + + /** + * Generates a dropdown menu. + * @param array $items the configuration of the dropdown items. See [[items]]. + * @return string the generated dropdown menu + * @see items + */ + protected function dropdown($items) + { + return Dropdown::widget(array('items' => $items, 'clientOptions' => false)); + } +} \ No newline at end of file From af5dbba6dae3c145e8dd00ea9d4bc5c1410c8fe4 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 11:59:55 +0200 Subject: [PATCH 08/70] Tabs refactored --- framework/yii/bootstrap/Tabs.php | 165 +++++++++++++++++++++++++++------------ 1 file changed, 115 insertions(+), 50 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 8bc61da..0f049fd 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -32,10 +32,15 @@ use yii\helpers\Html; * ), * array( * 'header' => 'Dropdown', - * 'items' => array( + * 'dropdown' => array( * array( - * 'header' => '@Dropdown1', - * 'content' => 'Anim pariatur cliche...', + * 'label' => 'DropdownA', + * 'content' => 'DropdownA, Anim pariatur cliche...', + * ), + * '-', // divider + * array( + * 'label' => 'DropdownB', + * 'content' => 'DropdownB, Anim pariatur cliche...', * ), * ), * ), @@ -63,9 +68,14 @@ class Tabs extends Widget * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * // optional the HTML attributes of the tab content container * 'options'=> array(...), - * // optional, an array of items so to dipslay a dropdown menu on the tab header - * // ***Important*** if `items` is set, then `content` will be ignored - * 'items'=> array(...) + * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. This + * // attribute, apart from the original [[Dropdown::items]] settings, also has two extra special keys: + * // - content: required, teh content (HTML) of teh tab the menu item is linked to + * // - contentOptions: optional the HTML attributes of the tab content container + * // note: if `dropdown` is set, then `content` will be ignored + * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown + * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * 'dropdown'=> array(...) * ) * ``` */ @@ -79,6 +89,8 @@ class Tabs extends Widget { parent::init(); $this->addCssClass($this->options, 'nav'); + $this->items = $this->normalizeItems(); + } /** @@ -86,83 +98,136 @@ class Tabs extends Widget */ public function run() { - echo $this->renderHeaders($this->items, $this->options) . "\n"; + echo Html::beginTag('ul', $this->options) . "\n"; + echo $this->renderHeaders() . "\n"; + echo Html::endTag('ul'); echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; - echo $this->renderContents($this->items) . "\n"; + echo $this->renderContents() . "\n"; echo Html::endTag('div') . "\n"; $this->registerPlugin('tab'); } /** - * @param array $items the items to render in the header. - * @param array $options the HTML attributes of the menu container. - * @param integer $index the starting index of header item. Used to set ids. + * Renders tabs navigation. * @return string the rendering result. - * @throws InvalidConfigException */ - protected function renderHeaders($items, $options = array(), $index = 0) + protected function renderHeaders() { $headers = array(); - - foreach ($items as $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); - } - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($index === 0) { - $this->addCssClass($headerOptions, 'active'); - } - if (isset($item['items'])) { - $this->getView()->registerAssetBundle("yii/bootstrap/dropdown"); - $this->addCssClass($headerOptions, 'dropdown'); + foreach ($this->items['headers'] as $item) { + $options = ArrayHelper::getValue($item, 'options', array()); + if (isset($item['dropdown'])) { $headers[] = Html::tag( 'li', Html::a($item['header'] . ' ', "#", array( 'class' => 'dropdown-toggle', 'data-toggle' => 'dropdown' )) . - $this->renderHeaders($item['items'], array('class' => 'dropdown-menu'), $index++), - $headerOptions + Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)), + $options ); - } else { - - $contentOptions = ArrayHelper::getValue($item, 'options', array()); - $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $index++); - $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); + continue; } - } + $id = ArrayHelper::getValue($item, 'url'); + $headers[] = Html::tag('li', Html::a($item['header'], "{$id}", array('data-toggle' => 'tab')), $options); - return Html::tag('ul', implode("\n", $headers), $options); + } + return implode("\n", $headers); } /** - * Renders tabs contents as specified on [[items]]. - * @param array $items the items to get the contents from. - * @param integer $index the starting index (for recursion) + * Renders tabs contents. * @return string the rendering result. - * @throws InvalidConfigException */ - protected function renderContents($items, $index = 0) + protected function renderContents() { $contents = array(); - foreach ($items as $item) { - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } + foreach ($this->items['contents'] as $item) { $options = ArrayHelper::getValue($item, 'options', array()); $this->addCssClass($options, 'tab-pane'); + $contents[] = Html::tag('div', $item['content'], $options); + + } + return implode("\n", $contents); + } + + /** + * Normalizes the [[items]] property to divide headers from contents and to ease its rendering when there are + * headers with dropdown menus. + * @return array the normalized tabs items + * @throws InvalidConfigException + */ + protected function normalizeItems() + { + $items = array(); + $index = 0; + foreach ($this->items as $item) { + if (!isset($item['header'])) { + throw new InvalidConfigException("The 'header' option is required."); + } + if (!isset($item['content']) && !isset($item['dropdown'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $header = $content = array(); + $header['header'] = ArrayHelper::getValue($item, 'header'); + $header['options'] = ArrayHelper::getValue($item, 'headerOptions', array()); + if ($index === 0) { + $this->addCssClass($header['options'], 'active'); + } + if (isset($item['dropdown'])) { + $this->addCssClass($header['options'], 'dropdown'); + + $self = $this; + $dropdown = function ($list) use (&$dropdown, &$items, &$index, $self) { + $ddItems = $content = array(); + foreach ($list as $item) { + if (is_string($item)) { + $ddItems[] = $item; + continue; + } + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + if (isset($item['items'])) { + $item['items'] = $dropdown($item['items']); + } else { + $content['content'] = ArrayHelper::remove($item, 'content'); + $content['options'] = ArrayHelper::remove($item, 'contentOptions', array()); + if ($index === 0) { + $self->addCssClass($content['options'], 'active'); + $self->addCssClass($item['options'], 'active'); + } + $content['options']['id'] = ArrayHelper::getValue( + $content['options'], + 'id', + $self->options['id'] . '-tab' . $index++); + $item['url'] = '#' . $content['options']['id']; + $item['urlOptions']['data-toggle'] = 'tab'; + + $items['contents'][] = $content; + } + $ddItems[] = $item; + } + return $ddItems; + }; + $header['dropdown'] = $dropdown($item['dropdown']); - if (isset($item['items'])) { - $contents[] = $this->renderContents($item['items'], $index++); } else { + $content['content'] = ArrayHelper::getValue($item, 'content'); + $content['options'] = ArrayHelper::getValue($item, 'options', array()); if ($index === 0) { - $this->addCssClass($options, 'active'); + $this->addCssClass($content['options'], 'active'); } - $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $index++); - $contents[] = Html::tag('div', $item['content'], $options); + $content['options']['id'] = ArrayHelper::getValue( + $content['options'], + 'id', + $this->options['id'] . '-tab' . $index++); + + $header['url'] = "#" . ArrayHelper::getValue($content['options'], 'id'); + $items['contents'][] = $content; } + $items['headers'][] = $header; } - - return implode("\n", $contents); + return $items; } } \ No newline at end of file From cc9b175c5f9637e194b86f831857f1181a502d8f Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 16:24:37 +0200 Subject: [PATCH 09/70] Added Nav widget closes #367 --- framework/yii/bootstrap/Nav.php | 137 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 framework/yii/bootstrap/Nav.php diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php new file mode 100644 index 0000000..f373961 --- /dev/null +++ b/framework/yii/bootstrap/Nav.php @@ -0,0 +1,137 @@ + array( + * array( + * 'label' => 'Home', + * 'url' => '/', + * 'options' => array(...), + * 'active' => true, + * ), + * array( + * 'label' => 'Dropdown', + * 'dropdown' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * ), + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/components.html#nav + * @author Antonio Ramirez + * @since 2.0 + */ +class Nav extends Widget +{ + /** + * @var array list of items in the nav widget. Each array element represents a single + * menu item with the following structure: + * + * ```php + * array( + * // required, the menu item label. + * 'label' => 'Nav item label', + * // optional, the URL of the menu item. Defaults to "#" + * 'url'=> '#', + * // optional, the HTML options of the URL. + * 'urlOptions' => array(...), + * // optional the HTML attributes of the item container (LI). + * 'options' => array(...), + * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. + * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown + * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * 'dropdown'=> array(...) + * ) + * ``` + * + * Optionally, you can also use a plain string instead of an array element. + */ + public $items = array(); + + + /** + * Initializes the widget. + */ + public function init() + { + $this->addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + } + + /** + * Renders widget items. + */ + public function renderItems() + { + $items = array(); + foreach ($this->items as $item) { + $items[] = $this->renderItem($item); + } + + return Html::tag('ul', implode("\n", $items), $this->options); + } + + /** + * Renders a widget's item. + * @param mixed $item the item to render. + * @return string the rendering result. + * @throws InvalidConfigException + */ + public function renderItem($item) + { + if (is_string($item)) { + return $item; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $label = $item['label']; + $url = ArrayHelper::getValue($item, 'url', '#'); + $options = ArrayHelper::getValue($item, 'options', array()); + $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); + $dropdown = null; + + // does it has a dropdown widget? + if (isset($item['dropdown'])) { + $urlOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($options, 'dropdown'); + $this->addCssClass($urlOptions, 'dropdown-toggle'); + $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); + $dropdown = Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)); + } + + return Html::tag('li', Html::a($label, $url, $urlOptions) . $dropdown, $options); + } +} \ No newline at end of file From 70f1d4169996c1259bfa02d8627c2f5dd0d34c0f Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 16:27:33 +0200 Subject: [PATCH 10/70] Added NavBar widget closes #437 --- framework/yii/bootstrap/NavBar.php | 169 +++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 framework/yii/bootstrap/NavBar.php diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php new file mode 100644 index 0000000..1ed5a9a --- /dev/null +++ b/framework/yii/bootstrap/NavBar.php @@ -0,0 +1,169 @@ + array( + * // a Nav widget + * array( + * // defaults to Nav anyway. + * 'class' => 'yii\bootstrap\Nav', + * // widget configuration + * 'options' => array( + * 'items' => array( + * array( + * 'label' => 'Home', + * 'url' => '/', + * 'options' => array('class' => 'active'), + * ), + * array( + * 'label' => 'Dropdown', + * // configure a dropdown menu + * 'dropdown' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#' + * ), + * ) + * ), + * ) + * ), + * ), + * // you can also use strings + * '', + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/components.html#navbar + * @author Antonio Ramirez + * @since 2.0 + */ +class NavBar extends Widget +{ + public $brand; + /** + * @var array list of menu items in the navbar widget. Each array element represents a single + * menu item with the following structure: + * + * ```php + * array( + * // optional, the menu item class type of the widget to render. Defaults to "Nav" widget. + * 'class' => 'Menu item class type', + * // required, the configuration options of the widget. + * 'options'=> array(...), + * ), + * // optionally, you can pass a string + * '', + * ``` + * + * Optionally, you can also use a plain string instead of an array element. + */ + public $items = array(); + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'navbar'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options); + echo $this->renderItems(); + echo Html::endTag('div'); + } + + /** + * @return string the rendering items. + */ + protected function renderItems() + { + $items = array(); + + foreach ($this->items as $item) { + $items[] = $this->renderItem($item); + } + $contents =implode("\n", $items); + if (self::$responsive === true) { + $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); + $contents = + Html::tag('div', + $this->renderToggleButton() . + $this->brand . "\n" . + Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')), + array('class' => 'container')); + + } else { + $contents = $this->brand . "\n" . $contents; + } + return Html::tag('div', $contents, array('class' => 'navbar-inner')); + } + + /** + * Renders a item. The item can be a string, a custom class or a Nav widget (defaults if no class specified. + * @param mixed $item the item to render. If array, it is assumed the configuration of a widget being `class` + * required and if not specified, then defaults to `yii\bootstrap\Nav`. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderItem($item) + { + if (is_string($item)) { + return $item; + } + $config = ArrayHelper::getValue($item, 'options', array()); + $config['class'] = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); + $widget = \Yii::createObject($config); + ob_start(); + $widget->run(); + return ob_get_clean(); + } + + /** + * Renders collapsible toggle button. + * @return string the rendering toggle button. + */ + protected function renderToggleButton() + { + $items = array(); + for ($i = 0; $i < 3; $i++) { + $items[] = Html::tag('span', '', array('class' => 'icon-bar')); + } + return Html::tag('a', implode("\n", $items), array( + 'class' => 'btn btn-navbar', + 'data-toggle' => 'collapse', + 'data-target' => 'div.navbar-collapse', + )) . "\n"; + } +} \ No newline at end of file From e1b145a7068b886764efc4b0dfb9de4fa0526bca Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 17:12:27 +0200 Subject: [PATCH 11/70] Enhance Dropdown to use items as strings. Remove string=divider rule --- framework/yii/bootstrap/Dropdown.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index fa966d3..7a8cd51 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -40,11 +40,7 @@ class Dropdown extends Widget * 'items'=> array(...) * ) * ``` - * If you wish to display a `divider`, use any string. The widget will render a bootstrap dropdown divider: - * - * ```html - *
  • - * ``` + * Additionally, you can also configure a dropdown item as string. */ public $items = array(); @@ -80,7 +76,7 @@ class Dropdown extends Widget $contents = array(); foreach ($this->items as $item) { if (is_string($item)) { - $contents[] = Html::tag('li', '', array('class' => 'divider')); + $contents[] = $item; continue; } if (!isset($item['label'])) { From ff70e151d9c9b9eb8f63f9dfd8c12361dc57bb35 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Tue, 28 May 2013 20:47:39 +0400 Subject: [PATCH 12/70] Comment for content --- framework/yii/jui/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index bbc3534..c7a4b5a 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -77,7 +77,7 @@ class Tabs extends Widget * * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label * will be HTML-encoded. - * - content: string, @todo comment + * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified. * - url: mixed, @todo comment * - template: string, optional, @todo comment * - options: array, optional, @todo comment From 000fdc3de067a8e4b7314a21edf3815f86371e7b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 17:27:37 -0400 Subject: [PATCH 13/70] formatter WIP. --- framework/yii/base/Formatter.php | 288 ++++++++++++++++++++++++++++ framework/yii/i18n/Formatter.php | 232 ++++++++++++++++++++++ tests/unit/framework/base/FormatterTest.php | 101 ++++++++++ 3 files changed, 621 insertions(+) create mode 100644 framework/yii/base/Formatter.php create mode 100644 framework/yii/i18n/Formatter.php create mode 100644 tests/unit/framework/base/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php new file mode 100644 index 0000000..a31fac9 --- /dev/null +++ b/framework/yii/base/Formatter.php @@ -0,0 +1,288 @@ + + * @since 2.0 + */ +class Formatter extends Component +{ + /** + * @var string the default format string to be used to format a date using PHP date() function. + */ + public $dateFormat = 'Y/m/d'; + /** + * @var string the default format string to be used to format a time using PHP date() function. + */ + public $timeFormat = 'h:i:s A'; + /** + * @var string the default format string to be used to format a date and time using PHP date() function. + */ + public $datetimeFormat = 'Y/m/d h:i:s A'; + /** + * @var array the format used to format a number with PHP number_format() function. + * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". + * They correspond to the number of digits after the decimal point, the character displayed as the decimal point + * and the thousands separator character. + */ + public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); + /** + * @var array the text to be displayed when formatting a boolean value. The first element corresponds + * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). + */ + public $booleanFormat; + + + /** + * Initializes the component. + */ + public function init() + { + if (empty($this->booleanFormat)) { + $this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes')); + } + } + + /** + * Formats the value as is without any formatting. + * This method simply returns back the parameter without any format. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asRaw($value) + { + return $value; + } + + /** + * Formats the value as an HTML-encoded plain text. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asText($value) + { + return Html::encode($value); + } + + /** + * Formats the value as an HTML-encoded plain text with newlines converted into breaks. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asNtext($value) + { + return nl2br(Html::encode($value)); + } + + /** + * Formats the value as HTML-encoded text paragraphs. + * Each text paragraph is enclosed within a `

    ` tag. + * One or multiple consecutive empty lines divide two paragraphs. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asParagraphs($value) + { + return str_replace('

    ', '', + '

    ' . preg_replace('/[\r\n]{2,}/', "

    \n

    ", Html::encode($value)) . '

    ' + ); + } + + /** + * Formats the value as HTML text. + * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks. + * Use [[asRaw()]] if you do not want any purification of the value. + * @param mixed $value the value to be formatted + * @param array|null $config the configuration for the HTMLPurifier class. + * @return string the formatted result + */ + public function asHtml($value, $config = null) + { + return HtmlPurifier::process($value, $config); + } + + /** + * Formats the value as a mailto link. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asEmail($value) + { + return Html::mailto($value); + } + + /** + * Formats the value as an image tag. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asImage($value) + { + return Html::img($value); + } + + /** + * Formats the value as a hyperlink. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asUrl($value) + { + $url = $value; + if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { + $url = 'http://' . $url; + } + return Html::a(Html::encode($value), $url); + } + + /** + * Formats the value as a boolean. + * @param mixed $value the value to be formatted + * @return string the formatted result + * @see booleanFormat + */ + public function asBoolean($value) + { + return $value ? $this->booleanFormat[1] : $this->booleanFormat[0]; + } + + /** + * Formats the value as a date. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see dateFormat + */ + public function asDate($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->dateFormat : $format, $value); + } + + /** + * Formats the value as a time. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[timeFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see timeFormat + */ + public function asTime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->timeFormat : $format, $value); + } + + /** + * Formats the value as a datetime. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[datetimeFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see datetimeFormat + */ + public function asDatetime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->datetimeFormat : $format, $value); + } + + /** + * Normalizes the given datetime value as one that can be taken by various date/time formatting methods. + * @param mixed $value the datetime value to be normalized. + * @return mixed the normalized datetime value + */ + protected function normalizeDatetimeValue($value) + { + if (is_string($value)) { + if (ctype_digit($value) || $value[0] === '-' && ctype_digit(substr($value, 1))) { + return (int)$value; + } else { + return strtotime($value); + } + } elseif ($value instanceof DateTime) { + return $value->getTimestamp(); + } else { + return (int)$value; + } + } + + /** + * Formats the value as an integer. + * @param mixed $value the value to be formatted + * @return string the formatting result. + */ + public function asInteger($value) + { + if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) { + return $matches[1]; + } else { + $value = (int)$value; + return "$value"; + } + } + + /** + * Formats the value as a double number. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @return string the formatting result. + */ + public function asDouble($value, $decimals = 2) + { + return sprintf("%.{$decimals}f", $value); + } + + /** + * Formats the value as a decimal number using the PHP number_format() function. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @param string $decimalSeparator the character displayed as the decimal point + * @param string $thousandSeparator the character displayed as the thousands separator character. + * @return string the formatted result + */ + public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + { + return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + } +} diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php new file mode 100644 index 0000000..a0570a0 --- /dev/null +++ b/framework/yii/i18n/Formatter.php @@ -0,0 +1,232 @@ + + * @since 2.0 + */ +class Formatter extends \yii\base\Formatter +{ + /** + * @var string the locale ID that is used to localize the date and number formatting. + * If not set, [[\yii\base\Application::language]] will be used. + */ + public $locale; + /** + * @var string the default format string to be used to format a date using PHP date() function. + */ + public $dateFormat = 'short'; + /** + * @var string the default format string to be used to format a time using PHP date() function. + */ + public $timeFormat = 'short'; + /** + * @var string the default format string to be used to format a date and time using PHP date() function. + */ + public $datetimeFormat = 'short'; + /** + * @var array the options to be set for the NumberFormatter objects. Please refer to + */ + public $numberFormatOptions = array(); + + + /** + * Initializes the component. + * This method will check if the "intl" PHP extension is installed and set the + * default value of [[locale]]. + * @throws InvalidConfigException if the "intl" PHP extension is not installed. + */ + public function init() + { + if (!extension_loaded('intl')) { + throw new InvalidConfigException('The "intl" PHP extension is not install. It is required to format data values in localized formats.'); + } + if ($this->locale === null) { + $this->locale = Yii::$app->language; + } + parent::init(); + } + + private $_dateFormats = array( + 'short' => IntlDateFormatter::SHORT, + 'medium' => IntlDateFormatter::MEDIUM, + 'long' => IntlDateFormatter::LONG, + 'full' => IntlDateFormatter::FULL, + ); + + /** + * Formats the value as a date. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see dateFormat + */ + public function asDate($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->dateFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a time. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see timeFormat + */ + public function asTime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->timeFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a datetime. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see datetimeFormat + */ + public function asDatetime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->datetimeFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a decimal number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asDecimal($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); + } + + /** + * Formats the value as a currency number. + * @param mixed $value the value to be formatted + * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use. + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asCurrency($value, $currency = 'USD', $format = null) + { + $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); + } + + /** + * Formats the value as a percent number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asPercent($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); + } + + /** + * Formats the value as a scientific number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asScientific($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); + } + + /** + * Creates a number formatter based on the given type and format. + * @param integer $type the type of the number formatter + * @param string $format the format to be used + * @return NumberFormatter the created formatter instance + */ + protected function createNumberFormatter($type, $format) + { + $formatter = new NumberFormatter($this->locale, $type); + if ($format !== null) { + $formatter->setPattern($format); + } + if (!empty($this->numberFormatOptions)) { + foreach ($this->numberFormatOptions as $name => $attribute) { + $formatter->setAttribute($name, $attribute); + } + } + return $formatter; + } +} diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php new file mode 100644 index 0000000..53d8770 --- /dev/null +++ b/tests/unit/framework/base/FormatterTest.php @@ -0,0 +1,101 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + $this->formatter = new Formatter(); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsRaw() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = 123; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = '<>'; + $this->assertSame($value, $this->formatter->asRaw($value)); + } + + public function testAsText() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asText($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asText($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asText($value)); + } + + public function testAsNtext() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asNtext($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asNtext($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asNtext($value)); + $value = "123\n456"; + $this->assertSame("123
    \n456", $this->formatter->asNtext($value)); + } + + public function testAsParagraphs() + { + $value = '123'; + $this->assertSame("

    $value

    ", $this->formatter->asParagraphs($value)); + $value = 123; + $this->assertSame("

    $value

    ", $this->formatter->asParagraphs($value)); + $value = '<>'; + $this->assertSame('

    <>

    ', $this->formatter->asParagraphs($value)); + $value = "123\n456"; + $this->assertSame("

    123\n456

    ", $this->formatter->asParagraphs($value)); + $value = "123\n\n456"; + $this->assertSame("

    123

    \n

    456

    ", $this->formatter->asParagraphs($value)); + $value = "123\n\n\n456"; + $this->assertSame("

    123

    \n

    456

    ", $this->formatter->asParagraphs($value)); + } + + public function testAsHtml() + { + // todo + } + + public function testAsEmail() + { + $value = 'test@sample.com'; + $this->assertSame("$value", $this->formatter->asEmail($value)); + } + + public function testAsImage() + { + $value = 'http://sample.com/img.jpg'; + $this->assertSame("\"\"", $this->formatter->asImage($value)); + } +} From 9a970370129000ebd1b416a7ce05a62056a266c8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:55:26 -0400 Subject: [PATCH 14/70] Finished Formatter. --- framework/yii/base/Formatter.php | 12 +--- framework/yii/i18n/Formatter.php | 8 +-- tests/unit/framework/base/FormatterTest.php | 59 +++++++++++++++++- tests/unit/framework/i18n/FormatterTest.php | 93 +++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 tests/unit/framework/i18n/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index a31fac9..835b657 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -38,13 +38,6 @@ class Formatter extends Component */ public $datetimeFormat = 'Y/m/d h:i:s A'; /** - * @var array the format used to format a number with PHP number_format() function. - * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". - * They correspond to the number of digits after the decimal point, the character displayed as the decimal point - * and the thousands separator character. - */ - public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); - /** * @var array the text to be displayed when formatting a boolean value. The first element corresponds * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). */ @@ -274,14 +267,15 @@ class Formatter extends Component } /** - * Formats the value as a decimal number using the PHP number_format() function. + * Formats the value as a number with decimal and thousand separators. + * This method calls the PHP number_format() function to do the formatting. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point * @param string $decimalSeparator the character displayed as the decimal point * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result */ - public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) { return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); } diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a0570a0..a90f5c9 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -170,7 +170,7 @@ class Formatter extends \yii\base\Formatter */ public function asDecimal($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); } /** @@ -183,7 +183,7 @@ class Formatter extends \yii\base\Formatter */ public function asCurrency($value, $currency = 'USD', $format = null) { - $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); + return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); } /** @@ -195,7 +195,7 @@ class Formatter extends \yii\base\Formatter */ public function asPercent($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); } /** @@ -207,7 +207,7 @@ class Formatter extends \yii\base\Formatter */ public function asScientific($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); } /** diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 53d8770..3e7ac5f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -84,7 +84,7 @@ class FormatterTest extends TestCase public function testAsHtml() { - // todo + // todo: dependency on HtmlPurifier } public function testAsEmail() @@ -98,4 +98,61 @@ class FormatterTest extends TestCase $value = 'http://sample.com/img.jpg'; $this->assertSame("\"\"", $this->formatter->asImage($value)); } + + public function testAsBoolean() + { + $value = true; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = false; + $this->assertSame('No', $this->formatter->asBoolean($value)); + $value = "111"; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = ""; + $this->assertSame('No', $this->formatter->asBoolean($value)); + } + + public function testAsDate() + { + $value = time(); + $this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value)); + $this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d')); + } + + public function testAsTime() + { + $value = time(); + $this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value)); + $this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s')); + } + + public function testAsDatetime() + { + $value = time(); + $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value)); + $this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s')); + } + + public function testAsInteger() + { + $value = 123; + $this->assertSame("$value", $this->formatter->asInteger($value)); + $value = 123.23; + $this->assertSame("123", $this->formatter->asInteger($value)); + $value = 'a'; + $this->assertSame("0", $this->formatter->asInteger($value)); + } + + public function testAsDouble() + { + $value = 123.12; + $this->assertSame("123.12", $this->formatter->asDouble($value)); + $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + } + + public function testAsNumber() + { + $value = 123123.123; + $this->assertSame("123,123", $this->formatter->asNumber($value)); + $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + } } diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php new file mode 100644 index 0000000..cf479d1 --- /dev/null +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + $this->mockApplication(); + $this->formatter = new Formatter(array( + 'locale' => 'en_US', + )); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsDecimal() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asDecimal($value)); + $value = '123456'; + $this->assertSame("123,456", $this->formatter->asDecimal($value)); + $value = '-123456.123'; + $this->assertSame("-123,456.123", $this->formatter->asDecimal($value)); + } + + public function testAsPercent() + { + $value = '123'; + $this->assertSame('12,300%', $this->formatter->asPercent($value)); + $value = '0.1234'; + $this->assertSame("12%", $this->formatter->asPercent($value)); + $value = '-0.009343'; + $this->assertSame("-1%", $this->formatter->asPercent($value)); + } + + public function testAsScientific() + { + $value = '123'; + $this->assertSame('1.23E2', $this->formatter->asScientific($value)); + $value = '123456'; + $this->assertSame("1.23456E5", $this->formatter->asScientific($value)); + $value = '-123456.123'; + $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value)); + } + + public function testAsCurrency() + { + $value = '123'; + $this->assertSame('$123.00', $this->formatter->asCurrency($value)); + $value = '123.456'; + $this->assertSame("$123.46", $this->formatter->asCurrency($value)); + $value = '-123456.123'; + $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value)); + } + + public function testDate() + { + $time = time(); + $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); + $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); + $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); + + $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); + $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); + $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); + } +} From 2f1021830d5cd92da15cc99d25f27bcffdebf915 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:58:35 -0400 Subject: [PATCH 15/70] Added "formatter" app component. --- framework/yii/base/Application.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index eb0a0d3..d38f6a9 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -279,6 +279,15 @@ class Application extends Module } /** + * Returns the formatter component. + * @return \yii\base\Formatter the formatter application component. + */ + public function getFormatter() + { + return $this->getComponent('formatter'); + } + + /** * Returns the request component. * @return \yii\web\Request|\yii\console\Request the request component */ @@ -333,6 +342,9 @@ class Application extends Module 'errorHandler' => array( 'class' => 'yii\base\ErrorHandler', ), + 'formatter' => array( + 'class' => 'yii\base\Formatter', + ), 'i18n' => array( 'class' => 'yii\i18n\I18N', ), From 6aba9ede8ba20200b0e1f840703b0559a513f30b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:04 -0400 Subject: [PATCH 16/70] Removed breaking test. --- tests/unit/framework/i18n/FormatterTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index cf479d1..0f53c2a 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -87,7 +87,5 @@ class FormatterTest extends TestCase $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); - $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); - $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); } } From 33dbe8fb43ad706e635e07ec7fa0a2a7ba75be02 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:39 -0400 Subject: [PATCH 17/70] Removed breaking tests. --- tests/unit/framework/i18n/FormatterTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index 0f53c2a..f742f22 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -83,9 +83,6 @@ class FormatterTest extends TestCase { $time = time(); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); - $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); - $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); - $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); } } From f690f0db13425ca7ed528e590b34d5359fb9b238 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:24:08 -0400 Subject: [PATCH 18/70] Fixes issue #441: InvalidRequestException should be HttpException --- framework/yii/base/Controller.php | 32 +------------------ framework/yii/base/InvalidRequestException.php | 26 --------------- framework/yii/web/Controller.php | 44 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 57 deletions(-) delete mode 100644 framework/yii/base/InvalidRequestException.php diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 543605f..756f34a 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -151,43 +151,13 @@ class Controller extends Component /** * Binds the parameters to the action. * This method is invoked by [[Action]] when it begins to run with the given parameters. - * This method will check the parameter names that the action requires and return - * the provided parameters according to the requirement. If there is any missing parameter, - * an exception will be thrown. * @param Action $action the action to be bound with parameters * @param array $params the parameters to be bound to the action * @return array the valid parameters that the action can run with. - * @throws InvalidRequestException if there are missing parameters. */ public function bindActionParams($action, $params) { - if ($action instanceof InlineAction) { - $method = new \ReflectionMethod($this, $action->actionMethod); - } else { - $method = new \ReflectionMethod($action, 'run'); - } - - $args = array(); - $missing = array(); - foreach ($method->getParameters() as $param) { - $name = $param->getName(); - if (array_key_exists($name, $params)) { - $args[] = $params[$name]; - unset($params[$name]); - } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); - } else { - $missing[] = $name; - } - } - - if (!empty($missing)) { - throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array( - '{params}' => implode(', ', $missing), - ))); - } - - return $args; + return array(); } /** diff --git a/framework/yii/base/InvalidRequestException.php b/framework/yii/base/InvalidRequestException.php deleted file mode 100644 index f4806ce..0000000 --- a/framework/yii/base/InvalidRequestException.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @since 2.0 - */ -class InvalidRequestException extends UserException -{ - /** - * @return string the user-friendly name of this exception - */ - public function getName() - { - return \Yii::t('yii', 'Invalid Request'); - } -} - diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 517f4b4..026c078 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -8,6 +8,8 @@ namespace yii\web; use Yii; +use yii\base\HttpException; +use yii\base\InlineAction; /** * Controller is the base class of Web controllers. @@ -19,6 +21,48 @@ use Yii; class Controller extends \yii\base\Controller { /** + * Binds the parameters to the action. + * This method is invoked by [[Action]] when it begins to run with the given parameters. + * This method will check the parameter names that the action requires and return + * the provided parameters according to the requirement. If there is any missing parameter, + * an exception will be thrown. + * @param \yii\base\Action $action the action to be bound with parameters + * @param array $params the parameters to be bound to the action + * @return array the valid parameters that the action can run with. + * @throws HttpException if there are missing parameters. + */ + public function bindActionParams($action, $params) + { + if ($action instanceof InlineAction) { + $method = new \ReflectionMethod($this, $action->actionMethod); + } else { + $method = new \ReflectionMethod($action, 'run'); + } + + $args = array(); + $missing = array(); + foreach ($method->getParameters() as $param) { + $name = $param->getName(); + if (array_key_exists($name, $params)) { + $args[] = $params[$name]; + unset($params[$name]); + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + $missing[] = $name; + } + } + + if (!empty($missing)) { + throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array( + '{params}' => implode(', ', $missing), + ))); + } + + return $args; + } + + /** * Creates a URL using the given route and parameters. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. From 07011a2542af5370244d7bf640184d06a6bbed11 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 01:49:19 +0200 Subject: [PATCH 19/70] Fixed typos in new Formatter classes --- framework/yii/base/Formatter.php | 12 ++++++------ framework/yii/i18n/Formatter.php | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 835b657..8945d74 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -162,12 +162,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one + * If null, [[dateFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see dateFormat @@ -183,12 +183,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[timeFormat]] will be used. The format string should be the one + * If null, [[timeFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see timeFormat @@ -204,12 +204,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[datetimeFormat]] will be used. The format string should be the one + * If null, [[datetimeFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see datetimeFormat diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a90f5c9..d688a15 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -76,7 +76,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -106,7 +106,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -136,7 +136,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * From 2553e9a043002893ff8ce1aeb42b99af33737a01 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:56:06 -0400 Subject: [PATCH 20/70] Fixes issue #439 --- apps/advanced/README.md | 28 ++++---- apps/advanced/backstage/config/main.php | 2 +- apps/advanced/composer.json | 3 - apps/advanced/console/config/main.php | 2 +- apps/advanced/frontend/config/main.php | 2 +- apps/advanced/init | 112 ++++++++++++++++++++++++++++++++ apps/advanced/init.bat | 20 ++++++ apps/advanced/install | 112 -------------------------------- apps/advanced/install.bat | 20 ------ 9 files changed, 149 insertions(+), 152 deletions(-) create mode 100755 apps/advanced/init create mode 100644 apps/advanced/init.bat delete mode 100755 apps/advanced/install delete mode 100644 apps/advanced/install.bat diff --git a/apps/advanced/README.md b/apps/advanced/README.md index a2bcdd4..c443c90 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -67,32 +67,32 @@ If you do not have [Composer](http://getcomposer.org/), you may download it from curl -s http://getcomposer.org/installer | php ~~~ -You can then install the Bootstrap Application using the following command: +You can then install the application using the following command: ~~~ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced ~~~ -Now you should be able to access: - -- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` -- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` - -assuming `yii-advanced` is directly under the document root of your Web server. - ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + GETTING STARTED --------------- -After template application and its dependencies are downloaded you need to initialize it and set some config values to -match your application requirements. +After you install the application, you have to conduct the following steps to initialize +the installed application. You only need to do these once for all. -1. Execute `install` command selecting `dev` as environment. -2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`. -3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. -4. In `common/config/params.php` set your database details in `components.db` values. +1. Execute the `init` command and select `dev` as environment. +2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. +3. In `common/config/params.php` set your database details in `components.db` values. + +Now you should be able to access: + +- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` +- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` + +assuming `yii-advanced` is directly under the document root of your Web server. diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backstage/config/main.php index 4898bfd..6e55c47 100644 --- a/apps/advanced/backstage/config/main.php +++ b/apps/advanced/backstage/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-backend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index db97efd..95b3b13 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -36,9 +36,6 @@ "frontend/runtime", "frontend/www/assets" - ], - "yii-install-executable": [ - "yii" ] } } diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php index cceb311..37db1d2 100644 --- a/apps/advanced/console/config/main.php +++ b/apps/advanced/console/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-console', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index 02a66c9..e53cfe8 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-frontend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/init b/apps/advanced/init new file mode 100755 index 0000000..17ed854 --- /dev/null +++ b/apps/advanced/init @@ -0,0 +1,112 @@ +#!/usr/bin/env php + $name) { + echo " [$i] $name\n"; +} +echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; +$answer = trim(fgets(STDIN)); +if (!ctype_digit($answer) || !isset($envNames[$answer])) { + echo "\n Quit initialization.\n"; + return; +} + +$env = $envs[$envNames[$answer]]; +echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; +$answer = trim(fgets(STDIN)); +if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + return; +} + +echo "\n Start initialization ...\n\n"; +$files = getFileList("$root/environments/{$env['path']}"); +$all = false; +foreach ($files as $file) { + if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { + break; + } +} + +if (isset($env['writable'])) { + foreach ($env['writable'] as $writable) { + echo " chmod 0777 $writable\n"; + @chmod("$root/$writable", 0777); + } +} + +if (isset($env['executable'])) { + foreach ($env['executable'] as $executable) { + echo " chmod 0755 $executable\n"; + @chmod("$root/$executable", 0755); + } +} + +echo "\n ... initialization completed.\n\n"; + +function getFileList($root, $basePath = '') +{ + $files = array(); + $handle = opendir($root); + while (($path = readdir($handle)) !== false) { + if ($path === '.svn' || $path === '.' || $path === '..') { + continue; + } + $fullPath = "$root/$path"; + $relativePath = $basePath === '' ? $path : "$basePath/$path"; + if (is_dir($fullPath)) { + $files = array_merge($files, getFileList($fullPath, $relativePath)); + } else { + $files[] = $relativePath; + } + } + closedir($handle); + return $files; +} + +function copyFile($root, $source, $target, &$all) +{ + if (!is_file($root . '/' . $source)) { + echo " skip $target ($source not exist)\n"; + return true; + } + if (is_file($root . '/' . $target)) { + if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { + echo " unchanged $target\n"; + return true; + } + if ($all) { + echo " overwrite $target\n"; + } else { + echo " exist $target\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + $answer = trim(fgets(STDIN)); + if (!strncasecmp($answer, 'q', 1)) { + return false; + } else { + if (!strncasecmp($answer, 'y', 1)) { + echo " overwrite $target\n"; + } else { + if (!strncasecmp($answer, 'a', 1)) { + echo " overwrite $target\n"; + $all = true; + } else { + echo " skip $target\n"; + return true; + } + } + } + } + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; + } + echo " generate $target\n"; + @mkdir(dirname($root . '/' . $target), 0777, true); + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; +} diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat new file mode 100644 index 0000000..dc2cd83 --- /dev/null +++ b/apps/advanced/init.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line install script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright © 2012 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%install" %* + +@endlocal diff --git a/apps/advanced/install b/apps/advanced/install deleted file mode 100755 index 6864440..0000000 --- a/apps/advanced/install +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env php - $name) { - echo " [$i] $name\n"; -} -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit installation.\n"; - return; -} - -$env = $envs[$envNames[$answer]]; -echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit installation.\n"; - return; -} - -echo "\n Start installation ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { - break; - } -} - -if (isset($env['writable'])) { - foreach ($env['writable'] as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -if (isset($env['executable'])) { - foreach ($env['executable'] as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -echo "\n ... installation completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = array(); - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} diff --git a/apps/advanced/install.bat b/apps/advanced/install.bat deleted file mode 100644 index dc2cd83..0000000 --- a/apps/advanced/install.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line install script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%install" %* - -@endlocal From 66a03d61b69289089413a9dee66ef57e92e6ea34 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Wed, 29 May 2013 04:02:25 +0400 Subject: [PATCH 21/70] Lost array_merge() --- framework/yii/jui/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index c7a4b5a..d25eaa2 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -146,7 +146,7 @@ class Tabs extends Widget $url = '#' . $options['id']; $items[] = Html::tag($tag, $item['content'], $options); } - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); $headers[] = Html::tag('li', strtr($template, array( '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], From 8b6447876ad8570e4410fd917abff61969e64788 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:15:43 +0200 Subject: [PATCH 22/70] refactored numberformatting in base\Formatter added decimalSeparator and thousandSeparator properties. issue #48 --- framework/yii/base/Formatter.php | 20 +++++++++++++++----- tests/unit/framework/base/FormatterTest.php | 23 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 8945d74..d15e5f2 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -42,6 +42,14 @@ class Formatter extends Component * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). */ public $booleanFormat; + /** + * @var string the character displayed as the decimal point when formatting a number. + */ + public $decimalSeparator = '.'; + /** + * @var string the character displayed as the thousands separator character when formatting a number. + */ + public $thousandSeparator = ','; /** @@ -257,13 +265,15 @@ class Formatter extends Component /** * Formats the value as a double number. + * Property [[decimalSeparator]] will be used to represent the decimal point. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point * @return string the formatting result. + * @see decimalSeparator */ public function asDouble($value, $decimals = 2) { - return sprintf("%.{$decimals}f", $value); + return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); } /** @@ -271,12 +281,12 @@ class Formatter extends Component * This method calls the PHP number_format() function to do the formatting. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point - * @param string $decimalSeparator the character displayed as the decimal point - * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result + * @see decimalSeparator + * @see thousandSeparator */ - public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0) { - return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); } } diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 3e7ac5f..e9a909f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -140,6 +140,10 @@ class FormatterTest extends TestCase $this->assertSame("123", $this->formatter->asInteger($value)); $value = 'a'; $this->assertSame("0", $this->formatter->asInteger($value)); + $value = -123.23; + $this->assertSame("-123", $this->formatter->asInteger($value)); + $value = "-123abc"; + $this->assertSame("-123", $this->formatter->asInteger($value)); } public function testAsDouble() @@ -147,12 +151,29 @@ class FormatterTest extends TestCase $value = 123.12; $this->assertSame("123.12", $this->formatter->asDouble($value)); $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123; + $this->assertSame("123.00", $this->formatter->asDouble($value)); + $this->formatter->decimalSeparator = ','; + $value = 123.12; + $this->assertSame("123,12", $this->formatter->asDouble($value)); + $this->assertSame("123,1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123123.123; + $this->assertSame("123123,12", $this->formatter->asDouble($value)); } public function testAsNumber() { $value = 123123.123; $this->assertSame("123,123", $this->formatter->asNumber($value)); - $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + $this->assertSame("123,123.12", $this->formatter->asNumber($value, 2)); + $this->formatter->decimalSeparator = ','; + $this->formatter->thousandSeparator = ' '; + $this->assertSame("123 123", $this->formatter->asNumber($value)); + $this->assertSame("123 123,12", $this->formatter->asNumber($value, 2)); + $this->formatter->thousandSeparator = ''; + $this->assertSame("123123", $this->formatter->asNumber($value)); + $this->assertSame("123123,12", $this->formatter->asNumber($value, 2)); } } From c75e78c20a47b4b575ccef2405901d368aeccb44 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:17:00 +0200 Subject: [PATCH 23/70] Updated requirements about yii\i18n\Formatter --- framework/yii/requirements/requirements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 0dbc1fc..63aa70d 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -43,6 +43,6 @@ return array( 'mandatory' => false, 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'), 'by' => 'Internationalization support', - 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator.' + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator or the yii\i18n\Formatter class.' ), ); \ No newline at end of file From 5e8154ed7fd2ef0140027e136a2268d590fc5026 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 21:07:35 -0400 Subject: [PATCH 24/70] Removed unused method. --- framework/yii/base/Controller.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 756f34a..af33b63 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -242,18 +242,6 @@ class Controller extends Component } /** - * Validates the parameter being bound to actions. - * This method is invoked when parameters are being bound to the currently requested action. - * Child classes may override this method to throw exceptions when there are missing and/or unknown parameters. - * @param Action $action the currently requested action - * @param array $missingParams the names of the missing parameters - * @param array $unknownParams the unknown parameters (name => value) - */ - public function validateActionParams($action, $missingParams, $unknownParams) - { - } - - /** * @return string the controller ID that is prefixed with the module ID (if any). */ public function getUniqueId() From 2f051e9dab43a8ba5d537879f30a7f6f3bae150c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 03:40:18 +0200 Subject: [PATCH 25/70] Use class constants for Event declatration in ActionFiler --- framework/yii/base/ActionFilter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php index d69c0fe..20ff142 100644 --- a/framework/yii/base/ActionFilter.php +++ b/framework/yii/base/ActionFilter.php @@ -30,8 +30,8 @@ class ActionFilter extends Behavior public function events() { return array( - 'beforeAction' => 'beforeFilter', - 'afterAction' => 'afterFilter', + Controller::EVENT_BEFORE_ACTION => 'beforeFilter', + Controller::EVENT_AFTER_ACTION => 'afterFilter', ); } From 249f90f5ee78cee47f6fcb990ae10d2a56d24e1a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 22:06:31 -0400 Subject: [PATCH 26/70] Implemented MaskedInput widget. --- framework/yii/assets.php | 7 + framework/yii/assets/jquery.maskedinput.js | 338 +++++++++++++++++++++++++++++ framework/yii/widgets/ActiveForm.php | 4 +- framework/yii/widgets/InputWidget.php | 64 ++++++ framework/yii/widgets/MaskedInput.php | 136 ++++++++++++ 5 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 framework/yii/assets/jquery.maskedinput.js create mode 100644 framework/yii/widgets/InputWidget.php create mode 100644 framework/yii/widgets/MaskedInput.php diff --git a/framework/yii/assets.php b/framework/yii/assets.php index 79fbeb5..63f7560 100644 --- a/framework/yii/assets.php +++ b/framework/yii/assets.php @@ -48,4 +48,11 @@ return array( YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js', ), ), + 'yii/maskedinput' => array( + 'sourcePath' => __DIR__ . '/assets', + 'js' => array( + 'jquery.maskedinput.js', + ), + 'depends' => array('yii/jquery'), + ), ); diff --git a/framework/yii/assets/jquery.maskedinput.js b/framework/yii/assets/jquery.maskedinput.js new file mode 100644 index 0000000..49a5a72 --- /dev/null +++ b/framework/yii/assets/jquery.maskedinput.js @@ -0,0 +1,338 @@ +/* + Masked Input plugin for jQuery + Copyright (c) 2007-2013 Josh Bush (digitalbush.com) + Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) + Version: 1.3.1 +*/ +(function($) { + function getPasteEvent() { + var el = document.createElement('input'), + name = 'onpaste'; + el.setAttribute(name, ''); + return (typeof el[name] === 'function')?'paste':'input'; +} + +var pasteEventName = getPasteEvent() + ".mask", + ua = navigator.userAgent, + iPhone = /iphone/i.test(ua), + android=/android/i.test(ua), + caretTimeoutId; + +$.mask = { + //Predefined character definitions + definitions: { + '9': "[0-9]", + 'a': "[A-Za-z]", + '*': "[A-Za-z0-9]" + }, + dataName: "rawMaskFn", + placeholder: '_', +}; + +$.fn.extend({ + //Helper Function for Caret positioning + caret: function(begin, end) { + var range; + + if (this.length === 0 || this.is(":hidden")) { + return; + } + + if (typeof begin == 'number') { + end = (typeof end === 'number') ? end : begin; + return this.each(function() { + if (this.setSelectionRange) { + this.setSelectionRange(begin, end); + } else if (this.createTextRange) { + range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', begin); + range.select(); + } + }); + } else { + if (this[0].setSelectionRange) { + begin = this[0].selectionStart; + end = this[0].selectionEnd; + } else if (document.selection && document.selection.createRange) { + range = document.selection.createRange(); + begin = 0 - range.duplicate().moveStart('character', -100000); + end = begin + range.text.length; + } + return { begin: begin, end: end }; + } + }, + unmask: function() { + return this.trigger("unmask"); + }, + mask: function(mask, settings) { + var input, + defs, + tests, + partialPosition, + firstNonMaskPos, + len; + + if (!mask && this.length > 0) { + input = $(this[0]); + return input.data($.mask.dataName)(); + } + settings = $.extend({ + placeholder: $.mask.placeholder, // Load default placeholder + completed: null + }, settings); + + + defs = $.mask.definitions; + tests = []; + partialPosition = len = mask.length; + firstNonMaskPos = null; + + $.each(mask.split(""), function(i, c) { + if (c == '?') { + len--; + partialPosition = i; + } else if (defs[c]) { + tests.push(new RegExp(defs[c])); + if (firstNonMaskPos === null) { + firstNonMaskPos = tests.length - 1; + } + } else { + tests.push(null); + } + }); + + return this.trigger("unmask").each(function() { + var input = $(this), + buffer = $.map( + mask.split(""), + function(c, i) { + if (c != '?') { + return defs[c] ? settings.placeholder : c; + } + }), + focusText = input.val(); + + function seekNext(pos) { + while (++pos < len && !tests[pos]); + return pos; + } + + function seekPrev(pos) { + while (--pos >= 0 && !tests[pos]); + return pos; + } + + function shiftL(begin,end) { + var i, + j; + + if (begin<0) { + return; + } + + for (i = begin, j = seekNext(end); i < len; i++) { + if (tests[i]) { + if (j < len && tests[i].test(buffer[j])) { + buffer[i] = buffer[j]; + buffer[j] = settings.placeholder; + } else { + break; + } + + j = seekNext(j); + } + } + writeBuffer(); + input.caret(Math.max(firstNonMaskPos, begin)); + } + + function shiftR(pos) { + var i, + c, + j, + t; + + for (i = pos, c = settings.placeholder; i < len; i++) { + if (tests[i]) { + j = seekNext(i); + t = buffer[i]; + buffer[i] = c; + if (j < len && tests[j].test(t)) { + c = t; + } else { + break; + } + } + } + } + + function keydownEvent(e) { + var k = e.which, + pos, + begin, + end; + + //backspace, delete, and escape get special treatment + if (k === 8 || k === 46 || (iPhone && k === 127)) { + pos = input.caret(); + begin = pos.begin; + end = pos.end; + + if (end - begin === 0) { + begin=k!==46?seekPrev(begin):(end=seekNext(begin-1)); + end=k===46?seekNext(end):end; + } + clearBuffer(begin, end); + shiftL(begin, end - 1); + + e.preventDefault(); + } else if (k == 27) {//escape + input.val(focusText); + input.caret(0, checkVal()); + e.preventDefault(); + } + } + + function keypressEvent(e) { + var k = e.which, + pos = input.caret(), + p, + c, + next; + + if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore + return; + } else if (k) { + if (pos.end - pos.begin !== 0){ + clearBuffer(pos.begin, pos.end); + shiftL(pos.begin, pos.end-1); + } + + p = seekNext(pos.begin - 1); + if (p < len) { + c = String.fromCharCode(k); + if (tests[p].test(c)) { + shiftR(p); + + buffer[p] = c; + writeBuffer(); + next = seekNext(p); + + if(android){ + setTimeout($.proxy($.fn.caret,input,next),0); + }else{ + input.caret(next); + } + + if (settings.completed && next >= len) { + settings.completed.call(input); + } + } + } + e.preventDefault(); + } + } + + function clearBuffer(start, end) { + var i; + for (i = start; i < end && i < len; i++) { + if (tests[i]) { + buffer[i] = settings.placeholder; + } + } + } + + function writeBuffer() { input.val(buffer.join('')); } + + function checkVal(allow) { + //try to place characters where they belong + var test = input.val(), + lastMatch = -1, + i, + c; + + for (i = 0, pos = 0; i < len; i++) { + if (tests[i]) { + buffer[i] = settings.placeholder; + while (pos++ < test.length) { + c = test.charAt(pos - 1); + if (tests[i].test(c)) { + buffer[i] = c; + lastMatch = i; + break; + } + } + if (pos > test.length) { + break; + } + } else if (buffer[i] === test.charAt(pos) && i !== partialPosition) { + pos++; + lastMatch = i; + } + } + if (allow) { + writeBuffer(); + } else if (lastMatch + 1 < partialPosition) { + input.val(""); + clearBuffer(0, len); + } else { + writeBuffer(); + input.val(input.val().substring(0, lastMatch + 1)); + } + return (partialPosition ? i : firstNonMaskPos); + } + + input.data($.mask.dataName,function(){ + return $.map(buffer, function(c, i) { + return tests[i]&&c!=settings.placeholder ? c : null; + }).join(''); + }); + + if (!input.attr("readonly")) + input + .one("unmask", function() { + input + .unbind(".mask") + .removeData($.mask.dataName); + }) + .bind("focus.mask", function() { + clearTimeout(caretTimeoutId); + var pos, + moveCaret; + + focusText = input.val(); + pos = checkVal(); + + caretTimeoutId = setTimeout(function(){ + writeBuffer(); + if (pos == mask.length) { + input.caret(0, pos); + } else { + input.caret(pos); + } + }, 10); + }) + .bind("blur.mask", function() { + checkVal(); + if (input.val() != focusText) + input.change(); + }) + .bind("keydown.mask", keydownEvent) + .bind("keypress.mask", keypressEvent) + .bind(pasteEventName, function() { + setTimeout(function() { + var pos=checkVal(true); + input.caret(pos); + if (settings.completed && pos == input.val().length) + settings.completed.call(input); + }, 0); + }); + checkVal(); //Perform initial check for existing values + }); + } +}); + + +})(jQuery); \ No newline at end of file diff --git a/framework/yii/widgets/ActiveForm.php b/framework/yii/widgets/ActiveForm.php index 25a2054..eb14293 100644 --- a/framework/yii/widgets/ActiveForm.php +++ b/framework/yii/widgets/ActiveForm.php @@ -134,8 +134,8 @@ class ActiveForm extends Widget $id = $this->options['id']; $options = Json::encode($this->getClientOptions()); $attributes = Json::encode($this->attributes); - $this->view->registerAssetBundle('yii/form'); - $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + $this->getView()->registerAssetBundle('yii/form'); + $this->getView()->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); } echo Html::endForm(); } diff --git a/framework/yii/widgets/InputWidget.php b/framework/yii/widgets/InputWidget.php new file mode 100644 index 0000000..e1981c9 --- /dev/null +++ b/framework/yii/widgets/InputWidget.php @@ -0,0 +1,64 @@ + + * @since 2.0 + */ +class InputWidget extends Widget +{ + /** + * @var Model the data model that this widget is associated with. + */ + public $model; + /** + * @var string the model attribute that this widget is associated with. + */ + public $attribute; + /** + * @var string the input name. This must be set if [[model]] and [[attribute]] are not set. + */ + public $name; + /** + * @var string the input value. + */ + public $value; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + if (!$this->hasModel() && $this->name === null) { + throw new InvalidConfigException("Either 'name' or 'model' and 'attribute' properties must be specified."); + } + parent::init(); + } + + /** + * @return boolean whether this widget is associated with a data model. + */ + protected function hasModel() + { + return $this->model instanceof Model && $this->attribute !== null; + } +} diff --git a/framework/yii/widgets/MaskedInput.php b/framework/yii/widgets/MaskedInput.php new file mode 100644 index 0000000..a5a23f5 --- /dev/null +++ b/framework/yii/widgets/MaskedInput.php @@ -0,0 +1,136 @@ + 'phone', + * 'mask' => '999-999-9999', + * )); + * ~~~ + * + * The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin). + * + * @author Qiang Xue + * @since 2.0 + */ +class MaskedInput extends InputWidget +{ + /** + * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined: + * + * - `a`: represents an alpha character (A-Z,a-z) + * - `9`: represents a numeric character (0-9) + * - `*`: represents an alphanumeric character (A-Z,a-z,0-9) + * - `?`: anything listed after '?' within the mask is considered optional user input + * + * Additional characters can be defined by specifying the [[charMap]] property. + */ + public $mask; + /** + * @var array the mapping between mask characters and the corresponding patterns. + * For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input. + * Defaults to null, meaning using the map as described in [[mask]]. + */ + public $charMap; + /** + * @var string the character prompting for user input. Defaults to underscore '_'. + */ + public $placeholder; + /** + * @var string a JavaScript function callback that will be invoked when user finishes the input. + */ + public $completed; + /** + * @var array the HTML attributes for the input tag. + */ + public $options = array(); + + + /** + * Initializes the widget. + * @throws InvalidConfigException if the "mask" property is not set. + */ + public function init() + { + parent::init(); + if (empty($this->mask)) { + throw new InvalidConfigException('The "mask" property must be set.'); + } + + if (!isset($this->options['id'])) { + $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); + } + } + + /** + * Runs the widget. + */ + public function run() + { + if ($this->hasModel()) { + echo Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + echo Html::textInput($this->name, $this->value, $this->options); + } + $this->registerClientScript(); + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $options = $this->getClientOptions(); + $options = empty($options) ? '' : ',' . Json::encode($options); + $js = ''; + if (is_array($this->charMap) && !empty($this->charMap)) { + $js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n"; + } + $id = $this->options['id']; + $js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});"; + $this->getView()->registerAssetBundle('yii/maskedinput'); + $this->getView()->registerJs($js); + } + + /** + * @return array the options for the text field + */ + protected function getClientOptions() + { + $options = array(); + if ($this->placeholder !== null) { + $options['placeholder'] = $this->placeholder; + } + + if ($this->completed !== null) { + if ($this->completed instanceof JsExpression) { + $options['completed'] = $this->completed; + } else { + $options['completed'] = new JsExpression($this->completed); + } + } + + return $options; + } +} From a2066cbe4878ff084c9bf9d34a3fe424912e73e9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 04:24:38 +0200 Subject: [PATCH 27/70] Created a \yii\web\VerbFilter It allows to filter actions by HTTP request methods --- framework/yii/web/VerbFilter.php | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 framework/yii/web/VerbFilter.php diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php new file mode 100644 index 0000000..9b475e3 --- /dev/null +++ b/framework/yii/web/VerbFilter.php @@ -0,0 +1,90 @@ + array( + * 'class' => \yii\web\VerbFilter::className(), + * 'actions' => array( + * 'index' => array('get'), + * 'view' => array('get'), + * 'create' => array('get', 'post'), + * 'update' => array('get', 'put', 'post'), + * 'delete' => array('post', 'delete'), + * ), + * ), + * ); + * } + * ~~~ + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + * @author Carsten Brandt + * @since 2.0 + */ +class VerbFilter extends Behavior +{ + /** + * @var array this property defines the allowed request methods for each action. + * For each action that should only support limited set of request methods + * you add an entry with the action id as array key and an array of + * allowed methods (e.g. GET, HEAD, PUT) as the value. + * If an action is not listed all request methods are considered allowed. + */ + public $actions = array(); + + + /** + * Declares event handlers for the [[owner]]'s events. + * @return array events (array keys) and the corresponding event handler methods (array values). + */ + public function events() + { + return array( + Controller::EVENT_BEFORE_ACTION => 'beforeAction', + ); + } + + /** + * @param ActionEvent $event + * @return boolean + * @throws \yii\base\HttpException when the request method is not allowed. + */ + public function beforeAction($event) + { + $action = $event->action->id; + if (isset($this->actions[$action])) { + $verb = Yii::$app->getRequest()->getRequestMethod(); + $allowed = array_map('strtoupper', $this->actions[$action]); + if (!in_array($verb, $allowed)) { + $event->isValid = false; + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + header('Allow: ' . implode(', ', $allowed)); + throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed)); + } + } + return $event->isValid; + } +} From 9af5466be588bbaeb8a2d413d651a1698226861e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 05:55:14 +0300 Subject: [PATCH 28/70] typo in Html helper --- framework/yii/helpers/base/Html.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/base/Html.php b/framework/yii/helpers/base/Html.php index 90396ec..9a0001c 100644 --- a/framework/yii/helpers/base/Html.php +++ b/framework/yii/helpers/base/Html.php @@ -344,7 +344,7 @@ class Html /** * Generates a hyperlink tag. * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * such as an image tag. If this is coming from end users, you should consider [[encode()]] * it to prevent XSS attacks. * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute @@ -366,7 +366,7 @@ class Html /** * Generates a mailto hyperlink. * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * such as an image tag. If this is coming from end users, you should consider [[encode()]] * it to prevent XSS attacks. * @param string $email email address. If this is null, the first parameter (link body) will be treated * as the email address and used. From 9f832afb9f5ab792b50c76f5775ca3da6f83b871 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:17:53 +0200 Subject: [PATCH 29/70] Refactored based on comments and feedback (3rd round) --- framework/yii/bootstrap/Tabs.php | 246 +++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 126 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 0f049fd..9082c1c 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -18,26 +18,25 @@ use yii\helpers\Html; * * ```php * echo Tabs::widget(array( - * 'options' => array('class'=>'nav-tabs'), * 'items' => array( * array( - * 'header' => 'One', + * 'label' => 'One', * 'content' => 'Anim pariatur cliche...', + * 'active' => true * ), * array( - * 'header' => 'Two', + * 'label' => 'Two', * 'headerOptions' => array(...), * 'content' => 'Anim pariatur cliche...', * 'options' => array('id'=>'myveryownID'), * ), * array( - * 'header' => 'Dropdown', + * 'label' => 'Dropdown', * 'dropdown' => array( * array( * 'label' => 'DropdownA', * 'content' => 'DropdownA, Anim pariatur cliche...', * ), - * '-', // divider * array( * 'label' => 'DropdownB', * 'content' => 'DropdownB, Anim pariatur cliche...', @@ -58,28 +57,36 @@ class Tabs extends Widget * @var array list of tabs in the tabs widget. Each array element represents a single * tab with the following structure: * - * ```php - * array( - * // required, the header (HTML) of the tab - * 'header' => 'Tab label', - * // optional the HTML attributes of the tab header `LI` tag container - * 'headerOptions'=> array(...), - * // required, the content (HTML) of the tab - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * // optional the HTML attributes of the tab content container - * 'options'=> array(...), - * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. This - * // attribute, apart from the original [[Dropdown::items]] settings, also has two extra special keys: - * // - content: required, teh content (HTML) of teh tab the menu item is linked to - * // - contentOptions: optional the HTML attributes of the tab content container - * // note: if `dropdown` is set, then `content` will be ignored - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'dropdown'=> array(...) + * - label: string, the tab header label. + * - headerOptions: array, optional, the HTML attributes of the tab header. + * - content: array, required if `items` is not set. The content (HTML) of the tab pane. + * - options: array, optional, the HTML attributes of the tab pane container. + * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items + * configuration array. Items can also hold two extra keys: + * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - content: string, required if `items` is not set. The content (HTML) of the tab pane. + * - contentOptions: optional, array, the HTML attributes of the tab content container. * ) * ``` */ public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the item container tags. + */ + public $itemOptions = array(); + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = array(); + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; /** @@ -88,8 +95,7 @@ class Tabs extends Widget public function init() { parent::init(); - $this->addCssClass($this->options, 'nav'); - $this->items = $this->normalizeItems(); + $this->addCssClass($this->options, 'nav nav-tabs'); } @@ -98,136 +104,124 @@ class Tabs extends Widget */ public function run() { - echo Html::beginTag('ul', $this->options) . "\n"; - echo $this->renderHeaders() . "\n"; - echo Html::endTag('ul'); - echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; - echo $this->renderContents() . "\n"; - echo Html::endTag('div') . "\n"; + echo $this->renderItems(); $this->registerPlugin('tab'); } /** - * Renders tabs navigation. + * Renders tab items as specified on [[items]]. * @return string the rendering result. + * @throws InvalidConfigException. */ - protected function renderHeaders() + protected function renderItems() { $headers = array(); - foreach ($this->items['headers'] as $item) { - $options = ArrayHelper::getValue($item, 'options', array()); - if (isset($item['dropdown'])) { - $headers[] = Html::tag( - 'li', - Html::a($item['header'] . ' ', "#", array( - 'class' => 'dropdown-toggle', - 'data-toggle' => 'dropdown' - )) . - Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)), - $options - ); - continue; + $panes = array(); + foreach ($this->items as $n => $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); } - $id = ArrayHelper::getValue($item, 'url'); - $headers[] = Html::tag('li', Html::a($item['header'], "{$id}", array('data-toggle' => 'tab')), $options); + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $label = $this->label($item['label']); + $headerOptions = $this->mergedOptions($item, 'headerOptions'); + + if (isset($item['items'])) { + $label .= ' '; + $this->addCssClass($headerOptions, 'dropdown'); + + if ($this->normalizeItems($item['items'], $panes)) { + $this->addCssClass($headerOptions, 'active'); + } + + $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"; + $header .= Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + + } else { + $options = $this->mergedOptions($item, 'itemOptions', 'options'); + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); + + $this->addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + $this->addCssClass($options, 'active'); + $this->addCssClass($headerOptions, 'active'); + } + $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab', 'tabindex' => '-1')); + $panes[] = Html::tag('div', $item['content'], $options); + } + $headers[] = Html::tag('li', $header, array_merge($this->headerOptions, $headerOptions)); } - return implode("\n", $headers); + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" . + Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); } /** - * Renders tabs contents. - * @return string the rendering result. + * Returns encoded if specified on [[encodeLabels]], original string otherwise. + * @param string $content the label text to encode or return + * @return string the resulting label. */ - protected function renderContents() + protected function label($content) { - $contents = array(); - foreach ($this->items['contents'] as $item) { - $options = ArrayHelper::getValue($item, 'options', array()); - $this->addCssClass($options, 'tab-pane'); - $contents[] = Html::tag('div', $item['content'], $options); + return $this->encodeLabels ? Html::encode($content) : $content; + } + /** + * Returns array of options merged with specified attribute array. The availabel options are: + * - [[itemOptions]] + * - [[headerOptions]] + * @param array $item the item to merge the options with + * @param string $name the property name. + * @param string $key the key to extract. If null, it is assumed to be the same as `$name`. + * @return array the merged array options. + */ + protected function mergedOptions($item, $name, $key = null) + { + if ($key === null) { + $key = $name; } - return implode("\n", $contents); + return array_merge($this->{$name}, ArrayHelper::getValue($item, $key, array())); } /** - * Normalizes the [[items]] property to divide headers from contents and to ease its rendering when there are - * headers with dropdown menus. - * @return array the normalized tabs items + * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also + * configure `panes` accordingly. + * @param array $items the dropdown items configuration. + * @param array $panes the panes reference array. + * @return boolean whether any of the dropdown items is `active` or not. * @throws InvalidConfigException */ - protected function normalizeItems() + protected function normalizeItems(&$items, &$panes) { - $items = array(); - $index = 0; - foreach ($this->items as $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); + $itemActive = false; + + foreach ($items as $n => &$item) { + if (is_string($item)) { + continue; } - if (!isset($item['content']) && !isset($item['dropdown'])) { + if (!isset($item['content']) && !isset($item['items'])) { throw new InvalidConfigException("The 'content' option is required."); } - $header = $content = array(); - $header['header'] = ArrayHelper::getValue($item, 'header'); - $header['options'] = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($index === 0) { - $this->addCssClass($header['options'], 'active'); + + $content = ArrayHelper::remove($item, 'content'); + $options = ArrayHelper::remove($item, 'contentOptions', array()); + $this->addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + $this->addCssClass($options, 'active'); + $this->addCssClass($item['options'], 'active'); + $itemActive = true; } - if (isset($item['dropdown'])) { - $this->addCssClass($header['options'], 'dropdown'); - - $self = $this; - $dropdown = function ($list) use (&$dropdown, &$items, &$index, $self) { - $ddItems = $content = array(); - foreach ($list as $item) { - if (is_string($item)) { - $ddItems[] = $item; - continue; - } - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } - if (isset($item['items'])) { - $item['items'] = $dropdown($item['items']); - } else { - $content['content'] = ArrayHelper::remove($item, 'content'); - $content['options'] = ArrayHelper::remove($item, 'contentOptions', array()); - if ($index === 0) { - $self->addCssClass($content['options'], 'active'); - $self->addCssClass($item['options'], 'active'); - } - $content['options']['id'] = ArrayHelper::getValue( - $content['options'], - 'id', - $self->options['id'] . '-tab' . $index++); - $item['url'] = '#' . $content['options']['id']; - $item['urlOptions']['data-toggle'] = 'tab'; - - $items['contents'][] = $content; - } - $ddItems[] = $item; - } - return $ddItems; - }; - $header['dropdown'] = $dropdown($item['dropdown']); - } else { - $content['content'] = ArrayHelper::getValue($item, 'content'); - $content['options'] = ArrayHelper::getValue($item, 'options', array()); - if ($index === 0) { - $this->addCssClass($content['options'], 'active'); - } - $content['options']['id'] = ArrayHelper::getValue( - $content['options'], - 'id', - $this->options['id'] . '-tab' . $index++); + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n); + $item['url'] = '#' . $options['id']; + $item['linkOptions']['data-toggle'] = 'tab'; - $header['url'] = "#" . ArrayHelper::getValue($content['options'], 'id'); - $items['contents'][] = $content; - } - $items['headers'][] = $header; + $panes[] = Html::tag('div', $content, $options); + + unset($item); } - return $items; + return $itemActive; } } \ No newline at end of file From 6ce2f8f1374e74370b72a186e1a19a90a499a9ad Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:45:04 +0200 Subject: [PATCH 30/70] silly typo --- framework/yii/bootstrap/Tabs.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 9082c1c..4be1d24 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -68,7 +68,6 @@ class Tabs extends Widget * - content: string, required if `items` is not set. The content (HTML) of the tab pane. * - contentOptions: optional, array, the HTML attributes of the tab content container. * ) - * ``` */ public $items = array(); /** From 1c4cf30464e786f2c157058fe7d67a273c0fc5ea Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:47:36 +0200 Subject: [PATCH 31/70] another typo fix --- framework/yii/bootstrap/Tabs.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 4be1d24..178cc71 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -67,7 +67,6 @@ class Tabs extends Widget * - active: boolean, optional, whether the item tab header and pane should be visibles or not. * - content: string, required if `items` is not set. The content (HTML) of the tab pane. * - contentOptions: optional, array, the HTML attributes of the tab content container. - * ) */ public $items = array(); /** From 4ebc0ae7347d550aa967708a6f6d8ee47b51b40b Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:49:41 +0200 Subject: [PATCH 32/70] fix phpDoc --- framework/yii/bootstrap/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 178cc71..1af79b4 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -57,7 +57,7 @@ class Tabs extends Widget * @var array list of tabs in the tabs widget. Each array element represents a single * tab with the following structure: * - * - label: string, the tab header label. + * - label: string, required, the tab header label. * - headerOptions: array, optional, the HTML attributes of the tab header. * - content: array, required if `items` is not set. The content (HTML) of the tab pane. * - options: array, optional, the HTML attributes of the tab pane container. From cbb33bd164b21f504ceb461ec547604061627da3 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:54:01 +0200 Subject: [PATCH 33/70] refactored based on comments (2nd round) --- framework/yii/bootstrap/Nav.php | 64 +++++++++++++++++++---------------- framework/yii/bootstrap/NavBar.php | 68 ++++++++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 51 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index f373961..c247433 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -22,12 +22,12 @@ use yii\helpers\Html; * array( * 'label' => 'Home', * 'url' => '/', - * 'options' => array(...), + * 'linkOptions' => array(...), * 'active' => true, * ), * array( * 'label' => 'Dropdown', - * 'dropdown' => array( + * 'items' => array( * array( * 'label' => 'DropdownA', * 'url' => '#', @@ -52,26 +52,25 @@ class Nav extends Widget * @var array list of items in the nav widget. Each array element represents a single * menu item with the following structure: * - * ```php - * array( - * // required, the menu item label. - * 'label' => 'Nav item label', - * // optional, the URL of the menu item. Defaults to "#" - * 'url'=> '#', - * // optional, the HTML options of the URL. - * 'urlOptions' => array(...), - * // optional the HTML attributes of the item container (LI). - * 'options' => array(...), - * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'dropdown'=> array(...) - * ) - * ``` + * - label: string, required, the nav item label. + * - url: optional, the item's URL. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item's link. + * - options: array, optional, the HTML attributes of the item container (LI). + * - active: boolean, optional, whether the item should be on active state or not. + * - items: array, optional, the configuration of specify the item's dropdown menu. You can optionally set this as + * a string (ie. `'items'=> Dropdown::widget(array(...))` + * - important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown. * - * Optionally, you can also use a plain string instead of an array element. + * **Note:** Optionally, you can also use a plain string instead of an array element. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] */ public $items = array(); + /** + * @var boolean whether the nav items labels should be HTML-encoded. + */ + public $encodeLabels = true; /** @@ -79,6 +78,7 @@ class Nav extends Widget */ public function init() { + parent::init(); $this->addCssClass($this->options, 'nav'); } @@ -88,6 +88,7 @@ class Nav extends Widget public function run() { echo $this->renderItems(); + $this->registerPlugin('dropdown'); } /** @@ -117,21 +118,26 @@ class Nav extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - $label = $item['label']; - $url = ArrayHelper::getValue($item, 'url', '#'); + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; $options = ArrayHelper::getValue($item, 'options', array()); - $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); - $dropdown = null; + $dropdown = ArrayHelper::getValue($item, 'items'); + $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); + + if(ArrayHelper::getValue($item, 'active')) { + $this->addCssClass($options, 'active'); + } - // does it has a dropdown widget? - if (isset($item['dropdown'])) { - $urlOptions['data-toggle'] = 'dropdown'; + if ($dropdown !== null) { + $linkOptions['data-toggle'] = 'dropdown'; $this->addCssClass($options, 'dropdown'); $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); - $dropdown = Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)); + $dropdown = is_string($dropdown) + ? $dropdown + : Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); } - return Html::tag('li', Html::a($label, $url, $urlOptions) . $dropdown, $options); + return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options); } } \ No newline at end of file diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php index 1ed5a9a..cbcd2d3 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/framework/yii/bootstrap/NavBar.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -18,6 +18,7 @@ use yii\helpers\Html; * * ```php * echo NavBar::widget(array( + * 'brandLabel' => 'NavBar Test', * 'items' => array( * // a Nav widget * array( @@ -33,17 +34,18 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * // configure a dropdown menu - * 'dropdown' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '#', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#' - * ), - * ) + * 'content' => new Dropdown(array( + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#' + * ), + * ) + * ), * ), * ) * ), @@ -62,7 +64,20 @@ use yii\helpers\Html; */ class NavBar extends Widget { - public $brand; + /** + * @var string the text of the brand. + * @see http://twitter.github.io/bootstrap/components.html#navbar + */ + public $brandLabel; + /** + * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]] + * and will be used for the "href" attribute of the brand link. Defaults to site root. + */ + public $brandRoute = '/'; + /** + * @var array the HTML attributes of the brand link. + */ + public $brandOptions = array(); /** * @var array list of menu items in the navbar widget. Each array element represents a single * menu item with the following structure: @@ -83,6 +98,10 @@ class NavBar extends Widget * Optionally, you can also use a plain string instead of an array element. */ public $items = array(); + /** + * @var string the generated brand url if specified by [[brandLabel]] + */ + protected $brand; /** @@ -91,7 +110,10 @@ class NavBar extends Widget public function init() { parent::init(); + $this->clientOptions = false; $this->addCssClass($this->options, 'navbar'); + $this->addCssClass($this->brandOptions, 'brand'); + $this->brand = Html::a($this->brandLabel, $this->brandRoute, $this->brandOptions); } /** @@ -102,19 +124,20 @@ class NavBar extends Widget echo Html::beginTag('div', $this->options); echo $this->renderItems(); echo Html::endTag('div'); + $this->getView()->registerAssetBundle('yii/bootstrap'); } /** + * Renders the items. * @return string the rendering items. */ protected function renderItems() { $items = array(); - foreach ($this->items as $item) { $items[] = $this->renderItem($item); } - $contents =implode("\n", $items); + $contents = implode("\n", $items); if (self::$responsive === true) { $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); $contents = @@ -127,6 +150,7 @@ class NavBar extends Widget } else { $contents = $this->brand . "\n" . $contents; } + return Html::tag('div', $contents, array('class' => 'navbar-inner')); } @@ -143,11 +167,11 @@ class NavBar extends Widget return $item; } $config = ArrayHelper::getValue($item, 'options', array()); - $config['class'] = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); - $widget = \Yii::createObject($config); - ob_start(); - $widget->run(); - return ob_get_clean(); + $config['clientOptions'] = false; + + $class = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); + + return $class::widget($config); } /** @@ -160,10 +184,10 @@ class NavBar extends Widget for ($i = 0; $i < 3; $i++) { $items[] = Html::tag('span', '', array('class' => 'icon-bar')); } - return Html::tag('a', implode("\n", $items), array( + return Html::a(implode("\n", $items), null, array( 'class' => 'btn btn-navbar', 'data-toggle' => 'collapse', 'data-target' => 'div.navbar-collapse', - )) . "\n"; + )); } } \ No newline at end of file From 1e9cc35fd8ce005033668b0eee77d1edbfdccaf8 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:54:53 +0200 Subject: [PATCH 34/70] fix namespaces --- framework/yii/bootstrap/Carousel.php | 2 +- framework/yii/bootstrap/Collapse.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Carousel.php b/framework/yii/bootstrap/Carousel.php index 3d38b54..f8904fa 100644 --- a/framework/yii/bootstrap/Carousel.php +++ b/framework/yii/bootstrap/Carousel.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php index d83df3c..a7929e3 100644 --- a/framework/yii/bootstrap/Collapse.php +++ b/framework/yii/bootstrap/Collapse.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** From e8d3a7ff9287b683b2248d95f1affd3c45610a34 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:58:21 +0200 Subject: [PATCH 35/70] register asset bundle instead --- framework/yii/bootstrap/Nav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index c247433..7a29ecd 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -88,7 +88,7 @@ class Nav extends Widget public function run() { echo $this->renderItems(); - $this->registerPlugin('dropdown'); + $this->getView()->registerAssetBundle('yii/bootstrap'); } /** From 7d0b95c21aa98b5a308071724ed864defff0cbd0 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 13:22:00 +0200 Subject: [PATCH 36/70] modified phpDoc --- framework/yii/bootstrap/Dropdown.php | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index 8ba0eef..f4b5489 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -24,24 +24,14 @@ class Dropdown extends Widget /** * @var array list of menu items in the dropdown. Each array element represents a single * menu with the following structure: + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. if `items` is set, then `url` of the parent + * item will be ignored and automatically set to "#" * - * ```php - * array( - * // required, the label of the item link - * 'label' => 'Menu label', - * // optional, url of the item link - * 'url' => '', - * // optional the HTML attributes of the item link - * 'linkOptions'=> array(...), - * // optional the HTML attributes of the item - * 'options'=> array(...), - * // optional, an array of items that configure a sub menu of the item - * // note: if `items` is set, then `url` of the parent item will be ignored and automatically set to "#" - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'items'=> array(...) - * ) - * ``` + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 */ public $items = array(); /** From e2073612556db4f74fc3089d6b7dd556e3af69c0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 07:47:33 -0400 Subject: [PATCH 37/70] Refactored the widget usage with ActiveField. --- apps/basic/views/site/contact.php | 12 ++----- framework/yii/widgets/ActiveField.php | 12 ++++++- framework/yii/widgets/Captcha.php | 64 ++++++++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php index e740d0f..14a82bc 100644 --- a/apps/basic/views/site/contact.php +++ b/apps/basic/views/site/contact.php @@ -31,15 +31,9 @@ $this->params['breadcrumbs'][] = $this->title; field($model, 'email')->textInput(); ?> field($model, 'subject')->textInput(); ?> field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode'); - echo $field->begin() - . $field->label() - . Captcha::widget() - . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) - . $field->error() - . $field->end(); - ?> + field($model, 'verifyCode')->widget(Captcha::className(), array( + 'options' => array('class' => 'input-medium'), + )); ?>
    'btn btn-primary')); ?>
    diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index e3e7f2b..346f743 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -557,7 +557,14 @@ class ActiveField extends Component } /** - * Renders a field containing a widget. + * Renders a field containing an input widget. + * + * Note that the widget must have both `model` and `attribute` properties. They will + * be initialized with [[model]] and [[attribute]] of this field, respectively. + * + * If you want to use a widget that does not have `model` and `attribute` properties, + * please use [[render()]] instead. + * * @param string $class the widget class name * @param array $config name-value pairs that will be used to initialize the widget * @return string the rendering result @@ -565,6 +572,9 @@ class ActiveField extends Component public function widget($class, $config = array()) { /** @var \yii\base\Widget $class */ + $config['model'] = $this->model; + $config['attribute'] = $this->attribute; + $config['view'] = $this->form->getView(); return $this->render($class::widget($config)); } } diff --git a/framework/yii/widgets/Captcha.php b/framework/yii/widgets/Captcha.php index 918e30c..ffe8802 100644 --- a/framework/yii/widgets/Captcha.php +++ b/framework/yii/widgets/Captcha.php @@ -9,13 +9,12 @@ namespace yii\widgets; use Yii; use yii\base\InvalidConfigException; -use yii\base\Widget; use yii\helpers\Html; use yii\helpers\Json; use yii\web\CaptchaAction; /** - * Captcha renders a CAPTCHA image element. + * Captcha renders a CAPTCHA image and an input field that takes user-entered verification code. * * Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha) * - a way of preventing Website spamming. @@ -32,7 +31,7 @@ use yii\web\CaptchaAction; * @author Qiang Xue * @since 2.0 */ -class Captcha extends Widget +class Captcha extends InputWidget { /** * @var string the route of the action that generates the CAPTCHA images. @@ -40,27 +39,66 @@ class Captcha extends Widget */ public $captchaAction = 'site/captcha'; /** - * @var array HTML attributes to be applied to the rendered image element. + * @var array HTML attributes to be applied to the text input field. */ public $options = array(); - + /** + * @var array HTML attributes to be applied to the CAPTCHA image tag. + */ + public $imageOptions = array(); + /** + * @var string the template for arranging the CAPTCHA image tag and the text input tag. + * In this template, the token `{image}` will be replaced with the actual image tag, + * while `{input}` will be replaced with the text input tag. + */ + public $template = '{image} {input}'; /** - * Renders the widget. + * Initializes the widget. */ - public function run() + public function init() { + parent::init(); + $this->checkRequirements(); if (!isset($this->options['id'])) { - $this->options['id'] = $this->getId(); + $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); + } + if (!isset($this->imageOptions['id'])) { + $this->imageOptions['id'] = $this->options['id'] . '-image'; + } + } + + /** + * Renders the widget. + */ + public function run() + { + $this->registerClientScript(); + if ($this->hasModel()) { + $input = Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + $input = Html::textInput($this->name, $this->value, $this->options); } - $id = $this->options['id']; - $options = Json::encode($this->getClientOptions()); - $this->view->registerAssetBundle('yii/captcha'); - $this->view->registerJs("jQuery('#$id').yiiCaptcha($options);"); $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid())); - echo Html::img($url, $this->options); + $image = Html::img($url, $this->imageOptions); + echo strtr($this->template, array( + '{input}' => $input, + '{image}' => $image, + )); + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $options = $this->getClientOptions(); + $options = empty($options) ? '' : Json::encode($options); + $id = $this->imageOptions['id']; + $this->getView()->registerAssetBundle('yii/captcha'); + $this->getView()->registerJs("jQuery('#$id').yiiCaptcha($options);"); } /** From 3647c3f12846919630f1ecfc5f62e897350c36c8 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 13:54:47 +0200 Subject: [PATCH 38/70] added button group dropdowns --- framework/yii/bootstrap/Button.php | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 framework/yii/bootstrap/Button.php diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php new file mode 100644 index 0000000..bdc29ca --- /dev/null +++ b/framework/yii/bootstrap/Button.php @@ -0,0 +1,147 @@ + 'Action', + * 'items' => Dropdown::widget(array( + * 'clientOptions' => false, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * ), + * ), + * )); + * + * // split button group using `items` dropdown configuration + * echo \yii\bootstrap\Button::widget(array( + * 'label' => 'Action', + * 'split' => true, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns + * @author Antonio Ramirez + * @since 2.0 + */ +class Button extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] + */ + public $items = array(); + /** + * @var boolean whether to display a group or split styled button group. + */ + public $split = false; + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderLabel() . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('button'); + } + + /** + * Generates the button label. + * @return string the rendering result. + */ + protected function renderLabel() + { + $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; + $options = array('class' => 'btn dropdown-toggle', 'data-toggle' => 'dropdown'); + if ($this->split) { + $tag = 'button'; + $label .= Html::tag('button', '', $options); + $options = array('class' => 'btn'); + } else { + $tag = 'a'; + $options['href'] = '#'; + } + return Html::tag($tag, $label, $options); + } + + /** + * Generates the dropdown menu as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $config = array('items' => $this->items, 'clientOptions' => false); + return Dropdown::widget($config); + } +} \ No newline at end of file From f651e72372fdca6b0a0eca3767fe5fb1865b68f0 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:13:19 +0200 Subject: [PATCH 39/70] refactored label render --- framework/yii/bootstrap/Button.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index bdc29ca..94cdae8 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -64,6 +64,10 @@ class Button extends Widget */ public $label; /** + * @var array the HTML attributes of the button. + */ + public $buttonOptions = array(); + /** * @var array list of menu items in the dropdown. Each array element represents a single * menu with the following structure: * @@ -120,16 +124,23 @@ class Button extends Widget protected function renderLabel() { $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; - $options = array('class' => 'btn dropdown-toggle', 'data-toggle' => 'dropdown'); + $this->addCssClass($this->buttonOptions, 'btn'); + $splitButton = ''; if ($this->split) { $tag = 'button'; - $label .= Html::tag('button', '', $options); - $options = array('class' => 'btn'); + $options = $this->buttonOptions; + $this->buttonOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); + $splitButton = Html::tag('button', '', $this->buttonOptions); } else { $tag = 'a'; - $options['href'] = '#'; + $options = $this->buttonOptions; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + $options['data-toggle'] = 'dropdown'; } - return Html::tag($tag, $label, $options); + return Html::tag($tag, $label, $options) . "\n" . $splitButton; } /** From 1165508a6aef6395b93c1415c430965900913acf Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 14:22:15 +0200 Subject: [PATCH 40/70] updated composer for example apps issue #439 --- apps/advanced/composer.lock | 10 +++++----- apps/basic/composer.lock | 8 ++++---- apps/benchmark/composer.lock | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/advanced/composer.lock b/apps/advanced/composer.lock index 761ae2f..516ae32 100644 --- a/apps/advanced/composer.lock +++ b/apps/advanced/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "0b96a35ac23eae4e84ffd588653e88d2", + "hash": "05f7bcd0e99931b52415eaeb62d54daf", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-25 20:59:05" + "time": "2013-05-29 02:55:14" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock index a66bbea..fe87399 100644 --- a/apps/basic/composer.lock +++ b/apps/basic/composer.lock @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-25 20:59:05" + "time": "2013-05-29 02:55:14" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/benchmark/composer.lock b/apps/benchmark/composer.lock index c7a0324..bc8c472 100644 --- a/apps/benchmark/composer.lock +++ b/apps/benchmark/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "5ce5f1ad2aa7d7e31c3e216b8ce23387", + "hash": "25a338a613af40ef87f2e280057f0d8c", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f3c3d9d764de25fc46711bce2259274bcceade1c", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-26 21:57:00" + "time": "2013-05-29 02:55:14" } ], "packages-dev": [ From fce33a25ddfe47abb4224fe35c5cb8e723cd4b68 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:32:33 +0200 Subject: [PATCH 41/70] fixed indentation and button group label bug --- framework/yii/bootstrap/Button.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index 94cdae8..b340a4c 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -22,18 +22,17 @@ use yii\helpers\Html; * 'label' => 'Action', * 'items' => Dropdown::widget(array( * 'clientOptions' => false, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), * ), - * ), + * )), * )); * * // split button group using `items` dropdown configuration @@ -134,10 +133,12 @@ class Button extends Widget $splitButton = Html::tag('button', '', $this->buttonOptions); } else { $tag = 'a'; + $label .= ' '; $options = $this->buttonOptions; if (!isset($options['href'])) { $options['href'] = '#'; } + $this->addCssClass($options, 'dropdown-toggle'); $options['data-toggle'] = 'dropdown'; } return Html::tag($tag, $label, $options) . "\n" . $splitButton; From d3bbc5fe401334b52d3176256070cd71266c72c6 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:36:15 +0200 Subject: [PATCH 42/70] small fix --- framework/yii/bootstrap/Dropdown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index f4b5489..0594b08 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -100,6 +100,6 @@ class Dropdown extends Widget */ protected function dropdown($items) { - return Dropdown::widget(array('items' => $items, 'clientOptions' => false)); + return static::widget(array('items' => $items, 'clientOptions' => false)); } } \ No newline at end of file From 4d1666704a89c1b6400a94cd0fc1d4ca4427afff Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:17:39 +0400 Subject: [PATCH 43/70] jQuery UI resizeable widget --- framework/yii/jui/Resizable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Resizable.php diff --git a/framework/yii/jui/Resizable.php b/framework/yii/jui/Resizable.php new file mode 100644 index 0000000..ffc3501 --- /dev/null +++ b/framework/yii/jui/Resizable.php @@ -0,0 +1,52 @@ + array( + * 'grid' => array(20, 10), + * ), + * )); + * + * echo 'Resizable contents here...'; + * + * Resizable::end(); + * ``` + * + * @see http://api.jqueryui.com/resizable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Resizable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('resizable'); + } +} From 77e34ac91f7ff7b97f797e0b5203cf56ed436899 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:23:12 +0400 Subject: [PATCH 44/70] jQuery UI draggable widget --- framework/yii/jui/Draggable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Draggable.php diff --git a/framework/yii/jui/Draggable.php b/framework/yii/jui/Draggable.php new file mode 100644 index 0000000..adf92f1 --- /dev/null +++ b/framework/yii/jui/Draggable.php @@ -0,0 +1,52 @@ + array( + * 'modal' => true, + * ), + * )); + * + * echo 'Draggable contents here...'; + * + * Draggable::end(); + * ``` + * + * @see http://api.jqueryui.com/draggable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Draggable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('draggable', false); + } +} From 280727408fe0e8a77c4ae3964397a3789efca927 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 22:23:44 +0200 Subject: [PATCH 45/70] fixed wrong usage of $.inArray in validators fixes #448 --- framework/yii/assets/yii.validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js index 2748a74..015040e 100644 --- a/framework/yii/assets/yii.validation.js +++ b/framework/yii/assets/yii.validation.js @@ -87,8 +87,8 @@ yii.validation = (function ($) { if (options.skipOnEmpty && isEmpty(value)) { return; } - var valid = !options.not && $.inArray(value, options.range) - || options.not && !$.inArray(value, options.range); + var valid = !options.not && $.inArray(value, options.range) > -1 + || options.not && $.inArray(value, options.range) == -1; if (!valid) { messages.push(options.message); From 670a16200da80c364e37c9527fecba00a7d52e55 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:25:14 +0400 Subject: [PATCH 46/70] Change example option --- framework/yii/jui/Draggable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Draggable.php b/framework/yii/jui/Draggable.php index adf92f1..c73f269 100644 --- a/framework/yii/jui/Draggable.php +++ b/framework/yii/jui/Draggable.php @@ -17,7 +17,7 @@ use yii\helpers\Html; * ```php * Draggable::begin(array( * 'clientOptions' => array( - * 'modal' => true, + * 'grid' => array(50, 20), * ), * )); * From d55b1049767ea7dd97d9ff1a665222a72e3f31b4 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:30:59 +0400 Subject: [PATCH 47/70] jQuery UI droppable widget --- framework/yii/jui/Droppable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Droppable.php diff --git a/framework/yii/jui/Droppable.php b/framework/yii/jui/Droppable.php new file mode 100644 index 0000000..2f580bd --- /dev/null +++ b/framework/yii/jui/Droppable.php @@ -0,0 +1,52 @@ + array( + * 'accept' => '.special', + * ), + * )); + * + * echo 'Droppable body here...'; + * + * Droppable::end(); + * ``` + * + * @see http://api.jqueryui.com/droppable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Droppable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('droppable', false); + } +} From 2e574b9646c54ce5191bc4d8d71d9b8feb45ac23 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:39:43 +0400 Subject: [PATCH 48/70] jQuery UI selectable widget --- framework/yii/jui/Selectable.php | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 framework/yii/jui/Selectable.php diff --git a/framework/yii/jui/Selectable.php b/framework/yii/jui/Selectable.php new file mode 100644 index 0000000..a1a9b5d --- /dev/null +++ b/framework/yii/jui/Selectable.php @@ -0,0 +1,116 @@ + array( + * 'Item 1', + * array( + * 'content' => 'Item2', + * ), + * array( + * 'content' => 'Item3', + * 'options' => array( + * 'tag' => 'li', + * ), + * ), + * ), + * 'options' => array( + * 'tag' => 'ul', + * ), + * 'itemOptions' => array( + * 'tag' => 'li', + * ), + * 'clientOptions' => array( + * 'tolerance' => 'fit', + * ), + * )); + * ``` + * + * @see http://api.jqueryui.com/selectable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Selectable extends Widget +{ + /** + * @var array the HTML attributes for the widget container tag. The following special options are recognized: + * + * - tag: string, defaults to "ul", the tag name of the container tag of this widget + */ + public $options = array(); + /** + * @var array list of selectable items. Each item can be a string representing the item content + * or an array of the following structure: + * + * ~~~ + * array( + * 'content' => 'item content', + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => array(), + * ) + * ~~~ + */ + public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "li", the tag name of the item container tags. + */ + public $itemOptions = array(); + + + /** + * Renders the widget. + */ + public function run() + { + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + echo Html::beginTag($tag, $options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag($tag) . "\n"; + $this->registerWidget('selectable'); + } + + /** + * Renders selectable items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + public function renderItems() + { + $items = array(); + foreach ($this->items as $item) { + $options = $this->itemOptions; + $tag = ArrayHelper::remove($options, 'tag', 'li'); + if (is_array($item)) { + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $options = array_merge($options, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', $tag); + $items[] = Html::tag($tag, $item['content'], $options); + } else { + $items[] = Html::tag($tag, $item, $options); + } + } + return implode("\n", $items); + } +} From becc88e4b6602d4bc59daa98534466f48c54c9bb Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:49:37 +0400 Subject: [PATCH 49/70] jQuery UI spinner widget --- framework/yii/jui/Spinner.php | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 framework/yii/jui/Spinner.php diff --git a/framework/yii/jui/Spinner.php b/framework/yii/jui/Spinner.php new file mode 100644 index 0000000..8d80f89 --- /dev/null +++ b/framework/yii/jui/Spinner.php @@ -0,0 +1,66 @@ + $model, + * 'attribute' => 'country', + * 'clientOptions' => array( + * 'step' => 2, + * ), + * )); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo Spinner::widget(array( + * 'name' => 'country', + * 'clientOptions' => array( + * 'step' => 2, + * ), + * )); + *``` + * + * @see http://api.jqueryui.com/spinner/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Spinner extends InputWidget +{ + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderWidget(); + $this->registerWidget('spinner'); + } + + /** + * Renders the Spinner widget. + * @return string the rendering result. + */ + public function renderWidget() + { + if ($this->hasModel()) { + return Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + return Html::textInput($this->name, $this->value, $this->options); + } + } +} From a57597719c409465311ef5b14a72a565794db8be Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 23:50:06 +0200 Subject: [PATCH 50/70] buttons refactored (2nd round) --- framework/yii/bootstrap/Button.php | 166 +++++++--------------------- framework/yii/bootstrap/ButtonDropdown.php | 168 +++++++++++++++++++++++++++++ framework/yii/bootstrap/ButtonGroup.php | 127 ++++++++++++++++++++++ 3 files changed, 333 insertions(+), 128 deletions(-) create mode 100644 framework/yii/bootstrap/ButtonDropdown.php create mode 100644 framework/yii/bootstrap/ButtonGroup.php diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index b340a4c..be605b9 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -12,148 +12,58 @@ use yii\helpers\Html; /** - * Button renders a group or split button dropdown bootstrap component. + * Button renders a bootstrap button. * * For example, * * ```php - * // a button group using Dropdown widget - * echo \yii\bootstrap\Button::widget(array( + * echo Button::widget(array( * 'label' => 'Action', - * 'items' => Dropdown::widget(array( - * 'clientOptions' => false, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), - * )), - * )); - * - * // split button group using `items` dropdown configuration - * echo \yii\bootstrap\Button::widget(array( - * 'label' => 'Action', - * 'split' => true, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * 'options' => array('class' => 'btn-large'), * )); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons - * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns * @author Antonio Ramirez * @since 2.0 */ class Button extends Widget { - /** - * @var string the button label - */ - public $label; - /** - * @var array the HTML attributes of the button. - */ - public $buttonOptions = array(); - /** - * @var array list of menu items in the dropdown. Each array element represents a single - * menu with the following structure: - * - * - label: string, required, the label of the item link - * - url: string, optional, the url of the item link. Defaults to "#". - * - linkOptions: array, optional, the HTML attributes of the item link. - * - options: array, optional, the HTML attributes of the item. - * - items: array, optional, the dropdown items configuration array. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] - */ - public $items = array(); - /** - * @var boolean whether to display a group or split styled button group. - */ - public $split = false; - /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. - */ - public $encodeLabels = true; - - - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException - */ - public function init() - { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } - parent::init(); - $this->addCssClass($this->options, 'btn-group'); - } + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var string the button label + */ + public $label; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; - /** - * Renders the widget. - */ - public function run() - { - echo Html::beginTag('div', $this->options) . "\n"; - echo $this->renderLabel() . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag('div') . "\n"; - $this->registerPlugin('button'); - } - /** - * Generates the button label. - * @return string the rendering result. - */ - protected function renderLabel() - { - $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; - $this->addCssClass($this->buttonOptions, 'btn'); - $splitButton = ''; - if ($this->split) { - $tag = 'button'; - $options = $this->buttonOptions; - $this->buttonOptions['data-toggle'] = 'dropdown'; - $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); - $splitButton = Html::tag('button', '', $this->buttonOptions); - } else { - $tag = 'a'; - $label .= ' '; - $options = $this->buttonOptions; - if (!isset($options['href'])) { - $options['href'] = '#'; - } - $this->addCssClass($options, 'dropdown-toggle'); - $options['data-toggle'] = 'dropdown'; - } - return Html::tag($tag, $label, $options) . "\n" . $splitButton; - } + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn'); + $this->label = $this->encodeLabel ? Html::encode($this->label) : $this->label; + } - /** - * Generates the dropdown menu as specified on [[items]]. - * @return string the rendering result. - */ - protected function renderItems() - { - if (is_string($this->items)) { - return $this->items; - } - $config = array('items' => $this->items, 'clientOptions' => false); - return Dropdown::widget($config); - } + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag($this->tagName, $this->label, $this->options) . "\n"; + $this->registerPlugin('button'); + } } \ No newline at end of file diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php new file mode 100644 index 0000000..65d0398 --- /dev/null +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -0,0 +1,168 @@ + 'Action', + * 'items' => Dropdown::widget(array( + * 'clientOptions' => false, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )), + * )); + * + * // split button dropdown using `items` configuration + * echo ButtonDropdown::widget(array( + * 'label' => 'Action', + * 'split' => true, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonDropdown extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var array the HTML attributes of the button. + */ + public $buttonOptions = array(); + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] + */ + public $items = array(); + /** + * @var boolean whether to display a group or split styled button group. + */ + public $split = false; + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderButton() . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('button'); + } + + /** + * Generates the button dropdown. + * @return string the rendering result. + */ + protected function renderButton() + { + $this->addCssClass($this->buttonOptions, 'btn'); + $splitButton = ''; + if ($this->split) { + $tag = 'button'; + $options = $this->buttonOptions; + $this->buttonOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); + $splitButton = Button::widget(array( + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->buttonOptions, + ) + ); + } else { + $tag = 'a'; + $this->label .= ' '; + $options = $this->buttonOptions; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + $this->addCssClass($options, 'dropdown-toggle'); + $options['data-toggle'] = 'dropdown'; + } + return Button::widget(array( + 'tagName' => $tag, + 'label' => $this->label, + 'options' => $options, + 'encodeLabel' => false, + )) . "\n" . $splitButton; + } + + /** + * Generates the dropdown menu as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $config = array('items' => $this->items, 'clientOptions' => false); + return Dropdown::widget($config); + } +} \ No newline at end of file diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php new file mode 100644 index 0000000..9b98cc8 --- /dev/null +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -0,0 +1,127 @@ + array( + * array('label'=>'A'), + * array('label'=>'B'), + * ) + * )); + * + * // button group with an item as a string + * echo ButtonGroup::::widget(array( + * 'items' => array( + * Button::widget(array('label'=>'A')), + * array('label'=>'B'), + * ) + * )); + * + * // button group with body content as string + * ButtonGroup::beging(); + * Button::widget(array('label'=>'A')), // you can also use plain string + * ButtonGroup::end(); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonGroups + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonGroup extends Widget +{ + /** + * @var array list of buttons. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the button label. + * - options: array, optional, the HTML attributes of the button. + */ + public $items = array(); + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn-group'); + echo $this->renderGroupBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderGroupEnd(); + $this->registerPlugin('button'); + } + + /** + * Renders the opening tag of the button group. + * @return string the rendering result. + */ + protected function renderGroupBegin() + { + return Html::beginTag('div', $this->options); + } + + /** + * Renders the items and closing tag of the button group. + * @return string the rendering result. + */ + protected function renderGroupEnd() + { + return $this->renderItems() . "\n" . Html::endTag('div'); + } + + /** + * Generates the buttons that compound the group as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $buttons = array(); + foreach ($this->items as $item) { + if (is_string($item)) { + $buttons[] = $item; + continue; + } + $label = ArrayHelper::getValue($item, 'label'); + $options = ArrayHelper::getValue($item, 'options'); + $buttons[] = Button::widget(array( + 'label' => $label, + 'options' => $options, + 'encodeLabel' => $this->encodeLabels + ) + ); + } + return implode("\n", $buttons); + } +} \ No newline at end of file From d5b878d35e54000ccb96d5375b273c8ecf30f5f9 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 23:53:58 +0200 Subject: [PATCH 51/70] docs minor change --- framework/yii/bootstrap/ButtonGroup.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php index 9b98cc8..fa903a1 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -51,6 +51,8 @@ class ButtonGroup extends Widget * * - label: string, required, the button label. * - options: array, optional, the HTML attributes of the button. + * + * Optionally, you can also set each item as a string, or even the [[items]] attribute. */ public $items = array(); /** From c1b74ba9459813ceab70f3dc80b702188e34112f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 18:01:24 -0400 Subject: [PATCH 52/70] refactored Nav and NavBar. --- framework/yii/bootstrap/Nav.php | 21 ++++++++------------- framework/yii/bootstrap/NavBar.php | 23 +++++++++-------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 7a29ecd..548fe19 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -57,14 +57,8 @@ class Nav extends Widget * - linkOptions: array, optional, the HTML attributes of the item's link. * - options: array, optional, the HTML attributes of the item container (LI). * - active: boolean, optional, whether the item should be on active state or not. - * - items: array, optional, the configuration of specify the item's dropdown menu. You can optionally set this as - * a string (ie. `'items'=> Dropdown::widget(array(...))` - * - important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown. - * - * **Note:** Optionally, you can also use a plain string instead of an array element. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] + * - dropdown: array|string, optional, the configuration array for creating a [[Dropdown]] widget, + * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. */ public $items = array(); /** @@ -120,7 +114,7 @@ class Nav extends Widget } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; $options = ArrayHelper::getValue($item, 'options', array()); - $dropdown = ArrayHelper::getValue($item, 'items'); + $dropdown = ArrayHelper::getValue($item, 'dropdown'); $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); @@ -133,11 +127,12 @@ class Nav extends Widget $this->addCssClass($options, 'dropdown'); $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); - $dropdown = is_string($dropdown) - ? $dropdown - : Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + if (is_array($dropdown)) { + $dropdown['clientOptions'] = false; + $dropdown = Dropdown::widget($dropdown); + } } return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options); } -} \ No newline at end of file +} diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php index cbcd2d3..17a938c 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/framework/yii/bootstrap/NavBar.php @@ -73,7 +73,7 @@ class NavBar extends Widget * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]] * and will be used for the "href" attribute of the brand link. Defaults to site root. */ - public $brandRoute = '/'; + public $brandUrl = '/'; /** * @var array the HTML attributes of the brand link. */ @@ -98,10 +98,6 @@ class NavBar extends Widget * Optionally, you can also use a plain string instead of an array element. */ public $items = array(); - /** - * @var string the generated brand url if specified by [[brandLabel]] - */ - protected $brand; /** @@ -113,7 +109,6 @@ class NavBar extends Widget $this->clientOptions = false; $this->addCssClass($this->options, 'navbar'); $this->addCssClass($this->brandOptions, 'brand'); - $this->brand = Html::a($this->brandLabel, $this->brandRoute, $this->brandOptions); } /** @@ -124,7 +119,7 @@ class NavBar extends Widget echo Html::beginTag('div', $this->options); echo $this->renderItems(); echo Html::endTag('div'); - $this->getView()->registerAssetBundle('yii/bootstrap'); + $this->getView()->registerAssetBundle(self::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** @@ -138,17 +133,17 @@ class NavBar extends Widget $items[] = $this->renderItem($item); } $contents = implode("\n", $items); - if (self::$responsive === true) { + $brand = Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions); + + if (self::$responsive) { $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); - $contents = - Html::tag('div', + $contents = Html::tag('div', $this->renderToggleButton() . - $this->brand . "\n" . + $brand . "\n" . Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')), array('class' => 'container')); - } else { - $contents = $this->brand . "\n" . $contents; + $contents = $brand . "\n" . $contents; } return Html::tag('div', $contents, array('class' => 'navbar-inner')); @@ -190,4 +185,4 @@ class NavBar extends Widget 'data-target' => 'div.navbar-collapse', )); } -} \ No newline at end of file +} From b459990e0064fb891372019cf03972f7e8ed091b Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Thu, 30 May 2013 00:35:34 +0200 Subject: [PATCH 53/70] added progress widget --- framework/yii/bootstrap/Progress.php | 147 +++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 framework/yii/bootstrap/Progress.php diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php new file mode 100644 index 0000000..3bc346f --- /dev/null +++ b/framework/yii/bootstrap/Progress.php @@ -0,0 +1,147 @@ + 60, + * 'label' => 'test', + * )); + * + * // styled + * echo Progress::widget(array( + * 'percent' => 65, + * 'barOptions' => array('class' => 'bar-danger') + * )); + * + * // striped + * echo Progress::widget(array( + * 'percent' => 70, + * 'barOptions' => array('class' => 'bar-warning'), + * 'options' => array('class' => 'progress-striped') + * )); + * + * // striped animated + * echo Progress::widget(array( + * 'percent' => 70, + * 'barOptions' => array('class' => 'bar-success'), + * 'options' => array('class' => 'active progress-striped') + * )); + * + * // stacked and one with label + * echo Progress::widget(array( + * 'stacked' => array( + * array('percent' => 30, 'options' => array('class' => 'bar-danger')), + * array('label'=>'test', 'percent' => 30, 'options' => array('class' => 'bar-success')), + * array('percent' => 35, 'options' => array('class' => 'bar-warning')) + * ) + * )); + * ``` + * @see http://twitter.github.io/bootstrap/components.html#progress + * @author Antonio Ramirez + * @since 2.0 + */ +class Progress extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var integer the amount of progress as a percentage. + */ + public $percent = 0; + /** + * @var array the HTML attributes of the + */ + public $barOptions = array(); + /** + * @var array $stacked set to an array of progress bar values to display stacked progress bars + * + * ```php + * 'stacked'=>array( + * array('percent'=>'30', 'options'=>array('class'=>'custom')), + * array('percent'=>'30'), + * ) + * ``` + */ + public $stacked = false; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + if ($this->label === null && $this->stacked == false) { + throw new InvalidConfigException("The 'percent' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'progress'); + $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderProgress() . "\n"; + echo Html::endTag('div') . "\n"; + } + + /** + * Generates a bar + * @param int $percent the percentage of the bar + * @param string $label, optional, the label to display at the bar + * @param array $options the HTML attributes of the bar + * @return string the rendering result. + */ + public function bar($percent, $label = '', $options = array()) + { + $this->addCssClass($options, 'bar'); + $options['style'] = "width:{$percent}%"; + return Html::tag('div', $label, $options); + } + + /** + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderProgress() + { + if ($this->stacked === false) { + return $this->bar($this->percent, $this->label, $this->barOptions); + } + $bars = array(); + foreach ($this->stacked as $item) { + $label = ArrayHelper::getValue($item, 'label', ''); + if (!isset($item['percent'])) { + throw new InvalidConfigException("The 'percent' option is required."); + } + $options = ArrayHelper::getValue($item, 'options', array()); + + $bars[] = $this->bar($item['percent'], $label, $options); + } + return implode("\n", $bars); + } + +} \ No newline at end of file From c77d3193a5ec007a11ebe124ca747ce1f69c628a Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 03:30:14 +0400 Subject: [PATCH 54/70] Comments --- framework/yii/jui/Tabs.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index d25eaa2..13bd4eb 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -78,10 +78,11 @@ class Tabs extends Widget * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label * will be HTML-encoded. * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified. - * - url: mixed, @todo comment - * - template: string, optional, @todo comment - * - options: array, optional, @todo comment - * - headerOptions: array, optional, @todo comment + * - url: mixed, mixed, optional, the url to load tab contents via AJAX. It is required if no content is specified. + * - template: string, optional, the header link template to render the header link. If none specified + * [[linkTemplate]] will be used instead. + * - options: array, optional, the HTML attributes of the header. + * - headerOptions: array, optional, the HTML attributes for the header container tag. */ public $items = array(); /** @@ -97,7 +98,7 @@ class Tabs extends Widget */ public $headerOptions = array(); /** - * @var string @todo comment + * @var string the default header template to render the link. */ public $linkTemplate = '{label}'; /** From 9339edbd45c15edeb5341962b8628668a6892891 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 20:07:02 -0400 Subject: [PATCH 55/70] Refactored Progress. --- framework/yii/bootstrap/Progress.php | 78 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php index 3bc346f..708c0fe 100644 --- a/framework/yii/bootstrap/Progress.php +++ b/framework/yii/bootstrap/Progress.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -44,11 +44,11 @@ use yii\helpers\Html; * 'options' => array('class' => 'active progress-striped') * )); * - * // stacked and one with label + * // stacked bars * echo Progress::widget(array( - * 'stacked' => array( + * 'bars' => array( * array('percent' => 30, 'options' => array('class' => 'bar-danger')), - * array('label'=>'test', 'percent' => 30, 'options' => array('class' => 'bar-success')), + * array('percent' => 30, 'label'=>'test', 'options' => array('class' => 'bar-success')), * array('percent' => 35, 'options' => array('class' => 'bar-warning')) * ) * )); @@ -72,16 +72,20 @@ class Progress extends Widget */ public $barOptions = array(); /** - * @var array $stacked set to an array of progress bar values to display stacked progress bars + * @var array a set of bars that are stacked together to form a single progress bar. + * Each bar is an array of the following structure: * * ```php - * 'stacked'=>array( - * array('percent'=>'30', 'options'=>array('class'=>'custom')), - * array('percent'=>'30'), - * ) - * ``` + * array( + * // required, the amount of progress as a percentage. + * 'percent' => 30, + * // optional, the label to be displayed on the bar + * 'label' => '30%', + * // optional, array, additional HTML attributes for the bar tag + * 'options' => array(), + * ) */ - public $stacked = false; + public $bars; /** @@ -90,12 +94,8 @@ class Progress extends Widget */ public function init() { - if ($this->label === null && $this->stacked == false) { - throw new InvalidConfigException("The 'percent' option is required."); - } parent::init(); $this->addCssClass($this->options, 'progress'); - $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** @@ -106,42 +106,42 @@ class Progress extends Widget echo Html::beginTag('div', $this->options) . "\n"; echo $this->renderProgress() . "\n"; echo Html::endTag('div') . "\n"; + $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** - * Generates a bar - * @param int $percent the percentage of the bar - * @param string $label, optional, the label to display at the bar - * @param array $options the HTML attributes of the bar - * @return string the rendering result. - */ - public function bar($percent, $label = '', $options = array()) - { - $this->addCssClass($options, 'bar'); - $options['style'] = "width:{$percent}%"; - return Html::tag('div', $label, $options); - } - - /** + * Renders the progress. * @return string the rendering result. - * @throws InvalidConfigException + * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar. */ protected function renderProgress() { - if ($this->stacked === false) { - return $this->bar($this->percent, $this->label, $this->barOptions); + if (empty($this->bars)) { + return $this->renderBar($this->percent, $this->label, $this->barOptions); } $bars = array(); - foreach ($this->stacked as $item) { - $label = ArrayHelper::getValue($item, 'label', ''); - if (!isset($item['percent'])) { + foreach ($this->bars as $bar) { + $label = ArrayHelper::getValue($bar, 'label', ''); + if (!isset($bar['percent'])) { throw new InvalidConfigException("The 'percent' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - - $bars[] = $this->bar($item['percent'], $label, $options); + $options = ArrayHelper::getValue($bar, 'options', array()); + $bars[] = $this->renderBar($bar['percent'], $label, $options); } return implode("\n", $bars); } -} \ No newline at end of file + /** + * Generates a bar + * @param int $percent the percentage of the bar + * @param string $label, optional, the label to display at the bar + * @param array $options the HTML attributes of the bar + * @return string the rendering result. + */ + protected function renderBar($percent, $label = '', $options = array()) + { + $this->addCssClass($options, 'bar'); + $options['style'] = "width:{$percent}%"; + return Html::tag('div', $label, $options); + } +} From f05f85fcfe9fd46f37f79d014f2544bc96bb16da Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 20:12:24 -0400 Subject: [PATCH 56/70] Removed Connection::__sleep. --- framework/yii/db/Connection.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index e14eeb7..f8883bb 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -251,16 +251,7 @@ class Connection extends Component */ private $_schema; - /** - * Closes the connection when this component is being serialized. - * @return array - */ - public function __sleep() - { - $this->close(); - return array_keys(get_object_vars($this)); - } - + /** * Returns a value indicating whether the DB connection is established. * @return boolean whether the DB connection is established From 66e698673d1fb95222d160f3948d23b8d6372940 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 21:53:25 -0400 Subject: [PATCH 57/70] Fixes issue #457: incorrect HTTPS detection. --- framework/yii/db/Connection.php | 2 +- framework/yii/web/Request.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index f8883bb..3a4d0ad 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -251,7 +251,7 @@ class Connection extends Component */ private $_schema; - + /** * Returns a value indicating whether the DB connection is established. * @return boolean whether the DB connection is established diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 5cd6912..a857926 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -533,8 +533,8 @@ class Request extends \yii\base\Request */ public function getIsSecureConnection() { - return isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') - || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']==='https'; + return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1) + || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; } /** From 94b2d170314f79f15fdd6d4dd41413caace7f02f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 07:35:03 -0400 Subject: [PATCH 58/70] Fixes issue #460 --- framework/yii/db/mysql/QueryBuilder.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 7bf293b..70c6d64 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -150,17 +150,20 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function batchInsert($table, $columns, $rows) { + foreach ($columns as $i => $name) { + $columns[$i] = $this->db->quoteColumnName($name); + } + $values = array(); foreach ($rows as $row) { $vs = array(); foreach ($row as $value) { $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value; } - $values[] = $vs; + $values[] = '(' . implode(', ', $vs) . ')'; } return 'INSERT INTO ' . $this->db->quoteTableName($table) - . ' (' . implode(', ', $columns) . ') VALUES (' - . implode(', ', $values) . ')'; + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); } } From e1cde74b7b0cfcffd3613e4caf1a35f1ae430413 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 07:57:06 -0400 Subject: [PATCH 59/70] Fixed doc. --- framework/yii/base/View.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 9d6b921..7f965fa 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -130,8 +130,8 @@ class View extends Component */ public $dynamicPlaceholders = array(); /** - * @var array the registered asset bundles. The keys are the bundle names, and the values - * are the corresponding [[AssetBundle]] objects. + * @var array list of the registered asset bundles. The keys are the bundle names, and the values + * are booleans indicating whether the bundles have been registered. * @see registerAssetBundle */ public $assetBundles; From a93f75068e1062ef22896a01329339acd772f9f6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:01:11 -0400 Subject: [PATCH 60/70] refactoring button classes. --- framework/yii/bootstrap/Button.php | 68 +++++++++++++---------------- framework/yii/bootstrap/ButtonDropdown.php | 33 +++++--------- framework/yii/bootstrap/ButtonGroup.php | 70 +++++++++--------------------- 3 files changed, 63 insertions(+), 108 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index be605b9..104e700 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -6,7 +6,6 @@ */ namespace yii\bootstrap; - use yii\base\InvalidConfigException; use yii\helpers\Html; @@ -28,42 +27,37 @@ use yii\helpers\Html; */ class Button extends Widget { - /** - * @var string the tag to use to render the button - */ - public $tagName = 'button'; - /** - * @var string the button label - */ - public $label; - /** - * @var boolean whether the label should be HTML-encoded. - */ - public $encodeLabel = true; + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var string the button label + */ + public $label = 'Button'; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException - */ - public function init() - { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } - parent::init(); - $this->clientOptions = false; - $this->addCssClass($this->options, 'btn'); - $this->label = $this->encodeLabel ? Html::encode($this->label) : $this->label; - } + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn'); + } - /** - * Renders the widget. - */ - public function run() - { - echo Html::tag($this->tagName, $this->label, $this->options) . "\n"; - $this->registerPlugin('button'); - } -} \ No newline at end of file + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options); + $this->registerPlugin('button'); + } +} diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php index 65d0398..6b9c51d 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -6,8 +6,6 @@ */ namespace yii\bootstrap; - -use yii\base\InvalidConfigException; use yii\helpers\Html; @@ -61,13 +59,14 @@ class ButtonDropdown extends Widget /** * @var string the button label */ - public $label; + public $label = 'Button'; /** * @var array the HTML attributes of the button. */ public $buttonOptions = array(); /** - * @var array list of menu items in the dropdown. Each array element represents a single + * @var array list of menu items in the dropdown. This will be used to + * set the [[Dropdown::items]] property. Each array element represents a single * menu with the following structure: * * - label: string, required, the label of the item link @@ -81,7 +80,7 @@ class ButtonDropdown extends Widget */ public $items = array(); /** - * @var boolean whether to display a group or split styled button group. + * @var boolean whether to display a group of split-styled button group. */ public $split = false; /** @@ -93,13 +92,9 @@ class ButtonDropdown extends Widget /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException */ public function init() { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } parent::init(); $this->addCssClass($this->options, 'btn-group'); } @@ -111,7 +106,7 @@ class ButtonDropdown extends Widget { echo Html::beginTag('div', $this->options) . "\n"; echo $this->renderButton() . "\n"; - echo $this->renderItems() . "\n"; + echo $this->renderDropdown() . "\n"; echo Html::endTag('div') . "\n"; $this->registerPlugin('button'); } @@ -123,18 +118,16 @@ class ButtonDropdown extends Widget protected function renderButton() { $this->addCssClass($this->buttonOptions, 'btn'); - $splitButton = ''; if ($this->split) { $tag = 'button'; $options = $this->buttonOptions; $this->buttonOptions['data-toggle'] = 'dropdown'; $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); $splitButton = Button::widget(array( - 'label' => '', - 'encodeLabel' => false, - 'options' => $this->buttonOptions, - ) - ); + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->buttonOptions, + )); } else { $tag = 'a'; $this->label .= ' '; @@ -144,6 +137,7 @@ class ButtonDropdown extends Widget } $this->addCssClass($options, 'dropdown-toggle'); $options['data-toggle'] = 'dropdown'; + $splitButton = ''; } return Button::widget(array( 'tagName' => $tag, @@ -157,12 +151,9 @@ class ButtonDropdown extends Widget * Generates the dropdown menu as specified on [[items]]. * @return string the rendering result. */ - protected function renderItems() + protected function renderDropdown() { - if (is_string($this->items)) { - return $this->items; - } $config = array('items' => $this->items, 'clientOptions' => false); return Dropdown::widget($config); } -} \ No newline at end of file +} diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php index fa903a1..f50d6a8 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -20,23 +20,18 @@ use yii\helpers\Html; * // a button group with items configuration * echo ButtonGroup::::widget(array( * 'items' => array( - * array('label'=>'A'), - * array('label'=>'B'), + * array('label' => 'A'), + * array('label' => 'B'), * ) * )); * * // button group with an item as a string * echo ButtonGroup::::widget(array( * 'items' => array( - * Button::widget(array('label'=>'A')), - * array('label'=>'B'), + * Button::widget(array('label' => 'A')), + * array('label' => 'B'), * ) * )); - * - * // button group with body content as string - * ButtonGroup::beging(); - * Button::widget(array('label'=>'A')), // you can also use plain string - * ButtonGroup::end(); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons * @see http://twitter.github.io/bootstrap/components.html#buttonGroups @@ -46,17 +41,15 @@ use yii\helpers\Html; class ButtonGroup extends Widget { /** - * @var array list of buttons. Each array element represents a single - * menu with the following structure: + * @var array list of buttons. Each array element represents a single button + * which can be specified as a string or an array of the following structure: * * - label: string, required, the button label. * - options: array, optional, the HTML attributes of the button. - * - * Optionally, you can also set each item as a string, or even the [[items]] attribute. */ - public $items = array(); + public $buttons = array(); /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. + * @var boolean whether to HTML-encode the button labels. */ public $encodeLabels = true; @@ -70,7 +63,6 @@ class ButtonGroup extends Widget parent::init(); $this->clientOptions = false; $this->addCssClass($this->options, 'btn-group'); - echo $this->renderGroupBegin() . "\n"; } /** @@ -78,52 +70,30 @@ class ButtonGroup extends Widget */ public function run() { - echo "\n" . $this->renderGroupEnd(); + echo Html::tag('div', $this->renderButtons(), $this->options); $this->registerPlugin('button'); } /** - * Renders the opening tag of the button group. - * @return string the rendering result. - */ - protected function renderGroupBegin() - { - return Html::beginTag('div', $this->options); - } - - /** - * Renders the items and closing tag of the button group. - * @return string the rendering result. - */ - protected function renderGroupEnd() - { - return $this->renderItems() . "\n" . Html::endTag('div'); - } - - /** * Generates the buttons that compound the group as specified on [[items]]. * @return string the rendering result. */ - protected function renderItems() + protected function renderButtons() { - if (is_string($this->items)) { - return $this->items; - } $buttons = array(); - foreach ($this->items as $item) { - if (is_string($item)) { - $buttons[] = $item; - continue; - } - $label = ArrayHelper::getValue($item, 'label'); - $options = ArrayHelper::getValue($item, 'options'); - $buttons[] = Button::widget(array( + foreach ($this->buttons as $button) { + if (is_array($button)) { + $label = ArrayHelper::getValue($button, 'label'); + $options = ArrayHelper::getValue($button, 'options'); + $buttons[] = Button::widget(array( 'label' => $label, 'options' => $options, 'encodeLabel' => $this->encodeLabels - ) - ); + )); + } else { + $buttons[] = $button; + } } return implode("\n", $buttons); } -} \ No newline at end of file +} From 79aaf1fbec5e422062993a6078111997e0c0561f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:13:39 -0400 Subject: [PATCH 61/70] refactored ButtonDropdown and Dropdown. --- framework/yii/bootstrap/ButtonDropdown.php | 45 +++++------------------------- framework/yii/bootstrap/Dropdown.php | 36 +++++++++--------------- 2 files changed, 20 insertions(+), 61 deletions(-) diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php index 6b9c51d..4168bd5 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -18,8 +18,7 @@ use yii\helpers\Html; * // a button group using Dropdown widget * echo ButtonDropdown::widget(array( * 'label' => 'Action', - * 'items' => Dropdown::widget(array( - * 'clientOptions' => false, + * 'dropdown' => array( * 'items' => array( * array( * 'label' => 'DropdownA', @@ -30,23 +29,7 @@ use yii\helpers\Html; * 'url' => '#', * ), * ), - * )), - * )); - * - * // split button dropdown using `items` configuration - * echo ButtonDropdown::widget(array( - * 'label' => 'Action', - * 'split' => true, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * ), * )); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons @@ -65,28 +48,13 @@ class ButtonDropdown extends Widget */ public $buttonOptions = array(); /** - * @var array list of menu items in the dropdown. This will be used to - * set the [[Dropdown::items]] property. Each array element represents a single - * menu with the following structure: - * - * - label: string, required, the label of the item link - * - url: string, optional, the url of the item link. Defaults to "#". - * - linkOptions: array, optional, the HTML attributes of the item link. - * - options: array, optional, the HTML attributes of the item. - * - items: array, optional, the dropdown items configuration array. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] + * @var array the configuration array for [[Dropdown]]. */ - public $items = array(); + public $dropdown = array(); /** * @var boolean whether to display a group of split-styled button group. */ public $split = false; - /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. - */ - public $encodeLabels = true; /** @@ -148,12 +116,13 @@ class ButtonDropdown extends Widget } /** - * Generates the dropdown menu as specified on [[items]]. + * Generates the dropdown menu. * @return string the rendering result. */ protected function renderDropdown() { - $config = array('items' => $this->items, 'clientOptions' => false); + $config = $this->dropdown; + $config['clientOptions'] = false; return Dropdown::widget($config); } } diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index 0594b08..2bee0ff 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -13,7 +13,7 @@ use yii\helpers\Html; /** - * Dropdown renders a Tab bootstrap javascript component. + * Dropdown renders a Bootstrap dropdown menu component. * * @see http://twitter.github.io/bootstrap/javascript.html#dropdowns * @author Antonio Ramirez @@ -55,21 +55,22 @@ class Dropdown extends Widget */ public function run() { - echo $this->renderItems() . "\n"; + echo $this->renderItems($this->items); $this->registerPlugin('dropdown'); } /** - * Renders dropdown items as specified on [[items]]. + * Renders menu items. + * @param array $items the menu items to be rendered * @return string the rendering result. - * @throws InvalidConfigException + * @throws InvalidConfigException if the label option is not specified in one of the items. */ - protected function renderItems() + protected function renderItems($items) { - $items = array(); - foreach ($this->items as $item) { + $lines = array(); + foreach ($items as $item) { if (is_string($item)) { - $items[] = $item; + $lines[] = $item; continue; } if (!isset($item['label'])) { @@ -82,24 +83,13 @@ class Dropdown extends Widget if (isset($item['items'])) { $this->addCssClass($options, 'dropdown-submenu'); - $content = Html::a($label, '#', $linkOptions) . $this->dropdown($item['items']); + $content = Html::a($label, '#', $linkOptions) . $this->renderItems($item['items']); } else { $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); } - $items[] = Html::tag('li', $content , $options); + $lines[] = Html::tag('li', $content, $options); } - return Html::tag('ul', implode("\n", $items), $this->options); + return Html::tag('ul', implode("\n", $lines), $this->options); } - - /** - * Generates a dropdown menu. - * @param array $items the configuration of the dropdown items. See [[items]]. - * @return string the generated dropdown menu - * @see items - */ - protected function dropdown($items) - { - return static::widget(array('items' => $items, 'clientOptions' => false)); - } -} \ No newline at end of file +} From 60c351ea9348b3e4f88ae5628c3af5e700adf19c Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:35:29 -0400 Subject: [PATCH 62/70] refactored Tabs. --- framework/yii/bootstrap/Tabs.php | 72 ++++++++++++---------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 1af79b4..5aeac92 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -26,8 +26,8 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Two', - * 'headerOptions' => array(...), * 'content' => 'Anim pariatur cliche...', + * 'headerOptions' => array(...), * 'options' => array('id'=>'myveryownID'), * ), * array( @@ -61,12 +61,12 @@ class Tabs extends Widget * - headerOptions: array, optional, the HTML attributes of the tab header. * - content: array, required if `items` is not set. The content (HTML) of the tab pane. * - options: array, optional, the HTML attributes of the tab pane container. - * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - active: boolean, optional, whether the item tab header and pane should be visible or not. * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items * configuration array. Items can also hold two extra keys: - * - active: boolean, optional, whether the item tab header and pane should be visibles or not. - * - content: string, required if `items` is not set. The content (HTML) of the tab pane. - * - contentOptions: optional, array, the HTML attributes of the tab content container. + * * active: boolean, optional, whether the item tab header and pane should be visible or not. + * * content: string, required if `items` is not set. The content (HTML) of the tab pane. + * * contentOptions: optional, array, the HTML attributes of the tab content container. */ public $items = array(); /** @@ -94,7 +94,6 @@ class Tabs extends Widget { parent::init(); $this->addCssClass($this->options, 'nav nav-tabs'); - } /** @@ -119,25 +118,21 @@ class Tabs extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } - $label = $this->label($item['label']); - $headerOptions = $this->mergedOptions($item, 'headerOptions'); + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); if (isset($item['items'])) { $label .= ' '; $this->addCssClass($headerOptions, 'dropdown'); - if ($this->normalizeItems($item['items'], $panes)) { + if ($this->renderDropdown($item['items'], $panes)) { $this->addCssClass($headerOptions, 'active'); } - $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"; - $header .= Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); - - } else { - $options = $this->mergedOptions($item, 'itemOptions', 'options'); + $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n" + . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + } elseif (isset($item['content'])) { + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options')); $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); $this->addCssClass($options, 'tab-pane'); @@ -147,40 +142,15 @@ class Tabs extends Widget } $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab', 'tabindex' => '-1')); $panes[] = Html::tag('div', $item['content'], $options); - + } else { + throw new InvalidConfigException("Either the 'content' or 'items' option must be set."); } - $headers[] = Html::tag('li', $header, array_merge($this->headerOptions, $headerOptions)); - } - return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" . - Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); - } - - /** - * Returns encoded if specified on [[encodeLabels]], original string otherwise. - * @param string $content the label text to encode or return - * @return string the resulting label. - */ - protected function label($content) - { - return $this->encodeLabels ? Html::encode($content) : $content; - } - - /** - * Returns array of options merged with specified attribute array. The availabel options are: - * - [[itemOptions]] - * - [[headerOptions]] - * @param array $item the item to merge the options with - * @param string $name the property name. - * @param string $key the key to extract. If null, it is assumed to be the same as `$name`. - * @return array the merged array options. - */ - protected function mergedOptions($item, $name, $key = null) - { - if ($key === null) { - $key = $name; + $headers[] = Html::tag('li', $header, $headerOptions); } - return array_merge($this->{$name}, ArrayHelper::getValue($item, $key, array())); + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" + . Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); } /** @@ -191,7 +161,7 @@ class Tabs extends Widget * @return boolean whether any of the dropdown items is `active` or not. * @throws InvalidConfigException */ - protected function normalizeItems(&$items, &$panes) + protected function renderDropdown(&$items, &$panes) { $itemActive = false; @@ -199,7 +169,7 @@ class Tabs extends Widget if (is_string($item)) { continue; } - if (!isset($item['content']) && !isset($item['items'])) { + if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } @@ -222,4 +192,4 @@ class Tabs extends Widget } return $itemActive; } -} \ No newline at end of file +} From c8df8e2abac4c6e74d3f0232e3b0e3b329ad528a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:37:33 -0400 Subject: [PATCH 63/70] doc fix. --- framework/yii/bootstrap/Tabs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 5aeac92..a18eef6 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -32,7 +32,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * 'dropdown' => array( + * 'items' => array( * array( * 'label' => 'DropdownA', * 'content' => 'DropdownA, Anim pariatur cliche...', @@ -63,7 +63,7 @@ class Tabs extends Widget * - options: array, optional, the HTML attributes of the tab pane container. * - active: boolean, optional, whether the item tab header and pane should be visible or not. * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items - * configuration array. Items can also hold two extra keys: + * configuration array. Each item can hold two extra keys, besides the above ones: * * active: boolean, optional, whether the item tab header and pane should be visible or not. * * content: string, required if `items` is not set. The content (HTML) of the tab pane. * * contentOptions: optional, array, the HTML attributes of the tab content container. From 5e9c8cb7c4a190f9f696225bd476617b6a18f0aa Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 14:57:28 -0400 Subject: [PATCH 64/70] Fixes issue #446: automatically scroll to first error. --- framework/yii/assets/yii.activeForm.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index 483df96..fb4c16a 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -135,12 +135,16 @@ data.submitting = true; if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) { validate($form, function (messages) { - var hasError = false; + var errors = []; $.each(data.attributes, function () { - hasError = updateInput($form, this, messages) || hasError; + if (updateInput($form, this, messages)) { + errors.push(this.input); + } }); updateSummary($form, messages); - if (!hasError) { + if (errors.length) { + $(window).scrollTop($form.find(errors.join(',')).first().offset().top); + } else { data.validated = true; var $button = data.submitObject || $form.find(':submit:first'); // TODO: if the submission is caused by "change" event, it will not work From d0ba90c1e4658ff7b22462ad841042161005b4ef Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 15:05:42 -0400 Subject: [PATCH 65/70] better auto scrolling. --- framework/yii/assets/yii.activeForm.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index fb4c16a..1a2e58d 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -143,7 +143,11 @@ }); updateSummary($form, messages); if (errors.length) { - $(window).scrollTop($form.find(errors.join(',')).first().offset().top); + var top = $form.find(errors.join(',')).first().offset().top; + var wtop = $(window).scrollTop(); + if (top < wtop || top > wtop + $(window).height) { + $(window).scrollTop(top); + } } else { data.validated = true; var $button = data.submitObject || $form.find(':submit:first'); From c9a7119b91beb7908405054569bee8f558d13802 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 08:43:15 -0400 Subject: [PATCH 66/70] Fixes issue #467: allow view file to be absent as long as the themed version exists. --- framework/yii/base/View.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 7f965fa..219a0fb 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -235,10 +235,10 @@ class View extends Component public function renderFile($viewFile, $params = array(), $context = null) { $viewFile = Yii::getAlias($viewFile); + if ($this->theme !== null) { + $viewFile = $this->theme->applyTo($viewFile); + } if (is_file($viewFile)) { - if ($this->theme !== null) { - $viewFile = $this->theme->applyTo($viewFile); - } $viewFile = FileHelper::localize($viewFile); } else { throw new InvalidParamException("The view file does not exist: $viewFile"); From 00dec2bfbbbc30abeda70bc8bebcc2ecf6ce4188 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 31 May 2013 19:02:04 +0400 Subject: [PATCH 67/70] \yii\widgets\Menu improvement --- framework/yii/widgets/Menu.php | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/framework/yii/widgets/Menu.php b/framework/yii/widgets/Menu.php index 08088d3..5b5d48c 100644 --- a/framework/yii/widgets/Menu.php +++ b/framework/yii/widgets/Menu.php @@ -9,6 +9,7 @@ namespace yii\widgets; use Yii; use yii\base\Widget; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -16,15 +17,15 @@ use yii\helpers\Html; * * The main property of Menu is [[items]], which specifies the possible items in the menu. * A menu item can contain sub-items which specify the sub-menu under that menu item. - * + * * Menu checks the current route and request parameters to toggle certain menu items * with active state. - * + * * Note that Menu only renders the HTML tags about the menu. It does do any styling. * You are responsible to provide CSS styles to make it look like a real menu. * * The following example shows how to use Menu: - * + * * ~~~ * echo Menu::widget(array( * 'items' => array( @@ -40,7 +41,7 @@ use yii\helpers\Html; * ), * )); * ~~~ - * + * * @author Qiang Xue * @since 2.0 */ @@ -68,6 +69,13 @@ class Menu extends Widget */ public $items = array(); /** + * @var array list of HTML attributes for the menu container tag. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "li", the tag name of the item container tags. + */ + public $itemOptions = array(); + /** * @var string the template used to render the body of a menu which is a link. * In this template, the token `{url}` will be replaced with the corresponding link URL; * while `{label}` will be replaced with the link text. @@ -110,7 +118,9 @@ class Menu extends Widget */ public $hideEmptyItems = true; /** - * @var array the HTML attributes for the menu's container tag. + * @var array the HTML attributes for the menu's container tag. The following special options are recognized: + * + * - tag: string, defaults to "ul", the tag name of the item container tags. */ public $options = array(); /** @@ -125,7 +135,7 @@ class Menu extends Widget public $lastItemCssClass; /** * @var string the route used to determine if a menu item is active or not. - * If not set, it will use the route of the current request. + * If not set, it will use the route of the current request. * @see params * @see isItemActive */ @@ -151,7 +161,9 @@ class Menu extends Widget $this->params = $_GET; } $items = $this->normalizeItems($this->items, $hasActiveChild); - echo Html::tag('ul', $this->renderItems($items), $this->options); + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + echo Html::tag($tag, $this->renderItems($items), $options); } /** @@ -164,7 +176,8 @@ class Menu extends Widget $n = count($items); $lines = array(); foreach ($items as $i => $item) { - $options = isset($item['options']) ? $item['options'] : array(); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', 'li'); $class = array(); if ($item['active']) { $class[] = $this->activeCssClass; @@ -189,7 +202,7 @@ class Menu extends Widget '{items}' => $this->renderItems($item['items']), )); } - $lines[] = Html::tag('li', $menu, $options); + $lines[] = Html::tag($tag, $menu, $options); } return implode("\n", $lines); } @@ -203,13 +216,13 @@ class Menu extends Widget protected function renderItem($item) { if (isset($item['url'])) { - $template = isset($item['template']) ? $item['template'] : $this->linkTemplate; + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); return strtr($template, array( '{url}' => Html::url($item['url']), '{label}' => $item['label'], )); } else { - $template = isset($item['template']) ? $item['template'] : $this->labelTemplate; + $template = ArrayHelper::getValue($item, 'template', $this->labelTemplate); return strtr($template, array( '{label}' => $item['label'], )); @@ -284,5 +297,4 @@ class Menu extends Widget } return false; } - } From 0d34aab0ab8bf27d27f01d248a37acfce7a3d332 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 31 May 2013 18:54:13 +0200 Subject: [PATCH 68/70] added missing default to getValue in boostrap tabs fixes #465 --- framework/yii/bootstrap/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index a18eef6..4a85b9a 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -132,7 +132,7 @@ class Tabs extends Widget $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n" . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); } elseif (isset($item['content'])) { - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options')); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); $this->addCssClass($options, 'tab-pane'); From 800723945b98430fb56fbe36bf74eeac44006f59 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 21:52:20 -0400 Subject: [PATCH 69/70] Fixes issue #194: Added Application::catchAll. --- framework/yii/web/Application.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index a786985..12c9295 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -23,6 +23,26 @@ class Application extends \yii\base\Application * @var string the default route of this application. Defaults to 'site'. */ public $defaultRoute = 'site'; + /** + * @var array the configuration specifying a controller action which should handle + * all user requests. This is mainly used when the application is in maintenance mode + * and needs to handle all incoming requests via a single action. + * The configuration is an array whose first element specifies the route of the action. + * The rest of the array elements (key-value pairs) specify the parameters to be bound + * to the action. For example, + * + * ~~~ + * array( + * 'offline/notice', + * 'param1' => 'value1', + * 'param2' => 'value2', + * ) + * ~~~ + * + * Defaults to null, meaning catch-all is not effective. + */ + public $catchAll; + /** * Processes the request. @@ -34,7 +54,12 @@ class Application extends \yii\base\Application $request = $this->getRequest(); Yii::setAlias('@wwwroot', dirname($request->getScriptFile())); Yii::setAlias('@www', $request->getBaseUrl()); - list ($route, $params) = $request->resolve(); + if (empty($this->catchAll)) { + list ($route, $params) = $request->resolve(); + } else { + $route = $this->catchAll[0]; + $params = array_splice($this->catchAll, 1); + } try { return $this->runAction($route, $params); } catch (InvalidRouteException $e) { From 800b3dbed0bc8639a2052986a1c85c1a47b1b2a6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 21:55:11 -0400 Subject: [PATCH 70/70] Fixed build break. --- tests/unit/framework/i18n/FormatterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index f742f22..2a67422 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -83,6 +83,6 @@ class FormatterTest extends TestCase { $time = time(); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); - $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); + $this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long')); } }