Commit 1dcb63ca by resurtm

Merge branch 'master' of github.com:yiisoft/yii2

parents 2f360e53 f9a92b82
...@@ -232,6 +232,13 @@ for Russian: ...@@ -232,6 +232,13 @@ for Russian:
In the above it worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`. In the above it worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`.
Note that if you are using placeholder twice and one time it's used as plural another one should be used as number else
you'll get "Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATCH" error:
```
Total {count, number} {count, plural, one{item} other{items}}.
```
To learn which inflection forms you should specify for your language you can referer to To learn which inflection forms you should specify for your language you can referer to
[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).
......
...@@ -106,6 +106,15 @@ Yii::$app->on($eventName, $handler); ...@@ -106,6 +106,15 @@ Yii::$app->on($eventName, $handler);
Yii::$app->trigger($eventName); Yii::$app->trigger($eventName);
``` ```
If you need to handle all instances of a class instead of the object you can attach a handler like the following:
```php
Event::on([ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT], function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted.');
});
```
The code above defines a handler that will be triggered for every Active Record object's `EVENT_AFTER_INSERT` event.
Path Alias Path Alias
---------- ----------
......
...@@ -358,13 +358,13 @@ class Component extends Object ...@@ -358,13 +358,13 @@ class Component extends Object
public function hasEventHandlers($name) public function hasEventHandlers($name)
{ {
$this->ensureBehaviors(); $this->ensureBehaviors();
return !empty($this->_events[$name]); return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
} }
/** /**
* Attaches an event handler to an event. * Attaches an event handler to an event.
* *
* An event handler must be a valid PHP callback. The followings are * The event handler must be a valid PHP callback. The followings are
* some examples: * some examples:
* *
* ~~~ * ~~~
...@@ -374,7 +374,7 @@ class Component extends Object ...@@ -374,7 +374,7 @@ class Component extends Object
* 'handleClick' // global function handleClick() * 'handleClick' // global function handleClick()
* ~~~ * ~~~
* *
* An event handler must be defined with the following signature, * The event handler must be defined with the following signature,
* *
* ~~~ * ~~~
* function ($event) * function ($event)
...@@ -455,6 +455,7 @@ class Component extends Object ...@@ -455,6 +455,7 @@ class Component extends Object
} }
} }
} }
Event::trigger($this, $name, $event);
} }
/** /**
......
...@@ -45,4 +45,136 @@ class Event extends Object ...@@ -45,4 +45,136 @@ class Event extends Object
* Note that this varies according to which event handler is currently executing. * Note that this varies according to which event handler is currently executing.
*/ */
public $data; public $data;
private static $_events = [];
/**
* Attaches an event handler to a class-level event.
*
* When a class-level event is triggered, event handlers attached
* to that class and all parent classes will be invoked.
*
* For example, the following code attaches an event handler to `ActiveRecord`'s
* `afterInsert` event:
*
* ~~~
* Event::on([ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT], function ($event) {
* Yii::trace(get_class($event->sender) . ' is inserted.');
* });
* ~~~
*
* The handler will be invoked for EVERY successful ActiveRecord insertion.
*
* For more details about how to declare an event handler, please refer to [[Component::on()]].
*
* @param string $class the fully qualified class name to which the event handler needs to attach
* @param string $name the event name
* @param callback $handler the event handler
* @param mixed $data the data to be passed to the event handler when the event is triggered.
* When the event handler is invoked, this data can be accessed via [[Event::data]].
* @see off()
*/
public static function on($class, $name, $handler, $data = null)
{
self::$_events[$name][ltrim($class, '\\')][] = [$handler, $data];
}
/**
* Detaches an event handler from a class-level event.
*
* This method is the opposite of [[on()]].
*
* @param string $class the fully qualified class name from which the event handler needs to be detached
* @param string $name the event name
* @param callback $handler the event handler to be removed.
* If it is null, all handlers attached to the named event will be removed.
* @return boolean if a handler is found and detached
* @see on()
*/
public static function off($class, $name, $handler = null)
{
$class = ltrim($class, '\\');
if (empty(self::$_events[$name][$class])) {
return false;
}
if ($handler === null) {
unset(self::$_events[$name][$class]);
return true;
} else {
$removed = false;
foreach (self::$_events[$name][$class] as $i => $event) {
if ($event[0] === $handler) {
unset(self::$_events[$name][$class][$i]);
$removed = true;
}
}
if ($removed) {
self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
}
return $removed;
}
}
/**
* Returns a value indicating whether there is any handler attached to the specified class-level event.
* Note that this method will also check all parent classes to see if there is any handler attached
* to the named event.
* @param string|object $class the object or the fully qualified class name specifying the class-level event
* @param string $name the event name
* @return boolean whether there is any handler attached to the event.
*/
public static function hasHandlers($class, $name)
{
if (empty(self::$_events[$name])) {
return false;
}
if (is_object($class)) {
$class = get_class($class);
} else {
$class = ltrim($class, '\\');
}
do {
if (!empty(self::$_events[$name][$class])) {
return true;
}
} while (($class = get_parent_class($class)) !== false);
return false;
}
/**
* Triggers a class-level event.
* This method will cause invocation of event handlers that are attached to the named event
* for the specified class and all its parent classes.
* @param string|object $class the object or the fully qualified class name specifying the class-level event
* @param string $name the event name
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
*/
public static function trigger($class, $name, $event = null)
{
if (empty(self::$_events[$name])) {
return;
}
if ($event === null) {
$event = new self;
}
$event->handled = false;
$event->name = $name;
if (is_object($class)) {
$class = get_class($class);
} else {
$class = ltrim($class, '\\');
}
do {
if (!empty(self::$_events[$name][$class])) {
foreach (self::$_events[$name][$class] as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
if ($event instanceof Event && $event->handled) {
return;
}
}
}
} while (($class = get_parent_class($class)) !== false);
}
} }
...@@ -111,6 +111,17 @@ abstract class Generator extends Model ...@@ -111,6 +111,17 @@ abstract class Generator extends Model
} }
/** /**
* Returns the list of auto complete values.
* The array keys are the attribute names, and the array values are the corresponding auto complete values.
* Auto complete values can also be callable typed in order one want to make postponed data generation.
* @return array the list of auto complete values
*/
public function autoCompleteData()
{
return [];
}
/**
* Returns the message to be displayed when the newly generated code is saved successfully. * Returns the message to be displayed when the newly generated code is saved successfully.
* Child classes may override this method to customize the message. * Child classes may override this method to customize the message.
* @return string the message to be displayed when the newly generated code is saved successfully. * @return string the message to be displayed when the newly generated code is saved successfully.
......
...@@ -26,12 +26,14 @@ class GiiAsset extends AssetBundle ...@@ -26,12 +26,14 @@ class GiiAsset extends AssetBundle
*/ */
public $css = [ public $css = [
'main.css', 'main.css',
'typeahead.js-bootstrap.css',
]; ];
/** /**
* @inheritdoc * @inheritdoc
*/ */
public $js = [ public $js = [
'gii.js', 'gii.js',
'typeahead.js',
]; ];
/** /**
* @inheritdoc * @inheritdoc
......
...@@ -201,3 +201,11 @@ body { ...@@ -201,3 +201,11 @@ body {
.DifferencesInline .ChangeReplace del { .DifferencesInline .ChangeReplace del {
background: #e99; background: #e99;
} }
/* additional styles for typeahead.js-bootstrap.css */
.twitter-typeahead {
display: block !important;
}
.twitter-typeahead .tt-hint {
padding: 6px 12px !important;
}
/* always keep this link here when updating this file: https://github.com/jharding/typeahead.js-bootstrap.css */
.twitter-typeahead .tt-query,
.twitter-typeahead .tt-hint {
margin-bottom: 0;
}
.tt-dropdown-menu {
min-width: 160px;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.tt-suggestion {
display: block;
padding: 3px 20px;
}
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
}
.tt-suggestion.tt-is-under-cursor a {
color: #fff;
}
.tt-suggestion p {
margin: 0;
}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace yii\gii\components; namespace yii\gii\components;
use yii\gii\Generator; use yii\gii\Generator;
use yii\helpers\Json;
/** /**
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
...@@ -30,10 +31,18 @@ class ActiveField extends \yii\widgets\ActiveField ...@@ -30,10 +31,18 @@ class ActiveField extends \yii\widgets\ActiveField
if (isset($hints[$this->attribute])) { if (isset($hints[$this->attribute])) {
$this->hint($hints[$this->attribute]); $this->hint($hints[$this->attribute]);
} }
$autoCompleteData = $this->model->autoCompleteData();
if (isset($autoCompleteData[$this->attribute])) {
if (is_callable($autoCompleteData[$this->attribute])) {
$this->autoComplete(call_user_func($autoCompleteData[$this->attribute]));
} else {
$this->autoComplete($autoCompleteData[$this->attribute]);
}
}
} }
/** /**
* Makes filed remember its value between page reloads * Makes field remember its value between page reloads
* @return static the field object itself * @return static the field object itself
*/ */
public function sticky() public function sticky()
...@@ -41,4 +50,17 @@ class ActiveField extends \yii\widgets\ActiveField ...@@ -41,4 +50,17 @@ class ActiveField extends \yii\widgets\ActiveField
$this->options['class'] .= ' sticky'; $this->options['class'] .= ' sticky';
return $this; return $this;
} }
/**
* Makes field auto completable
* @param array $data auto complete data (array of callables or scalars)
* @return static the field object itself
*/
public function autoComplete($data)
{
static $counter = 0;
$this->inputOptions['class'] .= ' typeahead-' . (++$counter);
$this->form->getView()->registerJs("jQuery('.typeahead-{$counter}').typeahead({local: " . Json::encode($data) . "});");
return $this;
}
} }
...@@ -34,7 +34,7 @@ foreach ($generator->getTableSchema()->getColumnNames() as $attribute) { ...@@ -34,7 +34,7 @@ foreach ($generator->getTableSchema()->getColumnNames() as $attribute) {
if (++$count < 6) { if (++$count < 6) {
echo "\t\t<?= " . $generator->generateActiveSearchField($attribute) . " ?>\n\n"; echo "\t\t<?= " . $generator->generateActiveSearchField($attribute) . " ?>\n\n";
} else { } else {
echo "\t\t<?= // " . $generator->generateActiveSearchField($attribute) . " ?>\n\n"; echo "\t\t<?php // echo " . $generator->generateActiveSearchField($attribute) . " ?>\n\n";
} }
} }
?> ?>
......
...@@ -113,6 +113,18 @@ class Generator extends \yii\gii\Generator ...@@ -113,6 +113,18 @@ class Generator extends \yii\gii\Generator
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function autoCompleteData()
{
return [
'tableName' => function () {
return $this->getDbConnection()->getSchema()->getTableNames();
},
];
}
/**
* @inheritdoc
*/
public function requiredTemplates() public function requiredTemplates()
{ {
return ['model.php']; return ['model.php'];
......
...@@ -92,7 +92,7 @@ class MessageFormatter extends Component ...@@ -92,7 +92,7 @@ class MessageFormatter extends Component
return $this->fallbackFormat($pattern, $params, $language); return $this->fallbackFormat($pattern, $params, $language);
} }
if (version_compare(PHP_VERSION, '5.5.0', '<')) { if (version_compare(PHP_VERSION, '5.5.0', '<') || version_compare(INTL_ICU_VERSION, '4.8', '<')) {
$pattern = $this->replaceNamedArguments($pattern, $params); $pattern = $this->replaceNamedArguments($pattern, $params);
$params = array_values($params); $params = array_values($params);
} }
......
...@@ -135,18 +135,21 @@ abstract class BaseListView extends Widget ...@@ -135,18 +135,21 @@ abstract class BaseListView extends Widget
$totalCount = $this->dataProvider->getTotalCount(); $totalCount = $this->dataProvider->getTotalCount();
$begin = $pagination->getPage() * $pagination->pageSize + 1; $begin = $pagination->getPage() * $pagination->pageSize + 1;
$end = $begin + $count - 1; $end = $begin + $count - 1;
if ($begin > $end) {
$begin = $end;
}
$page = $pagination->getPage() + 1; $page = $pagination->getPage() + 1;
$pageCount = $pagination->pageCount; $pageCount = $pagination->pageCount;
if (($summaryContent = $this->summary) === null) { if (($summaryContent = $this->summary) === null) {
$summaryContent = '<div class="summary">' $summaryContent = '<div class="summary">'
. Yii::t('yii', 'Showing <b>{totalCount, plural, zero{0} other{{begin, number, integer}-{end, number, integer}}}</b> of <b>{totalCount, number, integer}</b> {totalCount, plural, one{item} other{items}}.') . Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.')
. '</div>'; . '</div>';
} }
} else { } else {
$begin = $page = $pageCount = 1; $begin = $page = $pageCount = 1;
$end = $totalCount = $count; $end = $totalCount = $count;
if (($summaryContent = $this->summary) === null) { if (($summaryContent = $this->summary) === null) {
$summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count}</b> {count, plural, one{item} other{items}}.') . '</div>'; $summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.') . '</div>';
} }
} }
return Yii::$app->getI18n()->format($summaryContent, [ return Yii::$app->getI18n()->format($summaryContent, [
......
...@@ -302,7 +302,6 @@ class ComponentTest extends TestCase ...@@ -302,7 +302,6 @@ class ComponentTest extends TestCase
$component->detachBehaviors(); $component->detachBehaviors();
$this->assertNull($component->getBehavior('a')); $this->assertNull($component->getBehavior('a'));
$this->assertNull($component->getBehavior('b')); $this->assertNull($component->getBehavior('b'));
} }
} }
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\base;
use yii\base\Component;
use yii\base\Event;
use yiiunit\TestCase;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class EventTest extends TestCase
{
public $counter;
public function setUp()
{
$this->counter = 0;
Event::off(ActiveRecord::className(), 'save');
Event::off(Post::className(), 'save');
Event::off(User::className(), 'save');
}
public function testOn()
{
Event::on(Post::className(), 'save', function ($event) {
$this->counter += 1;
});
Event::on(ActiveRecord::className(), 'save', function ($event) {
$this->counter += 3;
});
$this->assertEquals(0, $this->counter);
$post = new Post;
$post->save();
$this->assertEquals(4, $this->counter);
$user = new User;
$user->save();
$this->assertEquals(7, $this->counter);
}
public function testOff()
{
$handler = function ($event) {
$this->counter ++;
};
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
Event::on(Post::className(), 'save', $handler);
$this->assertTrue(Event::hasHandlers(Post::className(), 'save'));
Event::off(Post::className(), 'save', $handler);
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
}
public function testHasHandlers()
{
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
$this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save'));
Event::on(Post::className(), 'save', function ($event) {
$this->counter += 1;
});
$this->assertTrue(Event::hasHandlers(Post::className(), 'save'));
$this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save'));
$this->assertFalse(Event::hasHandlers(User::className(), 'save'));
Event::on(ActiveRecord::className(), 'save', function ($event) {
$this->counter += 1;
});
$this->assertTrue(Event::hasHandlers(User::className(), 'save'));
$this->assertTrue(Event::hasHandlers(ActiveRecord::className(), 'save'));
}
}
class ActiveRecord extends Component
{
public function save()
{
$this->trigger('save');
}
}
class Post extends ActiveRecord
{
}
class User extends ActiveRecord
{
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment