From 492f35621c213535c38156cfd74952cc58c51791 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Tue, 22 Oct 2013 17:22:08 +0300 Subject: [PATCH 001/203] Email components hierarchy created. SwiftMailer applied as email solution. --- .travis.yml | 2 +- framework/yii/email/BaseMailer.php | 125 ++++++++++++++++ framework/yii/email/BaseMessage.php | 125 ++++++++++++++++ framework/yii/email/Message.php | 34 +++++ framework/yii/email/swift/Mailer.php | 180 +++++++++++++++++++++++ framework/yii/email/swift/Message.php | 116 +++++++++++++++ framework/yii/email/swift/autoload.php | 9 ++ tests/unit/framework/email/BaseMailerTest.php | 97 ++++++++++++ tests/unit/framework/email/swift/MailerTest.php | 90 ++++++++++++ tests/unit/framework/email/swift/MessageTest.php | 40 +++++ 10 files changed, 817 insertions(+), 1 deletion(-) create mode 100644 framework/yii/email/BaseMailer.php create mode 100644 framework/yii/email/BaseMessage.php create mode 100644 framework/yii/email/Message.php create mode 100644 framework/yii/email/swift/Mailer.php create mode 100644 framework/yii/email/swift/Message.php create mode 100644 framework/yii/email/swift/autoload.php create mode 100644 tests/unit/framework/email/BaseMailerTest.php create mode 100644 tests/unit/framework/email/swift/MailerTest.php create mode 100644 tests/unit/framework/email/swift/MessageTest.php diff --git a/.travis.yml b/.travis.yml index a905b36..0218654 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ before_script: - tests/unit/data/travis/cubrid-setup.sh script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,swiftmailer after_script: - php vendor/bin/coveralls diff --git a/framework/yii/email/BaseMailer.php b/framework/yii/email/BaseMailer.php new file mode 100644 index 0000000..92fa3df --- /dev/null +++ b/framework/yii/email/BaseMailer.php @@ -0,0 +1,125 @@ + + * @since 2.0 + */ +abstract class BaseMailer extends Component +{ + /** + * @var \yii\base\View|array view instance or its array configuration. + */ + private $_view = []; + /** + * @var array configuration, which should be applied by default to any new created + * email message instance. + * For example: + * ~~~ + * array( + * 'encoding' => 'UTF-8', + * 'from' => 'noreply@mydomain.com', + * 'bcc' => 'email.test@mydomain.com', + * ) + * ~~~ + */ + private $_defaultMessageConfig = []; + + /** + * @param array|\yii\base\View $view view instance or its array configuration. + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setView($view) + { + if (!is_array($view) && !is_object($view)) { + throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or array, "' . gettype($view) . '" given.'); + } + $this->_view = $view; + } + + /** + * @return \yii\base\View view instance. + */ + public function getView() + { + if (!is_object($this->_view)) { + $this->_view = $this->createView($this->_view); + } + return $this->_view; + } + + /** + * @param array $defaultMessageConfig default message config + */ + public function setDefaultMessageConfig(array $defaultMessageConfig) + { + $this->_defaultMessageConfig = $defaultMessageConfig; + } + + /** + * @return array default message config + */ + public function getDefaultMessageConfig() + { + return $this->_defaultMessageConfig; + } + + /** + * Creates view instance from given configuration. + * @param array $config view configuration. + * @return \yii\base\View view instance. + */ + protected function createView(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = '\yii\base\View'; + } + $config['context'] = $this; + return Yii::createObject($config); + } + + /** + * Sends the given email message. + * @param object $message email message instance + * @return boolean whether the message has been sent. + */ + abstract public function send($message); + + /** + * Sends a couple of messages at once. + * Note: some particular mailers may benefit from sending messages as batch, + * saving resources, for example on open/close connection operations, + * they may override this method to create their specific implementation. + * @param array $messages list of email messages, which should be sent. + * @return integer number of successfull sends + */ + public function sendMultiple(array $messages) { + $successCount = 0; + foreach ($messages as $message) { + if ($this->send($message)) { + $successCount++; + } + } + return $successCount; + } +} \ No newline at end of file diff --git a/framework/yii/email/BaseMessage.php b/framework/yii/email/BaseMessage.php new file mode 100644 index 0000000..8127c9c --- /dev/null +++ b/framework/yii/email/BaseMessage.php @@ -0,0 +1,125 @@ + + * @since 2.0 + */ +abstract class BaseMessage extends Object +{ + /** + * @return \yii\email\BaseMailer + */ + public function getMailer() + { + return Yii::$app->getComponent('email'); + } + + /** + * Initializes the object. + * This method is invoked at the end of the constructor after the object is initialized with the + * given configuration. + */ + public function init() + { + Yii::configure($this, $this->getMailer()->getDefaultMessageConfig()); + } + + /** + * Sends this email message. + * @return boolean success. + */ + public function send() + { + return $this->getMailer()->send($this); + } + + /** + * Sets message sender. + * @param string|array $from sender email address, if array is given, + * its first element should be sender email address, second - sender name. + */ + abstract public function setFrom($from); + + /** + * Sets message receiver. + * @param string|array $to receiver email address, if array is given, + * its first element should be receiver email address, second - receiver name. + */ + abstract public function setTo($to); + + /** + * Sets message subject. + * @param string $subject message subject + */ + abstract public function setSubject($subject); + + /** + * Sets message plain text content. + * @param string $text message plain text content. + */ + abstract public function setText($text); + + /** + * Sets message HTML content. + * @param string $html message HTML content. + */ + abstract public function setHtml($html); + + /** + * Create file attachment for the email message. + * @param string $content attachment file content. + * @param string $fileName attachment file name. + * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. + */ + abstract public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); + + /** + * Attaches existing file to the email message. + * @param string $fileName full file name + * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. + * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. + * @throws \yii\base\InvalidParamException if given file does not exist. + */ + public function attachFile($fileName, $contentType = null, $attachFileName = null) + { + if (!file_exists($fileName)) { + throw new InvalidParamException('Unable to attach file "' . $fileName . '": file does not exists!'); + } + if (empty($contentType)) { + $contentType = FileHelper::getMimeType($fileName); + } + if (empty($attachFileName)) { + $attachFileName = basename($fileName); + } + $content = file_get_contents($fileName); + $this->createAttachment($content, $attachFileName, $contentType); + } +} \ No newline at end of file diff --git a/framework/yii/email/Message.php b/framework/yii/email/Message.php new file mode 100644 index 0000000..c7ec534 --- /dev/null +++ b/framework/yii/email/Message.php @@ -0,0 +1,34 @@ +from = 'sender@domain.com'; + * $email->to = 'receiver@domain.com'; + * $email->subject = 'Message Subject'; + * $email->text = 'Message Content'; + * $email->send(); + * ~~~ + * + * This particular class uses 'SwiftMailer' library to perform the message sending. + * Note: you can replace usage of this class by your own one, using [[Yii::$classMap]]: + * ~~~ + * Yii::$classMap['yii\email\Message'] = '/path/to/my/email/Message.php' + * ~~~ + * + * @author Paul Klimov + * @since 2.0 + */ +class Message extends SwiftMessage {} \ No newline at end of file diff --git a/framework/yii/email/swift/Mailer.php b/framework/yii/email/swift/Mailer.php new file mode 100644 index 0000000..1f589a2 --- /dev/null +++ b/framework/yii/email/swift/Mailer.php @@ -0,0 +1,180 @@ + array( + * ... + * 'email' => array( + * 'class' => 'yii\email\swift\Mailer', + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'host' => 'localhost', + * 'username' => 'username', + * 'password' => 'password', + * 'port' => '587', + * 'encryption' => 'tls', + * ], + * ), + * ... + * ), + * ~~~ + * + * @see http://swiftmailer.org + * + * @author Paul Klimov + * @since 2.0 + */ +class Mailer extends BaseMailer +{ + /** + * @var string|callback SwiftMailer autoloader callback or path to autoloader file. + * If the SwiftMailer classes autoloading is already managed in some other place, + * for example via Composer, you should leave this field blank. + */ + public $autoload; + /** + * @var \Swift_Mailer Swift mailer instance. + */ + private $_swiftMailer; + /** + * @var \Swift_Transport|array Swift transport instance or its array configuration. + */ + private $_transport = []; + + /** + * @return array|\Swift_Mailer Swift mailer instance or array configuration. + */ + public function getSwiftMailer() + { + if (!is_object($this->_swiftMailer)) { + $this->_swiftMailer = $this->createSwiftMailer(); + } + return $this->_swiftMailer; + } + + /** + * @param array|\Swift_Transport $transport + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setTransport($transport) + { + if (!is_array($transport) && !is_object($transport)) { + throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); + } + $this->_transport = $transport; + } + + /** + * @return array|\Swift_Transport + */ + public function getTransport() + { + if (!is_object($this->_transport)) { + $this->_transport = $this->createTransport($this->_transport); + } + return $this->_transport; + } + + /** + * @inheritdoc + */ + public function init() + { + $this->setupSwiftMailerAutoload(); + } + + /** + * Sets up the SwiftMailer autoloader, if it is specified. + */ + protected function setupSwiftMailerAutoload() + { + if (!class_exists('Swift', false)) { + if (empty($this->autoload)) { + $this->autoload = __DIR__ . '/autoload.php'; + } + if (is_string($this->autoload)) { + if (file_exists($this->autoload)) { + require_once($this->autoload); + } elseif (function_exists($this->autoload)) { + spl_autoload_register($this->autoload); + } else { + throw new InvalidConfigException('"' . get_class($this) . '::autoload" value "' . $this->autoload . '" is invalid: no such function or file exists.'); + } + } else { + spl_autoload_register($this->autoload); + } + } + } + + /** + * @inheritdoc + */ + public function send($message) + { + return ($this->getSwiftMailer()->send($message->getSwiftMessage()) > 0); + } + + /** + * Creates Swift mailer instance. + * @return \Swift_Mailer mailer instance. + */ + protected function createSwiftMailer() + { + return \Swift_Mailer::newInstance($this->getTransport()); + } + + /** + * Creates email transport instance by its array configuration. + * @param array $config transport configuration. + * @throws \yii\base\InvalidConfigException on invalid transport configuration. + * @return \Swift_Transport transport instance. + */ + protected function createTransport(array $config) + { + if (array_key_exists('class', $config)) { + $className = $config['class']; + unset($config['class']); + } else { + $className = 'Swift_MailTransport'; + } + $transport = call_user_func([$className, 'newInstance']); + if (!empty($config)) { + foreach ($config as $name => $value) { + if (property_exists($transport, $name)) { + $transport->$name = $value; + } else { + $setter = 'set' . $name; + if (method_exists($transport, $setter)) { + $transport->$setter($value); + } else { + throw new InvalidConfigException('Setting unknown property: ' . get_class($transport) . '::' . $name); + } + } + } + } + return $transport; + } + + /** + * Creates the Swift email message instance. + * @return \Swift_Message email message instance. + */ + public function createSwiftMessage() + { + return new \Swift_Message(); + } +} \ No newline at end of file diff --git a/framework/yii/email/swift/Message.php b/framework/yii/email/swift/Message.php new file mode 100644 index 0000000..49a4b75 --- /dev/null +++ b/framework/yii/email/swift/Message.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +class Message extends BaseMessage +{ + /** + * @var \Swift_Message Swift message instance. + */ + private $_swiftMessage; + + /** + * @return \Swift_Message Swift message instance. + */ + public function getSwiftMessage() + { + if (!is_object($this->_swiftMessage)) { + $this->_swiftMessage = $this->getMailer()->createSwiftMessage(); + } + return $this->_swiftMessage; + } + + /** + * Sets message sender. + * @param string|array $from sender email address, if array is given, + * its first element should be sender email address, second - sender name. + */ + public function setFrom($from) + { + if (is_array($from)) { + list ($address, $name) = $from; + } else { + $address = $from; + $name = null; + } + $this->getSwiftMessage()->setFrom($address, $name); + $this->getSwiftMessage()->setReplyTo($address, $name); + } + + /** + * Sets message receiver. + * @param string|array $to receiver email address, if array is given, + * its first element should be receiver email address, second - receiver name. + */ + public function setTo($to) + { + if (is_array($to)) { + list ($address, $name) = $to; + } else { + $address = $to; + $name = null; + } + $this->getSwiftMessage()->setTo($address, $name); + } + + /** + * Sets message subject. + * @param string $subject message subject + */ + public function setSubject($subject) + { + $this->getSwiftMessage()->setSubject($subject); + } + + /** + * Sets message plain text content. + * @param string $text message plain text content. + */ + public function setText($text) + { + $this->getSwiftMessage()->setBody($text, 'text/plain'); + } + + /** + * Sets message HTML content. + * @param string $html message HTML content. + */ + public function setHtml($html) + { + $this->getSwiftMessage()->setBody($html, 'text/html'); + } + + /** + * Create file attachment for the email message. + * @param string $content - attachment file content. + * @param string $fileName - attachment file name. + * @param string $contentType - MIME type of the attachment file, by default 'application/octet-stream' will be used. + */ + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') + { + if (empty($contentType)) { + $contentType = 'application/octet-stream'; + } + $attachment = \Swift_Attachment::newInstance($content, $fileName, $contentType); + $this->getSwiftMessage()->attach($attachment); + } +} \ No newline at end of file diff --git a/framework/yii/email/swift/autoload.php b/framework/yii/email/swift/autoload.php new file mode 100644 index 0000000..494150f --- /dev/null +++ b/framework/yii/email/swift/autoload.php @@ -0,0 +1,9 @@ +mockApplication(); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testSetupView() + { + $mailer = new Mailer(); + + $view = new View(); + $mailer->setView($view); + $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); + } + + public function testGetDefaultView() + { + $mailer = new Mailer(); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to get default view!'); + } + + public function testDefaultMessageConfig() + { + $defaultMessageConfig = array( + 'id' => 'test-id', + 'encoding' => 'test-encoding', + ); + Yii::$app->getComponent('email')->setDefaultMessageConfig($defaultMessageConfig); + + $message = new Message(); + + foreach ($defaultMessageConfig as $name => $value) { + $this->assertEquals($value, $message->$name); + } + } +} + +/** + * Test Mailer class + */ +class Mailer extends BaseMailer +{ + public $sentMessages = array(); + + public function send($message) + { + $this->sentMessages[] = $message; + } +} + +/** + * Test Message class + */ +class Message extends BaseMessage +{ + public $id; + public $encoding; + + public function setFrom($from) {} + + public function setTo($to) {} + + public function setSubject($subject) {} + + public function setText($text) {} + + public function setHtml($html) {} + + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') {} +} \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MailerTest.php b/tests/unit/framework/email/swift/MailerTest.php new file mode 100644 index 0000000..f984139 --- /dev/null +++ b/tests/unit/framework/email/swift/MailerTest.php @@ -0,0 +1,90 @@ +mockApplication(array( + 'vendorPath' => Yii::getAlias('@yiiunit/vendor') + )); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testSetupTransport() + { + $mailer = new Mailer(); + + $transport = \Swift_MailTransport::newInstance(); + $mailer->setTransport($transport); + $this->assertEquals($transport, $mailer->getTransport(), 'Unable to setup transport!'); + } + + /** + * @depends testSetupTransport + */ + public function testConfigureTransport() + { + $mailer = new Mailer(); + + $transportConfig = [ + 'class' => 'Swift_SmtpTransport', + 'host' => 'localhost', + ]; + $mailer->setTransport($transportConfig); + $transport = $mailer->getTransport(); + $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); + $this->assertEquals($transportConfig['class'], get_class($transport), 'Invalid transport class!'); + $this->assertEquals($transportConfig['host'], $transport->getHost(), 'Invalid transport host!'); + } + + public function testGetSwiftMailer() + { + $mailer = new Mailer(); + $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); + } + + public function testCreateSwiftMessage() + { + $mailer = new Mailer(); + $message = $mailer->createSwiftMessage(); + $this->assertTrue(is_object($message), 'Unable to create Swift message instance!'); + } + + /** + * @depends testGetSwiftMailer + * @depends testCreateSwiftMessage + */ + public function testSend() + { + $emailAddress = 'someuser@somedomain.com'; + $message = new Message(); + $message->setTo($emailAddress); + $message->setFrom($emailAddress); + $message->setSubject('Yii Swift Test'); + $message->setText('Yii Swift Test body'); + $message->send(); + $this->assertTrue(true); + } +} \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MessageTest.php b/tests/unit/framework/email/swift/MessageTest.php new file mode 100644 index 0000000..50742c9 --- /dev/null +++ b/tests/unit/framework/email/swift/MessageTest.php @@ -0,0 +1,40 @@ +mockApplication(array( + 'vendorPath' => Yii::getAlias('@yiiunit/vendor') + )); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testGetSwiftMessage() + { + $message = new Message(); + $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); + } +} \ No newline at end of file From 88126bc4d3147a4bef44c17b55935616b57af041 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Wed, 23 Oct 2013 12:19:01 +0300 Subject: [PATCH 002/203] Unit tests for \yii\email\swift have been improved. Methods 'addText' and 'addHtml' added to \yii\email\BaseMessage. --- framework/yii/email/BaseMessage.php | 12 +++++ framework/yii/email/swift/Message.php | 18 +++++++ tests/unit/framework/email/BaseMailerTest.php | 4 ++ tests/unit/framework/email/swift/MailerTest.php | 16 ------- tests/unit/framework/email/swift/MessageTest.php | 60 ++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 16 deletions(-) diff --git a/framework/yii/email/BaseMessage.php b/framework/yii/email/BaseMessage.php index 8127c9c..506563b 100644 --- a/framework/yii/email/BaseMessage.php +++ b/framework/yii/email/BaseMessage.php @@ -94,6 +94,18 @@ abstract class BaseMessage extends Object abstract public function setHtml($html); /** + * Add message plain text content part. + * @param string $text message plain text content. + */ + abstract public function addText($text); + + /** + * Add message HTML content part. + * @param string $html message HTML content. + */ + abstract public function addHtml($html); + + /** * Create file attachment for the email message. * @param string $content attachment file content. * @param string $fileName attachment file name. diff --git a/framework/yii/email/swift/Message.php b/framework/yii/email/swift/Message.php index 49a4b75..5b8c575 100644 --- a/framework/yii/email/swift/Message.php +++ b/framework/yii/email/swift/Message.php @@ -100,6 +100,24 @@ class Message extends BaseMessage } /** + * Add message plain text content part. + * @param string $text message plain text content. + */ + public function addText($text) + { + $this->getSwiftMessage()->addPart($text, 'text/plain'); + } + + /** + * Add message HTML content part. + * @param string $html message HTML content. + */ + public function addHtml($html) + { + $this->getSwiftMessage()->addPart($html, 'text/html'); + } + + /** * Create file attachment for the email message. * @param string $content - attachment file content. * @param string $fileName - attachment file name. diff --git a/tests/unit/framework/email/BaseMailerTest.php b/tests/unit/framework/email/BaseMailerTest.php index 8418d2b..178028b 100644 --- a/tests/unit/framework/email/BaseMailerTest.php +++ b/tests/unit/framework/email/BaseMailerTest.php @@ -93,5 +93,9 @@ class Message extends BaseMessage public function setHtml($html) {} + public function addText($text) {} + + public function addHtml($html) {} + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') {} } \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MailerTest.php b/tests/unit/framework/email/swift/MailerTest.php index f984139..f9cee72 100644 --- a/tests/unit/framework/email/swift/MailerTest.php +++ b/tests/unit/framework/email/swift/MailerTest.php @@ -71,20 +71,4 @@ class MailerTest extends TestCase $message = $mailer->createSwiftMessage(); $this->assertTrue(is_object($message), 'Unable to create Swift message instance!'); } - - /** - * @depends testGetSwiftMailer - * @depends testCreateSwiftMessage - */ - public function testSend() - { - $emailAddress = 'someuser@somedomain.com'; - $message = new Message(); - $message->setTo($emailAddress); - $message->setFrom($emailAddress); - $message->setSubject('Yii Swift Test'); - $message->setText('Yii Swift Test body'); - $message->send(); - $this->assertTrue(true); - } } \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MessageTest.php b/tests/unit/framework/email/swift/MessageTest.php index 50742c9..112f4fc 100644 --- a/tests/unit/framework/email/swift/MessageTest.php +++ b/tests/unit/framework/email/swift/MessageTest.php @@ -13,6 +13,11 @@ use yiiunit\TestCase; */ class MessageTest extends TestCase { + /** + * @var string test email address, which will be used as receiver for the messages. + */ + protected $testEmailReceiver = 'someuser@somedomain.com'; + public function setUp() { $this->mockApplication(array( @@ -37,4 +42,59 @@ class MessageTest extends TestCase $message = new Message(); $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); } + + /** + * @depends testGetSwiftMessage + */ + public function testSend() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Test'); + $message->setText('Yii Swift Test body'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testAttachFile() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Attach File Test'); + $message->setText('Yii Swift Attach File Test body'); + $message->attachFile(__FILE__); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testCreateAttachment() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Create Attachment Test'); + $message->setText('Yii Swift Create Attachment Test body'); + $message->createAttachment('Test attachment content', 'test.txt'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testSendAlternativeBody() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Alternative Body Test'); + $message->addHtml('Yii Swift test HTML body'); + $message->addText('Yii Swift test plain text body'); + $this->assertTrue($message->send()); + } } \ No newline at end of file From e5e0de8c8f25ef4f5cfb515ed8fbc64743ba8569 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Wed, 23 Oct 2013 16:33:25 +0300 Subject: [PATCH 003/203] '\yii\email\ViewResolver' added. Email message render functionality added. --- framework/yii/email/BaseMailer.php | 55 +++++++++++++++- framework/yii/email/BaseMessage.php | 14 ++++ framework/yii/email/ViewResolver.php | 57 ++++++++++++++++ tests/unit/framework/email/BaseMailerTest.php | 86 +++++++++++++++++++++++++ tests/unit/framework/email/ViewResolverTest.php | 57 ++++++++++++++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 framework/yii/email/ViewResolver.php create mode 100644 tests/unit/framework/email/ViewResolverTest.php diff --git a/framework/yii/email/BaseMailer.php b/framework/yii/email/BaseMailer.php index 92fa3df..b3b40b3 100644 --- a/framework/yii/email/BaseMailer.php +++ b/framework/yii/email/BaseMailer.php @@ -18,7 +18,8 @@ use Yii; * * @see BaseMessage * - * @property array $view view instance or its array configuration. + * @property \yii\base\View|array $view view instance or its array configuration. + * @property \yii\email\ViewResolver|array $viewResolver view resolver instance or its array configuration. * @property array $defaultMessageConfig configuration, which should be applied by default to any * new created email message instance. * @@ -32,6 +33,10 @@ abstract class BaseMailer extends Component */ private $_view = []; /** + * @var \yii\email\ViewResolver|array view resolver instance or its array configuration. + */ + private $_viewResolver = []; + /** * @var array configuration, which should be applied by default to any new created * email message instance. * For example: @@ -69,6 +74,29 @@ abstract class BaseMailer extends Component } /** + * @param array|\yii\email\ViewResolver $viewResolver view resolver instance or its array configuration. + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setViewResolver($viewResolver) + { + if (!is_array($viewResolver) && !is_object($viewResolver)) { + throw new InvalidConfigException('"' . get_class($this) . '::viewResolver" should be either object or array, "' . gettype($viewResolver) . '" given.'); + } + $this->_viewResolver = $viewResolver; + } + + /** + * @return \yii\email\ViewResolver view resolver. + */ + public function getViewResolver() + { + if (!is_object($this->_viewResolver)) { + $this->_viewResolver = $this->createViewResolver($this->_viewResolver); + } + return $this->_viewResolver; + } + + /** * @param array $defaultMessageConfig default message config */ public function setDefaultMessageConfig(array $defaultMessageConfig) @@ -94,7 +122,19 @@ abstract class BaseMailer extends Component if (!array_key_exists('class', $config)) { $config['class'] = '\yii\base\View'; } - $config['context'] = $this; + return Yii::createObject($config); + } + + /** + * Creates view resolver instance from given configuration. + * @param array $config view resolver configuration. + * @return \yii\email\ViewResolver view resolver instance. + */ + protected function createViewResolver(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = '\yii\email\ViewResolver'; + } return Yii::createObject($config); } @@ -122,4 +162,15 @@ abstract class BaseMailer extends Component } return $successCount; } + + /** + * Renders a view. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string string the rendering result + */ + public function render($view, $params = []) + { + return $this->getView()->renderFile($this->getViewResolver()->findViewFile($view), $params, $this); + } } \ No newline at end of file diff --git a/framework/yii/email/BaseMessage.php b/framework/yii/email/BaseMessage.php index 506563b..00d27e0 100644 --- a/framework/yii/email/BaseMessage.php +++ b/framework/yii/email/BaseMessage.php @@ -134,4 +134,18 @@ abstract class BaseMessage extends Object $content = file_get_contents($fileName); $this->createAttachment($content, $attachFileName, $contentType); } + + /** + * Renders a view. + * The view to be rendered can be specified in one of the following formats: + * - path alias (e.g. "@app/emails/contact/body"); + * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string string the rendering result + */ + public function render($view, $params = []) + { + return $this->getMailer()->render($view, $params); + } } \ No newline at end of file diff --git a/framework/yii/email/ViewResolver.php b/framework/yii/email/ViewResolver.php new file mode 100644 index 0000000..4f522be --- /dev/null +++ b/framework/yii/email/ViewResolver.php @@ -0,0 +1,57 @@ + + * @since 2.0 + */ +class ViewResolver extends Component +{ + /** + * @var string directory containing view files for this email messages. + */ + public $viewPath = '@app/emails'; + + /** + * Finds the view file based on the given view name. + * The view to be rendered can be specified in one of the following formats: + * - path alias (e.g. "@app/emails/contact/body"); + * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. + * @param string $view the view name or the path alias of the view file. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view) + { + if (strncmp($view, '@', 1) === 0) { + // e.g. "@app/views/main" + $file = Yii::getAlias($view); + } else { + $file = $this->resolveView($view); + } + return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; + } + + /** + * Composes file name for the view name, appending view name to [[viewPath]]. + * Child classes may override this method to provide more sophisticated + * search of the view files or even composition of the view files "on the fly". + * @param string $view the view name. + * @return string the view file path. + */ + protected function resolveView($view) + { + return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; + } +} \ No newline at end of file diff --git a/tests/unit/framework/email/BaseMailerTest.php b/tests/unit/framework/email/BaseMailerTest.php index 178028b..bd97aeb 100644 --- a/tests/unit/framework/email/BaseMailerTest.php +++ b/tests/unit/framework/email/BaseMailerTest.php @@ -6,6 +6,8 @@ use Yii; use yii\base\View; use yii\email\BaseMailer; use yii\email\BaseMessage; +use yii\email\ViewResolver; +use yii\helpers\FileHelper; use yiiunit\TestCase; /** @@ -17,6 +19,26 @@ class BaseMailerTest extends TestCase { $this->mockApplication(); Yii::$app->setComponent('email', $this->createTestEmailComponent()); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown() + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + /** + * @return string test file path. + */ + protected function getTestFilePath() + { + return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); } /** @@ -37,8 +59,22 @@ class BaseMailerTest extends TestCase $view = new View(); $mailer->setView($view); $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); + + $viewConfig = [ + 'params' => [ + 'param1' => 'value1', + 'param2' => 'value2', + ] + ]; + $mailer->setView($viewConfig); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to setup view via config!'); + $this->assertEquals($viewConfig['params'], $view->params, 'Unable to configure view via config array!'); } + /** + * @depends testSetupView + */ public function testGetDefaultView() { $mailer = new Mailer(); @@ -46,6 +82,33 @@ class BaseMailerTest extends TestCase $this->assertTrue(is_object($view), 'Unable to get default view!'); } + public function testSetupViewResolver() + { + $mailer = new Mailer(); + + $viewResolver = new ViewResolver(); + $mailer->setViewResolver($viewResolver); + $this->assertEquals($viewResolver, $mailer->getViewResolver(), 'Unable to setup view resolver!'); + + $viewResolverConfig = [ + 'viewPath' => '/test/view/path', + ]; + $mailer->setViewResolver($viewResolverConfig); + $viewResolver = $mailer->getViewResolver(); + $this->assertTrue(is_object($viewResolver), 'Unable to setup view resolver via config!'); + $this->assertEquals($viewResolverConfig['viewPath'], $viewResolver->viewPath, 'Unable to configure view resolver via config array!'); + } + + /** + * @depends testSetupViewResolver + */ + public function testGetDefaultViewResolver() + { + $mailer = new Mailer(); + $viewResolver = $mailer->getViewResolver(); + $this->assertTrue(is_object($viewResolver), 'Unable to get default view resolver!'); + } + public function testDefaultMessageConfig() { $defaultMessageConfig = array( @@ -60,6 +123,29 @@ class BaseMailerTest extends TestCase $this->assertEquals($value, $message->$name); } } + + /** + * @depends testGetDefaultView + * @depends testGetDefaultViewResolver + */ + public function testRender() + { + $mailer = new Mailer(); + + $filePath = $this->getTestFilePath(); + $mailer->getViewResolver()->viewPath = $filePath; + + $viewName = 'test_view'; + $fileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; + $fileContent = ''; + file_put_contents($fileName, $fileContent); + + $params = [ + 'testParam' => 'test output' + ]; + $renderResult = $mailer->render($viewName, $params); + $this->assertEquals($params['testParam'], $renderResult); + } } /** diff --git a/tests/unit/framework/email/ViewResolverTest.php b/tests/unit/framework/email/ViewResolverTest.php new file mode 100644 index 0000000..9185f72 --- /dev/null +++ b/tests/unit/framework/email/ViewResolverTest.php @@ -0,0 +1,57 @@ +testViewPath); + return [ + [ + $alias . '/test', + $aliasPath . '/test.php', + ], + [ + $alias . '/test.tpl', + $aliasPath . '/test.tpl', + ], + [ + 'contact/html', + $viewPath . '/contact/html.php', + ], + [ + 'contact/html.tpl', + $viewPath . '/contact/html.tpl', + ], + ]; + } + + /** + * @dataProvider dataProviderFindViewFile + * + * @param string $view + * @param string $expectedFileName + */ + public function testFindViewFile($view, $expectedFileName) + { + $viewResolver = new ViewResolver(); + $viewResolver->viewPath = $this->testViewPath; + $fileName = $viewResolver->findViewFile($view); + $this->assertEquals($expectedFileName, $fileName); + } +} \ No newline at end of file From b611c4241f3a6a5318411c973ecf31d4204299c1 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Wed, 23 Oct 2013 17:12:33 +0300 Subject: [PATCH 004/203] Email message interface extracted. --- framework/yii/email/BaseMessage.php | 79 ++---------------- framework/yii/email/Message.php | 11 +++ framework/yii/email/MessageInterface.php | 116 ++++++++++++++++++++++++++ framework/yii/email/swift/Message.php | 62 ++++++-------- tests/unit/framework/email/BaseMailerTest.php | 4 + 5 files changed, 166 insertions(+), 106 deletions(-) create mode 100644 framework/yii/email/MessageInterface.php diff --git a/framework/yii/email/BaseMessage.php b/framework/yii/email/BaseMessage.php index 00d27e0..aca71ee 100644 --- a/framework/yii/email/BaseMessage.php +++ b/framework/yii/email/BaseMessage.php @@ -21,10 +21,10 @@ use Yii; * @see BaseMailer * * @property \yii\email\BaseMailer $mailer mailer component instance. This property is read-only. - * @property string|array $from sender email address, if array is given, its first element should - * be sender email address, second - sender name. - * @property string|array $to receiver email address, if array is given, its first element should - * be receiver email address, second - receiver name. + * @property string|array $from sender email address. + * @property string|array $to receiver email address. + * @property string|array $cc copy receiver email address. + * @property string|array $bcc hidden copy receiver email address. * @property string $subject message subject. * @property string $text message plain text content. * @property string $html message HTML content. @@ -32,7 +32,7 @@ use Yii; * @author Paul Klimov * @since 2.0 */ -abstract class BaseMessage extends Object +abstract class BaseMessage extends Object implements MessageInterface { /** * @return \yii\email\BaseMailer @@ -53,8 +53,7 @@ abstract class BaseMessage extends Object } /** - * Sends this email message. - * @return boolean success. + * @inheritdoc */ public function send() { @@ -62,63 +61,7 @@ abstract class BaseMessage extends Object } /** - * Sets message sender. - * @param string|array $from sender email address, if array is given, - * its first element should be sender email address, second - sender name. - */ - abstract public function setFrom($from); - - /** - * Sets message receiver. - * @param string|array $to receiver email address, if array is given, - * its first element should be receiver email address, second - receiver name. - */ - abstract public function setTo($to); - - /** - * Sets message subject. - * @param string $subject message subject - */ - abstract public function setSubject($subject); - - /** - * Sets message plain text content. - * @param string $text message plain text content. - */ - abstract public function setText($text); - - /** - * Sets message HTML content. - * @param string $html message HTML content. - */ - abstract public function setHtml($html); - - /** - * Add message plain text content part. - * @param string $text message plain text content. - */ - abstract public function addText($text); - - /** - * Add message HTML content part. - * @param string $html message HTML content. - */ - abstract public function addHtml($html); - - /** - * Create file attachment for the email message. - * @param string $content attachment file content. - * @param string $fileName attachment file name. - * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. - */ - abstract public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); - - /** - * Attaches existing file to the email message. - * @param string $fileName full file name - * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. - * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. - * @throws \yii\base\InvalidParamException if given file does not exist. + * @inheritdoc */ public function attachFile($fileName, $contentType = null, $attachFileName = null) { @@ -136,13 +79,7 @@ abstract class BaseMessage extends Object } /** - * Renders a view. - * The view to be rendered can be specified in one of the following formats: - * - path alias (e.g. "@app/emails/contact/body"); - * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. - * @param string $view the view name or the path alias of the view file. - * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. - * @return string string the rendering result + * @inheritdoc */ public function render($view, $params = []) { diff --git a/framework/yii/email/Message.php b/framework/yii/email/Message.php index c7ec534..e8a14ab 100644 --- a/framework/yii/email/Message.php +++ b/framework/yii/email/Message.php @@ -22,6 +22,17 @@ use yii\email\swift\Message as SwiftMessage; * $email->send(); * ~~~ * + * You can use message object to render view, which can be used to compose the message content: + * ~~~ + * $email = new Message(); + * $email->from = $contactForm->email; + * $email->to = 'admin@domain.com'; + * $email->subject = $email->render('contact/subject', ['form' => $contactForm]); + * $email->addHtml($email->render('contact/html', ['form' => $contactForm])); + * $email->addText($email->render('contact/text', ['form' => $contactForm])); + * $email->send(); + * ~~~ + * * This particular class uses 'SwiftMailer' library to perform the message sending. * Note: you can replace usage of this class by your own one, using [[Yii::$classMap]]: * ~~~ diff --git a/framework/yii/email/MessageInterface.php b/framework/yii/email/MessageInterface.php new file mode 100644 index 0000000..c94d2ab --- /dev/null +++ b/framework/yii/email/MessageInterface.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +interface MessageInterface +{ + /** + * Sets message sender. + * @param string|array $from sender email address. + * You may pass an array of addresses if this message is from multiple people. + * You may also specify sender name in addition to email address using format: + * [email => name]. + */ + public function setFrom($from); + + /** + * Sets message receiver. + * @param string|array $to receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setTo($to); + + /** + * Set the Cc (additional copy receiver) addresses of this message. + * @param string|array $cc copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setCc($cc); + + /** + * Set the Bcc (hidden copy receiver) addresses of this message. + * @param string|array $bcc hidden copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setBcc($bcc); + + /** + * Sets message subject. + * @param string $subject message subject + */ + public function setSubject($subject); + + /** + * Sets message plain text content. + * @param string $text message plain text content. + */ + public function setText($text); + + /** + * Sets message HTML content. + * @param string $html message HTML content. + */ + public function setHtml($html); + + /** + * Add message plain text content part. + * @param string $text message plain text content. + */ + public function addText($text); + + /** + * Add message HTML content part. + * @param string $html message HTML content. + */ + public function addHtml($html); + + /** + * Create file attachment for the email message. + * @param string $content attachment file content. + * @param string $fileName attachment file name. + * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. + */ + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); + + /** + * Attaches existing file to the email message. + * @param string $fileName full file name + * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. + * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. + */ + public function attachFile($fileName, $contentType = null, $attachFileName = null); + + /** + * Sends this email message. + * @return boolean success. + */ + public function send(); + + /** + * Renders a view. + * The view to be rendered can be specified in one of the following formats: + * - path alias (e.g. "@app/emails/contact/body"); + * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string string the rendering result + */ + public function render($view, $params = []); +} \ No newline at end of file diff --git a/framework/yii/email/swift/Message.php b/framework/yii/email/swift/Message.php index 5b8c575..04ef7b5 100644 --- a/framework/yii/email/swift/Message.php +++ b/framework/yii/email/swift/Message.php @@ -40,41 +40,40 @@ class Message extends BaseMessage } /** - * Sets message sender. - * @param string|array $from sender email address, if array is given, - * its first element should be sender email address, second - sender name. + * @inheritdoc */ public function setFrom($from) { - if (is_array($from)) { - list ($address, $name) = $from; - } else { - $address = $from; - $name = null; - } - $this->getSwiftMessage()->setFrom($address, $name); - $this->getSwiftMessage()->setReplyTo($address, $name); + $this->getSwiftMessage()->setFrom($from); + $this->getSwiftMessage()->setReplyTo($from); } /** - * Sets message receiver. - * @param string|array $to receiver email address, if array is given, - * its first element should be receiver email address, second - receiver name. + * @inheritdoc */ public function setTo($to) { - if (is_array($to)) { - list ($address, $name) = $to; - } else { - $address = $to; - $name = null; - } - $this->getSwiftMessage()->setTo($address, $name); + $this->getSwiftMessage()->setTo($to); + } + + /** + * @inheritdoc + */ + public function setCc($cc) + { + $this->getSwiftMessage()->setCc($cc); + } + + /** + * @inheritdoc + */ + public function setBcc($bcc) + { + $this->getSwiftMessage()->setBcc($bcc); } /** - * Sets message subject. - * @param string $subject message subject + * @inheritdoc */ public function setSubject($subject) { @@ -82,8 +81,7 @@ class Message extends BaseMessage } /** - * Sets message plain text content. - * @param string $text message plain text content. + * @inheritdoc */ public function setText($text) { @@ -91,8 +89,7 @@ class Message extends BaseMessage } /** - * Sets message HTML content. - * @param string $html message HTML content. + * @inheritdoc */ public function setHtml($html) { @@ -100,8 +97,7 @@ class Message extends BaseMessage } /** - * Add message plain text content part. - * @param string $text message plain text content. + * @inheritdoc */ public function addText($text) { @@ -109,8 +105,7 @@ class Message extends BaseMessage } /** - * Add message HTML content part. - * @param string $html message HTML content. + * @inheritdoc */ public function addHtml($html) { @@ -118,10 +113,7 @@ class Message extends BaseMessage } /** - * Create file attachment for the email message. - * @param string $content - attachment file content. - * @param string $fileName - attachment file name. - * @param string $contentType - MIME type of the attachment file, by default 'application/octet-stream' will be used. + * @inheritdoc */ public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') { diff --git a/tests/unit/framework/email/BaseMailerTest.php b/tests/unit/framework/email/BaseMailerTest.php index bd97aeb..b75d628 100644 --- a/tests/unit/framework/email/BaseMailerTest.php +++ b/tests/unit/framework/email/BaseMailerTest.php @@ -173,6 +173,10 @@ class Message extends BaseMessage public function setTo($to) {} + public function setCc($cc) {} + + public function setBcc($bcc) {} + public function setSubject($subject) {} public function setText($text) {} From 2a326c7d7b6ef44889613fbdc09cb1e5fe64f6d1 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 25 Oct 2013 13:15:36 +0300 Subject: [PATCH 005/203] 'email' renamed to 'mail'. 'SwiftMailer' recomposed into extension. --- extensions/swiftmailer/LICENSE.md | 32 ++++ extensions/swiftmailer/README.md | 58 +++++++ extensions/swiftmailer/composer.json | 28 +++ extensions/swiftmailer/yii/swiftmailer/Mailer.php | 143 +++++++++++++++ extensions/swiftmailer/yii/swiftmailer/Message.php | 126 ++++++++++++++ framework/yii/email/BaseMailer.php | 176 ------------------- framework/yii/email/BaseMessage.php | 88 ---------- framework/yii/email/Message.php | 45 ----- framework/yii/email/MessageInterface.php | 116 ------------- framework/yii/email/ViewResolver.php | 57 ------ framework/yii/email/swift/Mailer.php | 180 ------------------- framework/yii/email/swift/Message.php | 126 -------------- framework/yii/email/swift/autoload.php | 9 - framework/yii/mail/BaseMailer.php | 176 +++++++++++++++++++ framework/yii/mail/BaseMessage.php | 88 ++++++++++ framework/yii/mail/Message.php | 45 +++++ framework/yii/mail/MessageInterface.php | 116 +++++++++++++ framework/yii/mail/ViewResolver.php | 57 ++++++ tests/unit/extensions/swiftmailer/MailerTest.php | 74 ++++++++ tests/unit/extensions/swiftmailer/MessageTest.php | 100 +++++++++++ tests/unit/framework/email/BaseMailerTest.php | 191 --------------------- tests/unit/framework/email/ViewResolverTest.php | 57 ------ tests/unit/framework/email/swift/MailerTest.php | 74 -------- tests/unit/framework/email/swift/MessageTest.php | 100 ----------- tests/unit/framework/mail/BaseMailerTest.php | 191 +++++++++++++++++++++ tests/unit/framework/mail/ViewResolverTest.php | 61 +++++++ 26 files changed, 1295 insertions(+), 1219 deletions(-) create mode 100644 extensions/swiftmailer/LICENSE.md create mode 100644 extensions/swiftmailer/README.md create mode 100644 extensions/swiftmailer/composer.json create mode 100644 extensions/swiftmailer/yii/swiftmailer/Mailer.php create mode 100644 extensions/swiftmailer/yii/swiftmailer/Message.php delete mode 100644 framework/yii/email/BaseMailer.php delete mode 100644 framework/yii/email/BaseMessage.php delete mode 100644 framework/yii/email/Message.php delete mode 100644 framework/yii/email/MessageInterface.php delete mode 100644 framework/yii/email/ViewResolver.php delete mode 100644 framework/yii/email/swift/Mailer.php delete mode 100644 framework/yii/email/swift/Message.php delete mode 100644 framework/yii/email/swift/autoload.php create mode 100644 framework/yii/mail/BaseMailer.php create mode 100644 framework/yii/mail/BaseMessage.php create mode 100644 framework/yii/mail/Message.php create mode 100644 framework/yii/mail/MessageInterface.php create mode 100644 framework/yii/mail/ViewResolver.php create mode 100644 tests/unit/extensions/swiftmailer/MailerTest.php create mode 100644 tests/unit/extensions/swiftmailer/MessageTest.php delete mode 100644 tests/unit/framework/email/BaseMailerTest.php delete mode 100644 tests/unit/framework/email/ViewResolverTest.php delete mode 100644 tests/unit/framework/email/swift/MailerTest.php delete mode 100644 tests/unit/framework/email/swift/MessageTest.php create mode 100644 tests/unit/framework/mail/BaseMailerTest.php create mode 100644 tests/unit/framework/mail/ViewResolverTest.php diff --git a/extensions/swiftmailer/LICENSE.md b/extensions/swiftmailer/LICENSE.md new file mode 100644 index 0000000..0bb1a8d --- /dev/null +++ b/extensions/swiftmailer/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/swiftmailer/README.md b/extensions/swiftmailer/README.md new file mode 100644 index 0000000..bb04a83 --- /dev/null +++ b/extensions/swiftmailer/README.md @@ -0,0 +1,58 @@ +Yii 2.0 Public Preview - SwiftMailer Mail Solution +================================================== + +Thank you for choosing Yii - a high-performance component-based PHP framework. + +If you are looking for a production-ready PHP framework, please use +[Yii v1.1](https://github.com/yiisoft/yii). + +Yii 2.0 is still under heavy development. We may make significant changes +without prior notices. **Yii 2.0 is not ready for production use yet.** + +[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) + +This is the yii2-swiftmailer extension. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run +``` +php composer.phar require yiisoft/yii2-swiftmailer "*" +``` + +or add +```json +"yiisoft/yii2-swiftmailer": "*" +``` +to the require section of your composer.json. + + +*Note: You might have to run `php composer.phar selfupdate`* + + +Usage & Documentation +--------------------- + +This extension has to be registered prior to usage. +To enable this view renderer add it to the $rendereres property of your view object. + +Example: + +```php + [ + 'mail' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + +For further instructions refer to the related section in the yii guide. diff --git a/extensions/swiftmailer/composer.json b/extensions/swiftmailer/composer.json new file mode 100644 index 0000000..2aa47ae --- /dev/null +++ b/extensions/swiftmailer/composer.json @@ -0,0 +1,28 @@ +{ + "name": "yiisoft/yii2-swiftmailer", + "description": "The SwiftMailer integration for the Yii framework", + "keywords": ["yii", "swift", "swiftmailer", "mail", "email", "mailer"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "minimum-stability": "dev", + "require": { + "yiisoft/yii2": "*", + "swiftmailer/swiftmailer": ">=v5.0" + }, + "autoload": { + "psr-0": { "yii\\swiftmailer\\": "" } + } +} diff --git a/extensions/swiftmailer/yii/swiftmailer/Mailer.php b/extensions/swiftmailer/yii/swiftmailer/Mailer.php new file mode 100644 index 0000000..2f8bbe7 --- /dev/null +++ b/extensions/swiftmailer/yii/swiftmailer/Mailer.php @@ -0,0 +1,143 @@ + array( + * ... + * 'email' => array( + * 'class' => 'yii\email\swift\Mailer', + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'host' => 'localhost', + * 'username' => 'username', + * 'password' => 'password', + * 'port' => '587', + * 'encryption' => 'tls', + * ], + * ), + * ... + * ), + * ~~~ + * + * @see http://swiftmailer.org + * + * @author Paul Klimov + * @since 2.0 + */ +class Mailer extends BaseMailer +{ + /** + * @var \Swift_Mailer Swift mailer instance. + */ + private $_swiftMailer; + /** + * @var \Swift_Transport|array Swift transport instance or its array configuration. + */ + private $_transport = []; + + /** + * @return array|\Swift_Mailer Swift mailer instance or array configuration. + */ + public function getSwiftMailer() + { + if (!is_object($this->_swiftMailer)) { + $this->_swiftMailer = $this->createSwiftMailer(); + } + return $this->_swiftMailer; + } + + /** + * @param array|\Swift_Transport $transport + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setTransport($transport) + { + if (!is_array($transport) && !is_object($transport)) { + throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); + } + $this->_transport = $transport; + } + + /** + * @return array|\Swift_Transport + */ + public function getTransport() + { + if (!is_object($this->_transport)) { + $this->_transport = $this->createTransport($this->_transport); + } + return $this->_transport; + } + + /** + * @inheritdoc + */ + public function send($message) + { + return ($this->getSwiftMailer()->send($message->getSwiftMessage()) > 0); + } + + /** + * Creates Swift mailer instance. + * @return \Swift_Mailer mailer instance. + */ + protected function createSwiftMailer() + { + return \Swift_Mailer::newInstance($this->getTransport()); + } + + /** + * Creates email transport instance by its array configuration. + * @param array $config transport configuration. + * @throws \yii\base\InvalidConfigException on invalid transport configuration. + * @return \Swift_Transport transport instance. + */ + protected function createTransport(array $config) + { + if (array_key_exists('class', $config)) { + $className = $config['class']; + unset($config['class']); + } else { + $className = 'Swift_MailTransport'; + } + $transport = call_user_func([$className, 'newInstance']); + if (!empty($config)) { + foreach ($config as $name => $value) { + if (property_exists($transport, $name)) { + $transport->$name = $value; + } else { + $setter = 'set' . $name; + if (method_exists($transport, $setter)) { + $transport->$setter($value); + } else { + throw new InvalidConfigException('Setting unknown property: ' . get_class($transport) . '::' . $name); + } + } + } + } + return $transport; + } + + /** + * Creates the Swift email message instance. + * @return \Swift_Message email message instance. + */ + public function createSwiftMessage() + { + return new \Swift_Message(); + } +} \ No newline at end of file diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php new file mode 100644 index 0000000..e850208 --- /dev/null +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -0,0 +1,126 @@ + + * @since 2.0 + */ +class Message extends BaseMessage +{ + /** + * @var \Swift_Message Swift message instance. + */ + private $_swiftMessage; + + /** + * @return \Swift_Message Swift message instance. + */ + public function getSwiftMessage() + { + if (!is_object($this->_swiftMessage)) { + $this->_swiftMessage = $this->getMailer()->createSwiftMessage(); + } + return $this->_swiftMessage; + } + + /** + * @inheritdoc + */ + public function setFrom($from) + { + $this->getSwiftMessage()->setFrom($from); + $this->getSwiftMessage()->setReplyTo($from); + } + + /** + * @inheritdoc + */ + public function setTo($to) + { + $this->getSwiftMessage()->setTo($to); + } + + /** + * @inheritdoc + */ + public function setCc($cc) + { + $this->getSwiftMessage()->setCc($cc); + } + + /** + * @inheritdoc + */ + public function setBcc($bcc) + { + $this->getSwiftMessage()->setBcc($bcc); + } + + /** + * @inheritdoc + */ + public function setSubject($subject) + { + $this->getSwiftMessage()->setSubject($subject); + } + + /** + * @inheritdoc + */ + public function setText($text) + { + $this->getSwiftMessage()->setBody($text, 'text/plain'); + } + + /** + * @inheritdoc + */ + public function setHtml($html) + { + $this->getSwiftMessage()->setBody($html, 'text/html'); + } + + /** + * @inheritdoc + */ + public function addText($text) + { + $this->getSwiftMessage()->addPart($text, 'text/plain'); + } + + /** + * @inheritdoc + */ + public function addHtml($html) + { + $this->getSwiftMessage()->addPart($html, 'text/html'); + } + + /** + * @inheritdoc + */ + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') + { + if (empty($contentType)) { + $contentType = 'application/octet-stream'; + } + $attachment = \Swift_Attachment::newInstance($content, $fileName, $contentType); + $this->getSwiftMessage()->attach($attachment); + } +} \ No newline at end of file diff --git a/framework/yii/email/BaseMailer.php b/framework/yii/email/BaseMailer.php deleted file mode 100644 index b3b40b3..0000000 --- a/framework/yii/email/BaseMailer.php +++ /dev/null @@ -1,176 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseMailer extends Component -{ - /** - * @var \yii\base\View|array view instance or its array configuration. - */ - private $_view = []; - /** - * @var \yii\email\ViewResolver|array view resolver instance or its array configuration. - */ - private $_viewResolver = []; - /** - * @var array configuration, which should be applied by default to any new created - * email message instance. - * For example: - * ~~~ - * array( - * 'encoding' => 'UTF-8', - * 'from' => 'noreply@mydomain.com', - * 'bcc' => 'email.test@mydomain.com', - * ) - * ~~~ - */ - private $_defaultMessageConfig = []; - - /** - * @param array|\yii\base\View $view view instance or its array configuration. - * @throws \yii\base\InvalidConfigException on invalid argument. - */ - public function setView($view) - { - if (!is_array($view) && !is_object($view)) { - throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or array, "' . gettype($view) . '" given.'); - } - $this->_view = $view; - } - - /** - * @return \yii\base\View view instance. - */ - public function getView() - { - if (!is_object($this->_view)) { - $this->_view = $this->createView($this->_view); - } - return $this->_view; - } - - /** - * @param array|\yii\email\ViewResolver $viewResolver view resolver instance or its array configuration. - * @throws \yii\base\InvalidConfigException on invalid argument. - */ - public function setViewResolver($viewResolver) - { - if (!is_array($viewResolver) && !is_object($viewResolver)) { - throw new InvalidConfigException('"' . get_class($this) . '::viewResolver" should be either object or array, "' . gettype($viewResolver) . '" given.'); - } - $this->_viewResolver = $viewResolver; - } - - /** - * @return \yii\email\ViewResolver view resolver. - */ - public function getViewResolver() - { - if (!is_object($this->_viewResolver)) { - $this->_viewResolver = $this->createViewResolver($this->_viewResolver); - } - return $this->_viewResolver; - } - - /** - * @param array $defaultMessageConfig default message config - */ - public function setDefaultMessageConfig(array $defaultMessageConfig) - { - $this->_defaultMessageConfig = $defaultMessageConfig; - } - - /** - * @return array default message config - */ - public function getDefaultMessageConfig() - { - return $this->_defaultMessageConfig; - } - - /** - * Creates view instance from given configuration. - * @param array $config view configuration. - * @return \yii\base\View view instance. - */ - protected function createView(array $config) - { - if (!array_key_exists('class', $config)) { - $config['class'] = '\yii\base\View'; - } - return Yii::createObject($config); - } - - /** - * Creates view resolver instance from given configuration. - * @param array $config view resolver configuration. - * @return \yii\email\ViewResolver view resolver instance. - */ - protected function createViewResolver(array $config) - { - if (!array_key_exists('class', $config)) { - $config['class'] = '\yii\email\ViewResolver'; - } - return Yii::createObject($config); - } - - /** - * Sends the given email message. - * @param object $message email message instance - * @return boolean whether the message has been sent. - */ - abstract public function send($message); - - /** - * Sends a couple of messages at once. - * Note: some particular mailers may benefit from sending messages as batch, - * saving resources, for example on open/close connection operations, - * they may override this method to create their specific implementation. - * @param array $messages list of email messages, which should be sent. - * @return integer number of successfull sends - */ - public function sendMultiple(array $messages) { - $successCount = 0; - foreach ($messages as $message) { - if ($this->send($message)) { - $successCount++; - } - } - return $successCount; - } - - /** - * Renders a view. - * @param string $view the view name or the path alias of the view file. - * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. - * @return string string the rendering result - */ - public function render($view, $params = []) - { - return $this->getView()->renderFile($this->getViewResolver()->findViewFile($view), $params, $this); - } -} \ No newline at end of file diff --git a/framework/yii/email/BaseMessage.php b/framework/yii/email/BaseMessage.php deleted file mode 100644 index aca71ee..0000000 --- a/framework/yii/email/BaseMessage.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @since 2.0 - */ -abstract class BaseMessage extends Object implements MessageInterface -{ - /** - * @return \yii\email\BaseMailer - */ - public function getMailer() - { - return Yii::$app->getComponent('email'); - } - - /** - * Initializes the object. - * This method is invoked at the end of the constructor after the object is initialized with the - * given configuration. - */ - public function init() - { - Yii::configure($this, $this->getMailer()->getDefaultMessageConfig()); - } - - /** - * @inheritdoc - */ - public function send() - { - return $this->getMailer()->send($this); - } - - /** - * @inheritdoc - */ - public function attachFile($fileName, $contentType = null, $attachFileName = null) - { - if (!file_exists($fileName)) { - throw new InvalidParamException('Unable to attach file "' . $fileName . '": file does not exists!'); - } - if (empty($contentType)) { - $contentType = FileHelper::getMimeType($fileName); - } - if (empty($attachFileName)) { - $attachFileName = basename($fileName); - } - $content = file_get_contents($fileName); - $this->createAttachment($content, $attachFileName, $contentType); - } - - /** - * @inheritdoc - */ - public function render($view, $params = []) - { - return $this->getMailer()->render($view, $params); - } -} \ No newline at end of file diff --git a/framework/yii/email/Message.php b/framework/yii/email/Message.php deleted file mode 100644 index e8a14ab..0000000 --- a/framework/yii/email/Message.php +++ /dev/null @@ -1,45 +0,0 @@ -from = 'sender@domain.com'; - * $email->to = 'receiver@domain.com'; - * $email->subject = 'Message Subject'; - * $email->text = 'Message Content'; - * $email->send(); - * ~~~ - * - * You can use message object to render view, which can be used to compose the message content: - * ~~~ - * $email = new Message(); - * $email->from = $contactForm->email; - * $email->to = 'admin@domain.com'; - * $email->subject = $email->render('contact/subject', ['form' => $contactForm]); - * $email->addHtml($email->render('contact/html', ['form' => $contactForm])); - * $email->addText($email->render('contact/text', ['form' => $contactForm])); - * $email->send(); - * ~~~ - * - * This particular class uses 'SwiftMailer' library to perform the message sending. - * Note: you can replace usage of this class by your own one, using [[Yii::$classMap]]: - * ~~~ - * Yii::$classMap['yii\email\Message'] = '/path/to/my/email/Message.php' - * ~~~ - * - * @author Paul Klimov - * @since 2.0 - */ -class Message extends SwiftMessage {} \ No newline at end of file diff --git a/framework/yii/email/MessageInterface.php b/framework/yii/email/MessageInterface.php deleted file mode 100644 index c94d2ab..0000000 --- a/framework/yii/email/MessageInterface.php +++ /dev/null @@ -1,116 +0,0 @@ - - * @since 2.0 - */ -interface MessageInterface -{ - /** - * Sets message sender. - * @param string|array $from sender email address. - * You may pass an array of addresses if this message is from multiple people. - * You may also specify sender name in addition to email address using format: - * [email => name]. - */ - public function setFrom($from); - - /** - * Sets message receiver. - * @param string|array $to receiver email address. - * You may pass an array of addresses if multiple recipients should receive this message. - * You may also specify receiver name in addition to email address using format: - * [email => name]. - */ - public function setTo($to); - - /** - * Set the Cc (additional copy receiver) addresses of this message. - * @param string|array $cc copy receiver email address. - * You may pass an array of addresses if multiple recipients should receive this message. - * You may also specify receiver name in addition to email address using format: - * [email => name]. - */ - public function setCc($cc); - - /** - * Set the Bcc (hidden copy receiver) addresses of this message. - * @param string|array $bcc hidden copy receiver email address. - * You may pass an array of addresses if multiple recipients should receive this message. - * You may also specify receiver name in addition to email address using format: - * [email => name]. - */ - public function setBcc($bcc); - - /** - * Sets message subject. - * @param string $subject message subject - */ - public function setSubject($subject); - - /** - * Sets message plain text content. - * @param string $text message plain text content. - */ - public function setText($text); - - /** - * Sets message HTML content. - * @param string $html message HTML content. - */ - public function setHtml($html); - - /** - * Add message plain text content part. - * @param string $text message plain text content. - */ - public function addText($text); - - /** - * Add message HTML content part. - * @param string $html message HTML content. - */ - public function addHtml($html); - - /** - * Create file attachment for the email message. - * @param string $content attachment file content. - * @param string $fileName attachment file name. - * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. - */ - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); - - /** - * Attaches existing file to the email message. - * @param string $fileName full file name - * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. - * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. - */ - public function attachFile($fileName, $contentType = null, $attachFileName = null); - - /** - * Sends this email message. - * @return boolean success. - */ - public function send(); - - /** - * Renders a view. - * The view to be rendered can be specified in one of the following formats: - * - path alias (e.g. "@app/emails/contact/body"); - * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. - * @param string $view the view name or the path alias of the view file. - * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. - * @return string string the rendering result - */ - public function render($view, $params = []); -} \ No newline at end of file diff --git a/framework/yii/email/ViewResolver.php b/framework/yii/email/ViewResolver.php deleted file mode 100644 index 4f522be..0000000 --- a/framework/yii/email/ViewResolver.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @since 2.0 - */ -class ViewResolver extends Component -{ - /** - * @var string directory containing view files for this email messages. - */ - public $viewPath = '@app/emails'; - - /** - * Finds the view file based on the given view name. - * The view to be rendered can be specified in one of the following formats: - * - path alias (e.g. "@app/emails/contact/body"); - * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. - * @param string $view the view name or the path alias of the view file. - * @return string the view file path. Note that the file may not exist. - */ - public function findViewFile($view) - { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/main" - $file = Yii::getAlias($view); - } else { - $file = $this->resolveView($view); - } - return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; - } - - /** - * Composes file name for the view name, appending view name to [[viewPath]]. - * Child classes may override this method to provide more sophisticated - * search of the view files or even composition of the view files "on the fly". - * @param string $view the view name. - * @return string the view file path. - */ - protected function resolveView($view) - { - return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; - } -} \ No newline at end of file diff --git a/framework/yii/email/swift/Mailer.php b/framework/yii/email/swift/Mailer.php deleted file mode 100644 index 1f589a2..0000000 --- a/framework/yii/email/swift/Mailer.php +++ /dev/null @@ -1,180 +0,0 @@ - array( - * ... - * 'email' => array( - * 'class' => 'yii\email\swift\Mailer', - * 'transport' => [ - * 'class' => 'Swift_SmtpTransport', - * 'host' => 'localhost', - * 'username' => 'username', - * 'password' => 'password', - * 'port' => '587', - * 'encryption' => 'tls', - * ], - * ), - * ... - * ), - * ~~~ - * - * @see http://swiftmailer.org - * - * @author Paul Klimov - * @since 2.0 - */ -class Mailer extends BaseMailer -{ - /** - * @var string|callback SwiftMailer autoloader callback or path to autoloader file. - * If the SwiftMailer classes autoloading is already managed in some other place, - * for example via Composer, you should leave this field blank. - */ - public $autoload; - /** - * @var \Swift_Mailer Swift mailer instance. - */ - private $_swiftMailer; - /** - * @var \Swift_Transport|array Swift transport instance or its array configuration. - */ - private $_transport = []; - - /** - * @return array|\Swift_Mailer Swift mailer instance or array configuration. - */ - public function getSwiftMailer() - { - if (!is_object($this->_swiftMailer)) { - $this->_swiftMailer = $this->createSwiftMailer(); - } - return $this->_swiftMailer; - } - - /** - * @param array|\Swift_Transport $transport - * @throws \yii\base\InvalidConfigException on invalid argument. - */ - public function setTransport($transport) - { - if (!is_array($transport) && !is_object($transport)) { - throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); - } - $this->_transport = $transport; - } - - /** - * @return array|\Swift_Transport - */ - public function getTransport() - { - if (!is_object($this->_transport)) { - $this->_transport = $this->createTransport($this->_transport); - } - return $this->_transport; - } - - /** - * @inheritdoc - */ - public function init() - { - $this->setupSwiftMailerAutoload(); - } - - /** - * Sets up the SwiftMailer autoloader, if it is specified. - */ - protected function setupSwiftMailerAutoload() - { - if (!class_exists('Swift', false)) { - if (empty($this->autoload)) { - $this->autoload = __DIR__ . '/autoload.php'; - } - if (is_string($this->autoload)) { - if (file_exists($this->autoload)) { - require_once($this->autoload); - } elseif (function_exists($this->autoload)) { - spl_autoload_register($this->autoload); - } else { - throw new InvalidConfigException('"' . get_class($this) . '::autoload" value "' . $this->autoload . '" is invalid: no such function or file exists.'); - } - } else { - spl_autoload_register($this->autoload); - } - } - } - - /** - * @inheritdoc - */ - public function send($message) - { - return ($this->getSwiftMailer()->send($message->getSwiftMessage()) > 0); - } - - /** - * Creates Swift mailer instance. - * @return \Swift_Mailer mailer instance. - */ - protected function createSwiftMailer() - { - return \Swift_Mailer::newInstance($this->getTransport()); - } - - /** - * Creates email transport instance by its array configuration. - * @param array $config transport configuration. - * @throws \yii\base\InvalidConfigException on invalid transport configuration. - * @return \Swift_Transport transport instance. - */ - protected function createTransport(array $config) - { - if (array_key_exists('class', $config)) { - $className = $config['class']; - unset($config['class']); - } else { - $className = 'Swift_MailTransport'; - } - $transport = call_user_func([$className, 'newInstance']); - if (!empty($config)) { - foreach ($config as $name => $value) { - if (property_exists($transport, $name)) { - $transport->$name = $value; - } else { - $setter = 'set' . $name; - if (method_exists($transport, $setter)) { - $transport->$setter($value); - } else { - throw new InvalidConfigException('Setting unknown property: ' . get_class($transport) . '::' . $name); - } - } - } - } - return $transport; - } - - /** - * Creates the Swift email message instance. - * @return \Swift_Message email message instance. - */ - public function createSwiftMessage() - { - return new \Swift_Message(); - } -} \ No newline at end of file diff --git a/framework/yii/email/swift/Message.php b/framework/yii/email/swift/Message.php deleted file mode 100644 index 04ef7b5..0000000 --- a/framework/yii/email/swift/Message.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @since 2.0 - */ -class Message extends BaseMessage -{ - /** - * @var \Swift_Message Swift message instance. - */ - private $_swiftMessage; - - /** - * @return \Swift_Message Swift message instance. - */ - public function getSwiftMessage() - { - if (!is_object($this->_swiftMessage)) { - $this->_swiftMessage = $this->getMailer()->createSwiftMessage(); - } - return $this->_swiftMessage; - } - - /** - * @inheritdoc - */ - public function setFrom($from) - { - $this->getSwiftMessage()->setFrom($from); - $this->getSwiftMessage()->setReplyTo($from); - } - - /** - * @inheritdoc - */ - public function setTo($to) - { - $this->getSwiftMessage()->setTo($to); - } - - /** - * @inheritdoc - */ - public function setCc($cc) - { - $this->getSwiftMessage()->setCc($cc); - } - - /** - * @inheritdoc - */ - public function setBcc($bcc) - { - $this->getSwiftMessage()->setBcc($bcc); - } - - /** - * @inheritdoc - */ - public function setSubject($subject) - { - $this->getSwiftMessage()->setSubject($subject); - } - - /** - * @inheritdoc - */ - public function setText($text) - { - $this->getSwiftMessage()->setBody($text, 'text/plain'); - } - - /** - * @inheritdoc - */ - public function setHtml($html) - { - $this->getSwiftMessage()->setBody($html, 'text/html'); - } - - /** - * @inheritdoc - */ - public function addText($text) - { - $this->getSwiftMessage()->addPart($text, 'text/plain'); - } - - /** - * @inheritdoc - */ - public function addHtml($html) - { - $this->getSwiftMessage()->addPart($html, 'text/html'); - } - - /** - * @inheritdoc - */ - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') - { - if (empty($contentType)) { - $contentType = 'application/octet-stream'; - } - $attachment = \Swift_Attachment::newInstance($content, $fileName, $contentType); - $this->getSwiftMessage()->attach($attachment); - } -} \ No newline at end of file diff --git a/framework/yii/email/swift/autoload.php b/framework/yii/email/swift/autoload.php deleted file mode 100644 index 494150f..0000000 --- a/framework/yii/email/swift/autoload.php +++ /dev/null @@ -1,9 +0,0 @@ - + * @since 2.0 + */ +abstract class BaseMailer extends Component +{ + /** + * @var \yii\base\View|array view instance or its array configuration. + */ + private $_view = []; + /** + * @var \yii\mail\ViewResolver|array view resolver instance or its array configuration. + */ + private $_viewResolver = []; + /** + * @var array configuration, which should be applied by default to any new created + * email message instance. + * For example: + * ~~~ + * array( + * 'encoding' => 'UTF-8', + * 'from' => 'noreply@mydomain.com', + * 'bcc' => 'email.test@mydomain.com', + * ) + * ~~~ + */ + private $_defaultMessageConfig = []; + + /** + * @param array|\yii\base\View $view view instance or its array configuration. + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setView($view) + { + if (!is_array($view) && !is_object($view)) { + throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or array, "' . gettype($view) . '" given.'); + } + $this->_view = $view; + } + + /** + * @return \yii\base\View view instance. + */ + public function getView() + { + if (!is_object($this->_view)) { + $this->_view = $this->createView($this->_view); + } + return $this->_view; + } + + /** + * @param array|\yii\mail\ViewResolver $viewResolver view resolver instance or its array configuration. + * @throws \yii\base\InvalidConfigException on invalid argument. + */ + public function setViewResolver($viewResolver) + { + if (!is_array($viewResolver) && !is_object($viewResolver)) { + throw new InvalidConfigException('"' . get_class($this) . '::viewResolver" should be either object or array, "' . gettype($viewResolver) . '" given.'); + } + $this->_viewResolver = $viewResolver; + } + + /** + * @return \yii\mail\ViewResolver view resolver. + */ + public function getViewResolver() + { + if (!is_object($this->_viewResolver)) { + $this->_viewResolver = $this->createViewResolver($this->_viewResolver); + } + return $this->_viewResolver; + } + + /** + * @param array $defaultMessageConfig default message config + */ + public function setDefaultMessageConfig(array $defaultMessageConfig) + { + $this->_defaultMessageConfig = $defaultMessageConfig; + } + + /** + * @return array default message config + */ + public function getDefaultMessageConfig() + { + return $this->_defaultMessageConfig; + } + + /** + * Creates view instance from given configuration. + * @param array $config view configuration. + * @return \yii\base\View view instance. + */ + protected function createView(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = '\yii\base\View'; + } + return Yii::createObject($config); + } + + /** + * Creates view resolver instance from given configuration. + * @param array $config view resolver configuration. + * @return \yii\mail\ViewResolver view resolver instance. + */ + protected function createViewResolver(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = '\yii\mail\ViewResolver'; + } + return Yii::createObject($config); + } + + /** + * Sends the given email message. + * @param object $message email message instance + * @return boolean whether the message has been sent. + */ + abstract public function send($message); + + /** + * Sends a couple of messages at once. + * Note: some particular mailers may benefit from sending messages as batch, + * saving resources, for example on open/close connection operations, + * they may override this method to create their specific implementation. + * @param array $messages list of email messages, which should be sent. + * @return integer number of successfull sends + */ + public function sendMultiple(array $messages) { + $successCount = 0; + foreach ($messages as $message) { + if ($this->send($message)) { + $successCount++; + } + } + return $successCount; + } + + /** + * Renders a view. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string string the rendering result + */ + public function render($view, $params = []) + { + return $this->getView()->renderFile($this->getViewResolver()->findViewFile($view), $params, $this); + } +} \ No newline at end of file diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php new file mode 100644 index 0000000..17658f4 --- /dev/null +++ b/framework/yii/mail/BaseMessage.php @@ -0,0 +1,88 @@ + + * @since 2.0 + */ +abstract class BaseMessage extends Object implements MessageInterface +{ + /** + * @return \yii\mail\BaseMailer + */ + public function getMailer() + { + return Yii::$app->getComponent('email'); + } + + /** + * Initializes the object. + * This method is invoked at the end of the constructor after the object is initialized with the + * given configuration. + */ + public function init() + { + Yii::configure($this, $this->getMailer()->getDefaultMessageConfig()); + } + + /** + * @inheritdoc + */ + public function send() + { + return $this->getMailer()->send($this); + } + + /** + * @inheritdoc + */ + public function attachFile($fileName, $contentType = null, $attachFileName = null) + { + if (!file_exists($fileName)) { + throw new InvalidParamException('Unable to attach file "' . $fileName . '": file does not exists!'); + } + if (empty($contentType)) { + $contentType = FileHelper::getMimeType($fileName); + } + if (empty($attachFileName)) { + $attachFileName = basename($fileName); + } + $content = file_get_contents($fileName); + $this->createAttachment($content, $attachFileName, $contentType); + } + + /** + * @inheritdoc + */ + public function render($view, $params = []) + { + return $this->getMailer()->render($view, $params); + } +} \ No newline at end of file diff --git a/framework/yii/mail/Message.php b/framework/yii/mail/Message.php new file mode 100644 index 0000000..fb34e24 --- /dev/null +++ b/framework/yii/mail/Message.php @@ -0,0 +1,45 @@ +from = 'sender@domain.com'; + * $email->to = 'receiver@domain.com'; + * $email->subject = 'Message Subject'; + * $email->text = 'Message Content'; + * $email->send(); + * ~~~ + * + * You can use message object to render view, which can be used to compose the message content: + * ~~~ + * $email = new Message(); + * $email->from = $contactForm->email; + * $email->to = 'admin@domain.com'; + * $email->subject = $email->render('contact/subject', ['form' => $contactForm]); + * $email->addHtml($email->render('contact/html', ['form' => $contactForm])); + * $email->addText($email->render('contact/text', ['form' => $contactForm])); + * $email->send(); + * ~~~ + * + * This particular class uses 'SwiftMailer' library to perform the message sending. + * Note: you can replace usage of this class by your own one, using [[Yii::$classMap]]: + * ~~~ + * Yii::$classMap['yii\mail\Message'] = '/path/to/my/email/Message.php' + * ~~~ + * + * @author Paul Klimov + * @since 2.0 + */ +class Message extends SwiftMessage {} \ No newline at end of file diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php new file mode 100644 index 0000000..c592edb --- /dev/null +++ b/framework/yii/mail/MessageInterface.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +interface MessageInterface +{ + /** + * Sets message sender. + * @param string|array $from sender email address. + * You may pass an array of addresses if this message is from multiple people. + * You may also specify sender name in addition to email address using format: + * [email => name]. + */ + public function setFrom($from); + + /** + * Sets message receiver. + * @param string|array $to receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setTo($to); + + /** + * Set the Cc (additional copy receiver) addresses of this message. + * @param string|array $cc copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setCc($cc); + + /** + * Set the Bcc (hidden copy receiver) addresses of this message. + * @param string|array $bcc hidden copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * [email => name]. + */ + public function setBcc($bcc); + + /** + * Sets message subject. + * @param string $subject message subject + */ + public function setSubject($subject); + + /** + * Sets message plain text content. + * @param string $text message plain text content. + */ + public function setText($text); + + /** + * Sets message HTML content. + * @param string $html message HTML content. + */ + public function setHtml($html); + + /** + * Add message plain text content part. + * @param string $text message plain text content. + */ + public function addText($text); + + /** + * Add message HTML content part. + * @param string $html message HTML content. + */ + public function addHtml($html); + + /** + * Create file attachment for the email message. + * @param string $content attachment file content. + * @param string $fileName attachment file name. + * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. + */ + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); + + /** + * Attaches existing file to the email message. + * @param string $fileName full file name + * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. + * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. + */ + public function attachFile($fileName, $contentType = null, $attachFileName = null); + + /** + * Sends this email message. + * @return boolean success. + */ + public function send(); + + /** + * Renders a view. + * The view to be rendered can be specified in one of the following formats: + * - path alias (e.g. "@app/emails/contact/body"); + * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string string the rendering result + */ + public function render($view, $params = []); +} \ No newline at end of file diff --git a/framework/yii/mail/ViewResolver.php b/framework/yii/mail/ViewResolver.php new file mode 100644 index 0000000..31f57cb --- /dev/null +++ b/framework/yii/mail/ViewResolver.php @@ -0,0 +1,57 @@ + + * @since 2.0 + */ +class ViewResolver extends Component +{ + /** + * @var string directory containing view files for this email messages. + */ + public $viewPath = '@app/emails'; + + /** + * Finds the view file based on the given view name. + * The view to be rendered can be specified in one of the following formats: + * - path alias (e.g. "@app/emails/contact/body"); + * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. + * @param string $view the view name or the path alias of the view file. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view) + { + if (strncmp($view, '@', 1) === 0) { + // e.g. "@app/views/main" + $file = Yii::getAlias($view); + } else { + $file = $this->resolveView($view); + } + return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; + } + + /** + * Composes file name for the view name, appending view name to [[viewPath]]. + * Child classes may override this method to provide more sophisticated + * search of the view files or even composition of the view files "on the fly". + * @param string $view the view name. + * @return string the view file path. + */ + protected function resolveView($view) + { + return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; + } +} \ No newline at end of file diff --git a/tests/unit/extensions/swiftmailer/MailerTest.php b/tests/unit/extensions/swiftmailer/MailerTest.php new file mode 100644 index 0000000..db5f298 --- /dev/null +++ b/tests/unit/extensions/swiftmailer/MailerTest.php @@ -0,0 +1,74 @@ +mockApplication(array( + 'vendorPath' => Yii::getAlias('@yiiunit/vendor') + )); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testSetupTransport() + { + $mailer = new Mailer(); + + $transport = \Swift_MailTransport::newInstance(); + $mailer->setTransport($transport); + $this->assertEquals($transport, $mailer->getTransport(), 'Unable to setup transport!'); + } + + /** + * @depends testSetupTransport + */ + public function testConfigureTransport() + { + $mailer = new Mailer(); + + $transportConfig = [ + 'class' => 'Swift_SmtpTransport', + 'host' => 'localhost', + ]; + $mailer->setTransport($transportConfig); + $transport = $mailer->getTransport(); + $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); + $this->assertEquals($transportConfig['class'], get_class($transport), 'Invalid transport class!'); + $this->assertEquals($transportConfig['host'], $transport->getHost(), 'Invalid transport host!'); + } + + public function testGetSwiftMailer() + { + $mailer = new Mailer(); + $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); + } + + public function testCreateSwiftMessage() + { + $mailer = new Mailer(); + $message = $mailer->createSwiftMessage(); + $this->assertTrue(is_object($message), 'Unable to create Swift message instance!'); + } +} \ No newline at end of file diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php new file mode 100644 index 0000000..cabf22a --- /dev/null +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -0,0 +1,100 @@ +mockApplication(array( + 'vendorPath' => Yii::getAlias('@yiiunit/vendor') + )); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testGetSwiftMessage() + { + $message = new Message(); + $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); + } + + /** + * @depends testGetSwiftMessage + */ + public function testSend() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Test'); + $message->setText('Yii Swift Test body'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testAttachFile() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Attach File Test'); + $message->setText('Yii Swift Attach File Test body'); + $message->attachFile(__FILE__); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testCreateAttachment() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Create Attachment Test'); + $message->setText('Yii Swift Create Attachment Test body'); + $message->createAttachment('Test attachment content', 'test.txt'); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testSendAlternativeBody() + { + $message = new Message(); + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Alternative Body Test'); + $message->addHtml('Yii Swift test HTML body'); + $message->addText('Yii Swift test plain text body'); + $this->assertTrue($message->send()); + } +} \ No newline at end of file diff --git a/tests/unit/framework/email/BaseMailerTest.php b/tests/unit/framework/email/BaseMailerTest.php deleted file mode 100644 index b75d628..0000000 --- a/tests/unit/framework/email/BaseMailerTest.php +++ /dev/null @@ -1,191 +0,0 @@ -mockApplication(); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); - $filePath = $this->getTestFilePath(); - if (!file_exists($filePath)) { - FileHelper::createDirectory($filePath); - } - } - - public function tearDown() - { - $filePath = $this->getTestFilePath(); - if (file_exists($filePath)) { - FileHelper::removeDirectory($filePath); - } - } - - /** - * @return string test file path. - */ - protected function getTestFilePath() - { - return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new Mailer(); - return $component; - } - - // Tests : - - public function testSetupView() - { - $mailer = new Mailer(); - - $view = new View(); - $mailer->setView($view); - $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); - - $viewConfig = [ - 'params' => [ - 'param1' => 'value1', - 'param2' => 'value2', - ] - ]; - $mailer->setView($viewConfig); - $view = $mailer->getView(); - $this->assertTrue(is_object($view), 'Unable to setup view via config!'); - $this->assertEquals($viewConfig['params'], $view->params, 'Unable to configure view via config array!'); - } - - /** - * @depends testSetupView - */ - public function testGetDefaultView() - { - $mailer = new Mailer(); - $view = $mailer->getView(); - $this->assertTrue(is_object($view), 'Unable to get default view!'); - } - - public function testSetupViewResolver() - { - $mailer = new Mailer(); - - $viewResolver = new ViewResolver(); - $mailer->setViewResolver($viewResolver); - $this->assertEquals($viewResolver, $mailer->getViewResolver(), 'Unable to setup view resolver!'); - - $viewResolverConfig = [ - 'viewPath' => '/test/view/path', - ]; - $mailer->setViewResolver($viewResolverConfig); - $viewResolver = $mailer->getViewResolver(); - $this->assertTrue(is_object($viewResolver), 'Unable to setup view resolver via config!'); - $this->assertEquals($viewResolverConfig['viewPath'], $viewResolver->viewPath, 'Unable to configure view resolver via config array!'); - } - - /** - * @depends testSetupViewResolver - */ - public function testGetDefaultViewResolver() - { - $mailer = new Mailer(); - $viewResolver = $mailer->getViewResolver(); - $this->assertTrue(is_object($viewResolver), 'Unable to get default view resolver!'); - } - - public function testDefaultMessageConfig() - { - $defaultMessageConfig = array( - 'id' => 'test-id', - 'encoding' => 'test-encoding', - ); - Yii::$app->getComponent('email')->setDefaultMessageConfig($defaultMessageConfig); - - $message = new Message(); - - foreach ($defaultMessageConfig as $name => $value) { - $this->assertEquals($value, $message->$name); - } - } - - /** - * @depends testGetDefaultView - * @depends testGetDefaultViewResolver - */ - public function testRender() - { - $mailer = new Mailer(); - - $filePath = $this->getTestFilePath(); - $mailer->getViewResolver()->viewPath = $filePath; - - $viewName = 'test_view'; - $fileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; - $fileContent = ''; - file_put_contents($fileName, $fileContent); - - $params = [ - 'testParam' => 'test output' - ]; - $renderResult = $mailer->render($viewName, $params); - $this->assertEquals($params['testParam'], $renderResult); - } -} - -/** - * Test Mailer class - */ -class Mailer extends BaseMailer -{ - public $sentMessages = array(); - - public function send($message) - { - $this->sentMessages[] = $message; - } -} - -/** - * Test Message class - */ -class Message extends BaseMessage -{ - public $id; - public $encoding; - - public function setFrom($from) {} - - public function setTo($to) {} - - public function setCc($cc) {} - - public function setBcc($bcc) {} - - public function setSubject($subject) {} - - public function setText($text) {} - - public function setHtml($html) {} - - public function addText($text) {} - - public function addHtml($html) {} - - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') {} -} \ No newline at end of file diff --git a/tests/unit/framework/email/ViewResolverTest.php b/tests/unit/framework/email/ViewResolverTest.php deleted file mode 100644 index 9185f72..0000000 --- a/tests/unit/framework/email/ViewResolverTest.php +++ /dev/null @@ -1,57 +0,0 @@ -testViewPath); - return [ - [ - $alias . '/test', - $aliasPath . '/test.php', - ], - [ - $alias . '/test.tpl', - $aliasPath . '/test.tpl', - ], - [ - 'contact/html', - $viewPath . '/contact/html.php', - ], - [ - 'contact/html.tpl', - $viewPath . '/contact/html.tpl', - ], - ]; - } - - /** - * @dataProvider dataProviderFindViewFile - * - * @param string $view - * @param string $expectedFileName - */ - public function testFindViewFile($view, $expectedFileName) - { - $viewResolver = new ViewResolver(); - $viewResolver->viewPath = $this->testViewPath; - $fileName = $viewResolver->findViewFile($view); - $this->assertEquals($expectedFileName, $fileName); - } -} \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MailerTest.php b/tests/unit/framework/email/swift/MailerTest.php deleted file mode 100644 index f9cee72..0000000 --- a/tests/unit/framework/email/swift/MailerTest.php +++ /dev/null @@ -1,74 +0,0 @@ -mockApplication(array( - 'vendorPath' => Yii::getAlias('@yiiunit/vendor') - )); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new Mailer(); - return $component; - } - - // Tests : - - public function testSetupTransport() - { - $mailer = new Mailer(); - - $transport = \Swift_MailTransport::newInstance(); - $mailer->setTransport($transport); - $this->assertEquals($transport, $mailer->getTransport(), 'Unable to setup transport!'); - } - - /** - * @depends testSetupTransport - */ - public function testConfigureTransport() - { - $mailer = new Mailer(); - - $transportConfig = [ - 'class' => 'Swift_SmtpTransport', - 'host' => 'localhost', - ]; - $mailer->setTransport($transportConfig); - $transport = $mailer->getTransport(); - $this->assertTrue(is_object($transport), 'Unable to setup transport via config!'); - $this->assertEquals($transportConfig['class'], get_class($transport), 'Invalid transport class!'); - $this->assertEquals($transportConfig['host'], $transport->getHost(), 'Invalid transport host!'); - } - - public function testGetSwiftMailer() - { - $mailer = new Mailer(); - $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); - } - - public function testCreateSwiftMessage() - { - $mailer = new Mailer(); - $message = $mailer->createSwiftMessage(); - $this->assertTrue(is_object($message), 'Unable to create Swift message instance!'); - } -} \ No newline at end of file diff --git a/tests/unit/framework/email/swift/MessageTest.php b/tests/unit/framework/email/swift/MessageTest.php deleted file mode 100644 index 112f4fc..0000000 --- a/tests/unit/framework/email/swift/MessageTest.php +++ /dev/null @@ -1,100 +0,0 @@ -mockApplication(array( - 'vendorPath' => Yii::getAlias('@yiiunit/vendor') - )); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); - } - - /** - * @return Mailer test email component instance. - */ - protected function createTestEmailComponent() - { - $component = new Mailer(); - return $component; - } - - // Tests : - - public function testGetSwiftMessage() - { - $message = new Message(); - $this->assertTrue(is_object($message->getSwiftMessage()), 'Unable to get Swift message!'); - } - - /** - * @depends testGetSwiftMessage - */ - public function testSend() - { - $message = new Message(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Test'); - $message->setText('Yii Swift Test body'); - $this->assertTrue($message->send()); - } - - /** - * @depends testSend - */ - public function testAttachFile() - { - $message = new Message(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Attach File Test'); - $message->setText('Yii Swift Attach File Test body'); - $message->attachFile(__FILE__); - $this->assertTrue($message->send()); - } - - /** - * @depends testSend - */ - public function testCreateAttachment() - { - $message = new Message(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Create Attachment Test'); - $message->setText('Yii Swift Create Attachment Test body'); - $message->createAttachment('Test attachment content', 'test.txt'); - $this->assertTrue($message->send()); - } - - /** - * @depends testSend - */ - public function testSendAlternativeBody() - { - $message = new Message(); - $message->setTo($this->testEmailReceiver); - $message->setFrom('someuser@somedomain.com'); - $message->setSubject('Yii Swift Alternative Body Test'); - $message->addHtml('Yii Swift test HTML body'); - $message->addText('Yii Swift test plain text body'); - $this->assertTrue($message->send()); - } -} \ No newline at end of file diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php new file mode 100644 index 0000000..88d6a4e --- /dev/null +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -0,0 +1,191 @@ +mockApplication(); + Yii::$app->setComponent('email', $this->createTestEmailComponent()); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown() + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + /** + * @return string test file path. + */ + protected function getTestFilePath() + { + return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); + } + + /** + * @return Mailer test email component instance. + */ + protected function createTestEmailComponent() + { + $component = new Mailer(); + return $component; + } + + // Tests : + + public function testSetupView() + { + $mailer = new Mailer(); + + $view = new View(); + $mailer->setView($view); + $this->assertEquals($view, $mailer->getView(), 'Unable to setup view!'); + + $viewConfig = [ + 'params' => [ + 'param1' => 'value1', + 'param2' => 'value2', + ] + ]; + $mailer->setView($viewConfig); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to setup view via config!'); + $this->assertEquals($viewConfig['params'], $view->params, 'Unable to configure view via config array!'); + } + + /** + * @depends testSetupView + */ + public function testGetDefaultView() + { + $mailer = new Mailer(); + $view = $mailer->getView(); + $this->assertTrue(is_object($view), 'Unable to get default view!'); + } + + public function testSetupViewResolver() + { + $mailer = new Mailer(); + + $viewResolver = new ViewResolver(); + $mailer->setViewResolver($viewResolver); + $this->assertEquals($viewResolver, $mailer->getViewResolver(), 'Unable to setup view resolver!'); + + $viewResolverConfig = [ + 'viewPath' => '/test/view/path', + ]; + $mailer->setViewResolver($viewResolverConfig); + $viewResolver = $mailer->getViewResolver(); + $this->assertTrue(is_object($viewResolver), 'Unable to setup view resolver via config!'); + $this->assertEquals($viewResolverConfig['viewPath'], $viewResolver->viewPath, 'Unable to configure view resolver via config array!'); + } + + /** + * @depends testSetupViewResolver + */ + public function testGetDefaultViewResolver() + { + $mailer = new Mailer(); + $viewResolver = $mailer->getViewResolver(); + $this->assertTrue(is_object($viewResolver), 'Unable to get default view resolver!'); + } + + public function testDefaultMessageConfig() + { + $defaultMessageConfig = array( + 'id' => 'test-id', + 'encoding' => 'test-encoding', + ); + Yii::$app->getComponent('email')->setDefaultMessageConfig($defaultMessageConfig); + + $message = new Message(); + + foreach ($defaultMessageConfig as $name => $value) { + $this->assertEquals($value, $message->$name); + } + } + + /** + * @depends testGetDefaultView + * @depends testGetDefaultViewResolver + */ + public function testRender() + { + $mailer = new Mailer(); + + $filePath = $this->getTestFilePath(); + $mailer->getViewResolver()->viewPath = $filePath; + + $viewName = 'test_view'; + $fileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; + $fileContent = ''; + file_put_contents($fileName, $fileContent); + + $params = [ + 'testParam' => 'test output' + ]; + $renderResult = $mailer->render($viewName, $params); + $this->assertEquals($params['testParam'], $renderResult); + } +} + +/** + * Test Mailer class + */ +class Mailer extends BaseMailer +{ + public $sentMessages = array(); + + public function send($message) + { + $this->sentMessages[] = $message; + } +} + +/** + * Test Message class + */ +class Message extends BaseMessage +{ + public $id; + public $encoding; + + public function setFrom($from) {} + + public function setTo($to) {} + + public function setCc($cc) {} + + public function setBcc($bcc) {} + + public function setSubject($subject) {} + + public function setText($text) {} + + public function setHtml($html) {} + + public function addText($text) {} + + public function addHtml($html) {} + + public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') {} +} \ No newline at end of file diff --git a/tests/unit/framework/mail/ViewResolverTest.php b/tests/unit/framework/mail/ViewResolverTest.php new file mode 100644 index 0000000..4ff4dbd --- /dev/null +++ b/tests/unit/framework/mail/ViewResolverTest.php @@ -0,0 +1,61 @@ +testViewPath); + return [ + [ + $alias . '/test', + $aliasPath . '/test.php', + ], + [ + $alias . '/test.tpl', + $aliasPath . '/test.tpl', + ], + [ + 'contact/html', + $viewPath . '/contact/html.php', + ], + [ + 'contact/html.tpl', + $viewPath . '/contact/html.tpl', + ], + ]; + } + + /** + * @dataProvider dataProviderFindViewFile + * + * @param string $view + * @param string $expectedFileName + */ + public function testFindViewFile($view, $expectedFileName) + { + $viewResolver = new ViewResolver(); + $viewResolver->viewPath = $this->testViewPath; + $fileName = $viewResolver->findViewFile($view); + $this->assertEquals($expectedFileName, $fileName); + } +} \ No newline at end of file From c7c32659b0d0dec85d63b75eedc6dfc303deae03 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 25 Oct 2013 14:49:43 +0300 Subject: [PATCH 006/203] Vendor testing support provided. 'swiftmailer' extension unit tests fixed. --- .travis.yml | 2 +- extensions/swiftmailer/yii/swiftmailer/Mailer.php | 2 +- extensions/swiftmailer/yii/swiftmailer/Message.php | 2 +- tests/unit/VendorTestCase.php | 30 ++++++++++++++++++++++ tests/unit/extensions/swiftmailer/MailerTest.php | 14 +++++----- tests/unit/extensions/swiftmailer/MessageTest.php | 14 +++++----- 6 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 tests/unit/VendorTestCase.php diff --git a/.travis.yml b/.travis.yml index 0218654..99ff95a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ before_script: - tests/unit/data/travis/cubrid-setup.sh script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,swiftmailer + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor after_script: - php vendor/bin/coveralls diff --git a/extensions/swiftmailer/yii/swiftmailer/Mailer.php b/extensions/swiftmailer/yii/swiftmailer/Mailer.php index 2f8bbe7..1d5bc5c 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Mailer.php +++ b/extensions/swiftmailer/yii/swiftmailer/Mailer.php @@ -8,7 +8,7 @@ namespace yii\swiftmailer; use yii\base\InvalidConfigException; -use yii\email\BaseMailer; +use yii\mail\BaseMailer; /** * Mailer based on SwiftMailer library. diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index e850208..7cf3032 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -7,7 +7,7 @@ namespace yii\swiftmailer; -use yii\email\BaseMessage; +use yii\mail\BaseMessage; /** * Email message based on SwiftMailer library. diff --git a/tests/unit/VendorTestCase.php b/tests/unit/VendorTestCase.php new file mode 100644 index 0000000..d633d02 --- /dev/null +++ b/tests/unit/VendorTestCase.php @@ -0,0 +1,30 @@ +mockApplication(array( - 'vendorPath' => Yii::getAlias('@yiiunit/vendor') - )); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); + $this->mockApplication([ + 'components' => [ + 'email' => $this->createTestEmailComponent() + ] + ]); } /** diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php index cabf22a..530d725 100644 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -5,13 +5,14 @@ namespace yiiunit\extensions\swiftmailer; use Yii; use yii\swiftmailer\Mailer; use yii\swiftmailer\Message; -use yiiunit\TestCase; +use yiiunit\VendorTestCase; /** + * @group vendor * @group email * @group swiftmailer */ -class MessageTest extends TestCase +class MessageTest extends VendorTestCase { /** * @var string test email address, which will be used as receiver for the messages. @@ -20,10 +21,11 @@ class MessageTest extends TestCase public function setUp() { - $this->mockApplication(array( - 'vendorPath' => Yii::getAlias('@yiiunit/vendor') - )); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); + $this->mockApplication([ + 'components' => [ + 'email' => $this->createTestEmailComponent() + ] + ]); } /** From 4a9d546c7914db54df5a4f7a5046c814d9d898af Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 25 Oct 2013 16:08:14 +0300 Subject: [PATCH 007/203] 'MessageInterface::createAttachment()' renamed to 'MessageInterface::attachContentAsFile()' --- extensions/swiftmailer/yii/swiftmailer/Message.php | 2 +- framework/yii/mail/BaseMessage.php | 2 +- framework/yii/mail/MessageInterface.php | 4 ++-- tests/unit/extensions/swiftmailer/MessageTest.php | 2 +- tests/unit/framework/mail/BaseMailerTest.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index 7cf3032..8ae3ee4 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -115,7 +115,7 @@ class Message extends BaseMessage /** * @inheritdoc */ - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') + public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream') { if (empty($contentType)) { $contentType = 'application/octet-stream'; diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index 17658f4..5593cec 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -75,7 +75,7 @@ abstract class BaseMessage extends Object implements MessageInterface $attachFileName = basename($fileName); } $content = file_get_contents($fileName); - $this->createAttachment($content, $attachFileName, $contentType); + $this->attachContentAsFile($content, $attachFileName, $contentType); } /** diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index c592edb..28223a4 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -82,12 +82,12 @@ interface MessageInterface public function addHtml($html); /** - * Create file attachment for the email message. + * Attach specified content as file for the email message. * @param string $content attachment file content. * @param string $fileName attachment file name. * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. */ - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream'); + public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream'); /** * Attaches existing file to the email message. diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php index 530d725..dabe831 100644 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -82,7 +82,7 @@ class MessageTest extends VendorTestCase $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Create Attachment Test'); $message->setText('Yii Swift Create Attachment Test body'); - $message->createAttachment('Test attachment content', 'test.txt'); + $message->attachContentAsFile('Test attachment content', 'test.txt'); $this->assertTrue($message->send()); } diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index 88d6a4e..95bd836 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -187,5 +187,5 @@ class Message extends BaseMessage public function addHtml($html) {} - public function createAttachment($content, $fileName, $contentType = 'application/octet-stream') {} + public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream') {} } \ No newline at end of file From d33519e3e5afffddeed7dc5e6cbd359b7ad27fd4 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 25 Oct 2013 16:58:30 +0300 Subject: [PATCH 008/203] Method 'MessageInterface::__toString()' added --- extensions/swiftmailer/yii/swiftmailer/Message.php | 48 ++++++++++++++++++++++ framework/yii/mail/MessageInterface.php | 7 ++++ tests/unit/framework/mail/BaseMailerTest.php | 5 +++ 3 files changed, 60 insertions(+) diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index 8ae3ee4..bfefcde 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -49,6 +49,14 @@ class Message extends BaseMessage } /** + * @return string from address of this message. + */ + public function getFrom() + { + return $this->getSwiftMessage()->getFrom(); + } + + /** * @inheritdoc */ public function setTo($to) @@ -57,6 +65,14 @@ class Message extends BaseMessage } /** + * @return array To addresses of this message. + */ + public function getTo() + { + return $this->getSwiftMessage()->getTo(); + } + + /** * @inheritdoc */ public function setCc($cc) @@ -65,6 +81,14 @@ class Message extends BaseMessage } /** + * @return array Cc address of this message. + */ + public function getCc() + { + return $this->getSwiftMessage()->getCc(); + } + + /** * @inheritdoc */ public function setBcc($bcc) @@ -73,6 +97,14 @@ class Message extends BaseMessage } /** + * @return array Bcc addresses of this message. + */ + public function getBcc() + { + return $this->getSwiftMessage()->getBcc(); + } + + /** * @inheritdoc */ public function setSubject($subject) @@ -81,6 +113,14 @@ class Message extends BaseMessage } /** + * @return string the subject of this message. + */ + public function getSubject() + { + return $this->getSwiftMessage()->getSubject(); + } + + /** * @inheritdoc */ public function setText($text) @@ -123,4 +163,12 @@ class Message extends BaseMessage $attachment = \Swift_Attachment::newInstance($content, $fileName, $contentType); $this->getSwiftMessage()->attach($attachment); } + + /** + * @inheritdoc + */ + public function __toString() + { + return $this->getSwiftMessage()->toString(); + } } \ No newline at end of file diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index 28223a4..48a4f10 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -113,4 +113,11 @@ interface MessageInterface * @return string string the rendering result */ public function render($view, $params = []); + + /** + * String output. + * This is PHP magic method that returns string representation of an object. + * @return string the string representation of the object + */ + public function __toString(); } \ No newline at end of file diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index 95bd836..e80ca0c 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -188,4 +188,9 @@ class Message extends BaseMessage public function addHtml($html) {} public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream') {} + + public function __toString() + { + return get_class($this); + } } \ No newline at end of file From 73a8e77c3d1dde759d01f95073c0124554fb1f58 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 25 Oct 2013 17:03:33 +0300 Subject: [PATCH 009/203] BaseMailer::defaultMessageConfig published as BaseMailer::messageConfig --- framework/yii/mail/BaseMailer.php | 18 +----------------- framework/yii/mail/BaseMessage.php | 2 +- tests/unit/framework/mail/BaseMailerTest.php | 6 +++--- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/framework/yii/mail/BaseMailer.php b/framework/yii/mail/BaseMailer.php index 8e88749..a78b6fc 100644 --- a/framework/yii/mail/BaseMailer.php +++ b/framework/yii/mail/BaseMailer.php @@ -48,7 +48,7 @@ abstract class BaseMailer extends Component * ) * ~~~ */ - private $_defaultMessageConfig = []; + public $messageConfig = []; /** * @param array|\yii\base\View $view view instance or its array configuration. @@ -97,22 +97,6 @@ abstract class BaseMailer extends Component } /** - * @param array $defaultMessageConfig default message config - */ - public function setDefaultMessageConfig(array $defaultMessageConfig) - { - $this->_defaultMessageConfig = $defaultMessageConfig; - } - - /** - * @return array default message config - */ - public function getDefaultMessageConfig() - { - return $this->_defaultMessageConfig; - } - - /** * Creates view instance from given configuration. * @param array $config view configuration. * @return \yii\base\View view instance. diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index 5593cec..f48310c 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -49,7 +49,7 @@ abstract class BaseMessage extends Object implements MessageInterface */ public function init() { - Yii::configure($this, $this->getMailer()->getDefaultMessageConfig()); + Yii::configure($this, $this->getMailer()->messageConfig); } /** diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index e80ca0c..84bb293 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -111,15 +111,15 @@ class BaseMailerTest extends TestCase public function testDefaultMessageConfig() { - $defaultMessageConfig = array( + $messageConfig = array( 'id' => 'test-id', 'encoding' => 'test-encoding', ); - Yii::$app->getComponent('email')->setDefaultMessageConfig($defaultMessageConfig); + Yii::$app->getComponent('email')->messageConfig = $messageConfig; $message = new Message(); - foreach ($defaultMessageConfig as $name => $value) { + foreach ($messageConfig as $name => $value) { $this->assertEquals($value, $message->$name); } } From 5bc22a077fafa0e8f1100d274dcf407205bdc8c8 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Tue, 29 Oct 2013 12:13:26 +0200 Subject: [PATCH 010/203] Interface 'MailerInterface' extracted. Method 'MailerInterface::createMessage()' added. --- extensions/swiftmailer/yii/swiftmailer/Mailer.php | 17 ++++---- extensions/swiftmailer/yii/swiftmailer/Message.php | 11 +++++- framework/yii/mail/BaseMailer.php | 30 ++++++++++----- framework/yii/mail/BaseMessage.php | 12 +----- framework/yii/mail/MailerInterface.php | 40 +++++++++++++++++++ framework/yii/mail/Message.php | 45 ---------------------- framework/yii/mail/MessageInterface.php | 2 +- tests/unit/extensions/swiftmailer/MailerTest.php | 9 +---- tests/unit/extensions/swiftmailer/MessageTest.php | 20 +++++++--- tests/unit/framework/mail/BaseMailerTest.php | 37 +++++++++++++++--- tests/unit/framework/mail/ViewResolverTest.php | 2 +- 11 files changed, 128 insertions(+), 97 deletions(-) create mode 100644 framework/yii/mail/MailerInterface.php delete mode 100644 framework/yii/mail/Message.php diff --git a/extensions/swiftmailer/yii/swiftmailer/Mailer.php b/extensions/swiftmailer/yii/swiftmailer/Mailer.php index 1d5bc5c..f53001b 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Mailer.php +++ b/extensions/swiftmailer/yii/swiftmailer/Mailer.php @@ -19,7 +19,7 @@ use yii\mail\BaseMailer; * 'components' => array( * ... * 'email' => array( - * 'class' => 'yii\email\swift\Mailer', + * 'class' => 'yii\swiftmailer\Mailer', * 'transport' => [ * 'class' => 'Swift_SmtpTransport', * 'host' => 'localhost', @@ -35,12 +35,18 @@ use yii\mail\BaseMailer; * * @see http://swiftmailer.org * + * @method Message createMessage(array $config = []) creates new message instance from given configuration. + * * @author Paul Klimov * @since 2.0 */ class Mailer extends BaseMailer { /** + * @var string message default class name. + */ + public $messageClass = 'yii\swiftmailer\Message'; + /** * @var \Swift_Mailer Swift mailer instance. */ private $_swiftMailer; @@ -131,13 +137,4 @@ class Mailer extends BaseMailer } return $transport; } - - /** - * Creates the Swift email message instance. - * @return \Swift_Message email message instance. - */ - public function createSwiftMessage() - { - return new \Swift_Message(); - } } \ No newline at end of file diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index bfefcde..7fc908b 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -34,7 +34,7 @@ class Message extends BaseMessage public function getSwiftMessage() { if (!is_object($this->_swiftMessage)) { - $this->_swiftMessage = $this->getMailer()->createSwiftMessage(); + $this->_swiftMessage = $this->createSwiftMessage(); } return $this->_swiftMessage; } @@ -171,4 +171,13 @@ class Message extends BaseMessage { return $this->getSwiftMessage()->toString(); } + + /** + * Creates the Swift email message instance. + * @return \Swift_Message email message instance. + */ + protected function createSwiftMessage() + { + return new \Swift_Message(); + } } \ No newline at end of file diff --git a/framework/yii/mail/BaseMailer.php b/framework/yii/mail/BaseMailer.php index a78b6fc..ea7690b 100644 --- a/framework/yii/mail/BaseMailer.php +++ b/framework/yii/mail/BaseMailer.php @@ -20,13 +20,11 @@ use Yii; * * @property \yii\base\View|array $view view instance or its array configuration. * @property \yii\mail\ViewResolver|array $viewResolver view resolver instance or its array configuration. - * @property array $defaultMessageConfig configuration, which should be applied by default to any - * new created email message instance. * * @author Paul Klimov * @since 2.0 */ -abstract class BaseMailer extends Component +abstract class BaseMailer extends Component implements MailerInterface { /** * @var \yii\base\View|array view instance or its array configuration. @@ -49,6 +47,10 @@ abstract class BaseMailer extends Component * ~~~ */ public $messageConfig = []; + /** + * @var string message default class name. + */ + public $messageClass = 'yii\mail\BaseMessage'; /** * @param array|\yii\base\View $view view instance or its array configuration. @@ -123,11 +125,20 @@ abstract class BaseMailer extends Component } /** - * Sends the given email message. - * @param object $message email message instance - * @return boolean whether the message has been sent. + * Creates new message instance from given configuration. + * Message configuration will be merged with [[messageConfig]]. + * If 'class' parameter is omitted [[messageClass]], will be used. + * @param array $config message configuration. + * @return MessageInterface message instance. */ - abstract public function send($message); + public function createMessage(array $config = []) + { + $config = array_merge($this->messageConfig, $config); + if (!array_key_exists('class', $config)) { + $config['class'] = $this->messageClass; + } + return Yii::createObject($config); + } /** * Sends a couple of messages at once. @@ -135,9 +146,10 @@ abstract class BaseMailer extends Component * saving resources, for example on open/close connection operations, * they may override this method to create their specific implementation. * @param array $messages list of email messages, which should be sent. - * @return integer number of successfull sends + * @return integer number of successful sends. */ - public function sendMultiple(array $messages) { + public function sendMultiple(array $messages) + { $successCount = 0; foreach ($messages as $message) { if ($this->send($message)) { diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index f48310c..b573b16 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -39,17 +39,7 @@ abstract class BaseMessage extends Object implements MessageInterface */ public function getMailer() { - return Yii::$app->getComponent('email'); - } - - /** - * Initializes the object. - * This method is invoked at the end of the constructor after the object is initialized with the - * given configuration. - */ - public function init() - { - Yii::configure($this, $this->getMailer()->messageConfig); + return Yii::$app->getComponent('mail'); } /** diff --git a/framework/yii/mail/MailerInterface.php b/framework/yii/mail/MailerInterface.php new file mode 100644 index 0000000..60e231c --- /dev/null +++ b/framework/yii/mail/MailerInterface.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +interface MailerInterface +{ + /** + * Creates new message instance from given configuration. + * @param array $config message configuration. + * @return MessageInterface message instance. + */ + public function createMessage(array $config = []); + + /** + * Sends the given email message. + * @param object $message email message instance + * @return boolean whether the message has been sent. + */ + public function send($message); + + /** + * Sends a couple of messages at once. + * Note: some particular mailers may benefit from sending messages as batch, + * saving resources, for example on open/close connection operations. + * @param array $messages list of email messages, which should be sent. + * @return integer number of successful sends. + */ + public function sendMultiple(array $messages); +} \ No newline at end of file diff --git a/framework/yii/mail/Message.php b/framework/yii/mail/Message.php deleted file mode 100644 index fb34e24..0000000 --- a/framework/yii/mail/Message.php +++ /dev/null @@ -1,45 +0,0 @@ -from = 'sender@domain.com'; - * $email->to = 'receiver@domain.com'; - * $email->subject = 'Message Subject'; - * $email->text = 'Message Content'; - * $email->send(); - * ~~~ - * - * You can use message object to render view, which can be used to compose the message content: - * ~~~ - * $email = new Message(); - * $email->from = $contactForm->email; - * $email->to = 'admin@domain.com'; - * $email->subject = $email->render('contact/subject', ['form' => $contactForm]); - * $email->addHtml($email->render('contact/html', ['form' => $contactForm])); - * $email->addText($email->render('contact/text', ['form' => $contactForm])); - * $email->send(); - * ~~~ - * - * This particular class uses 'SwiftMailer' library to perform the message sending. - * Note: you can replace usage of this class by your own one, using [[Yii::$classMap]]: - * ~~~ - * Yii::$classMap['yii\mail\Message'] = '/path/to/my/email/Message.php' - * ~~~ - * - * @author Paul Klimov - * @since 2.0 - */ -class Message extends SwiftMessage {} \ No newline at end of file diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index 48a4f10..cdf2d3d 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -8,7 +8,7 @@ namespace yii\mail; /** - * Class MessageInterface + * MessageInterface is an interface, which email message should apply. * * @author Paul Klimov * @since 2.0 diff --git a/tests/unit/extensions/swiftmailer/MailerTest.php b/tests/unit/extensions/swiftmailer/MailerTest.php index 14c3719..306e128 100644 --- a/tests/unit/extensions/swiftmailer/MailerTest.php +++ b/tests/unit/extensions/swiftmailer/MailerTest.php @@ -9,7 +9,7 @@ use yiiunit\VendorTestCase; /** * @group vendor - * @group email + * @group mail * @group swiftmailer */ class MailerTest extends VendorTestCase @@ -66,11 +66,4 @@ class MailerTest extends VendorTestCase $mailer = new Mailer(); $this->assertTrue(is_object($mailer->getSwiftMailer()), 'Unable to get Swift mailer instance!'); } - - public function testCreateSwiftMessage() - { - $mailer = new Mailer(); - $message = $mailer->createSwiftMessage(); - $this->assertTrue(is_object($message), 'Unable to create Swift message instance!'); - } } \ No newline at end of file diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php index dabe831..d55ac3a 100644 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -9,7 +9,7 @@ use yiiunit\VendorTestCase; /** * @group vendor - * @group email + * @group mail * @group swiftmailer */ class MessageTest extends VendorTestCase @@ -23,7 +23,7 @@ class MessageTest extends VendorTestCase { $this->mockApplication([ 'components' => [ - 'email' => $this->createTestEmailComponent() + 'mail' => $this->createTestEmailComponent() ] ]); } @@ -37,6 +37,14 @@ class MessageTest extends VendorTestCase return $component; } + /** + * @return Message test message instance. + */ + protected function createTestMessage() + { + return Yii::$app->getComponent('mail')->createMessage(); + } + // Tests : public function testGetSwiftMessage() @@ -50,7 +58,7 @@ class MessageTest extends VendorTestCase */ public function testSend() { - $message = new Message(); + $message = $this->createTestMessage(); $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Test'); @@ -63,7 +71,7 @@ class MessageTest extends VendorTestCase */ public function testAttachFile() { - $message = new Message(); + $message = $this->createTestMessage(); $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Attach File Test'); @@ -77,7 +85,7 @@ class MessageTest extends VendorTestCase */ public function testCreateAttachment() { - $message = new Message(); + $message = $this->createTestMessage(); $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Create Attachment Test'); @@ -91,7 +99,7 @@ class MessageTest extends VendorTestCase */ public function testSendAlternativeBody() { - $message = new Message(); + $message = $this->createTestMessage(); $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Alternative Body Test'); diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index 84bb293..ff37da2 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -11,14 +11,17 @@ use yii\helpers\FileHelper; use yiiunit\TestCase; /** - * @group email + * @group mail */ class BaseMailerTest extends TestCase { public function setUp() { - $this->mockApplication(); - Yii::$app->setComponent('email', $this->createTestEmailComponent()); + $this->mockApplication([ + 'components' => [ + 'mail' => $this->createTestEmailComponent() + ] + ]); $filePath = $this->getTestFilePath(); if (!file_exists($filePath)) { FileHelper::createDirectory($filePath); @@ -109,15 +112,38 @@ class BaseMailerTest extends TestCase $this->assertTrue(is_object($viewResolver), 'Unable to get default view resolver!'); } + public function testCreateMessage() + { + $mailer = new Mailer(); + $message = $mailer->createMessage(); + $this->assertTrue(is_object($message), 'Unable to create message instance!'); + $this->assertEquals($mailer->messageClass, get_class($message), 'Invalid message class!'); + + $messageConfig = array( + 'id' => 'test-id', + 'encoding' => 'test-encoding', + ); + $message = $mailer->createMessage($messageConfig); + + foreach ($messageConfig as $name => $value) { + $this->assertEquals($value, $message->$name, 'Unable to apply message config!'); + } + } + + /** + * @depends testCreateMessage + */ public function testDefaultMessageConfig() { + $mailer = new Mailer(); + $messageConfig = array( 'id' => 'test-id', 'encoding' => 'test-encoding', ); - Yii::$app->getComponent('email')->messageConfig = $messageConfig; + $mailer->messageConfig = $messageConfig; - $message = new Message(); + $message = $mailer->createMessage(); foreach ($messageConfig as $name => $value) { $this->assertEquals($value, $message->$name); @@ -153,6 +179,7 @@ class BaseMailerTest extends TestCase */ class Mailer extends BaseMailer { + public $messageClass = 'yiiunit\framework\mail\Message'; public $sentMessages = array(); public function send($message) diff --git a/tests/unit/framework/mail/ViewResolverTest.php b/tests/unit/framework/mail/ViewResolverTest.php index 4ff4dbd..5caa947 100644 --- a/tests/unit/framework/mail/ViewResolverTest.php +++ b/tests/unit/framework/mail/ViewResolverTest.php @@ -7,7 +7,7 @@ use Yii; use yiiunit\TestCase; /** - * @group email + * @group mail */ class ViewResolverTest extends TestCase { From 35429fbd440887bb0ddd6f05b65ba88e0f7d25f7 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Tue, 29 Oct 2013 12:34:08 +0200 Subject: [PATCH 011/203] 'yii\mail\ViewResolve' removed. Interface 'ViewContextInterface' applied to BaseMailer. --- framework/yii/mail/BaseMailer.php | 55 +++++++---------------- framework/yii/mail/ViewResolver.php | 57 ------------------------ tests/unit/framework/mail/BaseMailerTest.php | 30 +------------ tests/unit/framework/mail/ViewResolverTest.php | 61 -------------------------- 4 files changed, 16 insertions(+), 187 deletions(-) delete mode 100644 framework/yii/mail/ViewResolver.php delete mode 100644 tests/unit/framework/mail/ViewResolverTest.php diff --git a/framework/yii/mail/BaseMailer.php b/framework/yii/mail/BaseMailer.php index ea7690b..e5a1dde 100644 --- a/framework/yii/mail/BaseMailer.php +++ b/framework/yii/mail/BaseMailer.php @@ -10,6 +10,7 @@ namespace yii\mail; use yii\base\Component; use yii\base\InvalidConfigException; use Yii; +use yii\base\ViewContextInterface; /** * BaseMailer provides the basic interface for the email mailer application component. @@ -24,16 +25,16 @@ use Yii; * @author Paul Klimov * @since 2.0 */ -abstract class BaseMailer extends Component implements MailerInterface +abstract class BaseMailer extends Component implements MailerInterface, ViewContextInterface { /** * @var \yii\base\View|array view instance or its array configuration. */ private $_view = []; /** - * @var \yii\mail\ViewResolver|array view resolver instance or its array configuration. + * @var string directory containing view files for this email messages. */ - private $_viewResolver = []; + public $viewPath = '@app/mailviews'; /** * @var array configuration, which should be applied by default to any new created * email message instance. @@ -76,29 +77,6 @@ abstract class BaseMailer extends Component implements MailerInterface } /** - * @param array|\yii\mail\ViewResolver $viewResolver view resolver instance or its array configuration. - * @throws \yii\base\InvalidConfigException on invalid argument. - */ - public function setViewResolver($viewResolver) - { - if (!is_array($viewResolver) && !is_object($viewResolver)) { - throw new InvalidConfigException('"' . get_class($this) . '::viewResolver" should be either object or array, "' . gettype($viewResolver) . '" given.'); - } - $this->_viewResolver = $viewResolver; - } - - /** - * @return \yii\mail\ViewResolver view resolver. - */ - public function getViewResolver() - { - if (!is_object($this->_viewResolver)) { - $this->_viewResolver = $this->createViewResolver($this->_viewResolver); - } - return $this->_viewResolver; - } - - /** * Creates view instance from given configuration. * @param array $config view configuration. * @return \yii\base\View view instance. @@ -112,19 +90,6 @@ abstract class BaseMailer extends Component implements MailerInterface } /** - * Creates view resolver instance from given configuration. - * @param array $config view resolver configuration. - * @return \yii\mail\ViewResolver view resolver instance. - */ - protected function createViewResolver(array $config) - { - if (!array_key_exists('class', $config)) { - $config['class'] = '\yii\mail\ViewResolver'; - } - return Yii::createObject($config); - } - - /** * Creates new message instance from given configuration. * Message configuration will be merged with [[messageConfig]]. * If 'class' parameter is omitted [[messageClass]], will be used. @@ -167,6 +132,16 @@ abstract class BaseMailer extends Component implements MailerInterface */ public function render($view, $params = []) { - return $this->getView()->renderFile($this->getViewResolver()->findViewFile($view), $params, $this); + return $this->getView()->render($view, $params, $this); + } + + /** + * Finds the view file corresponding to the specified relative view name. + * @param string $view a relative view name. The name does NOT start with a slash. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view) + { + return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; } } \ No newline at end of file diff --git a/framework/yii/mail/ViewResolver.php b/framework/yii/mail/ViewResolver.php deleted file mode 100644 index 31f57cb..0000000 --- a/framework/yii/mail/ViewResolver.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @since 2.0 - */ -class ViewResolver extends Component -{ - /** - * @var string directory containing view files for this email messages. - */ - public $viewPath = '@app/emails'; - - /** - * Finds the view file based on the given view name. - * The view to be rendered can be specified in one of the following formats: - * - path alias (e.g. "@app/emails/contact/body"); - * - relative path (e.g. "contact"): the actual view file will be resolved by [[resolveView]]. - * @param string $view the view name or the path alias of the view file. - * @return string the view file path. Note that the file may not exist. - */ - public function findViewFile($view) - { - if (strncmp($view, '@', 1) === 0) { - // e.g. "@app/views/main" - $file = Yii::getAlias($view); - } else { - $file = $this->resolveView($view); - } - return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file; - } - - /** - * Composes file name for the view name, appending view name to [[viewPath]]. - * Child classes may override this method to provide more sophisticated - * search of the view files or even composition of the view files "on the fly". - * @param string $view the view name. - * @return string the view file path. - */ - protected function resolveView($view) - { - return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view; - } -} \ No newline at end of file diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index ff37da2..cded1e4 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -85,33 +85,6 @@ class BaseMailerTest extends TestCase $this->assertTrue(is_object($view), 'Unable to get default view!'); } - public function testSetupViewResolver() - { - $mailer = new Mailer(); - - $viewResolver = new ViewResolver(); - $mailer->setViewResolver($viewResolver); - $this->assertEquals($viewResolver, $mailer->getViewResolver(), 'Unable to setup view resolver!'); - - $viewResolverConfig = [ - 'viewPath' => '/test/view/path', - ]; - $mailer->setViewResolver($viewResolverConfig); - $viewResolver = $mailer->getViewResolver(); - $this->assertTrue(is_object($viewResolver), 'Unable to setup view resolver via config!'); - $this->assertEquals($viewResolverConfig['viewPath'], $viewResolver->viewPath, 'Unable to configure view resolver via config array!'); - } - - /** - * @depends testSetupViewResolver - */ - public function testGetDefaultViewResolver() - { - $mailer = new Mailer(); - $viewResolver = $mailer->getViewResolver(); - $this->assertTrue(is_object($viewResolver), 'Unable to get default view resolver!'); - } - public function testCreateMessage() { $mailer = new Mailer(); @@ -152,14 +125,13 @@ class BaseMailerTest extends TestCase /** * @depends testGetDefaultView - * @depends testGetDefaultViewResolver */ public function testRender() { $mailer = new Mailer(); $filePath = $this->getTestFilePath(); - $mailer->getViewResolver()->viewPath = $filePath; + $mailer->viewPath = $filePath; $viewName = 'test_view'; $fileName = $filePath . DIRECTORY_SEPARATOR . $viewName . '.php'; diff --git a/tests/unit/framework/mail/ViewResolverTest.php b/tests/unit/framework/mail/ViewResolverTest.php deleted file mode 100644 index 5caa947..0000000 --- a/tests/unit/framework/mail/ViewResolverTest.php +++ /dev/null @@ -1,61 +0,0 @@ -testViewPath); - return [ - [ - $alias . '/test', - $aliasPath . '/test.php', - ], - [ - $alias . '/test.tpl', - $aliasPath . '/test.tpl', - ], - [ - 'contact/html', - $viewPath . '/contact/html.php', - ], - [ - 'contact/html.tpl', - $viewPath . '/contact/html.tpl', - ], - ]; - } - - /** - * @dataProvider dataProviderFindViewFile - * - * @param string $view - * @param string $expectedFileName - */ - public function testFindViewFile($view, $expectedFileName) - { - $viewResolver = new ViewResolver(); - $viewResolver->viewPath = $this->testViewPath; - $fileName = $viewResolver->findViewFile($view); - $this->assertEquals($expectedFileName, $fileName); - } -} \ No newline at end of file From 774c4db860d6b2a4327dbc8502ff78892b248d19 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Tue, 29 Oct 2013 14:06:16 +0200 Subject: [PATCH 012/203] 'yii\mail\MessageInterface' file related methods interface adjusted to use options, embed file methods added. --- extensions/swiftmailer/yii/swiftmailer/Message.php | 56 +++++++++++++-- framework/yii/mail/BaseMessage.php | 18 ----- framework/yii/mail/MessageInterface.php | 36 ++++++++-- tests/unit/extensions/swiftmailer/MessageTest.php | 83 +++++++++++++++++++++- tests/unit/framework/mail/BaseMailerTest.php | 8 ++- 5 files changed, 170 insertions(+), 31 deletions(-) diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index 7fc908b..808fbf8 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -155,18 +155,66 @@ class Message extends BaseMessage /** * @inheritdoc */ - public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream') + public function attachFile($fileName, array $options = []) { - if (empty($contentType)) { - $contentType = 'application/octet-stream'; + $attachment = \Swift_Attachment::fromPath($fileName); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); } - $attachment = \Swift_Attachment::newInstance($content, $fileName, $contentType); $this->getSwiftMessage()->attach($attachment); } /** * @inheritdoc */ + public function attachContent($content, array $options = []) + { + $attachment = \Swift_Attachment::newInstance($content); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); + } + $this->getSwiftMessage()->attach($attachment); + } + + /** + * @inheritdoc + */ + public function embedFile($fileName, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::fromPath($fileName); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ + public function embedContent($content, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::newInstance($content); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ public function __toString() { return $this->getSwiftMessage()->toString(); diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index b573b16..8c4daa8 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -53,24 +53,6 @@ abstract class BaseMessage extends Object implements MessageInterface /** * @inheritdoc */ - public function attachFile($fileName, $contentType = null, $attachFileName = null) - { - if (!file_exists($fileName)) { - throw new InvalidParamException('Unable to attach file "' . $fileName . '": file does not exists!'); - } - if (empty($contentType)) { - $contentType = FileHelper::getMimeType($fileName); - } - if (empty($attachFileName)) { - $attachFileName = basename($fileName); - } - $content = file_get_contents($fileName); - $this->attachContentAsFile($content, $attachFileName, $contentType); - } - - /** - * @inheritdoc - */ public function render($view, $params = []) { return $this->getMailer()->render($view, $params); diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index cdf2d3d..0f3e5a4 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -84,18 +84,42 @@ interface MessageInterface /** * Attach specified content as file for the email message. * @param string $content attachment file content. - * @param string $fileName attachment file name. - * @param string $contentType MIME type of the attachment file, by default 'application/octet-stream' will be used. + * @param array $options options for embed file. Valid options are: + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. */ - public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream'); + public function attachContent($content, array $options = []); /** * Attaches existing file to the email message. * @param string $fileName full file name - * @param string $contentType MIME type of the attachment file, if empty it will be suggested automatically. - * @param string $attachFileName name, which should be used for attachment, if empty file base name will be used. + * @param array $options options for embed file. Valid options are: + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. */ - public function attachFile($fileName, $contentType = null, $attachFileName = null); + public function attachFile($fileName, array $options = []); + + /** + * Attach a file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $fileName file name. + * @param array $options options for embed file. Valid options are: + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * @return string attachment CID. + */ + public function embedFile($fileName, array $options = []); + + /** + * Attach a content as file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $content attachment file content. + * @param array $options options for embed file. Valid options are: + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * @return string attachment CID. + */ + public function embedContent($content, array $options = []); /** * Sends this email message. diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php index d55ac3a..0a02b6d 100644 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -3,6 +3,7 @@ namespace yiiunit\extensions\swiftmailer; use Yii; +use yii\helpers\FileHelper; use yii\swiftmailer\Mailer; use yii\swiftmailer\Message; use yiiunit\VendorTestCase; @@ -26,6 +27,26 @@ class MessageTest extends VendorTestCase 'mail' => $this->createTestEmailComponent() ] ]); + $filePath = $this->getTestFilePath(); + if (!file_exists($filePath)) { + FileHelper::createDirectory($filePath); + } + } + + public function tearDown() + { + $filePath = $this->getTestFilePath(); + if (file_exists($filePath)) { + FileHelper::removeDirectory($filePath); + } + } + + /** + * @return string test file path. + */ + protected function getTestFilePath() + { + return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); } /** @@ -45,6 +66,26 @@ class MessageTest extends VendorTestCase return Yii::$app->getComponent('mail')->createMessage(); } + /** + * Creates image file with given text. + * @param string $fileName file name. + * @param string $text text to be applied on image. + * @return string image file full name. + */ + protected function createImageFile($fileName = 'test.jpg', $text = 'Test Image') + { + if (!function_exists('imagecreatetruecolor')) { + $this->markTestSkipped('GD lib required.'); + } + $fileFullName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . $fileName; + $image = imagecreatetruecolor(120, 20); + $textColor = imagecolorallocate($image, 233, 14, 91); + imagestring($image, 1, 5, 5, $text, $textColor); + imagejpeg($image, $fileFullName); + imagedestroy($image); + return $fileFullName; + } + // Tests : public function testGetSwiftMessage() @@ -83,14 +124,52 @@ class MessageTest extends VendorTestCase /** * @depends testSend */ - public function testCreateAttachment() + public function testAttachContent() { $message = $this->createTestMessage(); $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Create Attachment Test'); $message->setText('Yii Swift Create Attachment Test body'); - $message->attachContentAsFile('Test attachment content', 'test.txt'); + $message->attachContent('Test attachment content', ['fileName' => 'test.txt']); + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testEmbedFile() + { + $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + + $message = $this->createTestMessage(); + + $cid = $message->embedFile($fileName); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Embed File Test'); + $message->setHtml('Embed image: pic'); + + $this->assertTrue($message->send()); + } + + /** + * @depends testSend + */ + public function testEmbedContent() + { + $fileName = $this->createImageFile('embed_file.jpg', 'Embed Image File'); + + $message = $this->createTestMessage(); + + $cid = $message->embedContent(file_get_contents($fileName), ['contentType' => 'image/jpeg']); + + $message->setTo($this->testEmailReceiver); + $message->setFrom('someuser@somedomain.com'); + $message->setSubject('Yii Swift Embed File Test'); + $message->setHtml('Embed image: pic'); + $this->assertTrue($message->send()); } diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index cded1e4..f135f51 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -186,7 +186,13 @@ class Message extends BaseMessage public function addHtml($html) {} - public function attachContentAsFile($content, $fileName, $contentType = 'application/octet-stream') {} + public function attachContent($content, array $options = []) {} + + public function attachFile($fileName, array $options = []) {} + + public function embedFile($fileName, array $options = []) {} + + public function embedContent($content, array $options = []) {} public function __toString() { From 0e3d48f2884c175d435524350c314dc1dc8c4935 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 13:10:51 +0100 Subject: [PATCH 013/203] skip expiry test for memcache on travis fixes #877 --- tests/unit/framework/caching/MemCacheTest.php | 8 ++++++++ tests/unit/framework/caching/MemCachedTest.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 32374b5..858196e 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -26,4 +26,12 @@ class MemCacheTest extends CacheTestCase } return $this->_cacheInstance; } + + public function testExpire() + { + if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { + $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); + } + parent::testExpire(); + } } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index 807cef1..1536ac9 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -26,4 +26,12 @@ class MemCachedTest extends CacheTestCase } return $this->_cacheInstance; } + + public function testExpire() + { + if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { + $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); + } + parent::testExpire(); + } } From a75c07be227ef50d9a9569a361021e22283f3241 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 13:35:15 +0100 Subject: [PATCH 014/203] print travis ENV --- tests/unit/framework/caching/MemCacheTest.php | 1 + tests/unit/framework/caching/MemCachedTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 858196e..96f64e4 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -29,6 +29,7 @@ class MemCacheTest extends CacheTestCase public function testExpire() { + print_r($_ENV); if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index 1536ac9..ca7baea 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -29,6 +29,7 @@ class MemCachedTest extends CacheTestCase public function testExpire() { + print_r($_ENV); if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); } From f5c25e0858014445303ee1ffba05d8efbff7d9c3 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 13:49:00 +0100 Subject: [PATCH 015/203] travis memcache env check: try again --- tests/unit/framework/caching/MemCacheTest.php | 4 ++-- tests/unit/framework/caching/MemCachedTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 96f64e4..47978e3 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -29,8 +29,8 @@ class MemCacheTest extends CacheTestCase public function testExpire() { - print_r($_ENV); - if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { + echo getenv('TRAVIS'); + if (($env = getenv('TRAVIS')) !== false && $env == 'true') { $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); } parent::testExpire(); diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index ca7baea..b9a1163 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -29,8 +29,8 @@ class MemCachedTest extends CacheTestCase public function testExpire() { - print_r($_ENV); - if (isset($_ENV['TRAVIS']) && $_ENV['TRAVIS']) { + echo getenv('TRAVIS'); + if (($env = getenv('TRAVIS')) !== false && $env == 'true') { $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); } parent::testExpire(); From 15a9b04fccc373ce0ff78c154a09ce8077c69677 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 13:54:55 +0100 Subject: [PATCH 016/203] simplified travis env check for memcache(d) --- tests/unit/framework/caching/MemCacheTest.php | 3 +-- tests/unit/framework/caching/MemCachedTest.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 47978e3..e489a39 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -29,8 +29,7 @@ class MemCacheTest extends CacheTestCase public function testExpire() { - echo getenv('TRAVIS'); - if (($env = getenv('TRAVIS')) !== false && $env == 'true') { + if (getenv('TRAVIS') == 'true') { $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); } parent::testExpire(); diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index b9a1163..57ee110 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -29,8 +29,7 @@ class MemCachedTest extends CacheTestCase public function testExpire() { - echo getenv('TRAVIS'); - if (($env = getenv('TRAVIS')) !== false && $env == 'true') { + if (getenv('TRAVIS') == 'true') { $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); } parent::testExpire(); From 1d0891f0f6bea4e3f6fbc7d5e1878aefe1ac133e Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Tue, 29 Oct 2013 16:04:20 +0200 Subject: [PATCH 017/203] 'yii\mail\MessageInterface' methods 'addText()' and 'addHtml()' removed. --- extensions/swiftmailer/yii/swiftmailer/Message.php | 54 ++++++++++++++++------ framework/yii/mail/MessageInterface.php | 12 ----- tests/unit/extensions/swiftmailer/MessageTest.php | 4 +- tests/unit/framework/mail/BaseMailerTest.php | 4 -- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index 808fbf8..42d25e7 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -125,7 +125,7 @@ class Message extends BaseMessage */ public function setText($text) { - $this->getSwiftMessage()->setBody($text, 'text/plain'); + $this->setBody($text, 'text/plain'); } /** @@ -133,23 +133,51 @@ class Message extends BaseMessage */ public function setHtml($html) { - $this->getSwiftMessage()->setBody($html, 'text/html'); + $this->setBody($html, 'text/html'); } /** - * @inheritdoc + * Sets the message body. + * If body is already set and its content type matches given one, it will + * be overridden, if content type miss match the multipart message will be composed. + * @param string $body body content. + * @param string $contentType body content type. */ - public function addText($text) + protected function setBody($body, $contentType) { - $this->getSwiftMessage()->addPart($text, 'text/plain'); - } - - /** - * @inheritdoc - */ - public function addHtml($html) - { - $this->getSwiftMessage()->addPart($html, 'text/html'); + $message = $this->getSwiftMessage(); + $oldBody = $message->getBody(); + if (empty($oldBody)) { + $parts = $message->getChildren(); + $partFound = false; + foreach ($parts as $key => $part) { + if (!($part instanceof \Swift_Mime_Attachment)) { + /* @var $part \Swift_Mime_MimePart */ + if ($part->getContentType() == $contentType) { + unset($parts[$key]); + $partFound = true; + break; + } + } + } + if ($partFound) { + reset($parts); + $message->setChildren($parts); + $message->addPart($body, $contentType); + } else { + $message->setBody($body, $contentType); + } + } else { + $oldContentType = $message->getContentType(); + if ($oldContentType == $contentType) { + $message->setBody($body, $contentType); + } else { + $message->setBody(null); + $message->setContentType(null); + $message->addPart($oldBody, $oldContentType); + $message->addPart($body, $contentType); + } + } } /** diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index 0f3e5a4..e7de37b 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -70,18 +70,6 @@ interface MessageInterface public function setHtml($html); /** - * Add message plain text content part. - * @param string $text message plain text content. - */ - public function addText($text); - - /** - * Add message HTML content part. - * @param string $html message HTML content. - */ - public function addHtml($html); - - /** * Attach specified content as file for the email message. * @param string $content attachment file content. * @param array $options options for embed file. Valid options are: diff --git a/tests/unit/extensions/swiftmailer/MessageTest.php b/tests/unit/extensions/swiftmailer/MessageTest.php index 0a02b6d..2e45acb 100644 --- a/tests/unit/extensions/swiftmailer/MessageTest.php +++ b/tests/unit/extensions/swiftmailer/MessageTest.php @@ -182,8 +182,8 @@ class MessageTest extends VendorTestCase $message->setTo($this->testEmailReceiver); $message->setFrom('someuser@somedomain.com'); $message->setSubject('Yii Swift Alternative Body Test'); - $message->addHtml('Yii Swift test HTML body'); - $message->addText('Yii Swift test plain text body'); + $message->setHtml('Yii Swift test HTML body'); + $message->setText('Yii Swift test plain text body'); $this->assertTrue($message->send()); } } \ No newline at end of file diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index f135f51..ab70563 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -182,10 +182,6 @@ class Message extends BaseMessage public function setHtml($html) {} - public function addText($text) {} - - public function addHtml($html) {} - public function attachContent($content, array $options = []) {} public function attachFile($fileName, array $options = []) {} From 842971c44d9d9c867dcb6cb452804b24fbc3475b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 15:05:38 +0100 Subject: [PATCH 018/203] set error_reporting for tests --- tests/unit/bootstrap.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 0580db6..c63e002 100644 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -1,5 +1,8 @@ Date: Tue, 29 Oct 2013 16:11:57 +0200 Subject: [PATCH 019/203] Email message charset setup added. --- extensions/swiftmailer/yii/swiftmailer/Message.php | 20 ++++++++++++++++++-- framework/yii/mail/BaseMessage.php | 1 + framework/yii/mail/MessageInterface.php | 6 ++++++ tests/unit/framework/mail/BaseMailerTest.php | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/extensions/swiftmailer/yii/swiftmailer/Message.php b/extensions/swiftmailer/yii/swiftmailer/Message.php index 42d25e7..89a871d 100644 --- a/extensions/swiftmailer/yii/swiftmailer/Message.php +++ b/extensions/swiftmailer/yii/swiftmailer/Message.php @@ -42,6 +42,22 @@ class Message extends BaseMessage /** * @inheritdoc */ + public function setCharset($charset) + { + $this->getSwiftMessage()->setCharset($charset); + } + + /** + * @return string the character set of this message. + */ + public function getCharset() + { + return $this->getSwiftMessage()->getCharset(); + } + + /** + * @inheritdoc + */ public function setFrom($from) { $this->getSwiftMessage()->setFrom($from); @@ -232,8 +248,8 @@ class Message extends BaseMessage { $embedFile = \Swift_EmbeddedFile::newInstance($content); if (!empty($options['fileName'])) { - $embedFile->setFilename($options['fileName']); - } + $embedFile->setFilename($options['fileName']); + } if (!empty($options['contentType'])) { $embedFile->setContentType($options['contentType']); } diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index 8c4daa8..a641521 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -21,6 +21,7 @@ use Yii; * @see BaseMailer * * @property \yii\mail\BaseMailer $mailer mailer component instance. This property is read-only. + * @property string $charset the character set of this message. * @property string|array $from sender email address. * @property string|array $to receiver email address. * @property string|array $cc copy receiver email address. diff --git a/framework/yii/mail/MessageInterface.php b/framework/yii/mail/MessageInterface.php index e7de37b..171c4f3 100644 --- a/framework/yii/mail/MessageInterface.php +++ b/framework/yii/mail/MessageInterface.php @@ -16,6 +16,12 @@ namespace yii\mail; interface MessageInterface { /** + * Set the character set of this message. + * @param string $charset character set name. + */ + public function setCharset($charset); + + /** * Sets message sender. * @param string|array $from sender email address. * You may pass an array of addresses if this message is from multiple people. diff --git a/tests/unit/framework/mail/BaseMailerTest.php b/tests/unit/framework/mail/BaseMailerTest.php index ab70563..1781657 100644 --- a/tests/unit/framework/mail/BaseMailerTest.php +++ b/tests/unit/framework/mail/BaseMailerTest.php @@ -168,6 +168,8 @@ class Message extends BaseMessage public $id; public $encoding; + public function setCharset($charset) {} + public function setFrom($from) {} public function setTo($to) {} From ec490dcaa60481addcaca7889cc6cc49f7b956ad Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 17:14:34 +0100 Subject: [PATCH 020/203] made gridview plural rule compatible with ICU version < 4.8 issue #1072 --- framework/yii/widgets/BaseListView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index ffbba38..ba1f6d4 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -139,7 +139,7 @@ abstract class BaseListView extends Widget $pageCount = $pagination->pageCount; if (($summaryContent = $this->summary) === null) { $summaryContent = '
' - . Yii::t('yii', 'Showing {totalCount, plural, =0{0} other{{begin, number, integer}-{end, number, integer}}} of {totalCount, number, integer} {totalCount, plural, one{item} other{items}}.') + . Yii::t('yii', 'Showing {totalCount, plural, zero{0} other{{begin, number, integer}-{end, number, integer}}} of {totalCount, number, integer} {totalCount, plural, one{item} other{items}}.') . '
'; } } else { From 34945b0b69011bc7cab684c7f7095d837892a0d4 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 18:27:20 +0100 Subject: [PATCH 021/203] added unit test to verify storing of null values http://www.yiiframework.com/forum/index.php/topic/48359-inserting-nulls/page__view__findpost__p__226019 --- tests/unit/data/ar/NullValues.php | 20 +++++++++++ tests/unit/data/cubrid.sql | 11 ++++++ tests/unit/data/mssql.sql | 10 ++++++ tests/unit/data/mysql.sql | 10 ++++++ tests/unit/data/postgres.sql | 10 ++++++ tests/unit/data/sqlite.sql | 9 +++++ tests/unit/framework/db/ActiveRecordTest.php | 51 ++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+) create mode 100644 tests/unit/data/ar/NullValues.php diff --git a/tests/unit/data/ar/NullValues.php b/tests/unit/data/ar/NullValues.php new file mode 100644 index 0000000..e6aa3b9 --- /dev/null +++ b/tests/unit/data/ar/NullValues.php @@ -0,0 +1,20 @@ +all(); $this->assertEquals(0, count($customers)); } + + public function testStoreNull() + { + $record = new NullValues(); + $this->assertNull($record->var1); + $this->assertNull($record->var2); + $this->assertNull($record->var3); + $this->assertNull($record->stringcol); + + $record->id = 1; + + $record->var1 = 123; + $record->var2 = 456; + $record->var3 = 789; + $record->stringcol = 'hello!'; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertEquals(123, $record->var1); + $this->assertEquals(456, $record->var2); + $this->assertEquals(789, $record->var3); + $this->assertEquals('hello!', $record->stringcol); + + $record->var1 = null; + $record->var2 = null; + $record->var3 = null; + $record->stringcol = null; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertNull($record->var1); + $this->assertNull($record->var2); + $this->assertNull($record->var3); + $this->assertNull($record->stringcol); + + $record->var1 = 0; + $record->var2 = 0; + $record->var3 = 0; + $record->stringcol = ''; + + $record->save(false); + $this->assertTrue($record->refresh()); + + $this->assertEquals(0, $record->var1); + $this->assertEquals(0, $record->var2); + $this->assertEquals(0, $record->var3); + $this->assertEquals('', $record->stringcol); + } } From 06a91d62712632d0fa1d80e0e4e1a5043b3848cf Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 29 Oct 2013 19:10:47 +0100 Subject: [PATCH 022/203] fixed unit tests for cubrid and postgresql both do not support unsigned integers --- tests/unit/data/cubrid.sql | 4 ++-- tests/unit/data/postgres.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index a9d4815..905ebd2 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -63,8 +63,8 @@ CREATE TABLE `tbl_order_item` ( ); CREATE TABLE tbl_null_values ( - `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `var1` INT UNSIGNED NULL, + `id` INT(11) NOT NULL AUTO_INCREMENT, + `var1` INT NULL, `var2` INT NULL, `var3` INT DEFAULT NULL, `stringcol` VARCHAR (32) DEFAULT NULL, diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index f86d0d2..f9ee192 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -56,8 +56,8 @@ CREATE TABLE tbl_order_item ( ); CREATE TABLE tbl_null_values ( - id INT UNSIGNED AUTOINCREMENT NOT NULL, - var1 INT UNSIGNED NULL, + id INT NOT NULL, + var1 INT NULL, var2 INT NULL, var3 INT DEFAULT NULL, stringcol VARCHAR(32) DEFAULT NULL, From 0ee120f5f939a4ad8b0e5b332b6818a078666ce4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 29 Oct 2013 23:17:50 -0400 Subject: [PATCH 023/203] refactored Component::off(). --- framework/yii/base/Component.php | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 9d5258a..1d26dd7 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -406,24 +406,25 @@ class Component extends Object public function off($name, $handler = null) { $this->ensureBehaviors(); - if (isset($this->_events[$name])) { - if ($handler === null) { - $this->_events[$name] = []; - } else { - $removed = false; - foreach ($this->_events[$name] as $i => $event) { - if ($event[0] === $handler) { - unset($this->_events[$name][$i]); - $removed = true; - } - } - if ($removed) { - $this->_events[$name] = array_values($this->_events[$name]); + if (empty($this->_events[$name])) { + return false; + } + if ($handler === null) { + unset($this->_events[$name]); + return true; + } else { + $removed = false; + foreach ($this->_events[$name] as $i => $event) { + if ($event[0] === $handler) { + unset($this->_events[$name][$i]); + $removed = true; } - return $removed; } + if ($removed) { + $this->_events[$name] = array_values($this->_events[$name]); + } + return $removed; } - return false; } /** From 8b00693a0a4e13ea07623f7a96f57d68bffbe416 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 30 Oct 2013 00:12:48 -0400 Subject: [PATCH 024/203] Fixes #1025: Implemented support for class-level events. --- framework/yii/base/Component.php | 7 +- framework/yii/base/Event.php | 132 ++++++++++++++++++++++++++++ tests/unit/framework/base/ComponentTest.php | 1 - tests/unit/framework/base/EventTest.php | 93 ++++++++++++++++++++ 4 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 tests/unit/framework/base/EventTest.php diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 1d26dd7..b67a09f 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -358,13 +358,13 @@ class Component extends Object public function hasEventHandlers($name) { $this->ensureBehaviors(); - return !empty($this->_events[$name]); + return !empty($this->_events[$name]) || Event::hasHandlers($this, $name); } /** * 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: * * ~~~ @@ -374,7 +374,7 @@ class Component extends Object * '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) @@ -455,6 +455,7 @@ class Component extends Object } } } + Event::trigger($this, $name, $event); } /** diff --git a/framework/yii/base/Event.php b/framework/yii/base/Event.php index 5d40736..97ef905 100644 --- a/framework/yii/base/Event.php +++ b/framework/yii/base/Event.php @@ -45,4 +45,136 @@ class Event extends Object * Note that this varies according to which event handler is currently executing. */ 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); + } } diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php index d1698eb..2cad56d 100644 --- a/tests/unit/framework/base/ComponentTest.php +++ b/tests/unit/framework/base/ComponentTest.php @@ -302,7 +302,6 @@ class ComponentTest extends TestCase $component->detachBehaviors(); $this->assertNull($component->getBehavior('a')); $this->assertNull($component->getBehavior('b')); - } } diff --git a/tests/unit/framework/base/EventTest.php b/tests/unit/framework/base/EventTest.php new file mode 100644 index 0000000..6226793 --- /dev/null +++ b/tests/unit/framework/base/EventTest.php @@ -0,0 +1,93 @@ + + * @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 +{ + +} From 2880fb2899647de06bc0d0105cbd6792b5cc460f Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Wed, 30 Oct 2013 01:38:48 -0300 Subject: [PATCH 025/203] Typo [skip ci] --- framework/yii/base/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/base/Event.php b/framework/yii/base/Event.php index 97ef905..02d12b5 100644 --- a/framework/yii/base/Event.php +++ b/framework/yii/base/Event.php @@ -58,7 +58,7 @@ class Event extends Object * `afterInsert` event: * * ~~~ - * Event::on([ActiveRecord::className, ActiveRecord::EVENT_AFTER_INSERT], function ($event) { + * Event::on([ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT], function ($event) { * Yii::trace(get_class($event->sender) . ' is inserted.'); * }); * ~~~ From 3920301c2b9843e2fa91a8e726ec913d5f8e4416 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 30 Oct 2013 12:14:19 +0400 Subject: [PATCH 026/203] Added note about "Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATCH" ICU error to i18n docs --- docs/guide/i18n.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index de1c80d..33f7758 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -232,6 +232,13 @@ for Russian: 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 [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). From 474d4aeaafaa42c854b6e8de10abd84c00cfa570 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 30 Oct 2013 12:44:54 +0400 Subject: [PATCH 027/203] Added info about class-level event handlers to doc --- docs/guide/upgrade-from-v1.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index 628cf54..4ccd726 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -106,6 +106,15 @@ Yii::$app->on($eventName, $handler); 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 ---------- From 66fd16e8a6c9b0b99cda81bbd23c506916b59c69 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 30 Oct 2013 10:17:26 +0100 Subject: [PATCH 028/203] fixed gridview message pattern --- framework/yii/widgets/BaseListView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index ba1f6d4..2ed27a2 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -146,7 +146,7 @@ abstract class BaseListView extends Widget $begin = $page = $pageCount = 1; $end = $totalCount = $count; if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total {count} {count, plural, one{item} other{items}}.') . '
'; + $summaryContent = '
' . Yii::t('yii', 'Total {count, number, integer} {count, plural, one{item} other{items}}.') . '
'; } } return Yii::$app->getI18n()->format($summaryContent, [ From 6559b06ead48f58f89e23d1e958945391e687744 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 30 Oct 2013 11:20:03 +0100 Subject: [PATCH 029/203] made intl messages more compatible with various ICU versions issue #1072 --- framework/yii/i18n/MessageFormatter.php | 2 +- framework/yii/widgets/BaseListView.php | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php index 7abff83..f966344 100644 --- a/framework/yii/i18n/MessageFormatter.php +++ b/framework/yii/i18n/MessageFormatter.php @@ -92,7 +92,7 @@ class MessageFormatter extends Component 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); $params = array_values($params); } diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php index 2ed27a2..310201a 100644 --- a/framework/yii/widgets/BaseListView.php +++ b/framework/yii/widgets/BaseListView.php @@ -135,18 +135,21 @@ abstract class BaseListView extends Widget $totalCount = $this->dataProvider->getTotalCount(); $begin = $pagination->getPage() * $pagination->pageSize + 1; $end = $begin + $count - 1; + if ($begin > $end) { + $begin = $end; + } $page = $pagination->getPage() + 1; $pageCount = $pagination->pageCount; if (($summaryContent = $this->summary) === null) { $summaryContent = '
' - . Yii::t('yii', 'Showing {totalCount, plural, zero{0} other{{begin, number, integer}-{end, number, integer}}} of {totalCount, number, integer} {totalCount, plural, one{item} other{items}}.') + . Yii::t('yii', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.') . '
'; } } else { $begin = $page = $pageCount = 1; $end = $totalCount = $count; if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total {count, number, integer} {count, plural, one{item} other{items}}.') . '
'; + $summaryContent = '
' . Yii::t('yii', 'Total {count, number} {count, plural, one{item} other{items}}.') . '
'; } } return Yii::$app->getI18n()->format($summaryContent, [ From 50ba760277dd984b7bb93c0f2d03ee2b4a853639 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 30 Oct 2013 10:33:02 -0400 Subject: [PATCH 030/203] Fixes #1106 --- framework/yii/gii/generators/crud/templates/views/_search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php index ff9a0dd..03e21f2 100644 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -34,7 +34,7 @@ foreach ($generator->getTableSchema()->getColumnNames() as $attribute) { if (++$count < 6) { echo "\t\tgenerateActiveSearchField($attribute) . " ?>\n\n"; } else { - echo "\t\tgenerateActiveSearchField($attribute) . " ?>\n\n"; + echo "\t\tgenerateActiveSearchField($attribute) . " ?>\n\n"; } } ?> From 4cb1a26477fab1a3c8ecc3f0a7ce808556f9b8c0 Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 30 Oct 2013 21:56:02 +0600 Subject: [PATCH 031/203] Fixes #1104. Model generator table names auto complete. --- framework/yii/gii/Generator.php | 11 + framework/yii/gii/GiiAsset.php | 2 + framework/yii/gii/assets/main.css | 8 + framework/yii/gii/assets/typeahead.js | 1139 ++++++++++++++++++++ .../yii/gii/assets/typeahead.js-bootstrap.css | 51 + framework/yii/gii/components/ActiveField.php | 24 +- framework/yii/gii/generators/model/Generator.php | 12 + 7 files changed, 1246 insertions(+), 1 deletion(-) create mode 100644 framework/yii/gii/assets/typeahead.js create mode 100644 framework/yii/gii/assets/typeahead.js-bootstrap.css diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php index 5c74567..83cdf6e 100644 --- a/framework/yii/gii/Generator.php +++ b/framework/yii/gii/Generator.php @@ -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. * 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. diff --git a/framework/yii/gii/GiiAsset.php b/framework/yii/gii/GiiAsset.php index 26b6412..b100750 100644 --- a/framework/yii/gii/GiiAsset.php +++ b/framework/yii/gii/GiiAsset.php @@ -26,12 +26,14 @@ class GiiAsset extends AssetBundle */ public $css = [ 'main.css', + 'typeahead.js-bootstrap.css', ]; /** * @inheritdoc */ public $js = [ 'gii.js', + 'typeahead.js', ]; /** * @inheritdoc diff --git a/framework/yii/gii/assets/main.css b/framework/yii/gii/assets/main.css index 8efc56c..1a4f794 100644 --- a/framework/yii/gii/assets/main.css +++ b/framework/yii/gii/assets/main.css @@ -201,3 +201,11 @@ body { .DifferencesInline .ChangeReplace del { background: #e99; } + +/* additional styles for typeahead.js-bootstrap.css */ +.twitter-typeahead { + display: block !important; +} +.twitter-typeahead .tt-hint { + padding: 6px 12px !important; +} diff --git a/framework/yii/gii/assets/typeahead.js b/framework/yii/gii/assets/typeahead.js new file mode 100644 index 0000000..9365bd6 --- /dev/null +++ b/framework/yii/gii/assets/typeahead.js @@ -0,0 +1,1139 @@ +/*! + * typeahead.js 0.9.3 + * https://github.com/twitter/typeahead + * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function($) { + var VERSION = "0.9.3"; + var utils = { + isMsie: function() { + var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); + return match ? parseInt(match[2], 10) : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + bindAll: function(obj) { + var val; + for (var key in obj) { + $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); + } + }, + indexOf: function(haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return -1; + }, + each: $.each, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + tokenizeQuery: function(str) { + return $.trim(str).toLowerCase().split(/[\s]+/); + }, + tokenizeText: function(str) { + return $.trim(str).toLowerCase().split(/[\s\-_]+/); + }, + getProtocol: function() { + return location.protocol; + }, + noop: function() {} + }; + var EventTarget = function() { + var eventSplitter = /\s+/; + return { + on: function(events, callback) { + var event; + if (!callback) { + return this; + } + this._callbacks = this._callbacks || {}; + events = events.split(eventSplitter); + while (event = events.shift()) { + this._callbacks[event] = this._callbacks[event] || []; + this._callbacks[event].push(callback); + } + return this; + }, + trigger: function(events, data) { + var event, callbacks; + if (!this._callbacks) { + return this; + } + events = events.split(eventSplitter); + while (event = events.shift()) { + if (callbacks = this._callbacks[event]) { + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i].call(this, { + type: event, + data: data + }); + } + } + } + return this; + } + }; + }(); + var EventBus = function() { + var namespace = "typeahead:"; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + utils.mixin(EventBus.prototype, { + trigger: function(type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (utils.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return utils.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: utils.noop, + set: utils.noop, + remove: utils.noop, + clear: utils.noop, + isExpired: utils.noop + }; + } + utils.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(utils.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } + }(); + var RequestCache = function() { + function RequestCache(o) { + utils.bindAll(this); + o = o || {}; + this.sizeLimit = o.sizeLimit || 10; + this.cache = {}; + this.cachedKeysByAge = []; + } + utils.mixin(RequestCache.prototype, { + get: function(url) { + return this.cache[url]; + }, + set: function(url, resp) { + var requestToEvict; + if (this.cachedKeysByAge.length === this.sizeLimit) { + requestToEvict = this.cachedKeysByAge.shift(); + delete this.cache[requestToEvict]; + } + this.cache[url] = resp; + this.cachedKeysByAge.push(url); + } + }); + return RequestCache; + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; + function Transport(o) { + utils.bindAll(this); + o = utils.isString(o) ? { + url: o + } : o; + requestCache = requestCache || new RequestCache(); + maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; + this.url = o.url; + this.wildcard = o.wildcard || "%QUERY"; + this.filter = o.filter; + this.replace = o.replace; + this.ajaxSettings = { + type: "get", + cache: o.cache, + timeout: o.timeout, + dataType: o.dataType || "json", + beforeSend: o.beforeSend + }; + this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); + } + utils.mixin(Transport.prototype, { + _get: function(url, cb) { + var that = this; + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + cb && cb(data); + requestCache.set(url, resp); + } + }, + _sendRequest: function(url) { + var that = this, jqXhr = pendingRequests[url]; + if (!jqXhr) { + incrementPendingRequests(); + jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); + } + return jqXhr; + function always() { + decrementPendingRequests(); + pendingRequests[url] = null; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(query, cb) { + var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); + if (resp = requestCache.get(url)) { + utils.defer(function() { + cb(that.filter ? that.filter(resp) : resp); + }); + } else { + this._get(url, cb); + } + return !!resp; + } + }); + return Transport; + function incrementPendingRequests() { + pendingRequestsCount++; + } + function decrementPendingRequests() { + pendingRequestsCount--; + } + function belowPendingRequestsThreshold() { + return pendingRequestsCount < maxPendingRequests; + } + }(); + var Dataset = function() { + var keys = { + thumbprint: "thumbprint", + protocol: "protocol", + itemHash: "itemHash", + adjacencyList: "adjacencyList" + }; + function Dataset(o) { + utils.bindAll(this); + if (utils.isString(o.template) && !o.engine) { + $.error("no template engine specified"); + } + if (!o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.name = o.name || utils.getUniqueId(); + this.limit = o.limit || 5; + this.minLength = o.minLength || 1; + this.header = o.header; + this.footer = o.footer; + this.valueKey = o.valueKey || "value"; + this.template = compileTemplate(o.template, o.engine, this.valueKey); + this.local = o.local; + this.prefetch = o.prefetch; + this.remote = o.remote; + this.itemHash = {}; + this.adjacencyList = {}; + this.storage = o.name ? new PersistentStorage(o.name) : null; + } + utils.mixin(Dataset.prototype, { + _processLocalData: function(data) { + this._mergeProcessedData(this._processData(data)); + }, + _loadPrefetchData: function(o) { + var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; + if (this.storage) { + storedThumbprint = this.storage.get(keys.thumbprint); + storedProtocol = this.storage.get(keys.protocol); + storedItemHash = this.storage.get(keys.itemHash); + storedAdjacencyList = this.storage.get(keys.adjacencyList); + } + isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); + o = utils.isString(o) ? { + url: o + } : o; + o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; + if (storedItemHash && storedAdjacencyList && !isExpired) { + this._mergeProcessedData({ + itemHash: storedItemHash, + adjacencyList: storedAdjacencyList + }); + deferred = $.Deferred().resolve(); + } else { + deferred = $.getJSON(o.url).done(processPrefetchData); + } + return deferred; + function processPrefetchData(data) { + var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; + if (that.storage) { + that.storage.set(keys.itemHash, itemHash, o.ttl); + that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); + that.storage.set(keys.thumbprint, thumbprint, o.ttl); + that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); + } + that._mergeProcessedData(processedData); + } + }, + _transformDatum: function(datum) { + var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { + value: value, + tokens: tokens + }; + if (utils.isString(datum)) { + item.datum = {}; + item.datum[this.valueKey] = datum; + } else { + item.datum = datum; + } + item.tokens = utils.filter(item.tokens, function(token) { + return !utils.isBlankString(token); + }); + item.tokens = utils.map(item.tokens, function(token) { + return token.toLowerCase(); + }); + return item; + }, + _processData: function(data) { + var that = this, itemHash = {}, adjacencyList = {}; + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); + itemHash[id] = item; + utils.each(item.tokens, function(i, token) { + var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); + !~utils.indexOf(adjacency, id) && adjacency.push(id); + }); + }); + return { + itemHash: itemHash, + adjacencyList: adjacencyList + }; + }, + _mergeProcessedData: function(processedData) { + var that = this; + utils.mixin(this.itemHash, processedData.itemHash); + utils.each(processedData.adjacencyList, function(character, adjacency) { + var masterAdjacency = that.adjacencyList[character]; + that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; + }); + }, + _getLocalSuggestions: function(terms) { + var that = this, firstChars = [], lists = [], shortestList, suggestions = []; + utils.each(terms, function(i, term) { + var firstChar = term.charAt(0); + !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); + }); + utils.each(firstChars, function(i, firstChar) { + var list = that.adjacencyList[firstChar]; + if (!list) { + return false; + } + lists.push(list); + if (!shortestList || list.length < shortestList.length) { + shortestList = list; + } + }); + if (lists.length < firstChars.length) { + return []; + } + utils.each(shortestList, function(i, id) { + var item = that.itemHash[id], isCandidate, isMatch; + isCandidate = utils.every(lists, function(list) { + return ~utils.indexOf(list, id); + }); + isMatch = isCandidate && utils.every(terms, function(term) { + return utils.some(item.tokens, function(token) { + return token.indexOf(term) === 0; + }); + }); + isMatch && suggestions.push(item); + }); + return suggestions; + }, + initialize: function() { + var deferred; + this.local && this._processLocalData(this.local); + this.transport = this.remote ? new Transport(this.remote) : null; + deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); + this.local = this.prefetch = this.remote = null; + this.initialize = function() { + return deferred; + }; + return deferred; + }, + getSuggestions: function(query, cb) { + var that = this, terms, suggestions, cacheHit = false; + if (query.length < this.minLength) { + return; + } + terms = utils.tokenizeQuery(query); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + if (suggestions.length < this.limit && this.transport) { + cacheHit = this.transport.get(query, processRemoteData); + } + !cacheHit && cb && cb(suggestions); + function processRemoteData(data) { + suggestions = suggestions.slice(0); + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), isDuplicate; + isDuplicate = utils.some(suggestions, function(suggestion) { + return item.value === suggestion.value; + }); + !isDuplicate && suggestions.push(item); + return suggestions.length < that.limit; + }); + cb && cb(suggestions); + } + } + }); + return Dataset; + function compileTemplate(template, engine, valueKey) { + var renderFn, compiledTemplate; + if (utils.isFunction(template)) { + renderFn = template; + } else if (utils.isString(template)) { + compiledTemplate = engine.compile(template); + renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + } else { + renderFn = function(context) { + return "

" + context[valueKey] + "

"; + }; + } + return renderFn; + } + }(); + var InputView = function() { + function InputView(o) { + var that = this; + utils.bindAll(this); + this.specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); + if (!utils.isMsie()) { + this.$input.on("input.tt", this._compareQueryToInputValue); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + utils.defer(that._compareQueryToInputValue); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + utils.mixin(InputView.prototype, EventTarget, { + _handleFocus: function() { + this.trigger("focused"); + }, + _handleBlur: function() { + this.trigger("blured"); + }, + _handleSpecialKeyEvent: function($e) { + var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; + keyName && this.trigger(keyName + "Keyed", $e); + }, + _compareQueryToInputValue: function() { + var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; + if (isSameQueryExceptWhitespace) { + this.trigger("whitespaceChanged", { + value: this.query + }); + } else if (!isSameQuery) { + this.trigger("queryChanged", { + value: this.query = inputValue + }); + } + }, + destroy: function() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + }, + focus: function() { + this.$input.focus(); + }, + blur: function() { + this.$input.blur(); + }, + getQuery: function() { + return this.query; + }, + setQuery: function(query) { + this.query = query; + }, + getInputValue: function() { + return this.$input.val(); + }, + setInputValue: function(value, silent) { + this.$input.val(value); + !silent && this._compareQueryToInputValue(); + }, + getHintValue: function() { + return this.$hint.val(); + }, + setHintValue: function(value) { + this.$hint.val(value); + }, + getLanguageDirection: function() { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + isOverflow: function() { + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() > this.$input.width(); + }, + isCursorAtEnd: function() { + var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; + if (utils.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + } + }); + return InputView; + function buildOverflowHelper($input) { + return $("").css({ + position: "absolute", + left: "-9999px", + visibility: "hidden", + whiteSpace: "nowrap", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function compareQueries(a, b) { + a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return a === b; + } + }(); + var DropdownView = function() { + var html = { + suggestionsList: '' + }, css = { + suggestionsList: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + } + }; + function DropdownView(o) { + utils.bindAll(this); + this.isOpen = false; + this.isEmpty = true; + this.isMouseOverDropdown = false; + this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); + } + utils.mixin(DropdownView.prototype, EventTarget, { + _handleMouseenter: function() { + this.isMouseOverDropdown = true; + }, + _handleMouseleave: function() { + this.isMouseOverDropdown = false; + }, + _handleMouseover: function($e) { + var $suggestion = $($e.currentTarget); + this._getSuggestions().removeClass("tt-is-under-cursor"); + $suggestion.addClass("tt-is-under-cursor"); + }, + _handleSelection: function($e) { + var $suggestion = $($e.currentTarget); + this.trigger("suggestionSelected", extractSuggestion($suggestion)); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _hide: function() { + this.$menu.hide(); + }, + _moveCursor: function(increment) { + var $suggestions, $cur, nextIndex, $underCursor; + if (!this.isVisible()) { + return; + } + $suggestions = this._getSuggestions(); + $cur = $suggestions.filter(".tt-is-under-cursor"); + $cur.removeClass("tt-is-under-cursor"); + nextIndex = $suggestions.index($cur) + increment; + nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; + if (nextIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (nextIndex < -1) { + nextIndex = $suggestions.length - 1; + } + $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); + this._ensureVisibility($underCursor); + this.trigger("cursorMoved", extractSuggestion($underCursor)); + }, + _getSuggestions: function() { + return this.$menu.find(".tt-suggestions > .tt-suggestion"); + }, + _ensureVisibility: function($el) { + var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + destroy: function() { + this.$menu.off(".tt"); + this.$menu = null; + }, + isVisible: function() { + return this.isOpen && !this.isEmpty; + }, + closeUnlessMouseIsOverDropdown: function() { + if (!this.isMouseOverDropdown) { + this.close(); + } + }, + close: function() { + if (this.isOpen) { + this.isOpen = false; + this.isMouseOverDropdown = false; + this._hide(); + this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); + this.trigger("closed"); + } + }, + open: function() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function(dir) { + var ltrCss = { + left: "0", + right: "auto" + }, rtlCss = { + left: "auto", + right: " 0" + }; + dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); + }, + moveCursorUp: function() { + this._moveCursor(-1); + }, + moveCursorDown: function() { + this._moveCursor(+1); + }, + getSuggestionUnderCursor: function() { + var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + getFirstSuggestion: function() { + var $suggestion = this._getSuggestions().first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + renderSuggestions: function(dataset, suggestions) { + var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
%body
', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; + if ($dataset.length === 0) { + $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); + $dataset = $("
").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); + } + if (suggestions.length > 0) { + this.isEmpty = false; + this.isOpen && this._show(); + elBuilder = document.createElement("div"); + fragment = document.createDocumentFragment(); + utils.each(suggestions, function(i, suggestion) { + suggestion.dataset = dataset.name; + compiledHtml = dataset.template(suggestion.datum); + elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); + $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + fragment.appendChild($el[0]); + }); + $dataset.show().find(".tt-suggestions").html(fragment); + } else { + this.clearSuggestions(dataset.name); + } + this.trigger("suggestionsRendered"); + }, + clearSuggestions: function(datasetName) { + var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); + $datasets.hide(); + $suggestions.empty(); + if (this._getSuggestions().length === 0) { + this.isEmpty = true; + this._hide(); + } + } + }); + return DropdownView; + function extractSuggestion($el) { + return $el.data("suggestion"); + } + }(); + var TypeaheadView = function() { + var html = { + wrapper: '', + hint: '', + dropdown: '' + }, css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + query: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + } + }; + if (utils.isMsie()) { + utils.mixin(css.query, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (utils.isMsie() && utils.isMsie() <= 7) { + utils.mixin(css.wrapper, { + display: "inline", + zoom: "1" + }); + utils.mixin(css.query, { + marginTop: "-1px" + }); + } + function TypeaheadView(o) { + var $menu, $input, $hint; + utils.bindAll(this); + this.$node = buildDomStructure(o.input); + this.datasets = o.datasets; + this.dir = null; + this.eventBus = o.eventBus; + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-query"); + $hint = this.$node.find(".tt-hint"); + this.dropdownView = new DropdownView({ + menu: $menu + }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); + this.inputView = new InputView({ + input: $input, + hint: $hint + }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); + } + utils.mixin(TypeaheadView.prototype, EventTarget, { + _managePreventDefault: function(e) { + var $e = e.data, hint, inputValue, preventDefault = false; + switch (e.type) { + case "tabKeyed": + hint = this.inputView.getHintValue(); + inputValue = this.inputView.getInputValue(); + preventDefault = hint && hint !== inputValue; + break; + + case "upKeyed": + case "downKeyed": + preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; + break; + } + preventDefault && $e.preventDefault(); + }, + _setLanguageDirection: function() { + var dir = this.inputView.getLanguageDirection(); + if (dir !== this.dir) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdownView.setLanguageDirection(dir); + } + }, + _updateHint: function() { + var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; + if (hint && dropdownIsVisible && !inputHasOverflow) { + inputValue = this.inputView.getInputValue(); + query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); + escapedQuery = utils.escapeRegExChars(query); + beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); + match = beginsWithQuery.exec(hint); + this.inputView.setHintValue(inputValue + (match ? match[1] : "")); + } + }, + _clearHint: function() { + this.inputView.setHintValue(""); + }, + _clearSuggestions: function() { + this.dropdownView.clearSuggestions(); + }, + _setInputValueToQuery: function() { + this.inputView.setInputValue(this.inputView.getQuery()); + }, + _setInputValueToSuggestionUnderCursor: function(e) { + var suggestion = e.data; + this.inputView.setInputValue(suggestion.value, true); + }, + _openDropdown: function() { + this.dropdownView.open(); + }, + _closeDropdown: function(e) { + this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); + }, + _moveDropdownCursor: function(e) { + var $e = e.data; + if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { + this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); + } + }, + _handleSelection: function(e) { + var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); + if (suggestion) { + this.inputView.setInputValue(suggestion.value); + byClick ? this.inputView.focus() : e.data.preventDefault(); + byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); + this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); + } + }, + _getSuggestions: function() { + var that = this, query = this.inputView.getQuery(); + if (utils.isBlankString(query)) { + return; + } + utils.each(this.datasets, function(i, dataset) { + dataset.getSuggestions(query, function(suggestions) { + if (query === that.inputView.getQuery()) { + that.dropdownView.renderSuggestions(dataset, suggestions); + } + }); + }); + }, + _autocomplete: function(e) { + var isCursorAtEnd, ignoreEvent, query, hint, suggestion; + if (e.type === "rightKeyed" || e.type === "leftKeyed") { + isCursorAtEnd = this.inputView.isCursorAtEnd(); + ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; + if (!isCursorAtEnd || ignoreEvent) { + return; + } + } + query = this.inputView.getQuery(); + hint = this.inputView.getHintValue(); + if (hint !== "" && query !== hint) { + suggestion = this.dropdownView.getFirstSuggestion(); + this.inputView.setInputValue(suggestion.value); + this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); + } + }, + _propagateEvent: function(e) { + this.eventBus.trigger(e.type); + }, + destroy: function() { + this.inputView.destroy(); + this.dropdownView.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + }, + setQuery: function(query) { + this.inputView.setQuery(query); + this.inputView.setInputValue(query); + this._clearHint(); + this._clearSuggestions(); + this._getSuggestions(); + } + }); + return TypeaheadView; + function buildDomStructure(input) { + var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); + $wrapper = $wrapper.css(css.wrapper); + $dropdown = $dropdown.css(css.dropdown); + $hint.css(css.hint).css({ + backgroundAttachment: $input.css("background-attachment"), + backgroundClip: $input.css("background-clip"), + backgroundColor: $input.css("background-color"), + backgroundImage: $input.css("background-image"), + backgroundOrigin: $input.css("background-origin"), + backgroundPosition: $input.css("background-position"), + backgroundRepeat: $input.css("background-repeat"), + backgroundSize: $input.css("background-size") + }); + $input.data("ttAttrs", { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-query").attr({ + autocomplete: "off", + spellcheck: false + }).css(css.query); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-query"); + utils.each($input.data("ttAttrs"), function(key, val) { + utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); + $node.remove(); + } + }(); + (function() { + var cache = {}, viewKey = "ttView", methods; + methods = { + initialize: function(datasetDefs) { + var datasets; + datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; + if (datasetDefs.length === 0) { + $.error("no datasets provided"); + } + datasets = utils.map(datasetDefs, function(o) { + var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); + if (o.name) { + cache[o.name] = dataset; + } + return dataset; + }); + return this.each(initialize); + function initialize() { + var $input = $(this), deferreds, eventBus = new EventBus({ + el: $input + }); + deferreds = utils.map(datasets, function(dataset) { + return dataset.initialize(); + }); + $input.data(viewKey, new TypeaheadView({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + datasets: datasets + })); + $.when.apply($, deferreds).always(function() { + utils.defer(function() { + eventBus.trigger("initialized"); + }); + }); + } + }, + destroy: function() { + return this.each(destroy); + function destroy() { + var $this = $(this), view = $this.data(viewKey); + if (view) { + view.destroy(); + $this.removeData(viewKey); + } + } + }, + setQuery: function(query) { + return this.each(setQuery); + function setQuery() { + var view = $(this).data(viewKey); + view && view.setQuery(query); + } + } + }; + jQuery.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/framework/yii/gii/assets/typeahead.js-bootstrap.css b/framework/yii/gii/assets/typeahead.js-bootstrap.css new file mode 100644 index 0000000..987aaf5 --- /dev/null +++ b/framework/yii/gii/assets/typeahead.js-bootstrap.css @@ -0,0 +1,51 @@ +/* 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; +} diff --git a/framework/yii/gii/components/ActiveField.php b/framework/yii/gii/components/ActiveField.php index 8bb67a9..ae6f144 100644 --- a/framework/yii/gii/components/ActiveField.php +++ b/framework/yii/gii/components/ActiveField.php @@ -8,6 +8,7 @@ namespace yii\gii\components; use yii\gii\Generator; +use yii\helpers\Json; /** * @author Qiang Xue @@ -30,10 +31,18 @@ class ActiveField extends \yii\widgets\ActiveField if (isset($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 */ public function sticky() @@ -41,4 +50,17 @@ class ActiveField extends \yii\widgets\ActiveField $this->options['class'] .= ' sticky'; 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; + } } diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index bd42fab..a7f1aa7 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -113,6 +113,18 @@ class Generator extends \yii\gii\Generator /** * @inheritdoc */ + public function autoCompleteData() + { + return [ + 'tableName' => function () { + return $this->getDbConnection()->getSchema()->getTableNames(); + }, + ]; + } + + /** + * @inheritdoc + */ public function requiredTemplates() { return ['model.php']; From f9a92b82df44475af381bbbdfe5a03d2b1871535 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 31 Oct 2013 00:48:37 +0400 Subject: [PATCH 032/203] fixed typo --- docs/guide/upgrade-from-v1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index 4ccd726..2bf080a 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -109,7 +109,7 @@ 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) { +Event::on([ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT], function ($event) { Yii::trace(get_class($event->sender) . ' is inserted.'); }); ``` From 2f360e53c39cfc4fa3bc28d15d12b560422c17f8 Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 31 Oct 2013 17:55:44 +0600 Subject: [PATCH 033/203] Add CoffeeScript and TypeScript commands to AssetConverter. --- framework/yii/web/AssetConverter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index b7f406d..75a3106 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -28,6 +28,8 @@ class AssetConverter extends Component implements AssetConverterInterface 'scss' => ['css', 'sass {from} {to}'], 'sass' => ['css', 'sass {from} {to}'], 'styl' => ['js', 'stylus < {from} > {to}'], + 'coffee' => ['js', 'coffee -p {from} > {to}'], + 'ts' => ['js', 'tsc --out {to} {from}'], ]; /** From 7e805864d426e885aa4aa4ab717093344218961f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 31 Oct 2013 23:55:33 +0400 Subject: [PATCH 034/203] added brief description of forms --- docs/guide/form.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/guide/form.md b/docs/guide/form.md index c1f1ba3..c811c10 100644 --- a/docs/guide/form.md +++ b/docs/guide/form.md @@ -1,3 +1,96 @@ Working with forms ================== +The primary way of using forms in Yii is [[\yii\widgets\ActiveForm]]. It should be preferred when you have a model +behind a form. Additionally there are some useful methods in [[\yii\helpers\Html]] that are typically used for adding +buttons and help text. + +First step creating a form is to create a model. It can be either Active Record or regular Model. Let's use regular +login model as an example: + +```php +use yii\base\Model; + +class LoginForm extends Model +{ + public $username; + public $password; + + /** + * @return array the validation rules. + */ + public function rules() + { + return [ + // username and password are both required + ['username, password', 'required'], + // password is validated by validatePassword() + ['password', 'validatePassword'], + ]; + } + + /** + * Validates the password. + * This method serves as the inline validation for password. + */ + public function validatePassword() + { + $user = User::findByUsername($this->username); + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Incorrect username or password.'); + } + } + + /** + * Logs in a user using the provided username and password. + * @return boolean whether the user is logged in successfully + */ + public function login() + { + if ($this->validate()) { + $user = User::findByUsername($this->username); + return true; + } else { + return false; + } + } +} +``` + +In controller we're passing model to view where Active Form is used: + +```php +use yii\helpers\Html; +use yii\widgets\ActiveForm; + + 'login-form', + 'options' => ['class' => 'form-horizontal'], +]) ?> + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + +
+
+ 'btn btn-primary']) ?> +
+
+ +``` + +In the code above `ActiveForm::begin()` not only creates form instance but marks the beginning of the form. All the content +that is located between `ActiveForm::begin()` and `ActiveForm::end()` will be wrapped with appropriate `
` tag. +Same as with any other widget you can specify some options passing an array to `begin` method. In our case we're adding +extra CSS class and specifying ID that will be used in the tag. + +In order to insert a form field along with its label all necessary validation JavaScript we're calling `field` method +and it gives back `\yii\widgets\ActiveField`. It it's echoed directly it creates a regular input. In case you want to +customize it you can add a chain of additional methods: + +```php +field($model, 'password')->passwordInput() ?> + +// or + +field($model, 'username')->textInput()->hint('Please enter your name')->label('Name') ?> +``` From b355019278dc7efa8ba0a06f880eee30a8096e48 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 00:12:24 +0400 Subject: [PATCH 035/203] some docs on Authentication --- docs/guide/authentication.md | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/guide/authentication.md b/docs/guide/authentication.md index e69de29..216b4c6 100644 --- a/docs/guide/authentication.md +++ b/docs/guide/authentication.md @@ -0,0 +1,72 @@ +Authentication +============== + +Authentication is basically what happens when one is trying to sign in. Typically login and passwords are read from +the form and then application checks if there's such user with such password. + +In Yii all this is done semi-automatically and what's left to developer is to implement [[\yii\web\IdentityInterface]]. +Typically it is being implemented in `User` model. You can find a full featured example in +[advanced application template](installation.md). Below only interface methods are listed: + +```php +class User extends ActiveRecord implements IdentityInterface +{ + // ... + + /** + * Finds an identity by the given ID. + * + * @param string|integer $id the ID to be looked for + * @return IdentityInterface|null the identity object that matches the given ID. + */ + public static function findIdentity($id) + { + return static::find($id); + } + + /** + * @return int|string current user ID + */ + public function getId() + { + return $this->id; + } + + /** + * @return string current user auth key + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @param string $authKey + * @return boolean if auth key is valid for current user + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey; + } +} +``` + +First two methods are simple. `findIdentity` given ID returns model instance while `getId` returns ID itself. +`getAuthKey` and `validateAuthKey` are used to provide extra security to the "remember me" cookie. +`getAuthKey` should return a string that is unique for each user. A good idea is to save this value when user is +created using `Security::generateRandomKey()`: + +```php +public function beforeSave($insert) +{ + if (parent::beforeSave($insert)) { + if ($this->isNewRecord) { + $this->auth_key = Security::generateRandomKey(); + } + return true; + } + return false; +} +``` + +`validateAuthKey` just compares `$authKey` passed as parameter (got from cookie) with the value got from database. From 4c8a11793d45062b02d948bf2baf4d3292b2b963 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 01:17:58 +0400 Subject: [PATCH 036/203] started authorization docs --- docs/guide/authorization.md | 124 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md index e69de29..47b9409 100644 --- a/docs/guide/authorization.md +++ b/docs/guide/authorization.md @@ -0,0 +1,124 @@ +Authorization +============= + +Authorization is the process of verifying that user has enough permissions to do something. Yii provides several methods +of controlling it. + +Access control basics +--------------------- + +Basic acces control is very simple to implement using [[\yii\web\AccessControl]]: + +```php +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => \yii\web\AccessControl::className(), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'actions' => ['login', 'signup'], + 'allow' => true, + 'roles' => ['?'], + ], + [ + 'actions' => ['logout'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +``` + +In the code above we're attaching access control behavior to a controller. Since there's `only` option specified, it +will be applied to 'login', 'logout' and 'signup' actions only. A set of rules that are basically options for +[[\yii\web\AccessRule]] reads as follows: + +- Allow all guest (not yet authenticated) users to access 'login' and 'signup' actions. +- Allow authenticated users to access 'logout' action. + +Rules are checked one by one from top to bottom. If rule matches, action takes place immediately. If not, next rule is +checked. If no rules matched access is denied. + +[[\yii\web\AccessRule]] is quite flexible and allows additionally to what was demonstrated checking IPs and request method +(i.e. POST, GET). If it's not enough you can specify your own check via anonymous function: + +```php +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => \yii\web\AccessControl::className(), + 'only' => ['special'], + 'rules' => [ + [ + 'actions' => ['special'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], +``` + +Sometimes you want a custom action to be taken when access is denied. In this case you can specify `denyCallback`. + +Role based access control (RBAC) +-------------------------------- + +Role based access control is very flexible approach to controlling access that is a perfect match for complex systems +where permissions are customizable. + +In order to start using it some extra steps are required. First of all we need to configure `authManager` application +component: + +```php + +``` + +Then create permissions hierarchy. + +Specify roles from RBAC in controller's access control configuration or call [[User::checkAccess()]] where appropriate. + +### How it works + +TBD: write about how it works with pictures :) + +### Avoiding too much RBAC + +In order to keep auth hierarchy simple and efficient you should avoid creating and using too much nodes. Most of the time +simple checks could be used instead. For example such code that uses RBAC: + +```php +public function editArticle($id) +{ + $article = Article::find($id); + if (!$article) { + throw new HttpException(404); + } + if (!\Yii::$app->user->checkAccess('edit_article', ['article' => $article])) { + throw new HttpException(403); + } + // ... +} +``` + +can be replaced with simpler code that doesn't use RBAC: + +```php +public function editArticle($id) +{ + $article = Article::find(['id' => $id, 'author_id' => \Yii::$app->user->id]); + if (!$article) { + throw new HttpException(404); + } + // ... +} +``` From d13aaa1e85457c2ee91d7b244b426eb0423e22a7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 01:25:00 +0400 Subject: [PATCH 037/203] added composer basic docs --- docs/guide/composer.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/guide/index.md | 1 + 2 files changed, 51 insertions(+) create mode 100644 docs/guide/composer.md diff --git a/docs/guide/composer.md b/docs/guide/composer.md new file mode 100644 index 0000000..7f72f9c --- /dev/null +++ b/docs/guide/composer.md @@ -0,0 +1,50 @@ +Composer +======== + +Yii2 uses Composer as its package manager. It is a PHP utility that allows you to automatically install libraries and +extensions keeping them up to date and handling dependencies. + +Installing Composer +------------------- + +Check the official guide for [linux](http://getcomposer.org/doc/00-intro.md#installation-nix) or +[Windows](http://getcomposer.org/doc/00-intro.md#installation-windows). + +Adding more packages to your project +------------------------------------ + +After [installing an application](installing.md) you will find `composer.json` in the root directory of your project. +This file lists packages that your application uses. The part we're interested in is `require` section. + +``` +{ + "require": { + "Michelf/php-markdown": ">=1.3", + "ezyang/htmlpurifier": ">=4.5.0" + } +} +``` + +Here you can specify package name and version. Additionally to Yii extensions you may check +[packagist](http://packagist.org/) repository for general purpose PHP packages. + +After packages are specified you can type either + +``` +php composer.phar install +``` + +or + +``` +php composer.phar update +``` + +depending if you're doing it for the first time or not. Then, after some waiting, packages will be installed and ready +to use. You don't need anything to be configured additionally. + + +See also +-------- + +- [Official Composer documentation](http://getcomposer.org). \ No newline at end of file diff --git a/docs/guide/index.md b/docs/guide/index.md index 601323a..95a204f 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -77,4 +77,5 @@ More - [Performance Tuning](performance.md) - [Managing assets](assets.md) - [Testing](testing.md) +- [Composer](composer.md) - [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) From 986a8b0ca49d9f98b75a21bba975691207e5a570 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 02:50:39 +0400 Subject: [PATCH 038/203] Fixes #1105: better validation for Gii model generator --- framework/yii/gii/generators/model/Generator.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index a7f1aa7..99f2d38 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -61,7 +61,7 @@ class Generator extends \yii\gii\Generator ['db', 'validateDb'], ['ns', 'validateNamespace'], ['tableName', 'validateTableName'], - ['modelClass', 'validateModelClass'], + ['modelClass', 'validateModelClass', 'skipOnEmpty' => false], ['baseClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], ['generateRelations, generateLabelsFromComments', 'boolean'], ]); @@ -93,14 +93,14 @@ class Generator extends \yii\gii\Generator 'db' => 'This is the ID of the DB application component.', 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. tbl_post. The table name may consist of the DB schema part if needed, e.g. public.tbl_post. - The table name may contain an asterisk to match multiple table names, e.g. tbl_* + The table name may end with asterisk to match multiple table names, e.g. tbl_* will match tables who name starts with tbl_. In this case, multiple ActiveRecord classes will be generated, one for each matching table name; and the class names will be generated from the matching characters. For example, table tbl_post will generate Post class.', 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain the namespace part as it is specified in "Namespace". You do not need to specify the class name - if "Table Name" contains an asterisk at the end, in which case multiple ActiveRecord classes will be generated.', + if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', 'generateRelations' => 'This indicates whether the generator should generate relations based on foreign key constraints it detects in the database. Note that if your database contains too many tables, @@ -434,8 +434,8 @@ class Generator extends \yii\gii\Generator if ($this->isReservedKeyword($this->modelClass)) { $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); } - if (strpos($this->tableName, '*') === false && $this->modelClass == '') { - $this->addError('modelClass', 'Model Class cannot be blank.'); + if (substr($this->tableName, -1) !== '*' && $this->modelClass == '') { + $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); } } @@ -444,8 +444,8 @@ class Generator extends \yii\gii\Generator */ public function validateTableName() { - if (($pos = strpos($this->tableName, '*')) !== false && strpos($this->tableName, '*', $pos + 1) !== false) { - $this->addError('tableName', 'At most one asterisk is allowed.'); + if (($pos = strpos($this->tableName, '*')) !== false && substr($this->tableName, -1) !== '*') { + $this->addError('tableName', 'Asterisk is only allowed as the last character.'); return; } $tables = $this->getTableNames(); From 3dbfd3ea0eeb3be7485074003bb2a1f7c4ce7d08 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 03:13:40 +0400 Subject: [PATCH 039/203] Gii: renamed action "new" to "create" --- framework/yii/gii/CodeFile.php | 6 +++--- framework/yii/gii/Generator.php | 2 +- framework/yii/gii/views/default/view/files.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/yii/gii/CodeFile.php b/framework/yii/gii/CodeFile.php index 2676c38..a5196d9 100644 --- a/framework/yii/gii/CodeFile.php +++ b/framework/yii/gii/CodeFile.php @@ -28,7 +28,7 @@ class CodeFile extends Object /** * The code file is new. */ - const OP_NEW = 'new'; + const OP_CREATE = 'create'; /** * The code file already exists, and the new one may need to overwrite it. */ @@ -68,7 +68,7 @@ class CodeFile extends Object if (is_file($path)) { $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE; } else { - $this->operation = self::OP_NEW; + $this->operation = self::OP_CREATE; } } @@ -79,7 +79,7 @@ class CodeFile extends Object public function save() { $module = Yii::$app->controller->module; - if ($this->operation === self::OP_NEW) { + if ($this->operation === self::OP_CREATE) { $dir = dirname($this->path); if (!is_dir($dir)) { $mask = @umask(0); diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php index 83cdf6e..6f63628 100644 --- a/framework/yii/gii/Generator.php +++ b/framework/yii/gii/Generator.php @@ -250,7 +250,7 @@ abstract class Generator extends Model $hasError = true; $lines[] = "generating $relativePath\n$error"; } else { - $lines[] = $file->operation === CodeFile::OP_NEW ? " generated $relativePath" : " overwrote $relativePath"; + $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; } } else { $lines[] = " skipped $relativePath"; diff --git a/framework/yii/gii/views/default/view/files.php b/framework/yii/gii/views/default/view/files.php index af61a02..299f12b 100644 --- a/framework/yii/gii/views/default/view/files.php +++ b/framework/yii/gii/views/default/view/files.php @@ -53,7 +53,7 @@ use yii\gii\CodeFile; if ($file->operation === CodeFile::OP_SKIP) { echo ' '; } else { - echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_NEW)); + echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_CREATE)); } ?> From 91b61692c5b3f083ec27ccfaa6c4c9d3da7e9d28 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 03:16:34 +0400 Subject: [PATCH 040/203] Better alias paths for advanced application --- apps/advanced/common/config/params.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/advanced/common/config/params.php b/apps/advanced/common/config/params.php index 7dab548..5212f84 100644 --- a/apps/advanced/common/config/params.php +++ b/apps/advanced/common/config/params.php @@ -1,8 +1,8 @@ 'admin@example.com', From 1c1cd863cadf87dde34d9b0ba5204aa85bd8c691 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 03:30:40 +0400 Subject: [PATCH 041/203] Fixes #1107: Gii CRUD generator now validates same named model and search model class names --- framework/yii/gii/generators/crud/Generator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index cb4bce6..a38cf5e 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -44,6 +44,7 @@ class Generator extends \yii\gii\Generator return array_merge(parent::rules(), [ ['moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'], ['modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'], + ['searchModelClass', 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], ['modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], ['modelClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], ['baseControllerClass', 'validateClass', 'params' => ['extends' => Controller::className()]], From eeed9c3ff20994079b24991dac5f2d22b663530b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 31 Oct 2013 20:56:13 -0400 Subject: [PATCH 042/203] Fixes #998: Added support for generating canonical URL. --- framework/yii/web/Controller.php | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 9d22d01..6927893 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -24,6 +24,10 @@ class Controller extends \yii\base\Controller * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. */ public $enableCsrfValidation = true; + /** + * @var array the parameters bound to the current action. This is mainly used by [[getCanonicalUrl()]]. + */ + public $actionParams = []; /** * Binds the parameters to the action. @@ -46,13 +50,14 @@ class Controller extends \yii\base\Controller $args = []; $missing = []; + $actionParams = []; foreach ($method->getParameters() as $param) { $name = $param->getName(); if (array_key_exists($name, $params)) { - $args[] = $params[$name]; + $args[] = $actionParams[$name] = $params[$name]; unset($params[$name]); } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); + $args[] = $actionParams[$name] = $param->getDefaultValue(); } else { $missing[] = $name; } @@ -63,6 +68,8 @@ class Controller extends \yii\base\Controller 'params' => implode(', ', $missing), ])); } + + $this->actionParams = $actionParams; return $args; } @@ -113,6 +120,22 @@ class Controller extends \yii\base\Controller } /** + * Returns the canonical URL of the currently requested page. + * The canonical URL is constructed using [[route]] and [[actionParams]]. You may use the following code + * in the layout view to add a link tag about canonical URL: + * + * ~~~ + * $this->registerLinkTag(['rel' => 'canonical', 'href' => Yii::$app->controller->canonicalUrl]); + * ~~~ + * + * @return string + */ + public function getCanonicalUrl() + { + return Yii::$app->getUrlManager()->createAbsoluteUrl($this->getRoute(), $this->actionParams); + } + + /** * Redirects the browser to the specified URL. * This method is a shortcut to [[Response::redirect()]]. * From 1b497ad73be9f80e5fb122419ffe2c06875dfe56 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Nov 2013 13:23:05 +0400 Subject: [PATCH 043/203] Fixes #1107: if basename of Gii CRUD model and search model are equal than alias is automatically used for search model --- framework/yii/gii/generators/crud/templates/controller.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index d1921de..a863b30 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -12,6 +12,9 @@ use yii\helpers\StringHelper; $controllerClass = StringHelper::basename($generator->controllerClass); $modelClass = StringHelper::basename($generator->modelClass); $searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $searchModelAlias = $searchModelClass.'Search'; +} $pks = $generator->getTableSchema()->primaryKey; $urlParams = $generator->generateUrlParams(); @@ -24,7 +27,7 @@ echo "controllerClass, '\\')) ?>; use modelClass, '\\') ?>; -use searchModelClass, '\\') ?>; +use searchModelClass, '\\') ?> as ; use yii\data\ActiveDataProvider; use baseControllerClass, '\\') ?>; use yii\web\HttpException; @@ -53,7 +56,7 @@ class extends bas */ public function actionIndex() { - $searchModel = new ; + $searchModel = new ; $dataProvider = $searchModel->search($_GET); return $this->render('index', [ From bd8217014204ed37e31f2d1b3985082652979fd4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 1 Nov 2013 10:03:13 -0400 Subject: [PATCH 044/203] Fixes #1096: pgsql: sequence name not matched --- framework/yii/db/pgsql/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index c4da801..20c6fb6 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -281,7 +281,7 @@ SQL; $table->columns[$column->name] = $column; if ($column->isPrimaryKey === true) { $table->primaryKey[] = $column->name; - if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) { + if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) { $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue); } } From 1bee84746c07232731cbaaf1e2735cca2a5955a3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 1 Nov 2013 10:21:54 -0400 Subject: [PATCH 045/203] Fixes #1060: added code comment --- framework/yii/gii/generators/crud/templates/views/index.php | 2 +- framework/yii/widgets/ActiveField.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index d3cebcc..8543fad 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -15,7 +15,7 @@ echo " use yii\helpers\Html; -use indexWidgetType === 'grid' ? "yii\grid\GridView" : "yii\widgets\ListView" ?>; +use indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; /** * @var yii\base\View $this diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index fc30af5..dc97cbd 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -324,6 +324,7 @@ class ActiveField extends Component */ public function fileInput($options = []) { + // https://github.com/yiisoft/yii2/pull/795 if ($this->inputOptions !== ['class' => 'form-control']) { $options = array_merge($this->inputOptions, $options); } From e5a1244e0f630ac56a3d356b5a22cf715f2b45b1 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 16:46:41 +0100 Subject: [PATCH 046/203] moved unit test from cubrid to general should be tested for all dbms not only cubrid even if the problem may not exist in all of them it is good to verify that. --- tests/unit/framework/db/ActiveRecordTest.php | 28 ++++++++++++++++++++++ .../framework/db/cubrid/CubridActiveRecordTest.php | 28 ---------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index 1d6005e..9e68f5e 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -426,4 +426,32 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals(0, $record->var3); $this->assertEquals('', $record->stringcol); } + + /** + * Some PDO implementations(e.g. cubrid) do not support boolean values. + * Make sure this does not affect AR layer. + */ + public function testBooleanAttribute() + { + $customer = new Customer(); + $customer->name = 'boolean customer'; + $customer->email = 'mail@example.com'; + $customer->status = true; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(1, $customer->status); + + $customer->status = false; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(0, $customer->status); + + $customers = Customer::find()->where(['status' => true])->all(); + $this->assertEquals(2, count($customers)); + + $customers = Customer::find()->where(['status' => false])->all(); + $this->assertEquals(1, count($customers)); + } } diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php index 9fb9915..3949ba2 100644 --- a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -11,32 +11,4 @@ use yiiunit\framework\db\ActiveRecordTest; class CubridActiveRecordTest extends ActiveRecordTest { public $driverName = 'cubrid'; - - /** - * cubrid PDO does not support boolean values. - * Make sure this does not affect AR layer. - */ - public function testBooleanAttribute() - { - $customer = new Customer(); - $customer->name = 'boolean customer'; - $customer->email = 'mail@example.com'; - $customer->status = true; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(1, $customer->status); - - $customer->status = false; - $customer->save(false); - - $customer->refresh(); - $this->assertEquals(0, $customer->status); - - $customers = Customer::find()->where(['status' => true])->all(); - $this->assertEquals(2, count($customers)); - - $customers = Customer::find()->where(['status' => false])->all(); - $this->assertEquals(1, count($customers)); - } } From 7c630a6246d70ac0d9af53e1cfcdf0100215cd24 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 17:02:31 +0100 Subject: [PATCH 047/203] fixed problem with Postgres PDO and Boolean values See the following resources for details: yiisoft/yii#779 https://bugs.php.net/bug.php?id=33876 http://www.yiiframework.com/forum/index.php/topic/32334-boolean-type-with-postgresql/page__view__findpost__p__155647 --- framework/yii/db/pgsql/Schema.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 20c6fb6..bb38605 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -318,4 +318,24 @@ SQL; $column->phpType = $this->getColumnPhpType($column); return $column; } + + /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = [ + // php type => PDO type + 'boolean' => \PDO::PARAM_INT, // Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876 + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } } From f153ce443ab91e51c8379f9d463c733d2ad5a02a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 17:20:19 +0100 Subject: [PATCH 048/203] reverted non working fix for #1115 --- framework/yii/db/pgsql/Schema.php | 20 -------------------- .../db/pgsql/PostgreSQLActiveRecordTest.php | 5 +++++ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index bb38605..20c6fb6 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -318,24 +318,4 @@ SQL; $column->phpType = $this->getColumnPhpType($column); return $column; } - - /** - * Determines the PDO type for the given PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - public function getPdoType($data) - { - static $typeMap = [ - // php type => PDO type - 'boolean' => \PDO::PARAM_INT, // Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876 - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ]; - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index 1fffad7..4146e8c 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -11,4 +11,9 @@ use yiiunit\framework\db\ActiveRecordTest; class PostgreSQLActiveRecordTest extends ActiveRecordTest { protected $driverName = 'pgsql'; + + public function testBooleanAttribute() + { + $this->markTestSkipped('Storing boolean values does not work in PostgreSQL right now. See https://github.com/yiisoft/yii2/issues/1115 for details.'); + } } From 937a55f4fa11a325ac64c9673a8393fc482e56a5 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 17:39:49 +0100 Subject: [PATCH 049/203] fixed unit test for sqlite --- .../framework/db/sqlite/SqliteActiveRecordTest.php | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index a689e5d..5afcff2 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -1,6 +1,7 @@ name = 'boolean customer'; + $customer->email = 'mail@example.com'; + $customer->status = true; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(1, $customer->status); + + $customer->status = false; + $customer->save(false); + + $customer->refresh(); + // sqlite will return empty string here but it would still + // evaluate to false or null so we accept it + $this->assertTrue(0 == $customer->status); + + $customers = Customer::find()->where(['status' => true])->all(); + $this->assertEquals(2, count($customers)); + + $customers = Customer::find()->where(['status' => false])->all(); + $this->assertEquals(1, count($customers)); + } } From ee1689da036f519930fa9567fee61650e969f195 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 17:57:28 +0100 Subject: [PATCH 050/203] some more on active record unit tests and sqlite sqlite does not seem to allow using boolean values in select query --- tests/unit/framework/db/ActiveRecordTest.php | 19 +++++++++++++++++++ .../framework/db/sqlite/SqliteActiveRecordTest.php | 11 ++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index 9e68f5e..a86c084 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -427,6 +427,25 @@ class ActiveRecordTest extends DatabaseTestCase $this->assertEquals('', $record->stringcol); } + public function testStoreEmpty() + { + $record = new NullValues(); + $record->id = 1; + + // this is to simulate empty html form submission + $record->var1 = ''; + $record->var2 = ''; + $record->var3 = ''; + $record->stringcol = ''; + + $record->save(false); + $this->assertTrue($record->refresh()); + + // https://github.com/yiisoft/yii2/commit/34945b0b69011bc7cab684c7f7095d837892a0d4#commitcomment-4458225 + $this->assertTrue($record->var1 === $record->var2); + $this->assertTrue($record->var2 === $record->var3); + } + /** * Some PDO implementations(e.g. cubrid) do not support boolean values. * Make sure this does not affect AR layer. diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index 5afcff2..659908e 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -35,10 +35,11 @@ class SqliteActiveRecordTest extends ActiveRecordTest // evaluate to false or null so we accept it $this->assertTrue(0 == $customer->status); - $customers = Customer::find()->where(['status' => true])->all(); - $this->assertEquals(2, count($customers)); - - $customers = Customer::find()->where(['status' => false])->all(); - $this->assertEquals(1, count($customers)); + // select with boolean values does not seem to work in sqlite +// $customers = Customer::find()->where(['status' => true])->all(); +// $this->assertEquals(2, count($customers)); +// +// $customers = Customer::find()->where(['status' => false])->all(); +// $this->assertEquals(1, count($customers)); } } From fedc38fdb61554f161aaf46ed41f718f7e72f4b7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 1 Nov 2013 13:08:01 -0400 Subject: [PATCH 051/203] Fixes #1116. --- framework/yii/gii/assets/gii.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/gii/assets/gii.js b/framework/yii/gii/assets/gii.js index b581d3b..a95221e 100644 --- a/framework/yii/gii/assets/gii.js +++ b/framework/yii/gii/assets/gii.js @@ -14,7 +14,7 @@ yii.gii = (function ($) { }; var initStickyInputs = function () { - $('.sticky:not(.error) input[type="text"],select,textarea').each(function () { + $('.sticky:not(.error)').find('input[type="text"],select,textarea').each(function () { var value; if (this.tagName === 'SELECT') { value = this.options[this.selectedIndex].text; From c8c377e698595fa7af78c85c116ba32e6eb7f584 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Nov 2013 18:10:57 +0100 Subject: [PATCH 052/203] skip test on postgres --- tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index 4146e8c..d41a837 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -16,4 +16,9 @@ class PostgreSQLActiveRecordTest extends ActiveRecordTest { $this->markTestSkipped('Storing boolean values does not work in PostgreSQL right now. See https://github.com/yiisoft/yii2/issues/1115 for details.'); } + + public function testStoreEmpty() + { + // as this test attempts to store data with invalid type it is okay for postgres to fail, skipping silently. + } } From 4b42d78f4ef38666137dd883c1f18fa44c264c32 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 1 Nov 2013 21:43:33 -0400 Subject: [PATCH 053/203] Fixes #1117: added support to map a single view directory to multiple themed view directories. --- framework/yii/base/Theme.php | 51 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/framework/yii/base/Theme.php b/framework/yii/base/Theme.php index ff6780c..b864412 100644 --- a/framework/yii/base/Theme.php +++ b/framework/yii/base/Theme.php @@ -13,17 +13,38 @@ use yii\helpers\FileHelper; /** * Theme represents an application theme. * - * A theme is directory consisting of view and layout files which are meant to replace their - * non-themed counterparts. + * When [[View]] renders a view file, it will check the [[Application::theme|active theme]] + * to see if there is a themed version of the view file exists. If so, the themed version will be rendered instead. * - * Theme uses [[pathMap]] to achieve the file replacement. A view or layout file will be replaced - * with its themed version if part of its path matches one of the keys in [[pathMap]]. - * Then the matched part will be replaced with the corresponding array value. + * A theme is directory consisting of view files which are meant to replace their non-themed counterparts. + * + * Theme uses [[pathMap]] to achieve the view file replacement: + * + * 1. It first looks for a key in [[pathMap]] that is a substring of the given view file path; + * 2. If such a key exists, the corresponding value will be used to replace the corresponding part + * in the view file path; + * 3. It will then check if the updated view file exists or not. If so, that file will be used + * to replace the original view file. + * 4. If Step 2 or 3 fails, the original view file will be used. * * For example, if [[pathMap]] is `['/web/views' => '/web/themes/basic']`, * then the themed version for a view file `/web/views/site/index.php` will be * `/web/themes/basic/site/index.php`. * + * It is possible to map a single path to multiple paths. For example, + * + * ~~~ + * 'pathMap' => [ + * '/web/views' => [ + * '/web/themes/christmas', + * '/web/themes/basic', + * ], + * ] + * ~~~ + * + * In this case, the themed version could be either `/web/themes/christmas/site/index.php` or + * `/web/themes/basic/site/index.php`. The former has precedence over the latter if both files exist. + * * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application * component like the following: * @@ -75,16 +96,18 @@ class Theme extends Component if (empty($this->pathMap)) { if ($this->basePath !== null) { $this->basePath = Yii::getAlias($this->basePath); - $this->pathMap = [Yii::$app->getBasePath() => $this->basePath]; + $this->pathMap = [Yii::$app->getBasePath() => [$this->basePath]]; } else { throw new InvalidConfigException('The "basePath" property must be set.'); } } $paths = []; - foreach ($this->pathMap as $from => $to) { + foreach ($this->pathMap as $from => $tos) { $from = FileHelper::normalizePath(Yii::getAlias($from)); - $to = FileHelper::normalizePath(Yii::getAlias($to)); - $paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR; + foreach ((array)$tos as $to) { + $to = FileHelper::normalizePath(Yii::getAlias($to)); + $paths[$from . DIRECTORY_SEPARATOR][] = $to . DIRECTORY_SEPARATOR; + } } $this->pathMap = $paths; if ($this->baseUrl === null) { @@ -103,12 +126,14 @@ class Theme extends Component public function applyTo($path) { $path = FileHelper::normalizePath($path); - foreach ($this->pathMap as $from => $to) { + foreach ($this->pathMap as $from => $tos) { if (strpos($path, $from) === 0) { $n = strlen($from); - $file = $to . substr($path, $n); - if (is_file($file)) { - return $file; + foreach ($tos as $to) { + $file = $to . substr($path, $n); + if (is_file($file)) { + return $file; + } } } } From 64641cbd766366941c3da0b059efc42859d09fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=20=D0=91=D0=B0=D0=B3=D0=B0=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Sat, 2 Nov 2013 10:42:14 +0500 Subject: [PATCH 054/203] Add batchInsert method to yii\db\Migration --- framework/yii/db/Migration.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php index 307b02a..37fdf3f 100644 --- a/framework/yii/db/Migration.php +++ b/framework/yii/db/Migration.php @@ -158,6 +158,21 @@ class Migration extends \yii\base\Component } /** + * Creates and executes an batch INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names. + * @param array $rows the rows to be batch inserted into the table + */ + public function batchInsert($table, $columns, $rows) + { + echo " > insert into $table ..."; + $time = microtime(true); + $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** * Creates and executes an UPDATE SQL statement. * The method will properly escape the column names and bind the values to be updated. * @param string $table the table to be updated. From cc5fe76c9ee421fe75d4e7bd8976ab109deee4a1 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 2 Nov 2013 16:31:30 +0400 Subject: [PATCH 055/203] Added ability to get all GET, POST, PUT, DELETE or PATCH parameters to Request --- framework/yii/web/Request.php | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 610e907..a6a92fa 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -290,59 +290,74 @@ class Request extends \yii\base\Request /** * Returns the named GET parameter value. * If the GET parameter does not exist, the second parameter to this method will be returned. - * @param string $name the GET parameter name + * @param string $name the GET parameter name. If not specified, whole $_GET is returned. * @param mixed $defaultValue the default parameter value if the GET parameter does not exist. * @return mixed the GET parameter value * @see getPost */ - public function get($name, $defaultValue = null) + public function get($name = null, $defaultValue = null) { + if ($name === null) { + return $_GET; + } return isset($_GET[$name]) ? $_GET[$name] : $defaultValue; } /** * Returns the named POST parameter value. * If the POST parameter does not exist, the second parameter to this method will be returned. - * @param string $name the POST parameter name + * @param string $name the POST parameter name. If not specified, whole $_POST is returned. * @param mixed $defaultValue the default parameter value if the POST parameter does not exist. * @return mixed the POST parameter value * @see getParam */ - public function getPost($name, $defaultValue = null) + public function getPost($name = null, $defaultValue = null) { + if ($name === null) { + return $_POST; + } return isset($_POST[$name]) ? $_POST[$name] : $defaultValue; } /** * Returns the named DELETE parameter value. - * @param string $name the DELETE parameter name + * @param string $name the DELETE parameter name. If not specified, an array of DELETE parameters is returned. * @param mixed $defaultValue the default parameter value if the DELETE parameter does not exist. * @return mixed the DELETE parameter value */ - public function getDelete($name, $defaultValue = null) + public function getDelete($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsDelete() ? $this->getRestParam($name, $defaultValue) : null; } /** * Returns the named PUT parameter value. - * @param string $name the PUT parameter name + * @param string $name the PUT parameter name. If not specified, an array of PUT parameters is returned. * @param mixed $defaultValue the default parameter value if the PUT parameter does not exist. * @return mixed the PUT parameter value */ - public function getPut($name, $defaultValue = null) + public function getPut($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsPut() ? $this->getRestParam($name, $defaultValue) : null; } /** * Returns the named PATCH parameter value. - * @param string $name the PATCH parameter name + * @param string $name the PATCH parameter name. If not specified, an array of PATCH parameters is returned. * @param mixed $defaultValue the default parameter value if the PATCH parameter does not exist. * @return mixed the PATCH parameter value */ - public function getPatch($name, $defaultValue = null) + public function getPatch($name = null, $defaultValue = null) { + if ($name === null) { + return $this->getRestParams(); + } return $this->getIsPatch() ? $this->getRestParam($name, $defaultValue) : null; } From 62148a2e33ea372198fb3b5b8e2604d25a2487fc Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 2 Nov 2013 16:39:24 +0400 Subject: [PATCH 056/203] Changed php-diff dependency to use code from master (they haven't tagged changes we need yet) --- framework/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/composer.json b/framework/composer.json index 419b4b7..05c4360 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -68,7 +68,7 @@ "yiisoft/yii2-composer": "*", "ext-mbstring": "*", "lib-pcre": "*", - "phpspec/php-diff": "1.0.*", + "phpspec/php-diff": "dev-master", "ezyang/htmlpurifier": "4.5.*" }, "autoload": { From 18fbd7510a1f5c01ece1079f7346dc957812888e Mon Sep 17 00:00:00 2001 From: Alexander Mohorev Date: Sat, 2 Nov 2013 16:12:28 +0300 Subject: [PATCH 057/203] Missing return statement --- framework/yii/db/ActiveRecord.php | 2 ++ framework/yii/db/mssql/Schema.php | 2 ++ framework/yii/i18n/GettextMoFile.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 79d5146..2c1689c 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -1266,6 +1266,8 @@ class ActiveRecord extends Model $relation = $this->$getter(); if ($relation instanceof ActiveRelation) { return $relation; + } else { + return null; } } catch (UnknownMethodException $e) { throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index 0bb5924..deb92f9 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -118,6 +118,8 @@ class Schema extends \yii\db\Schema if ($this->findColumns($table)) { $this->findForeignKeys($table); return $table; + } else { + return null; } } diff --git a/framework/yii/i18n/GettextMoFile.php b/framework/yii/i18n/GettextMoFile.php index a92293c..4a0a93c 100644 --- a/framework/yii/i18n/GettextMoFile.php +++ b/framework/yii/i18n/GettextMoFile.php @@ -203,6 +203,8 @@ class GettextMoFile extends GettextFile { if ($byteCount > 0) { return fread($fileHandle, $byteCount); + } else { + return null; } } From 5860599ef7fc6788203348579c5cb8ca133a7760 Mon Sep 17 00:00:00 2001 From: Alexander Mohorev Date: Sat, 2 Nov 2013 17:03:37 +0300 Subject: [PATCH 058/203] PhpDoc comment --- framework/yii/db/sqlite/QueryBuilder.php | 1 + framework/yii/i18n/GettextMoFile.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 2a6f345..4a5407f 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -80,6 +80,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @param boolean $check whether to turn on or off the integrity check. * @param string $schema the schema of the tables. Meaningless for SQLite. * @param string $table the table name. Meaningless for SQLite. + * @return string the SQL statement for checking integrity * @throws NotSupportedException this is not supported by SQLite */ public function checkIntegrity($check = true, $schema = '', $table = '') diff --git a/framework/yii/i18n/GettextMoFile.php b/framework/yii/i18n/GettextMoFile.php index 4a0a93c..b4a016d 100644 --- a/framework/yii/i18n/GettextMoFile.php +++ b/framework/yii/i18n/GettextMoFile.php @@ -54,6 +54,7 @@ class GettextMoFile extends GettextFile * @param string $context message context * @return array message translations. Array keys are source messages and array values are translated messages: * source message => translated message. + * @throws Exception if unable to read the MO file */ public function load($filePath, $context) { @@ -128,6 +129,7 @@ class GettextMoFile extends GettextFile * @param array $messages message translations. Array keys are source messages and array values are * translated messages: source message => translated message. Note if the message has a context, * the message ID must be prefixed with the context with chr(4) as the separator. + * @throws Exception if unable to save the MO file */ public function save($filePath, $messages) { From eee63f172114970a9fd59801ff8b8d074e9f47e2 Mon Sep 17 00:00:00 2001 From: Alexander Mohorev Date: Sat, 2 Nov 2013 17:53:48 +0300 Subject: [PATCH 059/203] Small typos --- docs/api/db/ActiveRecord.md | 2 +- docs/guide/authorization.md | 2 +- docs/guide/i18n.md | 2 +- docs/guide/query-builder.md | 2 +- docs/guide/validation.md | 2 +- docs/guide/view.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md index 70f171b..ef050d0 100644 --- a/docs/api/db/ActiveRecord.md +++ b/docs/api/db/ActiveRecord.md @@ -1,6 +1,6 @@ ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). The idea is that an ActiveRecord object is associated with a row in a database table -so object properties are mapped to colums of the corresponding database row. +so object properties are mapped to columns of the corresponding database row. For example, a `Customer` object is associated with a row in the `tbl_customer` table. Instead of writing raw SQL statements to access the data in the table, you can call intuitive methods available in the corresponding ActiveRecord class diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md index 47b9409..b49f1af 100644 --- a/docs/guide/authorization.md +++ b/docs/guide/authorization.md @@ -7,7 +7,7 @@ of controlling it. Access control basics --------------------- -Basic acces control is very simple to implement using [[\yii\web\AccessControl]]: +Basic access control is very simple to implement using [[\yii\web\AccessControl]]: ```php class SiteController extends Controller diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 33f7758..6524801 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -121,7 +121,7 @@ extension. After installing and enabling it you will be able to use extended syn that allows you to specify formatting style. Full reference is [available at ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but since it's -a bit crypric we have our own reference below. +a bit cryptic we have our own reference below. ### Numbers diff --git a/docs/guide/query-builder.md b/docs/guide/query-builder.md index ffc9871..7625c0b 100644 --- a/docs/guide/query-builder.md +++ b/docs/guide/query-builder.md @@ -45,7 +45,7 @@ $query->select(['tbl_user.name AS author', 'tbl_post.title as title']) // <-- sp ->leftJoin('tbl_post', 'tbl_post.user_id = tbl_user.id'); // <-- join with another table ``` -In the code above we've used `leftJoin` method to select from two related tables at the same time. Firsrt parameter +In the code above we've used `leftJoin` method to select from two related tables at the same time. First parameter specifies table name and the second is the join condition. Query builder has the following methods to join tables: - `innerJoin` diff --git a/docs/guide/validation.md b/docs/guide/validation.md index c8c8674..59242f5 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -8,7 +8,7 @@ Standard Yii validators ----------------------- Standard Yii validators could be specified using aliases instead of referring to class names. Here's the list of all -validators budled with Yii with their most useful properties: +validators bundled with Yii with their most useful properties: ### `boolean`: [[BooleanValidator]] diff --git a/docs/guide/view.md b/docs/guide/view.md index ee469e6..1069e89 100644 --- a/docs/guide/view.md +++ b/docs/guide/view.md @@ -38,7 +38,7 @@ Widgets Widgets are a self-contained building blocks for your views. A widget may contain advanced logic, typically takes some configuration and data and returns HTML. There is a good number of widgets bundled with Yii such as [active form](form.md), -breadcrumbs, menu or [wrappers around bootstrap component framework](boostrap-widgets.md). Additionally there are +breadcrumbs, menu or [wrappers around bootstrap component framework](bootstrap-widgets.md). Additionally there are extensions providing additional widgets such as official one for jQueryUI components. In order to use widget you need to do the following: From 5d17dd06d12a2702a6faeb0e8710c4c88b88a4de Mon Sep 17 00:00:00 2001 From: Alexander Mohorev Date: Sat, 2 Nov 2013 17:57:37 +0300 Subject: [PATCH 060/203] Specify the exact type of the exception. --- framework/yii/base/Module.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 4bc5351..b3e28f6 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -241,7 +241,7 @@ abstract class Module extends Component * Sets the directory that contains the controller classes. * @param string $path the directory that contains the controller classes. * This can be either a directory name or a path alias. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setControllerPath($path) { @@ -264,7 +264,7 @@ abstract class Module extends Component /** * Sets the directory that contains the view files. * @param string $path the root directory of view files. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setViewPath($path) { @@ -287,7 +287,7 @@ abstract class Module extends Component /** * Sets the directory that contains the layout files. * @param string $path the root directory of layout files. - * @throws Exception if the directory is invalid + * @throws InvalidParamException if the directory is invalid */ public function setLayoutPath($path) { From 1d092d17555fe76d2656dd5557bfc7bd828d0f35 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 2 Nov 2013 13:06:04 -0400 Subject: [PATCH 061/203] Changed the exit status to normal. --- apps/advanced/init | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/advanced/init b/apps/advanced/init index c8f7f73..4015748 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -18,7 +18,7 @@ if (empty($params['env'])) { if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { echo "\n Quit initialization.\n"; - exit(1); + exit(0); } if (isset($envNames[$answer])) { @@ -42,7 +42,7 @@ if (empty($params['env'])) { $answer = trim(fgets(STDIN)); if (strncasecmp($answer, 'y', 1)) { echo "\n Quit initialization.\n"; - exit(1); + exit(0); } } From 13c3123ca8fbfe832c46ae3bf449f2b0eedc1cad Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 2 Nov 2013 14:16:38 -0400 Subject: [PATCH 062/203] moved mutex back from extensions. --- extensions/mutex/LICENSE.md | 32 --------- extensions/mutex/README.md | 42 ------------ extensions/mutex/composer.json | 27 -------- extensions/mutex/yii/mutex/DbMutex.php | 41 ------------ extensions/mutex/yii/mutex/FileMutex.php | 104 ------------------------------ extensions/mutex/yii/mutex/Mutex.php | 95 --------------------------- extensions/mutex/yii/mutex/MysqlMutex.php | 57 ---------------- framework/yii/mutex/DbMutex.php | 41 ++++++++++++ framework/yii/mutex/FileMutex.php | 104 ++++++++++++++++++++++++++++++ framework/yii/mutex/Mutex.php | 95 +++++++++++++++++++++++++++ framework/yii/mutex/MysqlMutex.php | 57 ++++++++++++++++ 11 files changed, 297 insertions(+), 398 deletions(-) delete mode 100644 extensions/mutex/LICENSE.md delete mode 100644 extensions/mutex/README.md delete mode 100644 extensions/mutex/composer.json delete mode 100644 extensions/mutex/yii/mutex/DbMutex.php delete mode 100644 extensions/mutex/yii/mutex/FileMutex.php delete mode 100644 extensions/mutex/yii/mutex/Mutex.php delete mode 100644 extensions/mutex/yii/mutex/MysqlMutex.php create mode 100644 framework/yii/mutex/DbMutex.php create mode 100644 framework/yii/mutex/FileMutex.php create mode 100644 framework/yii/mutex/Mutex.php create mode 100644 framework/yii/mutex/MysqlMutex.php diff --git a/extensions/mutex/LICENSE.md b/extensions/mutex/LICENSE.md deleted file mode 100644 index 0bb1a8d..0000000 --- a/extensions/mutex/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/mutex/README.md b/extensions/mutex/README.md deleted file mode 100644 index 161ee8a..0000000 --- a/extensions/mutex/README.md +++ /dev/null @@ -1,42 +0,0 @@ -Yii 2.0 Public Preview - Mutex Extension -======================================== - -Thank you for choosing Yii - a high-performance component-based PHP framework. - -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) - -This is the yii2-mutex extension. - - -Installation ------------- - -The prefered way to install this extension is through [composer](http://getcomposer.org/download/). - -Either run -``` -php composer.phar require yiisoft/yii2-mutex "*" -``` - -or add -``` -"yiisoft/yii2-mutex": "*" -``` -to the require section of your composer.json. - - -*Note: You might have to run `php composer.phar selfupdate`* - - -Usage & Documentation ---------------------- - -This component can be used to perform actions similar to the concept of [mutual exclusion](http://en.wikipedia.org/wiki/Mutual_exclusion). - -For concrete examples and advanced usage refer to the yii guide. diff --git a/extensions/mutex/composer.json b/extensions/mutex/composer.json deleted file mode 100644 index e0079ef..0000000 --- a/extensions/mutex/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "yiisoft/yii2-mutex", - "description": "Mutual exclusion extension for the Yii framework", - "keywords": ["yii", "mutex"], - "type": "yii2-extension", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "forum": "http://www.yiiframework.com/forum/", - "wiki": "http://www.yiiframework.com/wiki/", - "irc": "irc://irc.freenode.net/yii", - "source": "https://github.com/yiisoft/yii2" - }, - "authors": [ - { - "name": "resurtm", - "email": "resurtm@gmail.com" - } - ], - "minimum-stability": "dev", - "require": { - "yiisoft/yii2": "*" - }, - "autoload": { - "psr-0": { "yii\\mutex\\": "" } - } -} diff --git a/extensions/mutex/yii/mutex/DbMutex.php b/extensions/mutex/yii/mutex/DbMutex.php deleted file mode 100644 index 3699c36..0000000 --- a/extensions/mutex/yii/mutex/DbMutex.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @since 2.0 - */ -abstract class DbMutex extends Mutex -{ - /** - * @var Connection|string the DB connection object or the application component ID of the DB connection. - * After the Mutex object is created, if you want to change this property, you should only assign - * it with a DB connection object. - */ - public $db = 'db'; - - /** - * Initializes generic database table based mutex implementation. - * @throws InvalidConfigException if [[db]] is invalid. - */ - public function init() - { - parent::init(); - if (is_string($this->db)) { - $this->db = Yii::$app->getComponent($this->db); - } - if (!$this->db instanceof Connection) { - throw new InvalidConfigException('Mutex::db must be either a DB connection instance or the application component ID of a DB connection.'); - } - } -} diff --git a/extensions/mutex/yii/mutex/FileMutex.php b/extensions/mutex/yii/mutex/FileMutex.php deleted file mode 100644 index fd5cb00..0000000 --- a/extensions/mutex/yii/mutex/FileMutex.php +++ /dev/null @@ -1,104 +0,0 @@ - - * @since 2.0 - */ -class FileMutex extends Mutex -{ - /** - * @var string the directory to store mutex files. You may use path alias here. - * Defaults to the "mutex" subdirectory under the application runtime path. - */ - public $mutexPath = '@runtime/mutex'; - /** - * @var integer the permission to be set for newly created mutex files. - * This value will be used by PHP chmod() function. No umask will be applied. - * If not set, the permission will be determined by the current environment. - */ - public $fileMode; - /** - * @var integer the permission to be set for newly created directories. - * This value will be used by PHP chmod() function. No umask will be applied. - * Defaults to 0775, meaning the directory is read-writable by owner and group, - * but read-only for other users. - */ - public $dirMode = 0775; - /** - * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. - */ - private $_files = []; - - - /** - * Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like - * operating systems. - * @throws InvalidConfigException - */ - public function init() - { - if (stripos(php_uname('s'), 'win') === 0) { - throw new InvalidConfigException('FileMutex does not have MS Windows operating system support.'); - } - $this->mutexPath = Yii::getAlias($this->mutexPath); - if (!is_dir($this->mutexPath)) { - FileHelper::createDirectory($this->mutexPath, $this->dirMode, true); - } - } - - /** - * Acquires lock by given name. - * @param string $name of the lock to be acquired. - * @param integer $timeout to wait for lock to become released. - * @return boolean acquiring result. - */ - protected function acquireLock($name, $timeout = 0) - { - $fileName = $this->mutexPath . '/' . md5($name) . '.lock'; - $file = fopen($fileName, 'w+'); - if ($file === false) { - return false; - } - if ($this->fileMode !== null) { - @chmod($fileName, $this->fileMode); - } - $waitTime = 0; - while (!flock($file, LOCK_EX | LOCK_NB)) { - $waitTime++; - if ($waitTime > $timeout) { - fclose($file); - return false; - } - sleep(1); - } - $this->_files[$name] = $file; - return true; - } - - /** - * Releases lock by given name. - * @param string $name of the lock to be released. - * @return boolean release result. - */ - protected function releaseLock($name) - { - if (!isset($this->_files[$name]) || !flock($this->_files[$name], LOCK_UN)) { - return false; - } else { - fclose($this->_files[$name]); - unset($this->_files[$name]); - return true; - } - } -} diff --git a/extensions/mutex/yii/mutex/Mutex.php b/extensions/mutex/yii/mutex/Mutex.php deleted file mode 100644 index 611e725..0000000 --- a/extensions/mutex/yii/mutex/Mutex.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @since 2.0 - */ -abstract class Mutex extends Component -{ - /** - * @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically - * before finishing script execution. Defaults to true. Setting this property to true means that all locks - * acquire in this process must be released in any case (regardless any kind of errors or exceptions). - */ - public $autoRelease = true; - /** - * @var string[] names of the locks acquired in the current PHP process. - */ - private $_locks = []; - - - /** - * Initializes the mutex component. - */ - public function init() - { - if ($this->autoRelease) { - $locks = &$this->_locks; - register_shutdown_function(function () use (&$locks) { - foreach ($locks as $lock) { - $this->release($lock); - } - }); - } - } - - /** - * Acquires lock by given name. - * @param string $name of the lock to be acquired. Must be unique. - * @param integer $timeout to wait for lock to be released. Defaults to zero meaning that method will return - * false immediately in case lock was already acquired. - * @return boolean lock acquiring result. - */ - public function acquire($name, $timeout = 0) - { - if ($this->acquireLock($name, $timeout)) { - $this->_locks[] = $name; - return true; - } else { - return false; - } - } - - /** - * Release acquired lock. This method will return false in case named lock was not found. - * @param string $name of the lock to be released. This lock must be already created. - * @return boolean lock release result: false in case named lock was not found.. - */ - public function release($name) - { - if ($this->releaseLock($name)) { - $index = array_search($name, $this->_locks); - if ($index !== false) { - unset($this->_locks[$index]); - } - return true; - } else { - return false; - } - } - - /** - * This method should be extended by concrete mutex implementations. Acquires lock by given name. - * @param string $name of the lock to be acquired. - * @param integer $timeout to wait for lock to become released. - * @return boolean acquiring result. - */ - abstract protected function acquireLock($name, $timeout = 0); - - /** - * This method should be extended by concrete mutex implementations. Releases lock by given name. - * @param string $name of the lock to be released. - * @return boolean release result. - */ - abstract protected function releaseLock($name); -} diff --git a/extensions/mutex/yii/mutex/MysqlMutex.php b/extensions/mutex/yii/mutex/MysqlMutex.php deleted file mode 100644 index af05b9c..0000000 --- a/extensions/mutex/yii/mutex/MysqlMutex.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @since 2.0 - */ -class MysqlMutex extends Mutex -{ - /** - * Initializes MySQL specific mutex component implementation. - * @throws InvalidConfigException if [[db]] is not MySQL connection. - */ - public function init() - { - parent::init(); - if ($this->db->driverName !== 'mysql') { - throw new InvalidConfigException('In order to use MysqlMutex connection must be configured to use MySQL database.'); - } - } - - /** - * Acquires lock by given name. - * @param string $name of the lock to be acquired. - * @param integer $timeout to wait for lock to become released. - * @return boolean acquiring result. - * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock - */ - protected function acquireLock($name, $timeout = 0) - { - return (boolean)$this->db - ->createCommand('SELECT GET_LOCK(:name, :timeout)', [':name' => $name, ':timeout' => $timeout]) - ->queryScalar(); - } - - /** - * Releases lock by given name. - * @param string $name of the lock to be released. - * @return boolean release result. - * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock - */ - protected function releaseLock($name) - { - return (boolean)$this->db - ->createCommand('SELECT RELEASE_LOCK(:name)', [':name' => $name]) - ->queryScalar(); - } -} diff --git a/framework/yii/mutex/DbMutex.php b/framework/yii/mutex/DbMutex.php new file mode 100644 index 0000000..3699c36 --- /dev/null +++ b/framework/yii/mutex/DbMutex.php @@ -0,0 +1,41 @@ + + * @since 2.0 + */ +abstract class DbMutex extends Mutex +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the Mutex object is created, if you want to change this property, you should only assign + * it with a DB connection object. + */ + public $db = 'db'; + + /** + * Initializes generic database table based mutex implementation. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException('Mutex::db must be either a DB connection instance or the application component ID of a DB connection.'); + } + } +} diff --git a/framework/yii/mutex/FileMutex.php b/framework/yii/mutex/FileMutex.php new file mode 100644 index 0000000..fd5cb00 --- /dev/null +++ b/framework/yii/mutex/FileMutex.php @@ -0,0 +1,104 @@ + + * @since 2.0 + */ +class FileMutex extends Mutex +{ + /** + * @var string the directory to store mutex files. You may use path alias here. + * Defaults to the "mutex" subdirectory under the application runtime path. + */ + public $mutexPath = '@runtime/mutex'; + /** + * @var integer the permission to be set for newly created mutex files. + * This value will be used by PHP chmod() function. No umask will be applied. + * If not set, the permission will be determined by the current environment. + */ + public $fileMode; + /** + * @var integer the permission to be set for newly created directories. + * This value will be used by PHP chmod() function. No umask will be applied. + * Defaults to 0775, meaning the directory is read-writable by owner and group, + * but read-only for other users. + */ + public $dirMode = 0775; + /** + * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. + */ + private $_files = []; + + + /** + * Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like + * operating systems. + * @throws InvalidConfigException + */ + public function init() + { + if (stripos(php_uname('s'), 'win') === 0) { + throw new InvalidConfigException('FileMutex does not have MS Windows operating system support.'); + } + $this->mutexPath = Yii::getAlias($this->mutexPath); + if (!is_dir($this->mutexPath)) { + FileHelper::createDirectory($this->mutexPath, $this->dirMode, true); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + */ + protected function acquireLock($name, $timeout = 0) + { + $fileName = $this->mutexPath . '/' . md5($name) . '.lock'; + $file = fopen($fileName, 'w+'); + if ($file === false) { + return false; + } + if ($this->fileMode !== null) { + @chmod($fileName, $this->fileMode); + } + $waitTime = 0; + while (!flock($file, LOCK_EX | LOCK_NB)) { + $waitTime++; + if ($waitTime > $timeout) { + fclose($file); + return false; + } + sleep(1); + } + $this->_files[$name] = $file; + return true; + } + + /** + * Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + */ + protected function releaseLock($name) + { + if (!isset($this->_files[$name]) || !flock($this->_files[$name], LOCK_UN)) { + return false; + } else { + fclose($this->_files[$name]); + unset($this->_files[$name]); + return true; + } + } +} diff --git a/framework/yii/mutex/Mutex.php b/framework/yii/mutex/Mutex.php new file mode 100644 index 0000000..611e725 --- /dev/null +++ b/framework/yii/mutex/Mutex.php @@ -0,0 +1,95 @@ + + * @since 2.0 + */ +abstract class Mutex extends Component +{ + /** + * @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically + * before finishing script execution. Defaults to true. Setting this property to true means that all locks + * acquire in this process must be released in any case (regardless any kind of errors or exceptions). + */ + public $autoRelease = true; + /** + * @var string[] names of the locks acquired in the current PHP process. + */ + private $_locks = []; + + + /** + * Initializes the mutex component. + */ + public function init() + { + if ($this->autoRelease) { + $locks = &$this->_locks; + register_shutdown_function(function () use (&$locks) { + foreach ($locks as $lock) { + $this->release($lock); + } + }); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. Must be unique. + * @param integer $timeout to wait for lock to be released. Defaults to zero meaning that method will return + * false immediately in case lock was already acquired. + * @return boolean lock acquiring result. + */ + public function acquire($name, $timeout = 0) + { + if ($this->acquireLock($name, $timeout)) { + $this->_locks[] = $name; + return true; + } else { + return false; + } + } + + /** + * Release acquired lock. This method will return false in case named lock was not found. + * @param string $name of the lock to be released. This lock must be already created. + * @return boolean lock release result: false in case named lock was not found.. + */ + public function release($name) + { + if ($this->releaseLock($name)) { + $index = array_search($name, $this->_locks); + if ($index !== false) { + unset($this->_locks[$index]); + } + return true; + } else { + return false; + } + } + + /** + * This method should be extended by concrete mutex implementations. Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + */ + abstract protected function acquireLock($name, $timeout = 0); + + /** + * This method should be extended by concrete mutex implementations. Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + */ + abstract protected function releaseLock($name); +} diff --git a/framework/yii/mutex/MysqlMutex.php b/framework/yii/mutex/MysqlMutex.php new file mode 100644 index 0000000..af05b9c --- /dev/null +++ b/framework/yii/mutex/MysqlMutex.php @@ -0,0 +1,57 @@ + + * @since 2.0 + */ +class MysqlMutex extends Mutex +{ + /** + * Initializes MySQL specific mutex component implementation. + * @throws InvalidConfigException if [[db]] is not MySQL connection. + */ + public function init() + { + parent::init(); + if ($this->db->driverName !== 'mysql') { + throw new InvalidConfigException('In order to use MysqlMutex connection must be configured to use MySQL database.'); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock + */ + protected function acquireLock($name, $timeout = 0) + { + return (boolean)$this->db + ->createCommand('SELECT GET_LOCK(:name, :timeout)', [':name' => $name, ':timeout' => $timeout]) + ->queryScalar(); + } + + /** + * Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock + */ + protected function releaseLock($name) + { + return (boolean)$this->db + ->createCommand('SELECT RELEASE_LOCK(:name)', [':name' => $name]) + ->queryScalar(); + } +} From baf6de3c0caa2ee8525e43e3c26d0d8b0be798c4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 2 Nov 2013 14:52:24 -0400 Subject: [PATCH 063/203] Adjusted jui directories. --- extensions/jui/Accordion.php | 121 ++ extensions/jui/AccordionAsset.php | 26 + extensions/jui/AutoComplete.php | 66 + extensions/jui/AutoCompleteAsset.php | 25 + extensions/jui/ButtonAsset.php | 24 + extensions/jui/CoreAsset.php | 27 + extensions/jui/DatePicker.php | 110 + extensions/jui/DatePickerAsset.php | 25 + extensions/jui/DatePickerRegionalAsset.php | 24 + extensions/jui/Dialog.php | 52 + extensions/jui/DialogAsset.php | 27 + extensions/jui/Draggable.php | 50 + extensions/jui/DraggableAsset.php | 24 + extensions/jui/Droppable.php | 50 + extensions/jui/DroppableAsset.php | 24 + extensions/jui/EffectAsset.php | 24 + extensions/jui/Extension.php | 27 + extensions/jui/InputWidget.php | 59 + extensions/jui/Menu.php | 78 + extensions/jui/MenuAsset.php | 24 + extensions/jui/ProgressBar.php | 60 + extensions/jui/ProgressBarAsset.php | 24 + extensions/jui/Resizable.php | 52 + extensions/jui/ResizableAsset.php | 24 + extensions/jui/Selectable.php | 116 + extensions/jui/SelectableAsset.php | 24 + extensions/jui/Slider.php | 68 + extensions/jui/SliderAsset.php | 24 + extensions/jui/Sortable.php | 106 + extensions/jui/SortableAsset.php | 24 + extensions/jui/Spinner.php | 62 + extensions/jui/SpinnerAsset.php | 25 + extensions/jui/Tabs.php | 145 ++ extensions/jui/TabsAsset.php | 25 + extensions/jui/ThemeAsset.php | 21 + extensions/jui/TooltipAsset.php | 25 + extensions/jui/Widget.php | 87 + extensions/jui/assets.php | 23 + extensions/jui/assets/UPGRADE.md | 14 + extensions/jui/assets/jquery.ui.accordion.js | 572 +++++ extensions/jui/assets/jquery.ui.autocomplete.js | 610 ++++++ extensions/jui/assets/jquery.ui.button.js | 419 ++++ extensions/jui/assets/jquery.ui.core.js | 320 +++ extensions/jui/assets/jquery.ui.datepicker-i18n.js | 1793 ++++++++++++++++ extensions/jui/assets/jquery.ui.datepicker.js | 2038 ++++++++++++++++++ extensions/jui/assets/jquery.ui.dialog.js | 808 +++++++ extensions/jui/assets/jquery.ui.draggable.js | 958 +++++++++ extensions/jui/assets/jquery.ui.droppable.js | 372 ++++ extensions/jui/assets/jquery.ui.effect-all.js | 2261 ++++++++++++++++++++ extensions/jui/assets/jquery.ui.menu.js | 621 ++++++ extensions/jui/assets/jquery.ui.mouse.js | 169 ++ extensions/jui/assets/jquery.ui.position.js | 497 +++++ extensions/jui/assets/jquery.ui.progressbar.js | 145 ++ extensions/jui/assets/jquery.ui.resizable.js | 968 +++++++++ extensions/jui/assets/jquery.ui.selectable.js | 277 +++ extensions/jui/assets/jquery.ui.slider.js | 672 ++++++ extensions/jui/assets/jquery.ui.sortable.js | 1285 +++++++++++ extensions/jui/assets/jquery.ui.spinner.js | 493 +++++ extensions/jui/assets/jquery.ui.tabs.js | 846 ++++++++ extensions/jui/assets/jquery.ui.tooltip.js | 402 ++++ extensions/jui/assets/jquery.ui.widget.js | 521 +++++ .../jui/assets/theme/images/animated-overlay.gif | Bin 0 -> 1738 bytes .../theme/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 212 bytes .../theme/images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 208 bytes .../theme/images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 335 bytes .../theme/images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 207 bytes .../theme/images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 262 bytes .../theme/images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 262 bytes .../theme/images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 332 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 280 bytes .../theme/images/ui-icons_222222_256x240.png | Bin 0 -> 6922 bytes .../theme/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4549 bytes .../theme/images/ui-icons_454545_256x240.png | Bin 0 -> 6992 bytes .../theme/images/ui-icons_888888_256x240.png | Bin 0 -> 6999 bytes .../theme/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4549 bytes extensions/jui/assets/theme/jquery.ui.css | 1177 ++++++++++ extensions/jui/composer.json | 1 + extensions/jui/yii/jui/Accordion.php | 121 -- extensions/jui/yii/jui/AccordionAsset.php | 26 - extensions/jui/yii/jui/AutoComplete.php | 66 - extensions/jui/yii/jui/AutoCompleteAsset.php | 25 - extensions/jui/yii/jui/ButtonAsset.php | 24 - extensions/jui/yii/jui/CoreAsset.php | 27 - extensions/jui/yii/jui/DatePicker.php | 110 - extensions/jui/yii/jui/DatePickerAsset.php | 25 - extensions/jui/yii/jui/DatePickerRegionalAsset.php | 24 - extensions/jui/yii/jui/Dialog.php | 52 - extensions/jui/yii/jui/DialogAsset.php | 27 - extensions/jui/yii/jui/Draggable.php | 50 - extensions/jui/yii/jui/DraggableAsset.php | 24 - extensions/jui/yii/jui/Droppable.php | 50 - extensions/jui/yii/jui/DroppableAsset.php | 24 - extensions/jui/yii/jui/EffectAsset.php | 24 - extensions/jui/yii/jui/Extension.php | 27 - extensions/jui/yii/jui/InputWidget.php | 59 - extensions/jui/yii/jui/Menu.php | 78 - extensions/jui/yii/jui/MenuAsset.php | 24 - extensions/jui/yii/jui/ProgressBar.php | 60 - extensions/jui/yii/jui/ProgressBarAsset.php | 24 - extensions/jui/yii/jui/Resizable.php | 52 - extensions/jui/yii/jui/ResizableAsset.php | 24 - extensions/jui/yii/jui/Selectable.php | 116 - extensions/jui/yii/jui/SelectableAsset.php | 24 - extensions/jui/yii/jui/Slider.php | 68 - extensions/jui/yii/jui/SliderAsset.php | 24 - extensions/jui/yii/jui/Sortable.php | 106 - extensions/jui/yii/jui/SortableAsset.php | 24 - extensions/jui/yii/jui/Spinner.php | 62 - extensions/jui/yii/jui/SpinnerAsset.php | 25 - extensions/jui/yii/jui/Tabs.php | 145 -- extensions/jui/yii/jui/TabsAsset.php | 25 - extensions/jui/yii/jui/ThemeAsset.php | 21 - extensions/jui/yii/jui/TooltipAsset.php | 25 - extensions/jui/yii/jui/Widget.php | 87 - extensions/jui/yii/jui/assets.php | 23 - extensions/jui/yii/jui/assets/UPGRADE.md | 14 - .../jui/yii/jui/assets/jquery.ui.accordion.js | 572 ----- .../jui/yii/jui/assets/jquery.ui.autocomplete.js | 610 ------ extensions/jui/yii/jui/assets/jquery.ui.button.js | 419 ---- extensions/jui/yii/jui/assets/jquery.ui.core.js | 320 --- .../yii/jui/assets/jquery.ui.datepicker-i18n.js | 1793 ---------------- .../jui/yii/jui/assets/jquery.ui.datepicker.js | 2038 ------------------ extensions/jui/yii/jui/assets/jquery.ui.dialog.js | 808 ------- .../jui/yii/jui/assets/jquery.ui.draggable.js | 958 --------- .../jui/yii/jui/assets/jquery.ui.droppable.js | 372 ---- .../jui/yii/jui/assets/jquery.ui.effect-all.js | 2261 -------------------- extensions/jui/yii/jui/assets/jquery.ui.menu.js | 621 ------ extensions/jui/yii/jui/assets/jquery.ui.mouse.js | 169 -- .../jui/yii/jui/assets/jquery.ui.position.js | 497 ----- .../jui/yii/jui/assets/jquery.ui.progressbar.js | 145 -- .../jui/yii/jui/assets/jquery.ui.resizable.js | 968 --------- .../jui/yii/jui/assets/jquery.ui.selectable.js | 277 --- extensions/jui/yii/jui/assets/jquery.ui.slider.js | 672 ------ .../jui/yii/jui/assets/jquery.ui.sortable.js | 1285 ----------- extensions/jui/yii/jui/assets/jquery.ui.spinner.js | 493 ----- extensions/jui/yii/jui/assets/jquery.ui.tabs.js | 846 -------- extensions/jui/yii/jui/assets/jquery.ui.tooltip.js | 402 ---- extensions/jui/yii/jui/assets/jquery.ui.widget.js | 521 ----- .../jui/assets/theme/images/animated-overlay.gif | Bin 1738 -> 0 bytes .../theme/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 212 -> 0 bytes .../theme/images/ui-bg_flat_75_ffffff_40x100.png | Bin 208 -> 0 bytes .../theme/images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 335 -> 0 bytes .../theme/images/ui-bg_glass_65_ffffff_1x400.png | Bin 207 -> 0 bytes .../theme/images/ui-bg_glass_75_dadada_1x400.png | Bin 262 -> 0 bytes .../theme/images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 262 -> 0 bytes .../theme/images/ui-bg_glass_95_fef1ec_1x400.png | Bin 332 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 280 -> 0 bytes .../theme/images/ui-icons_222222_256x240.png | Bin 6922 -> 0 bytes .../theme/images/ui-icons_2e83ff_256x240.png | Bin 4549 -> 0 bytes .../theme/images/ui-icons_454545_256x240.png | Bin 6992 -> 0 bytes .../theme/images/ui-icons_888888_256x240.png | Bin 6999 -> 0 bytes .../theme/images/ui-icons_cd0a0a_256x240.png | Bin 4549 -> 0 bytes extensions/jui/yii/jui/assets/theme/jquery.ui.css | 1177 ---------- 153 files changed, 20061 insertions(+), 20060 deletions(-) create mode 100644 extensions/jui/Accordion.php create mode 100644 extensions/jui/AccordionAsset.php create mode 100644 extensions/jui/AutoComplete.php create mode 100644 extensions/jui/AutoCompleteAsset.php create mode 100644 extensions/jui/ButtonAsset.php create mode 100644 extensions/jui/CoreAsset.php create mode 100644 extensions/jui/DatePicker.php create mode 100644 extensions/jui/DatePickerAsset.php create mode 100644 extensions/jui/DatePickerRegionalAsset.php create mode 100644 extensions/jui/Dialog.php create mode 100644 extensions/jui/DialogAsset.php create mode 100644 extensions/jui/Draggable.php create mode 100644 extensions/jui/DraggableAsset.php create mode 100644 extensions/jui/Droppable.php create mode 100644 extensions/jui/DroppableAsset.php create mode 100644 extensions/jui/EffectAsset.php create mode 100644 extensions/jui/Extension.php create mode 100644 extensions/jui/InputWidget.php create mode 100644 extensions/jui/Menu.php create mode 100644 extensions/jui/MenuAsset.php create mode 100644 extensions/jui/ProgressBar.php create mode 100644 extensions/jui/ProgressBarAsset.php create mode 100644 extensions/jui/Resizable.php create mode 100644 extensions/jui/ResizableAsset.php create mode 100644 extensions/jui/Selectable.php create mode 100644 extensions/jui/SelectableAsset.php create mode 100644 extensions/jui/Slider.php create mode 100644 extensions/jui/SliderAsset.php create mode 100644 extensions/jui/Sortable.php create mode 100644 extensions/jui/SortableAsset.php create mode 100644 extensions/jui/Spinner.php create mode 100644 extensions/jui/SpinnerAsset.php create mode 100644 extensions/jui/Tabs.php create mode 100644 extensions/jui/TabsAsset.php create mode 100644 extensions/jui/ThemeAsset.php create mode 100644 extensions/jui/TooltipAsset.php create mode 100644 extensions/jui/Widget.php create mode 100644 extensions/jui/assets.php create mode 100644 extensions/jui/assets/UPGRADE.md create mode 100644 extensions/jui/assets/jquery.ui.accordion.js create mode 100644 extensions/jui/assets/jquery.ui.autocomplete.js create mode 100644 extensions/jui/assets/jquery.ui.button.js create mode 100644 extensions/jui/assets/jquery.ui.core.js create mode 100755 extensions/jui/assets/jquery.ui.datepicker-i18n.js create mode 100644 extensions/jui/assets/jquery.ui.datepicker.js create mode 100644 extensions/jui/assets/jquery.ui.dialog.js create mode 100644 extensions/jui/assets/jquery.ui.draggable.js create mode 100644 extensions/jui/assets/jquery.ui.droppable.js create mode 100755 extensions/jui/assets/jquery.ui.effect-all.js create mode 100644 extensions/jui/assets/jquery.ui.menu.js create mode 100644 extensions/jui/assets/jquery.ui.mouse.js create mode 100644 extensions/jui/assets/jquery.ui.position.js create mode 100644 extensions/jui/assets/jquery.ui.progressbar.js create mode 100644 extensions/jui/assets/jquery.ui.resizable.js create mode 100644 extensions/jui/assets/jquery.ui.selectable.js create mode 100644 extensions/jui/assets/jquery.ui.slider.js create mode 100644 extensions/jui/assets/jquery.ui.sortable.js create mode 100644 extensions/jui/assets/jquery.ui.spinner.js create mode 100644 extensions/jui/assets/jquery.ui.tabs.js create mode 100644 extensions/jui/assets/jquery.ui.tooltip.js create mode 100644 extensions/jui/assets/jquery.ui.widget.js create mode 100755 extensions/jui/assets/theme/images/animated-overlay.gif create mode 100755 extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100755 extensions/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100755 extensions/jui/assets/theme/images/ui-icons_222222_256x240.png create mode 100755 extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png create mode 100755 extensions/jui/assets/theme/images/ui-icons_454545_256x240.png create mode 100755 extensions/jui/assets/theme/images/ui-icons_888888_256x240.png create mode 100755 extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png create mode 100755 extensions/jui/assets/theme/jquery.ui.css delete mode 100644 extensions/jui/yii/jui/Accordion.php delete mode 100644 extensions/jui/yii/jui/AccordionAsset.php delete mode 100644 extensions/jui/yii/jui/AutoComplete.php delete mode 100644 extensions/jui/yii/jui/AutoCompleteAsset.php delete mode 100644 extensions/jui/yii/jui/ButtonAsset.php delete mode 100644 extensions/jui/yii/jui/CoreAsset.php delete mode 100644 extensions/jui/yii/jui/DatePicker.php delete mode 100644 extensions/jui/yii/jui/DatePickerAsset.php delete mode 100644 extensions/jui/yii/jui/DatePickerRegionalAsset.php delete mode 100644 extensions/jui/yii/jui/Dialog.php delete mode 100644 extensions/jui/yii/jui/DialogAsset.php delete mode 100644 extensions/jui/yii/jui/Draggable.php delete mode 100644 extensions/jui/yii/jui/DraggableAsset.php delete mode 100644 extensions/jui/yii/jui/Droppable.php delete mode 100644 extensions/jui/yii/jui/DroppableAsset.php delete mode 100644 extensions/jui/yii/jui/EffectAsset.php delete mode 100644 extensions/jui/yii/jui/Extension.php delete mode 100644 extensions/jui/yii/jui/InputWidget.php delete mode 100644 extensions/jui/yii/jui/Menu.php delete mode 100644 extensions/jui/yii/jui/MenuAsset.php delete mode 100644 extensions/jui/yii/jui/ProgressBar.php delete mode 100644 extensions/jui/yii/jui/ProgressBarAsset.php delete mode 100644 extensions/jui/yii/jui/Resizable.php delete mode 100644 extensions/jui/yii/jui/ResizableAsset.php delete mode 100644 extensions/jui/yii/jui/Selectable.php delete mode 100644 extensions/jui/yii/jui/SelectableAsset.php delete mode 100644 extensions/jui/yii/jui/Slider.php delete mode 100644 extensions/jui/yii/jui/SliderAsset.php delete mode 100644 extensions/jui/yii/jui/Sortable.php delete mode 100644 extensions/jui/yii/jui/SortableAsset.php delete mode 100644 extensions/jui/yii/jui/Spinner.php delete mode 100644 extensions/jui/yii/jui/SpinnerAsset.php delete mode 100644 extensions/jui/yii/jui/Tabs.php delete mode 100644 extensions/jui/yii/jui/TabsAsset.php delete mode 100644 extensions/jui/yii/jui/ThemeAsset.php delete mode 100644 extensions/jui/yii/jui/TooltipAsset.php delete mode 100644 extensions/jui/yii/jui/Widget.php delete mode 100644 extensions/jui/yii/jui/assets.php delete mode 100644 extensions/jui/yii/jui/assets/UPGRADE.md delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.accordion.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.button.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.core.js delete mode 100755 extensions/jui/yii/jui/assets/jquery.ui.datepicker-i18n.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.datepicker.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.dialog.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.draggable.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.droppable.js delete mode 100755 extensions/jui/yii/jui/assets/jquery.ui.effect-all.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.menu.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.mouse.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.position.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.progressbar.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.resizable.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.selectable.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.slider.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.sortable.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.spinner.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.tabs.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.tooltip.js delete mode 100644 extensions/jui/yii/jui/assets/jquery.ui.widget.js delete mode 100755 extensions/jui/yii/jui/assets/theme/images/animated-overlay.gif delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-icons_222222_256x240.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-icons_2e83ff_256x240.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-icons_454545_256x240.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-icons_888888_256x240.png delete mode 100755 extensions/jui/yii/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png delete mode 100755 extensions/jui/yii/jui/assets/theme/jquery.ui.css diff --git a/extensions/jui/Accordion.php b/extensions/jui/Accordion.php new file mode 100644 index 0000000..42897a9 --- /dev/null +++ b/extensions/jui/Accordion.php @@ -0,0 +1,121 @@ + [ + * [ + * 'header' => 'Section 1', + * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', + * ], + * [ + * 'header' => 'Section 2', + * 'headerOptions' => ['tag' => 'h3'], + * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', + * 'options' => ['tag' => 'div'], + * ], + * ], + * 'options' => ['tag' => 'div'], + * 'itemOptions' => ['tag' => 'div'], + * 'headerOptions' => ['tag' => 'h3'], + * 'clientOptions' => ['collapsible' => false], + * ]); + * ``` + * + * @see http://api.jqueryui.com/accordion/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Accordion 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 = []; + /** + * @var array list of collapsible items. Each item can be an array of the following structure: + * + * ~~~ + * [ + * 'header' => 'Item header', + * 'content' => 'Item content', + * // the HTML attributes of the item header container tag. This will overwrite "headerOptions". + * 'headerOptions' => [], + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => [], + * ] + * ~~~ + */ + public $items = []; + /** + * @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 = []; + /** + * @var array list of HTML attributes for the item header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "h3", the tag name of the item container tags. + */ + public $headerOptions = []; + + + /** + * Renders the widget. + */ + public function run() + { + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + echo Html::beginTag($tag, $options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag($tag) . "\n"; + $this->registerWidget('accordion', AccordionAsset::className()); + } + + /** + * Renders collapsible items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + protected function renderItems() + { + $items = []; + foreach ($this->items as $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."); + } + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); + $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3'); + $items[] = Html::tag($headerTag, $item['header'], $headerOptions); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + $items[] = Html::tag($tag, $item['content'], $options); + } + + return implode("\n", $items); + } +} diff --git a/extensions/jui/AccordionAsset.php b/extensions/jui/AccordionAsset.php new file mode 100644 index 0000000..05c1e20 --- /dev/null +++ b/extensions/jui/AccordionAsset.php @@ -0,0 +1,26 @@ + + * @since 2.0 + */ +class AccordionAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.accordion.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\EffectAsset', + ]; +} diff --git a/extensions/jui/AutoComplete.php b/extensions/jui/AutoComplete.php new file mode 100644 index 0000000..ac0c997 --- /dev/null +++ b/extensions/jui/AutoComplete.php @@ -0,0 +1,66 @@ + $model, + * 'attribute' => 'country', + * 'clientOptions' => [ + * 'source' => ['USA', 'RUS'], + * ], + * ]); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo AutoComplete::widget([ + * 'name' => 'country', + * 'clientOptions' => [ + * 'source' => ['USA', 'RUS'], + * ], + * ]); + *``` + * + * @see http://api.jqueryui.com/autocomplete/ + * @author Alexander Kochetov + * @since 2.0 + */ +class AutoComplete extends InputWidget +{ + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderWidget(); + $this->registerWidget('autocomplete', AutoCompleteAsset::className()); + } + + /** + * Renders the AutoComplete 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); + } + } +} diff --git a/extensions/jui/AutoCompleteAsset.php b/extensions/jui/AutoCompleteAsset.php new file mode 100644 index 0000000..f48e064 --- /dev/null +++ b/extensions/jui/AutoCompleteAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class AutoCompleteAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.autocomplete.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\MenuAsset', + ]; +} diff --git a/extensions/jui/ButtonAsset.php b/extensions/jui/ButtonAsset.php new file mode 100644 index 0000000..6616b34 --- /dev/null +++ b/extensions/jui/ButtonAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class ButtonAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.button.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/CoreAsset.php b/extensions/jui/CoreAsset.php new file mode 100644 index 0000000..d77a25f --- /dev/null +++ b/extensions/jui/CoreAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class CoreAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.core.js', + 'jquery.ui.widget.js', + 'jquery.ui.position.js', + 'jquery.ui.mouse.js', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + ]; +} diff --git a/extensions/jui/DatePicker.php b/extensions/jui/DatePicker.php new file mode 100644 index 0000000..06ca356 --- /dev/null +++ b/extensions/jui/DatePicker.php @@ -0,0 +1,110 @@ + 'ru', + * 'model' => $model, + * 'attribute' => 'country', + * 'clientOptions' => [ + * 'dateFormat' => 'yy-mm-dd', + * ], + * ]); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo DatePicker::widget([ + * 'language' => 'ru', + * 'name' => 'country', + * 'clientOptions' => [ + * 'dateFormat' => 'yy-mm-dd', + * ], + * ]); + *``` + * + * @see http://api.jqueryui.com/datepicker/ + * @author Alexander Kochetov + * @since 2.0 + */ +class DatePicker extends InputWidget +{ + /** + * @var string the locale ID (eg 'fr', 'de') for the language to be used by the date picker. + * If this property set to false, I18N will not be involved. That is, the date picker will show in English. + */ + public $language = false; + /** + * @var boolean If true, shows the widget as an inline calendar and the input as a hidden field. + */ + public $inline = false; + + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderWidget() . "\n"; + if ($this->language !== false) { + $view = $this->getView(); + DatePickerRegionalAsset::register($view); + + $options = Json::encode($this->clientOptions); + $view->registerJs("$('#{$this->options['id']}').datepicker($.extend({}, $.datepicker.regional['{$this->language}'], $options));"); + + $options = $this->clientOptions; + $this->clientOptions = false; // the datepicker js widget is already registered + $this->registerWidget('datepicker', DatePickerAsset::className()); + $this->clientOptions = $options; + } else { + $this->registerWidget('datepicker', DatePickerAsset::className()); + } + } + + /** + * Renders the DatePicker widget. + * @return string the rendering result. + */ + protected function renderWidget() + { + $contents = []; + + if ($this->inline === false) { + if ($this->hasModel()) { + $contents[] = Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + $contents[] = Html::textInput($this->name, $this->value, $this->options); + } + } else { + if ($this->hasModel()) { + $contents[] = Html::activeHiddenInput($this->model, $this->attribute, $this->options); + $this->clientOptions['defaultDate'] = $this->model->{$this->attribute}; + } else { + $contents[] = Html::hiddenInput($this->name, $this->value, $this->options); + $this->clientOptions['defaultDate'] = $this->value; + } + $this->clientOptions['altField'] = '#' . $this->options['id']; + $this->options['id'] .= '-container'; + $contents[] = Html::tag('div', null, $this->options); + } + + return implode("\n", $contents); + } +} diff --git a/extensions/jui/DatePickerAsset.php b/extensions/jui/DatePickerAsset.php new file mode 100644 index 0000000..fddd8df --- /dev/null +++ b/extensions/jui/DatePickerAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class DatePickerAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.datepicker.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\EffectAsset', + ]; +} diff --git a/extensions/jui/DatePickerRegionalAsset.php b/extensions/jui/DatePickerRegionalAsset.php new file mode 100644 index 0000000..249373a --- /dev/null +++ b/extensions/jui/DatePickerRegionalAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class DatePickerRegionalAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.datepicker-i18n.js', + ]; + public $depends = [ + 'yii\jui\DatePickerAsset', + ]; +} diff --git a/extensions/jui/Dialog.php b/extensions/jui/Dialog.php new file mode 100644 index 0000000..a5cbaf2 --- /dev/null +++ b/extensions/jui/Dialog.php @@ -0,0 +1,52 @@ + [ + * 'modal' => true, + * ], + * ]); + * + * echo 'Dialog contents here...'; + * + * Dialog::end(); + * ``` + * + * @see http://api.jqueryui.com/dialog/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Dialog 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('dialog', DialogAsset::className()); + } +} diff --git a/extensions/jui/DialogAsset.php b/extensions/jui/DialogAsset.php new file mode 100644 index 0000000..109243e --- /dev/null +++ b/extensions/jui/DialogAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class DialogAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.dialog.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\ButtonAsset', + 'yii\jui\DraggableAsset', + 'yii\jui\ResizableAsset', + ]; +} diff --git a/extensions/jui/Draggable.php b/extensions/jui/Draggable.php new file mode 100644 index 0000000..02e4973 --- /dev/null +++ b/extensions/jui/Draggable.php @@ -0,0 +1,50 @@ + ['grid' => [50, 20]], + * ]); + * + * 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', DraggableAsset::className()); + } +} diff --git a/extensions/jui/DraggableAsset.php b/extensions/jui/DraggableAsset.php new file mode 100644 index 0000000..f3286a5 --- /dev/null +++ b/extensions/jui/DraggableAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class DraggableAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.draggable.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Droppable.php b/extensions/jui/Droppable.php new file mode 100644 index 0000000..530e736 --- /dev/null +++ b/extensions/jui/Droppable.php @@ -0,0 +1,50 @@ + ['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', DroppableAsset::className()); + } +} diff --git a/extensions/jui/DroppableAsset.php b/extensions/jui/DroppableAsset.php new file mode 100644 index 0000000..84b64b8 --- /dev/null +++ b/extensions/jui/DroppableAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class DroppableAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.droppable.js', + ]; + public $depends = [ + 'yii\jui\DraggableAsset', + ]; +} diff --git a/extensions/jui/EffectAsset.php b/extensions/jui/EffectAsset.php new file mode 100644 index 0000000..79c5aaa --- /dev/null +++ b/extensions/jui/EffectAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class EffectAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.effect-all.js', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + ]; +} diff --git a/extensions/jui/Extension.php b/extensions/jui/Extension.php new file mode 100644 index 0000000..4b680ce --- /dev/null +++ b/extensions/jui/Extension.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class Extension extends \yii\base\Extension +{ + /** + * @inheritdoc + */ + public static function init() + { + Yii::setAlias('@yii/jui', __DIR__); + } +} diff --git a/extensions/jui/InputWidget.php b/extensions/jui/InputWidget.php new file mode 100644 index 0000000..e100d6c --- /dev/null +++ b/extensions/jui/InputWidget.php @@ -0,0 +1,59 @@ + + * @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/extensions/jui/Menu.php b/extensions/jui/Menu.php new file mode 100644 index 0000000..46c7ee0 --- /dev/null +++ b/extensions/jui/Menu.php @@ -0,0 +1,78 @@ + + * @since 2.0 + */ +class Menu extends \yii\widgets\Menu +{ + /** + * @var array the options for the underlying jQuery UI widget. + * Please refer to the corresponding jQuery UI widget Web page for possible options. + * For example, [this page](http://api.jqueryui.com/accordion/) shows + * how to use the "Accordion" widget and the supported options (e.g. "header"). + */ + public $clientOptions = []; + /** + * @var array the event handlers for the underlying jQuery UI widget. + * Please refer to the corresponding jQuery UI widget Web page for possible events. + * For example, [this page](http://api.jqueryui.com/accordion/) shows + * how to use the "Accordion" widget and the supported events (e.g. "create"). + */ + public $clientEvents = []; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + } + + /** + * Renders the widget. + */ + public function run() + { + parent::run(); + + $view = $this->getView(); + MenuAsset::register($view); + /** @var \yii\web\AssetBundle $themeAsset */ + $themeAsset = Widget::$theme; + $themeAsset::register($view); + + $id = $this->options['id']; + if ($this->clientOptions !== false) { + $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); + $js = "jQuery('#$id').menu($options);"; + $view->registerJs($js); + } + + if (!empty($this->clientEvents)) { + $js = []; + foreach ($this->clientEvents as $event => $handler) { + $js[] = "jQuery('#$id').on('menu$event', $handler);"; + } + $view->registerJs(implode("\n", $js)); + } + } +} diff --git a/extensions/jui/MenuAsset.php b/extensions/jui/MenuAsset.php new file mode 100644 index 0000000..8b840a8 --- /dev/null +++ b/extensions/jui/MenuAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class MenuAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.menu.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/ProgressBar.php b/extensions/jui/ProgressBar.php new file mode 100644 index 0000000..1c555f1 --- /dev/null +++ b/extensions/jui/ProgressBar.php @@ -0,0 +1,60 @@ + [ + * 'value' => 75, + * ], + * ]); + * ``` + * + * The following example will show the content enclosed between the [[begin()]] + * and [[end()]] calls within the widget container: + * + * ~~~php + * ProgressBar::widget([ + * 'clientOptions' => ['value' => 75], + * ]); + * + * echo '
Loading...
'; + * + * ProgressBar::end(); + * ~~~ + * @see http://api.jqueryui.com/progressbar/ + * @author Alexander Kochetov + * @since 2.0 + */ +class ProgressBar 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('progressbar', ProgressBarAsset::className()); + } +} diff --git a/extensions/jui/ProgressBarAsset.php b/extensions/jui/ProgressBarAsset.php new file mode 100644 index 0000000..d485fbd --- /dev/null +++ b/extensions/jui/ProgressBarAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class ProgressBarAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.progressbar.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Resizable.php b/extensions/jui/Resizable.php new file mode 100644 index 0000000..bcff9a8 --- /dev/null +++ b/extensions/jui/Resizable.php @@ -0,0 +1,52 @@ + [ + * 'grid' => [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', ResizableAsset::className()); + } +} diff --git a/extensions/jui/ResizableAsset.php b/extensions/jui/ResizableAsset.php new file mode 100644 index 0000000..acf4c73 --- /dev/null +++ b/extensions/jui/ResizableAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class ResizableAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.resizable.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Selectable.php b/extensions/jui/Selectable.php new file mode 100644 index 0000000..94e4faf --- /dev/null +++ b/extensions/jui/Selectable.php @@ -0,0 +1,116 @@ + [ + * 'Item 1', + * [ + * 'content' => 'Item2', + * ], + * [ + * 'content' => 'Item3', + * 'options' => [ + * 'tag' => 'li', + * ], + * ], + * ), + * 'options' => [ + * 'tag' => 'ul', + * ], + * 'itemOptions' => [ + * 'tag' => 'li', + * ], + * 'clientOptions' => [ + * '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 = []; + /** + * @var array list of selectable items. Each item can be a string representing the item content + * or an array of the following structure: + * + * ~~~ + * [ + * 'content' => 'item content', + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => [], + * ] + * ~~~ + */ + public $items = []; + /** + * @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 = []; + + + /** + * 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', SelectableAsset::className()); + } + + /** + * Renders selectable items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + public function renderItems() + { + $items = []; + 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', [])); + $tag = ArrayHelper::remove($options, 'tag', $tag); + $items[] = Html::tag($tag, $item['content'], $options); + } else { + $items[] = Html::tag($tag, $item, $options); + } + } + return implode("\n", $items); + } +} diff --git a/extensions/jui/SelectableAsset.php b/extensions/jui/SelectableAsset.php new file mode 100644 index 0000000..61f405f --- /dev/null +++ b/extensions/jui/SelectableAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class SelectableAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.selectable.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Slider.php b/extensions/jui/Slider.php new file mode 100644 index 0000000..c19f2db --- /dev/null +++ b/extensions/jui/Slider.php @@ -0,0 +1,68 @@ + $model, + * 'attribute' => 'amount', + * 'clientOptions' => [ + * 'min' => 1, + * 'max' => 10, + * ], + * ]); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo Slider::widget([ + * 'name' => 'amount', + * 'clientOptions' => [ + * 'min' => 1, + * 'max' => 10, + * ], + * ]); + *``` + * + * @see http://api.jqueryui.com/slider/ + * @author Alexander Makarov + * @since 2.0 + */ +class Slider extends InputWidget +{ + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderWidget(); + $this->registerWidget('slider', SliderAsset::className()); + } + + /** + * Renders the Slider 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); + } + } +} diff --git a/extensions/jui/SliderAsset.php b/extensions/jui/SliderAsset.php new file mode 100644 index 0000000..56c2451 --- /dev/null +++ b/extensions/jui/SliderAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class SliderAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.slider.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Sortable.php b/extensions/jui/Sortable.php new file mode 100644 index 0000000..6209cb6 --- /dev/null +++ b/extensions/jui/Sortable.php @@ -0,0 +1,106 @@ + [ + * 'Item 1', + * ['content' => 'Item2'], + * [ + * 'content' => 'Item3', + * 'options' => ['tag' => 'li'], + * ], + * ], + * 'options' => ['tag' => 'ul'], + * 'itemOptions' => ['tag' => 'li'], + * 'clientOptions' => ['cursor' => 'move'], + * )); + * ``` + * + * @see http://api.jqueryui.com/sortable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Sortable 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 = []; + /** + * @var array list of sortable items. Each item can be a string representing the item content + * or an array of the following structure: + * + * ~~~ + * [ + * 'content' => 'item content', + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => [], + * ] + * ~~~ + */ + public $items = []; + /** + * @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 = []; + + + /** + * 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('sortable', SortableAsset::className()); + } + + /** + * Renders sortable items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + public function renderItems() + { + $items = []; + 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', [])); + $tag = ArrayHelper::remove($options, 'tag', $tag); + $items[] = Html::tag($tag, $item['content'], $options); + } else { + $items[] = Html::tag($tag, $item, $options); + } + } + return implode("\n", $items); + } +} diff --git a/extensions/jui/SortableAsset.php b/extensions/jui/SortableAsset.php new file mode 100644 index 0000000..69c9ba3 --- /dev/null +++ b/extensions/jui/SortableAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class SortableAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.sortable.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + ]; +} diff --git a/extensions/jui/Spinner.php b/extensions/jui/Spinner.php new file mode 100644 index 0000000..caf73f3 --- /dev/null +++ b/extensions/jui/Spinner.php @@ -0,0 +1,62 @@ + $model, + * 'attribute' => 'country', + * 'clientOptions' => ['step' => 2], + * ]); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo Spinner::widget([ + * 'name' => 'country', + * 'clientOptions' => ['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', SpinnerAsset::className()); + } + + /** + * 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); + } + } +} diff --git a/extensions/jui/SpinnerAsset.php b/extensions/jui/SpinnerAsset.php new file mode 100644 index 0000000..89a8c59 --- /dev/null +++ b/extensions/jui/SpinnerAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class SpinnerAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.spinner.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\ButtonAsset', + ]; +} diff --git a/extensions/jui/Tabs.php b/extensions/jui/Tabs.php new file mode 100644 index 0000000..9d2a2be --- /dev/null +++ b/extensions/jui/Tabs.php @@ -0,0 +1,145 @@ + [ + * [ + * 'label' => 'Tab one', + * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', + * ], + * [ + * 'label' => 'Tab two', + * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', + * 'options' => ['tag' => 'div'], + * 'headerOptions' => ['class' => 'my-class'], + * ], + * [ + * 'label' => 'Tab with custom id', + * 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...', + * 'options' => ['id' => 'my-tab'], + * ], + * [ + * 'label' => 'Ajax tab', + * 'url' => ['ajax/content'], + * ], + * ), + * 'options' => ['tag' => 'div'], + * 'itemOptions' => ['tag' => 'div'], + * 'headerOptions' => ['class' => 'my-class'], + * 'clientOptions' => ['collapsible' => false], + * ]); + * ``` + * + * @see http://api.jqueryui.com/tabs/ + * @author Alexander Kochetov + * @since 2.0 + */ +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 = []; + /** + * @var array list of tab items. Each item can be an array of the following structure: + * + * - 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, 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 = []; + /** + * @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 = []; + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = []; + /** + * @var string the default header template to render the link. + */ + public $linkTemplate = '{label}'; + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Renders the widget. + */ + public function run() + { + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + echo Html::beginTag($tag, $options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag($tag) . "\n"; + $this->registerWidget('tabs', TabsAsset::className()); + } + + /** + * Renders tab items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + protected function renderItems() + { + $headers = []; + $items = []; + foreach ($this->items as $n => $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + if (isset($item['url'])) { + $url = Html::url($item['url']); + } else { + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' or 'url' option is required."); + } + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); + $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 = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); + $headers[] = Html::tag('li', strtr($template, [ + '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], + '{url}' => $url, + ]), $headerOptions); + } + return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); + } +} diff --git a/extensions/jui/TabsAsset.php b/extensions/jui/TabsAsset.php new file mode 100644 index 0000000..5bef4c0 --- /dev/null +++ b/extensions/jui/TabsAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class TabsAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.tabs.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\EffectAsset', + ]; +} diff --git a/extensions/jui/ThemeAsset.php b/extensions/jui/ThemeAsset.php new file mode 100644 index 0000000..dedcb00 --- /dev/null +++ b/extensions/jui/ThemeAsset.php @@ -0,0 +1,21 @@ + + * @since 2.0 + */ +class ThemeAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $css = [ + 'theme/jquery.ui.css', + ]; +} diff --git a/extensions/jui/TooltipAsset.php b/extensions/jui/TooltipAsset.php new file mode 100644 index 0000000..1fa4490 --- /dev/null +++ b/extensions/jui/TooltipAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class TooltipAsset extends AssetBundle +{ + public $sourcePath = '@yii/jui/assets'; + public $js = [ + 'jquery.ui.tooltip.js', + ]; + public $depends = [ + 'yii\jui\CoreAsset', + 'yii\jui\EffectAsset', + ]; +} diff --git a/extensions/jui/Widget.php b/extensions/jui/Widget.php new file mode 100644 index 0000000..2bbc9e4 --- /dev/null +++ b/extensions/jui/Widget.php @@ -0,0 +1,87 @@ + + * @since 2.0 + */ +class Widget extends \yii\base\Widget +{ + /** + * @var string the jQuery UI theme. This refers to an asset bundle class + * representing the JUI theme. The default theme is the official "Smoothness" theme. + */ + public static $theme = 'yii\jui\ThemeAsset'; + /** + * @var array the HTML attributes for the widget container tag. + */ + public $options = []; + /** + * @var array the options for the underlying jQuery UI widget. + * Please refer to the corresponding jQuery UI widget Web page for possible options. + * For example, [this page](http://api.jqueryui.com/accordion/) shows + * how to use the "Accordion" widget and the supported options (e.g. "header"). + */ + public $clientOptions = []; + /** + * @var array the event handlers for the underlying jQuery UI widget. + * Please refer to the corresponding jQuery UI widget Web page for possible events. + * For example, [this page](http://api.jqueryui.com/accordion/) shows + * how to use the "Accordion" widget and the supported events (e.g. "create"). + */ + public $clientEvents = []; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + } + + /** + * Registers a specific jQuery UI widget and the related events + * @param string $name the name of the jQuery UI widget + * @param string $assetBundle the asset bundle for the widget + */ + protected function registerWidget($name, $assetBundle) + { + $view = $this->getView(); + /** @var \yii\web\AssetBundle $assetBundle */ + $assetBundle::register($view); + /** @var \yii\web\AssetBundle $themeAsset */ + $themeAsset = self::$theme; + $themeAsset::register($view); + + $id = $this->options['id']; + if ($this->clientOptions !== false) { + $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); + $js = "jQuery('#$id').$name($options);"; + $view->registerJs($js); + } + + if (!empty($this->clientEvents)) { + $js = []; + foreach ($this->clientEvents as $event => $handler) { + $js[] = "jQuery('#$id').on('$name$event', $handler);"; + } + $view->registerJs(implode("\n", $js)); + } + } +} diff --git a/extensions/jui/assets.php b/extensions/jui/assets.php new file mode 100644 index 0000000..ab4c930 --- /dev/null +++ b/extensions/jui/assets.php @@ -0,0 +1,23 @@ + li > :first-child,> :not(li):even", + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // callbacks + activate: null, + beforeActivate: null + }, + + _create: function() { + var options = this.options; + this.prevShow = this.prevHide = $(); + this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) + // ARIA + .attr( "role", "tablist" ); + + // don't allow collapsible: false and active: false / null + if ( !options.collapsible && (options.active === false || options.active == null) ) { + options.active = 0; + } + + this._processPanels(); + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next(), + content: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icons = this.options.icons; + if ( icons ) { + $( "" ) + .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-accordion-header-icon" ) + .removeClass( icons.header ) + .addClass( icons.activeHeader ); + this.headers.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers + .removeClass( "ui-accordion-icons" ) + .children( ".ui-accordion-header-icon" ) + .remove(); + }, + + _destroy: function() { + var contents; + + // clean up main element + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + // clean up headers + this.headers + .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-controls" ) + .removeAttr( "tabIndex" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + this._destroyIcons(); + + // clean up content panels + contents = this.headers.next() + .css( "display", "" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-labelledby" ) + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key === "disabled" ) { + this.headers.add( this.headers.next() ) + .toggleClass( "ui-state-disabled", !!value ); + } + }, + + _keydown: function( event ) { + /*jshint maxcomplexity:15*/ + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + event.preventDefault(); + } + }, + + _panelKeyDown : function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().focus(); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { + options.active = false; + this.active = $(); + // active false only when collapsible is true + } else if ( options.active === false ) { + this._activate( 0 ); + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + // all remaining panel are disabled + if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { + options.active = false; + this.active = $(); + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + // was active, active panel still exists + } else { + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + this.headers = this.element.find( this.options.header ) + .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); + + this.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) + .filter(":not(.ui-accordion-content-active)") + .hide(); + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(), + accordionId = this.accordionId = "ui-accordion-" + + (this.element.attr( "id" ) || ++uid); + + this.active = this._findActive( options.active ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) + .removeClass( "ui-corner-all" ); + this.active.next() + .addClass( "ui-accordion-content-active" ) + .show(); + + this.headers + .attr( "role", "tab" ) + .each(function( i ) { + var header = $( this ), + headerId = header.attr( "id" ), + panel = header.next(), + panelId = panel.attr( "id" ); + if ( !headerId ) { + headerId = accordionId + "-header-" + i; + header.attr( "id", headerId ); + } + if ( !panelId ) { + panelId = accordionId + "-panel-" + i; + panel.attr( "id", panelId ); + } + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + }) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr({ + "aria-selected": "false", + tabIndex: -1 + }) + .next() + .attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }) + .hide(); + + // make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr({ + "aria-selected": "true", + tabIndex: 0 + }) + .next() + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + }) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler({ + target: active, + currentTarget: active, + preventDefault: $.noop + }); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" }); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // switch classes + // corner classes on the previously active header stay after the animation + active.removeClass( "ui-accordion-header-active ui-state-active" ); + if ( options.icons ) { + active.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.activeHeader ) + .addClass( options.icons.header ); + } + + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-corner-all" ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); + if ( options.icons ) { + clicked.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.activeHeader ); + } + + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + toHide.prev().attr( "aria-selected", "false" ); + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.headers.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }) + .prev() + .attr({ + "aria-selected": "true", + tabIndex: 0 + }); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + }); + toShow + .hide() + .animate( showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + adjust += fx.now; + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + }); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel; + + toHide + .removeClass( "ui-accordion-content-active" ) + .prev() + .removeClass( "ui-corner-top" ) + .addClass( "ui-corner-all" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[0].className = toHide.parent()[0].className; + } + + this._trigger( "activate", null, data ); + } +}); + +})( jQuery ); diff --git a/extensions/jui/assets/jquery.ui.autocomplete.js b/extensions/jui/assets/jquery.ui.autocomplete.js new file mode 100644 index 0000000..ca53d2c --- /dev/null +++ b/extensions/jui/assets/jquery.ui.autocomplete.js @@ -0,0 +1,610 @@ +/*! + * jQuery UI Autocomplete 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/autocomplete/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + * jquery.ui.menu.js + */ +(function( $, undefined ) { + +// used to prevent race conditions with remote data sources +var requestIndex = 0; + +$.widget( "ui.autocomplete", { + version: "1.10.3", + defaultElement: "", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + pending: 0, + + _create: function() { + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[0].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + this.isMultiLine = + // Textareas are always multi-line + isTextarea ? true : + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + isInput ? false : + // All other element types are determined by whether or not they're contentEditable + this.element.prop( "isContentEditable" ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + /*jshint maxcomplexity:15*/ + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + case keyCode.NUMPAD_ENTER: + // when menu is open and has focus + if ( this.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + this._value( this.term ); + this.close( event ); + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + event.preventDefault(); + } + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + }); + + this._initSource(); + this.menu = $( "
    " ) + .addClass( "ui-autocomplete ui-front" ) + .appendTo( this._appendTo() ) + .menu({ + // disable ARIA support, the live region takes care of that + role: null + }) + .hide() + .data( "ui-menu" ); + + this._on( this.menu.element, { + mousedown: function( event ) { + // prevent moving focus out of the text field + event.preventDefault(); + + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + }); + + // clicking on the scrollbar causes focus to shift to the body + // but we can't detect a mouseup or a click immediately afterward + // so we have to track the next mousedown and close the menu if + // the user clicks somewhere outside of the autocomplete + var menuElement = this.menu.element[ 0 ]; + if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { + this._delay(function() { + var that = this; + this.document.one( "mousedown", function( event ) { + if ( event.target !== that.element[ 0 ] && + event.target !== menuElement && + !$.contains( menuElement, event.target ) ) { + that.close(); + } + }); + }); + } + }, + menufocus: function( event, ui ) { + // support: Firefox + // Prevent accidental activation of menu items in Firefox (#7024 #9118) + if ( this.isNewMenu ) { + this.isNewMenu = false; + if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { + this.menu.blur(); + + this.document.one( "mousemove", function() { + $( event.target ).trigger( event.originalEvent ); + }); + + return; + } + } + + var item = ui.item.data( "ui-autocomplete-item" ); + if ( false !== this._trigger( "focus", event, { item: item } ) ) { + // use value to match what will end up in the input, if it was a key event + if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { + this._value( item.value ); + } + } else { + // Normally the input is populated with the item's value as the + // menu is navigated, causing screen readers to notice a change and + // announce the item. Since the focus event was canceled, this doesn't + // happen, so we update the live region so that screen readers can + // still notice the change and announce it. + this.liveRegion.text( item.value ); + } + }, + menuselect: function( event, ui ) { + var item = ui.item.data( "ui-autocomplete-item" ), + previous = this.previous; + + // only trigger when focus was lost (click on menu) + if ( this.element[0] !== this.document[0].activeElement ) { + this.element.focus(); + this.previous = previous; + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + this._delay(function() { + this.previous = previous; + this.selectedItem = item; + }); + } + + if ( false !== this._trigger( "select", event, { item: item } ) ) { + this._value( item.value ); + } + // reset the term after the select event + // this allows custom select handling to work properly + this.term = this._value(); + + this.close( event ); + this.selectedItem = item; + } + }); + + this.liveRegion = $( "", { + role: "status", + "aria-live": "polite" + }) + .addClass( "ui-helper-hidden-accessible" ) + .insertBefore( this.element ); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _destroy: function() { + clearTimeout( this.searching ); + this.element + .removeClass( "ui-autocomplete-input" ) + .removeAttr( "autocomplete" ); + this.menu.element.remove(); + this.liveRegion.remove(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( this._appendTo() ); + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element ) { + element = this.element.closest( ".ui-front" ); + } + + if ( !element.length ) { + element = this.document[0].body; + } + + return element; + }, + + _initSource: function() { + var array, url, + that = this; + if ( $.isArray(this.options.source) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter( array, request.term ) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( that.xhr ) { + that.xhr.abort(); + } + that.xhr = $.ajax({ + url: url, + data: request, + dataType: "json", + success: function( data ) { + response( data ); + }, + error: function() { + response( [] ); + } + }); + }; + } else { + this.source = this.options.source; + } + }, + + _searchTimeout: function( event ) { + clearTimeout( this.searching ); + this.searching = this._delay(function() { + // only search if the value has changed + if ( this.term !== this._value() ) { + this.selectedItem = null; + this.search( null, event ); + } + }, this.options.delay ); + }, + + search: function( value, event ) { + value = value != null ? value : this._value(); + + // always save the actual value, not the one passed as an argument + this.term = this._value(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this.element.addClass( "ui-autocomplete-loading" ); + this.cancelSearch = false; + + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var that = this, + index = ++requestIndex; + + return function( content ) { + if ( index === requestIndex ) { + that.__response( content ); + } + + that.pending--; + if ( !that.pending ) { + that.element.removeClass( "ui-autocomplete-loading" ); + } + }; + }, + + __response: function( content ) { + if ( content ) { + content = this._normalize( content ); + } + this._trigger( "response", null, { content: content } ); + if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { + this._suggest( content ); + this._trigger( "open" ); + } else { + // use ._close() instead of .close() so we don't cancel future searches + this._close(); + } + }, + + close: function( event ) { + this.cancelSearch = true; + this._close( event ); + }, + + _close: function( event ) { + if ( this.menu.element.is( ":visible" ) ) { + this.menu.element.hide(); + this.menu.blur(); + this.isNewMenu = true; + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this._value() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + // assume all items have the right format when the first item is complete + if ( items.length && items[0].label && items[0].value ) { + return items; + } + return $.map( items, function( item ) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend({ + label: item.label || item.value, + value: item.value || item.label + }, item ); + }); + }, + + _suggest: function( items ) { + var ul = this.menu.element.empty(); + this._renderMenu( ul, items ); + this.isNewMenu = true; + this.menu.refresh(); + + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend({ + of: this.element + }, this.options.position )); + + if ( this.options.autoFocus ) { + this.menu.next(); + } + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + }); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + }, + + _renderItem: function( ul, item ) { + return $( "
  • " ) + .append( $( "" ).text( item.label ) ) + .appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is( ":visible" ) ) { + this.search( null, event ); + return; + } + if ( this.menu.isFirstItem() && /^previous/.test( direction ) || + this.menu.isLastItem() && /^next/.test( direction ) ) { + this._value( this.term ); + this.menu.blur(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + }, + + _value: function() { + return this.valueMethod.apply( this.element, arguments ); + }, + + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } + } +}); + +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + }, + filter: function(array, term) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); + return $.grep( array, function(value) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + + +// live region extension, adding a `messages` option +// NOTE: This is an experimental API. We are still investigating +// a full solution for string manipulation and internationalization. +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } + } + }, + + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.text( message ); + } +}); + +}( jQuery )); diff --git a/extensions/jui/assets/jquery.ui.button.js b/extensions/jui/assets/jquery.ui.button.js new file mode 100644 index 0000000..5926642 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.button.js @@ -0,0 +1,419 @@ +/*! + * jQuery UI Button 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/button/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var lastActive, startXPos, startYPos, clickDragged, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + stateClasses = "ui-state-hover ui-state-active ", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function() { + var form = $( this ); + setTimeout(function() { + form.find( ":ui-button" ).button( "refresh" ); + }, 1 ); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + name = name.replace( /'/g, "\\'" ); + if ( form ) { + radios = $( form ).find( "[name='" + name + "']" ); + } else { + radios = $( "[name='" + name + "']", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } + } + return radios; + }; + +$.widget( "ui.button", { + version: "1.10.3", + defaultElement: "").addClass(this._triggerClass). + html(!buttonImage ? buttonText : $("").attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? "before" : "after"](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { + $.datepicker._hideDatepicker(); + } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else { + $.datepicker._showDatepicker(input[0]); + } + return false; + }); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, "autoSize") && !inst.inline) { + var findMax, max, maxI, i, + date = new Date(2009, 12 - 1, 20), // Ensure double digits + dateFormat = this._get(inst, "dateFormat"); + + if (dateFormat.match(/[DM]/)) { + findMax = function(names) { + max = 0; + maxI = 0; + for (i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + "monthNames" : "monthNamesShort")))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); + } + inst.input.attr("size", this._formatDate(inst, date).length); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) { + return; + } + divSpan.addClass(this.markerClassName).append(inst.dpDiv); + $.data(target, PROP_NAME, inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if (!inst) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $(""); + this._dialogInput.keydown(this._doKeyDown); + $("body").append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], PROP_NAME, inst); + } + extendRemove(inst.settings, settings || {}); + date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); + + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } + + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) { + $.blockUI(this.dpDiv); + } + $.data(this._dialogInput[0], PROP_NAME, inst); + return this; + }, + + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function(target) { + var nodeName, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData(target, PROP_NAME); + if (nodeName === "input") { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind("focus", this._showDatepicker). + unbind("keydown", this._doKeyDown). + unbind("keypress", this._doKeyPress). + unbind("keyup", this._doKeyUp); + } else if (nodeName === "div" || nodeName === "span") { + $target.removeClass(this.markerClassName).empty(); + } + }, + + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = false; + inst.trigger.filter("button"). + each(function() { this.disabled = false; }).end(). + filter("img").css({opacity: "1.0", cursor: ""}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().removeClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", false); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = true; + inst.trigger.filter("button"). + each(function() { this.disabled = true; }).end(). + filter("img").css({opacity: "0.5", cursor: "default"}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().addClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", true); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] === target) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function(target) { + try { + return $.data(target, PROP_NAME); + } + catch (err) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function(target, name, value) { + var settings, date, minDate, maxDate, + inst = this._getInst(target); + + if (arguments.length === 2 && typeof name === "string") { + return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : + (inst ? (name === "all" ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); + } + + settings = name || {}; + if (typeof name === "string") { + settings = {}; + settings[name] = value; + } + + if (inst) { + if (this._curInst === inst) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker(target, true); + minDate = this._getMinMaxDate(inst, "min"); + maxDate = this._getMinMaxDate(inst, "max"); + extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { + inst.settings.minDate = this._formatDate(inst, minDate); + } + if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { + inst.settings.maxDate = this._formatDate(inst, maxDate); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker(target); + } else { + this._enableDatepicker(target); + } + } + this._attachments($(target), inst); + this._autoSize(inst); + this._setDate(inst, date); + this._updateAlternate(inst); + this._updateDatepicker(inst); + } + }, + + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, + + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); + } + }, + + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date + */ + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date + */ + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) { + this._setDateFromField(inst, noDefault); + } + return (inst ? this._getDate(inst) : null); + }, + + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst(event.target), + handled = true, + isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); + + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) { + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv); + if (sel[0]) { + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + } + + onSelect = $.datepicker._get(inst, "onSelect"); + if (onSelect) { + dateStr = $.datepicker._formatDate(inst); + + // trigger custom callback + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) { + $.datepicker._clearDate(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) { + $.datepicker._gotoToday(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +left on Mac + break; + case 38: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, -7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +right + break; + case 40: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, +7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + } else { + handled = false; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var chars, chr, + inst = $.datepicker._getInst(event.target); + + if ($.datepicker._get(inst, "constrainInput")) { + chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); + chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var date, + inst = $.datepicker._getInst(event.target); + + if (inst.input.val() !== inst.lastVal) { + try { + date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); + + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (err) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus + */ + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger + input = $("input", input.parentNode)[0]; + } + + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst !== inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); + } + } + + beforeShow = $.datepicker._get(inst, "beforeShow"); + beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; + if(beforeShowSettings === false){ + return; + } + extendRemove(inst.settings, beforeShowSettings); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); + + if ($.datepicker._inDialog) { // hide cursor + input.value = ""; + } + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height + } + + isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css("position") === "fixed"; + return !isFixed; + }); + + offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + "static" : (isFixed ? "fixed" : "absolute")), display: "none", + left: offset.left + "px", top: offset.top + "px"}); + + if (!inst.inline) { + showAnim = $.datepicker._get(inst, "showAnim"); + duration = $.datepicker._get(inst, "duration"); + inst.dpDiv.zIndex($(input).zIndex()+1); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); + } else { + inst.dpDiv[showAnim || "show"](showAnim ? duration : null); + } + + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + instActive = inst; // for delegate hover events + inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); + inst.dpDiv.find("." + this._dayOverClass + " a").mouseover(); + + var origyearshtml, + numMonths = this._getNumberOfMonths(inst), + cols = numMonths[1], + width = 17; + + inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); + if (cols > 1) { + inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); + } + inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + + "Class"]("ui-datepicker-multi"); + inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + + "Class"]("ui-datepicker-rtl"); + + if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml && inst.yearshtml ){ + inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); + } + }, + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), + viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); + + offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var position, + inst = this._getInst(obj), + isRTL = this._get(inst, "isRTL"); + + while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? "previousSibling" : "nextSibling"]; + } + + position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function(input) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if (!inst || (input && inst !== $.data(input, PROP_NAME))) { + return; + } + + if (this._datepickerShowing) { + showAnim = this._get(inst, "showAnim"); + duration = this._get(inst, "duration"); + postProcess = function() { + $.datepicker._tidyDialog(inst); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); + } else { + inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : + (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); + } + + if (!showAnim) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get(inst, "onClose"); + if (onClose) { + onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); + } + + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); + if ($.blockUI) { + $.unblockUI(); + $("body").append(this.dpDiv); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) { + return; + } + + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); + + if ( ( ( $target[0].id !== $.datepicker._mainDivId && + $target.parents("#" + $.datepicker._mainDivId).length === 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.closest("." + $.datepicker._triggerClass).length && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id), + inst = this._getInst(target[0]); + + if (this._isDisabledDatepicker(target[0])) { + return; + } + this._adjustInstDate(inst, offset + + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, + + /* Action for current link. */ + _gotoToday: function(id) { + var date, + target = $(id), + inst = this._getInst(target[0]); + + if (this._get(inst, "gotoCurrent") && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id), + inst = this._getInst(target[0]); + + inst["selected" + (period === "M" ? "Month" : "Year")] = + inst["draw" + (period === "M" ? "Month" : "Year")] = + parseInt(select.options[select.selectedIndex].value,10); + + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var inst, + target = $(id); + + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; + } + + inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $("a", td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + this._selectDate(target, ""); + }, + + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var onSelect, + target = $(id), + inst = this._getInst(target[0]); + + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) { + inst.input.val(dateStr); + } + this._updateAlternate(inst); + + onSelect = this._get(inst, "onSelect"); + if (onSelect) { + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + } else if (inst.input) { + inst.input.trigger("change"); // fire the change event + } + + if (inst.inline){ + this._updateDatepicker(inst); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) !== "object") { + inst.input.focus(); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altFormat, date, dateStr, + altField = this._get(inst, "altField"); + + if (altField) { // update alternate field too + altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); + date = this._getDate(inst); + dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), ""]; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function(date) { + var time, + checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, + + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function (format, value, settings) { + if (format == null || value == null) { + throw "Invalid arguments"; + } + + value = (typeof value === "object" ? value.toString() : value + ""); + if (value === "") { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, + shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Extract a number from the string value + getNumber = function(match) { + var isDoubled = lookAhead(match), + size = (match === "@" ? 14 : (match === "!" ? 20 : + (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), + digits = new RegExp("^\\d{1," + size + "}"), + num = value.substring(iValue).match(digits); + if (!num) { + throw "Missing number at position " + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }, + // Extract a name from the string value and convert to an index + getName = function(match, shortNames, longNames) { + var index = -1, + names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { + return [ [k, v] ]; + }).sort(function (a, b) { + return -(a[1].length - b[1].length); + }); + + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index !== -1) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + // Confirm that a literal character matches the string value + checkLiteral = function() { + if (value.charAt(iValue) !== format.charAt(iFormat)) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + day = getNumber("d"); + break; + case "D": + getName("D", dayNamesShort, dayNames); + break; + case "o": + doy = getNumber("o"); + break; + case "m": + month = getNumber("m"); + break; + case "M": + month = getName("M", monthNamesShort, monthNames); + break; + case "y": + year = getNumber("y"); + break; + case "@": + date = new Date(getNumber("@")); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date((getNumber("!") - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")){ + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if (iValue < value.length){ + extra = value.substr(iValue); + if (!/^\s+/.test(extra)) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if (year === -1) { + year = new Date().getFullYear(); + } else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); + } + + if (doy > -1) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) { + break; + } + month++; + day -= dim; + } while (true); + } + + date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + + /* Format a date object into a string value. + * The format can be combinations of the following: + * d - day of month (no leading zero) + * dd - day of month (two digit) + * o - day of year (no leading zeros) + * oo - day of year (three digit) + * D - day name short + * DD - day name long + * m - month of year (no leading zero) + * mm - month of year (two digit) + * M - month name short + * MM - month name long + * y - year (two digit) + * yy - year (four digit) + * @ - Unix timestamp (ms since 01/01/1970) + * ! - Windows ticks (100ns since 01/01/0001) + * "..." - literal text + * '' - single quote + * + * @param format string - the desired format of the date + * @param date Date - the date value to format + * @param settings Object - attributes include: + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return string - the date in the above format + */ + formatDate: function (format, date, settings) { + if (!date) { + return ""; + } + + var iFormat, + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Format a number, with leading zero if necessary + formatNumber = function(match, value, len) { + var num = "" + value; + if (lookAhead(match)) { + while (num.length < len) { + num = "0" + num; + } + } + return num; + }, + // Format a name, short or long as requested + formatName = function(match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }, + output = "", + literal = false; + + if (date) { + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + output += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + output += formatNumber("d", date.getDate(), 2); + break; + case "D": + output += formatName("D", date.getDay(), dayNamesShort, dayNames); + break; + case "o": + output += formatNumber("o", + Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); + break; + case "m": + output += formatNumber("m", date.getMonth() + 1, 2); + break; + case "M": + output += formatName("M", date.getMonth(), monthNamesShort, monthNames); + break; + case "y": + output += (lookAhead("y") ? date.getFullYear() : + (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt(iFormat); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function (format) { + var iFormat, + chars = "", + literal = false, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + chars += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if (lookAhead("'")) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt(iFormat); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function(inst, name) { + return inst.settings[name] !== undefined ? + inst.settings[name] : this._defaults[name]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function(inst, noDefault) { + if (inst.input.val() === inst.lastVal) { + return; + } + + var dateFormat = this._get(inst, "dateFormat"), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate(inst), + date = defaultDate, + settings = this._getFormatConfig(inst); + + try { + date = this.parseDate(dateFormat, dates, settings) || defaultDate; + } catch (event) { + dates = (noDefault ? "" : dates); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = (dates ? date.getDate() : 0); + inst.currentMonth = (dates ? date.getMonth() : 0); + inst.currentYear = (dates ? date.getFullYear() : 0); + this._adjustInstDate(inst); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function(inst) { + return this._restrictMinMax(inst, + this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function(inst, date, defaultDate) { + var offsetNumeric = function(offset) { + var date = new Date(); + date.setDate(date.getDate() + offset); + return date; + }, + offsetString = function(offset) { + try { + return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + offset, $.datepicker._getFormatConfig(inst)); + } + catch (e) { + // Ignore + } + + var date = (offset.toLowerCase().match(/^c/) ? + $.datepicker._getDate(inst) : null) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec(offset); + + while (matches) { + switch (matches[2] || "d") { + case "d" : case "D" : + day += parseInt(matches[1],10); break; + case "w" : case "W" : + day += parseInt(matches[1],10) * 7; break; + case "m" : case "M" : + month += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + case "y": case "Y" : + year += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day); + }, + newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : + (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + + newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); + if (newDate) { + newDate.setHours(0); + newDate.setMinutes(0); + newDate.setSeconds(0); + newDate.setMilliseconds(0); + } + return this._daylightSavingAdjust(newDate); + }, + + /* Handle switch to/from daylight saving. + * Hours may be non-zero on daylight saving cut-over: + * > 12 when midnight changeover, but then cannot generate + * midnight datetime, so jump to 1AM, otherwise reset. + * @param date (Date) the date to check + * @return (Date) the corrected date + */ + _daylightSavingAdjust: function(date) { + if (!date) { + return null; + } + date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function(inst, date, noChange) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { + this._notifyChange(inst); + } + this._adjustInstDate(inst); + if (inst.input) { + inst.input.val(clear ? "" : this._formatDate(inst)); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function(inst) { + var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : + this._daylightSavingAdjust(new Date( + inst.currentYear, inst.currentMonth, inst.currentDay))); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function(inst) { + var stepMonths = this._get(inst, "stepMonths"), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find("[data-handler]").map(function () { + var handler = { + prev: function () { + $.datepicker._adjustDate(id, -stepMonths, "M"); + }, + next: function () { + $.datepicker._adjustDate(id, +stepMonths, "M"); + }, + hide: function () { + $.datepicker._hideDatepicker(); + }, + today: function () { + $.datepicker._gotoToday(id); + }, + selectDay: function () { + $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); + return false; + }, + selectMonth: function () { + $.datepicker._selectMonthYear(id, this, "M"); + return false; + }, + selectYear: function () { + $.datepicker._selectMonthYear(id, this, "Y"); + return false; + } + }; + $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); + }); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function(inst) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time + isRTL = this._get(inst, "isRTL"), + showButtonPanel = this._get(inst, "showButtonPanel"), + hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), + navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), + numMonths = this._getNumberOfMonths(inst), + showCurrentAtPos = this._get(inst, "showCurrentAtPos"), + stepMonths = this._get(inst, "stepMonths"), + isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), + currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : + new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if (drawMonth < 0) { + drawMonth += 12; + drawYear--; + } + if (maxDate) { + maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), + maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); + maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); + while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { + drawMonth--; + if (drawMonth < 0) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get(inst, "prevText"); + prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), + this._getFormatConfig(inst))); + + prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? + "" + prevText + "" : + (hideIfNoPrevNext ? "" : "" + prevText + "")); + + nextText = this._get(inst, "nextText"); + nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), + this._getFormatConfig(inst))); + + next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? + "" + nextText + "" : + (hideIfNoPrevNext ? "" : "" + nextText + "")); + + currentText = this._get(inst, "currentText"); + gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); + currentText = (!navigationAsDateFormat ? currentText : + this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + + controls = (!inst.inline ? "" : ""); + + buttonPanel = (showButtonPanel) ? "
    " + (isRTL ? controls : "") + + (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
    " : ""; + + firstDay = parseInt(this._get(inst, "firstDay"),10); + firstDay = (isNaN(firstDay) ? 0 : firstDay); + + showWeek = this._get(inst, "showWeek"); + dayNames = this._get(inst, "dayNames"); + dayNamesMin = this._get(inst, "dayNamesMin"); + monthNames = this._get(inst, "monthNames"); + monthNamesShort = this._get(inst, "monthNamesShort"); + beforeShowDay = this._get(inst, "beforeShowDay"); + showOtherMonths = this._get(inst, "showOtherMonths"); + selectOtherMonths = this._get(inst, "selectOtherMonths"); + defaultDate = this._getDefaultDate(inst); + html = ""; + dow; + for (row = 0; row < numMonths[0]; row++) { + group = ""; + this.maxRows = 4; + for (col = 0; col < numMonths[1]; col++) { + selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); + cornerClass = " ui-corner-all"; + calender = ""; + if (isMultiMonth) { + calender += "
    "; + } + calender += "
    " + + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers + "
    " + + ""; + thead = (showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // days of the week + day = (dow + firstDay) % 7; + thead += "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + + "" + dayNamesMin[day] + ""; + } + calender += thead + ""; + daysInMonth = this._getDaysInMonth(drawYear, drawMonth); + if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { + inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); + } + leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; + curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate + numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); + for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows + calender += ""; + tbody = (!showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // create date picker days + daySettings = (beforeShowDay ? + beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); + otherMonth = (printDate.getMonth() !== drawMonth); + unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || + (minDate && printDate < minDate) || (maxDate && printDate > maxDate); + tbody += ""; // display selectable date + printDate.setDate(printDate.getDate() + 1); + printDate = this._daylightSavingAdjust(printDate); + } + calender += tbody + ""; + } + drawMonth++; + if (drawMonth > 11) { + drawMonth = 0; + drawYear++; + } + calender += "
    " + this._get(inst, "weekHeader") + "
    " + + this._get(inst, "calculateWeek")(printDate) + "" + // actions + (otherMonth && !showOtherMonths ? " " : // display for other months + (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
    " + (isMultiMonth ? "
    " + + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
    " : "") : ""); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get(inst, "changeMonth"), + changeYear = this._get(inst, "changeYear"), + showMonthAfterYear = this._get(inst, "showMonthAfterYear"), + html = "
    ", + monthHtml = ""; + + // month selection + if (secondary || !changeMonth) { + monthHtml += "" + monthNames[drawMonth] + ""; + } else { + inMinYear = (minDate && minDate.getFullYear() === drawYear); + inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); + monthHtml += ""; + } + + if (!showMonthAfterYear) { + html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); + } + + // year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if (secondary || !changeYear) { + html += "" + drawYear + ""; + } else { + // determine range of years to display + years = this._get(inst, "yearRange").split(":"); + thisYear = new Date().getFullYear(); + determineYear = function(value) { + var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : + (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : + parseInt(value, 10))); + return (isNaN(year) ? thisYear : year); + }; + year = determineYear(years[0]); + endYear = Math.max(year, determineYear(years[1] || "")); + year = (minDate ? Math.max(year, minDate.getFullYear()) : year); + endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); + inst.yearshtml += ""; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get(inst, "yearSuffix"); + if (showMonthAfterYear) { + html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; + } + html += "
    "; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function(inst, offset, period) { + var year = inst.drawYear + (period === "Y" ? offset : 0), + month = inst.drawMonth + (period === "M" ? offset : 0), + day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), + date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if (period === "M" || period === "Y") { + this._notifyChange(inst); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function(inst, date) { + var minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + newDate = (minDate && date < minDate ? minDate : date); + return (maxDate && newDate > maxDate ? maxDate : newDate); + }, + + /* Notify change of month/year. */ + _notifyChange: function(inst) { + var onChange = this._get(inst, "onChangeMonthYear"); + if (onChange) { + onChange.apply((inst.input ? inst.input[0] : null), + [inst.selectedYear, inst.selectedMonth + 1, inst]); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function(inst) { + var numMonths = this._get(inst, "numberOfMonths"); + return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function(inst, minMax) { + return this._determineDate(inst, this._get(inst, minMax + "Date"), null); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function(year, month) { + return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function(year, month) { + return new Date(year, month, 1).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function(inst, offset, curYear, curMonth) { + var numMonths = this._getNumberOfMonths(inst), + date = this._daylightSavingAdjust(new Date(curYear, + curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + + if (offset < 0) { + date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + } + return this._isInRange(inst, date); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function(inst, date) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + minYear = null, + maxYear = null, + years = this._get(inst, "yearRange"); + if (years){ + yearSplit = years.split(":"); + currentYear = new Date().getFullYear(); + minYear = parseInt(yearSplit[0], 10); + maxYear = parseInt(yearSplit[1], 10); + if ( yearSplit[0].match(/[+\-].*/) ) { + minYear += currentYear; + } + if ( yearSplit[1].match(/[+\-].*/) ) { + maxYear += currentYear; + } + } + + return ((!minDate || date.getTime() >= minDate.getTime()) && + (!maxDate || date.getTime() <= maxDate.getTime()) && + (!minYear || date.getFullYear() >= minYear) && + (!maxYear || date.getFullYear() <= maxYear)); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function(inst) { + var shortYearCutoff = this._get(inst, "shortYearCutoff"); + shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + return {shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), + monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; + }, + + /* Format the given date for display. */ + _formatDate: function(inst, day, month, year) { + if (!day) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = (day ? (typeof day === "object" ? day : + this._daylightSavingAdjust(new Date(year, month, day))) : + this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); + } +}); + +/* + * Bind hover events for datepicker elements. + * Done via delegate so the binding only occurs once in the lifetime of the parent div. + * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. + */ +function bindHover(dpDiv) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.delegate(selector, "mouseout", function() { + $(this).removeClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).removeClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).removeClass("ui-datepicker-next-hover"); + } + }) + .delegate(selector, "mouseover", function(){ + if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { + $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); + $(this).addClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).addClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).addClass("ui-datepicker-next-hover"); + } + } + }); +} + +/* jQuery extend now ignores nulls! */ +function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] == null) { + target[name] = props[name]; + } + } + return target; +} + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function(options){ + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if (!$.datepicker.initialized) { + $(document).mousedown($.datepicker._checkExternalClick); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ($("#"+$.datepicker._mainDivId).length === 0) { + $("body").append($.datepicker.dpDiv); + } + + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + return this.each(function() { + typeof options === "string" ? + $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this].concat(otherArgs)) : + $.datepicker._attachDatepicker(this, options); + }); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.10.3"; + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.dialog.js b/extensions/jui/assets/jquery.ui.dialog.js new file mode 100644 index 0000000..3a01e0c --- /dev/null +++ b/extensions/jui/assets/jquery.ui.dialog.js @@ -0,0 +1,808 @@ +/*! + * jQuery UI Dialog 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/dialog/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function( $, undefined ) { + +var sizeRelatedOptions = { + buttons: true, + height: true, + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true, + width: true + }, + resizableRelatedOptions = { + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true + }; + +$.widget( "ui.dialog", { + version: "1.10.3", + options: { + appendTo: "body", + autoOpen: true, + buttons: [], + closeOnEscape: true, + closeText: "close", + dialogClass: "", + draggable: true, + hide: null, + height: "auto", + maxHeight: null, + maxWidth: null, + minHeight: 150, + minWidth: 150, + modal: false, + position: { + my: "center", + at: "center", + of: window, + collision: "fit", + // Ensure the titlebar is always visible + using: function( pos ) { + var topOffset = $( this ).css( pos ).offset().top; + if ( topOffset < 0 ) { + $( this ).css( "top", pos.top - topOffset ); + } + } + }, + resizable: true, + show: null, + title: null, + width: 300, + + // callbacks + beforeClose: null, + close: null, + drag: null, + dragStart: null, + dragStop: null, + focus: null, + open: null, + resize: null, + resizeStart: null, + resizeStop: null + }, + + _create: function() { + this.originalCss = { + display: this.element[0].style.display, + width: this.element[0].style.width, + minHeight: this.element[0].style.minHeight, + maxHeight: this.element[0].style.maxHeight, + height: this.element[0].style.height + }; + this.originalPosition = { + parent: this.element.parent(), + index: this.element.parent().children().index( this.element ) + }; + this.originalTitle = this.element.attr("title"); + this.options.title = this.options.title || this.originalTitle; + + this._createWrapper(); + + this.element + .show() + .removeAttr("title") + .addClass("ui-dialog-content ui-widget-content") + .appendTo( this.uiDialog ); + + this._createTitlebar(); + this._createButtonPane(); + + if ( this.options.draggable && $.fn.draggable ) { + this._makeDraggable(); + } + if ( this.options.resizable && $.fn.resizable ) { + this._makeResizable(); + } + + this._isOpen = false; + }, + + _init: function() { + if ( this.options.autoOpen ) { + this.open(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + if ( element && (element.jquery || element.nodeType) ) { + return $( element ); + } + return this.document.find( element || "body" ).eq( 0 ); + }, + + _destroy: function() { + var next, + originalPosition = this.originalPosition; + + this._destroyOverlay(); + + this.element + .removeUniqueId() + .removeClass("ui-dialog-content ui-widget-content") + .css( this.originalCss ) + // Without detaching first, the following becomes really slow + .detach(); + + this.uiDialog.stop( true, true ).remove(); + + if ( this.originalTitle ) { + this.element.attr( "title", this.originalTitle ); + } + + next = originalPosition.parent.children().eq( originalPosition.index ); + // Don't try to place the dialog next to itself (#8613) + if ( next.length && next[0] !== this.element[0] ) { + next.before( this.element ); + } else { + originalPosition.parent.append( this.element ); + } + }, + + widget: function() { + return this.uiDialog; + }, + + disable: $.noop, + enable: $.noop, + + close: function( event ) { + var that = this; + + if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { + return; + } + + this._isOpen = false; + this._destroyOverlay(); + + if ( !this.opener.filter(":focusable").focus().length ) { + // Hiding a focused element doesn't trigger blur in WebKit + // so in case we have nothing to focus on, explicitly blur the active element + // https://bugs.webkit.org/show_bug.cgi?id=47182 + $( this.document[0].activeElement ).blur(); + } + + this._hide( this.uiDialog, this.options.hide, function() { + that._trigger( "close", event ); + }); + }, + + isOpen: function() { + return this._isOpen; + }, + + moveToTop: function() { + this._moveToTop(); + }, + + _moveToTop: function( event, silent ) { + var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length; + if ( moved && !silent ) { + this._trigger( "focus", event ); + } + return moved; + }, + + open: function() { + var that = this; + if ( this._isOpen ) { + if ( this._moveToTop() ) { + this._focusTabbable(); + } + return; + } + + this._isOpen = true; + this.opener = $( this.document[0].activeElement ); + + this._size(); + this._position(); + this._createOverlay(); + this._moveToTop( null, true ); + this._show( this.uiDialog, this.options.show, function() { + that._focusTabbable(); + that._trigger("focus"); + }); + + this._trigger("open"); + }, + + _focusTabbable: function() { + // Set focus to the first match: + // 1. First element inside the dialog matching [autofocus] + // 2. Tabbable element inside the content element + // 3. Tabbable element inside the buttonpane + // 4. The close button + // 5. The dialog itself + var hasFocus = this.element.find("[autofocus]"); + if ( !hasFocus.length ) { + hasFocus = this.element.find(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogButtonPane.find(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogTitlebarClose.filter(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialog; + } + hasFocus.eq( 0 ).focus(); + }, + + _keepFocus: function( event ) { + function checkFocus() { + var activeElement = this.document[0].activeElement, + isActive = this.uiDialog[0] === activeElement || + $.contains( this.uiDialog[0], activeElement ); + if ( !isActive ) { + this._focusTabbable(); + } + } + event.preventDefault(); + checkFocus.call( this ); + // support: IE + // IE <= 8 doesn't prevent moving focus even with event.preventDefault() + // so we check again later + this._delay( checkFocus ); + }, + + _createWrapper: function() { + this.uiDialog = $("
    ") + .addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " + + this.options.dialogClass ) + .hide() + .attr({ + // Setting tabIndex makes the div focusable + tabIndex: -1, + role: "dialog" + }) + .appendTo( this._appendTo() ); + + this._on( this.uiDialog, { + keydown: function( event ) { + if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE ) { + event.preventDefault(); + this.close( event ); + return; + } + + // prevent tabbing out of dialogs + if ( event.keyCode !== $.ui.keyCode.TAB ) { + return; + } + var tabbables = this.uiDialog.find(":tabbable"), + first = tabbables.filter(":first"), + last = tabbables.filter(":last"); + + if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) { + first.focus( 1 ); + event.preventDefault(); + } else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) { + last.focus( 1 ); + event.preventDefault(); + } + }, + mousedown: function( event ) { + if ( this._moveToTop( event ) ) { + this._focusTabbable(); + } + } + }); + + // We assume that any existing aria-describedby attribute means + // that the dialog content is marked up properly + // otherwise we brute force the content as the description + if ( !this.element.find("[aria-describedby]").length ) { + this.uiDialog.attr({ + "aria-describedby": this.element.uniqueId().attr("id") + }); + } + }, + + _createTitlebar: function() { + var uiDialogTitle; + + this.uiDialogTitlebar = $("
    ") + .addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix") + .prependTo( this.uiDialog ); + this._on( this.uiDialogTitlebar, { + mousedown: function( event ) { + // Don't prevent click on close button (#8838) + // Focusing a dialog that is partially scrolled out of view + // causes the browser to scroll it into view, preventing the click event + if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) { + // Dialog isn't getting focus when dragging (#8063) + this.uiDialog.focus(); + } + } + }); + + this.uiDialogTitlebarClose = $("") + .button({ + label: this.options.closeText, + icons: { + primary: "ui-icon-closethick" + }, + text: false + }) + .addClass("ui-dialog-titlebar-close") + .appendTo( this.uiDialogTitlebar ); + this._on( this.uiDialogTitlebarClose, { + click: function( event ) { + event.preventDefault(); + this.close( event ); + } + }); + + uiDialogTitle = $("") + .uniqueId() + .addClass("ui-dialog-title") + .prependTo( this.uiDialogTitlebar ); + this._title( uiDialogTitle ); + + this.uiDialog.attr({ + "aria-labelledby": uiDialogTitle.attr("id") + }); + }, + + _title: function( title ) { + if ( !this.options.title ) { + title.html(" "); + } + title.text( this.options.title ); + }, + + _createButtonPane: function() { + this.uiDialogButtonPane = $("
    ") + .addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"); + + this.uiButtonSet = $("
    ") + .addClass("ui-dialog-buttonset") + .appendTo( this.uiDialogButtonPane ); + + this._createButtons(); + }, + + _createButtons: function() { + var that = this, + buttons = this.options.buttons; + + // if we already have a button pane, remove it + this.uiDialogButtonPane.remove(); + this.uiButtonSet.empty(); + + if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) { + this.uiDialog.removeClass("ui-dialog-buttons"); + return; + } + + $.each( buttons, function( name, props ) { + var click, buttonOptions; + props = $.isFunction( props ) ? + { click: props, text: name } : + props; + // Default to a non-submitting button + props = $.extend( { type: "button" }, props ); + // Change the context for the click callback to be the main element + click = props.click; + props.click = function() { + click.apply( that.element[0], arguments ); + }; + buttonOptions = { + icons: props.icons, + text: props.showText + }; + delete props.icons; + delete props.showText; + $( "", props ) + .button( buttonOptions ) + .appendTo( that.uiButtonSet ); + }); + this.uiDialog.addClass("ui-dialog-buttons"); + this.uiDialogButtonPane.appendTo( this.uiDialog ); + }, + + _makeDraggable: function() { + var that = this, + options = this.options; + + function filteredUi( ui ) { + return { + position: ui.position, + offset: ui.offset + }; + } + + this.uiDialog.draggable({ + cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", + handle: ".ui-dialog-titlebar", + containment: "document", + start: function( event, ui ) { + $( this ).addClass("ui-dialog-dragging"); + that._blockFrames(); + that._trigger( "dragStart", event, filteredUi( ui ) ); + }, + drag: function( event, ui ) { + that._trigger( "drag", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + options.position = [ + ui.position.left - that.document.scrollLeft(), + ui.position.top - that.document.scrollTop() + ]; + $( this ).removeClass("ui-dialog-dragging"); + that._unblockFrames(); + that._trigger( "dragStop", event, filteredUi( ui ) ); + } + }); + }, + + _makeResizable: function() { + var that = this, + options = this.options, + handles = options.resizable, + // .ui-resizable has position: relative defined in the stylesheet + // but dialogs have to use absolute or fixed positioning + position = this.uiDialog.css("position"), + resizeHandles = typeof handles === "string" ? + handles : + "n,e,s,w,se,sw,ne,nw"; + + function filteredUi( ui ) { + return { + originalPosition: ui.originalPosition, + originalSize: ui.originalSize, + position: ui.position, + size: ui.size + }; + } + + this.uiDialog.resizable({ + cancel: ".ui-dialog-content", + containment: "document", + alsoResize: this.element, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + minWidth: options.minWidth, + minHeight: this._minHeight(), + handles: resizeHandles, + start: function( event, ui ) { + $( this ).addClass("ui-dialog-resizing"); + that._blockFrames(); + that._trigger( "resizeStart", event, filteredUi( ui ) ); + }, + resize: function( event, ui ) { + that._trigger( "resize", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + options.height = $( this ).height(); + options.width = $( this ).width(); + $( this ).removeClass("ui-dialog-resizing"); + that._unblockFrames(); + that._trigger( "resizeStop", event, filteredUi( ui ) ); + } + }) + .css( "position", position ); + }, + + _minHeight: function() { + var options = this.options; + + return options.height === "auto" ? + options.minHeight : + Math.min( options.minHeight, options.height ); + }, + + _position: function() { + // Need to show the dialog to get the actual offset in the position plugin + var isVisible = this.uiDialog.is(":visible"); + if ( !isVisible ) { + this.uiDialog.show(); + } + this.uiDialog.position( this.options.position ); + if ( !isVisible ) { + this.uiDialog.hide(); + } + }, + + _setOptions: function( options ) { + var that = this, + resize = false, + resizableOptions = {}; + + $.each( options, function( key, value ) { + that._setOption( key, value ); + + if ( key in sizeRelatedOptions ) { + resize = true; + } + if ( key in resizableRelatedOptions ) { + resizableOptions[ key ] = value; + } + }); + + if ( resize ) { + this._size(); + this._position(); + } + if ( this.uiDialog.is(":data(ui-resizable)") ) { + this.uiDialog.resizable( "option", resizableOptions ); + } + }, + + _setOption: function( key, value ) { + /*jshint maxcomplexity:15*/ + var isDraggable, isResizable, + uiDialog = this.uiDialog; + + if ( key === "dialogClass" ) { + uiDialog + .removeClass( this.options.dialogClass ) + .addClass( value ); + } + + if ( key === "disabled" ) { + return; + } + + this._super( key, value ); + + if ( key === "appendTo" ) { + this.uiDialog.appendTo( this._appendTo() ); + } + + if ( key === "buttons" ) { + this._createButtons(); + } + + if ( key === "closeText" ) { + this.uiDialogTitlebarClose.button({ + // Ensure that we always pass a string + label: "" + value + }); + } + + if ( key === "draggable" ) { + isDraggable = uiDialog.is(":data(ui-draggable)"); + if ( isDraggable && !value ) { + uiDialog.draggable("destroy"); + } + + if ( !isDraggable && value ) { + this._makeDraggable(); + } + } + + if ( key === "position" ) { + this._position(); + } + + if ( key === "resizable" ) { + // currently resizable, becoming non-resizable + isResizable = uiDialog.is(":data(ui-resizable)"); + if ( isResizable && !value ) { + uiDialog.resizable("destroy"); + } + + // currently resizable, changing handles + if ( isResizable && typeof value === "string" ) { + uiDialog.resizable( "option", "handles", value ); + } + + // currently non-resizable, becoming resizable + if ( !isResizable && value !== false ) { + this._makeResizable(); + } + } + + if ( key === "title" ) { + this._title( this.uiDialogTitlebar.find(".ui-dialog-title") ); + } + }, + + _size: function() { + // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content + // divs will both have width and height set, so we need to reset them + var nonContentHeight, minContentHeight, maxContentHeight, + options = this.options; + + // Reset content sizing + this.element.show().css({ + width: "auto", + minHeight: 0, + maxHeight: "none", + height: 0 + }); + + if ( options.minWidth > options.width ) { + options.width = options.minWidth; + } + + // reset wrapper sizing + // determine the height of all the non-content elements + nonContentHeight = this.uiDialog.css({ + height: "auto", + width: options.width + }) + .outerHeight(); + minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); + maxContentHeight = typeof options.maxHeight === "number" ? + Math.max( 0, options.maxHeight - nonContentHeight ) : + "none"; + + if ( options.height === "auto" ) { + this.element.css({ + minHeight: minContentHeight, + maxHeight: maxContentHeight, + height: "auto" + }); + } else { + this.element.height( Math.max( 0, options.height - nonContentHeight ) ); + } + + if (this.uiDialog.is(":data(ui-resizable)") ) { + this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); + } + }, + + _blockFrames: function() { + this.iframeBlocks = this.document.find( "iframe" ).map(function() { + var iframe = $( this ); + + return $( "
    " ) + .css({ + position: "absolute", + width: iframe.outerWidth(), + height: iframe.outerHeight() + }) + .appendTo( iframe.parent() ) + .offset( iframe.offset() )[0]; + }); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _allowInteraction: function( event ) { + if ( $( event.target ).closest(".ui-dialog").length ) { + return true; + } + + // TODO: Remove hack when datepicker implements + // the .ui-front logic (#8989) + return !!$( event.target ).closest(".ui-datepicker").length; + }, + + _createOverlay: function() { + if ( !this.options.modal ) { + return; + } + + var that = this, + widgetFullName = this.widgetFullName; + if ( !$.ui.dialog.overlayInstances ) { + // Prevent use of anchors and inputs. + // We use a delay in case the overlay is created from an + // event that we're going to be cancelling. (#2804) + this._delay(function() { + // Handle .dialog().dialog("close") (#4065) + if ( $.ui.dialog.overlayInstances ) { + this.document.bind( "focusin.dialog", function( event ) { + if ( !that._allowInteraction( event ) ) { + event.preventDefault(); + $(".ui-dialog:visible:last .ui-dialog-content") + .data( widgetFullName )._focusTabbable(); + } + }); + } + }); + } + + this.overlay = $("
    ") + .addClass("ui-widget-overlay ui-front") + .appendTo( this._appendTo() ); + this._on( this.overlay, { + mousedown: "_keepFocus" + }); + $.ui.dialog.overlayInstances++; + }, + + _destroyOverlay: function() { + if ( !this.options.modal ) { + return; + } + + if ( this.overlay ) { + $.ui.dialog.overlayInstances--; + + if ( !$.ui.dialog.overlayInstances ) { + this.document.unbind( "focusin.dialog" ); + } + this.overlay.remove(); + this.overlay = null; + } + } +}); + +$.ui.dialog.overlayInstances = 0; + +// DEPRECATED +if ( $.uiBackCompat !== false ) { + // position option with array notation + // just override with old implementation + $.widget( "ui.dialog", $.ui.dialog, { + _position: function() { + var position = this.options.position, + myAt = [], + offset = [ 0, 0 ], + isVisible; + + if ( position ) { + if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) { + myAt = position.split ? position.split(" ") : [ position[0], position[1] ]; + if ( myAt.length === 1 ) { + myAt[1] = myAt[0]; + } + + $.each( [ "left", "top" ], function( i, offsetPosition ) { + if ( +myAt[ i ] === myAt[ i ] ) { + offset[ i ] = myAt[ i ]; + myAt[ i ] = offsetPosition; + } + }); + + position = { + my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " + + myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]), + at: myAt.join(" ") + }; + } + + position = $.extend( {}, $.ui.dialog.prototype.options.position, position ); + } else { + position = $.ui.dialog.prototype.options.position; + } + + // need to show the dialog to get the actual offset in the position plugin + isVisible = this.uiDialog.is(":visible"); + if ( !isVisible ) { + this.uiDialog.show(); + } + this.uiDialog.position( position ); + if ( !isVisible ) { + this.uiDialog.hide(); + } + } + }); +} + +}( jQuery ) ); diff --git a/extensions/jui/assets/jquery.ui.draggable.js b/extensions/jui/assets/jquery.ui.draggable.js new file mode 100644 index 0000000..a52519b --- /dev/null +++ b/extensions/jui/assets/jquery.ui.draggable.js @@ -0,0 +1,958 @@ +/*! + * jQuery UI Draggable 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/draggable/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.draggable", $.ui.mouse, { + version: "1.10.3", + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false, + + // callbacks + drag: null, + start: null, + stop: null + }, + _create: function() { + + if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { + this.element[0].style.position = "relative"; + } + if (this.options.addClasses){ + this.element.addClass("ui-draggable"); + } + if (this.options.disabled){ + this.element.addClass("ui-draggable-disabled"); + } + + this._mouseInit(); + + }, + + _destroy: function() { + this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); + this._mouseDestroy(); + }, + + _mouseCapture: function(event) { + + var o = this.options; + + // among others, prevent a drag on a resizable-handle + if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { + return false; + } + + //Quit if we're not on a valid handle + this.handle = this._getHandle(event); + if (!this.handle) { + return false; + } + + $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { + $("
    ") + .css({ + width: this.offsetWidth+"px", height: this.offsetHeight+"px", + position: "absolute", opacity: "0.001", zIndex: 1000 + }) + .css($(this).offset()) + .appendTo("body"); + }); + + return true; + + }, + + _mouseStart: function(event) { + + var o = this.options; + + //Create and append the visible helper + this.helper = this._createHelper(event); + + this.helper.addClass("ui-draggable-dragging"); + + //Cache the helper size + this._cacheHelperProportions(); + + //If ddmanager is used for droppables, set the global draggable + if($.ui.ddmanager) { + $.ui.ddmanager.current = this; + } + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Store the helper's css position + this.cssPosition = this.helper.css( "position" ); + this.scrollParent = this.helper.scrollParent(); + this.offsetParent = this.helper.offsetParent(); + this.offsetParentCssPosition = this.offsetParent.css( "position" ); + + //The element's absolute position on the page minus margins + this.offset = this.positionAbs = this.element.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + //Reset scroll cache + this.offset.scroll = false; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + //Generate the original position + this.originalPosition = this.position = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Set a containment if given in the options + this._setContainment(); + + //Trigger event + callbacks + if(this._trigger("start", event) === false) { + this._clear(); + return false; + } + + //Recache the helper size + this._cacheHelperProportions(); + + //Prepare the droppable offsets + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + + + this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position + + //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) + if ( $.ui.ddmanager ) { + $.ui.ddmanager.dragStart(this, event); + } + + return true; + }, + + _mouseDrag: function(event, noPropagation) { + // reset any necessary cached properties (see #5009) + if ( this.offsetParentCssPosition === "fixed" ) { + this.offset.parent = this._getParentOffset(); + } + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + //Call plugins and callbacks and use the resulting position if something is returned + if (!noPropagation) { + var ui = this._uiHash(); + if(this._trigger("drag", event, ui) === false) { + this._mouseUp({}); + return false; + } + this.position = ui.position; + } + + if(!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left+"px"; + } + if(!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top+"px"; + } + if($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + return false; + }, + + _mouseStop: function(event) { + + //If we are using droppables, inform the manager about the drop + var that = this, + dropped = false; + if ($.ui.ddmanager && !this.options.dropBehaviour) { + dropped = $.ui.ddmanager.drop(this, event); + } + + //if a drop comes from outside (a sortable) + if(this.dropped) { + dropped = this.dropped; + this.dropped = false; + } + + //if the original element is no longer in the DOM don't bother to continue (see #8269) + if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) { + return false; + } + + if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { + $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { + if(that._trigger("stop", event) !== false) { + that._clear(); + } + }); + } else { + if(this._trigger("stop", event) !== false) { + this._clear(); + } + } + + return false; + }, + + _mouseUp: function(event) { + //Remove frame helpers + $("div.ui-draggable-iframeFix").each(function() { + this.parentNode.removeChild(this); + }); + + //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) + if( $.ui.ddmanager ) { + $.ui.ddmanager.dragStop(this, event); + } + + return $.ui.mouse.prototype._mouseUp.call(this, event); + }, + + cancel: function() { + + if(this.helper.is(".ui-draggable-dragging")) { + this._mouseUp({}); + } else { + this._clear(); + } + + return this; + + }, + + _getHandle: function(event) { + return this.options.handle ? + !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : + true; + }, + + _createHelper: function(event) { + + var o = this.options, + helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element); + + if(!helper.parents("body").length) { + helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo)); + } + + if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) { + helper.css("position", "absolute"); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj === "string") { + obj = obj.split(" "); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ("left" in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ("right" in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ("top" in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ("bottom" in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + //Get the offsetParent and cache its position + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + //This needs to be actually done for all browsers, since pageX/pageY includes this information + //Ugly IE fix + if((this.offsetParent[0] === document.body) || + (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition === "relative") { + var p = this.element.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.element.css("marginLeft"),10) || 0), + top: (parseInt(this.element.css("marginTop"),10) || 0), + right: (parseInt(this.element.css("marginRight"),10) || 0), + bottom: (parseInt(this.element.css("marginBottom"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var over, c, ce, + o = this.options; + + if ( !o.containment ) { + this.containment = null; + return; + } + + if ( o.containment === "window" ) { + this.containment = [ + $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, + $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, + $( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left, + $( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top + ]; + return; + } + + if ( o.containment === "document") { + this.containment = [ + 0, + 0, + $( document ).width() - this.helperProportions.width - this.margins.left, + ( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top + ]; + return; + } + + if ( o.containment.constructor === Array ) { + this.containment = o.containment; + return; + } + + if ( o.containment === "parent" ) { + o.containment = this.helper[ 0 ].parentNode; + } + + c = $( o.containment ); + ce = c[ 0 ]; + + if( !ce ) { + return; + } + + over = c.css( "overflow" ) !== "hidden"; + + this.containment = [ + ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), + ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) , + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right, + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top - this.margins.bottom + ]; + this.relative_container = c; + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) { + pos = this.position; + } + + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent; + + //Cache the scroll + if (!this.offset.scroll) { + this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; + } + + return { + top: ( + pos.top + // The absolute mouse position + this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod ) + ), + left: ( + pos.left + // The absolute mouse position + this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod ) + ) + }; + + }, + + _generatePosition: function(event) { + + var containment, co, top, left, + o = this.options, + scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent, + pageX = event.pageX, + pageY = event.pageY; + + //Cache the scroll + if (!this.offset.scroll) { + this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; + } + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + // If we are not dragging yet, we won't check for options + if ( this.originalPosition ) { + if ( this.containment ) { + if ( this.relative_container ){ + co = this.relative_container.offset(); + containment = [ + this.containment[ 0 ] + co.left, + this.containment[ 1 ] + co.top, + this.containment[ 2 ] + co.left, + this.containment[ 3 ] + co.top + ]; + } + else { + containment = this.containment; + } + + if(event.pageX - this.offset.click.left < containment[0]) { + pageX = containment[0] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top < containment[1]) { + pageY = containment[1] + this.offset.click.top; + } + if(event.pageX - this.offset.click.left > containment[2]) { + pageX = containment[2] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top > containment[3]) { + pageY = containment[3] + this.offset.click.top; + } + } + + if(o.grid) { + //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950) + top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY; + pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX; + pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY - // The absolute mouse position + this.offset.click.top - // Click offset (relative to the element) + this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top + // The offsetParent's offset without borders (offset + border) + ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) + ), + left: ( + pageX - // The absolute mouse position + this.offset.click.left - // Click offset (relative to the element) + this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left + // The offsetParent's offset without borders (offset + border) + ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) + ) + }; + + }, + + _clear: function() { + this.helper.removeClass("ui-draggable-dragging"); + if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) { + this.helper.remove(); + } + this.helper = null; + this.cancelHelperRemoval = false; + }, + + // From now on bulk stuff - mainly helpers + + _trigger: function(type, event, ui) { + ui = ui || this._uiHash(); + $.ui.plugin.call(this, type, [event, ui]); + //The absolute position has to be recalculated after plugins + if(type === "drag") { + this.positionAbs = this._convertPositionTo("absolute"); + } + return $.Widget.prototype._trigger.call(this, type, event, ui); + }, + + plugins: {}, + + _uiHash: function() { + return { + helper: this.helper, + position: this.position, + originalPosition: this.originalPosition, + offset: this.positionAbs + }; + } + +}); + +$.ui.plugin.add("draggable", "connectToSortable", { + start: function(event, ui) { + + var inst = $(this).data("ui-draggable"), o = inst.options, + uiSortable = $.extend({}, ui, { item: inst.element }); + inst.sortables = []; + $(o.connectToSortable).each(function() { + var sortable = $.data(this, "ui-sortable"); + if (sortable && !sortable.options.disabled) { + inst.sortables.push({ + instance: sortable, + shouldRevert: sortable.options.revert + }); + sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). + sortable._trigger("activate", event, uiSortable); + } + }); + + }, + stop: function(event, ui) { + + //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper + var inst = $(this).data("ui-draggable"), + uiSortable = $.extend({}, ui, { item: inst.element }); + + $.each(inst.sortables, function() { + if(this.instance.isOver) { + + this.instance.isOver = 0; + + inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance + this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) + + //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid" + if(this.shouldRevert) { + this.instance.options.revert = this.shouldRevert; + } + + //Trigger the stop of the sortable + this.instance._mouseStop(event); + + this.instance.options.helper = this.instance.options._helper; + + //If the helper has been the original item, restore properties in the sortable + if(inst.options.helper === "original") { + this.instance.currentItem.css({ top: "auto", left: "auto" }); + } + + } else { + this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance + this.instance._trigger("deactivate", event, uiSortable); + } + + }); + + }, + drag: function(event, ui) { + + var inst = $(this).data("ui-draggable"), that = this; + + $.each(inst.sortables, function() { + + var innermostIntersecting = false, + thisSortable = this; + + //Copy over some variables to allow calling the sortable's native _intersectsWith + this.instance.positionAbs = inst.positionAbs; + this.instance.helperProportions = inst.helperProportions; + this.instance.offset.click = inst.offset.click; + + if(this.instance._intersectsWith(this.instance.containerCache)) { + innermostIntersecting = true; + $.each(inst.sortables, function () { + this.instance.positionAbs = inst.positionAbs; + this.instance.helperProportions = inst.helperProportions; + this.instance.offset.click = inst.offset.click; + if (this !== thisSortable && + this.instance._intersectsWith(this.instance.containerCache) && + $.contains(thisSortable.instance.element[0], this.instance.element[0]) + ) { + innermostIntersecting = false; + } + return innermostIntersecting; + }); + } + + + if(innermostIntersecting) { + //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once + if(!this.instance.isOver) { + + this.instance.isOver = 1; + //Now we fake the start of dragging for the sortable instance, + //by cloning the list group item, appending it to the sortable and using it as inst.currentItem + //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) + this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true); + this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it + this.instance.options.helper = function() { return ui.helper[0]; }; + + event.target = this.instance.currentItem[0]; + this.instance._mouseCapture(event, true); + this.instance._mouseStart(event, true, true); + + //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes + this.instance.offset.click.top = inst.offset.click.top; + this.instance.offset.click.left = inst.offset.click.left; + this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; + this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; + + inst._trigger("toSortable", event); + inst.dropped = this.instance.element; //draggable revert needs that + //hack so receive/update callbacks work (mostly) + inst.currentItem = inst.element; + this.instance.fromOutside = inst; + + } + + //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable + if(this.instance.currentItem) { + this.instance._mouseDrag(event); + } + + } else { + + //If it doesn't intersect with the sortable, and it intersected before, + //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval + if(this.instance.isOver) { + + this.instance.isOver = 0; + this.instance.cancelHelperRemoval = true; + + //Prevent reverting on this forced stop + this.instance.options.revert = false; + + // The out event needs to be triggered independently + this.instance._trigger("out", event, this.instance._uiHash(this.instance)); + + this.instance._mouseStop(event, true); + this.instance.options.helper = this.instance.options._helper; + + //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size + this.instance.currentItem.remove(); + if(this.instance.placeholder) { + this.instance.placeholder.remove(); + } + + inst._trigger("fromSortable", event); + inst.dropped = false; //draggable revert needs that + } + + } + + }); + + } +}); + +$.ui.plugin.add("draggable", "cursor", { + start: function() { + var t = $("body"), o = $(this).data("ui-draggable").options; + if (t.css("cursor")) { + o._cursor = t.css("cursor"); + } + t.css("cursor", o.cursor); + }, + stop: function() { + var o = $(this).data("ui-draggable").options; + if (o._cursor) { + $("body").css("cursor", o._cursor); + } + } +}); + +$.ui.plugin.add("draggable", "opacity", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data("ui-draggable").options; + if(t.css("opacity")) { + o._opacity = t.css("opacity"); + } + t.css("opacity", o.opacity); + }, + stop: function(event, ui) { + var o = $(this).data("ui-draggable").options; + if(o._opacity) { + $(ui.helper).css("opacity", o._opacity); + } + } +}); + +$.ui.plugin.add("draggable", "scroll", { + start: function() { + var i = $(this).data("ui-draggable"); + if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { + i.overflowOffset = i.scrollParent.offset(); + } + }, + drag: function( event ) { + + var i = $(this).data("ui-draggable"), o = i.options, scrolled = false; + + if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { + + if(!o.axis || o.axis !== "x") { + if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; + } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) { + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; + } + } + + if(!o.axis || o.axis !== "y") { + if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; + } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) { + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; + } + } + + } else { + + if(!o.axis || o.axis !== "x") { + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + } + + if(!o.axis || o.axis !== "y") { + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(i, event); + } + + } +}); + +$.ui.plugin.add("draggable", "snap", { + start: function() { + + var i = $(this).data("ui-draggable"), + o = i.options; + + i.snapElements = []; + + $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() { + var $t = $(this), + $o = $t.offset(); + if(this !== i.element[0]) { + i.snapElements.push({ + item: this, + width: $t.outerWidth(), height: $t.outerHeight(), + top: $o.top, left: $o.left + }); + } + }); + + }, + drag: function(event, ui) { + + var ts, bs, ls, rs, l, r, t, b, i, first, + inst = $(this).data("ui-draggable"), + o = inst.options, + d = o.snapTolerance, + x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, + y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; + + for (i = inst.snapElements.length - 1; i >= 0; i--){ + + l = inst.snapElements[i].left; + r = l + inst.snapElements[i].width; + t = inst.snapElements[i].top; + b = t + inst.snapElements[i].height; + + if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) { + if(inst.snapElements[i].snapping) { + (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + } + inst.snapElements[i].snapping = false; + continue; + } + + if(o.snapMode !== "inner") { + ts = Math.abs(t - y2) <= d; + bs = Math.abs(b - y1) <= d; + ls = Math.abs(l - x2) <= d; + rs = Math.abs(r - x1) <= d; + if(ts) { + ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + } + if(bs) { + ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; + } + if(ls) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; + } + if(rs) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; + } + } + + first = (ts || bs || ls || rs); + + if(o.snapMode !== "outer") { + ts = Math.abs(t - y1) <= d; + bs = Math.abs(b - y2) <= d; + ls = Math.abs(l - x1) <= d; + rs = Math.abs(r - x2) <= d; + if(ts) { + ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; + } + if(bs) { + ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + } + if(ls) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; + } + if(rs) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; + } + } + + if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) { + (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + } + inst.snapElements[i].snapping = (ts || bs || ls || rs || first); + + } + + } +}); + +$.ui.plugin.add("draggable", "stack", { + start: function() { + var min, + o = this.data("ui-draggable").options, + group = $.makeArray($(o.stack)).sort(function(a,b) { + return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); + }); + + if (!group.length) { return; } + + min = parseInt($(group[0]).css("zIndex"), 10) || 0; + $(group).each(function(i) { + $(this).css("zIndex", min + i); + }); + this.css("zIndex", (min + group.length)); + } +}); + +$.ui.plugin.add("draggable", "zIndex", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data("ui-draggable").options; + if(t.css("zIndex")) { + o._zIndex = t.css("zIndex"); + } + t.css("zIndex", o.zIndex); + }, + stop: function(event, ui) { + var o = $(this).data("ui-draggable").options; + if(o._zIndex) { + $(ui.helper).css("zIndex", o._zIndex); + } + } +}); + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.droppable.js b/extensions/jui/assets/jquery.ui.droppable.js new file mode 100644 index 0000000..24337d0 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.droppable.js @@ -0,0 +1,372 @@ +/*! + * jQuery UI Droppable 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/droppable/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.mouse.js + * jquery.ui.draggable.js + */ +(function( $, undefined ) { + +function isOverAxis( x, reference, size ) { + return ( x > reference ) && ( x < ( reference + size ) ); +} + +$.widget("ui.droppable", { + version: "1.10.3", + widgetEventPrefix: "drop", + options: { + accept: "*", + activeClass: false, + addClasses: true, + greedy: false, + hoverClass: false, + scope: "default", + tolerance: "intersect", + + // callbacks + activate: null, + deactivate: null, + drop: null, + out: null, + over: null + }, + _create: function() { + + var o = this.options, + accept = o.accept; + + this.isover = false; + this.isout = true; + + this.accept = $.isFunction(accept) ? accept : function(d) { + return d.is(accept); + }; + + //Store the droppable's proportions + this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; + + // Add the reference and positions to the manager + $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; + $.ui.ddmanager.droppables[o.scope].push(this); + + (o.addClasses && this.element.addClass("ui-droppable")); + + }, + + _destroy: function() { + var i = 0, + drop = $.ui.ddmanager.droppables[this.options.scope]; + + for ( ; i < drop.length; i++ ) { + if ( drop[i] === this ) { + drop.splice(i, 1); + } + } + + this.element.removeClass("ui-droppable ui-droppable-disabled"); + }, + + _setOption: function(key, value) { + + if(key === "accept") { + this.accept = $.isFunction(value) ? value : function(d) { + return d.is(value); + }; + } + $.Widget.prototype._setOption.apply(this, arguments); + }, + + _activate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) { + this.element.addClass(this.options.activeClass); + } + if(draggable){ + this._trigger("activate", event, this.ui(draggable)); + } + }, + + _deactivate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) { + this.element.removeClass(this.options.activeClass); + } + if(draggable){ + this._trigger("deactivate", event, this.ui(draggable)); + } + }, + + _over: function(event) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return; + } + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) { + this.element.addClass(this.options.hoverClass); + } + this._trigger("over", event, this.ui(draggable)); + } + + }, + + _out: function(event) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return; + } + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) { + this.element.removeClass(this.options.hoverClass); + } + this._trigger("out", event, this.ui(draggable)); + } + + }, + + _drop: function(event,custom) { + + var draggable = custom || $.ui.ddmanager.current, + childrenIntersection = false; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return false; + } + + this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() { + var inst = $.data(this, "ui-droppable"); + if( + inst.options.greedy && + !inst.options.disabled && + inst.options.scope === draggable.options.scope && + inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) && + $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) + ) { childrenIntersection = true; return false; } + }); + if(childrenIntersection) { + return false; + } + + if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.activeClass) { + this.element.removeClass(this.options.activeClass); + } + if(this.options.hoverClass) { + this.element.removeClass(this.options.hoverClass); + } + this._trigger("drop", event, this.ui(draggable)); + return this.element; + } + + return false; + + }, + + ui: function(c) { + return { + draggable: (c.currentItem || c.element), + helper: c.helper, + position: c.position, + offset: c.positionAbs + }; + } + +}); + +$.ui.intersect = function(draggable, droppable, toleranceMode) { + + if (!droppable.offset) { + return false; + } + + var draggableLeft, draggableTop, + x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, + y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height, + l = droppable.offset.left, r = l + droppable.proportions.width, + t = droppable.offset.top, b = t + droppable.proportions.height; + + switch (toleranceMode) { + case "fit": + return (l <= x1 && x2 <= r && t <= y1 && y2 <= b); + case "intersect": + return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half + x2 - (draggable.helperProportions.width / 2) < r && // Left Half + t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half + y2 - (draggable.helperProportions.height / 2) < b ); // Top Half + case "pointer": + draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left); + draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top); + return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width ); + case "touch": + return ( + (y1 >= t && y1 <= b) || // Top edge touching + (y2 >= t && y2 <= b) || // Bottom edge touching + (y1 < t && y2 > b) // Surrounded vertically + ) && ( + (x1 >= l && x1 <= r) || // Left edge touching + (x2 >= l && x2 <= r) || // Right edge touching + (x1 < l && x2 > r) // Surrounded horizontally + ); + default: + return false; + } + +}; + +/* + This manager tracks offsets of draggables and droppables +*/ +$.ui.ddmanager = { + current: null, + droppables: { "default": [] }, + prepareOffsets: function(t, event) { + + var i, j, + m = $.ui.ddmanager.droppables[t.options.scope] || [], + type = event ? event.type : null, // workaround for #2317 + list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack(); + + droppablesLoop: for (i = 0; i < m.length; i++) { + + //No disabled and non-accepted + if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) { + continue; + } + + // Filter out elements in the current dragged item + for (j=0; j < list.length; j++) { + if(list[j] === m[i].element[0]) { + m[i].proportions.height = 0; + continue droppablesLoop; + } + } + + m[i].visible = m[i].element.css("display") !== "none"; + if(!m[i].visible) { + continue; + } + + //Activate the droppable if used directly from draggables + if(type === "mousedown") { + m[i]._activate.call(m[i], event); + } + + m[i].offset = m[i].element.offset(); + m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; + + } + + }, + drop: function(draggable, event) { + + var dropped = false; + // Create a copy of the droppables in case the list changes during the drop (#9116) + $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() { + + if(!this.options) { + return; + } + if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) { + dropped = this._drop.call(this, event) || dropped; + } + + if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + this.isout = true; + this.isover = false; + this._deactivate.call(this, event); + } + + }); + return dropped; + + }, + dragStart: function( draggable, event ) { + //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) + draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() { + if( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + }); + }, + drag: function(draggable, event) { + + //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. + if(draggable.options.refreshPositions) { + $.ui.ddmanager.prepareOffsets(draggable, event); + } + + //Run through all droppables and check their positions based on specific tolerance options + $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { + + if(this.options.disabled || this.greedyChild || !this.visible) { + return; + } + + var parentInstance, scope, parent, + intersects = $.ui.intersect(draggable, this, this.options.tolerance), + c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); + if(!c) { + return; + } + + if (this.options.greedy) { + // find droppable parents with same scope + scope = this.options.scope; + parent = this.element.parents(":data(ui-droppable)").filter(function () { + return $.data(this, "ui-droppable").options.scope === scope; + }); + + if (parent.length) { + parentInstance = $.data(parent[0], "ui-droppable"); + parentInstance.greedyChild = (c === "isover"); + } + } + + // we just moved into a greedy child + if (parentInstance && c === "isover") { + parentInstance.isover = false; + parentInstance.isout = true; + parentInstance._out.call(parentInstance, event); + } + + this[c] = true; + this[c === "isout" ? "isover" : "isout"] = false; + this[c === "isover" ? "_over" : "_out"].call(this, event); + + // we just moved out of a greedy child + if (parentInstance && c === "isout") { + parentInstance.isout = false; + parentInstance.isover = true; + parentInstance._over.call(parentInstance, event); + } + }); + + }, + dragStop: function( draggable, event ) { + draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" ); + //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) + if( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + } +}; + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.effect-all.js b/extensions/jui/assets/jquery.ui.effect-all.js new file mode 100755 index 0000000..f5c6bc7 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.effect-all.js @@ -0,0 +1,2261 @@ +/*! jQuery UI - v1.10.3 - 2013-07-18 +* http://jqueryui.com +* Includes: jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ + +(function($, undefined) { + +var dataSpace = "ui-effects-"; + +$.effects = { + effect: {} +}; + +/*! + * jQuery Color Animations v2.1.2 + * https://github.com/jquery/jquery-color + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * Date: Wed Jan 16 08:47:09 2013 -0600 + */ +(function( jQuery, undefined ) { + + var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", + + // plusequals test for += 100 -= 100 + rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, + // a set of RE's that can match strings and generate color tuples. + stringParsers = [{ + re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ], + execResult[ 3 ], + execResult[ 4 ] + ]; + } + }, { + re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ] * 2.55, + execResult[ 2 ] * 2.55, + execResult[ 3 ] * 2.55, + execResult[ 4 ] + ]; + } + }, { + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ], 16 ) + ]; + } + }, { + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) + ]; + } + }, { + re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + space: "hsla", + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ] / 100, + execResult[ 3 ] / 100, + execResult[ 4 ] + ]; + } + }], + + // jQuery.Color( ) + color = jQuery.Color = function( color, green, blue, alpha ) { + return new jQuery.Color.fn.parse( color, green, blue, alpha ); + }, + spaces = { + rgba: { + props: { + red: { + idx: 0, + type: "byte" + }, + green: { + idx: 1, + type: "byte" + }, + blue: { + idx: 2, + type: "byte" + } + } + }, + + hsla: { + props: { + hue: { + idx: 0, + type: "degrees" + }, + saturation: { + idx: 1, + type: "percent" + }, + lightness: { + idx: 2, + type: "percent" + } + } + } + }, + propTypes = { + "byte": { + floor: true, + max: 255 + }, + "percent": { + max: 1 + }, + "degrees": { + mod: 360, + floor: true + } + }, + support = color.support = {}, + + // element for support tests + supportElem = jQuery( "

    " )[ 0 ], + + // colors = jQuery.Color.names + colors, + + // local aliases of functions called often + each = jQuery.each; + +// determine rgba support immediately +supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; +support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; + +// define cache name and alpha properties +// for rgba and hsla spaces +each( spaces, function( spaceName, space ) { + space.cache = "_" + spaceName; + space.props.alpha = { + idx: 3, + type: "percent", + def: 1 + }; +}); + +function clamp( value, prop, allowEmpty ) { + var type = propTypes[ prop.type ] || {}; + + if ( value == null ) { + return (allowEmpty || !prop.def) ? null : prop.def; + } + + // ~~ is an short way of doing floor for positive numbers + value = type.floor ? ~~value : parseFloat( value ); + + // IE will pass in empty strings as value for alpha, + // which will hit this case + if ( isNaN( value ) ) { + return prop.def; + } + + if ( type.mod ) { + // we add mod before modding to make sure that negatives values + // get converted properly: -10 -> 350 + return (value + type.mod) % type.mod; + } + + // for now all property types without mod have min and max + return 0 > value ? 0 : type.max < value ? type.max : value; +} + +function stringParse( string ) { + var inst = color(), + rgba = inst._rgba = []; + + string = string.toLowerCase(); + + each( stringParsers, function( i, parser ) { + var parsed, + match = parser.re.exec( string ), + values = match && parser.parse( match ), + spaceName = parser.space || "rgba"; + + if ( values ) { + parsed = inst[ spaceName ]( values ); + + // if this was an rgba parse the assignment might happen twice + // oh well.... + inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; + rgba = inst._rgba = parsed._rgba; + + // exit each( stringParsers ) here because we matched + return false; + } + }); + + // Found a stringParser that handled it + if ( rgba.length ) { + + // if this came from a parsed string, force "transparent" when alpha is 0 + // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) + if ( rgba.join() === "0,0,0,0" ) { + jQuery.extend( rgba, colors.transparent ); + } + return inst; + } + + // named colors + return colors[ string ]; +} + +color.fn = jQuery.extend( color.prototype, { + parse: function( red, green, blue, alpha ) { + if ( red === undefined ) { + this._rgba = [ null, null, null, null ]; + return this; + } + if ( red.jquery || red.nodeType ) { + red = jQuery( red ).css( green ); + green = undefined; + } + + var inst = this, + type = jQuery.type( red ), + rgba = this._rgba = []; + + // more than 1 argument specified - assume ( red, green, blue, alpha ) + if ( green !== undefined ) { + red = [ red, green, blue, alpha ]; + type = "array"; + } + + if ( type === "string" ) { + return this.parse( stringParse( red ) || colors._default ); + } + + if ( type === "array" ) { + each( spaces.rgba.props, function( key, prop ) { + rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); + }); + return this; + } + + if ( type === "object" ) { + if ( red instanceof color ) { + each( spaces, function( spaceName, space ) { + if ( red[ space.cache ] ) { + inst[ space.cache ] = red[ space.cache ].slice(); + } + }); + } else { + each( spaces, function( spaceName, space ) { + var cache = space.cache; + each( space.props, function( key, prop ) { + + // if the cache doesn't exist, and we know how to convert + if ( !inst[ cache ] && space.to ) { + + // if the value was null, we don't need to copy it + // if the key was alpha, we don't need to copy it either + if ( key === "alpha" || red[ key ] == null ) { + return; + } + inst[ cache ] = space.to( inst._rgba ); + } + + // this is the only case where we allow nulls for ALL properties. + // call clamp with alwaysAllowEmpty + inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); + }); + + // everything defined but alpha? + if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { + // use the default of 1 + inst[ cache ][ 3 ] = 1; + if ( space.from ) { + inst._rgba = space.from( inst[ cache ] ); + } + } + }); + } + return this; + } + }, + is: function( compare ) { + var is = color( compare ), + same = true, + inst = this; + + each( spaces, function( _, space ) { + var localCache, + isCache = is[ space.cache ]; + if (isCache) { + localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; + each( space.props, function( _, prop ) { + if ( isCache[ prop.idx ] != null ) { + same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); + return same; + } + }); + } + return same; + }); + return same; + }, + _space: function() { + var used = [], + inst = this; + each( spaces, function( spaceName, space ) { + if ( inst[ space.cache ] ) { + used.push( spaceName ); + } + }); + return used.pop(); + }, + transition: function( other, distance ) { + var end = color( other ), + spaceName = end._space(), + space = spaces[ spaceName ], + startColor = this.alpha() === 0 ? color( "transparent" ) : this, + start = startColor[ space.cache ] || space.to( startColor._rgba ), + result = start.slice(); + + end = end[ space.cache ]; + each( space.props, function( key, prop ) { + var index = prop.idx, + startValue = start[ index ], + endValue = end[ index ], + type = propTypes[ prop.type ] || {}; + + // if null, don't override start value + if ( endValue === null ) { + return; + } + // if null - use end + if ( startValue === null ) { + result[ index ] = endValue; + } else { + if ( type.mod ) { + if ( endValue - startValue > type.mod / 2 ) { + startValue += type.mod; + } else if ( startValue - endValue > type.mod / 2 ) { + startValue -= type.mod; + } + } + result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); + } + }); + return this[ spaceName ]( result ); + }, + blend: function( opaque ) { + // if we are already opaque - return ourself + if ( this._rgba[ 3 ] === 1 ) { + return this; + } + + var rgb = this._rgba.slice(), + a = rgb.pop(), + blend = color( opaque )._rgba; + + return color( jQuery.map( rgb, function( v, i ) { + return ( 1 - a ) * blend[ i ] + a * v; + })); + }, + toRgbaString: function() { + var prefix = "rgba(", + rgba = jQuery.map( this._rgba, function( v, i ) { + return v == null ? ( i > 2 ? 1 : 0 ) : v; + }); + + if ( rgba[ 3 ] === 1 ) { + rgba.pop(); + prefix = "rgb("; + } + + return prefix + rgba.join() + ")"; + }, + toHslaString: function() { + var prefix = "hsla(", + hsla = jQuery.map( this.hsla(), function( v, i ) { + if ( v == null ) { + v = i > 2 ? 1 : 0; + } + + // catch 1 and 2 + if ( i && i < 3 ) { + v = Math.round( v * 100 ) + "%"; + } + return v; + }); + + if ( hsla[ 3 ] === 1 ) { + hsla.pop(); + prefix = "hsl("; + } + return prefix + hsla.join() + ")"; + }, + toHexString: function( includeAlpha ) { + var rgba = this._rgba.slice(), + alpha = rgba.pop(); + + if ( includeAlpha ) { + rgba.push( ~~( alpha * 255 ) ); + } + + return "#" + jQuery.map( rgba, function( v ) { + + // default to 0 when nulls exist + v = ( v || 0 ).toString( 16 ); + return v.length === 1 ? "0" + v : v; + }).join(""); + }, + toString: function() { + return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); + } +}); +color.fn.parse.prototype = color.fn; + +// hsla conversions adapted from: +// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 + +function hue2rgb( p, q, h ) { + h = ( h + 1 ) % 1; + if ( h * 6 < 1 ) { + return p + (q - p) * h * 6; + } + if ( h * 2 < 1) { + return q; + } + if ( h * 3 < 2 ) { + return p + (q - p) * ((2/3) - h) * 6; + } + return p; +} + +spaces.hsla.to = function ( rgba ) { + if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { + return [ null, null, null, rgba[ 3 ] ]; + } + var r = rgba[ 0 ] / 255, + g = rgba[ 1 ] / 255, + b = rgba[ 2 ] / 255, + a = rgba[ 3 ], + max = Math.max( r, g, b ), + min = Math.min( r, g, b ), + diff = max - min, + add = max + min, + l = add * 0.5, + h, s; + + if ( min === max ) { + h = 0; + } else if ( r === max ) { + h = ( 60 * ( g - b ) / diff ) + 360; + } else if ( g === max ) { + h = ( 60 * ( b - r ) / diff ) + 120; + } else { + h = ( 60 * ( r - g ) / diff ) + 240; + } + + // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% + // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) + if ( diff === 0 ) { + s = 0; + } else if ( l <= 0.5 ) { + s = diff / add; + } else { + s = diff / ( 2 - add ); + } + return [ Math.round(h) % 360, s, l, a == null ? 1 : a ]; +}; + +spaces.hsla.from = function ( hsla ) { + if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { + return [ null, null, null, hsla[ 3 ] ]; + } + var h = hsla[ 0 ] / 360, + s = hsla[ 1 ], + l = hsla[ 2 ], + a = hsla[ 3 ], + q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, + p = 2 * l - q; + + return [ + Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), + Math.round( hue2rgb( p, q, h ) * 255 ), + Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), + a + ]; +}; + + +each( spaces, function( spaceName, space ) { + var props = space.props, + cache = space.cache, + to = space.to, + from = space.from; + + // makes rgba() and hsla() + color.fn[ spaceName ] = function( value ) { + + // generate a cache for this space if it doesn't exist + if ( to && !this[ cache ] ) { + this[ cache ] = to( this._rgba ); + } + if ( value === undefined ) { + return this[ cache ].slice(); + } + + var ret, + type = jQuery.type( value ), + arr = ( type === "array" || type === "object" ) ? value : arguments, + local = this[ cache ].slice(); + + each( props, function( key, prop ) { + var val = arr[ type === "object" ? key : prop.idx ]; + if ( val == null ) { + val = local[ prop.idx ]; + } + local[ prop.idx ] = clamp( val, prop ); + }); + + if ( from ) { + ret = color( from( local ) ); + ret[ cache ] = local; + return ret; + } else { + return color( local ); + } + }; + + // makes red() green() blue() alpha() hue() saturation() lightness() + each( props, function( key, prop ) { + // alpha is included in more than one space + if ( color.fn[ key ] ) { + return; + } + color.fn[ key ] = function( value ) { + var vtype = jQuery.type( value ), + fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), + local = this[ fn ](), + cur = local[ prop.idx ], + match; + + if ( vtype === "undefined" ) { + return cur; + } + + if ( vtype === "function" ) { + value = value.call( this, cur ); + vtype = jQuery.type( value ); + } + if ( value == null && prop.empty ) { + return this; + } + if ( vtype === "string" ) { + match = rplusequals.exec( value ); + if ( match ) { + value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); + } + } + local[ prop.idx ] = value; + return this[ fn ]( local ); + }; + }); +}); + +// add cssHook and .fx.step function for each named hook. +// accept a space separated string of properties +color.hook = function( hook ) { + var hooks = hook.split( " " ); + each( hooks, function( i, hook ) { + jQuery.cssHooks[ hook ] = { + set: function( elem, value ) { + var parsed, curElem, + backgroundColor = ""; + + if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { + value = color( parsed || value ); + if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { + curElem = hook === "backgroundColor" ? elem.parentNode : elem; + while ( + (backgroundColor === "" || backgroundColor === "transparent") && + curElem && curElem.style + ) { + try { + backgroundColor = jQuery.css( curElem, "backgroundColor" ); + curElem = curElem.parentNode; + } catch ( e ) { + } + } + + value = value.blend( backgroundColor && backgroundColor !== "transparent" ? + backgroundColor : + "_default" ); + } + + value = value.toRgbaString(); + } + try { + elem.style[ hook ] = value; + } catch( e ) { + // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' + } + } + }; + jQuery.fx.step[ hook ] = function( fx ) { + if ( !fx.colorInit ) { + fx.start = color( fx.elem, hook ); + fx.end = color( fx.end ); + fx.colorInit = true; + } + jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); + }; + }); + +}; + +color.hook( stepHooks ); + +jQuery.cssHooks.borderColor = { + expand: function( value ) { + var expanded = {}; + + each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { + expanded[ "border" + part + "Color" ] = value; + }); + return expanded; + } +}; + +// Basic color names only. +// Usage of any of the other color names requires adding yourself or including +// jquery.color.svg-names.js. +colors = jQuery.Color.names = { + // 4.1. Basic color keywords + aqua: "#00ffff", + black: "#000000", + blue: "#0000ff", + fuchsia: "#ff00ff", + gray: "#808080", + green: "#008000", + lime: "#00ff00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + purple: "#800080", + red: "#ff0000", + silver: "#c0c0c0", + teal: "#008080", + white: "#ffffff", + yellow: "#ffff00", + + // 4.2.3. "transparent" color keyword + transparent: [ null, null, null, 0 ], + + _default: "#ffffff" +}; + +})( jQuery ); + + +/******************************************************************************/ +/****************************** CLASS ANIMATIONS ******************************/ +/******************************************************************************/ +(function() { + +var classAnimationActions = [ "add", "remove", "toggle" ], + shorthandStyles = { + border: 1, + borderBottom: 1, + borderColor: 1, + borderLeft: 1, + borderRight: 1, + borderTop: 1, + borderWidth: 1, + margin: 1, + padding: 1 + }; + +$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { + $.fx.step[ prop ] = function( fx ) { + if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { + jQuery.style( fx.elem, prop, fx.end ); + fx.setAttr = true; + } + }; +}); + +function getElementStyles( elem ) { + var key, len, + style = elem.ownerDocument.defaultView ? + elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : + elem.currentStyle, + styles = {}; + + if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { + len = style.length; + while ( len-- ) { + key = style[ len ]; + if ( typeof style[ key ] === "string" ) { + styles[ $.camelCase( key ) ] = style[ key ]; + } + } + // support: Opera, IE <9 + } else { + for ( key in style ) { + if ( typeof style[ key ] === "string" ) { + styles[ key ] = style[ key ]; + } + } + } + + return styles; +} + + +function styleDifference( oldStyle, newStyle ) { + var diff = {}, + name, value; + + for ( name in newStyle ) { + value = newStyle[ name ]; + if ( oldStyle[ name ] !== value ) { + if ( !shorthandStyles[ name ] ) { + if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { + diff[ name ] = value; + } + } + } + } + + return diff; +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +$.effects.animateClass = function( value, duration, easing, callback ) { + var o = $.speed( duration, easing, callback ); + + return this.queue( function() { + var animated = $( this ), + baseClass = animated.attr( "class" ) || "", + applyClassChange, + allAnimations = o.children ? animated.find( "*" ).addBack() : animated; + + // map the animated objects to store the original styles. + allAnimations = allAnimations.map(function() { + var el = $( this ); + return { + el: el, + start: getElementStyles( this ) + }; + }); + + // apply class change + applyClassChange = function() { + $.each( classAnimationActions, function(i, action) { + if ( value[ action ] ) { + animated[ action + "Class" ]( value[ action ] ); + } + }); + }; + applyClassChange(); + + // map all animated objects again - calculate new styles and diff + allAnimations = allAnimations.map(function() { + this.end = getElementStyles( this.el[ 0 ] ); + this.diff = styleDifference( this.start, this.end ); + return this; + }); + + // apply original class + animated.attr( "class", baseClass ); + + // map all animated objects again - this time collecting a promise + allAnimations = allAnimations.map(function() { + var styleInfo = this, + dfd = $.Deferred(), + opts = $.extend({}, o, { + queue: false, + complete: function() { + dfd.resolve( styleInfo ); + } + }); + + this.el.animate( this.diff, opts ); + return dfd.promise(); + }); + + // once all animations have completed: + $.when.apply( $, allAnimations.get() ).done(function() { + + // set the final class + applyClassChange(); + + // for each animated element, + // clear all css properties that were animated + $.each( arguments, function() { + var el = this.el; + $.each( this.diff, function(key) { + el.css( key, "" ); + }); + }); + + // this is guarnteed to be there if you use jQuery.speed() + // it also handles dequeuing the next anim... + o.complete.call( animated[ 0 ] ); + }); + }); +}; + +$.fn.extend({ + addClass: (function( orig ) { + return function( classNames, speed, easing, callback ) { + return speed ? + $.effects.animateClass.call( this, + { add: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + })( $.fn.addClass ), + + removeClass: (function( orig ) { + return function( classNames, speed, easing, callback ) { + return arguments.length > 1 ? + $.effects.animateClass.call( this, + { remove: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + })( $.fn.removeClass ), + + toggleClass: (function( orig ) { + return function( classNames, force, speed, easing, callback ) { + if ( typeof force === "boolean" || force === undefined ) { + if ( !speed ) { + // without speed parameter + return orig.apply( this, arguments ); + } else { + return $.effects.animateClass.call( this, + (force ? { add: classNames } : { remove: classNames }), + speed, easing, callback ); + } + } else { + // without force parameter + return $.effects.animateClass.call( this, + { toggle: classNames }, force, speed, easing ); + } + }; + })( $.fn.toggleClass ), + + switchClass: function( remove, add, speed, easing, callback) { + return $.effects.animateClass.call( this, { + add: add, + remove: remove + }, speed, easing, callback ); + } +}); + +})(); + +/******************************************************************************/ +/*********************************** EFFECTS **********************************/ +/******************************************************************************/ + +(function() { + +$.extend( $.effects, { + version: "1.10.3", + + // Saves a set of properties in a data storage + save: function( element, set ) { + for( var i=0; i < set.length; i++ ) { + if ( set[ i ] !== null ) { + element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); + } + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function( element, set ) { + var val, i; + for( i=0; i < set.length; i++ ) { + if ( set[ i ] !== null ) { + val = element.data( dataSpace + set[ i ] ); + // support: jQuery 1.6.2 + // http://bugs.jquery.com/ticket/9917 + // jQuery 1.6.2 incorrectly returns undefined for any falsy value. + // We can't differentiate between "" and 0 here, so we just assume + // empty string since it's likely to be a more common value... + if ( val === undefined ) { + val = ""; + } + element.css( set[ i ], val ); + } + } + }, + + setMode: function( el, mode ) { + if (mode === "toggle") { + mode = el.is( ":hidden" ) ? "show" : "hide"; + } + return mode; + }, + + // Translates a [top,left] array into a baseline value + // this should be a little more flexible in the future to handle a string & hash + getBaseline: function( origin, original ) { + var y, x; + switch ( origin[ 0 ] ) { + case "top": y = 0; break; + case "middle": y = 0.5; break; + case "bottom": y = 1; break; + default: y = origin[ 0 ] / original.height; + } + switch ( origin[ 1 ] ) { + case "left": x = 0; break; + case "center": x = 0.5; break; + case "right": x = 1; break; + default: x = origin[ 1 ] / original.width; + } + return { + x: x, + y: y + }; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function( element ) { + + // if the element is already wrapped, return it + if ( element.parent().is( ".ui-effects-wrapper" )) { + return element.parent(); + } + + // wrap the element + var props = { + width: element.outerWidth(true), + height: element.outerHeight(true), + "float": element.css( "float" ) + }, + wrapper = $( "

    " ) + .addClass( "ui-effects-wrapper" ) + .css({ + fontSize: "100%", + background: "transparent", + border: "none", + margin: 0, + padding: 0 + }), + // Store the size in case width/height are defined in % - Fixes #5245 + size = { + width: element.width(), + height: element.height() + }, + active = document.activeElement; + + // support: Firefox + // Firefox incorrectly exposes anonymous content + // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 + try { + active.id; + } catch( e ) { + active = document.body; + } + + element.wrap( wrapper ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + + wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element + + // transfer positioning properties to the wrapper + if ( element.css( "position" ) === "static" ) { + wrapper.css({ position: "relative" }); + element.css({ position: "relative" }); + } else { + $.extend( props, { + position: element.css( "position" ), + zIndex: element.css( "z-index" ) + }); + $.each([ "top", "left", "bottom", "right" ], function(i, pos) { + props[ pos ] = element.css( pos ); + if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { + props[ pos ] = "auto"; + } + }); + element.css({ + position: "relative", + top: 0, + left: 0, + right: "auto", + bottom: "auto" + }); + } + element.css(size); + + return wrapper.css( props ).show(); + }, + + removeWrapper: function( element ) { + var active = document.activeElement; + + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + element.parent().replaceWith( element ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + } + + + return element; + }, + + setTransition: function( element, list, factor, value ) { + value = value || {}; + $.each( list, function( i, x ) { + var unit = element.cssUnit( x ); + if ( unit[ 0 ] > 0 ) { + value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; + } + }); + return value; + } +}); + +// return an effect options object for the given parameters: +function _normalizeArguments( effect, options, speed, callback ) { + + // allow passing all options as the first parameter + if ( $.isPlainObject( effect ) ) { + options = effect; + effect = effect.effect; + } + + // convert to an object + effect = { effect: effect }; + + // catch (effect, null, ...) + if ( options == null ) { + options = {}; + } + + // catch (effect, callback) + if ( $.isFunction( options ) ) { + callback = options; + speed = null; + options = {}; + } + + // catch (effect, speed, ?) + if ( typeof options === "number" || $.fx.speeds[ options ] ) { + callback = speed; + speed = options; + options = {}; + } + + // catch (effect, options, callback) + if ( $.isFunction( speed ) ) { + callback = speed; + speed = null; + } + + // add options to effect + if ( options ) { + $.extend( effect, options ); + } + + speed = speed || options.duration; + effect.duration = $.fx.off ? 0 : + typeof speed === "number" ? speed : + speed in $.fx.speeds ? $.fx.speeds[ speed ] : + $.fx.speeds._default; + + effect.complete = callback || options.complete; + + return effect; +} + +function standardAnimationOption( option ) { + // Valid standard speeds (nothing, number, named speed) + if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { + return true; + } + + // Invalid strings - treat as "normal" speed + if ( typeof option === "string" && !$.effects.effect[ option ] ) { + return true; + } + + // Complete callback + if ( $.isFunction( option ) ) { + return true; + } + + // Options hash (but not naming an effect) + if ( typeof option === "object" && !option.effect ) { + return true; + } + + // Didn't match any standard API + return false; +} + +$.fn.extend({ + effect: function( /* effect, options, speed, callback */ ) { + var args = _normalizeArguments.apply( this, arguments ), + mode = args.mode, + queue = args.queue, + effectMethod = $.effects.effect[ args.effect ]; + + if ( $.fx.off || !effectMethod ) { + // delegate to the original method (e.g., .show()) if possible + if ( mode ) { + return this[ mode ]( args.duration, args.complete ); + } else { + return this.each( function() { + if ( args.complete ) { + args.complete.call( this ); + } + }); + } + } + + function run( next ) { + var elem = $( this ), + complete = args.complete, + mode = args.mode; + + function done() { + if ( $.isFunction( complete ) ) { + complete.call( elem[0] ); + } + if ( $.isFunction( next ) ) { + next(); + } + } + + // If the element already has the correct final state, delegate to + // the core methods so the internal tracking of "olddisplay" works. + if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[0], args, done ); + } + } + + return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); + }, + + show: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "show"; + return this.effect.call( this, args ); + } + }; + })( $.fn.show ), + + hide: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "hide"; + return this.effect.call( this, args ); + } + }; + })( $.fn.hide ), + + toggle: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) || typeof option === "boolean" ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "toggle"; + return this.effect.call( this, args ); + } + }; + })( $.fn.toggle ), + + // helper functions + cssUnit: function(key) { + var style = this.css( key ), + val = []; + + $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { + if ( style.indexOf( unit ) > 0 ) { + val = [ parseFloat( style ), unit ]; + } + }); + return val; + } +}); + +})(); + +/******************************************************************************/ +/*********************************** EASING ***********************************/ +/******************************************************************************/ + +(function() { + +// based on easing equations from Robert Penner (http://www.robertpenner.com/easing) + +var baseEasings = {}; + +$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { + baseEasings[ name ] = function( p ) { + return Math.pow( p, i + 2 ); + }; +}); + +$.extend( baseEasings, { + Sine: function ( p ) { + return 1 - Math.cos( p * Math.PI / 2 ); + }, + Circ: function ( p ) { + return 1 - Math.sqrt( 1 - p * p ); + }, + Elastic: function( p ) { + return p === 0 || p === 1 ? p : + -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 ); + }, + Back: function( p ) { + return p * p * ( 3 * p - 2 ); + }, + Bounce: function ( p ) { + var pow2, + bounce = 4; + + while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} + return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); + } +}); + +$.each( baseEasings, function( name, easeIn ) { + $.easing[ "easeIn" + name ] = easeIn; + $.easing[ "easeOut" + name ] = function( p ) { + return 1 - easeIn( 1 - p ); + }; + $.easing[ "easeInOut" + name ] = function( p ) { + return p < 0.5 ? + easeIn( p * 2 ) / 2 : + 1 - easeIn( p * -2 + 2 ) / 2; + }; +}); + +})(); + +})(jQuery); +(function( $, undefined ) { + +var rvertical = /up|down|vertical/, + rpositivemotion = /up|left|vertical|horizontal/; + +$.effects.effect.blind = function( o, done ) { + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + direction = o.direction || "up", + vertical = rvertical.test( direction ), + ref = vertical ? "height" : "width", + ref2 = vertical ? "top" : "left", + motion = rpositivemotion.test( direction ), + animation = {}, + show = mode === "show", + wrapper, distance, margin; + + // if already wrapped, the wrapper's properties are my property. #6245 + if ( el.parent().is( ".ui-effects-wrapper" ) ) { + $.effects.save( el.parent(), props ); + } else { + $.effects.save( el, props ); + } + el.show(); + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + + distance = wrapper[ ref ](); + margin = parseFloat( wrapper.css( ref2 ) ) || 0; + + animation[ ref ] = show ? distance : 0; + if ( !motion ) { + el + .css( vertical ? "bottom" : "right", 0 ) + .css( vertical ? "top" : "left", "auto" ) + .css({ position: "absolute" }); + + animation[ ref2 ] = show ? margin : distance + margin; + } + + // start at 0 if we are showing + if ( show ) { + wrapper.css( ref, 0 ); + if ( ! motion ) { + wrapper.css( ref2, margin + distance ); + } + } + + // Animate + wrapper.animate( animation, { + duration: o.duration, + easing: o.easing, + queue: false, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.bounce = function( o, done ) { + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + + // defaults: + mode = $.effects.setMode( el, o.mode || "effect" ), + hide = mode === "hide", + show = mode === "show", + direction = o.direction || "up", + distance = o.distance, + times = o.times || 5, + + // number of internal animations + anims = times * 2 + ( show || hide ? 1 : 0 ), + speed = o.duration / anims, + easing = o.easing, + + // utility: + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ), + i, + upAnim, + downAnim, + + // we will need to re-assemble the queue to stack our animations in place + queue = el.queue(), + queuelen = queue.length; + + // Avoid touching opacity to prevent clearType and PNG issues in IE + if ( show || hide ) { + props.push( "opacity" ); + } + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); // Create Wrapper + + // default distance for the BIGGEST bounce is the outer Distance / 3 + if ( !distance ) { + distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; + } + + if ( show ) { + downAnim = { opacity: 1 }; + downAnim[ ref ] = 0; + + // if we are showing, force opacity 0 and set the initial position + // then do the "first" animation + el.css( "opacity", 0 ) + .css( ref, motion ? -distance * 2 : distance * 2 ) + .animate( downAnim, speed, easing ); + } + + // start at the smallest distance if we are hiding + if ( hide ) { + distance = distance / Math.pow( 2, times - 1 ); + } + + downAnim = {}; + downAnim[ ref ] = 0; + // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here + for ( i = 0; i < times; i++ ) { + upAnim = {}; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + + el.animate( upAnim, speed, easing ) + .animate( downAnim, speed, easing ); + + distance = hide ? distance * 2 : distance / 2; + } + + // Last Bounce when Hiding + if ( hide ) { + upAnim = { opacity: 0 }; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + + el.animate( upAnim, speed, easing ); + } + + el.queue(function() { + if ( hide ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + + // inject all the animations we just queued to be first in line (after "inprogress") + if ( queuelen > 1) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + el.dequeue(); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.clip = function( o, done ) { + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + direction = o.direction || "vertical", + vert = direction === "vertical", + size = vert ? "height" : "width", + position = vert ? "top" : "left", + animation = {}, + wrapper, animate, distance; + + // Save & Show + $.effects.save( el, props ); + el.show(); + + // Create Wrapper + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + animate = ( el[0].tagName === "IMG" ) ? wrapper : el; + distance = animate[ size ](); + + // Shift + if ( show ) { + animate.css( size, 0 ); + animate.css( position, distance / 2 ); + } + + // Create Animation Object: + animation[ size ] = show ? distance : 0; + animation[ position ] = show ? 0 : distance / 2; + + // Animate + animate.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( !show ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.drop = function( o, done ) { + + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + direction = o.direction || "left", + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg", + animation = { + opacity: show ? 1 : 0 + }, + distance; + + // Adjust + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + + distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2; + + if ( show ) { + el + .css( "opacity", 0 ) + .css( ref, motion === "pos" ? -distance : distance ); + } + + // Animation + animation[ ref ] = ( show ? + ( motion === "pos" ? "+=" : "-=" ) : + ( motion === "pos" ? "-=" : "+=" ) ) + + distance; + + // Animate + el.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.explode = function( o, done ) { + + var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3, + cells = rows, + el = $( this ), + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + + // show and then visibility:hidden the element before calculating offset + offset = el.show().css( "visibility", "hidden" ).offset(), + + // width and height of a piece + width = Math.ceil( el.outerWidth() / cells ), + height = Math.ceil( el.outerHeight() / rows ), + pieces = [], + + // loop + i, j, left, top, mx, my; + + // children animate complete: + function childComplete() { + pieces.push( this ); + if ( pieces.length === rows * cells ) { + animComplete(); + } + } + + // clone the element for each row and cell. + for( i = 0; i < rows ; i++ ) { // ===> + top = offset.top + i * height; + my = i - ( rows - 1 ) / 2 ; + + for( j = 0; j < cells ; j++ ) { // ||| + left = offset.left + j * width; + mx = j - ( cells - 1 ) / 2 ; + + // Create a clone of the now hidden main element that will be absolute positioned + // within a wrapper div off the -left and -top equal to size of our pieces + el + .clone() + .appendTo( "body" ) + .wrap( "
    " ) + .css({ + position: "absolute", + visibility: "visible", + left: -j * width, + top: -i * height + }) + + // select the wrapper - make it overflow: hidden and absolute positioned based on + // where the original was located +left and +top equal to the size of pieces + .parent() + .addClass( "ui-effects-explode" ) + .css({ + position: "absolute", + overflow: "hidden", + width: width, + height: height, + left: left + ( show ? mx * width : 0 ), + top: top + ( show ? my * height : 0 ), + opacity: show ? 0 : 1 + }).animate({ + left: left + ( show ? 0 : mx * width ), + top: top + ( show ? 0 : my * height ), + opacity: show ? 1 : 0 + }, o.duration || 500, o.easing, childComplete ); + } + } + + function animComplete() { + el.css({ + visibility: "visible" + }); + $( pieces ).remove(); + if ( !show ) { + el.hide(); + } + done(); + } +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.fade = function( o, done ) { + var el = $( this ), + mode = $.effects.setMode( el, o.mode || "toggle" ); + + el.animate({ + opacity: mode + }, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: done + }); +}; + +})( jQuery ); +(function( $, undefined ) { + +$.effects.effect.fold = function( o, done ) { + + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + hide = mode === "hide", + size = o.size || 15, + percent = /([0-9]+)%/.exec( size ), + horizFirst = !!o.horizFirst, + widthFirst = show !== horizFirst, + ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ], + duration = o.duration / 2, + wrapper, distance, + animation1 = {}, + animation2 = {}; + + $.effects.save( el, props ); + el.show(); + + // Create Wrapper + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + distance = widthFirst ? + [ wrapper.width(), wrapper.height() ] : + [ wrapper.height(), wrapper.width() ]; + + if ( percent ) { + size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; + } + if ( show ) { + wrapper.css( horizFirst ? { + height: 0, + width: size + } : { + height: size, + width: 0 + }); + } + + // Animation + animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size; + animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0; + + // Animate + wrapper + .animate( animation1, duration, o.easing ) + .animate( animation2, duration, o.easing, function() { + if ( hide ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.highlight = function( o, done ) { + var elem = $( this ), + props = [ "backgroundImage", "backgroundColor", "opacity" ], + mode = $.effects.setMode( elem, o.mode || "show" ), + animation = { + backgroundColor: elem.css( "backgroundColor" ) + }; + + if (mode === "hide") { + animation.opacity = 0; + } + + $.effects.save( elem, props ); + + elem + .show() + .css({ + backgroundImage: "none", + backgroundColor: o.color || "#ffff99" + }) + .animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + elem.hide(); + } + $.effects.restore( elem, props ); + done(); + } + }); +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.pulsate = function( o, done ) { + var elem = $( this ), + mode = $.effects.setMode( elem, o.mode || "show" ), + show = mode === "show", + hide = mode === "hide", + showhide = ( show || mode === "hide" ), + + // showing or hiding leaves of the "last" animation + anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), + duration = o.duration / anims, + animateTo = 0, + queue = elem.queue(), + queuelen = queue.length, + i; + + if ( show || !elem.is(":visible")) { + elem.css( "opacity", 0 ).show(); + animateTo = 1; + } + + // anims - 1 opacity "toggles" + for ( i = 1; i < anims; i++ ) { + elem.animate({ + opacity: animateTo + }, duration, o.easing ); + animateTo = 1 - animateTo; + } + + elem.animate({ + opacity: animateTo + }, duration, o.easing); + + elem.queue(function() { + if ( hide ) { + elem.hide(); + } + done(); + }); + + // We just queued up "anims" animations, we need to put them next in the queue + if ( queuelen > 1 ) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + elem.dequeue(); +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.puff = function( o, done ) { + var elem = $( this ), + mode = $.effects.setMode( elem, o.mode || "hide" ), + hide = mode === "hide", + percent = parseInt( o.percent, 10 ) || 150, + factor = percent / 100, + original = { + height: elem.height(), + width: elem.width(), + outerHeight: elem.outerHeight(), + outerWidth: elem.outerWidth() + }; + + $.extend( o, { + effect: "scale", + queue: false, + fade: true, + mode: mode, + complete: done, + percent: hide ? percent : 100, + from: hide ? + original : + { + height: original.height * factor, + width: original.width * factor, + outerHeight: original.outerHeight * factor, + outerWidth: original.outerWidth * factor + } + }); + + elem.effect( o ); +}; + +$.effects.effect.scale = function( o, done ) { + + // Create element + var el = $( this ), + options = $.extend( true, {}, o ), + mode = $.effects.setMode( el, o.mode || "effect" ), + percent = parseInt( o.percent, 10 ) || + ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ), + direction = o.direction || "both", + origin = o.origin, + original = { + height: el.height(), + width: el.width(), + outerHeight: el.outerHeight(), + outerWidth: el.outerWidth() + }, + factor = { + y: direction !== "horizontal" ? (percent / 100) : 1, + x: direction !== "vertical" ? (percent / 100) : 1 + }; + + // We are going to pass this effect to the size effect: + options.effect = "size"; + options.queue = false; + options.complete = done; + + // Set default origin and restore for show/hide + if ( mode !== "effect" ) { + options.origin = origin || ["middle","center"]; + options.restore = true; + } + + options.from = o.from || ( mode === "show" ? { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + } : original ); + options.to = { + height: original.height * factor.y, + width: original.width * factor.x, + outerHeight: original.outerHeight * factor.y, + outerWidth: original.outerWidth * factor.x + }; + + // Fade option to support puff + if ( options.fade ) { + if ( mode === "show" ) { + options.from.opacity = 0; + options.to.opacity = 1; + } + if ( mode === "hide" ) { + options.from.opacity = 1; + options.to.opacity = 0; + } + } + + // Animate + el.effect( options ); + +}; + +$.effects.effect.size = function( o, done ) { + + // Create element + var original, baseline, factor, + el = $( this ), + props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ], + + // Always restore + props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ], + + // Copy for children + props2 = [ "width", "height", "overflow" ], + cProps = [ "fontSize" ], + vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], + hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], + + // Set options + mode = $.effects.setMode( el, o.mode || "effect" ), + restore = o.restore || mode !== "effect", + scale = o.scale || "both", + origin = o.origin || [ "middle", "center" ], + position = el.css( "position" ), + props = restore ? props0 : props1, + zero = { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + }; + + if ( mode === "show" ) { + el.show(); + } + original = { + height: el.height(), + width: el.width(), + outerHeight: el.outerHeight(), + outerWidth: el.outerWidth() + }; + + if ( o.mode === "toggle" && mode === "show" ) { + el.from = o.to || zero; + el.to = o.from || original; + } else { + el.from = o.from || ( mode === "show" ? zero : original ); + el.to = o.to || ( mode === "hide" ? zero : original ); + } + + // Set scaling factor + factor = { + from: { + y: el.from.height / original.height, + x: el.from.width / original.width + }, + to: { + y: el.to.height / original.height, + x: el.to.width / original.width + } + }; + + // Scale the css box + if ( scale === "box" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + props = props.concat( vProps ); + el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from ); + el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + props = props.concat( hProps ); + el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from ); + el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to ); + } + } + + // Scale the content + if ( scale === "content" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + props = props.concat( cProps ).concat( props2 ); + el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from ); + el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to ); + } + } + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + el.css( "overflow", "hidden" ).css( el.from ); + + // Adjust + if (origin) { // Calculate baseline shifts + baseline = $.effects.getBaseline( origin, original ); + el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y; + el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x; + el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y; + el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x; + } + el.css( el.from ); // set top & left + + // Animate + if ( scale === "content" || scale === "both" ) { // Scale the children + + // Add margins/font-size + vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps); + hProps = hProps.concat([ "marginLeft", "marginRight" ]); + props2 = props0.concat(vProps).concat(hProps); + + el.find( "*[width]" ).each( function(){ + var child = $( this ), + c_original = { + height: child.height(), + width: child.width(), + outerHeight: child.outerHeight(), + outerWidth: child.outerWidth() + }; + if (restore) { + $.effects.save(child, props2); + } + + child.from = { + height: c_original.height * factor.from.y, + width: c_original.width * factor.from.x, + outerHeight: c_original.outerHeight * factor.from.y, + outerWidth: c_original.outerWidth * factor.from.x + }; + child.to = { + height: c_original.height * factor.to.y, + width: c_original.width * factor.to.x, + outerHeight: c_original.height * factor.to.y, + outerWidth: c_original.width * factor.to.x + }; + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from ); + child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from ); + child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to ); + } + + // Animate children + child.css( child.from ); + child.animate( child.to, o.duration, o.easing, function() { + + // Restore children + if ( restore ) { + $.effects.restore( child, props2 ); + } + }); + }); + } + + // Animate + el.animate( el.to, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( el.to.opacity === 0 ) { + el.css( "opacity", el.from.opacity ); + } + if( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + if ( !restore ) { + + // we need to calculate our new positioning based on the scaling + if ( position === "static" ) { + el.css({ + position: "relative", + top: el.to.top, + left: el.to.left + }); + } else { + $.each([ "top", "left" ], function( idx, pos ) { + el.css( pos, function( _, str ) { + var val = parseInt( str, 10 ), + toRef = idx ? el.to.left : el.to.top; + + // if original was "auto", recalculate the new value from wrapper + if ( str === "auto" ) { + return toRef + "px"; + } + + return val + toRef + "px"; + }); + }); + } + } + + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.shake = function( o, done ) { + + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "effect" ), + direction = o.direction || "left", + distance = o.distance || 20, + times = o.times || 3, + anims = times * 2 + 1, + speed = Math.round(o.duration/anims), + ref = (direction === "up" || direction === "down") ? "top" : "left", + positiveMotion = (direction === "up" || direction === "left"), + animation = {}, + animation1 = {}, + animation2 = {}, + i, + + // we will need to re-assemble the queue to stack our animations in place + queue = el.queue(), + queuelen = queue.length; + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + + // Animation + animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; + animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; + animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; + + // Animate + el.animate( animation, speed, o.easing ); + + // Shakes + for ( i = 1; i < times; i++ ) { + el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing ); + } + el + .animate( animation1, speed, o.easing ) + .animate( animation, speed / 2, o.easing ) + .queue(function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + + // inject all the animations we just queued to be first in line (after "inprogress") + if ( queuelen > 1) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + el.dequeue(); + +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.slide = function( o, done ) { + + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "width", "height" ], + mode = $.effects.setMode( el, o.mode || "show" ), + show = mode === "show", + direction = o.direction || "left", + ref = (direction === "up" || direction === "down") ? "top" : "left", + positiveMotion = (direction === "up" || direction === "left"), + distance, + animation = {}; + + // Adjust + $.effects.save( el, props ); + el.show(); + distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ); + + $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + + if ( show ) { + el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance ); + } + + // Animation + animation[ ref ] = ( show ? + ( positiveMotion ? "+=" : "-=") : + ( positiveMotion ? "-=" : "+=")) + + distance; + + // Animate + el.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); +}; + +})(jQuery); +(function( $, undefined ) { + +$.effects.effect.transfer = function( o, done ) { + var elem = $( this ), + target = $( o.to ), + targetFixed = target.css( "position" ) === "fixed", + body = $("body"), + fixTop = targetFixed ? body.scrollTop() : 0, + fixLeft = targetFixed ? body.scrollLeft() : 0, + endPosition = target.offset(), + animation = { + top: endPosition.top - fixTop , + left: endPosition.left - fixLeft , + height: target.innerHeight(), + width: target.innerWidth() + }, + startPosition = elem.offset(), + transfer = $( "
    " ) + .appendTo( document.body ) + .addClass( o.className ) + .css({ + top: startPosition.top - fixTop , + left: startPosition.left - fixLeft , + height: elem.innerHeight(), + width: elem.innerWidth(), + position: targetFixed ? "fixed" : "absolute" + }) + .animate( animation, o.duration, o.easing, function() { + transfer.remove(); + done(); + }); +}; + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.menu.js b/extensions/jui/assets/jquery.ui.menu.js new file mode 100644 index 0000000..ace12e2 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.menu.js @@ -0,0 +1,621 @@ +/*! + * jQuery UI Menu 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/menu/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function( $, undefined ) { + +$.widget( "ui.menu", { + version: "1.10.3", + defaultElement: "
      ", + delay: 300, + options: { + icons: { + submenu: "ui-icon-carat-1-e" + }, + menus: "ul", + position: { + my: "left top", + at: "right top" + }, + role: "menu", + + // callbacks + blur: null, + focus: null, + select: null + }, + + _create: function() { + this.activeMenu = this.element; + // flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; + this.element + .uniqueId() + .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) + .attr({ + role: this.options.role, + tabIndex: 0 + }) + // need to catch all clicks on disabled menu + // not possible through _on + .bind( "click" + this.eventNamespace, $.proxy(function( event ) { + if ( this.options.disabled ) { + event.preventDefault(); + } + }, this )); + + if ( this.options.disabled ) { + this.element + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } + + this._on({ + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item > a": function( event ) { + event.preventDefault(); + }, + "click .ui-state-disabled > a": function( event ) { + event.preventDefault(); + }, + "click .ui-menu-item:has(a)": function( event ) { + var target = $( event.target ).closest( ".ui-menu-item" ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.mouseHandled = true; + + this.select( event ); + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) ) { + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } + } + }, + "mouseenter .ui-menu-item": function( event ) { + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); + }, + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 ); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } + }, + blur: function( event ) { + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }); + }, + keydown: "_keydown" + }); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( !$( event.target ).closest( ".ui-menu" ).length ) { + this.collapseAll( event ); + } + + // Reset the mouseHandled flag + this.mouseHandled = false; + } + }); + }, + + _destroy: function() { + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .show(); + + // Destroy menu items + this.element.find( ".ui-menu-item" ) + .removeClass( "ui-menu-item" ) + .removeAttr( "role" ) + .removeAttr( "aria-disabled" ) + .children( "a" ) + .removeUniqueId() + .removeClass( "ui-corner-all ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-carat" ) ) { + elem.remove(); + } + }); + + // Destroy menu dividers + this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); + }, + + _keydown: function( event ) { + /*jshint maxcomplexity:20*/ + var match, prev, character, skip, regex, + preventDefault = true; + + function escape( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + character = String.fromCharCode( event.keyCode ); + skip = false; + + clearTimeout( this.filterTimer ); + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { + return regex.test( $( this ).children( "a" ).text() ); + }); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { + return regex.test( $( this ).children( "a" ).text() ); + }); + } + + if ( match.length ) { + this.focus( event, match ); + if ( match.length > 1 ) { + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } else { + delete this.previousFilter; + } + } + + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.children( "a[aria-haspopup='true']" ).length ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + + refresh: function() { + var menus, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + // Initialize nested menus + submenus.filter( ":not(.ui-menu)" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .hide() + .attr({ + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" + }) + .each(function() { + var menu = $( this ), + item = menu.prev( "a" ), + submenuCarat = $( "" ) + .addClass( "ui-menu-icon ui-icon " + icon ) + .data( "ui-menu-submenu-carat", true ); + + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCarat ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + }); + + menus = submenus.add( this.element ); + + // Don't refresh list items that are already adapted + menus.children( ":not(.ui-menu-item):has(a)" ) + .addClass( "ui-menu-item" ) + .attr( "role", "presentation" ) + .children( "a" ) + .uniqueId() + .addClass( "ui-corner-all" ) + .attr({ + tabIndex: -1, + role: this._itemRole() + }); + + // Initialize unlinked menu-items containing spaces and/or dashes only as dividers + menus.children( ":not(.ui-menu-item)" ).each(function() { + var item = $( this ); + // hyphen, em dash, en dash + if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) { + item.addClass( "ui-widget-content ui-menu-divider" ); + } + }); + + // Add aria-disabled attribute to any disabled menu item + menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } + }, + + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + this.element.find( ".ui-menu-icon" ) + .removeClass( this.options.icons.submenu ) + .addClass( value.submenu ); + } + this._super( key, value ); + }, + + focus: function( event, item ) { + var nested, focused; + this.blur( event, event && event.type === "focus" ); + + this._scrollIntoView( item ); + + this.active = item.first(); + focused = this.active.children( "a" ).addClass( "ui-state-focus" ); + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } + + // Highlight active parent menu item, if any + this.active + .parent() + .closest( ".ui-menu-item" ) + .children( "a:first" ) + .addClass( "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay(function() { + this._close(); + }, this.delay ); + } + + nested = item.children( ".ui-menu" ); + if ( nested.length && ( /^mouse/.test( event.type ) ) ) { + this._startOpening(nested); + } + this.activeMenu = item.parent(); + + this._trigger( "focus", event, { item: item } ); + }, + + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.height(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } + + if ( !this.active ) { + return; + } + + this.active.children( "a" ).removeClass( "ui-state-focus" ); + this.active = null; + + this._trigger( "blur", event, { item: this.active } ); + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the carat icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { + return; + } + + this.timer = this._delay(function() { + this._close(); + this._open( submenu ); + }, this.delay ); + }, + + _open: function( submenu ) { + var position = $.extend({ + of: this.active + }, this.options.position ); + + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); + }, + + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay(function() { + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); + + this.blur( event ); + this.activeMenu = currentMenu; + }, this.delay ); + }, + + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu + .find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ) + .end() + .find( "a.ui-state-active" ) + .removeClass( "ui-state-active" ); + }, + + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); + } + }, + + expand: function( event ) { + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .children( ".ui-menu-item" ) + .first(); + + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); + + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay(function() { + this.focus( event, newItem ); + }); + } + }, + + next: function( event ) { + this._move( "next", "first", event ); + }, + + previous: function( event ) { + this._move( "prev", "last", event ); + }, + + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; + }, + + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; + }, + + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .eq( -1 ); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .eq( 0 ); + } + } + if ( !next || !next.length || !this.active ) { + next = this.activeMenu.children( ".ui-menu-item" )[ filter ](); + } + + this.focus( event, next ); + }, + + nextPage: function( event ) { + var item, base, height; + + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.nextAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base - height < 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.children( ".ui-menu-item" ) + [ !this.active ? "first" : "last" ]() ); + } + }, + + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.prevAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base + height > 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); + } + this._trigger( "select", event, ui ); + } +}); + +}( jQuery )); diff --git a/extensions/jui/assets/jquery.ui.mouse.js b/extensions/jui/assets/jquery.ui.mouse.js new file mode 100644 index 0000000..62022ce --- /dev/null +++ b/extensions/jui/assets/jquery.ui.mouse.js @@ -0,0 +1,169 @@ +/*! + * jQuery UI Mouse 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/mouse/ + * + * Depends: + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var mouseHandled = false; +$( document ).mouseup( function() { + mouseHandled = false; +}); + +$.widget("ui.mouse", { + version: "1.10.3", + options: { + cancel: "input,textarea,button,select,option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .bind("mousedown."+this.widgetName, function(event) { + return that._mouseDown(event); + }) + .bind("click."+this.widgetName, function(event) { + if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { + $.removeData(event.target, that.widgetName + ".preventClickEvent"); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind("."+this.widgetName); + if ( this._mouseMoveDelegate ) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + } + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if( mouseHandled ) { return; } + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = (event.which === 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + that.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { + $.removeData(event.target, this.widgetName + ".preventClickEvent"); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return that._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return that._mouseUp(event); + }; + $(document) + .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .bind("mouseup."+this.widgetName, this._mouseUpDelegate); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target === this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + ".preventClickEvent", true); + } + + this._mouseStop(event); + } + + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(/* event */) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(/* event */) {}, + _mouseDrag: function(/* event */) {}, + _mouseStop: function(/* event */) {}, + _mouseCapture: function(/* event */) { return true; } +}); + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.position.js b/extensions/jui/assets/jquery.ui.position.js new file mode 100644 index 0000000..3fcdf25 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.position.js @@ -0,0 +1,497 @@ +/*! + * jQuery UI Position 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ +(function( $, undefined ) { + +$.ui = $.ui || {}; + +var cachedScrollbarWidth, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[0]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "
      " ), + innerDiv = div.children()[0]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[0].clientWidth; + } + + div.remove(); + + return (cachedScrollbarWidth = w1 - w2); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ), + overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[0] ); + return { + element: withinElement, + isWindow: isWindow, + offset: withinElement.offset() || { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: isWindow ? withinElement.width() : withinElement.outerWidth(), + height: isWindow ? withinElement.height() : withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[0].preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + // clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each(function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + // if the browser doesn't support fractions, then round for consistent results + if ( !$.support.offsetFractions ) { + position.left = round( position.left ); + position.top = round( position.top ); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem : elem + }); + } + }); + + if ( options.using ) { + // adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // element is wider than within + if ( data.collisionWidth > outerWidth ) { + // element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + // too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + // adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // element is taller than within + if ( data.collisionHeight > outerHeight ) { + // element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + // too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + // adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } + else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { + position.top += myOffset + atOffset + offset; + } + } + else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; + if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +// fraction support test +(function () { + var testElement, testElementParent, testElementStyle, offsetLeft, i, + body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + $.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $( div ).offset().left; + $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); +})(); + +}( jQuery ) ); diff --git a/extensions/jui/assets/jquery.ui.progressbar.js b/extensions/jui/assets/jquery.ui.progressbar.js new file mode 100644 index 0000000..70d716e --- /dev/null +++ b/extensions/jui/assets/jquery.ui.progressbar.js @@ -0,0 +1,145 @@ +/*! + * jQuery UI Progressbar 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/progressbar/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget( "ui.progressbar", { + version: "1.10.3", + options: { + max: 100, + value: 0, + + change: null, + complete: null + }, + + min: 0, + + _create: function() { + // Constrain initial value + this.oldValue = this.options.value = this._constrainedValue(); + + this.element + .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .attr({ + // Only set static values, aria-valuenow and aria-valuemax are + // set inside _refreshValue() + role: "progressbar", + "aria-valuemin": this.min + }); + + this.valueDiv = $( "
      " ) + .appendTo( this.element ); + + this._refreshValue(); + }, + + _destroy: function() { + this.element + .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + + this.valueDiv.remove(); + }, + + value: function( newValue ) { + if ( newValue === undefined ) { + return this.options.value; + } + + this.options.value = this._constrainedValue( newValue ); + this._refreshValue(); + }, + + _constrainedValue: function( newValue ) { + if ( newValue === undefined ) { + newValue = this.options.value; + } + + this.indeterminate = newValue === false; + + // sanitize value + if ( typeof newValue !== "number" ) { + newValue = 0; + } + + return this.indeterminate ? false : + Math.min( this.options.max, Math.max( this.min, newValue ) ); + }, + + _setOptions: function( options ) { + // Ensure "value" option is set after other values (like max) + var value = options.value; + delete options.value; + + this._super( options ); + + this.options.value = this._constrainedValue( value ); + this._refreshValue(); + }, + + _setOption: function( key, value ) { + if ( key === "max" ) { + // Don't allow a max less than min + value = Math.max( this.min, value ); + } + + this._super( key, value ); + }, + + _percentage: function() { + return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); + }, + + _refreshValue: function() { + var value = this.options.value, + percentage = this._percentage(); + + this.valueDiv + .toggle( this.indeterminate || value > this.min ) + .toggleClass( "ui-corner-right", value === this.options.max ) + .width( percentage.toFixed(0) + "%" ); + + this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate ); + + if ( this.indeterminate ) { + this.element.removeAttr( "aria-valuenow" ); + if ( !this.overlayDiv ) { + this.overlayDiv = $( "
      " ).appendTo( this.valueDiv ); + } + } else { + this.element.attr({ + "aria-valuemax": this.options.max, + "aria-valuenow": value + }); + if ( this.overlayDiv ) { + this.overlayDiv.remove(); + this.overlayDiv = null; + } + } + + if ( this.oldValue !== value ) { + this.oldValue = value; + this._trigger( "change" ); + } + if ( value === this.options.max ) { + this._trigger( "complete" ); + } + } +}); + +})( jQuery ); diff --git a/extensions/jui/assets/jquery.ui.resizable.js b/extensions/jui/assets/jquery.ui.resizable.js new file mode 100644 index 0000000..f007732 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.resizable.js @@ -0,0 +1,968 @@ +/*! + * jQuery UI Resizable 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/resizable/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +function num(v) { + return parseInt(v, 10) || 0; +} + +function isNumber(value) { + return !isNaN(parseInt(value, 10)); +} + +$.widget("ui.resizable", $.ui.mouse, { + version: "1.10.3", + widgetEventPrefix: "resize", + options: { + alsoResize: false, + animate: false, + animateDuration: "slow", + animateEasing: "swing", + aspectRatio: false, + autoHide: false, + containment: false, + ghost: false, + grid: false, + handles: "e,s,se", + helper: false, + maxHeight: null, + maxWidth: null, + minHeight: 10, + minWidth: 10, + // See #7960 + zIndex: 90, + + // callbacks + resize: null, + start: null, + stop: null + }, + _create: function() { + + var n, i, handle, axis, hname, + that = this, + o = this.options; + this.element.addClass("ui-resizable"); + + $.extend(this, { + _aspectRatio: !!(o.aspectRatio), + aspectRatio: o.aspectRatio, + originalElement: this.element, + _proportionallyResizeElements: [], + _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null + }); + + //Wrap the element if it cannot hold child nodes + if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { + + //Create a wrapper element and set the wrapper to the new current internal element + this.element.wrap( + $("
      ").css({ + position: this.element.css("position"), + width: this.element.outerWidth(), + height: this.element.outerHeight(), + top: this.element.css("top"), + left: this.element.css("left") + }) + ); + + //Overwrite the original this.element + this.element = this.element.parent().data( + "ui-resizable", this.element.data("ui-resizable") + ); + + this.elementIsWrapper = true; + + //Move margins to the wrapper + this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); + this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); + + //Prevent Safari textarea resize + this.originalResizeStyle = this.originalElement.css("resize"); + this.originalElement.css("resize", "none"); + + //Push the actual element to our proportionallyResize internal array + this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" })); + + // avoid IE jump (hard set the margin) + this.originalElement.css({ margin: this.originalElement.css("margin") }); + + // fix handlers offset + this._proportionallyResize(); + + } + + this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" }); + if(this.handles.constructor === String) { + + if ( this.handles === "all") { + this.handles = "n,e,s,w,se,sw,ne,nw"; + } + + n = this.handles.split(","); + this.handles = {}; + + for(i = 0; i < n.length; i++) { + + handle = $.trim(n[i]); + hname = "ui-resizable-"+handle; + axis = $("
      "); + + // Apply zIndex to all handles - see #7960 + axis.css({ zIndex: o.zIndex }); + + //TODO : What's going on here? + if ("se" === handle) { + axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se"); + } + + //Insert into internal handles object and append to element + this.handles[handle] = ".ui-resizable-"+handle; + this.element.append(axis); + } + + } + + this._renderAxis = function(target) { + + var i, axis, padPos, padWrapper; + + target = target || this.element; + + for(i in this.handles) { + + if(this.handles[i].constructor === String) { + this.handles[i] = $(this.handles[i], this.element).show(); + } + + //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) + if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { + + axis = $(this.handles[i], this.element); + + //Checking the correct pad and border + padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); + + //The padding type i have to apply... + padPos = [ "padding", + /ne|nw|n/.test(i) ? "Top" : + /se|sw|s/.test(i) ? "Bottom" : + /^e$/.test(i) ? "Right" : "Left" ].join(""); + + target.css(padPos, padWrapper); + + this._proportionallyResize(); + + } + + //TODO: What's that good for? There's not anything to be executed left + if(!$(this.handles[i]).length) { + continue; + } + } + }; + + //TODO: make renderAxis a prototype function + this._renderAxis(this.element); + + this._handles = $(".ui-resizable-handle", this.element) + .disableSelection(); + + //Matching axis name + this._handles.mouseover(function() { + if (!that.resizing) { + if (this.className) { + axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); + } + //Axis, default = se + that.axis = axis && axis[1] ? axis[1] : "se"; + } + }); + + //If we want to auto hide the elements + if (o.autoHide) { + this._handles.hide(); + $(this.element) + .addClass("ui-resizable-autohide") + .mouseenter(function() { + if (o.disabled) { + return; + } + $(this).removeClass("ui-resizable-autohide"); + that._handles.show(); + }) + .mouseleave(function(){ + if (o.disabled) { + return; + } + if (!that.resizing) { + $(this).addClass("ui-resizable-autohide"); + that._handles.hide(); + } + }); + } + + //Initialize the mouse interaction + this._mouseInit(); + + }, + + _destroy: function() { + + this._mouseDestroy(); + + var wrapper, + _destroy = function(exp) { + $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") + .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove(); + }; + + //TODO: Unwrap at same DOM position + if (this.elementIsWrapper) { + _destroy(this.element); + wrapper = this.element; + this.originalElement.css({ + position: wrapper.css("position"), + width: wrapper.outerWidth(), + height: wrapper.outerHeight(), + top: wrapper.css("top"), + left: wrapper.css("left") + }).insertAfter( wrapper ); + wrapper.remove(); + } + + this.originalElement.css("resize", this.originalResizeStyle); + _destroy(this.originalElement); + + return this; + }, + + _mouseCapture: function(event) { + var i, handle, + capture = false; + + for (i in this.handles) { + handle = $(this.handles[i])[0]; + if (handle === event.target || $.contains(handle, event.target)) { + capture = true; + } + } + + return !this.options.disabled && capture; + }, + + _mouseStart: function(event) { + + var curleft, curtop, cursor, + o = this.options, + iniPos = this.element.position(), + el = this.element; + + this.resizing = true; + + // bugfix for http://dev.jquery.com/ticket/1749 + if ( (/absolute/).test( el.css("position") ) ) { + el.css({ position: "absolute", top: el.css("top"), left: el.css("left") }); + } else if (el.is(".ui-draggable")) { + el.css({ position: "absolute", top: iniPos.top, left: iniPos.left }); + } + + this._renderProxy(); + + curleft = num(this.helper.css("left")); + curtop = num(this.helper.css("top")); + + if (o.containment) { + curleft += $(o.containment).scrollLeft() || 0; + curtop += $(o.containment).scrollTop() || 0; + } + + //Store needed variables + this.offset = this.helper.offset(); + this.position = { left: curleft, top: curtop }; + this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalPosition = { left: curleft, top: curtop }; + this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; + this.originalMousePosition = { left: event.pageX, top: event.pageY }; + + //Aspect Ratio + this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); + + cursor = $(".ui-resizable-" + this.axis).css("cursor"); + $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); + + el.addClass("ui-resizable-resizing"); + this._propagate("start", event); + return true; + }, + + _mouseDrag: function(event) { + + //Increase performance, avoid regex + var data, + el = this.helper, props = {}, + smp = this.originalMousePosition, + a = this.axis, + prevTop = this.position.top, + prevLeft = this.position.left, + prevWidth = this.size.width, + prevHeight = this.size.height, + dx = (event.pageX-smp.left)||0, + dy = (event.pageY-smp.top)||0, + trigger = this._change[a]; + + if (!trigger) { + return false; + } + + // Calculate the attrs that will be change + data = trigger.apply(this, [event, dx, dy]); + + // Put this in the mouseDrag handler since the user can start pressing shift while resizing + this._updateVirtualBoundaries(event.shiftKey); + if (this._aspectRatio || event.shiftKey) { + data = this._updateRatio(data, event); + } + + data = this._respectSize(data, event); + + this._updateCache(data); + + // plugins callbacks need to be called first + this._propagate("resize", event); + + if (this.position.top !== prevTop) { + props.top = this.position.top + "px"; + } + if (this.position.left !== prevLeft) { + props.left = this.position.left + "px"; + } + if (this.size.width !== prevWidth) { + props.width = this.size.width + "px"; + } + if (this.size.height !== prevHeight) { + props.height = this.size.height + "px"; + } + el.css(props); + + if (!this._helper && this._proportionallyResizeElements.length) { + this._proportionallyResize(); + } + + // Call the user callback if the element was resized + if ( ! $.isEmptyObject(props) ) { + this._trigger("resize", event, this.ui()); + } + + return false; + }, + + _mouseStop: function(event) { + + this.resizing = false; + var pr, ista, soffseth, soffsetw, s, left, top, + o = this.options, that = this; + + if(this._helper) { + + pr = this._proportionallyResizeElements; + ista = pr.length && (/textarea/i).test(pr[0].nodeName); + soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height; + soffsetw = ista ? 0 : that.sizeDiff.width; + + s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) }; + left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null; + top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; + + if (!o.animate) { + this.element.css($.extend(s, { top: top, left: left })); + } + + that.helper.height(that.size.height); + that.helper.width(that.size.width); + + if (this._helper && !o.animate) { + this._proportionallyResize(); + } + } + + $("body").css("cursor", "auto"); + + this.element.removeClass("ui-resizable-resizing"); + + this._propagate("stop", event); + + if (this._helper) { + this.helper.remove(); + } + + return false; + + }, + + _updateVirtualBoundaries: function(forceAspectRatio) { + var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, + o = this.options; + + b = { + minWidth: isNumber(o.minWidth) ? o.minWidth : 0, + maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity, + minHeight: isNumber(o.minHeight) ? o.minHeight : 0, + maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity + }; + + if(this._aspectRatio || forceAspectRatio) { + // We want to create an enclosing box whose aspect ration is the requested one + // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension + pMinWidth = b.minHeight * this.aspectRatio; + pMinHeight = b.minWidth / this.aspectRatio; + pMaxWidth = b.maxHeight * this.aspectRatio; + pMaxHeight = b.maxWidth / this.aspectRatio; + + if(pMinWidth > b.minWidth) { + b.minWidth = pMinWidth; + } + if(pMinHeight > b.minHeight) { + b.minHeight = pMinHeight; + } + if(pMaxWidth < b.maxWidth) { + b.maxWidth = pMaxWidth; + } + if(pMaxHeight < b.maxHeight) { + b.maxHeight = pMaxHeight; + } + } + this._vBoundaries = b; + }, + + _updateCache: function(data) { + this.offset = this.helper.offset(); + if (isNumber(data.left)) { + this.position.left = data.left; + } + if (isNumber(data.top)) { + this.position.top = data.top; + } + if (isNumber(data.height)) { + this.size.height = data.height; + } + if (isNumber(data.width)) { + this.size.width = data.width; + } + }, + + _updateRatio: function( data ) { + + var cpos = this.position, + csize = this.size, + a = this.axis; + + if (isNumber(data.height)) { + data.width = (data.height * this.aspectRatio); + } else if (isNumber(data.width)) { + data.height = (data.width / this.aspectRatio); + } + + if (a === "sw") { + data.left = cpos.left + (csize.width - data.width); + data.top = null; + } + if (a === "nw") { + data.top = cpos.top + (csize.height - data.height); + data.left = cpos.left + (csize.width - data.width); + } + + return data; + }, + + _respectSize: function( data ) { + + var o = this._vBoundaries, + a = this.axis, + ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), + isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height), + dw = this.originalPosition.left + this.originalSize.width, + dh = this.position.top + this.size.height, + cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); + if (isminw) { + data.width = o.minWidth; + } + if (isminh) { + data.height = o.minHeight; + } + if (ismaxw) { + data.width = o.maxWidth; + } + if (ismaxh) { + data.height = o.maxHeight; + } + + if (isminw && cw) { + data.left = dw - o.minWidth; + } + if (ismaxw && cw) { + data.left = dw - o.maxWidth; + } + if (isminh && ch) { + data.top = dh - o.minHeight; + } + if (ismaxh && ch) { + data.top = dh - o.maxHeight; + } + + // fixing jump error on top/left - bug #2330 + if (!data.width && !data.height && !data.left && data.top) { + data.top = null; + } else if (!data.width && !data.height && !data.top && data.left) { + data.left = null; + } + + return data; + }, + + _proportionallyResize: function() { + + if (!this._proportionallyResizeElements.length) { + return; + } + + var i, j, borders, paddings, prel, + element = this.helper || this.element; + + for ( i=0; i < this._proportionallyResizeElements.length; i++) { + + prel = this._proportionallyResizeElements[i]; + + if (!this.borderDif) { + this.borderDif = []; + borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")]; + paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")]; + + for ( j = 0; j < borders.length; j++ ) { + this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 ); + } + } + + prel.css({ + height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, + width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 + }); + + } + + }, + + _renderProxy: function() { + + var el = this.element, o = this.options; + this.elementOffset = el.offset(); + + if(this._helper) { + + this.helper = this.helper || $("
      "); + + this.helper.addClass(this._helper).css({ + width: this.element.outerWidth() - 1, + height: this.element.outerHeight() - 1, + position: "absolute", + left: this.elementOffset.left +"px", + top: this.elementOffset.top +"px", + zIndex: ++o.zIndex //TODO: Don't modify option + }); + + this.helper + .appendTo("body") + .disableSelection(); + + } else { + this.helper = this.element; + } + + }, + + _change: { + e: function(event, dx) { + return { width: this.originalSize.width + dx }; + }, + w: function(event, dx) { + var cs = this.originalSize, sp = this.originalPosition; + return { left: sp.left + dx, width: cs.width - dx }; + }, + n: function(event, dx, dy) { + var cs = this.originalSize, sp = this.originalPosition; + return { top: sp.top + dy, height: cs.height - dy }; + }, + s: function(event, dx, dy) { + return { height: this.originalSize.height + dy }; + }, + se: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + sw: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + }, + ne: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + nw: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + } + }, + + _propagate: function(n, event) { + $.ui.plugin.call(this, n, [event, this.ui()]); + (n !== "resize" && this._trigger(n, event, this.ui())); + }, + + plugins: {}, + + ui: function() { + return { + originalElement: this.originalElement, + element: this.element, + helper: this.helper, + position: this.position, + size: this.size, + originalSize: this.originalSize, + originalPosition: this.originalPosition + }; + } + +}); + +/* + * Resizable Extensions + */ + +$.ui.plugin.add("resizable", "animate", { + + stop: function( event ) { + var that = $(this).data("ui-resizable"), + o = that.options, + pr = that._proportionallyResizeElements, + ista = pr.length && (/textarea/i).test(pr[0].nodeName), + soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height, + soffsetw = ista ? 0 : that.sizeDiff.width, + style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) }, + left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null, + top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; + + that.element.animate( + $.extend(style, top && left ? { top: top, left: left } : {}), { + duration: o.animateDuration, + easing: o.animateEasing, + step: function() { + + var data = { + width: parseInt(that.element.css("width"), 10), + height: parseInt(that.element.css("height"), 10), + top: parseInt(that.element.css("top"), 10), + left: parseInt(that.element.css("left"), 10) + }; + + if (pr && pr.length) { + $(pr[0]).css({ width: data.width, height: data.height }); + } + + // propagating resize, and updating values for each animation step + that._updateCache(data); + that._propagate("resize", event); + + } + } + ); + } + +}); + +$.ui.plugin.add("resizable", "containment", { + + start: function() { + var element, p, co, ch, cw, width, height, + that = $(this).data("ui-resizable"), + o = that.options, + el = that.element, + oc = o.containment, + ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; + + if (!ce) { + return; + } + + that.containerElement = $(ce); + + if (/document/.test(oc) || oc === document) { + that.containerOffset = { left: 0, top: 0 }; + that.containerPosition = { left: 0, top: 0 }; + + that.parentData = { + element: $(document), left: 0, top: 0, + width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight + }; + } + + // i'm a node, so compute top, left, right, bottom + else { + element = $(ce); + p = []; + $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); + + that.containerOffset = element.offset(); + that.containerPosition = element.position(); + that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; + + co = that.containerOffset; + ch = that.containerSize.height; + cw = that.containerSize.width; + width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ); + height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); + + that.parentData = { + element: ce, left: co.left, top: co.top, width: width, height: height + }; + } + }, + + resize: function( event ) { + var woset, hoset, isParent, isOffsetRelative, + that = $(this).data("ui-resizable"), + o = that.options, + co = that.containerOffset, cp = that.position, + pRatio = that._aspectRatio || event.shiftKey, + cop = { top:0, left:0 }, ce = that.containerElement; + + if (ce[0] !== document && (/static/).test(ce.css("position"))) { + cop = co; + } + + if (cp.left < (that._helper ? co.left : 0)) { + that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left)); + if (pRatio) { + that.size.height = that.size.width / that.aspectRatio; + } + that.position.left = o.helper ? co.left : 0; + } + + if (cp.top < (that._helper ? co.top : 0)) { + that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top); + if (pRatio) { + that.size.width = that.size.height * that.aspectRatio; + } + that.position.top = that._helper ? co.top : 0; + } + + that.offset.left = that.parentData.left+that.position.left; + that.offset.top = that.parentData.top+that.position.top; + + woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ); + hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height ); + + isParent = that.containerElement.get(0) === that.element.parent().get(0); + isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position")); + + if(isParent && isOffsetRelative) { + woset -= that.parentData.left; + } + + if (woset + that.size.width >= that.parentData.width) { + that.size.width = that.parentData.width - woset; + if (pRatio) { + that.size.height = that.size.width / that.aspectRatio; + } + } + + if (hoset + that.size.height >= that.parentData.height) { + that.size.height = that.parentData.height - hoset; + if (pRatio) { + that.size.width = that.size.height * that.aspectRatio; + } + } + }, + + stop: function(){ + var that = $(this).data("ui-resizable"), + o = that.options, + co = that.containerOffset, + cop = that.containerPosition, + ce = that.containerElement, + helper = $(that.helper), + ho = helper.offset(), + w = helper.outerWidth() - that.sizeDiff.width, + h = helper.outerHeight() - that.sizeDiff.height; + + if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) { + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + } + + if (that._helper && !o.animate && (/static/).test(ce.css("position"))) { + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + } + + } +}); + +$.ui.plugin.add("resizable", "alsoResize", { + + start: function () { + var that = $(this).data("ui-resizable"), + o = that.options, + _store = function (exp) { + $(exp).each(function() { + var el = $(this); + el.data("ui-resizable-alsoresize", { + width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), + left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10) + }); + }); + }; + + if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) { + if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } + else { $.each(o.alsoResize, function (exp) { _store(exp); }); } + }else{ + _store(o.alsoResize); + } + }, + + resize: function (event, ui) { + var that = $(this).data("ui-resizable"), + o = that.options, + os = that.originalSize, + op = that.originalPosition, + delta = { + height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0, + top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0 + }, + + _alsoResize = function (exp, c) { + $(exp).each(function() { + var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {}, + css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"]; + + $.each(css, function (i, prop) { + var sum = (start[prop]||0) + (delta[prop]||0); + if (sum && sum >= 0) { + style[prop] = sum || null; + } + }); + + el.css(style); + }); + }; + + if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) { + $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); + }else{ + _alsoResize(o.alsoResize); + } + }, + + stop: function () { + $(this).removeData("resizable-alsoresize"); + } +}); + +$.ui.plugin.add("resizable", "ghost", { + + start: function() { + + var that = $(this).data("ui-resizable"), o = that.options, cs = that.size; + + that.ghost = that.originalElement.clone(); + that.ghost + .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) + .addClass("ui-resizable-ghost") + .addClass(typeof o.ghost === "string" ? o.ghost : ""); + + that.ghost.appendTo(that.helper); + + }, + + resize: function(){ + var that = $(this).data("ui-resizable"); + if (that.ghost) { + that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width }); + } + }, + + stop: function() { + var that = $(this).data("ui-resizable"); + if (that.ghost && that.helper) { + that.helper.get(0).removeChild(that.ghost.get(0)); + } + } + +}); + +$.ui.plugin.add("resizable", "grid", { + + resize: function() { + var that = $(this).data("ui-resizable"), + o = that.options, + cs = that.size, + os = that.originalSize, + op = that.originalPosition, + a = that.axis, + grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid, + gridX = (grid[0]||1), + gridY = (grid[1]||1), + ox = Math.round((cs.width - os.width) / gridX) * gridX, + oy = Math.round((cs.height - os.height) / gridY) * gridY, + newWidth = os.width + ox, + newHeight = os.height + oy, + isMaxWidth = o.maxWidth && (o.maxWidth < newWidth), + isMaxHeight = o.maxHeight && (o.maxHeight < newHeight), + isMinWidth = o.minWidth && (o.minWidth > newWidth), + isMinHeight = o.minHeight && (o.minHeight > newHeight); + + o.grid = grid; + + if (isMinWidth) { + newWidth = newWidth + gridX; + } + if (isMinHeight) { + newHeight = newHeight + gridY; + } + if (isMaxWidth) { + newWidth = newWidth - gridX; + } + if (isMaxHeight) { + newHeight = newHeight - gridY; + } + + if (/^(se|s|e)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + } else if (/^(ne)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.top = op.top - oy; + } else if (/^(sw)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.left = op.left - ox; + } else { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.top = op.top - oy; + that.position.left = op.left - ox; + } + } + +}); + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.selectable.js b/extensions/jui/assets/jquery.ui.selectable.js new file mode 100644 index 0000000..e99ecb6 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.selectable.js @@ -0,0 +1,277 @@ +/*! + * jQuery UI Selectable 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/selectable/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +$.widget("ui.selectable", $.ui.mouse, { + version: "1.10.3", + options: { + appendTo: "body", + autoRefresh: true, + distance: 0, + filter: "*", + tolerance: "touch", + + // callbacks + selected: null, + selecting: null, + start: null, + stop: null, + unselected: null, + unselecting: null + }, + _create: function() { + var selectees, + that = this; + + this.element.addClass("ui-selectable"); + + this.dragged = false; + + // cache selectee children based on filter + this.refresh = function() { + selectees = $(that.options.filter, that.element[0]); + selectees.addClass("ui-selectee"); + selectees.each(function() { + var $this = $(this), + pos = $this.offset(); + $.data(this, "selectable-item", { + element: this, + $element: $this, + left: pos.left, + top: pos.top, + right: pos.left + $this.outerWidth(), + bottom: pos.top + $this.outerHeight(), + startselected: false, + selected: $this.hasClass("ui-selected"), + selecting: $this.hasClass("ui-selecting"), + unselecting: $this.hasClass("ui-unselecting") + }); + }); + }; + this.refresh(); + + this.selectees = selectees.addClass("ui-selectee"); + + this._mouseInit(); + + this.helper = $("
      "); + }, + + _destroy: function() { + this.selectees + .removeClass("ui-selectee") + .removeData("selectable-item"); + this.element + .removeClass("ui-selectable ui-selectable-disabled"); + this._mouseDestroy(); + }, + + _mouseStart: function(event) { + var that = this, + options = this.options; + + this.opos = [event.pageX, event.pageY]; + + if (this.options.disabled) { + return; + } + + this.selectees = $(options.filter, this.element[0]); + + this._trigger("start", event); + + $(options.appendTo).append(this.helper); + // position helper (lasso) + this.helper.css({ + "left": event.pageX, + "top": event.pageY, + "width": 0, + "height": 0 + }); + + if (options.autoRefresh) { + this.refresh(); + } + + this.selectees.filter(".ui-selected").each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.startselected = true; + if (!event.metaKey && !event.ctrlKey) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + }); + + $(event.target).parents().addBack().each(function() { + var doSelect, + selectee = $.data(this, "selectable-item"); + if (selectee) { + doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected"); + selectee.$element + .removeClass(doSelect ? "ui-unselecting" : "ui-selected") + .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); + selectee.unselecting = !doSelect; + selectee.selecting = doSelect; + selectee.selected = doSelect; + // selectable (UN)SELECTING callback + if (doSelect) { + that._trigger("selecting", event, { + selecting: selectee.element + }); + } else { + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + return false; + } + }); + + }, + + _mouseDrag: function(event) { + + this.dragged = true; + + if (this.options.disabled) { + return; + } + + var tmp, + that = this, + options = this.options, + x1 = this.opos[0], + y1 = this.opos[1], + x2 = event.pageX, + y2 = event.pageY; + + if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; } + if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; } + this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); + + this.selectees.each(function() { + var selectee = $.data(this, "selectable-item"), + hit = false; + + //prevent helper from being selected if appendTo: selectable + if (!selectee || selectee.element === that.element[0]) { + return; + } + + if (options.tolerance === "touch") { + hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); + } else if (options.tolerance === "fit") { + hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); + } + + if (hit) { + // SELECT + if (selectee.selected) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + } + if (selectee.unselecting) { + selectee.$element.removeClass("ui-unselecting"); + selectee.unselecting = false; + } + if (!selectee.selecting) { + selectee.$element.addClass("ui-selecting"); + selectee.selecting = true; + // selectable SELECTING callback + that._trigger("selecting", event, { + selecting: selectee.element + }); + } + } else { + // UNSELECT + if (selectee.selecting) { + if ((event.metaKey || event.ctrlKey) && selectee.startselected) { + selectee.$element.removeClass("ui-selecting"); + selectee.selecting = false; + selectee.$element.addClass("ui-selected"); + selectee.selected = true; + } else { + selectee.$element.removeClass("ui-selecting"); + selectee.selecting = false; + if (selectee.startselected) { + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + } + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + if (selectee.selected) { + if (!event.metaKey && !event.ctrlKey && !selectee.startselected) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + } + }); + + return false; + }, + + _mouseStop: function(event) { + var that = this; + + this.dragged = false; + + $(".ui-unselecting", this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass("ui-unselecting"); + selectee.unselecting = false; + selectee.startselected = false; + that._trigger("unselected", event, { + unselected: selectee.element + }); + }); + $(".ui-selecting", this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass("ui-selecting").addClass("ui-selected"); + selectee.selecting = false; + selectee.selected = true; + selectee.startselected = true; + that._trigger("selected", event, { + selected: selectee.element + }); + }); + this._trigger("stop", event); + + this.helper.remove(); + + return false; + } + +}); + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.slider.js b/extensions/jui/assets/jquery.ui.slider.js new file mode 100644 index 0000000..b2c4aa3 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.slider.js @@ -0,0 +1,672 @@ +/*! + * jQuery UI Slider 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/slider/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +// number of pages in a slider +// (how many times can you page up/down to go through the whole range) +var numPages = 5; + +$.widget( "ui.slider", $.ui.mouse, { + version: "1.10.3", + widgetEventPrefix: "slide", + + options: { + animate: false, + distance: 0, + max: 100, + min: 0, + orientation: "horizontal", + range: false, + step: 1, + value: 0, + values: null, + + // callbacks + change: null, + slide: null, + start: null, + stop: null + }, + + _create: function() { + this._keySliding = false; + this._mouseSliding = false; + this._animateOff = true; + this._handleIndex = null; + this._detectOrientation(); + this._mouseInit(); + + this.element + .addClass( "ui-slider" + + " ui-slider-" + this.orientation + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all"); + + this._refresh(); + this._setOption( "disabled", this.options.disabled ); + + this._animateOff = false; + }, + + _refresh: function() { + this._createRange(); + this._createHandles(); + this._setupEvents(); + this._refreshValue(); + }, + + _createHandles: function() { + var i, handleCount, + options = this.options, + existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ), + handle = "", + handles = []; + + handleCount = ( options.values && options.values.length ) || 1; + + if ( existingHandles.length > handleCount ) { + existingHandles.slice( handleCount ).remove(); + existingHandles = existingHandles.slice( 0, handleCount ); + } + + for ( i = existingHandles.length; i < handleCount; i++ ) { + handles.push( handle ); + } + + this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); + + this.handle = this.handles.eq( 0 ); + + this.handles.each(function( i ) { + $( this ).data( "ui-slider-handle-index", i ); + }); + }, + + _createRange: function() { + var options = this.options, + classes = ""; + + if ( options.range ) { + if ( options.range === true ) { + if ( !options.values ) { + options.values = [ this._valueMin(), this._valueMin() ]; + } else if ( options.values.length && options.values.length !== 2 ) { + options.values = [ options.values[0], options.values[0] ]; + } else if ( $.isArray( options.values ) ) { + options.values = options.values.slice(0); + } + } + + if ( !this.range || !this.range.length ) { + this.range = $( "
      " ) + .appendTo( this.element ); + + classes = "ui-slider-range" + + // note: this isn't the most fittingly semantic framework class for this element, + // but worked best visually with a variety of themes + " ui-widget-header ui-corner-all"; + } else { + this.range.removeClass( "ui-slider-range-min ui-slider-range-max" ) + // Handle range switching from true to min/max + .css({ + "left": "", + "bottom": "" + }); + } + + this.range.addClass( classes + + ( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) ); + } else { + this.range = $([]); + } + }, + + _setupEvents: function() { + var elements = this.handles.add( this.range ).filter( "a" ); + this._off( elements ); + this._on( elements, this._handleEvents ); + this._hoverable( elements ); + this._focusable( elements ); + }, + + _destroy: function() { + this.handles.remove(); + this.range.remove(); + + this.element + .removeClass( "ui-slider" + + " ui-slider-horizontal" + + " ui-slider-vertical" + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all" ); + + this._mouseDestroy(); + }, + + _mouseCapture: function( event ) { + var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, + that = this, + o = this.options; + + if ( o.disabled ) { + return false; + } + + this.elementSize = { + width: this.element.outerWidth(), + height: this.element.outerHeight() + }; + this.elementOffset = this.element.offset(); + + position = { x: event.pageX, y: event.pageY }; + normValue = this._normValueFromMouse( position ); + distance = this._valueMax() - this._valueMin() + 1; + this.handles.each(function( i ) { + var thisDistance = Math.abs( normValue - that.values(i) ); + if (( distance > thisDistance ) || + ( distance === thisDistance && + (i === that._lastChangedValue || that.values(i) === o.min ))) { + distance = thisDistance; + closestHandle = $( this ); + index = i; + } + }); + + allowed = this._start( event, index ); + if ( allowed === false ) { + return false; + } + this._mouseSliding = true; + + this._handleIndex = index; + + closestHandle + .addClass( "ui-state-active" ) + .focus(); + + offset = closestHandle.offset(); + mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); + this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { + left: event.pageX - offset.left - ( closestHandle.width() / 2 ), + top: event.pageY - offset.top - + ( closestHandle.height() / 2 ) - + ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) - + ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) + + ( parseInt( closestHandle.css("marginTop"), 10 ) || 0) + }; + + if ( !this.handles.hasClass( "ui-state-hover" ) ) { + this._slide( event, index, normValue ); + } + this._animateOff = true; + return true; + }, + + _mouseStart: function() { + return true; + }, + + _mouseDrag: function( event ) { + var position = { x: event.pageX, y: event.pageY }, + normValue = this._normValueFromMouse( position ); + + this._slide( event, this._handleIndex, normValue ); + + return false; + }, + + _mouseStop: function( event ) { + this.handles.removeClass( "ui-state-active" ); + this._mouseSliding = false; + + this._stop( event, this._handleIndex ); + this._change( event, this._handleIndex ); + + this._handleIndex = null; + this._clickOffset = null; + this._animateOff = false; + + return false; + }, + + _detectOrientation: function() { + this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; + }, + + _normValueFromMouse: function( position ) { + var pixelTotal, + pixelMouse, + percentMouse, + valueTotal, + valueMouse; + + if ( this.orientation === "horizontal" ) { + pixelTotal = this.elementSize.width; + pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 ); + } else { + pixelTotal = this.elementSize.height; + pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 ); + } + + percentMouse = ( pixelMouse / pixelTotal ); + if ( percentMouse > 1 ) { + percentMouse = 1; + } + if ( percentMouse < 0 ) { + percentMouse = 0; + } + if ( this.orientation === "vertical" ) { + percentMouse = 1 - percentMouse; + } + + valueTotal = this._valueMax() - this._valueMin(); + valueMouse = this._valueMin() + percentMouse * valueTotal; + + return this._trimAlignValue( valueMouse ); + }, + + _start: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + return this._trigger( "start", event, uiHash ); + }, + + _slide: function( event, index, newVal ) { + var otherVal, + newValues, + allowed; + + if ( this.options.values && this.options.values.length ) { + otherVal = this.values( index ? 0 : 1 ); + + if ( ( this.options.values.length === 2 && this.options.range === true ) && + ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) ) + ) { + newVal = otherVal; + } + + if ( newVal !== this.values( index ) ) { + newValues = this.values(); + newValues[ index ] = newVal; + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal, + values: newValues + } ); + otherVal = this.values( index ? 0 : 1 ); + if ( allowed !== false ) { + this.values( index, newVal, true ); + } + } + } else { + if ( newVal !== this.value() ) { + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal + } ); + if ( allowed !== false ) { + this.value( newVal ); + } + } + } + }, + + _stop: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + this._trigger( "stop", event, uiHash ); + }, + + _change: function( event, index ) { + if ( !this._keySliding && !this._mouseSliding ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + //store the last changed value index for reference when handles overlap + this._lastChangedValue = index; + + this._trigger( "change", event, uiHash ); + } + }, + + value: function( newValue ) { + if ( arguments.length ) { + this.options.value = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, 0 ); + return; + } + + return this._value(); + }, + + values: function( index, newValue ) { + var vals, + newValues, + i; + + if ( arguments.length > 1 ) { + this.options.values[ index ] = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, index ); + return; + } + + if ( arguments.length ) { + if ( $.isArray( arguments[ 0 ] ) ) { + vals = this.options.values; + newValues = arguments[ 0 ]; + for ( i = 0; i < vals.length; i += 1 ) { + vals[ i ] = this._trimAlignValue( newValues[ i ] ); + this._change( null, i ); + } + this._refreshValue(); + } else { + if ( this.options.values && this.options.values.length ) { + return this._values( index ); + } else { + return this.value(); + } + } + } else { + return this._values(); + } + }, + + _setOption: function( key, value ) { + var i, + valsLength = 0; + + if ( key === "range" && this.options.range === true ) { + if ( value === "min" ) { + this.options.value = this._values( 0 ); + this.options.values = null; + } else if ( value === "max" ) { + this.options.value = this._values( this.options.values.length-1 ); + this.options.values = null; + } + } + + if ( $.isArray( this.options.values ) ) { + valsLength = this.options.values.length; + } + + $.Widget.prototype._setOption.apply( this, arguments ); + + switch ( key ) { + case "orientation": + this._detectOrientation(); + this.element + .removeClass( "ui-slider-horizontal ui-slider-vertical" ) + .addClass( "ui-slider-" + this.orientation ); + this._refreshValue(); + break; + case "value": + this._animateOff = true; + this._refreshValue(); + this._change( null, 0 ); + this._animateOff = false; + break; + case "values": + this._animateOff = true; + this._refreshValue(); + for ( i = 0; i < valsLength; i += 1 ) { + this._change( null, i ); + } + this._animateOff = false; + break; + case "min": + case "max": + this._animateOff = true; + this._refreshValue(); + this._animateOff = false; + break; + case "range": + this._animateOff = true; + this._refresh(); + this._animateOff = false; + break; + } + }, + + //internal value getter + // _value() returns value trimmed by min and max, aligned by step + _value: function() { + var val = this.options.value; + val = this._trimAlignValue( val ); + + return val; + }, + + //internal values getter + // _values() returns array of values trimmed by min and max, aligned by step + // _values( index ) returns single value trimmed by min and max, aligned by step + _values: function( index ) { + var val, + vals, + i; + + if ( arguments.length ) { + val = this.options.values[ index ]; + val = this._trimAlignValue( val ); + + return val; + } else if ( this.options.values && this.options.values.length ) { + // .slice() creates a copy of the array + // this copy gets trimmed by min and max and then returned + vals = this.options.values.slice(); + for ( i = 0; i < vals.length; i+= 1) { + vals[ i ] = this._trimAlignValue( vals[ i ] ); + } + + return vals; + } else { + return []; + } + }, + + // returns the step-aligned value that val is closest to, between (inclusive) min and max + _trimAlignValue: function( val ) { + if ( val <= this._valueMin() ) { + return this._valueMin(); + } + if ( val >= this._valueMax() ) { + return this._valueMax(); + } + var step = ( this.options.step > 0 ) ? this.options.step : 1, + valModStep = (val - this._valueMin()) % step, + alignValue = val - valModStep; + + if ( Math.abs(valModStep) * 2 >= step ) { + alignValue += ( valModStep > 0 ) ? step : ( -step ); + } + + // Since JavaScript has problems with large floats, round + // the final value to 5 digits after the decimal point (see #4124) + return parseFloat( alignValue.toFixed(5) ); + }, + + _valueMin: function() { + return this.options.min; + }, + + _valueMax: function() { + return this.options.max; + }, + + _refreshValue: function() { + var lastValPercent, valPercent, value, valueMin, valueMax, + oRange = this.options.range, + o = this.options, + that = this, + animate = ( !this._animateOff ) ? o.animate : false, + _set = {}; + + if ( this.options.values && this.options.values.length ) { + this.handles.each(function( i ) { + valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100; + _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + if ( that.options.range === true ) { + if ( that.orientation === "horizontal" ) { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } else { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + } + lastValPercent = valPercent; + }); + } else { + value = this.value(); + valueMin = this._valueMin(); + valueMax = this._valueMax(); + valPercent = ( valueMax !== valueMin ) ? + ( value - valueMin ) / ( valueMax - valueMin ) * 100 : + 0; + _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + + if ( oRange === "min" && this.orientation === "horizontal" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "horizontal" ) { + this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + if ( oRange === "min" && this.orientation === "vertical" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "vertical" ) { + this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + }, + + _handleEvents: { + keydown: function( event ) { + /*jshint maxcomplexity:25*/ + var allowed, curVal, newVal, step, + index = $( event.target ).data( "ui-slider-handle-index" ); + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_UP: + case $.ui.keyCode.PAGE_DOWN: + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + event.preventDefault(); + if ( !this._keySliding ) { + this._keySliding = true; + $( event.target ).addClass( "ui-state-active" ); + allowed = this._start( event, index ); + if ( allowed === false ) { + return; + } + } + break; + } + + step = this.options.step; + if ( this.options.values && this.options.values.length ) { + curVal = newVal = this.values( index ); + } else { + curVal = newVal = this.value(); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + newVal = this._valueMin(); + break; + case $.ui.keyCode.END: + newVal = this._valueMax(); + break; + case $.ui.keyCode.PAGE_UP: + newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.PAGE_DOWN: + newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + if ( curVal === this._valueMax() ) { + return; + } + newVal = this._trimAlignValue( curVal + step ); + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + if ( curVal === this._valueMin() ) { + return; + } + newVal = this._trimAlignValue( curVal - step ); + break; + } + + this._slide( event, index, newVal ); + }, + click: function( event ) { + event.preventDefault(); + }, + keyup: function( event ) { + var index = $( event.target ).data( "ui-slider-handle-index" ); + + if ( this._keySliding ) { + this._keySliding = false; + this._stop( event, index ); + this._change( event, index ); + $( event.target ).removeClass( "ui-state-active" ); + } + } + } + +}); + +}(jQuery)); diff --git a/extensions/jui/assets/jquery.ui.sortable.js b/extensions/jui/assets/jquery.ui.sortable.js new file mode 100644 index 0000000..c9f2c90 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.sortable.js @@ -0,0 +1,1285 @@ +/*! + * jQuery UI Sortable 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/sortable/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +/*jshint loopfunc: true */ + +function isOverAxis( x, reference, size ) { + return ( x > reference ) && ( x < ( reference + size ) ); +} + +function isFloating(item) { + return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); +} + +$.widget("ui.sortable", $.ui.mouse, { + version: "1.10.3", + widgetEventPrefix: "sort", + ready: false, + options: { + appendTo: "parent", + axis: false, + connectWith: false, + containment: false, + cursor: "auto", + cursorAt: false, + dropOnEmpty: true, + forcePlaceholderSize: false, + forceHelperSize: false, + grid: false, + handle: false, + helper: "original", + items: "> *", + opacity: false, + placeholder: false, + revert: false, + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + scope: "default", + tolerance: "intersect", + zIndex: 1000, + + // callbacks + activate: null, + beforeStop: null, + change: null, + deactivate: null, + out: null, + over: null, + receive: null, + remove: null, + sort: null, + start: null, + stop: null, + update: null + }, + _create: function() { + + var o = this.options; + this.containerCache = {}; + this.element.addClass("ui-sortable"); + + //Get the items + this.refresh(); + + //Let's determine if the items are being displayed horizontally + this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; + + //Let's determine the parent's offset + this.offset = this.element.offset(); + + //Initialize mouse events for interaction + this._mouseInit(); + + //We're ready to go + this.ready = true; + + }, + + _destroy: function() { + this.element + .removeClass("ui-sortable ui-sortable-disabled"); + this._mouseDestroy(); + + for ( var i = this.items.length - 1; i >= 0; i-- ) { + this.items[i].item.removeData(this.widgetName + "-item"); + } + + return this; + }, + + _setOption: function(key, value){ + if ( key === "disabled" ) { + this.options[ key ] = value; + + this.widget().toggleClass( "ui-sortable-disabled", !!value ); + } else { + // Don't call widget base _setOption for disable as it adds ui-state-disabled class + $.Widget.prototype._setOption.apply(this, arguments); + } + }, + + _mouseCapture: function(event, overrideHandle) { + var currentItem = null, + validHandle = false, + that = this; + + if (this.reverting) { + return false; + } + + if(this.options.disabled || this.options.type === "static") { + return false; + } + + //We have to refresh the items data once first + this._refreshItems(event); + + //Find out if the clicked node (or one of its parents) is a actual item in this.items + $(event.target).parents().each(function() { + if($.data(this, that.widgetName + "-item") === that) { + currentItem = $(this); + return false; + } + }); + if($.data(event.target, that.widgetName + "-item") === that) { + currentItem = $(event.target); + } + + if(!currentItem) { + return false; + } + if(this.options.handle && !overrideHandle) { + $(this.options.handle, currentItem).find("*").addBack().each(function() { + if(this === event.target) { + validHandle = true; + } + }); + if(!validHandle) { + return false; + } + } + + this.currentItem = currentItem; + this._removeCurrentsFromItems(); + return true; + + }, + + _mouseStart: function(event, overrideHandle, noActivation) { + + var i, body, + o = this.options; + + this.currentContainer = this; + + //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture + this.refreshPositions(); + + //Create and append the visible helper + this.helper = this._createHelper(event); + + //Cache the helper size + this._cacheHelperProportions(); + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Get the next scrolling parent + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.currentItem.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + // Only after we got the offset, we can change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css("position", "absolute"); + this.cssPosition = this.helper.css("position"); + + //Generate the original position + this.originalPosition = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Cache the former DOM position + this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; + + //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way + if(this.helper[0] !== this.currentItem[0]) { + this.currentItem.hide(); + } + + //Create the placeholder + this._createPlaceholder(); + + //Set a containment if given in the options + if(o.containment) { + this._setContainment(); + } + + if( o.cursor && o.cursor !== "auto" ) { // cursor option + body = this.document.find( "body" ); + + // support: IE + this.storedCursor = body.css( "cursor" ); + body.css( "cursor", o.cursor ); + + this.storedStylesheet = $( "" ).appendTo( body ); + } + + if(o.opacity) { // opacity option + if (this.helper.css("opacity")) { + this._storedOpacity = this.helper.css("opacity"); + } + this.helper.css("opacity", o.opacity); + } + + if(o.zIndex) { // zIndex option + if (this.helper.css("zIndex")) { + this._storedZIndex = this.helper.css("zIndex"); + } + this.helper.css("zIndex", o.zIndex); + } + + //Prepare scrolling + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + this.overflowOffset = this.scrollParent.offset(); + } + + //Call callbacks + this._trigger("start", event, this._uiHash()); + + //Recache the helper size + if(!this._preserveHelperProportions) { + this._cacheHelperProportions(); + } + + + //Post "activate" events to possible containers + if( !noActivation ) { + for ( i = this.containers.length - 1; i >= 0; i-- ) { + this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); + } + } + + //Prepare possible droppables + if($.ui.ddmanager) { + $.ui.ddmanager.current = this; + } + + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + + this.dragging = true; + + this.helper.addClass("ui-sortable-helper"); + this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position + return true; + + }, + + _mouseDrag: function(event) { + var i, item, itemElement, intersection, + o = this.options, + scrolled = false; + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + //Do scrolling + if(this.options.scroll) { + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + } + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + } + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + //Set the helper position + if(!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left+"px"; + } + if(!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top+"px"; + } + + //Rearrange + for (i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + item = this.items[i]; + itemElement = item.item[0]; + intersection = this._intersectsWithPointer(item); + if (!intersection) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items form other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this moving items in "sub-sortables" can cause the placeholder to jitter + // beetween the outer and inner container. + if (item.instance !== this.currentContainer) { + continue; + } + + // cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if (itemElement !== this.currentItem[0] && + this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && + !$.contains(this.placeholder[0], itemElement) && + (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { + this._rearrange(event, item); + } else { + break; + } + + this._trigger("change", event, this._uiHash()); + break; + } + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + //Call callbacks + this._trigger("sort", event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + if(!event) { + return; + } + + //If we are using droppables, inform the manager about the drop + if ($.ui.ddmanager && !this.options.dropBehaviour) { + $.ui.ddmanager.drop(this, event); + } + + if(this.options.revert) { + var that = this, + cur = this.placeholder.offset(), + axis = this.options.axis, + animation = {}; + + if ( !axis || axis === "x" ) { + animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); + } + if ( !axis || axis === "y" ) { + animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); + } + this.reverting = true; + $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { + that._clear(event); + }); + } else { + this._clear(event, noPropagation); + } + + return false; + + }, + + cancel: function() { + + if(this.dragging) { + + this._mouseUp({ target: null }); + + if(this.options.helper === "original") { + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + //Post deactivating events to containers + for (var i = this.containers.length - 1; i >= 0; i--){ + this.containers[i]._trigger("deactivate", null, this._uiHash(this)); + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", null, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + if (this.placeholder) { + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + if(this.placeholder[0].parentNode) { + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + } + if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { + this.helper.remove(); + } + + $.extend(this, { + helper: null, + dragging: false, + reverting: false, + _noFinalSort: null + }); + + if(this.domPosition.prev) { + $(this.domPosition.prev).after(this.currentItem); + } else { + $(this.domPosition.parent).prepend(this.currentItem); + } + } + + return this; + + }, + + serialize: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + str = []; + o = o || {}; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); + if (res) { + str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); + } + }); + + if(!str.length && o.key) { + str.push(o.key + "="); + } + + return str.join("&"); + + }, + + toArray: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + ret = []; + + o = o || {}; + + items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); + return ret; + + }, + + /* Be careful with the following core functions */ + _intersectsWith: function(item) { + + var x1 = this.positionAbs.left, + x2 = x1 + this.helperProportions.width, + y1 = this.positionAbs.top, + y2 = y1 + this.helperProportions.height, + l = item.left, + r = l + item.width, + t = item.top, + b = t + item.height, + dyClick = this.offset.click.top, + dxClick = this.offset.click.left, + isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), + isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), + isOverElement = isOverElementHeight && isOverElementWidth; + + if ( this.options.tolerance === "pointer" || + this.options.forcePointerForContainers || + (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) + ) { + return isOverElement; + } else { + + return (l < x1 + (this.helperProportions.width / 2) && // Right Half + x2 - (this.helperProportions.width / 2) < r && // Left Half + t < y1 + (this.helperProportions.height / 2) && // Bottom Half + y2 - (this.helperProportions.height / 2) < b ); // Top Half + + } + }, + + _intersectsWithPointer: function(item) { + + var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), + isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), + isOverElement = isOverElementHeight && isOverElementWidth, + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (!isOverElement) { + return false; + } + + return this.floating ? + ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) + : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); + + }, + + _intersectsWithSides: function(item) { + + var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), + isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (this.floating && horizontalDirection) { + return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); + } else { + return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); + } + + }, + + _getDragVerticalDirection: function() { + var delta = this.positionAbs.top - this.lastPositionAbs.top; + return delta !== 0 && (delta > 0 ? "down" : "up"); + }, + + _getDragHorizontalDirection: function() { + var delta = this.positionAbs.left - this.lastPositionAbs.left; + return delta !== 0 && (delta > 0 ? "right" : "left"); + }, + + refresh: function(event) { + this._refreshItems(event); + this.refreshPositions(); + return this; + }, + + _connectWith: function() { + var options = this.options; + return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; + }, + + _getItemsAsjQuery: function(connected) { + + var i, j, cur, inst, + items = [], + queries = [], + connectWith = this._connectWith(); + + if(connectWith && connected) { + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for ( j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); + } + } + } + } + + queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); + + for (i = queries.length - 1; i >= 0; i--){ + queries[i][0].each(function() { + items.push(this); + }); + } + + return $(items); + + }, + + _removeCurrentsFromItems: function() { + + var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); + + this.items = $.grep(this.items, function (item) { + for (var j=0; j < list.length; j++) { + if(list[j] === item.item[0]) { + return false; + } + } + return true; + }); + + }, + + _refreshItems: function(event) { + + this.items = []; + this.containers = [this]; + + var i, j, cur, inst, targetData, _queries, item, queriesLength, + items = this.items, + queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], + connectWith = this._connectWith(); + + if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for (j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); + this.containers.push(inst); + } + } + } + } + + for (i = queries.length - 1; i >= 0; i--) { + targetData = queries[i][1]; + _queries = queries[i][0]; + + for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { + item = $(_queries[j]); + + item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) + + items.push({ + item: item, + instance: targetData, + width: 0, height: 0, + left: 0, top: 0 + }); + } + } + + }, + + refreshPositions: function(fast) { + + //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change + if(this.offsetParent && this.helper) { + this.offset.parent = this._getParentOffset(); + } + + var i, item, t, p; + + for (i = this.items.length - 1; i >= 0; i--){ + item = this.items[i]; + + //We ignore calculating positions of all connected containers when we're not over them + if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { + continue; + } + + t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; + + if (!fast) { + item.width = t.outerWidth(); + item.height = t.outerHeight(); + } + + p = t.offset(); + item.left = p.left; + item.top = p.top; + } + + if(this.options.custom && this.options.custom.refreshContainers) { + this.options.custom.refreshContainers.call(this); + } else { + for (i = this.containers.length - 1; i >= 0; i--){ + p = this.containers[i].element.offset(); + this.containers[i].containerCache.left = p.left; + this.containers[i].containerCache.top = p.top; + this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); + this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); + } + } + + return this; + }, + + _createPlaceholder: function(that) { + that = that || this; + var className, + o = that.options; + + if(!o.placeholder || o.placeholder.constructor === String) { + className = o.placeholder; + o.placeholder = { + element: function() { + + var nodeName = that.currentItem[0].nodeName.toLowerCase(), + element = $( "<" + nodeName + ">", that.document[0] ) + .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") + .removeClass("ui-sortable-helper"); + + if ( nodeName === "tr" ) { + that.currentItem.children().each(function() { + $( " ", that.document[0] ) + .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) + .appendTo( element ); + }); + } else if ( nodeName === "img" ) { + element.attr( "src", that.currentItem.attr( "src" ) ); + } + + if ( !className ) { + element.css( "visibility", "hidden" ); + } + + return element; + }, + update: function(container, p) { + + // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that + // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified + if(className && !o.forcePlaceholderSize) { + return; + } + + //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item + if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } + if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } + } + }; + } + + //Create the placeholder + that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); + + //Append it after the actual current item + that.currentItem.after(that.placeholder); + + //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) + o.placeholder.update(that, that.placeholder); + + }, + + _contactContainers: function(event) { + var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, + innermostContainer = null, + innermostIndex = null; + + // get innermost container that intersects with item + for (i = this.containers.length - 1; i >= 0; i--) { + + // never consider a container that's located within the item itself + if($.contains(this.currentItem[0], this.containers[i].element[0])) { + continue; + } + + if(this._intersectsWith(this.containers[i].containerCache)) { + + // if we've already found a container and it's more "inner" than this, then continue + if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { + continue; + } + + innermostContainer = this.containers[i]; + innermostIndex = i; + + } else { + // container doesn't intersect. trigger "out" event if necessary + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", event, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + // if no intersecting containers found, return + if(!innermostContainer) { + return; + } + + // move the item into the container if it's not there already + if(this.containers.length === 1) { + if (!this.containers[innermostIndex].containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + } else { + + //When entering a new container, we will find the item with the least distance and append our item near it + dist = 10000; + itemWithLeastDistance = null; + floating = innermostContainer.floating || isFloating(this.currentItem); + posProperty = floating ? "left" : "top"; + sizeProperty = floating ? "width" : "height"; + base = this.positionAbs[posProperty] + this.offset.click[posProperty]; + for (j = this.items.length - 1; j >= 0; j--) { + if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { + continue; + } + if(this.items[j].item[0] === this.currentItem[0]) { + continue; + } + if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { + continue; + } + cur = this.items[j].item.offset()[posProperty]; + nearBottom = false; + if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ + nearBottom = true; + cur += this.items[j][sizeProperty]; + } + + if(Math.abs(cur - base) < dist) { + dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; + this.direction = nearBottom ? "up": "down"; + } + } + + //Check if dropOnEmpty is enabled + if(!itemWithLeastDistance && !this.options.dropOnEmpty) { + return; + } + + if(this.currentContainer === this.containers[innermostIndex]) { + return; + } + + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + this.currentContainer = this.containers[innermostIndex]; + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + + + }, + + _createHelper: function(event) { + + var o = this.options, + helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); + + //Add the helper to the DOM if that didn't happen already + if(!helper.parents("body").length) { + $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); + } + + if(helper[0] === this.currentItem[0]) { + this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; + } + + if(!helper[0].style.width || o.forceHelperSize) { + helper.width(this.currentItem.width()); + } + if(!helper[0].style.height || o.forceHelperSize) { + helper.height(this.currentItem.height()); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj === "string") { + obj = obj.split(" "); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ("left" in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ("right" in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ("top" in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ("bottom" in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + // This needs to be actually done for all browsers, since pageX/pageY includes this information + // with an ugly IE fix + if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition === "relative") { + var p = this.currentItem.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), + top: (parseInt(this.currentItem.css("marginTop"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var ce, co, over, + o = this.options; + if(o.containment === "parent") { + o.containment = this.helper[0].parentNode; + } + if(o.containment === "document" || o.containment === "window") { + this.containment = [ + 0 - this.offset.relative.left - this.offset.parent.left, + 0 - this.offset.relative.top - this.offset.parent.top, + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + } + + if(!(/^(document|window|parent)$/).test(o.containment)) { + ce = $(o.containment)[0]; + co = $(o.containment).offset(); + over = ($(ce).css("overflow") !== "hidden"); + + this.containment = [ + co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, + co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, + co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, + co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top + ]; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) { + pos = this.position; + } + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, + scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top + // The absolute mouse position + this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left + // The absolute mouse position + this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var top, left, + o = this.options, + pageX = event.pageX, + pageY = event.pageY, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + // This is another very weird special case that only happens for relative elements: + // 1. If the css position is relative + // 2. and the scroll parent is the document or similar to the offset parent + // we have to refresh the relative offset during the scroll so there are no jumps + if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { + this.offset.relative = this._getRelativeOffset(); + } + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + + if(this.containment) { + if(event.pageX - this.offset.click.left < this.containment[0]) { + pageX = this.containment[0] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top < this.containment[1]) { + pageY = this.containment[1] + this.offset.click.top; + } + if(event.pageX - this.offset.click.left > this.containment[2]) { + pageX = this.containment[2] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top > this.containment[3]) { + pageY = this.containment[3] + this.offset.click.top; + } + } + + if(o.grid) { + top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; + pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; + pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY - // The absolute mouse position + this.offset.click.top - // Click offset (relative to the element) + this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX - // The absolute mouse position + this.offset.click.left - // Click offset (relative to the element) + this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _rearrange: function(event, i, a, hardRefresh) { + + a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); + + //Various things done here to improve the performance: + // 1. we create a setTimeout, that calls refreshPositions + // 2. on the instance, we have a counter variable, that get's higher after every append + // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same + // 4. this lets only the last addition to the timeout stack through + this.counter = this.counter ? ++this.counter : 1; + var counter = this.counter; + + this._delay(function() { + if(counter === this.counter) { + this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove + } + }); + + }, + + _clear: function(event, noPropagation) { + + this.reverting = false; + // We delay all events that have to be triggered to after the point where the placeholder has been removed and + // everything else normalized again + var i, + delayedTriggers = []; + + // We first have to update the dom position of the actual currentItem + // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) + if(!this._noFinalSort && this.currentItem.parent().length) { + this.placeholder.before(this.currentItem); + } + this._noFinalSort = null; + + if(this.helper[0] === this.currentItem[0]) { + for(i in this._storedCSS) { + if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { + this._storedCSS[i] = ""; + } + } + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + if(this.fromOutside && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); + } + if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed + } + + // Check if the items Container has Changed and trigger appropriate + // events. + if (this !== this.currentContainer) { + if(!noPropagation) { + delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); + delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + } + } + + + //Post events to containers + for (i = this.containers.length - 1; i >= 0; i--){ + if(!noPropagation) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + } + if(this.containers[i].containerCache.over) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + this.containers[i].containerCache.over = 0; + } + } + + //Do what was originally in plugins + if ( this.storedCursor ) { + this.document.find( "body" ).css( "cursor", this.storedCursor ); + this.storedStylesheet.remove(); + } + if(this._storedOpacity) { + this.helper.css("opacity", this._storedOpacity); + } + if(this._storedZIndex) { + this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); + } + + this.dragging = false; + if(this.cancelHelperRemoval) { + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return false; + } + + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + } + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + + if(this.helper[0] !== this.currentItem[0]) { + this.helper.remove(); + } + this.helper = null; + + if(!noPropagation) { + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return true; + + }, + + _trigger: function() { + if ($.Widget.prototype._trigger.apply(this, arguments) === false) { + this.cancel(); + } + }, + + _uiHash: function(_inst) { + var inst = _inst || this; + return { + helper: inst.helper, + placeholder: inst.placeholder || $([]), + position: inst.position, + originalPosition: inst.originalPosition, + offset: inst.positionAbs, + item: inst.currentItem, + sender: _inst ? _inst.element : null + }; + } + +}); + +})(jQuery); diff --git a/extensions/jui/assets/jquery.ui.spinner.js b/extensions/jui/assets/jquery.ui.spinner.js new file mode 100644 index 0000000..5bfbb27 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.spinner.js @@ -0,0 +1,493 @@ +/*! + * jQuery UI Spinner 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/spinner/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + */ +(function( $ ) { + +function modifier( fn ) { + return function() { + var previous = this.element.val(); + fn.apply( this, arguments ); + this._refresh(); + if ( previous !== this.element.val() ) { + this._trigger( "change" ); + } + }; +} + +$.widget( "ui.spinner", { + version: "1.10.3", + defaultElement: "", + widgetEventPrefix: "spin", + options: { + culture: null, + icons: { + down: "ui-icon-triangle-1-s", + up: "ui-icon-triangle-1-n" + }, + incremental: true, + max: null, + min: null, + numberFormat: null, + page: 10, + step: 1, + + change: null, + spin: null, + start: null, + stop: null + }, + + _create: function() { + // handle string values that need to be parsed + this._setOption( "max", this.options.max ); + this._setOption( "min", this.options.min ); + this._setOption( "step", this.options.step ); + + // format the value, but don't constrain + this._value( this.element.val(), true ); + + this._draw(); + this._on( this._events ); + this._refresh(); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _getCreateOptions: function() { + var options = {}, + element = this.element; + + $.each( [ "min", "max", "step" ], function( i, option ) { + var value = element.attr( option ); + if ( value !== undefined && value.length ) { + options[ option ] = value; + } + }); + + return options; + }, + + _events: { + keydown: function( event ) { + if ( this._start( event ) && this._keydown( event ) ) { + event.preventDefault(); + } + }, + keyup: "_stop", + focus: function() { + this.previous = this.element.val(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + this._stop(); + this._refresh(); + if ( this.previous !== this.element.val() ) { + this._trigger( "change", event ); + } + }, + mousewheel: function( event, delta ) { + if ( !delta ) { + return; + } + if ( !this.spinning && !this._start( event ) ) { + return false; + } + + this._spin( (delta > 0 ? 1 : -1) * this.options.step, event ); + clearTimeout( this.mousewheelTimer ); + this.mousewheelTimer = this._delay(function() { + if ( this.spinning ) { + this._stop( event ); + } + }, 100 ); + event.preventDefault(); + }, + "mousedown .ui-spinner-button": function( event ) { + var previous; + + // We never want the buttons to have focus; whenever the user is + // interacting with the spinner, the focus should be on the input. + // If the input is focused then this.previous is properly set from + // when the input first received focus. If the input is not focused + // then we need to set this.previous based on the value before spinning. + previous = this.element[0] === this.document[0].activeElement ? + this.previous : this.element.val(); + function checkFocus() { + var isActive = this.element[0] === this.document[0].activeElement; + if ( !isActive ) { + this.element.focus(); + this.previous = previous; + // support: IE + // IE sets focus asynchronously, so we need to check if focus + // moved off of the input because the user clicked on the button. + this._delay(function() { + this.previous = previous; + }); + } + } + + // ensure focus is on (or stays on) the text field + event.preventDefault(); + checkFocus.call( this ); + + // support: IE + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + // and check (again) if focus moved off of the input. + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + checkFocus.call( this ); + }); + + if ( this._start( event ) === false ) { + return; + } + + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + "mouseup .ui-spinner-button": "_stop", + "mouseenter .ui-spinner-button": function( event ) { + // button will add ui-state-active if mouse was down while mouseleave and kept down + if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { + return; + } + + if ( this._start( event ) === false ) { + return false; + } + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + // TODO: do we really want to consider this a stop? + // shouldn't we just stop the repeater and wait until mouseup before + // we trigger the stop event? + "mouseleave .ui-spinner-button": "_stop" + }, + + _draw: function() { + var uiSpinner = this.uiSpinner = this.element + .addClass( "ui-spinner-input" ) + .attr( "autocomplete", "off" ) + .wrap( this._uiSpinnerHtml() ) + .parent() + // add buttons + .append( this._buttonHtml() ); + + this.element.attr( "role", "spinbutton" ); + + // button bindings + this.buttons = uiSpinner.find( ".ui-spinner-button" ) + .attr( "tabIndex", -1 ) + .button() + .removeClass( "ui-corner-all" ); + + // IE 6 doesn't understand height: 50% for the buttons + // unless the wrapper has an explicit height + if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) && + uiSpinner.height() > 0 ) { + uiSpinner.height( uiSpinner.height() ); + } + + // disable spinner if element was already disabled + if ( this.options.disabled ) { + this.disable(); + } + }, + + _keydown: function( event ) { + var options = this.options, + keyCode = $.ui.keyCode; + + switch ( event.keyCode ) { + case keyCode.UP: + this._repeat( null, 1, event ); + return true; + case keyCode.DOWN: + this._repeat( null, -1, event ); + return true; + case keyCode.PAGE_UP: + this._repeat( null, options.page, event ); + return true; + case keyCode.PAGE_DOWN: + this._repeat( null, -options.page, event ); + return true; + } + + return false; + }, + + _uiSpinnerHtml: function() { + return ""; + }, + + _buttonHtml: function() { + return "" + + "" + + "" + + "" + + "" + + "" + + ""; + }, + + _start: function( event ) { + if ( !this.spinning && this._trigger( "start", event ) === false ) { + return false; + } + + if ( !this.counter ) { + this.counter = 1; + } + this.spinning = true; + return true; + }, + + _repeat: function( i, steps, event ) { + i = i || 500; + + clearTimeout( this.timer ); + this.timer = this._delay(function() { + this._repeat( 40, steps, event ); + }, i ); + + this._spin( steps * this.options.step, event ); + }, + + _spin: function( step, event ) { + var value = this.value() || 0; + + if ( !this.counter ) { + this.counter = 1; + } + + value = this._adjustValue( value + step * this._increment( this.counter ) ); + + if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) { + this._value( value ); + this.counter++; + } + }, + + _increment: function( i ) { + var incremental = this.options.incremental; + + if ( incremental ) { + return $.isFunction( incremental ) ? + incremental( i ) : + Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ); + } + + return 1; + }, + + _precision: function() { + var precision = this._precisionOf( this.options.step ); + if ( this.options.min !== null ) { + precision = Math.max( precision, this._precisionOf( this.options.min ) ); + } + return precision; + }, + + _precisionOf: function( num ) { + var str = num.toString(), + decimal = str.indexOf( "." ); + return decimal === -1 ? 0 : str.length - decimal - 1; + }, + + _adjustValue: function( value ) { + var base, aboveMin, + options = this.options; + + // make sure we're at a valid step + // - find out where we are relative to the base (min or 0) + base = options.min !== null ? options.min : 0; + aboveMin = value - base; + // - round to the nearest step + aboveMin = Math.round(aboveMin / options.step) * options.step; + // - rounding is based on 0, so adjust back to our base + value = base + aboveMin; + + // fix precision from bad JS floating point math + value = parseFloat( value.toFixed( this._precision() ) ); + + // clamp the value + if ( options.max !== null && value > options.max) { + return options.max; + } + if ( options.min !== null && value < options.min ) { + return options.min; + } + + return value; + }, + + _stop: function( event ) { + if ( !this.spinning ) { + return; + } + + clearTimeout( this.timer ); + clearTimeout( this.mousewheelTimer ); + this.counter = 0; + this.spinning = false; + this._trigger( "stop", event ); + }, + + _setOption: function( key, value ) { + if ( key === "culture" || key === "numberFormat" ) { + var prevValue = this._parse( this.element.val() ); + this.options[ key ] = value; + this.element.val( this._format( prevValue ) ); + return; + } + + if ( key === "max" || key === "min" || key === "step" ) { + if ( typeof value === "string" ) { + value = this._parse( value ); + } + } + if ( key === "icons" ) { + this.buttons.first().find( ".ui-icon" ) + .removeClass( this.options.icons.up ) + .addClass( value.up ); + this.buttons.last().find( ".ui-icon" ) + .removeClass( this.options.icons.down ) + .addClass( value.down ); + } + + this._super( key, value ); + + if ( key === "disabled" ) { + if ( value ) { + this.element.prop( "disabled", true ); + this.buttons.button( "disable" ); + } else { + this.element.prop( "disabled", false ); + this.buttons.button( "enable" ); + } + } + }, + + _setOptions: modifier(function( options ) { + this._super( options ); + this._value( this.element.val() ); + }), + + _parse: function( val ) { + if ( typeof val === "string" && val !== "" ) { + val = window.Globalize && this.options.numberFormat ? + Globalize.parseFloat( val, 10, this.options.culture ) : +val; + } + return val === "" || isNaN( val ) ? null : val; + }, + + _format: function( value ) { + if ( value === "" ) { + return ""; + } + return window.Globalize && this.options.numberFormat ? + Globalize.format( value, this.options.numberFormat, this.options.culture ) : + value; + }, + + _refresh: function() { + this.element.attr({ + "aria-valuemin": this.options.min, + "aria-valuemax": this.options.max, + // TODO: what should we do with values that can't be parsed? + "aria-valuenow": this._parse( this.element.val() ) + }); + }, + + // update the value without triggering change + _value: function( value, allowAny ) { + var parsed; + if ( value !== "" ) { + parsed = this._parse( value ); + if ( parsed !== null ) { + if ( !allowAny ) { + parsed = this._adjustValue( parsed ); + } + value = this._format( parsed ); + } + } + this.element.val( value ); + this._refresh(); + }, + + _destroy: function() { + this.element + .removeClass( "ui-spinner-input" ) + .prop( "disabled", false ) + .removeAttr( "autocomplete" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + this.uiSpinner.replaceWith( this.element ); + }, + + stepUp: modifier(function( steps ) { + this._stepUp( steps ); + }), + _stepUp: function( steps ) { + if ( this._start() ) { + this._spin( (steps || 1) * this.options.step ); + this._stop(); + } + }, + + stepDown: modifier(function( steps ) { + this._stepDown( steps ); + }), + _stepDown: function( steps ) { + if ( this._start() ) { + this._spin( (steps || 1) * -this.options.step ); + this._stop(); + } + }, + + pageUp: modifier(function( pages ) { + this._stepUp( (pages || 1) * this.options.page ); + }), + + pageDown: modifier(function( pages ) { + this._stepDown( (pages || 1) * this.options.page ); + }), + + value: function( newVal ) { + if ( !arguments.length ) { + return this._parse( this.element.val() ); + } + modifier( this._value ).call( this, newVal ); + }, + + widget: function() { + return this.uiSpinner; + } +}); + +}( jQuery ) ); diff --git a/extensions/jui/assets/jquery.ui.tabs.js b/extensions/jui/assets/jquery.ui.tabs.js new file mode 100644 index 0000000..b37858e --- /dev/null +++ b/extensions/jui/assets/jquery.ui.tabs.js @@ -0,0 +1,846 @@ +/*! + * jQuery UI Tabs 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/tabs/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var tabId = 0, + rhash = /#.*$/; + +function getNextTabId() { + return ++tabId; +} + +function isLocal( anchor ) { + return anchor.hash.length > 1 && + decodeURIComponent( anchor.href.replace( rhash, "" ) ) === + decodeURIComponent( location.href.replace( rhash, "" ) ); +} + +$.widget( "ui.tabs", { + version: "1.10.3", + delay: 300, + options: { + active: null, + collapsible: false, + event: "click", + heightStyle: "content", + hide: null, + show: null, + + // callbacks + activate: null, + beforeActivate: null, + beforeLoad: null, + load: null + }, + + _create: function() { + var that = this, + options = this.options; + + this.running = false; + + this.element + .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ) + .toggleClass( "ui-tabs-collapsible", options.collapsible ) + // Prevent users from focusing disabled tabs via click + .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) { + if ( $( this ).is( ".ui-state-disabled" ) ) { + event.preventDefault(); + } + }) + // support: IE <9 + // Preventing the default action in mousedown doesn't prevent IE + // from focusing the element, so if the anchor gets focused, blur. + // We don't have to worry about focusing the previously focused + // element since clicking on a non-focusable element should focus + // the body anyway. + .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() { + if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { + this.blur(); + } + }); + + this._processTabs(); + options.active = this._initialActive(); + + // Take disabling tabs via class attribute from HTML + // into account and update option properly. + if ( $.isArray( options.disabled ) ) { + options.disabled = $.unique( options.disabled.concat( + $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { + return that.tabs.index( li ); + }) + ) ).sort(); + } + + // check for length avoids error when initializing empty list + if ( this.options.active !== false && this.anchors.length ) { + this.active = this._findActive( options.active ); + } else { + this.active = $(); + } + + this._refresh(); + + if ( this.active.length ) { + this.load( options.active ); + } + }, + + _initialActive: function() { + var active = this.options.active, + collapsible = this.options.collapsible, + locationHash = location.hash.substring( 1 ); + + if ( active === null ) { + // check the fragment identifier in the URL + if ( locationHash ) { + this.tabs.each(function( i, tab ) { + if ( $( tab ).attr( "aria-controls" ) === locationHash ) { + active = i; + return false; + } + }); + } + + // check for a tab marked active via a class + if ( active === null ) { + active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); + } + + // no active tab, set to false + if ( active === null || active === -1 ) { + active = this.tabs.length ? 0 : false; + } + } + + // handle numbers: negative, out of range + if ( active !== false ) { + active = this.tabs.index( this.tabs.eq( active ) ); + if ( active === -1 ) { + active = collapsible ? false : 0; + } + } + + // don't allow collapsible: false and active: false + if ( !collapsible && active === false && this.anchors.length ) { + active = 0; + } + + return active; + }, + + _getCreateEventData: function() { + return { + tab: this.active, + panel: !this.active.length ? $() : this._getPanelForTab( this.active ) + }; + }, + + _tabKeydown: function( event ) { + /*jshint maxcomplexity:15*/ + var focusedTab = $( this.document[0].activeElement ).closest( "li" ), + selectedIndex = this.tabs.index( focusedTab ), + goingForward = true; + + if ( this._handlePageNav( event ) ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + selectedIndex++; + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.LEFT: + goingForward = false; + selectedIndex--; + break; + case $.ui.keyCode.END: + selectedIndex = this.anchors.length - 1; + break; + case $.ui.keyCode.HOME: + selectedIndex = 0; + break; + case $.ui.keyCode.SPACE: + // Activate only, no collapsing + event.preventDefault(); + clearTimeout( this.activating ); + this._activate( selectedIndex ); + return; + case $.ui.keyCode.ENTER: + // Toggle (cancel delayed activation, allow collapsing) + event.preventDefault(); + clearTimeout( this.activating ); + // Determine if we should collapse or activate + this._activate( selectedIndex === this.options.active ? false : selectedIndex ); + return; + default: + return; + } + + // Focus the appropriate tab, based on which key was pressed + event.preventDefault(); + clearTimeout( this.activating ); + selectedIndex = this._focusNextTab( selectedIndex, goingForward ); + + // Navigating with control key will prevent automatic activation + if ( !event.ctrlKey ) { + // Update aria-selected immediately so that AT think the tab is already selected. + // Otherwise AT may confuse the user by stating that they need to activate the tab, + // but the tab will already be activated by the time the announcement finishes. + focusedTab.attr( "aria-selected", "false" ); + this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); + + this.activating = this._delay(function() { + this.option( "active", selectedIndex ); + }, this.delay ); + } + }, + + _panelKeydown: function( event ) { + if ( this._handlePageNav( event ) ) { + return; + } + + // Ctrl+up moves focus to the current tab + if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { + event.preventDefault(); + this.active.focus(); + } + }, + + // Alt+page up/down moves focus to the previous/next tab (and activates) + _handlePageNav: function( event ) { + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { + this._activate( this._focusNextTab( this.options.active - 1, false ) ); + return true; + } + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { + this._activate( this._focusNextTab( this.options.active + 1, true ) ); + return true; + } + }, + + _findNextTab: function( index, goingForward ) { + var lastTabIndex = this.tabs.length - 1; + + function constrain() { + if ( index > lastTabIndex ) { + index = 0; + } + if ( index < 0 ) { + index = lastTabIndex; + } + return index; + } + + while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { + index = goingForward ? index + 1 : index - 1; + } + + return index; + }, + + _focusNextTab: function( index, goingForward ) { + index = this._findNextTab( index, goingForward ); + this.tabs.eq( index ).focus(); + return index; + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "disabled" ) { + // don't use the widget factory's disabled handling + this._setupDisabled( value ); + return; + } + + this._super( key, value); + + if ( key === "collapsible" ) { + this.element.toggleClass( "ui-tabs-collapsible", value ); + // Setting collapsible: false while collapsed; open first panel + if ( !value && this.options.active === false ) { + this._activate( 0 ); + } + } + + if ( key === "event" ) { + this._setupEvents( value ); + } + + if ( key === "heightStyle" ) { + this._setupHeightStyle( value ); + } + }, + + _tabId: function( tab ) { + return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); + }, + + _sanitizeSelector: function( hash ) { + return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; + }, + + refresh: function() { + var options = this.options, + lis = this.tablist.children( ":has(a[href])" ); + + // get disabled tabs from class attribute from HTML + // this will get converted to a boolean if needed in _refresh() + options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { + return lis.index( tab ); + }); + + this._processTabs(); + + // was collapsed or no tabs + if ( options.active === false || !this.anchors.length ) { + options.active = false; + this.active = $(); + // was active, but active tab is gone + } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { + // all remaining tabs are disabled + if ( this.tabs.length === options.disabled.length ) { + options.active = false; + this.active = $(); + // activate previous tab + } else { + this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); + } + // was active, active tab still exists + } else { + // make sure active index is correct + options.active = this.tabs.index( this.active ); + } + + this._refresh(); + }, + + _refresh: function() { + this._setupDisabled( this.options.disabled ); + this._setupEvents( this.options.event ); + this._setupHeightStyle( this.options.heightStyle ); + + this.tabs.not( this.active ).attr({ + "aria-selected": "false", + tabIndex: -1 + }); + this.panels.not( this._getPanelForTab( this.active ) ) + .hide() + .attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + + // Make sure one tab is in the tab order + if ( !this.active.length ) { + this.tabs.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active + .addClass( "ui-tabs-active ui-state-active" ) + .attr({ + "aria-selected": "true", + tabIndex: 0 + }); + this._getPanelForTab( this.active ) + .show() + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + } + }, + + _processTabs: function() { + var that = this; + + this.tablist = this._getList() + .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .attr( "role", "tablist" ); + + this.tabs = this.tablist.find( "> li:has(a[href])" ) + .addClass( "ui-state-default ui-corner-top" ) + .attr({ + role: "tab", + tabIndex: -1 + }); + + this.anchors = this.tabs.map(function() { + return $( "a", this )[ 0 ]; + }) + .addClass( "ui-tabs-anchor" ) + .attr({ + role: "presentation", + tabIndex: -1 + }); + + this.panels = $(); + + this.anchors.each(function( i, anchor ) { + var selector, panel, panelId, + anchorId = $( anchor ).uniqueId().attr( "id" ), + tab = $( anchor ).closest( "li" ), + originalAriaControls = tab.attr( "aria-controls" ); + + // inline tab + if ( isLocal( anchor ) ) { + selector = anchor.hash; + panel = that.element.find( that._sanitizeSelector( selector ) ); + // remote tab + } else { + panelId = that._tabId( tab ); + selector = "#" + panelId; + panel = that.element.find( selector ); + if ( !panel.length ) { + panel = that._createPanel( panelId ); + panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + } + panel.attr( "aria-live", "polite" ); + } + + if ( panel.length) { + that.panels = that.panels.add( panel ); + } + if ( originalAriaControls ) { + tab.data( "ui-tabs-aria-controls", originalAriaControls ); + } + tab.attr({ + "aria-controls": selector.substring( 1 ), + "aria-labelledby": anchorId + }); + panel.attr( "aria-labelledby", anchorId ); + }); + + this.panels + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .attr( "role", "tabpanel" ); + }, + + // allow overriding how to find the list for rare usage scenarios (#7715) + _getList: function() { + return this.element.find( "ol,ul" ).eq( 0 ); + }, + + _createPanel: function( id ) { + return $( "
      " ) + .attr( "id", id ) + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .data( "ui-tabs-destroy", true ); + }, + + _setupDisabled: function( disabled ) { + if ( $.isArray( disabled ) ) { + if ( !disabled.length ) { + disabled = false; + } else if ( disabled.length === this.anchors.length ) { + disabled = true; + } + } + + // disable tabs + for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) { + if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { + $( li ) + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } else { + $( li ) + .removeClass( "ui-state-disabled" ) + .removeAttr( "aria-disabled" ); + } + } + + this.options.disabled = disabled; + }, + + _setupEvents: function( event ) { + var events = { + click: function( event ) { + event.preventDefault(); + } + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.anchors.add( this.tabs ).add( this.panels ) ); + this._on( this.anchors, events ); + this._on( this.tabs, { keydown: "_tabKeydown" } ); + this._on( this.panels, { keydown: "_panelKeydown" } ); + + this._focusable( this.tabs ); + this._hoverable( this.tabs ); + }, + + _setupHeightStyle: function( heightStyle ) { + var maxHeight, + parent = this.element.parent(); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + maxHeight -= this.element.outerHeight() - this.element.height(); + + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.element.children().not( this.panels ).each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.panels.each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.panels.each(function() { + maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); + }).height( maxHeight ); + } + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + anchor = $( event.currentTarget ), + tab = anchor.closest( "li" ), + clickedIsActive = tab[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : this._getPanelForTab( tab ), + toHide = !active.length ? $() : this._getPanelForTab( active ), + eventData = { + oldTab: active, + oldPanel: toHide, + newTab: collapsing ? $() : tab, + newPanel: toShow + }; + + event.preventDefault(); + + if ( tab.hasClass( "ui-state-disabled" ) || + // tab is already loading + tab.hasClass( "ui-tabs-loading" ) || + // can't switch durning an animation + this.running || + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.tabs.index( tab ); + + this.active = clickedIsActive ? $() : tab; + if ( this.xhr ) { + this.xhr.abort(); + } + + if ( !toHide.length && !toShow.length ) { + $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); + } + + if ( toShow.length ) { + this.load( this.tabs.index( tab ), event ); + } + this._toggle( event, eventData ); + }, + + // handles show/hide for selecting tabs + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel; + + this.running = true; + + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } + + function show() { + eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); + + if ( toShow.length && that.options.show ) { + that._show( toShow, that.options.show, complete ); + } else { + toShow.show(); + complete(); + } + } + + // start out by hiding, then showing, then completing + if ( toHide.length && this.options.hide ) { + this._hide( toHide, this.options.hide, function() { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + show(); + }); + } else { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + toHide.hide(); + show(); + } + + toHide.attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + eventData.oldTab.attr( "aria-selected", "false" ); + // If we're switching tabs, remove the old tab from the tab order. + // If we're opening from collapsed state, remove the previous tab from the tab order. + // If we're collapsing, then keep the collapsing tab in the tab order. + if ( toShow.length && toHide.length ) { + eventData.oldTab.attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.tabs.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow.attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + eventData.newTab.attr({ + "aria-selected": "true", + tabIndex: 0 + }); + }, + + _activate: function( index ) { + var anchor, + active = this._findActive( index ); + + // trying to activate the already active panel + if ( active[ 0 ] === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the current active header + if ( !active.length ) { + active = this.active; + } + + anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; + this._eventHandler({ + target: anchor, + currentTarget: anchor, + preventDefault: $.noop + }); + }, + + _findActive: function( index ) { + return index === false ? $() : this.tabs.eq( index ); + }, + + _getIndex: function( index ) { + // meta-function to give users option to provide a href string instead of a numerical index. + if ( typeof index === "string" ) { + index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); + } + + return index; + }, + + _destroy: function() { + if ( this.xhr ) { + this.xhr.abort(); + } + + this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ); + + this.tablist + .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .removeAttr( "role" ); + + this.anchors + .removeClass( "ui-tabs-anchor" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeUniqueId(); + + this.tabs.add( this.panels ).each(function() { + if ( $.data( this, "ui-tabs-destroy" ) ) { + $( this ).remove(); + } else { + $( this ) + .removeClass( "ui-state-default ui-state-active ui-state-disabled " + + "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-live" ) + .removeAttr( "aria-busy" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "role" ); + } + }); + + this.tabs.each(function() { + var li = $( this ), + prev = li.data( "ui-tabs-aria-controls" ); + if ( prev ) { + li + .attr( "aria-controls", prev ) + .removeData( "ui-tabs-aria-controls" ); + } else { + li.removeAttr( "aria-controls" ); + } + }); + + this.panels.show(); + + if ( this.options.heightStyle !== "content" ) { + this.panels.css( "height", "" ); + } + }, + + enable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === false ) { + return; + } + + if ( index === undefined ) { + disabled = false; + } else { + index = this._getIndex( index ); + if ( $.isArray( disabled ) ) { + disabled = $.map( disabled, function( num ) { + return num !== index ? num : null; + }); + } else { + disabled = $.map( this.tabs, function( li, num ) { + return num !== index ? num : null; + }); + } + } + this._setupDisabled( disabled ); + }, + + disable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === true ) { + return; + } + + if ( index === undefined ) { + disabled = true; + } else { + index = this._getIndex( index ); + if ( $.inArray( index, disabled ) !== -1 ) { + return; + } + if ( $.isArray( disabled ) ) { + disabled = $.merge( [ index ], disabled ).sort(); + } else { + disabled = [ index ]; + } + } + this._setupDisabled( disabled ); + }, + + load: function( index, event ) { + index = this._getIndex( index ); + var that = this, + tab = this.tabs.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), + eventData = { + tab: tab, + panel: panel + }; + + // not remote + if ( isLocal( anchor[ 0 ] ) ) { + return; + } + + this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); + + // support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + tab.addClass( "ui-tabs-loading" ); + panel.attr( "aria-busy", "true" ); + + this.xhr + .success(function( response ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + panel.html( response ); + that._trigger( "load", event, eventData ); + }, 1 ); + }) + .complete(function( jqXHR, status ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + if ( status === "abort" ) { + that.panels.stop( false, true ); + } + + tab.removeClass( "ui-tabs-loading" ); + panel.removeAttr( "aria-busy" ); + + if ( jqXHR === that.xhr ) { + delete that.xhr; + } + }, 1 ); + }); + } + }, + + _ajaxSettings: function( anchor, event, eventData ) { + var that = this; + return { + url: anchor.attr( "href" ), + beforeSend: function( jqXHR, settings ) { + return that._trigger( "beforeLoad", event, + $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); + } + }; + }, + + _getPanelForTab: function( tab ) { + var id = $( tab ).attr( "aria-controls" ); + return this.element.find( this._sanitizeSelector( "#" + id ) ); + } +}); + +})( jQuery ); diff --git a/extensions/jui/assets/jquery.ui.tooltip.js b/extensions/jui/assets/jquery.ui.tooltip.js new file mode 100644 index 0000000..8ebf7b3 --- /dev/null +++ b/extensions/jui/assets/jquery.ui.tooltip.js @@ -0,0 +1,402 @@ +/*! + * jQuery UI Tooltip 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/tooltip/ + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function( $ ) { + +var increments = 0; + +function addDescribedBy( elem, id ) { + var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ); + describedby.push( id ); + elem + .data( "ui-tooltip-id", id ) + .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); +} + +function removeDescribedBy( elem ) { + var id = elem.data( "ui-tooltip-id" ), + describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ), + index = $.inArray( id, describedby ); + if ( index !== -1 ) { + describedby.splice( index, 1 ); + } + + elem.removeData( "ui-tooltip-id" ); + describedby = $.trim( describedby.join( " " ) ); + if ( describedby ) { + elem.attr( "aria-describedby", describedby ); + } else { + elem.removeAttr( "aria-describedby" ); + } +} + +$.widget( "ui.tooltip", { + version: "1.10.3", + options: { + content: function() { + // support: IE<9, Opera in jQuery <1.7 + // .text() can't accept undefined, so coerce to a string + var title = $( this ).attr( "title" ) || ""; + // Escape title, since we're going from an attribute to raw HTML + return $( "" ).text( title ).html(); + }, + hide: true, + // Disabled elements have inconsistent behavior across browsers (#8661) + items: "[title]:not([disabled])", + position: { + my: "left top+15", + at: "left bottom", + collision: "flipfit flip" + }, + show: true, + tooltipClass: null, + track: false, + + // callbacks + close: null, + open: null + }, + + _create: function() { + this._on({ + mouseover: "open", + focusin: "open" + }); + + // IDs of generated tooltips, needed for destroy + this.tooltips = {}; + // IDs of parent tooltips where we removed the title attribute + this.parents = {}; + + if ( this.options.disabled ) { + this._disable(); + } + }, + + _setOption: function( key, value ) { + var that = this; + + if ( key === "disabled" ) { + this[ value ? "_disable" : "_enable" ](); + this.options[ key ] = value; + // disable element style changes + return; + } + + this._super( key, value ); + + if ( key === "content" ) { + $.each( this.tooltips, function( id, element ) { + that._updateContent( element ); + }); + } + }, + + _disable: function() { + var that = this; + + // close open tooltips + $.each( this.tooltips, function( id, element ) { + var event = $.Event( "blur" ); + event.target = event.currentTarget = element[0]; + that.close( event, true ); + }); + + // remove title attributes to prevent native tooltips + this.element.find( this.options.items ).addBack().each(function() { + var element = $( this ); + if ( element.is( "[title]" ) ) { + element + .data( "ui-tooltip-title", element.attr( "title" ) ) + .attr( "title", "" ); + } + }); + }, + + _enable: function() { + // restore title attributes + this.element.find( this.options.items ).addBack().each(function() { + var element = $( this ); + if ( element.data( "ui-tooltip-title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + } + }); + }, + + open: function( event ) { + var that = this, + target = $( event ? event.target : this.element ) + // we need closest here due to mouseover bubbling, + // but always pointing at the same event target + .closest( this.options.items ); + + // No element to show a tooltip for or the tooltip is already open + if ( !target.length || target.data( "ui-tooltip-id" ) ) { + return; + } + + if ( target.attr( "title" ) ) { + target.data( "ui-tooltip-title", target.attr( "title" ) ); + } + + target.data( "ui-tooltip-open", true ); + + // kill parent tooltips, custom or native, for hover + if ( event && event.type === "mouseover" ) { + target.parents().each(function() { + var parent = $( this ), + blurEvent; + if ( parent.data( "ui-tooltip-open" ) ) { + blurEvent = $.Event( "blur" ); + blurEvent.target = blurEvent.currentTarget = this; + that.close( blurEvent, true ); + } + if ( parent.attr( "title" ) ) { + parent.uniqueId(); + that.parents[ this.id ] = { + element: this, + title: parent.attr( "title" ) + }; + parent.attr( "title", "" ); + } + }); + } + + this._updateContent( target, event ); + }, + + _updateContent: function( target, event ) { + var content, + contentOption = this.options.content, + that = this, + eventType = event ? event.type : null; + + if ( typeof contentOption === "string" ) { + return this._open( event, target, contentOption ); + } + + content = contentOption.call( target[0], function( response ) { + // ignore async response if tooltip was closed already + if ( !target.data( "ui-tooltip-open" ) ) { + return; + } + // IE may instantly serve a cached response for ajax requests + // delay this call to _open so the other call to _open runs first + that._delay(function() { + // jQuery creates a special event for focusin when it doesn't + // exist natively. To improve performance, the native event + // object is reused and the type is changed. Therefore, we can't + // rely on the type being correct after the event finished + // bubbling, so we set it back to the previous value. (#8740) + if ( event ) { + event.type = eventType; + } + this._open( event, target, response ); + }); + }); + if ( content ) { + this._open( event, target, content ); + } + }, + + _open: function( event, target, content ) { + var tooltip, events, delayedShow, + positionOption = $.extend( {}, this.options.position ); + + if ( !content ) { + return; + } + + // Content can be updated multiple times. If the tooltip already + // exists, then just update the content and bail. + tooltip = this._find( target ); + if ( tooltip.length ) { + tooltip.find( ".ui-tooltip-content" ).html( content ); + return; + } + + // if we have a title, clear it to prevent the native tooltip + // we have to check first to avoid defining a title if none exists + // (we don't want to cause an element to start matching [title]) + // + // We use removeAttr only for key events, to allow IE to export the correct + // accessible attributes. For mouse events, set to empty string to avoid + // native tooltip showing up (happens only when removing inside mouseover). + if ( target.is( "[title]" ) ) { + if ( event && event.type === "mouseover" ) { + target.attr( "title", "" ); + } else { + target.removeAttr( "title" ); + } + } + + tooltip = this._tooltip( target ); + addDescribedBy( target, tooltip.attr( "id" ) ); + tooltip.find( ".ui-tooltip-content" ).html( content ); + + function position( event ) { + positionOption.of = event; + if ( tooltip.is( ":hidden" ) ) { + return; + } + tooltip.position( positionOption ); + } + if ( this.options.track && event && /^mouse/.test( event.type ) ) { + this._on( this.document, { + mousemove: position + }); + // trigger once to override element-relative positioning + position( event ); + } else { + tooltip.position( $.extend({ + of: target + }, this.options.position ) ); + } + + tooltip.hide(); + + this._show( tooltip, this.options.show ); + // Handle tracking tooltips that are shown with a delay (#8644). As soon + // as the tooltip is visible, position the tooltip using the most recent + // event. + if ( this.options.show && this.options.show.delay ) { + delayedShow = this.delayedShow = setInterval(function() { + if ( tooltip.is( ":visible" ) ) { + position( positionOption.of ); + clearInterval( delayedShow ); + } + }, $.fx.interval ); + } + + this._trigger( "open", event, { tooltip: tooltip } ); + + events = { + keyup: function( event ) { + if ( event.keyCode === $.ui.keyCode.ESCAPE ) { + var fakeEvent = $.Event(event); + fakeEvent.currentTarget = target[0]; + this.close( fakeEvent, true ); + } + }, + remove: function() { + this._removeTooltip( tooltip ); + } + }; + if ( !event || event.type === "mouseover" ) { + events.mouseleave = "close"; + } + if ( !event || event.type === "focusin" ) { + events.focusout = "close"; + } + this._on( true, target, events ); + }, + + close: function( event ) { + var that = this, + target = $( event ? event.currentTarget : this.element ), + tooltip = this._find( target ); + + // disabling closes the tooltip, so we need to track when we're closing + // to avoid an infinite loop in case the tooltip becomes disabled on close + if ( this.closing ) { + return; + } + + // Clear the interval for delayed tracking tooltips + clearInterval( this.delayedShow ); + + // only set title if we had one before (see comment in _open()) + if ( target.data( "ui-tooltip-title" ) ) { + target.attr( "title", target.data( "ui-tooltip-title" ) ); + } + + removeDescribedBy( target ); + + tooltip.stop( true ); + this._hide( tooltip, this.options.hide, function() { + that._removeTooltip( $( this ) ); + }); + + target.removeData( "ui-tooltip-open" ); + this._off( target, "mouseleave focusout keyup" ); + // Remove 'remove' binding only on delegated targets + if ( target[0] !== this.element[0] ) { + this._off( target, "remove" ); + } + this._off( this.document, "mousemove" ); + + if ( event && event.type === "mouseleave" ) { + $.each( this.parents, function( id, parent ) { + $( parent.element ).attr( "title", parent.title ); + delete that.parents[ id ]; + }); + } + + this.closing = true; + this._trigger( "close", event, { tooltip: tooltip } ); + this.closing = false; + }, + + _tooltip: function( element ) { + var id = "ui-tooltip-" + increments++, + tooltip = $( "
      " ) + .attr({ + id: id, + role: "tooltip" + }) + .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " + + ( this.options.tooltipClass || "" ) ); + $( "
      " ) + .addClass( "ui-tooltip-content" ) + .appendTo( tooltip ); + tooltip.appendTo( this.document[0].body ); + this.tooltips[ id ] = element; + return tooltip; + }, + + _find: function( target ) { + var id = target.data( "ui-tooltip-id" ); + return id ? $( "#" + id ) : $(); + }, + + _removeTooltip: function( tooltip ) { + tooltip.remove(); + delete this.tooltips[ tooltip.attr( "id" ) ]; + }, + + _destroy: function() { + var that = this; + + // close open tooltips + $.each( this.tooltips, function( id, element ) { + // Delegate to close method to handle common cleanup + var event = $.Event( "blur" ); + event.target = event.currentTarget = element[0]; + that.close( event, true ); + + // Remove immediately; destroying an open tooltip doesn't use the + // hide animation + $( "#" + id ).remove(); + + // Restore the title + if ( element.data( "ui-tooltip-title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + element.removeData( "ui-tooltip-title" ); + } + }); + } +}); + +}( jQuery ) ); diff --git a/extensions/jui/assets/jquery.ui.widget.js b/extensions/jui/assets/jquery.ui.widget.js new file mode 100644 index 0000000..916a6ad --- /dev/null +++ b/extensions/jui/assets/jquery.ui.widget.js @@ -0,0 +1,521 @@ +/*! + * jQuery UI Widget 1.10.3 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ +(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
      ", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})( jQuery ); diff --git a/extensions/jui/assets/theme/images/animated-overlay.gif b/extensions/jui/assets/theme/images/animated-overlay.gif new file mode 100755 index 0000000000000000000000000000000000000000..d441f75ebfbdf26a265dfccd670120d25c0a341c GIT binary patch literal 1738 zcmZ|OX;ji_6b5ixNYt8>l?gOuO)6lU%W(mxn(`>1S(XO;u`D+P%xqBvMr|w-Vyr1s z7R|Cn0b8|Hu<=Zmv1mFqh9Fj!NuZfKB2MP$e75`XJ@>=!y!Ux9xR3x;EW!q1^V>X| znVFuRUN`NqJ2)ybXh%e__h!!pv(M|S3+?9F%(K}zyE40MGyhWF5-IDgL&=%2-9`Nk z!1@8uk4t%_{(K~>N;sK&dzJbwJ=$kYTlL=$%#0Pfh>U{%i@~wWbvYsD_K-D`&+u1( z#Ma`>%q<^UhzGvi(hyE`zCD{-=2|zL5>wnB=DE!U?(CZG%q4@lDnCq_%&3DCla#(X zmBhDD+RN$aMWWHm?ig*>1Onn6~r?Ma~N2JKAxN>H%UtRyRqS)6Um!-Tz%-r=& zQmTb^JFIe3W^-kAm`}`2P|niMh>RYyd)S^f(dbrx965?rzbhP|XeP}o&&DSZ4|oYQ z)I{f!SfycYw?3=9W;o-B%U5xs(pP267X~9-7L|4WzaYexC0GtG8wWygm63rF{llCEraxzkc=IxvFQ-y37=_;e5 zJLq^gsSO0Ayz?a>E_?{dmUc+t#qv$)XN8$<<}rQ#)lsiw+pmL&J>~+hgpo>i$m+;l zZIa_ZRIfSeT$~v5d`EBV&*k`apPgjv&B|+d`Q!nyu{L4rs%ZfoF0*Kq8I%ByOcFpL zK=>wzofZo<+0GZLCnWM3oQ^pb(gRSf02;~cEn@LJ>~XB9IkEX{$N#Z`m%>S!U{uPx zloI%bLdo$Adxlh(Uv^yX7s5G&C zLwNRG>~T?G{kzupp8EcyLGPoPf)@&9Wqfw_l&uU-6cexk%5;uQg%wb=0k_733{i#& z1a2p)gV3S2+QG1-K9tZ}E~I<(P0r2aFFY-c{o?TUOz3Xjod#TLE2A_c?*T7t z=1>~%YW450{Qqno4t`}gvLnuMrcu8+#xEBoY%2_+Mb#Z6S38+r*M4O`-+!zl(@m`D zQsi|GA2l3gEy}LFe<#Hv8?$_L#u8E|3-bP$*La*E>B{X!Sy4i6?TKam!49aXCAW4S*P_O^H4^*DpiA40o}Uqw~Eo&veh1`|8i zD2$x+>_b^bXE4N;AW=5>iYak2%!JAh0j1*k1{p#iRCjbB7!cSws~U{1IA@acLII$t z$>X#A+^s6iJ5~DFG!xa?>z{=lxtdi1rzbM-(nqAu3D8h-&64xo6|E!p?pK0xT;qoK z`6%+SpBk+~M?nO}>2mTw!A{yZ6O>Z@kwSd4;8aWU5z!P~tQl?u==^+R`{OmOS}oZh zOXQ3{6kuz?Is^n^L7;9ieB9C+8B{>t+pDrlq4xGDDn#T#3T5$l1g`FTQkU;b-981j zNm{zC`$wn7etklM#qHI4=3m5gwa6DNS{?Z!vSObi_od{4eUo=_S2BKNpkSdiqe(k9WtkeM79;2-%CFbb)aB=&H1?i1}uwFzoZQ(38Kn1zBP ORn*B%u*Wk|4g3!*Rv{Mv literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png b/extensions/jui/assets/theme/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..414803b7ec93c3453bdab0fca64e190b4691fee0 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F1SA+{?>A)!QcOwS?k)_>#w|r1Kptm-M`SUO z_5fqIli7AahM1>|V~EA+ zRdP`(kYX@0Ff`URFxNFS2{E*^GBvd_G6!<43=E_lCY(pnkei>9nO2EgLzb)t$UzLA Lu6{1-oD!M?)F zK#IZ0z|dINz+BhRB*f6t%GA`#$P~!6GBD`5-}nqgLvDUbW?Cg~4S$;~qktM1JYD@< J);T3K0RS5eHEaL? literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..6b782783576412dcfe013c1b8c6c98b6a94ad2c5 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCnc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12TF&T^vI^j=w#x$i?I+((tf;UXnmgbH|3oY>pC!)f}(GR!16S-u+#{ ze6YEqRkW=8vGl=5qArKM<9}TC-}iEvB{zdaTcX5$wyRTK&AL@}ZVjiMnpKP5A*61RpkS<1(O8W=oX{an^LB{Ts5Oy6~_ literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..b706fba1a6af55ef043aea9ef946c3b290a78ccd GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI2NH8$CE1Q=ADW)WEcNYf6yIsOdfIQ9ukH}&m z?E%JaC$sH<3_(v9#}JM4$r%X}t&0mp9yKsb%{ak$*<@oTP*SzTHKHUXu_Vl&Jb7+PAHnpzo|0=ZTO20iy1pP^{T%}>cptHiD0ZUftDnm{ Hr-UW|IBzs4 literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_75_dadada_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..b650a7ce4f09db28cfe807a36973e74b2851956e GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z#PD=+46!(!T=8puqDZgOs>RXUCGx5b?-VBQkUm|IuXOmYJrBRJgj{Vx zMbNnqUkncy+qa2-mWYc>swkcIuvGK#>(0d)B7)5f`@$Ei28nH~0h*~=;u=wsl30>z zm0Xkxq!^4042^XS%ykV-LJTdfOiitf%z#`g1B1Qhv#U`wmdKI;Vst0ALzUw*UYD literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..e70ab8f590c38e26ff192bba1a3fcf573f11a55a GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&0LWmFTHNUZq?nSt-Ch3w7g=q17Rci)@Q5r1 z(jH*!b~4)z#PD=+46!(!TrvH)L6@80)r*_cdCvDr%)6ghVL16=s@mbz7H!uRdGeDa z?kzLg)16i!f8fKx84s0>4AzwsH0hTQy=%(P0}8vZs}MgcW2 Nc)I$ztaD0e0s!1VPkjIY literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png b/extensions/jui/assets/theme/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..6e86fb1f5ea55ce8639f6af349d1963d1a6dbe9a GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^j6gI&fCnc6a#?2AmP!?*K(O3p^r= zfwTu0yPeFo12VciT^vI^j=w#>k(V)1qW$CZ|6)SVV-&*#dav<$DMuV&n0Dbpw@al&Jb7+PAHnpzo|0l8KN27AqC fSEFdi%}>cptHiD0OqTL-paup{S3j3^P6E%W-S*bfB&J`pw9sa4-R?IGW?p~6`>jMSP&M+u3 zY@9al)zrvpHlQu4C9V-ADTyViR>?)FK#IZ0z|dINz+BhRB*f6t%GA`#$PCD}GBDU{ hKD!!4LvDUbW?Cg~4QH~Hj{`L@c)I$ztaD0e0suAGTAly^ literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-icons_222222_256x240.png b/extensions/jui/assets/theme/images/ui-icons_222222_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..c1cb1170c8b3795835b8831ab81fa9ae63b606b1 GIT binary patch literal 6922 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 QJ~;stWz}Trq%1=J3#jBGg8%>k literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png b/extensions/jui/assets/theme/images/ui-icons_2e83ff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..84b601bf0f726bf95801da487deaf2344a32e4b8 GIT binary patch literal 4549 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 F{{S81aohj^ literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-icons_454545_256x240.png b/extensions/jui/assets/theme/images/ui-icons_454545_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..b6db1acdd433be80a472b045018f25c7f2cf7e08 GIT binary patch literal 6992 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 literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png b/extensions/jui/assets/theme/images/ui-icons_cd0a0a_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..ed5b6b0930f672fa08e9b9bdbe5e55370fd1dc30 GIT binary patch literal 4549 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 DTfr$v literal 0 HcmV?d00001 diff --git a/extensions/jui/assets/theme/jquery.ui.css b/extensions/jui/assets/theme/jquery.ui.css new file mode 100755 index 0000000..105197e --- /dev/null +++ b/extensions/jui/assets/theme/jquery.ui.css @@ -0,0 +1,1177 @@ +/*! jQuery UI - v1.10.3 - 2013-07-18 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-clearfix { + min-height: 0; /* support: IE7 */ +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin-top: 2px; + padding: .5em .5em .5em .7em; + min-height: 0; /* support: IE7 */ +} +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-noicons { + padding-left: .7em; +} +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-button { + display: inline-block; + position: relative; + padding: 0; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: visible; /* removes extra width in IE */ +} +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.ui-button-icons-only { + width: 3.4em; +} +button.ui-button-icons-only { + width: 3.7em; +} + +/* button text element */ +.ui-button .ui-button-text { + display: block; + line-height: normal; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/* button sets */ +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +/* reset extra padding in Firefox, see h5bp.com/l */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month-year { + width: 100%; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 49%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} +.ui-dialog { + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 21px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-se { + width: 12px; + height: 12px; + right: -5px; + bottom: -5px; + background-position: 16px 16px; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-menu { + list-style: none; + padding: 2px; + margin: 0; + display: block; + outline: none; +} +.ui-menu .ui-menu { + margin-top: -3px; + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + padding: 0; + width: 100%; + /* support: IE10, see #8844 */ + list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); +} +.ui-menu .ui-menu-divider { + margin: 5px -2px 5px -2px; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-menu-item a { + text-decoration: none; + display: block; + padding: 2px .4em; + line-height: 1.5; + min-height: 0; /* support: IE7 */ + font-weight: normal; +} +.ui-menu .ui-menu-item a.ui-state-focus, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} + +.ui-menu .ui-state-disabled { + font-weight: normal; + margin: .4em 0 .2em; + line-height: 1.5; +} +.ui-menu .ui-state-disabled a { + cursor: default; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item a { + position: relative; + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: .2em; + left: .2em; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + position: static; + float: right; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("images/animated-overlay.gif"); + height: 100%; + filter: alpha(opacity=25); + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* For IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to overide default borders */ +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} +/* vertical centre icon */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav li a { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active a, +.ui-tabs .ui-tabs-nav li.ui-state-disabled a, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading a { + cursor: text; +} +.ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + border: 1px solid #d3d3d3; + background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus { + border: 1px solid #999999; + background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited { + color: #212121; + text-decoration: none; +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #aaaaaa; + background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; + color: #363636; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* For IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url(images/ui-icons_222222_256x240.png); +} +.ui-widget-header .ui-icon { + background-image: url(images/ui-icons_222222_256x240.png); +} +.ui-state-default .ui-icon { + background-image: url(images/ui-icons_888888_256x240.png); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon { + background-image: url(images/ui-icons_454545_256x240.png); +} +.ui-state-active .ui-icon { + background-image: url(images/ui-icons_454545_256x240.png); +} +.ui-state-highlight .ui-icon { + background-image: url(images/ui-icons_2e83ff_256x240.png); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url(images/ui-icons_cd0a0a_256x240.png); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); +} +.ui-widget-shadow { + margin: -8px 0 0 -8px; + padding: 8px; + background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); + border-radius: 8px; +} diff --git a/extensions/jui/composer.json b/extensions/jui/composer.json index e7dab28..89987db 100644 --- a/extensions/jui/composer.json +++ b/extensions/jui/composer.json @@ -18,6 +18,7 @@ "autoload": { "psr-0": { "yii\\jui\\": "" } }, + "target-dir": "yii/jui", "extra": { "bootstrap": "yii\\jui\\Extension" } diff --git a/extensions/jui/yii/jui/Accordion.php b/extensions/jui/yii/jui/Accordion.php deleted file mode 100644 index 42897a9..0000000 --- a/extensions/jui/yii/jui/Accordion.php +++ /dev/null @@ -1,121 +0,0 @@ - [ - * [ - * 'header' => 'Section 1', - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * ], - * [ - * 'header' => 'Section 2', - * 'headerOptions' => ['tag' => 'h3'], - * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => ['tag' => 'div'], - * ], - * ], - * 'options' => ['tag' => 'div'], - * 'itemOptions' => ['tag' => 'div'], - * 'headerOptions' => ['tag' => 'h3'], - * 'clientOptions' => ['collapsible' => false], - * ]); - * ``` - * - * @see http://api.jqueryui.com/accordion/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Accordion 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 = []; - /** - * @var array list of collapsible items. Each item can be an array of the following structure: - * - * ~~~ - * [ - * 'header' => 'Item header', - * 'content' => 'Item content', - * // the HTML attributes of the item header container tag. This will overwrite "headerOptions". - * 'headerOptions' => [], - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @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 = []; - /** - * @var array list of HTML attributes for the item header container tags. This will be overwritten - * by the "headerOptions" set in individual [[items]]. The following special options are recognized: - * - * - tag: string, defaults to "h3", the tag name of the item container tags. - */ - public $headerOptions = []; - - - /** - * Renders the widget. - */ - public function run() - { - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'div'); - echo Html::beginTag($tag, $options) . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag($tag) . "\n"; - $this->registerWidget('accordion', AccordionAsset::className()); - } - - /** - * Renders collapsible items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - protected function renderItems() - { - $items = []; - foreach ($this->items as $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."); - } - $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); - $headerTag = ArrayHelper::remove($headerOptions, 'tag', 'h3'); - $items[] = Html::tag($headerTag, $item['header'], $headerOptions); - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); - $tag = ArrayHelper::remove($options, 'tag', 'div'); - $items[] = Html::tag($tag, $item['content'], $options); - } - - return implode("\n", $items); - } -} diff --git a/extensions/jui/yii/jui/AccordionAsset.php b/extensions/jui/yii/jui/AccordionAsset.php deleted file mode 100644 index 05c1e20..0000000 --- a/extensions/jui/yii/jui/AccordionAsset.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @since 2.0 - */ -class AccordionAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.accordion.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\EffectAsset', - ]; -} diff --git a/extensions/jui/yii/jui/AutoComplete.php b/extensions/jui/yii/jui/AutoComplete.php deleted file mode 100644 index ac0c997..0000000 --- a/extensions/jui/yii/jui/AutoComplete.php +++ /dev/null @@ -1,66 +0,0 @@ - $model, - * 'attribute' => 'country', - * 'clientOptions' => [ - * 'source' => ['USA', 'RUS'], - * ], - * ]); - * ``` - * - * The following example will use the name property instead: - * - * ```php - * echo AutoComplete::widget([ - * 'name' => 'country', - * 'clientOptions' => [ - * 'source' => ['USA', 'RUS'], - * ], - * ]); - *``` - * - * @see http://api.jqueryui.com/autocomplete/ - * @author Alexander Kochetov - * @since 2.0 - */ -class AutoComplete extends InputWidget -{ - /** - * Renders the widget. - */ - public function run() - { - echo $this->renderWidget(); - $this->registerWidget('autocomplete', AutoCompleteAsset::className()); - } - - /** - * Renders the AutoComplete 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); - } - } -} diff --git a/extensions/jui/yii/jui/AutoCompleteAsset.php b/extensions/jui/yii/jui/AutoCompleteAsset.php deleted file mode 100644 index f48e064..0000000 --- a/extensions/jui/yii/jui/AutoCompleteAsset.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class AutoCompleteAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.autocomplete.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\MenuAsset', - ]; -} diff --git a/extensions/jui/yii/jui/ButtonAsset.php b/extensions/jui/yii/jui/ButtonAsset.php deleted file mode 100644 index 6616b34..0000000 --- a/extensions/jui/yii/jui/ButtonAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class ButtonAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.button.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/CoreAsset.php b/extensions/jui/yii/jui/CoreAsset.php deleted file mode 100644 index d77a25f..0000000 --- a/extensions/jui/yii/jui/CoreAsset.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @since 2.0 - */ -class CoreAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.core.js', - 'jquery.ui.widget.js', - 'jquery.ui.position.js', - 'jquery.ui.mouse.js', - ]; - public $depends = [ - 'yii\web\JqueryAsset', - ]; -} diff --git a/extensions/jui/yii/jui/DatePicker.php b/extensions/jui/yii/jui/DatePicker.php deleted file mode 100644 index 06ca356..0000000 --- a/extensions/jui/yii/jui/DatePicker.php +++ /dev/null @@ -1,110 +0,0 @@ - 'ru', - * 'model' => $model, - * 'attribute' => 'country', - * 'clientOptions' => [ - * 'dateFormat' => 'yy-mm-dd', - * ], - * ]); - * ``` - * - * The following example will use the name property instead: - * - * ```php - * echo DatePicker::widget([ - * 'language' => 'ru', - * 'name' => 'country', - * 'clientOptions' => [ - * 'dateFormat' => 'yy-mm-dd', - * ], - * ]); - *``` - * - * @see http://api.jqueryui.com/datepicker/ - * @author Alexander Kochetov - * @since 2.0 - */ -class DatePicker extends InputWidget -{ - /** - * @var string the locale ID (eg 'fr', 'de') for the language to be used by the date picker. - * If this property set to false, I18N will not be involved. That is, the date picker will show in English. - */ - public $language = false; - /** - * @var boolean If true, shows the widget as an inline calendar and the input as a hidden field. - */ - public $inline = false; - - - /** - * Renders the widget. - */ - public function run() - { - echo $this->renderWidget() . "\n"; - if ($this->language !== false) { - $view = $this->getView(); - DatePickerRegionalAsset::register($view); - - $options = Json::encode($this->clientOptions); - $view->registerJs("$('#{$this->options['id']}').datepicker($.extend({}, $.datepicker.regional['{$this->language}'], $options));"); - - $options = $this->clientOptions; - $this->clientOptions = false; // the datepicker js widget is already registered - $this->registerWidget('datepicker', DatePickerAsset::className()); - $this->clientOptions = $options; - } else { - $this->registerWidget('datepicker', DatePickerAsset::className()); - } - } - - /** - * Renders the DatePicker widget. - * @return string the rendering result. - */ - protected function renderWidget() - { - $contents = []; - - if ($this->inline === false) { - if ($this->hasModel()) { - $contents[] = Html::activeTextInput($this->model, $this->attribute, $this->options); - } else { - $contents[] = Html::textInput($this->name, $this->value, $this->options); - } - } else { - if ($this->hasModel()) { - $contents[] = Html::activeHiddenInput($this->model, $this->attribute, $this->options); - $this->clientOptions['defaultDate'] = $this->model->{$this->attribute}; - } else { - $contents[] = Html::hiddenInput($this->name, $this->value, $this->options); - $this->clientOptions['defaultDate'] = $this->value; - } - $this->clientOptions['altField'] = '#' . $this->options['id']; - $this->options['id'] .= '-container'; - $contents[] = Html::tag('div', null, $this->options); - } - - return implode("\n", $contents); - } -} diff --git a/extensions/jui/yii/jui/DatePickerAsset.php b/extensions/jui/yii/jui/DatePickerAsset.php deleted file mode 100644 index fddd8df..0000000 --- a/extensions/jui/yii/jui/DatePickerAsset.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class DatePickerAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.datepicker.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\EffectAsset', - ]; -} diff --git a/extensions/jui/yii/jui/DatePickerRegionalAsset.php b/extensions/jui/yii/jui/DatePickerRegionalAsset.php deleted file mode 100644 index 249373a..0000000 --- a/extensions/jui/yii/jui/DatePickerRegionalAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class DatePickerRegionalAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.datepicker-i18n.js', - ]; - public $depends = [ - 'yii\jui\DatePickerAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Dialog.php b/extensions/jui/yii/jui/Dialog.php deleted file mode 100644 index a5cbaf2..0000000 --- a/extensions/jui/yii/jui/Dialog.php +++ /dev/null @@ -1,52 +0,0 @@ - [ - * 'modal' => true, - * ], - * ]); - * - * echo 'Dialog contents here...'; - * - * Dialog::end(); - * ``` - * - * @see http://api.jqueryui.com/dialog/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Dialog 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('dialog', DialogAsset::className()); - } -} diff --git a/extensions/jui/yii/jui/DialogAsset.php b/extensions/jui/yii/jui/DialogAsset.php deleted file mode 100644 index 109243e..0000000 --- a/extensions/jui/yii/jui/DialogAsset.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @since 2.0 - */ -class DialogAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.dialog.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\ButtonAsset', - 'yii\jui\DraggableAsset', - 'yii\jui\ResizableAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Draggable.php b/extensions/jui/yii/jui/Draggable.php deleted file mode 100644 index 02e4973..0000000 --- a/extensions/jui/yii/jui/Draggable.php +++ /dev/null @@ -1,50 +0,0 @@ - ['grid' => [50, 20]], - * ]); - * - * 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', DraggableAsset::className()); - } -} diff --git a/extensions/jui/yii/jui/DraggableAsset.php b/extensions/jui/yii/jui/DraggableAsset.php deleted file mode 100644 index f3286a5..0000000 --- a/extensions/jui/yii/jui/DraggableAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class DraggableAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.draggable.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Droppable.php b/extensions/jui/yii/jui/Droppable.php deleted file mode 100644 index 530e736..0000000 --- a/extensions/jui/yii/jui/Droppable.php +++ /dev/null @@ -1,50 +0,0 @@ - ['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', DroppableAsset::className()); - } -} diff --git a/extensions/jui/yii/jui/DroppableAsset.php b/extensions/jui/yii/jui/DroppableAsset.php deleted file mode 100644 index 84b64b8..0000000 --- a/extensions/jui/yii/jui/DroppableAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class DroppableAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.droppable.js', - ]; - public $depends = [ - 'yii\jui\DraggableAsset', - ]; -} diff --git a/extensions/jui/yii/jui/EffectAsset.php b/extensions/jui/yii/jui/EffectAsset.php deleted file mode 100644 index 79c5aaa..0000000 --- a/extensions/jui/yii/jui/EffectAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class EffectAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.effect-all.js', - ]; - public $depends = [ - 'yii\web\JqueryAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Extension.php b/extensions/jui/yii/jui/Extension.php deleted file mode 100644 index 4b680ce..0000000 --- a/extensions/jui/yii/jui/Extension.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @since 2.0 - */ -class Extension extends \yii\base\Extension -{ - /** - * @inheritdoc - */ - public static function init() - { - Yii::setAlias('@yii/jui', __DIR__); - } -} diff --git a/extensions/jui/yii/jui/InputWidget.php b/extensions/jui/yii/jui/InputWidget.php deleted file mode 100644 index e100d6c..0000000 --- a/extensions/jui/yii/jui/InputWidget.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @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/extensions/jui/yii/jui/Menu.php b/extensions/jui/yii/jui/Menu.php deleted file mode 100644 index 46c7ee0..0000000 --- a/extensions/jui/yii/jui/Menu.php +++ /dev/null @@ -1,78 +0,0 @@ - - * @since 2.0 - */ -class Menu extends \yii\widgets\Menu -{ - /** - * @var array the options for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible options. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported options (e.g. "header"). - */ - public $clientOptions = []; - /** - * @var array the event handlers for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible events. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported events (e.g. "create"). - */ - public $clientEvents = []; - - - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - */ - public function init() - { - parent::init(); - if (!isset($this->options['id'])) { - $this->options['id'] = $this->getId(); - } - } - - /** - * Renders the widget. - */ - public function run() - { - parent::run(); - - $view = $this->getView(); - MenuAsset::register($view); - /** @var \yii\web\AssetBundle $themeAsset */ - $themeAsset = Widget::$theme; - $themeAsset::register($view); - - $id = $this->options['id']; - if ($this->clientOptions !== false) { - $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); - $js = "jQuery('#$id').menu($options);"; - $view->registerJs($js); - } - - if (!empty($this->clientEvents)) { - $js = []; - foreach ($this->clientEvents as $event => $handler) { - $js[] = "jQuery('#$id').on('menu$event', $handler);"; - } - $view->registerJs(implode("\n", $js)); - } - } -} diff --git a/extensions/jui/yii/jui/MenuAsset.php b/extensions/jui/yii/jui/MenuAsset.php deleted file mode 100644 index 8b840a8..0000000 --- a/extensions/jui/yii/jui/MenuAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class MenuAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.menu.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/ProgressBar.php b/extensions/jui/yii/jui/ProgressBar.php deleted file mode 100644 index 1c555f1..0000000 --- a/extensions/jui/yii/jui/ProgressBar.php +++ /dev/null @@ -1,60 +0,0 @@ - [ - * 'value' => 75, - * ], - * ]); - * ``` - * - * The following example will show the content enclosed between the [[begin()]] - * and [[end()]] calls within the widget container: - * - * ~~~php - * ProgressBar::widget([ - * 'clientOptions' => ['value' => 75], - * ]); - * - * echo '
      Loading...
      '; - * - * ProgressBar::end(); - * ~~~ - * @see http://api.jqueryui.com/progressbar/ - * @author Alexander Kochetov - * @since 2.0 - */ -class ProgressBar 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('progressbar', ProgressBarAsset::className()); - } -} diff --git a/extensions/jui/yii/jui/ProgressBarAsset.php b/extensions/jui/yii/jui/ProgressBarAsset.php deleted file mode 100644 index d485fbd..0000000 --- a/extensions/jui/yii/jui/ProgressBarAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class ProgressBarAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.progressbar.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Resizable.php b/extensions/jui/yii/jui/Resizable.php deleted file mode 100644 index bcff9a8..0000000 --- a/extensions/jui/yii/jui/Resizable.php +++ /dev/null @@ -1,52 +0,0 @@ - [ - * 'grid' => [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', ResizableAsset::className()); - } -} diff --git a/extensions/jui/yii/jui/ResizableAsset.php b/extensions/jui/yii/jui/ResizableAsset.php deleted file mode 100644 index acf4c73..0000000 --- a/extensions/jui/yii/jui/ResizableAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class ResizableAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.resizable.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Selectable.php b/extensions/jui/yii/jui/Selectable.php deleted file mode 100644 index 94e4faf..0000000 --- a/extensions/jui/yii/jui/Selectable.php +++ /dev/null @@ -1,116 +0,0 @@ - [ - * 'Item 1', - * [ - * 'content' => 'Item2', - * ], - * [ - * 'content' => 'Item3', - * 'options' => [ - * 'tag' => 'li', - * ], - * ], - * ), - * 'options' => [ - * 'tag' => 'ul', - * ], - * 'itemOptions' => [ - * 'tag' => 'li', - * ], - * 'clientOptions' => [ - * '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 = []; - /** - * @var array list of selectable items. Each item can be a string representing the item content - * or an array of the following structure: - * - * ~~~ - * [ - * 'content' => 'item content', - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @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 = []; - - - /** - * 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', SelectableAsset::className()); - } - - /** - * Renders selectable items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - public function renderItems() - { - $items = []; - 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', [])); - $tag = ArrayHelper::remove($options, 'tag', $tag); - $items[] = Html::tag($tag, $item['content'], $options); - } else { - $items[] = Html::tag($tag, $item, $options); - } - } - return implode("\n", $items); - } -} diff --git a/extensions/jui/yii/jui/SelectableAsset.php b/extensions/jui/yii/jui/SelectableAsset.php deleted file mode 100644 index 61f405f..0000000 --- a/extensions/jui/yii/jui/SelectableAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class SelectableAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.selectable.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Slider.php b/extensions/jui/yii/jui/Slider.php deleted file mode 100644 index c19f2db..0000000 --- a/extensions/jui/yii/jui/Slider.php +++ /dev/null @@ -1,68 +0,0 @@ - $model, - * 'attribute' => 'amount', - * 'clientOptions' => [ - * 'min' => 1, - * 'max' => 10, - * ], - * ]); - * ``` - * - * The following example will use the name property instead: - * - * ```php - * echo Slider::widget([ - * 'name' => 'amount', - * 'clientOptions' => [ - * 'min' => 1, - * 'max' => 10, - * ], - * ]); - *``` - * - * @see http://api.jqueryui.com/slider/ - * @author Alexander Makarov - * @since 2.0 - */ -class Slider extends InputWidget -{ - /** - * Renders the widget. - */ - public function run() - { - echo $this->renderWidget(); - $this->registerWidget('slider', SliderAsset::className()); - } - - /** - * Renders the Slider 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); - } - } -} diff --git a/extensions/jui/yii/jui/SliderAsset.php b/extensions/jui/yii/jui/SliderAsset.php deleted file mode 100644 index 56c2451..0000000 --- a/extensions/jui/yii/jui/SliderAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class SliderAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.slider.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Sortable.php b/extensions/jui/yii/jui/Sortable.php deleted file mode 100644 index 6209cb6..0000000 --- a/extensions/jui/yii/jui/Sortable.php +++ /dev/null @@ -1,106 +0,0 @@ - [ - * 'Item 1', - * ['content' => 'Item2'], - * [ - * 'content' => 'Item3', - * 'options' => ['tag' => 'li'], - * ], - * ], - * 'options' => ['tag' => 'ul'], - * 'itemOptions' => ['tag' => 'li'], - * 'clientOptions' => ['cursor' => 'move'], - * )); - * ``` - * - * @see http://api.jqueryui.com/sortable/ - * @author Alexander Kochetov - * @since 2.0 - */ -class Sortable 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 = []; - /** - * @var array list of sortable items. Each item can be a string representing the item content - * or an array of the following structure: - * - * ~~~ - * [ - * 'content' => 'item content', - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => [], - * ] - * ~~~ - */ - public $items = []; - /** - * @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 = []; - - - /** - * 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('sortable', SortableAsset::className()); - } - - /** - * Renders sortable items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - public function renderItems() - { - $items = []; - 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', [])); - $tag = ArrayHelper::remove($options, 'tag', $tag); - $items[] = Html::tag($tag, $item['content'], $options); - } else { - $items[] = Html::tag($tag, $item, $options); - } - } - return implode("\n", $items); - } -} diff --git a/extensions/jui/yii/jui/SortableAsset.php b/extensions/jui/yii/jui/SortableAsset.php deleted file mode 100644 index 69c9ba3..0000000 --- a/extensions/jui/yii/jui/SortableAsset.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @since 2.0 - */ -class SortableAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.sortable.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Spinner.php b/extensions/jui/yii/jui/Spinner.php deleted file mode 100644 index caf73f3..0000000 --- a/extensions/jui/yii/jui/Spinner.php +++ /dev/null @@ -1,62 +0,0 @@ - $model, - * 'attribute' => 'country', - * 'clientOptions' => ['step' => 2], - * ]); - * ``` - * - * The following example will use the name property instead: - * - * ```php - * echo Spinner::widget([ - * 'name' => 'country', - * 'clientOptions' => ['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', SpinnerAsset::className()); - } - - /** - * 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); - } - } -} diff --git a/extensions/jui/yii/jui/SpinnerAsset.php b/extensions/jui/yii/jui/SpinnerAsset.php deleted file mode 100644 index 89a8c59..0000000 --- a/extensions/jui/yii/jui/SpinnerAsset.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class SpinnerAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.spinner.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\ButtonAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Tabs.php b/extensions/jui/yii/jui/Tabs.php deleted file mode 100644 index 9d2a2be..0000000 --- a/extensions/jui/yii/jui/Tabs.php +++ /dev/null @@ -1,145 +0,0 @@ - [ - * [ - * 'label' => 'Tab one', - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * ], - * [ - * 'label' => 'Tab two', - * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => ['tag' => 'div'], - * 'headerOptions' => ['class' => 'my-class'], - * ], - * [ - * 'label' => 'Tab with custom id', - * 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...', - * 'options' => ['id' => 'my-tab'], - * ], - * [ - * 'label' => 'Ajax tab', - * 'url' => ['ajax/content'], - * ], - * ), - * 'options' => ['tag' => 'div'], - * 'itemOptions' => ['tag' => 'div'], - * 'headerOptions' => ['class' => 'my-class'], - * 'clientOptions' => ['collapsible' => false], - * ]); - * ``` - * - * @see http://api.jqueryui.com/tabs/ - * @author Alexander Kochetov - * @since 2.0 - */ -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 = []; - /** - * @var array list of tab items. Each item can be an array of the following structure: - * - * - 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, 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 = []; - /** - * @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 = []; - /** - * @var array list of HTML attributes for the header container tags. This will be overwritten - * by the "headerOptions" set in individual [[items]]. - */ - public $headerOptions = []; - /** - * @var string the default header template to render the link. - */ - public $linkTemplate = '
      {label}'; - /** - * @var boolean whether the labels for header items should be HTML-encoded. - */ - public $encodeLabels = true; - - - /** - * Renders the widget. - */ - public function run() - { - $options = $this->options; - $tag = ArrayHelper::remove($options, 'tag', 'div'); - echo Html::beginTag($tag, $options) . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag($tag) . "\n"; - $this->registerWidget('tabs', TabsAsset::className()); - } - - /** - * Renders tab items as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - protected function renderItems() - { - $headers = []; - $items = []; - foreach ($this->items as $n => $item) { - if (!isset($item['label'])) { - throw new InvalidConfigException("The 'label' option is required."); - } - if (isset($item['url'])) { - $url = Html::url($item['url']); - } else { - if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' or 'url' option is required."); - } - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); - $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 = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); - $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); - $headers[] = Html::tag('li', strtr($template, [ - '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], - '{url}' => $url, - ]), $headerOptions); - } - return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); - } -} diff --git a/extensions/jui/yii/jui/TabsAsset.php b/extensions/jui/yii/jui/TabsAsset.php deleted file mode 100644 index 5bef4c0..0000000 --- a/extensions/jui/yii/jui/TabsAsset.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class TabsAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.tabs.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\EffectAsset', - ]; -} diff --git a/extensions/jui/yii/jui/ThemeAsset.php b/extensions/jui/yii/jui/ThemeAsset.php deleted file mode 100644 index dedcb00..0000000 --- a/extensions/jui/yii/jui/ThemeAsset.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @since 2.0 - */ -class ThemeAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $css = [ - 'theme/jquery.ui.css', - ]; -} diff --git a/extensions/jui/yii/jui/TooltipAsset.php b/extensions/jui/yii/jui/TooltipAsset.php deleted file mode 100644 index 1fa4490..0000000 --- a/extensions/jui/yii/jui/TooltipAsset.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -class TooltipAsset extends AssetBundle -{ - public $sourcePath = '@yii/jui/assets'; - public $js = [ - 'jquery.ui.tooltip.js', - ]; - public $depends = [ - 'yii\jui\CoreAsset', - 'yii\jui\EffectAsset', - ]; -} diff --git a/extensions/jui/yii/jui/Widget.php b/extensions/jui/yii/jui/Widget.php deleted file mode 100644 index 2bbc9e4..0000000 --- a/extensions/jui/yii/jui/Widget.php +++ /dev/null @@ -1,87 +0,0 @@ - - * @since 2.0 - */ -class Widget extends \yii\base\Widget -{ - /** - * @var string the jQuery UI theme. This refers to an asset bundle class - * representing the JUI theme. The default theme is the official "Smoothness" theme. - */ - public static $theme = 'yii\jui\ThemeAsset'; - /** - * @var array the HTML attributes for the widget container tag. - */ - public $options = []; - /** - * @var array the options for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible options. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported options (e.g. "header"). - */ - public $clientOptions = []; - /** - * @var array the event handlers for the underlying jQuery UI widget. - * Please refer to the corresponding jQuery UI widget Web page for possible events. - * For example, [this page](http://api.jqueryui.com/accordion/) shows - * how to use the "Accordion" widget and the supported events (e.g. "create"). - */ - public $clientEvents = []; - - - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - */ - public function init() - { - parent::init(); - if (!isset($this->options['id'])) { - $this->options['id'] = $this->getId(); - } - } - - /** - * Registers a specific jQuery UI widget and the related events - * @param string $name the name of the jQuery UI widget - * @param string $assetBundle the asset bundle for the widget - */ - protected function registerWidget($name, $assetBundle) - { - $view = $this->getView(); - /** @var \yii\web\AssetBundle $assetBundle */ - $assetBundle::register($view); - /** @var \yii\web\AssetBundle $themeAsset */ - $themeAsset = self::$theme; - $themeAsset::register($view); - - $id = $this->options['id']; - if ($this->clientOptions !== false) { - $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); - $js = "jQuery('#$id').$name($options);"; - $view->registerJs($js); - } - - if (!empty($this->clientEvents)) { - $js = []; - foreach ($this->clientEvents as $event => $handler) { - $js[] = "jQuery('#$id').on('$name$event', $handler);"; - } - $view->registerJs(implode("\n", $js)); - } - } -} diff --git a/extensions/jui/yii/jui/assets.php b/extensions/jui/yii/jui/assets.php deleted file mode 100644 index ab4c930..0000000 --- a/extensions/jui/yii/jui/assets.php +++ /dev/null @@ -1,23 +0,0 @@ - li > :first-child,> :not(li):even", - heightStyle: "auto", - icons: { - activeHeader: "ui-icon-triangle-1-s", - header: "ui-icon-triangle-1-e" - }, - - // callbacks - activate: null, - beforeActivate: null - }, - - _create: function() { - var options = this.options; - this.prevShow = this.prevHide = $(); - this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) - // ARIA - .attr( "role", "tablist" ); - - // don't allow collapsible: false and active: false / null - if ( !options.collapsible && (options.active === false || options.active == null) ) { - options.active = 0; - } - - this._processPanels(); - // handle negative values - if ( options.active < 0 ) { - options.active += this.headers.length; - } - this._refresh(); - }, - - _getCreateEventData: function() { - return { - header: this.active, - panel: !this.active.length ? $() : this.active.next(), - content: !this.active.length ? $() : this.active.next() - }; - }, - - _createIcons: function() { - var icons = this.options.icons; - if ( icons ) { - $( "" ) - .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) - .prependTo( this.headers ); - this.active.children( ".ui-accordion-header-icon" ) - .removeClass( icons.header ) - .addClass( icons.activeHeader ); - this.headers.addClass( "ui-accordion-icons" ); - } - }, - - _destroyIcons: function() { - this.headers - .removeClass( "ui-accordion-icons" ) - .children( ".ui-accordion-header-icon" ) - .remove(); - }, - - _destroy: function() { - var contents; - - // clean up main element - this.element - .removeClass( "ui-accordion ui-widget ui-helper-reset" ) - .removeAttr( "role" ); - - // clean up headers - this.headers - .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) - .removeAttr( "role" ) - .removeAttr( "aria-selected" ) - .removeAttr( "aria-controls" ) - .removeAttr( "tabIndex" ) - .each(function() { - if ( /^ui-accordion/.test( this.id ) ) { - this.removeAttribute( "id" ); - } - }); - this._destroyIcons(); - - // clean up content panels - contents = this.headers.next() - .css( "display", "" ) - .removeAttr( "role" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "aria-hidden" ) - .removeAttr( "aria-labelledby" ) - .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) - .each(function() { - if ( /^ui-accordion/.test( this.id ) ) { - this.removeAttribute( "id" ); - } - }); - if ( this.options.heightStyle !== "content" ) { - contents.css( "height", "" ); - } - }, - - _setOption: function( key, value ) { - if ( key === "active" ) { - // _activate() will handle invalid values and update this.options - this._activate( value ); - return; - } - - if ( key === "event" ) { - if ( this.options.event ) { - this._off( this.headers, this.options.event ); - } - this._setupEvents( value ); - } - - this._super( key, value ); - - // setting collapsible: false while collapsed; open first panel - if ( key === "collapsible" && !value && this.options.active === false ) { - this._activate( 0 ); - } - - if ( key === "icons" ) { - this._destroyIcons(); - if ( value ) { - this._createIcons(); - } - } - - // #5332 - opacity doesn't cascade to positioned elements in IE - // so we need to add the disabled class to the headers and panels - if ( key === "disabled" ) { - this.headers.add( this.headers.next() ) - .toggleClass( "ui-state-disabled", !!value ); - } - }, - - _keydown: function( event ) { - /*jshint maxcomplexity:15*/ - if ( event.altKey || event.ctrlKey ) { - return; - } - - var keyCode = $.ui.keyCode, - length = this.headers.length, - currentIndex = this.headers.index( event.target ), - toFocus = false; - - switch ( event.keyCode ) { - case keyCode.RIGHT: - case keyCode.DOWN: - toFocus = this.headers[ ( currentIndex + 1 ) % length ]; - break; - case keyCode.LEFT: - case keyCode.UP: - toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; - break; - case keyCode.SPACE: - case keyCode.ENTER: - this._eventHandler( event ); - break; - case keyCode.HOME: - toFocus = this.headers[ 0 ]; - break; - case keyCode.END: - toFocus = this.headers[ length - 1 ]; - break; - } - - if ( toFocus ) { - $( event.target ).attr( "tabIndex", -1 ); - $( toFocus ).attr( "tabIndex", 0 ); - toFocus.focus(); - event.preventDefault(); - } - }, - - _panelKeyDown : function( event ) { - if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { - $( event.currentTarget ).prev().focus(); - } - }, - - refresh: function() { - var options = this.options; - this._processPanels(); - - // was collapsed or no panel - if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { - options.active = false; - this.active = $(); - // active false only when collapsible is true - } else if ( options.active === false ) { - this._activate( 0 ); - // was active, but active panel is gone - } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { - // all remaining panel are disabled - if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { - options.active = false; - this.active = $(); - // activate previous panel - } else { - this._activate( Math.max( 0, options.active - 1 ) ); - } - // was active, active panel still exists - } else { - // make sure active index is correct - options.active = this.headers.index( this.active ); - } - - this._destroyIcons(); - - this._refresh(); - }, - - _processPanels: function() { - this.headers = this.element.find( this.options.header ) - .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); - - this.headers.next() - .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) - .filter(":not(.ui-accordion-content-active)") - .hide(); - }, - - _refresh: function() { - var maxHeight, - options = this.options, - heightStyle = options.heightStyle, - parent = this.element.parent(), - accordionId = this.accordionId = "ui-accordion-" + - (this.element.attr( "id" ) || ++uid); - - this.active = this._findActive( options.active ) - .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) - .removeClass( "ui-corner-all" ); - this.active.next() - .addClass( "ui-accordion-content-active" ) - .show(); - - this.headers - .attr( "role", "tab" ) - .each(function( i ) { - var header = $( this ), - headerId = header.attr( "id" ), - panel = header.next(), - panelId = panel.attr( "id" ); - if ( !headerId ) { - headerId = accordionId + "-header-" + i; - header.attr( "id", headerId ); - } - if ( !panelId ) { - panelId = accordionId + "-panel-" + i; - panel.attr( "id", panelId ); - } - header.attr( "aria-controls", panelId ); - panel.attr( "aria-labelledby", headerId ); - }) - .next() - .attr( "role", "tabpanel" ); - - this.headers - .not( this.active ) - .attr({ - "aria-selected": "false", - tabIndex: -1 - }) - .next() - .attr({ - "aria-expanded": "false", - "aria-hidden": "true" - }) - .hide(); - - // make sure at least one header is in the tab order - if ( !this.active.length ) { - this.headers.eq( 0 ).attr( "tabIndex", 0 ); - } else { - this.active.attr({ - "aria-selected": "true", - tabIndex: 0 - }) - .next() - .attr({ - "aria-expanded": "true", - "aria-hidden": "false" - }); - } - - this._createIcons(); - - this._setupEvents( options.event ); - - if ( heightStyle === "fill" ) { - maxHeight = parent.height(); - this.element.siblings( ":visible" ).each(function() { - var elem = $( this ), - position = elem.css( "position" ); - - if ( position === "absolute" || position === "fixed" ) { - return; - } - maxHeight -= elem.outerHeight( true ); - }); - - this.headers.each(function() { - maxHeight -= $( this ).outerHeight( true ); - }); - - this.headers.next() - .each(function() { - $( this ).height( Math.max( 0, maxHeight - - $( this ).innerHeight() + $( this ).height() ) ); - }) - .css( "overflow", "auto" ); - } else if ( heightStyle === "auto" ) { - maxHeight = 0; - this.headers.next() - .each(function() { - maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); - }) - .height( maxHeight ); - } - }, - - _activate: function( index ) { - var active = this._findActive( index )[ 0 ]; - - // trying to activate the already active panel - if ( active === this.active[ 0 ] ) { - return; - } - - // trying to collapse, simulate a click on the currently active header - active = active || this.active[ 0 ]; - - this._eventHandler({ - target: active, - currentTarget: active, - preventDefault: $.noop - }); - }, - - _findActive: function( selector ) { - return typeof selector === "number" ? this.headers.eq( selector ) : $(); - }, - - _setupEvents: function( event ) { - var events = { - keydown: "_keydown" - }; - if ( event ) { - $.each( event.split(" "), function( index, eventName ) { - events[ eventName ] = "_eventHandler"; - }); - } - - this._off( this.headers.add( this.headers.next() ) ); - this._on( this.headers, events ); - this._on( this.headers.next(), { keydown: "_panelKeyDown" }); - this._hoverable( this.headers ); - this._focusable( this.headers ); - }, - - _eventHandler: function( event ) { - var options = this.options, - active = this.active, - clicked = $( event.currentTarget ), - clickedIsActive = clicked[ 0 ] === active[ 0 ], - collapsing = clickedIsActive && options.collapsible, - toShow = collapsing ? $() : clicked.next(), - toHide = active.next(), - eventData = { - oldHeader: active, - oldPanel: toHide, - newHeader: collapsing ? $() : clicked, - newPanel: toShow - }; - - event.preventDefault(); - - if ( - // click on active header, but not collapsible - ( clickedIsActive && !options.collapsible ) || - // allow canceling activation - ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { - return; - } - - options.active = collapsing ? false : this.headers.index( clicked ); - - // when the call to ._toggle() comes after the class changes - // it causes a very odd bug in IE 8 (see #6720) - this.active = clickedIsActive ? $() : clicked; - this._toggle( eventData ); - - // switch classes - // corner classes on the previously active header stay after the animation - active.removeClass( "ui-accordion-header-active ui-state-active" ); - if ( options.icons ) { - active.children( ".ui-accordion-header-icon" ) - .removeClass( options.icons.activeHeader ) - .addClass( options.icons.header ); - } - - if ( !clickedIsActive ) { - clicked - .removeClass( "ui-corner-all" ) - .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); - if ( options.icons ) { - clicked.children( ".ui-accordion-header-icon" ) - .removeClass( options.icons.header ) - .addClass( options.icons.activeHeader ); - } - - clicked - .next() - .addClass( "ui-accordion-content-active" ); - } - }, - - _toggle: function( data ) { - var toShow = data.newPanel, - toHide = this.prevShow.length ? this.prevShow : data.oldPanel; - - // handle activating a panel during the animation for another activation - this.prevShow.add( this.prevHide ).stop( true, true ); - this.prevShow = toShow; - this.prevHide = toHide; - - if ( this.options.animate ) { - this._animate( toShow, toHide, data ); - } else { - toHide.hide(); - toShow.show(); - this._toggleComplete( data ); - } - - toHide.attr({ - "aria-expanded": "false", - "aria-hidden": "true" - }); - toHide.prev().attr( "aria-selected", "false" ); - // if we're switching panels, remove the old header from the tab order - // if we're opening from collapsed state, remove the previous header from the tab order - // if we're collapsing, then keep the collapsing header in the tab order - if ( toShow.length && toHide.length ) { - toHide.prev().attr( "tabIndex", -1 ); - } else if ( toShow.length ) { - this.headers.filter(function() { - return $( this ).attr( "tabIndex" ) === 0; - }) - .attr( "tabIndex", -1 ); - } - - toShow - .attr({ - "aria-expanded": "true", - "aria-hidden": "false" - }) - .prev() - .attr({ - "aria-selected": "true", - tabIndex: 0 - }); - }, - - _animate: function( toShow, toHide, data ) { - var total, easing, duration, - that = this, - adjust = 0, - down = toShow.length && - ( !toHide.length || ( toShow.index() < toHide.index() ) ), - animate = this.options.animate || {}, - options = down && animate.down || animate, - complete = function() { - that._toggleComplete( data ); - }; - - if ( typeof options === "number" ) { - duration = options; - } - if ( typeof options === "string" ) { - easing = options; - } - // fall back from options to animation in case of partial down settings - easing = easing || options.easing || animate.easing; - duration = duration || options.duration || animate.duration; - - if ( !toHide.length ) { - return toShow.animate( showProps, duration, easing, complete ); - } - if ( !toShow.length ) { - return toHide.animate( hideProps, duration, easing, complete ); - } - - total = toShow.show().outerHeight(); - toHide.animate( hideProps, { - duration: duration, - easing: easing, - step: function( now, fx ) { - fx.now = Math.round( now ); - } - }); - toShow - .hide() - .animate( showProps, { - duration: duration, - easing: easing, - complete: complete, - step: function( now, fx ) { - fx.now = Math.round( now ); - if ( fx.prop !== "height" ) { - adjust += fx.now; - } else if ( that.options.heightStyle !== "content" ) { - fx.now = Math.round( total - toHide.outerHeight() - adjust ); - adjust = 0; - } - } - }); - }, - - _toggleComplete: function( data ) { - var toHide = data.oldPanel; - - toHide - .removeClass( "ui-accordion-content-active" ) - .prev() - .removeClass( "ui-corner-top" ) - .addClass( "ui-corner-all" ); - - // Work around for rendering bug in IE (#5421) - if ( toHide.length ) { - toHide.parent()[0].className = toHide.parent()[0].className; - } - - this._trigger( "activate", null, data ); - } -}); - -})( jQuery ); diff --git a/extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js b/extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js deleted file mode 100644 index ca53d2c..0000000 --- a/extensions/jui/yii/jui/assets/jquery.ui.autocomplete.js +++ /dev/null @@ -1,610 +0,0 @@ -/*! - * jQuery UI Autocomplete 1.10.3 - * http://jqueryui.com - * - * Copyright 2013 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/autocomplete/ - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.position.js - * jquery.ui.menu.js - */ -(function( $, undefined ) { - -// used to prevent race conditions with remote data sources -var requestIndex = 0; - -$.widget( "ui.autocomplete", { - version: "1.10.3", - defaultElement: "", - options: { - appendTo: null, - autoFocus: false, - delay: 300, - minLength: 1, - position: { - my: "left top", - at: "left bottom", - collision: "none" - }, - source: null, - - // callbacks - change: null, - close: null, - focus: null, - open: null, - response: null, - search: null, - select: null - }, - - pending: 0, - - _create: function() { - // Some browsers only repeat keydown events, not keypress events, - // so we use the suppressKeyPress flag to determine if we've already - // handled the keydown event. #7269 - // Unfortunately the code for & in keypress is the same as the up arrow, - // so we use the suppressKeyPressRepeat flag to avoid handling keypress - // events when we know the keydown event was used to modify the - // search term. #7799 - var suppressKeyPress, suppressKeyPressRepeat, suppressInput, - nodeName = this.element[0].nodeName.toLowerCase(), - isTextarea = nodeName === "textarea", - isInput = nodeName === "input"; - - this.isMultiLine = - // Textareas are always multi-line - isTextarea ? true : - // Inputs are always single-line, even if inside a contentEditable element - // IE also treats inputs as contentEditable - isInput ? false : - // All other element types are determined by whether or not they're contentEditable - this.element.prop( "isContentEditable" ); - - this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; - this.isNewMenu = true; - - this.element - .addClass( "ui-autocomplete-input" ) - .attr( "autocomplete", "off" ); - - this._on( this.element, { - keydown: function( event ) { - /*jshint maxcomplexity:15*/ - if ( this.element.prop( "readOnly" ) ) { - suppressKeyPress = true; - suppressInput = true; - suppressKeyPressRepeat = true; - return; - } - - suppressKeyPress = false; - suppressInput = false; - suppressKeyPressRepeat = false; - var keyCode = $.ui.keyCode; - switch( event.keyCode ) { - case keyCode.PAGE_UP: - suppressKeyPress = true; - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - suppressKeyPress = true; - this._move( "nextPage", event ); - break; - case keyCode.UP: - suppressKeyPress = true; - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - suppressKeyPress = true; - this._keyEvent( "next", event ); - break; - case keyCode.ENTER: - case keyCode.NUMPAD_ENTER: - // when menu is open and has focus - if ( this.menu.active ) { - // #6055 - Opera still allows the keypress to occur - // which causes forms to submit - suppressKeyPress = true; - event.preventDefault(); - this.menu.select( event ); - } - break; - case keyCode.TAB: - if ( this.menu.active ) { - this.menu.select( event ); - } - break; - case keyCode.ESCAPE: - if ( this.menu.element.is( ":visible" ) ) { - this._value( this.term ); - this.close( event ); - // Different browsers have different default behavior for escape - // Single press can mean undo or clear - // Double press in IE means clear the whole form - event.preventDefault(); - } - break; - default: - suppressKeyPressRepeat = true; - // search timeout should be triggered before the input value is changed - this._searchTimeout( event ); - break; - } - }, - keypress: function( event ) { - if ( suppressKeyPress ) { - suppressKeyPress = false; - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - event.preventDefault(); - } - return; - } - if ( suppressKeyPressRepeat ) { - return; - } - - // replicate some key handlers to allow them to repeat in Firefox and Opera - var keyCode = $.ui.keyCode; - switch( event.keyCode ) { - case keyCode.PAGE_UP: - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - this._move( "nextPage", event ); - break; - case keyCode.UP: - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - this._keyEvent( "next", event ); - break; - } - }, - input: function( event ) { - if ( suppressInput ) { - suppressInput = false; - event.preventDefault(); - return; - } - this._searchTimeout( event ); - }, - focus: function() { - this.selectedItem = null; - this.previous = this._value(); - }, - blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; - } - - clearTimeout( this.searching ); - this.close( event ); - this._change( event ); - } - }); - - this._initSource(); - this.menu = $( "
      -EOD; - } - - public function getDetail() - { - $rows = []; - foreach ($this->data['messages'] as $log) { - list ($message, $level, $category, $time, $traces) = $log; - $time = date('H:i:s.', $time) . sprintf('%03d', (int)(($time - (int)$time) * 1000)); - $message = nl2br(Html::encode($message)); - if (!empty($traces)) { - $message .= Html::ul($traces, [ - 'class' => 'trace', - 'item' => function ($trace) { - return "
    • {$trace['file']}({$trace['line']})
    • "; - }, - ]); - } - if ($level == Logger::LEVEL_ERROR) { - $class = ' class="danger"'; - } elseif ($level == Logger::LEVEL_WARNING) { - $class = ' class="warning"'; - } elseif ($level == Logger::LEVEL_INFO) { - $class = ' class="success"'; - } else { - $class = ''; - } - $level = Logger::getLevelName($level); - $rows[] = "$time$level$category
      $message
      "; - } - $rows = implode("\n", $rows); - return <<Log Messages - - - - - - - - - - - -$rows - -
      TimeLevelCategoryMessage
      -EOD; - } - - public function save() - { - $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); - return ['messages' => $messages]; - } -} diff --git a/framework/yii/debug/panels/ProfilingPanel.php b/framework/yii/debug/panels/ProfilingPanel.php deleted file mode 100644 index 49cf081..0000000 --- a/framework/yii/debug/panels/ProfilingPanel.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @since 2.0 - */ -class ProfilingPanel extends Panel -{ - public function getName() - { - return 'Profiling'; - } - - public function getSummary() - { - $memory = sprintf('%.1f MB', $this->data['memory'] / 1048576); - $time = number_format($this->data['time'] * 1000) . ' ms'; - $url = $this->getUrl(); - - return << - Time $time -
      - -EOD; - } - - public function getDetail() - { - $messages = $this->data['messages']; - $timings = []; - $stack = []; - foreach ($messages as $i => $log) { - list($token, $level, $category, $timestamp, $traces) = $log; - if ($level == Logger::LEVEL_PROFILE_BEGIN) { - $stack[] = $log; - } elseif ($level == Logger::LEVEL_PROFILE_END) { - if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[] = [count($stack), $token, $category, $timestamp - $last[3], $traces]; - } - } - } - - $now = microtime(true); - while (($last = array_pop($stack)) !== null) { - $timings[] = [count($stack), $last[0], $last[2], $now - $last[3], $last[4]]; - } - - $rows = []; - foreach ($timings as $timing) { - $time = sprintf('%.1f ms', $timing[3] * 1000); - $procedure = str_repeat('', $timing[0]) . Html::encode($timing[1]); - $category = Html::encode($timing[2]); - $rows[] = "$time$category$procedure"; - } - $rows = implode("\n", $rows); - - $memory = sprintf('%.1f MB', $this->data['memory'] / 1048576); - $time = number_format($this->data['time'] * 1000) . ' ms'; - - return <<Performance Profiling - -

      Total processing time: $time; Peak memory: $memory.

      - - - - - - - - - - -$rows - -
      TimeCategoryProcedure
      -EOD; - } - - public function save() - { - $target = $this->module->logTarget; - $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); - return [ - 'memory' => memory_get_peak_usage(), - 'time' => microtime(true) - YII_BEGIN_TIME, - 'messages' => $messages, - ]; - } -} diff --git a/framework/yii/debug/panels/RequestPanel.php b/framework/yii/debug/panels/RequestPanel.php deleted file mode 100644 index 9d9cb02..0000000 --- a/framework/yii/debug/panels/RequestPanel.php +++ /dev/null @@ -1,167 +0,0 @@ - - * @since 2.0 - */ -class RequestPanel extends Panel -{ - public function getName() - { - return 'Request'; - } - - public function getSummary() - { - $url = $this->getUrl(); - $statusCode = $this->data['statusCode']; - if ($statusCode === null) { - $statusCode = 200; - } - if ($statusCode >= 200 && $statusCode < 300) { - $class = 'label-success'; - } elseif ($statusCode >= 100 && $statusCode < 200) { - $class = 'label-info'; - } else { - $class = 'label-important'; - } - $statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Response::$httpStatuses[$statusCode] : ''); - - return << - Status $statusCode -
      - -EOD; - } - - public function getDetail() - { - $data = [ - 'Route' => $this->data['route'], - 'Action' => $this->data['action'], - 'Parameters' => $this->data['actionParams'], - ]; - return Tabs::widget([ - 'items' => [ - [ - 'label' => 'Parameters', - 'content' => $this->renderData('Routing', $data) - . $this->renderData('$_GET', $this->data['GET']) - . $this->renderData('$_POST', $this->data['POST']) - . $this->renderData('$_FILES', $this->data['FILES']) - . $this->renderData('$_COOKIE', $this->data['COOKIE']), - 'active' => true, - ], - [ - 'label' => 'Headers', - 'content' => $this->renderData('Request Headers', $this->data['requestHeaders']) - . $this->renderData('Response Headers', $this->data['responseHeaders']), - ], - [ - 'label' => 'Session', - 'content' => $this->renderData('$_SESSION', $this->data['SESSION']) - . $this->renderData('Flashes', $this->data['flashes']), - ], - [ - 'label' => '$_SERVER', - 'content' => $this->renderData('$_SERVER', $this->data['SERVER']), - ], - ], - ]); - } - - public function save() - { - if (function_exists('apache_request_headers')) { - $requestHeaders = apache_request_headers(); - } elseif (function_exists('http_get_request_headers')) { - $requestHeaders = http_get_request_headers(); - } else { - $requestHeaders = []; - } - $responseHeaders = []; - foreach (headers_list() as $header) { - if (($pos = strpos($header, ':')) !== false) { - $name = substr($header, 0, $pos); - $value = trim(substr($header, $pos + 1)); - if (isset($responseHeaders[$name])) { - if (!is_array($responseHeaders[$name])) { - $responseHeaders[$name] = [$responseHeaders[$name], $value]; - } else { - $responseHeaders[$name][] = $value; - } - } else { - $responseHeaders[$name] = $value; - } - } else { - $responseHeaders[] = $header; - } - } - if (Yii::$app->requestedAction) { - if (Yii::$app->requestedAction instanceof InlineAction) { - $action = get_class(Yii::$app->requestedAction->controller) . '::' . Yii::$app->requestedAction->actionMethod . '()'; - } else { - $action = get_class(Yii::$app->requestedAction) . '::run()'; - } - } else { - $action = null; - } - /** @var \yii\web\Session $session */ - $session = Yii::$app->getComponent('session', false); - return [ - 'flashes' => $session ? $session->getAllFlashes() : [], - 'statusCode' => Yii::$app->getResponse()->getStatusCode(), - 'requestHeaders' => $requestHeaders, - 'responseHeaders' => $responseHeaders, - 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, - 'action' => $action, - 'actionParams' => Yii::$app->requestedParams, - 'SERVER' => empty($_SERVER) ? [] : $_SERVER, - 'GET' => empty($_GET) ? [] : $_GET, - 'POST' => empty($_POST) ? [] : $_POST, - 'COOKIE' => empty($_COOKIE) ? [] : $_COOKIE, - 'FILES' => empty($_FILES) ? [] : $_FILES, - 'SESSION' => empty($_SESSION) ? [] : $_SESSION, - ]; - } - - protected function renderData($caption, $values) - { - if (empty($values)) { - return "

      $caption

      \n

      Empty.

      "; - } - $rows = []; - foreach ($values as $name => $value) { - $rows[] = '' . Html::encode($name) . '' . htmlspecialchars(var_export($value, true), ENT_QUOTES|ENT_SUBSTITUTE, \Yii::$app->charset, TRUE) . ''; - } - $rows = implode("\n", $rows); - return <<$caption - - - -$rows - -
      NameValue
      -EOD; - } -} diff --git a/framework/yii/debug/views/default/index.php b/framework/yii/debug/views/default/index.php deleted file mode 100644 index c01230c..0000000 --- a/framework/yii/debug/views/default/index.php +++ /dev/null @@ -1,46 +0,0 @@ -title = 'Yii Debugger'; -?> -
      -
      -
      - Yii Debugger -
      -
      - -
      -
      -

      Available Debug Data

      - - - - - - - - - - - - $data): ?> - - - - - - - - - -
      TagTimeIPMethodURL
      $tag]) ?>
      -
      -
      -
      diff --git a/framework/yii/debug/views/default/toolbar.css b/framework/yii/debug/views/default/toolbar.css deleted file mode 100644 index 72bf3bf..0000000 --- a/framework/yii/debug/views/default/toolbar.css +++ /dev/null @@ -1,177 +0,0 @@ -#yii-debug-toolbar { - position: fixed; - left: 0; - right: 0; - bottom: 0; - margin: 0; - padding: 0; - z-index: 1000000; - font: 11px Verdana, Arial, sans-serif; - text-align: left; - height: 40px; - border-top: 1px solid #ccc; - background: rgb(237,237,237); - background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZWRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUzJSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); - background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1))); - background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 ); -} - -.yii-debug-toolbar-block { - float: left; - margin: 0; - border-right: 1px solid #e4e4e4; - padding: 4px 8px; - line-height: 32px; -} - -.yii-debug-toolbar-block a { - text-decoration: none; - color: black; -} - -.yii-debug-toolbar-block span { -} - -.yii-debug-toolbar-block img { - vertical-align: middle; -} - -#yii-debug-toolbar .label { - display: inline-block; - padding: 2px 4px; - font-size: 11.844px; - font-weight: normal; - line-height: 14px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - white-space: nowrap; - vertical-align: baseline; - background-color: #999999; -} - -#yii-debug-toolbar .label { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -#yii-debug-toolbar .label:empty { - display: none; -} - -#yii-debug-toolbar a.label:hover, -#yii-debug-toolbar a.label:focus { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -#yii-debug-toolbar .label-important { - background-color: #b94a48; -} - -#yii-debug-toolbar .label-important[href] { - background-color: #953b39; -} - -#yii-debug-toolbar .label-warning, -#yii-debug-toolbar .badge-warning { - background-color: #f89406; -} - -#yii-debug-toolbar .label-warning[href] { - background-color: #c67605; -} - -#yii-debug-toolbar .label-success { - background-color: #468847; -} - -#yii-debug-toolbar .label-success[href] { - background-color: #356635; -} - -#yii-debug-toolbar .label-info { - background-color: #3a87ad; -} - -#yii-debug-toolbar .label-info[href] { - background-color: #2d6987; -} - -#yii-debug-toolbar .label-inverse, -#yii-debug-toolbar .badge-inverse { - background-color: #333333; -} - -#yii-debug-toolbar .label-inverse[href], -#yii-debug-toolbar .badge-inverse[href] { - background-color: #1a1a1a; -} - -.yii-debug-toolbar-toggler { - cursor: pointer; - position: absolute; - right: 10px; - bottom: 4px; - width: 15px; - height: 30px; - font-size: 25px; - font-weight: 100; - line-height: 28px; - color: #ffffff; - text-align: center; - background: #666666; - -webkit-border-radius: 12px; - -moz-border-radius: 12px; - border-radius: 12px; - opacity: 0.5; - filter: alpha(opacity=50); -} - -.yii-debug-toolbar-toggler:hover, -.yii-debug-toolbar-toggler:focus { - color: #ffffff; - text-decoration: none; - opacity: 0.9; - filter: alpha(opacity=90); -} - -#yii-debug-toolbar-min { - display: none; - position: fixed; - right: 0; - bottom: 0; - margin: 0; - padding: 0; - z-index: 1000000; - font: 11px Verdana, Arial, sans-serif; - text-align: left; - width: 63px; - height: 38px; - border-top: 1px solid #ccc; - border-left: 1px solid #ccc; - -webkit-border-top-left-radius: 6px; - -moz-border-top-left-radius: 6px; - border-top-left-radius: 6px; - background: rgb(237,237,237); - background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZWRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUzJSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); - background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1))); - background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 ); -} - -#yii-debug-toolbar-logo { - position: fixed; - right: 31px; - bottom: 4px; -} diff --git a/framework/yii/debug/views/default/toolbar.js b/framework/yii/debug/views/default/toolbar.js deleted file mode 100644 index 0dca1de..0000000 --- a/framework/yii/debug/views/default/toolbar.js +++ /dev/null @@ -1,41 +0,0 @@ -(function() { - var ajax = function(url, settings) { - var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); - settings = settings || {}; - xhr.open(settings.method || 'GET', url, true); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.onreadystatechange = function(state) { - if (xhr.readyState == 4) { - if (xhr.status == 200 && settings.success) { - settings.success(xhr); - } else if (xhr.status != 200 && settings.error) { - settings.error(xhr); - } - } - }; - xhr.send(settings.data || ''); - }; - - var e = document.getElementById('yii-debug-toolbar'); - if (e) { - e.style.display = 'block'; - var url = e.getAttribute('data-url'); - ajax(url, { - success: function(xhr) { - var div = document.createElement('div'); - div.innerHTML = xhr.responseText; - e.parentNode.replaceChild(div, e); - if (window.localStorage) { - var pref = localStorage.getItem('yii-debug-toolbar'); - if (pref == 'minimized') { - document.getElementById('yii-debug-toolbar').style.display = 'none'; - document.getElementById('yii-debug-toolbar-min').style.display = 'block'; - } - } - }, - error: function(xhr) { - e.innerHTML = xhr.responseText; - } - }); - } -})(); diff --git a/framework/yii/debug/views/default/toolbar.php b/framework/yii/debug/views/default/toolbar.php deleted file mode 100644 index bc76a67..0000000 --- a/framework/yii/debug/views/default/toolbar.php +++ /dev/null @@ -1,38 +0,0 @@ -getUrl(); -?> -
      - - getSummary() ?> - - -
      -
      - - -
      diff --git a/framework/yii/debug/views/default/view.php b/framework/yii/debug/views/default/view.php deleted file mode 100644 index 338bef7..0000000 --- a/framework/yii/debug/views/default/view.php +++ /dev/null @@ -1,78 +0,0 @@ -title = 'Yii Debugger'; -?> -
      -
      -
      - Yii Debugger -
      - - getSummary() ?> - -
      - -
      -
      -
      -
      - $panel) { - $label = '' . Html::encode($panel->getName()); - echo Html::a($label, ['view', 'tag' => $tag, 'panel' => $id], [ - 'class' => $panel === $activePanel ? 'list-group-item active' : 'list-group-item', - ]); - } - ?> -
      -
      -
      -
      - $meta['tag'], 'panel' => $activePanel->id]; - $items[] = [ - 'label' => $label, - 'url' => $url, - ]; - if (++$count >= 10) { - break; - } - } - echo ButtonGroup::widget([ - 'buttons' => [ - Html::a('All', ['index'], ['class' => 'btn btn-default']), - ButtonDropdown::widget([ - 'label' => 'Last 10', - 'options' => ['class' => 'btn-default'], - 'dropdown' => ['items' => $items], - ]), - ], - ]); - echo "\n" . $summary['tag'] . ': ' . $summary['method'] . ' ' . Html::a(Html::encode($summary['url']), $summary['url']); - echo ' at ' . date('Y-m-d h:i:s a', $summary['time']) . ' by ' . $summary['ip']; - ?> -
      - getDetail() ?> -
      -
      -
      -
      diff --git a/framework/yii/debug/views/layouts/main.php b/framework/yii/debug/views/layouts/main.php deleted file mode 100644 index c16ac4f..0000000 --- a/framework/yii/debug/views/layouts/main.php +++ /dev/null @@ -1,23 +0,0 @@ - - - -beginPage(); ?> - - <?= Html::encode($this->title) ?> - head(); ?> - - -beginBody(); ?> - -endBody(); ?> - -endPage(); ?> - diff --git a/framework/yii/gii/CodeFile.php b/framework/yii/gii/CodeFile.php deleted file mode 100644 index a5196d9..0000000 --- a/framework/yii/gii/CodeFile.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @since 2.0 - */ -class CodeFile extends Object -{ - /** - * The code file is new. - */ - const OP_CREATE = 'create'; - /** - * The code file already exists, and the new one may need to overwrite it. - */ - const OP_OVERWRITE = 'overwrite'; - /** - * The new code file and the existing one are identical. - */ - const OP_SKIP = 'skip'; - - /** - * @var string an ID that uniquely identifies this code file. - */ - public $id; - /** - * @var string the file path that the new code should be saved to. - */ - public $path; - /** - * @var string the newly generated code content - */ - public $content; - /** - * @var string the operation to be performed. This can be [[OP_NEW]], [[OP_OVERWRITE]] or [[OP_SKIP]]. - */ - public $operation; - - /** - * Constructor. - * @param string $path the file path that the new code should be saved to. - * @param string $content the newly generated code content. - */ - public function __construct($path, $content) - { - $this->path = strtr($path, ['/' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]); - $this->content = $content; - $this->id = md5($this->path); - if (is_file($path)) { - $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE; - } else { - $this->operation = self::OP_CREATE; - } - } - - /** - * Saves the code into the file specified by [[path]]. - * @return string|boolean the error occurred while saving the code file, or true if no error. - */ - public function save() - { - $module = Yii::$app->controller->module; - if ($this->operation === self::OP_CREATE) { - $dir = dirname($this->path); - if (!is_dir($dir)) { - $mask = @umask(0); - $result = @mkdir($dir, $module->newDirMode, true); - @umask($mask); - if (!$result) { - return "Unable to create the directory '$dir'."; - } - } - } - if (@file_put_contents($this->path, $this->content) === false) { - return "Unable to write the file '{$this->path}'."; - } else { - $mask = @umask(0); - @chmod($this->path, $module->newFileMode); - @umask($mask); - } - return true; - } - - /** - * @return string the code file path relative to the application base path. - */ - public function getRelativePath() - { - if (strpos($this->path, Yii::$app->basePath) === 0) { - return substr($this->path, strlen(Yii::$app->basePath) + 1); - } else { - return $this->path; - } - } - - /** - * @return string the code file extension (e.g. php, txt) - */ - public function getType() - { - if (($pos = strrpos($this->path, '.')) !== false) { - return substr($this->path, $pos + 1); - } else { - return 'unknown'; - } - } - - public function preview() - { - if (($pos = strrpos($this->path, '.')) !== false) { - $type = substr($this->path, $pos + 1); - } else { - $type = 'unknown'; - } - - if ($type === 'php') { - return highlight_string($this->content, true); - } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) { - return nl2br(Html::encode($this->content)); - } else { - return false; - } - } - - public function diff() - { - $type = strtolower($this->getType()); - if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) { - return false; - } elseif ($this->operation === self::OP_OVERWRITE) { - return StringHelper::diff(file($this->path), $this->content); - } else { - return ''; - } - } -} diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php deleted file mode 100644 index 6f63628..0000000 --- a/framework/yii/gii/Generator.php +++ /dev/null @@ -1,449 +0,0 @@ - - * @since 2.0 - */ -abstract class Generator extends Model -{ - /** - * @var array a list of available code templates. The array keys are the template names, - * and the array values are the corresponding template paths or path aliases. - */ - public $templates = []; - /** - * @var string the name of the code template that the user has selected. - * The value of this property is internally managed by this class. - */ - public $template; - - /** - * @return string name of the code generator - */ - abstract public function getName(); - /** - * Generates the code based on the current user input and the specified code template files. - * This is the main method that child classes should implement. - * Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example - * on how to implement this method. - * @return CodeFile[] a list of code files to be created. - */ - abstract public function generate(); - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - if (!isset($this->templates['default'])) { - $this->templates['default'] = $this->defaultTemplate(); - } - foreach ($this->templates as $i => $template) { - $this->templates[$i] = Yii::getAlias($template); - } - } - - /** - * Returns a list of code template files that are required. - * Derived classes usually should override this method if they require the existence of - * certain template files. - * @return array list of code template files that are required. They should be file paths - * relative to [[templatePath]]. - */ - public function requiredTemplates() - { - return []; - } - - /** - * Returns the list of sticky attributes. - * A sticky attribute will remember its value and will initialize the attribute with this value - * when the generator is restarted. - * @return array list of sticky attributes - */ - public function stickyAttributes() - { - return ['template']; - } - - /** - * Returns the list of hint messages. - * The array keys are the attribute names, and the array values are the corresponding hint messages. - * Hint messages will be displayed to end users when they are filling the form for the generator. - * @return array the list of hint messages - */ - public function hints() - { - return []; - } - - /** - * 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. - * 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. - */ - public function successMessage() - { - return 'The code has been generated successfully.'; - } - - /** - * Returns the view file for the input form of the generator. - * The default implementation will return the "form.php" file under the directory - * that contains the generator class file. - * @return string the view file for the input form of the generator. - */ - public function formView() - { - $class = new ReflectionClass($this); - return dirname($class->getFileName()) . '/form.php'; - } - - /** - * Returns the root path to the default code template files. - * The default implementation will return the "templates" subdirectory of the - * directory containing the generator class file. - * @return string the root path to the default code template files. - */ - public function defaultTemplate() - { - $class = new ReflectionClass($this); - return dirname($class->getFileName()) . '/templates'; - } - - /** - * @return string the detailed description of the generator. - */ - public function getDescription() - { - return ''; - } - - /** - * @inheritdoc - * - * Child classes should override this method like the following so that the parent - * rules are included: - * - * ~~~ - * return array_merge(parent::rules(), [ - * ...rules for the child class... - * ]); - * ~~~ - */ - public function rules() - { - return [ - ['template', 'required', 'message' => 'A code template must be selected.'], - ['template', 'validateTemplate'], - ]; - } - - /** - * Loads sticky attributes from an internal file and populates them into the generator. - * @internal - */ - public function loadStickyAttributes() - { - $stickyAttributes = $this->stickyAttributes(); - $attributes[] = 'template'; - $path = $this->getStickyDataFile(); - if (is_file($path)) { - $result = @include($path); - if (is_array($result)) { - foreach ($stickyAttributes as $name) { - if (isset($result[$name])) { - $this->$name = $result[$name]; - } - } - } - } - } - - /** - * Saves sticky attributes into an internal file. - * @internal - */ - public function saveStickyAttributes() - { - $stickyAttributes = $this->stickyAttributes(); - $stickyAttributes[] = 'template'; - $values = []; - foreach ($stickyAttributes as $name) { - $values[$name] = $this->$name; - } - $path = $this->getStickyDataFile(); - @mkdir(dirname($path), 0755, true); - file_put_contents($path, "getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.php'; - } - - /** - * Saves the generated code into files. - * @param CodeFile[] $files the code files to be saved - * @param array $answers - * @param string $results this parameter receives a value from this method indicating the log messages - * generated while saving the code files. - * @return boolean whether there is any error while saving the code files. - */ - public function save($files, $answers, &$results) - { - $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...']; - $hasError = false; - foreach ($files as $file) { - $relativePath = $file->getRelativePath(); - if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) { - $error = $file->save(); - if (is_string($error)) { - $hasError = true; - $lines[] = "generating $relativePath\n$error"; - } else { - $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; - } - } else { - $lines[] = " skipped $relativePath"; - } - } - $lines[] = "done!\n"; - $results = implode("\n", $lines); - - return $hasError; - } - - /** - * @return string the root path of the template files that are currently being used. - * @throws InvalidConfigException if [[template]] is invalid - */ - public function getTemplatePath() - { - if (isset($this->templates[$this->template])) { - return $this->templates[$this->template]; - } else { - throw new InvalidConfigException("Unknown template: {$this->template}"); - } - } - - /** - * Generates code using the specified code template and parameters. - * Note that the code template will be used as a PHP file. - * @param string $template the code template file. This must be specified as a file path - * relative to [[templatePath]]. - * @param array $params list of parameters to be passed to the template file. - * @return string the generated code - */ - public function render($template, $params = []) - { - $view = new View; - $params['generator'] = $this; - return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this); - } - - /** - * Validates the template selection. - * This method validates whether the user selects an existing template - * and the template contains all required template files as specified in [[requiredTemplates()]]. - */ - public function validateTemplate() - { - $templates = $this->templates; - if (!isset($templates[$this->template])) { - $this->addError('template', 'Invalid template selection.'); - } else { - $templatePath = $this->templates[$this->template]; - foreach ($this->requiredTemplates() as $template) { - if (!is_file($templatePath . '/' . $template)) { - $this->addError('template', "Unable to find the required code template file '$template'."); - } - } - } - } - - /** - * An inline validator that checks if the attribute value refers to an existing class name. - * If the `extends` option is specified, it will also check if the class is a child class - * of the class represented by the `extends` option. - * @param string $attribute the attribute being validated - * @param array $params the validation options - */ - public function validateClass($attribute, $params) - { - $class = $this->$attribute; - try { - if (class_exists($class)) { - if (isset($params['extends'])) { - if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) { - $this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class."); - } - } - } else { - $this->addError($attribute, "Class '$class' does not exist or has syntax error."); - } - } catch (\Exception $e) { - $this->addError($attribute, "Class '$class' does not exist or has syntax error."); - } - } - - /** - * An inline validator that checks if the attribute value refers to a valid namespaced class name. - * The validator will check if the directory containing the new class file exist or not. - * @param string $attribute the attribute being validated - * @param array $params the validation options - */ - public function validateNewClass($attribute, $params) - { - $class = ltrim($this->$attribute, '\\'); - if (($pos = strrpos($class, '\\')) === false) { - $this->addError($attribute, "The class name must contain fully qualified namespace name."); - } else { - $ns = substr($class, 0, $pos); - $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); - if ($path === false) { - $this->addError($attribute, "The class namespace is invalid: $ns"); - } elseif (!is_dir($path)) { - $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); - } - } - } - - /** - * @param string $value the attribute to be validated - * @return boolean whether the value is a reserved PHP keyword. - */ - public function isReservedKeyword($value) - { - static $keywords = [ - '__class__', - '__dir__', - '__file__', - '__function__', - '__line__', - '__method__', - '__namespace__', - '__trait__', - 'abstract', - 'and', - 'array', - 'as', - 'break', - 'case', - 'catch', - 'callable', - 'cfunction', - 'class', - 'clone', - 'const', - 'continue', - 'declare', - 'default', - 'die', - 'do', - 'echo', - 'else', - 'elseif', - 'empty', - 'enddeclare', - 'endfor', - 'endforeach', - 'endif', - 'endswitch', - 'endwhile', - 'eval', - 'exception', - 'exit', - 'extends', - 'final', - 'finally', - 'for', - 'foreach', - 'function', - 'global', - 'goto', - 'if', - 'implements', - 'include', - 'include_once', - 'instanceof', - 'insteadof', - 'interface', - 'isset', - 'list', - 'namespace', - 'new', - 'old_function', - 'or', - 'parent', - 'php_user_filter', - 'print', - 'private', - 'protected', - 'public', - 'require', - 'require_once', - 'return', - 'static', - 'switch', - 'this', - 'throw', - 'trait', - 'try', - 'unset', - 'use', - 'var', - 'while', - 'xor', - ]; - return in_array(strtolower($value), $keywords, true); - } -} diff --git a/framework/yii/gii/GiiAsset.php b/framework/yii/gii/GiiAsset.php deleted file mode 100644 index b100750..0000000 --- a/framework/yii/gii/GiiAsset.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @since 2.0 - */ -class GiiAsset extends AssetBundle -{ - /** - * @inheritdoc - */ - public $sourcePath = '@yii/gii/assets'; - /** - * @inheritdoc - */ - public $css = [ - 'main.css', - 'typeahead.js-bootstrap.css', - ]; - /** - * @inheritdoc - */ - public $js = [ - 'gii.js', - 'typeahead.js', - ]; - /** - * @inheritdoc - */ - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - 'yii\bootstrap\BootstrapPluginAsset', - ]; -} diff --git a/framework/yii/gii/Module.php b/framework/yii/gii/Module.php deleted file mode 100644 index ab840a0..0000000 --- a/framework/yii/gii/Module.php +++ /dev/null @@ -1,145 +0,0 @@ - [ - * 'gii' => ['class' => 'yii\gii\Module'], - * ], - * ] - * ~~~ - * - * Because Gii generates new code files on the server, you should only use it on your own - * development machine. To prevent other people from using this module, by default, Gii - * can only be accessed by localhost. You may configure its [[allowedIPs]] property if - * you want to make it accessible on other machines. - * - * With the above configuration, you will be able to access GiiModule in your browser using - * the URL `http://localhost/path/to/index.php?r=gii` - * - * If your application enables [[UrlManager::enablePrettyUrl|pretty URLs]] and you have defined - * custom URL rules or enabled [[UrlManager::enableStrictParsing], you may need to add - * the following URL rules at the beginning of your URL rule set in your application configuration - * in order to access Gii: - * - * ~~~ - * 'rules' => [ - * 'gii' => 'gii', - * 'gii/' => 'gii/', - * 'gii//' => 'gii//', - * ... - * ], - * ~~~ - * - * You can then access Gii via URL: `http://localhost/path/to/index.php/gii` - * - * @author Qiang Xue - * @since 2.0 - */ -class Module extends \yii\base\Module -{ - /** - * @inheritdoc - */ - public $controllerNamespace = 'yii\gii\controllers'; - /** - * @var array the list of IPs that are allowed to access this module. - * Each array element represents a single IP filter which can be either an IP address - * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. - * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed - * by localhost. - */ - public $allowedIPs = ['127.0.0.1', '::1']; - /** - * @var array|Generator[] a list of generator configurations or instances. The array keys - * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator - * configurations or the instances. - * - * After the module is initialized, this property will become an array of generator instances - * which are created based on the configurations previously taken by this property. - * - * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former - * takes precedence in case when they have the same generator ID. - */ - public $generators = []; - /** - * @var integer the permission to be set for newly generated code files. - * This value will be used by PHP chmod function. - * Defaults to 0666, meaning the file is read-writable by all users. - */ - public $newFileMode = 0666; - /** - * @var integer the permission to be set for newly generated directories. - * This value will be used by PHP chmod function. - * Defaults to 0777, meaning the directory can be read, written and executed by all users. - */ - public $newDirMode = 0777; - - - /** - * @inheritdoc - */ - public function init() - { - parent::init(); - foreach (array_merge($this->coreGenerators(), $this->generators) as $id => $config) { - $this->generators[$id] = Yii::createObject($config); - } - } - - /** - * @inheritdoc - */ - public function beforeAction($action) - { - if ($this->checkAccess()) { - return parent::beforeAction($action); - } else { - throw new HttpException(403, 'You are not allowed to access this page.'); - } - } - - /** - * @return boolean whether the module can be accessed by the current user - */ - protected function checkAccess() - { - $ip = Yii::$app->getRequest()->getUserIP(); - foreach ($this->allowedIPs as $filter) { - if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { - return true; - } - } - return false; - } - - /** - * Returns the list of the core code generator configurations. - * @return array the list of the core code generator configurations. - */ - protected function coreGenerators() - { - return [ - 'model' => ['class' => 'yii\gii\generators\model\Generator'], - 'crud' => ['class' => 'yii\gii\generators\crud\Generator'], - 'controller' => ['class' => 'yii\gii\generators\controller\Generator'], - 'form' => ['class' => 'yii\gii\generators\form\Generator'], - 'module' => ['class' => 'yii\gii\generators\module\Generator'], - ]; - } -} diff --git a/framework/yii/gii/assets/gii.js b/framework/yii/gii/assets/gii.js deleted file mode 100644 index a95221e..0000000 --- a/framework/yii/gii/assets/gii.js +++ /dev/null @@ -1,99 +0,0 @@ -yii.gii = (function ($) { - var isActive = $('.default-view').length > 0; - - var initHintBlocks = function () { - $('.hint-block').each(function () { - var $hint = $(this); - $hint.parent().find('label').addClass('help').popover({ - html: true, - trigger: 'hover', - placement: 'right', - content: $hint.html() - }); - }); - }; - - var initStickyInputs = function () { - $('.sticky:not(.error)').find('input[type="text"],select,textarea').each(function () { - var value; - if (this.tagName === 'SELECT') { - value = this.options[this.selectedIndex].text; - } else if (this.tagName === 'TEXTAREA') { - value = $(this).html(); - } else { - value = $(this).val(); - } - if (value === '') { - value = '[empty]'; - } - $(this).before('
      ' + value + '
      ').hide(); - }); - $('.sticky-value').on('click', function () { - $(this).hide(); - $(this).next().show().get(0).focus(); - }); - }; - - var initPreviewDiffLinks = function () { - $('.preview-code,.diff-code').on('click', function () { - var $modal = $('#preview-modal'); - var $link = $(this); - $modal.find('.modal-title').text($link.data('title')); - $modal.find('.modal-body').html('Loading ...'); - $modal.modal('show'); - $.ajax({ - type: 'POST', - cache: false, - url: $link.prop('href'), - data: $('.default-view form').serializeArray(), - success: function (data) { - $modal.find('.modal-body').html(data); - $modal.find('.content').css('max-height', ($(window).height() - 200) + 'px'); - }, - error: function (XMLHttpRequest, textStatus, errorThrown) { - $modal.find('.modal-body').html('
      ' + XMLHttpRequest.responseText + '
      '); - } - }); - return false; - }); - }; - - var initConfirmationCheckboxes = function () { - var $checkAll = $('#check-all'); - $checkAll.click(function () { - $('.default-view-files table .check input').prop('checked', this.checked); - }); - $('.default-view-files table .check input').click(function () { - $checkAll.prop('checked', !$('.default-view-files table .check input:not(:checked)').length); - }); - $checkAll.prop('checked', !$('.default-view-files table .check input:not(:checked)').length); - }; - - return { - init: function () { - initHintBlocks(); - initStickyInputs(); - initPreviewDiffLinks(); - initConfirmationCheckboxes(); - - // model generator: hide class name input when table name input contains * - $('#model-generator #generator-tablename').on('change', function () { - $('#model-generator .field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); - }).change(); - - // hide Generate button if any input is changed - $('.default-view .form-group input,select,textarea').change(function () { - $('.default-view-results,.default-view-files').hide(); - $('.default-view button[name="generate"]').hide(); - }); - - $('.module-form #generator-moduleclass').change(function () { - var value = $(this).val().match(/(\w+)\\\w+$/); - var $idInput = $('#generator-moduleid'); - if (value && value[1] && $idInput.val() == '') { - $idInput.val(value[1]); - } - }); - } - }; -})(jQuery); diff --git a/framework/yii/gii/assets/logo.png b/framework/yii/gii/assets/logo.png deleted file mode 100644 index e48b5aad64109229acee2c02708f1d2ab885936a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6677 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 diff --git a/framework/yii/gii/assets/main.css b/framework/yii/gii/assets/main.css deleted file mode 100644 index 1a4f794..0000000 --- a/framework/yii/gii/assets/main.css +++ /dev/null @@ -1,211 +0,0 @@ -body { - padding-top: 70px; -} - -.footer { - border-top: 1px solid #ddd; - margin-top: 30px; - padding: 15px 0 30px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.navbar-brand { - padding: 0; - margin: 0; -} - -.default-index .generator { - min-height: 200px; - margin-bottom: 20px; -} - -.list-group .glyphicon { - float: right; -} - -.popover { - max-width: 400px; - width: 400px; -} - -.hint-block { - display: none; -} - -.default-view .sticky-value { - padding: 6px 12px; - background: lightyellow; - white-space: pre; - word-wrap: break-word; -} - -.default-view .form-group label.help { - border-bottom: 1px dashed #888; - cursor: help; -} - -.default-view .modal-dialog { - width: 800px; -} - -.default-view .modal-dialog .error { - color: #d9534f; -} - -.default-view .modal-dialog .content { - background: #fafafa; - border-left: #eee 5px solid; - padding: 5px 10px; - overflow: auto; -} - -.default-view .modal-dialog code { - background: transparent; -} - -.default-view-files table .action { - width: 100px; -} - -.default-view-files table .check { - width: 25px; - text-align: center; -} - -.default-view-results pre { - overflow: auto; - background-color: #333; - max-height: 300px; - color: white; - padding: 10px; - border-radius: 0; - white-space: nowrap; -} - -.default-view-results pre .error { - background: #FFE0E1; - color: black; - padding: 1px; -} - -.default-view-results .alert pre { - background: white; -} - -.default-diff pre { - padding: 0; - margin: 0; - background: transparent; - border: none; -} - -.default-diff pre del { - background: pink; -} - -.default-diff pre ins { - background: lightgreen; - text-decoration: none; -} - - -.Differences { - width: 100%; - border-collapse: collapse; - border-spacing: 0; - empty-cells: show; -} - -.Differences thead { - display: none; -} - -.Differences tbody th { - text-align: right; - background: #FAFAFA; - padding: 1px 2px; - border-right: 1px solid #eee; - vertical-align: top; - font-size: 13px; - font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; - font-weight: normal; - color: #999; - width: 5px; -} - -.Differences td { - padding: 1px 2px; - font-size: 13px; - font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; -} - -.DifferencesSideBySide .ChangeInsert td.Left { - background: #dfd; -} - -.DifferencesSideBySide .ChangeInsert td.Right { - background: #cfc; -} - -.DifferencesSideBySide .ChangeDelete td.Left { - background: #f88; -} - -.DifferencesSideBySide .ChangeDelete td.Right { - background: #faa; -} - -.DifferencesSideBySide .ChangeReplace .Left { - background: #fe9; -} - -.DifferencesSideBySide .ChangeReplace .Right { - background: #fd8; -} - -.Differences ins, .Differences del { - text-decoration: none; -} - -.DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del { - background: #fc0; -} - -.Differences .Skipped { - background: #f7f7f7; -} - -.DifferencesInline .ChangeReplace .Left, -.DifferencesInline .ChangeDelete .Left { - background: #fdd; -} - -.DifferencesInline .ChangeReplace .Right, -.DifferencesInline .ChangeInsert .Right { - background: #dfd; -} - -.DifferencesInline .ChangeReplace ins { - background: #9e9; -} - -.DifferencesInline .ChangeReplace del { - background: #e99; -} - -/* additional styles for typeahead.js-bootstrap.css */ -.twitter-typeahead { - display: block !important; -} -.twitter-typeahead .tt-hint { - padding: 6px 12px !important; -} diff --git a/framework/yii/gii/assets/typeahead.js b/framework/yii/gii/assets/typeahead.js deleted file mode 100644 index 9365bd6..0000000 --- a/framework/yii/gii/assets/typeahead.js +++ /dev/null @@ -1,1139 +0,0 @@ -/*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT - */ - -(function($) { - var VERSION = "0.9.3"; - var utils = { - isMsie: function() { - var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); - return match ? parseInt(match[2], 10) : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - bindAll: function(obj) { - var val; - for (var key in obj) { - $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); - } - }, - indexOf: function(haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return i; - } - } - return -1; - }, - each: $.each, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - tokenizeQuery: function(str) { - return $.trim(str).toLowerCase().split(/[\s]+/); - }, - tokenizeText: function(str) { - return $.trim(str).toLowerCase().split(/[\s\-_]+/); - }, - getProtocol: function() { - return location.protocol; - }, - noop: function() {} - }; - var EventTarget = function() { - var eventSplitter = /\s+/; - return { - on: function(events, callback) { - var event; - if (!callback) { - return this; - } - this._callbacks = this._callbacks || {}; - events = events.split(eventSplitter); - while (event = events.shift()) { - this._callbacks[event] = this._callbacks[event] || []; - this._callbacks[event].push(callback); - } - return this; - }, - trigger: function(events, data) { - var event, callbacks; - if (!this._callbacks) { - return this; - } - events = events.split(eventSplitter); - while (event = events.shift()) { - if (callbacks = this._callbacks[event]) { - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].call(this, { - type: event, - data: data - }); - } - } - } - return this; - } - }; - }(); - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - utils.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (utils.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return utils.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: utils.noop, - set: utils.noop, - remove: utils.noop, - clear: utils.noop, - isExpired: utils.noop - }; - } - utils.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(utils.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var RequestCache = function() { - function RequestCache(o) { - utils.bindAll(this); - o = o || {}; - this.sizeLimit = o.sizeLimit || 10; - this.cache = {}; - this.cachedKeysByAge = []; - } - utils.mixin(RequestCache.prototype, { - get: function(url) { - return this.cache[url]; - }, - set: function(url, resp) { - var requestToEvict; - if (this.cachedKeysByAge.length === this.sizeLimit) { - requestToEvict = this.cachedKeysByAge.shift(); - delete this.cache[requestToEvict]; - } - this.cache[url] = resp; - this.cachedKeysByAge.push(url); - } - }); - return RequestCache; - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; - function Transport(o) { - utils.bindAll(this); - o = utils.isString(o) ? { - url: o - } : o; - requestCache = requestCache || new RequestCache(); - maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; - this.url = o.url; - this.wildcard = o.wildcard || "%QUERY"; - this.filter = o.filter; - this.replace = o.replace; - this.ajaxSettings = { - type: "get", - cache: o.cache, - timeout: o.timeout, - dataType: o.dataType || "json", - beforeSend: o.beforeSend - }; - this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); - } - utils.mixin(Transport.prototype, { - _get: function(url, cb) { - var that = this; - if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - cb && cb(data); - requestCache.set(url, resp); - } - }, - _sendRequest: function(url) { - var that = this, jqXhr = pendingRequests[url]; - if (!jqXhr) { - incrementPendingRequests(); - jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); - } - return jqXhr; - function always() { - decrementPendingRequests(); - pendingRequests[url] = null; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(query, cb) { - var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; - cb = cb || utils.noop; - url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); - if (resp = requestCache.get(url)) { - utils.defer(function() { - cb(that.filter ? that.filter(resp) : resp); - }); - } else { - this._get(url, cb); - } - return !!resp; - } - }); - return Transport; - function incrementPendingRequests() { - pendingRequestsCount++; - } - function decrementPendingRequests() { - pendingRequestsCount--; - } - function belowPendingRequestsThreshold() { - return pendingRequestsCount < maxPendingRequests; - } - }(); - var Dataset = function() { - var keys = { - thumbprint: "thumbprint", - protocol: "protocol", - itemHash: "itemHash", - adjacencyList: "adjacencyList" - }; - function Dataset(o) { - utils.bindAll(this); - if (utils.isString(o.template) && !o.engine) { - $.error("no template engine specified"); - } - if (!o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.name = o.name || utils.getUniqueId(); - this.limit = o.limit || 5; - this.minLength = o.minLength || 1; - this.header = o.header; - this.footer = o.footer; - this.valueKey = o.valueKey || "value"; - this.template = compileTemplate(o.template, o.engine, this.valueKey); - this.local = o.local; - this.prefetch = o.prefetch; - this.remote = o.remote; - this.itemHash = {}; - this.adjacencyList = {}; - this.storage = o.name ? new PersistentStorage(o.name) : null; - } - utils.mixin(Dataset.prototype, { - _processLocalData: function(data) { - this._mergeProcessedData(this._processData(data)); - }, - _loadPrefetchData: function(o) { - var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; - if (this.storage) { - storedThumbprint = this.storage.get(keys.thumbprint); - storedProtocol = this.storage.get(keys.protocol); - storedItemHash = this.storage.get(keys.itemHash); - storedAdjacencyList = this.storage.get(keys.adjacencyList); - } - isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); - o = utils.isString(o) ? { - url: o - } : o; - o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; - if (storedItemHash && storedAdjacencyList && !isExpired) { - this._mergeProcessedData({ - itemHash: storedItemHash, - adjacencyList: storedAdjacencyList - }); - deferred = $.Deferred().resolve(); - } else { - deferred = $.getJSON(o.url).done(processPrefetchData); - } - return deferred; - function processPrefetchData(data) { - var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; - if (that.storage) { - that.storage.set(keys.itemHash, itemHash, o.ttl); - that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); - that.storage.set(keys.thumbprint, thumbprint, o.ttl); - that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); - } - that._mergeProcessedData(processedData); - } - }, - _transformDatum: function(datum) { - var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { - value: value, - tokens: tokens - }; - if (utils.isString(datum)) { - item.datum = {}; - item.datum[this.valueKey] = datum; - } else { - item.datum = datum; - } - item.tokens = utils.filter(item.tokens, function(token) { - return !utils.isBlankString(token); - }); - item.tokens = utils.map(item.tokens, function(token) { - return token.toLowerCase(); - }); - return item; - }, - _processData: function(data) { - var that = this, itemHash = {}, adjacencyList = {}; - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); - itemHash[id] = item; - utils.each(item.tokens, function(i, token) { - var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); - !~utils.indexOf(adjacency, id) && adjacency.push(id); - }); - }); - return { - itemHash: itemHash, - adjacencyList: adjacencyList - }; - }, - _mergeProcessedData: function(processedData) { - var that = this; - utils.mixin(this.itemHash, processedData.itemHash); - utils.each(processedData.adjacencyList, function(character, adjacency) { - var masterAdjacency = that.adjacencyList[character]; - that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; - }); - }, - _getLocalSuggestions: function(terms) { - var that = this, firstChars = [], lists = [], shortestList, suggestions = []; - utils.each(terms, function(i, term) { - var firstChar = term.charAt(0); - !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); - }); - utils.each(firstChars, function(i, firstChar) { - var list = that.adjacencyList[firstChar]; - if (!list) { - return false; - } - lists.push(list); - if (!shortestList || list.length < shortestList.length) { - shortestList = list; - } - }); - if (lists.length < firstChars.length) { - return []; - } - utils.each(shortestList, function(i, id) { - var item = that.itemHash[id], isCandidate, isMatch; - isCandidate = utils.every(lists, function(list) { - return ~utils.indexOf(list, id); - }); - isMatch = isCandidate && utils.every(terms, function(term) { - return utils.some(item.tokens, function(token) { - return token.indexOf(term) === 0; - }); - }); - isMatch && suggestions.push(item); - }); - return suggestions; - }, - initialize: function() { - var deferred; - this.local && this._processLocalData(this.local); - this.transport = this.remote ? new Transport(this.remote) : null; - deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); - this.local = this.prefetch = this.remote = null; - this.initialize = function() { - return deferred; - }; - return deferred; - }, - getSuggestions: function(query, cb) { - var that = this, terms, suggestions, cacheHit = false; - if (query.length < this.minLength) { - return; - } - terms = utils.tokenizeQuery(query); - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); - if (suggestions.length < this.limit && this.transport) { - cacheHit = this.transport.get(query, processRemoteData); - } - !cacheHit && cb && cb(suggestions); - function processRemoteData(data) { - suggestions = suggestions.slice(0); - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), isDuplicate; - isDuplicate = utils.some(suggestions, function(suggestion) { - return item.value === suggestion.value; - }); - !isDuplicate && suggestions.push(item); - return suggestions.length < that.limit; - }); - cb && cb(suggestions); - } - } - }); - return Dataset; - function compileTemplate(template, engine, valueKey) { - var renderFn, compiledTemplate; - if (utils.isFunction(template)) { - renderFn = template; - } else if (utils.isString(template)) { - compiledTemplate = engine.compile(template); - renderFn = utils.bind(compiledTemplate.render, compiledTemplate); - } else { - renderFn = function(context) { - return "

      " + context[valueKey] + "

      "; - }; - } - return renderFn; - } - }(); - var InputView = function() { - function InputView(o) { - var that = this; - utils.bindAll(this); - this.specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); - if (!utils.isMsie()) { - this.$input.on("input.tt", this._compareQueryToInputValue); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - utils.defer(that._compareQueryToInputValue); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - utils.mixin(InputView.prototype, EventTarget, { - _handleFocus: function() { - this.trigger("focused"); - }, - _handleBlur: function() { - this.trigger("blured"); - }, - _handleSpecialKeyEvent: function($e) { - var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; - keyName && this.trigger(keyName + "Keyed", $e); - }, - _compareQueryToInputValue: function() { - var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; - if (isSameQueryExceptWhitespace) { - this.trigger("whitespaceChanged", { - value: this.query - }); - } else if (!isSameQuery) { - this.trigger("queryChanged", { - value: this.query = inputValue - }); - } - }, - destroy: function() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - }, - focus: function() { - this.$input.focus(); - }, - blur: function() { - this.$input.blur(); - }, - getQuery: function() { - return this.query; - }, - setQuery: function(query) { - this.query = query; - }, - getInputValue: function() { - return this.$input.val(); - }, - setInputValue: function(value, silent) { - this.$input.val(value); - !silent && this._compareQueryToInputValue(); - }, - getHintValue: function() { - return this.$hint.val(); - }, - setHintValue: function(value) { - this.$hint.val(value); - }, - getLanguageDirection: function() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - isOverflow: function() { - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() > this.$input.width(); - }, - isCursorAtEnd: function() { - var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; - if (utils.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - } - }); - return InputView; - function buildOverflowHelper($input) { - return $("").css({ - position: "absolute", - left: "-9999px", - visibility: "hidden", - whiteSpace: "nowrap", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function compareQueries(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; - } - }(); - var DropdownView = function() { - var html = { - suggestionsList: '' - }, css = { - suggestionsList: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - } - }; - function DropdownView(o) { - utils.bindAll(this); - this.isOpen = false; - this.isEmpty = true; - this.isMouseOverDropdown = false; - this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); - } - utils.mixin(DropdownView.prototype, EventTarget, { - _handleMouseenter: function() { - this.isMouseOverDropdown = true; - }, - _handleMouseleave: function() { - this.isMouseOverDropdown = false; - }, - _handleMouseover: function($e) { - var $suggestion = $($e.currentTarget); - this._getSuggestions().removeClass("tt-is-under-cursor"); - $suggestion.addClass("tt-is-under-cursor"); - }, - _handleSelection: function($e) { - var $suggestion = $($e.currentTarget); - this.trigger("suggestionSelected", extractSuggestion($suggestion)); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _hide: function() { - this.$menu.hide(); - }, - _moveCursor: function(increment) { - var $suggestions, $cur, nextIndex, $underCursor; - if (!this.isVisible()) { - return; - } - $suggestions = this._getSuggestions(); - $cur = $suggestions.filter(".tt-is-under-cursor"); - $cur.removeClass("tt-is-under-cursor"); - nextIndex = $suggestions.index($cur) + increment; - nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; - if (nextIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (nextIndex < -1) { - nextIndex = $suggestions.length - 1; - } - $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); - this._ensureVisibility($underCursor); - this.trigger("cursorMoved", extractSuggestion($underCursor)); - }, - _getSuggestions: function() { - return this.$menu.find(".tt-suggestions > .tt-suggestion"); - }, - _ensureVisibility: function($el) { - var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - destroy: function() { - this.$menu.off(".tt"); - this.$menu = null; - }, - isVisible: function() { - return this.isOpen && !this.isEmpty; - }, - closeUnlessMouseIsOverDropdown: function() { - if (!this.isMouseOverDropdown) { - this.close(); - } - }, - close: function() { - if (this.isOpen) { - this.isOpen = false; - this.isMouseOverDropdown = false; - this._hide(); - this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); - this.trigger("closed"); - } - }, - open: function() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function(dir) { - var ltrCss = { - left: "0", - right: "auto" - }, rtlCss = { - left: "auto", - right: " 0" - }; - dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); - }, - moveCursorUp: function() { - this._moveCursor(-1); - }, - moveCursorDown: function() { - this._moveCursor(+1); - }, - getSuggestionUnderCursor: function() { - var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - getFirstSuggestion: function() { - var $suggestion = this._getSuggestions().first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - renderSuggestions: function(dataset, suggestions) { - var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
      %body
      ', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; - if ($dataset.length === 0) { - $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); - $dataset = $("
      ").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); - } - if (suggestions.length > 0) { - this.isEmpty = false; - this.isOpen && this._show(); - elBuilder = document.createElement("div"); - fragment = document.createDocumentFragment(); - utils.each(suggestions, function(i, suggestion) { - suggestion.dataset = dataset.name; - compiledHtml = dataset.template(suggestion.datum); - elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); - $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - fragment.appendChild($el[0]); - }); - $dataset.show().find(".tt-suggestions").html(fragment); - } else { - this.clearSuggestions(dataset.name); - } - this.trigger("suggestionsRendered"); - }, - clearSuggestions: function(datasetName) { - var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); - $datasets.hide(); - $suggestions.empty(); - if (this._getSuggestions().length === 0) { - this.isEmpty = true; - this._hide(); - } - } - }); - return DropdownView; - function extractSuggestion($el) { - return $el.data("suggestion"); - } - }(); - var TypeaheadView = function() { - var html = { - wrapper: '', - hint: '', - dropdown: '' - }, css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - query: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - } - }; - if (utils.isMsie()) { - utils.mixin(css.query, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (utils.isMsie() && utils.isMsie() <= 7) { - utils.mixin(css.wrapper, { - display: "inline", - zoom: "1" - }); - utils.mixin(css.query, { - marginTop: "-1px" - }); - } - function TypeaheadView(o) { - var $menu, $input, $hint; - utils.bindAll(this); - this.$node = buildDomStructure(o.input); - this.datasets = o.datasets; - this.dir = null; - this.eventBus = o.eventBus; - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-query"); - $hint = this.$node.find(".tt-hint"); - this.dropdownView = new DropdownView({ - menu: $menu - }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); - this.inputView = new InputView({ - input: $input, - hint: $hint - }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); - } - utils.mixin(TypeaheadView.prototype, EventTarget, { - _managePreventDefault: function(e) { - var $e = e.data, hint, inputValue, preventDefault = false; - switch (e.type) { - case "tabKeyed": - hint = this.inputView.getHintValue(); - inputValue = this.inputView.getInputValue(); - preventDefault = hint && hint !== inputValue; - break; - - case "upKeyed": - case "downKeyed": - preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; - break; - } - preventDefault && $e.preventDefault(); - }, - _setLanguageDirection: function() { - var dir = this.inputView.getLanguageDirection(); - if (dir !== this.dir) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdownView.setLanguageDirection(dir); - } - }, - _updateHint: function() { - var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; - if (hint && dropdownIsVisible && !inputHasOverflow) { - inputValue = this.inputView.getInputValue(); - query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); - escapedQuery = utils.escapeRegExChars(query); - beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); - match = beginsWithQuery.exec(hint); - this.inputView.setHintValue(inputValue + (match ? match[1] : "")); - } - }, - _clearHint: function() { - this.inputView.setHintValue(""); - }, - _clearSuggestions: function() { - this.dropdownView.clearSuggestions(); - }, - _setInputValueToQuery: function() { - this.inputView.setInputValue(this.inputView.getQuery()); - }, - _setInputValueToSuggestionUnderCursor: function(e) { - var suggestion = e.data; - this.inputView.setInputValue(suggestion.value, true); - }, - _openDropdown: function() { - this.dropdownView.open(); - }, - _closeDropdown: function(e) { - this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); - }, - _moveDropdownCursor: function(e) { - var $e = e.data; - if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { - this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); - } - }, - _handleSelection: function(e) { - var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); - if (suggestion) { - this.inputView.setInputValue(suggestion.value); - byClick ? this.inputView.focus() : e.data.preventDefault(); - byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); - this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); - } - }, - _getSuggestions: function() { - var that = this, query = this.inputView.getQuery(); - if (utils.isBlankString(query)) { - return; - } - utils.each(this.datasets, function(i, dataset) { - dataset.getSuggestions(query, function(suggestions) { - if (query === that.inputView.getQuery()) { - that.dropdownView.renderSuggestions(dataset, suggestions); - } - }); - }); - }, - _autocomplete: function(e) { - var isCursorAtEnd, ignoreEvent, query, hint, suggestion; - if (e.type === "rightKeyed" || e.type === "leftKeyed") { - isCursorAtEnd = this.inputView.isCursorAtEnd(); - ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; - if (!isCursorAtEnd || ignoreEvent) { - return; - } - } - query = this.inputView.getQuery(); - hint = this.inputView.getHintValue(); - if (hint !== "" && query !== hint) { - suggestion = this.dropdownView.getFirstSuggestion(); - this.inputView.setInputValue(suggestion.value); - this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); - } - }, - _propagateEvent: function(e) { - this.eventBus.trigger(e.type); - }, - destroy: function() { - this.inputView.destroy(); - this.dropdownView.destroy(); - destroyDomStructure(this.$node); - this.$node = null; - }, - setQuery: function(query) { - this.inputView.setQuery(query); - this.inputView.setInputValue(query); - this._clearHint(); - this._clearSuggestions(); - this._getSuggestions(); - } - }); - return TypeaheadView; - function buildDomStructure(input) { - var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); - $wrapper = $wrapper.css(css.wrapper); - $dropdown = $dropdown.css(css.dropdown); - $hint.css(css.hint).css({ - backgroundAttachment: $input.css("background-attachment"), - backgroundClip: $input.css("background-clip"), - backgroundColor: $input.css("background-color"), - backgroundImage: $input.css("background-image"), - backgroundOrigin: $input.css("background-origin"), - backgroundPosition: $input.css("background-position"), - backgroundRepeat: $input.css("background-repeat"), - backgroundSize: $input.css("background-size") - }); - $input.data("ttAttrs", { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-query").attr({ - autocomplete: "off", - spellcheck: false - }).css(css.query); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); - } - function destroyDomStructure($node) { - var $input = $node.find(".tt-query"); - utils.each($input.data("ttAttrs"), function(key, val) { - utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); - $node.remove(); - } - }(); - (function() { - var cache = {}, viewKey = "ttView", methods; - methods = { - initialize: function(datasetDefs) { - var datasets; - datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; - if (datasetDefs.length === 0) { - $.error("no datasets provided"); - } - datasets = utils.map(datasetDefs, function(o) { - var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); - if (o.name) { - cache[o.name] = dataset; - } - return dataset; - }); - return this.each(initialize); - function initialize() { - var $input = $(this), deferreds, eventBus = new EventBus({ - el: $input - }); - deferreds = utils.map(datasets, function(dataset) { - return dataset.initialize(); - }); - $input.data(viewKey, new TypeaheadView({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), - datasets: datasets - })); - $.when.apply($, deferreds).always(function() { - utils.defer(function() { - eventBus.trigger("initialized"); - }); - }); - } - }, - destroy: function() { - return this.each(destroy); - function destroy() { - var $this = $(this), view = $this.data(viewKey); - if (view) { - view.destroy(); - $this.removeData(viewKey); - } - } - }, - setQuery: function(query) { - return this.each(setQuery); - function setQuery() { - var view = $(this).data(viewKey); - view && view.setQuery(query); - } - } - }; - jQuery.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - })(); -})(window.jQuery); \ No newline at end of file diff --git a/framework/yii/gii/assets/typeahead.js-bootstrap.css b/framework/yii/gii/assets/typeahead.js-bootstrap.css deleted file mode 100644 index 987aaf5..0000000 --- a/framework/yii/gii/assets/typeahead.js-bootstrap.css +++ /dev/null @@ -1,51 +0,0 @@ -/* 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; -} diff --git a/framework/yii/gii/components/ActiveField.php b/framework/yii/gii/components/ActiveField.php deleted file mode 100644 index ae6f144..0000000 --- a/framework/yii/gii/components/ActiveField.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @since 2.0 - */ -class ActiveField extends \yii\widgets\ActiveField -{ - /** - * @var Generator - */ - public $model; - - public function init() - { - $stickyAttributes = $this->model->stickyAttributes(); - if (in_array($this->attribute, $stickyAttributes)) { - $this->sticky(); - } - $hints = $this->model->hints(); - if (isset($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 field remember its value between page reloads - * @return static the field object itself - */ - public function sticky() - { - $this->options['class'] .= ' sticky'; - 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; - } -} diff --git a/framework/yii/gii/controllers/DefaultController.php b/framework/yii/gii/controllers/DefaultController.php deleted file mode 100644 index ef104c3..0000000 --- a/framework/yii/gii/controllers/DefaultController.php +++ /dev/null @@ -1,152 +0,0 @@ - - * @since 2.0 - */ -class DefaultController extends Controller -{ - public $layout = 'generator'; - /** - * @var \yii\gii\Module - */ - public $module; - /** - * @var \yii\gii\Generator - */ - public $generator; - - public function actionIndex() - { - $this->layout = 'main'; - return $this->render('index'); - } - - public function actionView($id) - { - $generator = $this->loadGenerator($id); - $params = ['generator' => $generator, 'id' => $id]; - if (isset($_POST['preview']) || isset($_POST['generate'])) { - if ($generator->validate()) { - $generator->saveStickyAttributes(); - $files = $generator->generate(); - if (isset($_POST['generate']) && !empty($_POST['answers'])) { - $params['hasError'] = $generator->save($files, (array)$_POST['answers'], $results); - $params['results'] = $results; - } else { - $params['files'] = $files; - $params['answers'] = isset($_POST['answers']) ? $_POST['answers'] : null; - } - } - } - - return $this->render('view', $params); - } - - public function actionPreview($id, $file) - { - $generator = $this->loadGenerator($id); - if ($generator->validate()) { - foreach ($generator->generate() as $f) { - if ($f->id === $file) { - $content = $f->preview(); - if ($content !== false) { - return '
      ' . $content . ''; - } else { - return '
      Preview is not available for this file type.
      '; - } - } - } - } - throw new HttpException(404, "Code file not found: $file"); - } - - public function actionDiff($id, $file) - { - $generator = $this->loadGenerator($id); - if ($generator->validate()) { - foreach ($generator->generate() as $f) { - if ($f->id === $file) { - return $this->renderPartial('diff', [ - 'diff' => $f->diff(), - ]); - } - } - } - throw new HttpException(404, "Code file not found: $file"); - } - - /** - * Runs an action defined in the generator. - * Given an action named "xyz", the method "actionXyz()" in the generator will be called. - * If the method does not exist, a 400 HTTP exception will be thrown. - * @param string $id the ID of the generator - * @param string $name the action name - * @return mixed the result of the action. - * @throws HttpException if the action method does not exist. - */ - public function actionAction($id, $name) - { - $generator = $this->loadGenerator($id); - $method = 'action' . $name; - if (method_exists($generator, $method)) { - return $generator->$method(); - } else { - throw new HttpException(400, "Unknown generator action: $name"); - } - } - - public function createUrl($route, $params = []) - { - if (!isset($params['id']) && $this->generator !== null) { - foreach ($this->module->generators as $id => $generator) { - if ($generator === $this->generator) { - $params['id'] = $id; - break; - } - } - } - return parent::createUrl($route, $params); - } - - public function createActionUrl($name, $params = []) - { - foreach ($this->module->generators as $id => $generator) { - if ($generator === $this->generator) { - $params['id'] = $id; - break; - } - } - $params['name'] = $name; - return parent::createUrl('action', $params); - } - - /** - * Loads the generator with the specified ID. - * @param string $id the ID of the generator to be loaded. - * @return \yii\gii\Generator the loaded generator - * @throws \yii\web\HttpException - */ - protected function loadGenerator($id) - { - if (isset($this->module->generators[$id])) { - $this->generator = $this->module->generators[$id]; - $this->generator->loadStickyAttributes(); - $this->generator->load($_POST); - return $this->generator; - } else { - throw new HttpException(404, "Code generator not found: $id"); - } - } -} diff --git a/framework/yii/gii/generators/controller/Generator.php b/framework/yii/gii/generators/controller/Generator.php deleted file mode 100644 index 660ffb4..0000000 --- a/framework/yii/gii/generators/controller/Generator.php +++ /dev/null @@ -1,227 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - /** - * @var string the controller ID - */ - public $controller; - /** - * @var string the base class of the controller - */ - public $baseClass = 'yii\web\Controller'; - /** - * @var string the namespace of the controller class - */ - public $ns = 'app\controllers'; - /** - * @var string list of action IDs separated by commas or spaces - */ - public $actions = 'index'; - - /** - * @inheritdoc - */ - public function getName() - { - return 'Controller Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator helps you to quickly generate a new controller class, - one or several controller actions and their corresponding views.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - ['controller, actions, baseClass, ns', 'filter', 'filter' => 'trim'], - ['controller, baseClass', 'required'], - ['controller', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'], - ['actions', 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'], - ['baseClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - ['ns', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'baseClass' => 'Base Class', - 'controller' => 'Controller ID', - 'actions' => 'Action IDs', - 'ns' => 'Controller Namespace', - ]; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return [ - 'controller.php', - 'view.php', - ]; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return ['ns', 'baseClass']; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'controller' => 'Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: -
        -
      • order generates OrderController.php
      • -
      • order-item generates OrderItemController.php
      • -
      • admin/user generates UserController.php within the admin module.
      • -
      ', - 'actions' => 'Provide one or multiple action IDs to generate empty action method(s) in the controller. Separate multiple action IDs with commas or spaces. - Action IDs should be in lower case. For example: -
        -
      • index generates actionIndex()
      • -
      • create-order generates actionCreateOrder()
      • -
      ', - 'ns' => 'This is the namespace that the new controller class will use.', - 'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.', - ]; - } - - /** - * @inheritdoc - */ - public function successMessage() - { - $actions = $this->getActionIDs(); - if (in_array('index', $actions)) { - $route = $this->controller . '/index'; - } else { - $route = $this->controller . '/' . reset($actions); - } - $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), ['target' => '_blank']); - return "The controller has been generated successfully. You may $link."; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - - $files[] = new CodeFile( - $this->getControllerFile(), - $this->render('controller.php') - ); - - foreach ($this->getActionIDs() as $action) { - $files[] = new CodeFile( - $this->getViewFile($action), - $this->render('view.php', ['action' => $action]) - ); - } - - return $files; - } - - /** - * Normalizes [[actions]] into an array of action IDs. - * @return array an array of action IDs entered by the user - */ - public function getActionIDs() - { - $actions = array_unique(preg_split('/[\s,]+/', $this->actions, -1, PREG_SPLIT_NO_EMPTY)); - sort($actions); - return $actions; - } - - /** - * @return string the controller class name without the namespace part. - */ - public function getControllerClass() - { - return Inflector::id2camel($this->getControllerID()) . 'Controller'; - } - - /** - * @return string the controller ID (without the module ID prefix) - */ - public function getControllerID() - { - if (($pos = strrpos($this->controller, '/')) !== false) { - return substr($this->controller, $pos + 1); - } else { - return $this->controller; - } - } - - /** - * @return \yii\base\Module the module that the new controller belongs to - */ - public function getModule() - { - if (($pos = strrpos($this->controller, '/')) !== false) { - $id = substr($this->controller, 0, $pos); - if (($module = Yii::$app->getModule($id)) !== null) { - return $module; - } - } - return Yii::$app; - } - - /** - * @return string the controller class file path - */ - public function getControllerFile() - { - $module = $this->getModule(); - return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php'; - } - - /** - * @param string $action the action ID - * @return string the action view file path - */ - public function getViewFile($action) - { - $module = $this->getModule(); - return $module->getViewPath() . '/' . $this->getControllerID() . '/' . $action . '.php'; - } -} diff --git a/framework/yii/gii/generators/controller/form.php b/framework/yii/gii/generators/controller/form.php deleted file mode 100644 index e4d2947..0000000 --- a/framework/yii/gii/generators/controller/form.php +++ /dev/null @@ -1,10 +0,0 @@ -field($generator, 'controller'); -echo $form->field($generator, 'actions'); -echo $form->field($generator, 'ns'); -echo $form->field($generator, 'baseClass'); diff --git a/framework/yii/gii/generators/controller/templates/controller.php b/framework/yii/gii/generators/controller/templates/controller.php deleted file mode 100644 index 9d0d92c..0000000 --- a/framework/yii/gii/generators/controller/templates/controller.php +++ /dev/null @@ -1,28 +0,0 @@ - - -ns)): ?> -namespace ns ?>; - - -class getControllerClass() ?> extends baseClass, '\\') . "\n" ?> -{ -getActionIDs() as $action): ?> - public function action() - { - return $this->render(''); - } - - -} diff --git a/framework/yii/gii/generators/controller/templates/view.php b/framework/yii/gii/generators/controller/templates/view.php deleted file mode 100644 index 8a46f75..0000000 --- a/framework/yii/gii/generators/controller/templates/view.php +++ /dev/null @@ -1,22 +0,0 @@ - -/** - * @var yii\base\View $this - */ -" ?> - -

      getControllerID() . '/' . $action ?>

      - -

      - You may change the content of this page by modifying - the file echo __FILE__; ?>. -

      diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php deleted file mode 100644 index a38cf5e..0000000 --- a/framework/yii/gii/generators/crud/Generator.php +++ /dev/null @@ -1,387 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $modelClass; - public $moduleID; - public $controllerClass; - public $baseControllerClass = 'yii\web\Controller'; - public $indexWidgetType = 'grid'; - public $searchModelClass; - - public function getName() - { - return 'CRUD Generator'; - } - - public function getDescription() - { - return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) - operations for the specified data model.'; - } - - public function rules() - { - return array_merge(parent::rules(), [ - ['moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'], - ['modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'], - ['searchModelClass', 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], - ['modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - ['modelClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], - ['baseControllerClass', 'validateClass', 'params' => ['extends' => Controller::className()]], - ['controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], - ['controllerClass, searchModelClass', 'validateNewClass'], - ['indexWidgetType', 'in', 'range' => ['grid', 'list']], - ['modelClass', 'validateModelClass'], - ['moduleID', 'validateModuleID'], - ]); - } - - public function attributeLabels() - { - return array_merge(parent::attributeLabels(), [ - 'modelClass' => 'Model Class', - 'moduleID' => 'Module ID', - 'controllerClass' => 'Controller Class', - 'baseControllerClass' => 'Base Controller Class', - 'indexWidgetType' => 'Widget Used in Index Page', - 'searchModelClass' => 'Search Model Class', - ]); - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. - You should provide a fully qualified class name, e.g., app\models\Post.', - 'controllerClass' => 'This is the name of the controller class to be generated. You should - provide a fully qualified namespaced class, .e.g, app\controllers\PostController.', - 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. - You should provide a fully qualified class name, e.g., yii\web\Controller.', - 'moduleID' => 'This is the ID of the module that the generated controller will belong to. - If not set, it means the controller will belong to the application.', - 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. - You may choose either GridView or ListView', - 'searchModelClass' => 'This is the class representing the data being collecting in the search form. - A fully qualified namespaced class name is required, e.g., app\models\search\PostSearch.', - ]; - } - - public function requiredTemplates() - { - return ['controller.php']; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return ['baseControllerClass', 'moduleID', 'indexWidgetType']; - } - - public function validateModelClass() - { - /** @var ActiveRecord $class */ - $class = $this->modelClass; - $pk = $class::primaryKey(); - if (empty($pk)) { - $this->addError('modelClass', "The table associated with $class must have primary key(s)."); - } - } - - public function validateModuleID() - { - if (!empty($this->moduleID)) { - $module = Yii::$app->getModule($this->moduleID); - if ($module === null) { - $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); - } - } - } - - /** - * @inheritdoc - */ - public function generate() - { - $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); - $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); - $files = [ - new CodeFile($controllerFile, $this->render('controller.php')), - new CodeFile($searchModel, $this->render('search.php')), - ]; - - $viewPath = $this->getViewPath(); - $templatePath = $this->getTemplatePath() . '/views'; - foreach (scandir($templatePath) as $file) { - if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { - $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); - } - } - - - return $files; - } - - /** - * @return string the controller ID (without the module ID prefix) - */ - public function getControllerID() - { - $pos = strrpos($this->controllerClass, '\\'); - $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); - return Inflector::camel2id($class); - } - - /** - * @return string the action view file path - */ - public function getViewPath() - { - $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); - return $module->getViewPath() . '/' . $this->getControllerID() ; - } - - public function getNameAttribute() - { - /** @var \yii\db\ActiveRecord $class */ - $class = $this->modelClass; - foreach ($class::getTableSchema()->columnNames as $name) { - if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { - return $name; - } - } - $pk = $class::primaryKey(); - return $pk[0]; - } - - /** - * @param string $attribute - * @return string - */ - public function generateActiveField($attribute) - { - $tableSchema = $this->getTableSchema(); - if (!isset($tableSchema->columns[$attribute])) { - return "\$form->field(\$model, '$attribute');"; - } - $column = $tableSchema->columns[$attribute]; - if ($column->phpType === 'boolean') { - return "\$form->field(\$model, '$attribute')->checkbox()"; - } elseif ($column->type === 'text') { - return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])"; - } else { - if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { - $input = 'passwordInput'; - } else { - $input = 'textInput'; - } - if ($column->phpType !== 'string' || $column->size === null) { - return "\$form->field(\$model, '$attribute')->$input()"; - } else { - return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])"; - } - } - } - - /** - * @param string $attribute - * @return string - */ - public function generateActiveSearchField($attribute) - { - $tableSchema = $this->getTableSchema(); - $column = $tableSchema->columns[$attribute]; - if ($column->phpType === 'boolean') { - return "\$form->field(\$model, '$attribute')->checkbox()"; - } else { - return "\$form->field(\$model, '$attribute')"; - } - } - - /** - * @param \yii\db\ColumnSchema $column - * @return string - */ - public function generateColumnFormat($column) - { - if ($column->phpType === 'boolean') { - return 'boolean'; - } elseif ($column->type === 'text') { - return 'ntext'; - } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { - return 'datetime'; - } elseif (stripos($column->name, 'email') !== false) { - return 'email'; - } elseif (stripos($column->name, 'url') !== false) { - return 'url'; - } else { - return 'text'; - } - } - - /** - * Generates validation rules for the search model. - * @return array the generated validation rules - */ - public function generateSearchRules() - { - $table = $this->getTableSchema(); - $types = []; - foreach ($table->columns as $column) { - switch ($column->type) { - case Schema::TYPE_SMALLINT: - case Schema::TYPE_INTEGER: - case Schema::TYPE_BIGINT: - $types['integer'][] = $column->name; - break; - case Schema::TYPE_BOOLEAN: - $types['boolean'][] = $column->name; - break; - case Schema::TYPE_FLOAT: - case Schema::TYPE_DECIMAL: - case Schema::TYPE_MONEY: - $types['number'][] = $column->name; - break; - case Schema::TYPE_DATE: - case Schema::TYPE_TIME: - case Schema::TYPE_DATETIME: - case Schema::TYPE_TIMESTAMP: - default: - $types['safe'][] = $column->name; - break; - } - } - - $rules = []; - foreach ($types as $type => $columns) { - $rules[] = "['" . implode(', ', $columns) . "', '$type']"; - } - - return $rules; - } - - public function getSearchAttributes() - { - return $this->getTableSchema()->getColumnNames(); - } - - /** - * Generates the attribute labels for the search model. - * @return array the generated attribute labels (name => label) - */ - public function generateSearchLabels() - { - $table = $this->getTableSchema(); - $labels = []; - foreach ($table->columns as $column) { - if (!strcasecmp($column->name, 'id')) { - $labels[$column->name] = 'ID'; - } else { - $label = Inflector::camel2words($column->name); - if (strcasecmp(substr($label, -3), ' id') === 0) { - $label = substr($label, 0, -3) . ' ID'; - } - $labels[$column->name] = $label; - } - } - return $labels; - } - - public function generateSearchConditions() - { - $table = $this->getTableSchema(); - $conditions = []; - foreach ($table->columns as $column) { - switch ($column->type) { - case Schema::TYPE_SMALLINT: - case Schema::TYPE_INTEGER: - case Schema::TYPE_BIGINT: - case Schema::TYPE_BOOLEAN: - case Schema::TYPE_FLOAT: - case Schema::TYPE_DECIMAL: - case Schema::TYPE_MONEY: - case Schema::TYPE_DATE: - case Schema::TYPE_TIME: - case Schema::TYPE_DATETIME: - case Schema::TYPE_TIMESTAMP: - $conditions[] = "\$this->addCondition(\$query, '{$column->name}');"; - break; - default: - $conditions[] = "\$this->addCondition(\$query, '{$column->name}', true);"; - break; - } - } - - return $conditions; - } - - public function generateUrlParams() - { - $pks = $this->getTableSchema()->primaryKey; - if (count($pks) === 1) { - return "'id' => \$model->{$pks[0]}"; - } else { - $params = []; - foreach ($pks as $pk) { - $params[] = "'$pk' => \$model->$pk"; - } - return implode(', ', $params); - } - } - - public function generateActionParams() - { - $pks = $this->getTableSchema()->primaryKey; - if (count($pks) === 1) { - return '$id'; - } else { - return '$' . implode(', $', $pks); - } - } - - public function generateActionParamComments() - { - $table = $this->getTableSchema(); - $pks = $table->primaryKey; - if (count($pks) === 1) { - return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; - } else { - $params = []; - foreach ($pks as $pk) { - $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; - } - return $params; - } - } - - public function getTableSchema() - { - /** @var ActiveRecord $class */ - $class = $this->modelClass; - return $class::getTableSchema(); - } -} diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php deleted file mode 100644 index fd7df05..0000000 --- a/framework/yii/gii/generators/crud/form.php +++ /dev/null @@ -1,16 +0,0 @@ -field($generator, 'modelClass'); -echo $form->field($generator, 'searchModelClass'); -echo $form->field($generator, 'controllerClass'); -echo $form->field($generator, 'baseControllerClass'); -echo $form->field($generator, 'moduleID'); -echo $form->field($generator, 'indexWidgetType')->dropDownList([ - 'grid' => 'GridView', - 'list' => 'ListView', -]); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php deleted file mode 100644 index a863b30..0000000 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ /dev/null @@ -1,155 +0,0 @@ -controllerClass); -$modelClass = StringHelper::basename($generator->modelClass); -$searchModelClass = StringHelper::basename($generator->searchModelClass); -if ($modelClass === $searchModelClass) { - $searchModelAlias = $searchModelClass.'Search'; -} - -$pks = $generator->getTableSchema()->primaryKey; -$urlParams = $generator->generateUrlParams(); -$actionParams = $generator->generateActionParams(); -$actionParamComments = $generator->generateActionParamComments(); - -echo " - -namespace controllerClass, '\\')) ?>; - -use modelClass, '\\') ?>; -use searchModelClass, '\\') ?> as ; -use yii\data\ActiveDataProvider; -use baseControllerClass, '\\') ?>; -use yii\web\HttpException; -use yii\web\VerbFilter; - -/** - * implements the CRUD actions for model. - */ -class extends baseControllerClass) . "\n" ?> -{ - public function behaviors() - { - return [ - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new ; - $dataProvider = $searchModel->search($_GET); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - 'searchModel' => $searchModel, - ]); - } - - /** - * Displays a single model. - * - * @return mixed - */ - public function actionView() - { - return $this->render('view', [ - 'model' => $this->findModel(), - ]); - } - - /** - * Creates a new model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new ; - - if ($model->load($_POST) && $model->save()) { - return $this->redirect(['view', ]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing model. - * If update is successful, the browser will be redirected to the 'view' page. - * - * @return mixed - */ - public function actionUpdate() - { - $model = $this->findModel(); - - if ($model->load($_POST) && $model->save()) { - return $this->redirect(['view', ]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * - * @return mixed - */ - public function actionDelete() - { - $this->findModel()->delete(); - return $this->redirect(['index']); - } - - /** - * Finds the model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * - * @return the loaded model - * @throws HttpException if the model cannot be found - */ - protected function findModel() - { - \$$pk"; - } - $condition = '[' . implode(', ', $condition) . ']'; -} -?> - if (($model = ::find()) !== null) { - return $model; - } else { - throw new HttpException(404, 'The requested page does not exist.'); - } - } -} diff --git a/framework/yii/gii/generators/crud/templates/search.php b/framework/yii/gii/generators/crud/templates/search.php deleted file mode 100644 index ef8c0ca..0000000 --- a/framework/yii/gii/generators/crud/templates/search.php +++ /dev/null @@ -1,83 +0,0 @@ -modelClass); -$searchModelClass = StringHelper::basename($generator->searchModelClass); -$rules = $generator->generateSearchRules(); -$labels = $generator->generateSearchLabels(); -$searchAttributes = $generator->getSearchAttributes(); -$searchConditions = $generator->generateSearchConditions(); - -echo " - -namespace searchModelClass, '\\')) ?>; - -use yii\base\Model; -use yii\data\ActiveDataProvider; -use modelClass, '\\') ?>; - -/** - * represents the model behind the search form about . - */ -class extends Model -{ - public $; - - public function rules() - { - return [ - , - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - $label): ?> - '" . addslashes($label) . "',\n" ?> - - ]; - } - - public function search($params) - { - $query = ::find(); - $dataProvider = new ActiveDataProvider([ - 'query' => $query, - ]); - - if (!($this->load($params) && $this->validate())) { - return $dataProvider; - } - - - - return $dataProvider; - } - - protected function addCondition($query, $attribute, $partialMatch = false) - { - $value = $this->$attribute; - if (trim($value) === '') { - return; - } - if ($partialMatch) { - $value = '%' . strtr($value, ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']) . '%'; - $query->andWhere(['like', $attribute, $value]); - } else { - $query->andWhere([$attribute => $value]); - } - } -} diff --git a/framework/yii/gii/generators/crud/templates/views/_form.php b/framework/yii/gii/generators/crud/templates/views/_form.php deleted file mode 100644 index 5e31364..0000000 --- a/framework/yii/gii/generators/crud/templates/views/_form.php +++ /dev/null @@ -1,44 +0,0 @@ -modelClass; -$safeAttributes = $model->safeAttributes(); -if (empty($safeAttributes)) { - $safeAttributes = $model->getTableSchema()->columnNames; -} - -echo " - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/** - * @var yii\base\View $this - * @var modelClass, '\\') ?> $model - * @var yii\widgets\ActiveForm $form - */ -?> - -
      - - $form = ActiveForm::begin(); ?> - -generateActiveField($attribute) . " ?>\n\n"; -} ?> -
      - Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
      - - ActiveForm::end(); ?> - -
      diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php deleted file mode 100644 index 03e21f2..0000000 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ /dev/null @@ -1,48 +0,0 @@ - - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/** - * @var yii\base\View $this - * @var searchModelClass, '\\') ?> $model - * @var yii\widgets\ActiveForm $form - */ -?> - - diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php deleted file mode 100644 index 02c30bf..0000000 --- a/framework/yii/gii/generators/crud/templates/views/create.php +++ /dev/null @@ -1,33 +0,0 @@ - - -use yii\helpers\Html; - -/** - * @var yii\base\View $this - * @var modelClass, '\\') ?> $model - */ - -$this->title = 'Create modelClass)) ?>'; -$this->params['breadcrumbs'][] = ['label' => 'modelClass))) ?>', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
      - -

      Html::encode($this->title) ?>

      - - echo $this->render('_form', [ - 'model' => $model, - ]); ?> - -
      diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php deleted file mode 100644 index 8543fad..0000000 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ /dev/null @@ -1,71 +0,0 @@ -generateUrlParams(); -$nameAttribute = $generator->getNameAttribute(); - -echo " - -use yii\helpers\Html; -use indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; - -/** - * @var yii\base\View $this - * @var yii\data\ActiveDataProvider $dataProvider - * @var searchModelClass, '\\') ?> $searchModel - */ - -$this->title = 'modelClass))) ?>'; -$this->params['breadcrumbs'][] = $this->title; -?> -
      - -

      Html::encode($this->title) ?>

      - - indexWidgetType === 'grid' ? "// " : "") ?>echo $this->render('_search', ['model' => $searchModel]); ?> - -

      - Html::a('Create modelClass) ?>', ['create'], ['class' => 'btn btn-success']) ?> -

      - -indexWidgetType === 'grid'): ?> - echo GridView::widget([ - 'dataProvider' => $dataProvider, - 'filterModel' => $searchModel, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - -getTableSchema()->columns as $column) { - $format = $generator->generateColumnFormat($column); - if (++$count < 6) { - echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; - } else { - echo "\t\t\t// '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; - } -} -?> - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - - echo ListView::widget([ - 'dataProvider' => $dataProvider, - 'itemOptions' => ['class' => 'item'], - 'itemView' => function ($model, $key, $index, $widget) { - return Html::a(Html::encode($model->), ['view', ]); - }, - ]); ?> - - -
      diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php deleted file mode 100644 index 903a880..0000000 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ /dev/null @@ -1,36 +0,0 @@ -generateUrlParams(); - -echo " - -use yii\helpers\Html; - -/** - * @var yii\base\View $this - * @var modelClass, '\\') ?> $model - */ - -$this->title = 'Update modelClass)) ?>: ' . $model->getNameAttribute() ?>; -$this->params['breadcrumbs'][] = ['label' => 'modelClass))) ?>', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->getNameAttribute() ?>, 'url' => ['view', ]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
      - -

      Html::encode($this->title) ?>

      - - echo $this->render('_form', [ - 'model' => $model, - ]); ?> - -
      diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php deleted file mode 100644 index a15a7b6..0000000 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ /dev/null @@ -1,53 +0,0 @@ -generateUrlParams(); - -echo " - -use yii\helpers\Html; -use yii\widgets\DetailView; - -/** - * @var yii\base\View $this - * @var modelClass, '\\') ?> $model - */ - -$this->title = $model->getNameAttribute() ?>; -$this->params['breadcrumbs'][] = ['label' => 'modelClass))) ?>', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
      - -

      Html::encode($this->title) ?>

      - -

      - Html::a('Update', ['update', ], ['class' => 'btn btn-primary']) ?> - echo Html::a('Delete', ['delete', ], [ - 'class' => 'btn btn-danger', - 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), - 'data-method' => 'post', - ]); ?> -

      - - echo DetailView::widget([ - 'model' => $model, - 'attributes' => [ -getTableSchema()->columns as $column) { - $format = $generator->generateColumnFormat($column); - echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; -} -?> - ], - ]); ?> - -
      diff --git a/framework/yii/gii/generators/form/Generator.php b/framework/yii/gii/generators/form/Generator.php deleted file mode 100644 index 749f9d8..0000000 --- a/framework/yii/gii/generators/form/Generator.php +++ /dev/null @@ -1,152 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $modelClass; - public $viewPath = '@app/views'; - public $viewName; - public $scenarioName; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Form Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator generates a view script file that displays a form to collect input for the specified model class.'; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $files[] = new CodeFile( - Yii::getAlias($this->viewPath) . '/' . $this->viewName . '.php', - $this->render('form.php') - ); - return $files; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - ['modelClass, viewName, scenarioName, viewPath', 'filter', 'filter' => 'trim'], - ['modelClass, viewName, viewPath', 'required'], - ['modelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - ['modelClass', 'validateClass', 'params' => ['extends' => Model::className()]], - ['viewName', 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'], - ['viewPath', 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'], - ['viewPath', 'validateViewPath'], - ['scenarioName', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'modelClass' => 'Model Class', - 'viewName' => 'View Name', - 'viewPath' => 'View Path', - 'scenarioName' => 'Scenario', - ]; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['form.php', 'action.php']; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return ['viewPath', 'scenarioName']; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., app\models\Post.', - 'viewName' => 'This is the view name with respect to the view path. For example, site/index would generate a site/index.php view file under the view path.', - 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., @app/views.', - 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.', - ]; - } - - /** - * @inheritdoc - */ - public function successMessage() - { - $code = highlight_string($this->render('action.php'), true); - return <<The form has been generated successfully.

      -

      You may add the following code in an appropriate controller class to invoke the view:

      -
      $code
      -EOD; - } - - /** - * Validates [[viewPath]] to make sure it is a valid path or path alias and exists. - */ - public function validateViewPath() - { - $path = Yii::getAlias($this->viewPath, false); - if ($path === false || !is_dir($path)) { - $this->addError('viewPath', 'View path does not exist.'); - } - } - - /** - * @return array list of safe attributes of [[modelClass]] - */ - public function getModelAttributes() - { - /** @var Model $model */ - $model = new $this->modelClass; - if (!empty($this->scenarioName)) { - $model->setScenario($this->scenarioName); - } - return $model->safeAttributes(); - } -} diff --git a/framework/yii/gii/generators/form/form.php b/framework/yii/gii/generators/form/form.php deleted file mode 100644 index c04a26e..0000000 --- a/framework/yii/gii/generators/form/form.php +++ /dev/null @@ -1,10 +0,0 @@ -field($generator, 'viewName'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'scenarioName'); -echo $form->field($generator, 'viewPath'); diff --git a/framework/yii/gii/generators/form/templates/action.php b/framework/yii/gii/generators/form/templates/action.php deleted file mode 100644 index adc2a59..0000000 --- a/framework/yii/gii/generators/form/templates/action.php +++ /dev/null @@ -1,28 +0,0 @@ - - -public function actionviewName), '_')) ?>() -{ - $model = new modelClass ?>scenarioName) ? "" : "(['scenario' => '{$generator->scenarioName}'])" ?>; - - if ($model->load($_POST)) { - if ($model->validate()) { - // form inputs are valid, do something here - return; - } - } - return $this->render('viewName ?>', [ - 'model' => $model, - ]); -} diff --git a/framework/yii/gii/generators/form/templates/form.php b/framework/yii/gii/generators/form/templates/form.php deleted file mode 100644 index 12c2ac4..0000000 --- a/framework/yii/gii/generators/form/templates/form.php +++ /dev/null @@ -1,35 +0,0 @@ - - -use yii\helpers\Html; -use yii\widgets\ActiveForm; - -/** - * @var yii\base\View $this - * @var modelClass ?> $model - * @var ActiveForm $form - */ -" ?> - -
      - - $form = ActiveForm::begin(); ?> - - getModelAttributes() as $attribute): ?> - $form->field($model, '') ?> - - -
      - Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?> -
      - ActiveForm::end(); ?> - -
      diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php deleted file mode 100644 index 99f2d38..0000000 --- a/framework/yii/gii/generators/model/Generator.php +++ /dev/null @@ -1,550 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $db = 'db'; - public $ns = 'app\models'; - public $tableName; - public $modelClass; - public $baseClass = 'yii\db\ActiveRecord'; - public $generateRelations = true; - public $generateLabelsFromComments = false; - - - /** - * @inheritdoc - */ - public function getName() - { - return 'Model Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator generates an ActiveRecord class for the specified database table.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - ['db, ns, tableName, modelClass, baseClass', 'filter', 'filter' => 'trim'], - ['db, ns, tableName, baseClass', 'required'], - ['db, modelClass', 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], - ['ns, baseClass', 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], - ['tableName', 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], - ['db', 'validateDb'], - ['ns', 'validateNamespace'], - ['tableName', 'validateTableName'], - ['modelClass', 'validateModelClass', 'skipOnEmpty' => false], - ['baseClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], - ['generateRelations, generateLabelsFromComments', 'boolean'], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'ns' => 'Namespace', - 'db' => 'Database Connection ID', - 'tableName' => 'Table Name', - 'modelClass' => 'Model Class', - 'baseClass' => 'Base Class', - 'generateRelations' => 'Generate Relations', - 'generateLabelsFromComments' => 'Generate Labels from DB Comments', - ]; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., app\models', - 'db' => 'This is the ID of the DB application component.', - 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. tbl_post. - The table name may consist of the DB schema part if needed, e.g. public.tbl_post. - The table name may end with asterisk to match multiple table names, e.g. tbl_* - will match tables who name starts with tbl_. In this case, multiple ActiveRecord classes - will be generated, one for each matching table name; and the class names will be generated from - the matching characters. For example, table tbl_post will generate Post - class.', - 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain - the namespace part as it is specified in "Namespace". You do not need to specify the class name - if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', - 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', - 'generateRelations' => 'This indicates whether the generator should generate relations based on - foreign key constraints it detects in the database. Note that if your database contains too many tables, - you may want to uncheck this option to accelerate the code generation proc ess.', - 'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels - by using the comments of the corresponding DB columns.', - ]; - } - - /** - * @inheritdoc - */ - public function autoCompleteData() - { - return [ - 'tableName' => function () { - return $this->getDbConnection()->getSchema()->getTableNames(); - }, - ]; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['model.php']; - } - - /** - * @inheritdoc - */ - public function stickyAttributes() - { - return ['ns', 'db', 'baseClass', 'generateRelations', 'generateLabelsFromComments']; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $relations = $this->generateRelations(); - $db = $this->getDbConnection(); - foreach ($this->getTableNames() as $tableName) { - $className = $this->generateClassName($tableName); - $tableSchema = $db->getTableSchema($tableName); - $params = [ - 'tableName' => $tableName, - 'className' => $className, - 'tableSchema' => $tableSchema, - 'labels' => $this->generateLabels($tableSchema), - 'rules' => $this->generateRules($tableSchema), - 'relations' => isset($relations[$className]) ? $relations[$className] : [], - ]; - $files[] = new CodeFile( - Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php', - $this->render('model.php', $params) - ); - } - - return $files; - } - - /** - * Generates the attribute labels for the specified table. - * @param \yii\db\TableSchema $table the table schema - * @return array the generated attribute labels (name => label) - */ - public function generateLabels($table) - { - $labels = []; - foreach ($table->columns as $column) { - if ($this->generateLabelsFromComments && !empty($column->comment)) { - $labels[$column->name] = $column->comment; - } elseif (!strcasecmp($column->name, 'id')) { - $labels[$column->name] = 'ID'; - } else { - $label = Inflector::camel2words($column->name); - if (strcasecmp(substr($label, -3), ' id') === 0) { - $label = substr($label, 0, -3) . ' ID'; - } - $labels[$column->name] = $label; - } - } - return $labels; - } - - /** - * Generates validation rules for the specified table. - * @param \yii\db\TableSchema $table the table schema - * @return array the generated validation rules - */ - public function generateRules($table) - { - $types = []; - $lengths = []; - foreach ($table->columns as $column) { - if ($column->autoIncrement) { - continue; - } - if (!$column->allowNull && $column->defaultValue === null) { - $types['required'][] = $column->name; - } - switch ($column->type) { - case Schema::TYPE_SMALLINT: - case Schema::TYPE_INTEGER: - case Schema::TYPE_BIGINT: - $types['integer'][] = $column->name; - break; - case Schema::TYPE_BOOLEAN: - $types['boolean'][] = $column->name; - break; - case Schema::TYPE_FLOAT: - case Schema::TYPE_DECIMAL: - case Schema::TYPE_MONEY: - $types['number'][] = $column->name; - break; - case Schema::TYPE_DATE: - case Schema::TYPE_TIME: - case Schema::TYPE_DATETIME: - case Schema::TYPE_TIMESTAMP: - $types['safe'][] = $column->name; - break; - default: // strings - if ($column->size > 0) { - $lengths[$column->size][] = $column->name; - } else { - $types['string'][] = $column->name; - } - } - } - - $rules = []; - foreach ($types as $type => $columns) { - $rules[] = "['" . implode(', ', $columns) . "', '$type']"; - } - foreach ($lengths as $length => $columns) { - $rules[] = "['" . implode(', ', $columns) . "', 'string', 'max' => $length]"; - } - - return $rules; - } - - /** - * @return array the generated relation declarations - */ - protected function generateRelations() - { - if (!$this->generateRelations) { - return []; - } - - $db = $this->getDbConnection(); - - if (($pos = strpos($this->tableName, '.')) !== false) { - $schemaName = substr($this->tableName, 0, $pos); - } else { - $schemaName = ''; - } - - $relations = []; - foreach ($db->getSchema()->getTableSchemas($schemaName) as $table) { - $tableName = $table->name; - $className = $this->generateClassName($tableName); - foreach ($table->foreignKeys as $refs) { - $refTable = $refs[0]; - unset($refs[0]); - $fks = array_keys($refs); - $refClassName = $this->generateClassName($refTable); - - // Add relation for this table - $link = $this->generateRelationLink(array_flip($refs)); - $relationName = $this->generateRelationName($relations, $className, $table, $fks[0], false); - $relations[$className][$relationName] = [ - "return \$this->hasOne($refClassName::className(), $link);", - $refClassName, - false, - ]; - - // Add relation for the referenced table - $hasMany = false; - foreach ($fks as $key) { - if (!in_array($key, $table->primaryKey, true)) { - $hasMany = true; - break; - } - } - $link = $this->generateRelationLink($refs); - $relationName = $this->generateRelationName($relations, $refClassName, $refTable, $className, $hasMany); - $relations[$refClassName][$relationName] = [ - "return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "($refClassName::className(), $link);", - $className, - $hasMany, - ]; - } - - if (($fks = $this->checkPivotTable($table)) === false) { - continue; - } - $table0 = $fks[$table->primaryKey[0]][0]; - $table1 = $fks[$table->primaryKey[1]][0]; - $className0 = $this->generateClassName($table0); - $className1 = $this->generateClassName($table1); - - $link = $this->generateRelationLink([$fks[$table->primaryKey[1]][1] => $table->primaryKey[1]]); - $viaLink = $this->generateRelationLink([$table->primaryKey[0] => $fks[$table->primaryKey[0]][1]]); - $relationName = $this->generateRelationName($relations, $className0, $db->getTableSchema($table0), $table->primaryKey[1], true); - $relations[$className0][$relationName] = [ - "return \$this->hasMany($className1::className(), $link)->viaTable('{$table->name}', $viaLink);", - $className0, - true, - ]; - - $link = $this->generateRelationLink([$fks[$table->primaryKey[0]][1] => $table->primaryKey[0]]); - $viaLink = $this->generateRelationLink([$table->primaryKey[1] => $fks[$table->primaryKey[1]][1]]); - $relationName = $this->generateRelationName($relations, $className1, $db->getTableSchema($table1), $table->primaryKey[0], true); - $relations[$className1][$relationName] = [ - "return \$this->hasMany($className0::className(), $link)->viaTable('{$table->name}', $viaLink);", - $className1, - true, - ]; - } - return $relations; - } - - /** - * Generates the link parameter to be used in generating the relation declaration. - * @param array $refs reference constraint - * @return string the generated link parameter. - */ - protected function generateRelationLink($refs) - { - $pairs = []; - foreach ($refs as $a => $b) { - $pairs[] = "'$a' => '$b'"; - } - return '[' . implode(', ', $pairs) . ']'; - } - - /** - * Checks if the given table is a pivot table. - * For simplicity, this method only deals with the case where the pivot contains two PK columns, - * each referencing a column in a different table. - * @param \yii\db\TableSchema the table being checked - * @return array|boolean the relevant foreign key constraint information if the table is a pivot table, - * or false if the table is not a pivot table. - */ - protected function checkPivotTable($table) - { - $pk = $table->primaryKey; - if (count($pk) !== 2) { - return false; - } - $fks = []; - foreach ($table->foreignKeys as $refs) { - if (count($refs) === 2) { - if (isset($refs[$pk[0]])) { - $fks[$pk[0]] = [$refs[0], $refs[$pk[0]]]; - } elseif (isset($refs[$pk[1]])) { - $fks[$pk[1]] = [$refs[0], $refs[$pk[1]]]; - } - } - } - if (count($fks) === 2 && $fks[$pk[0]][0] !== $fks[$pk[1]][0]) { - return $fks; - } else { - return false; - } - } - - /** - * Generate a relation name for the specified table and a base name. - * @param array $relations the relations being generated currently. - * @param string $className the class name that will contain the relation declarations - * @param \yii\db\TableSchema $table the table schema - * @param string $key a base name that the relation name may be generated from - * @param boolean $multiple whether this is a has-many relation - * @return string the relation name - */ - protected function generateRelationName($relations, $className, $table, $key, $multiple) - { - if (strcasecmp(substr($key, -2), 'id') === 0 && strcasecmp($key, 'id')) { - $key = rtrim(substr($key, 0, -2), '_'); - } - if ($multiple) { - $key = Inflector::pluralize($key); - } - $name = $rawName = Inflector::id2camel($key, '_'); - $i = 0; - while (isset($table->columns[$name])) { - $name = $rawName . ($i++); - } - while (isset($relations[$className][$name])) { - $name = $rawName . ($i++); - } - - return $name; - } - - /** - * Validates the [[db]] attribute. - */ - public function validateDb() - { - if (Yii::$app->hasComponent($this->db) === false) { - $this->addError('db', 'There is no application component named "db".'); - } elseif (!Yii::$app->getComponent($this->db) instanceof Connection) { - $this->addError('db', 'The "db" application component must be a DB connection instance.'); - } - } - - /** - * Validates the [[ns]] attribute. - */ - public function validateNamespace() - { - $this->ns = ltrim($this->ns, '\\'); - $path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false); - if ($path === false) { - $this->addError('ns', 'Namespace must be associated with an existing directory.'); - } - } - - /** - * Validates the [[modelClass]] attribute. - */ - public function validateModelClass() - { - if ($this->isReservedKeyword($this->modelClass)) { - $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); - } - if (substr($this->tableName, -1) !== '*' && $this->modelClass == '') { - $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); - } - } - - /** - * Validates the [[tableName]] attribute. - */ - public function validateTableName() - { - if (($pos = strpos($this->tableName, '*')) !== false && substr($this->tableName, -1) !== '*') { - $this->addError('tableName', 'Asterisk is only allowed as the last character.'); - return; - } - $tables = $this->getTableNames(); - if (empty($tables)) { - $this->addError('tableName', "Table '{$this->tableName}' does not exist."); - } else { - foreach ($tables as $table) { - $class = $this->generateClassName($table); - if ($this->isReservedKeyword($class)) { - $this->addError('tableName', "Table '$table' will generate a class which is a reserved PHP keyword."); - break; - } - } - } - } - - private $_tableNames; - private $_classNames; - - /** - * @return array the table names that match the pattern specified by [[tableName]]. - */ - protected function getTableNames() - { - if ($this->_tableNames !== null) { - return $this->_tableNames; - } - $db = $this->getDbConnection(); - if ($db === null) { - return []; - } - $tableNames = []; - if (strpos($this->tableName, '*') !== false) { - if (($pos = strrpos($this->tableName, '.')) !== false) { - $schema = substr($this->tableName, 0, $pos); - $pattern = '/^' . str_replace('*', '\w+', substr($this->tableName, $pos + 1)) . '$/'; - } else { - $schema = ''; - $pattern = '/^' . str_replace('*', '\w+', $this->tableName) . '$/'; - } - - foreach ($db->schema->getTableNames($schema) as $table) { - if (preg_match($pattern, $table)) { - $tableNames[] = $schema === '' ? $table : ($schema . '.' . $table); - } - } - } elseif (($table = $db->getTableSchema($this->tableName, true)) !== null) { - $tableNames[] = $this->tableName; - $this->_classNames[$this->tableName] = $this->modelClass; - } - return $this->_tableNames = $tableNames; - } - - /** - * Generates a class name from the specified table name. - * @param string $tableName the table name (which may contain schema prefix) - * @return string the generated class name - */ - protected function generateClassName($tableName) - { - if (isset($this->_classNames[$tableName])) { - return $this->_classNames[$tableName]; - } - - if (($pos = strrpos($tableName, '.')) !== false) { - $tableName = substr($tableName, $pos + 1); - } - - $db = $this->getDbConnection(); - $patterns = []; - if (strpos($this->tableName, '*') !== false) { - $pattern = $this->tableName; - if (($pos = strrpos($pattern, '.')) !== false) { - $pattern = substr($pattern, $pos + 1); - } - $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; - } - if (!empty($db->tablePrefix)) { - $patterns[] = "/^{$db->tablePrefix}(.*?)$/"; - $patterns[] = "/^(.*?){$db->tablePrefix}$/"; - } else { - $patterns[] = "/^tbl_(.*?)$/"; - } - - $className = $tableName; - foreach ($patterns as $pattern) { - if (preg_match($pattern, $tableName, $matches)) { - $className = $matches[1]; - break; - } - } - return $this->_classNames[$tableName] = Inflector::id2camel($className, '_'); - } - - /** - * @return Connection the DB connection as specified by [[db]]. - */ - protected function getDbConnection() - { - return Yii::$app->{$this->db}; - } -} diff --git a/framework/yii/gii/generators/model/form.php b/framework/yii/gii/generators/model/form.php deleted file mode 100644 index 9eca27c..0000000 --- a/framework/yii/gii/generators/model/form.php +++ /dev/null @@ -1,14 +0,0 @@ -field($generator, 'tableName'); -echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'ns'); -echo $form->field($generator, 'baseClass'); -echo $form->field($generator, 'db'); -echo $form->field($generator, 'generateRelations')->checkbox(); -echo $form->field($generator, 'generateLabelsFromComments')->checkbox(); diff --git a/framework/yii/gii/generators/model/templates/model.php b/framework/yii/gii/generators/model/templates/model.php deleted file mode 100644 index b445425..0000000 --- a/framework/yii/gii/generators/model/templates/model.php +++ /dev/null @@ -1,72 +0,0 @@ -label) - * @var string[] $rules list of validation rules - * @var array $relations list of relations (name=>relation declaration) - */ - -echo " - -namespace ns ?>; - -/** - * This is the model class for table "". - * -columns as $column): ?> - * @property phpType} \${$column->name}\n" ?> - - - * - $relation): ?> - * @property - - - */ -class extends baseClass, '\\') . "\n" ?> -{ - /** - * @inheritdoc - */ - public static function tableName() - { - return ''; - } - - /** - * @inheritdoc - */ - public function rules() - { - return []; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - $label): ?> - '" . addslashes($label) . "',\n" ?> - - ]; - } - $relation): ?> - - /** - * @return \yii\db\ActiveRelation - */ - public function get() - { - - } - -} diff --git a/framework/yii/gii/generators/module/Generator.php b/framework/yii/gii/generators/module/Generator.php deleted file mode 100644 index fcb385d..0000000 --- a/framework/yii/gii/generators/module/Generator.php +++ /dev/null @@ -1,165 +0,0 @@ - - * @since 2.0 - */ -class Generator extends \yii\gii\Generator -{ - public $moduleClass; - public $moduleID; - - /** - * @inheritdoc - */ - public function getName() - { - return 'Module Generator'; - } - - /** - * @inheritdoc - */ - public function getDescription() - { - return 'This generator helps you to generate the skeleton code needed by a Yii module.'; - } - - /** - * @inheritdoc - */ - public function rules() - { - return array_merge(parent::rules(), [ - ['moduleID, moduleClass', 'filter', 'filter' => 'trim'], - ['moduleID, moduleClass', 'required'], - ['moduleID', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], - ['moduleClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - ['moduleClass', 'validateModuleClass'], - ]); - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'moduleID' => 'Module ID', - 'moduleClass' => 'Module Class', - ]; - } - - /** - * @inheritdoc - */ - public function hints() - { - return [ - 'moduleID' => 'This refers to the ID of the module, e.g., admin.', - 'moduleClass' => 'This is the fully qualified class name of the module, e.g., app\modules\admin\Module.', - ]; - } - - /** - * @inheritdoc - */ - public function successMessage() - { - if (Yii::$app->hasModule($this->moduleID)) { - $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($this->moduleID), ['target' => '_blank']); - return "The module has been generated successfully. You may $link."; - } - - $output = <<The module has been generated successfully.

      -

      To access the module, you need to add this to your application configuration:

      -EOD; - $code = << [ - '{$this->moduleID}' => [ - 'class' => '{$this->moduleClass}', - ], - ], - ...... -EOD; - - return $output . '
      ' . highlight_string($code, true) . '
      '; - } - - /** - * @inheritdoc - */ - public function requiredTemplates() - { - return ['module.php', 'controller.php', 'view.php']; - } - - /** - * @inheritdoc - */ - public function generate() - { - $files = []; - $modulePath = $this->getModulePath(); - $files[] = new CodeFile( - $modulePath . '/' . StringHelper::basename($this->moduleClass) . '.php', - $this->render("module.php") - ); - $files[] = new CodeFile( - $modulePath . '/controllers/DefaultController.php', - $this->render("controller.php") - ); - $files[] = new CodeFile( - $modulePath . '/views/default/index.php', - $this->render("view.php") - ); - - return $files; - } - - /** - * Validates [[moduleClass]] to make sure it is a fully qualified class name. - */ - public function validateModuleClass() - { - if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass)) === false) { - $this->addError('moduleClass', 'Module class must be properly namespaced.'); - } - if (substr($this->moduleClass, -1, 1) == '\\') { - $this->addError('moduleClass', 'Module class name must not be empty. Please enter a fully qualified class name. e.g. "app\\modules\\admin\\Module".'); - } - } - - /** - * @return boolean the directory that contains the module class - */ - public function getModulePath() - { - return Yii::getAlias('@' . str_replace('\\', '/', substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')))); - } - - /** - * @return string the controller namespace of the module. - */ - public function getControllerNamespace() - { - return substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')) . '\controllers'; - } -} diff --git a/framework/yii/gii/generators/module/form.php b/framework/yii/gii/generators/module/form.php deleted file mode 100644 index 8a0cc88..0000000 --- a/framework/yii/gii/generators/module/form.php +++ /dev/null @@ -1,13 +0,0 @@ - -
      -field($generator, 'moduleClass'); - echo $form->field($generator, 'moduleID'); -?> -
      diff --git a/framework/yii/gii/generators/module/templates/controller.php b/framework/yii/gii/generators/module/templates/controller.php deleted file mode 100644 index 2b24e0e..0000000 --- a/framework/yii/gii/generators/module/templates/controller.php +++ /dev/null @@ -1,21 +0,0 @@ - - -namespace getControllerNamespace() ?>; - -use yii\web\Controller; - -class DefaultController extends Controller -{ - public function actionIndex() - { - return $this->render('index'); - } -} diff --git a/framework/yii/gii/generators/module/templates/module.php b/framework/yii/gii/generators/module/templates/module.php deleted file mode 100644 index ef0c55a..0000000 --- a/framework/yii/gii/generators/module/templates/module.php +++ /dev/null @@ -1,29 +0,0 @@ -moduleClass; -$pos = strrpos($className, '\\'); -$ns = ltrim(substr($className, 0, $pos), '\\'); -$className = substr($className, $pos + 1); - -echo " - -namespace ; - - -class extends \yii\base\Module -{ - public $controllerNamespace = 'getControllerNamespace() ?>'; - - public function init() - { - parent::init(); - - // custom initialization code goes here - } -} diff --git a/framework/yii/gii/generators/module/templates/view.php b/framework/yii/gii/generators/module/templates/view.php deleted file mode 100644 index 41e8bbc..0000000 --- a/framework/yii/gii/generators/module/templates/view.php +++ /dev/null @@ -1,18 +0,0 @@ - -
      -

      $this->context->action->uniqueId ?>

      -

      - This is the view content for action "$this->context->action->id ?>". - The action belongs to the controller "get_class($this->context) ?>" - in the "$this->context->module->id ?>" module. -

      -

      - You may customize this page by editing the following file:
      - __FILE__ ?> -

      -
      diff --git a/framework/yii/gii/views/default/diff.php b/framework/yii/gii/views/default/diff.php deleted file mode 100644 index 6766528..0000000 --- a/framework/yii/gii/views/default/diff.php +++ /dev/null @@ -1,15 +0,0 @@ - -
      - -
      Diff is not supported for this file type.
      - -
      Identical.
      - -
      - -
      diff --git a/framework/yii/gii/views/default/index.php b/framework/yii/gii/views/default/index.php deleted file mode 100644 index 011a44b..0000000 --- a/framework/yii/gii/views/default/index.php +++ /dev/null @@ -1,33 +0,0 @@ -controller->module->generators; -$activeGenerator = Yii::$app->controller->generator; -$this->title = 'Welcome to Gii'; -?> -
      - - -

      Start the fun with the following code generators:

      - -
      - $generator): ?> -
      -

      getName()) ?>

      -

      getDescription() ?>

      -

      $id], ['class' => 'btn btn-default']) ?>

      -
      - -
      - -

      Get More Generators

      - -
      diff --git a/framework/yii/gii/views/default/view.php b/framework/yii/gii/views/default/view.php deleted file mode 100644 index 2104d75..0000000 --- a/framework/yii/gii/views/default/view.php +++ /dev/null @@ -1,72 +0,0 @@ -title = $generator->getName(); -$templates = []; -foreach ($generator->templates as $name => $path) { - $templates[$name] = "$name ($path)"; -} -?> -
      -

      title) ?>

      - -

      getDescription() ?>

      - - "$id-generator", - 'successCssClass' => '', - 'fieldConfig' => ['class' => ActiveField::className()], - ]); ?> -
      -
      - renderFile($generator->formView(), [ - 'generator' => $generator, - 'form' => $form, - ]) ?> - field($generator, 'template')->sticky() - ->label('Code Template') - ->dropDownList($templates)->hint(' - Please select which set of the templates should be used to generated the code. - ') ?> -
      - 'preview', 'class' => 'btn btn-primary']) ?> - - - 'generate', 'class' => 'btn btn-success']) ?> - -
      -
      -
      - - render('view/results', [ - 'generator' => $generator, - 'results' => $results, - 'hasError' => $hasError, - ]); - } elseif (isset($files)) { - echo $this->render('view/files', [ - 'generator' => $generator, - 'files' => $files, - 'answers' => $answers, - ]); - } - ?> - -
      diff --git a/framework/yii/gii/views/default/view/files.php b/framework/yii/gii/views/default/view/files.php deleted file mode 100644 index 299f12b..0000000 --- a/framework/yii/gii/views/default/view/files.php +++ /dev/null @@ -1,78 +0,0 @@ - -
      -

      Click on the above Generate button to generate the files selected below:

      - - - - - - - - - - - - - - - - - - -
      Code FileAction - operation !== CodeFile::OP_SKIP) { - echo ''; - break; - } - } - ?> -
      - getRelativePath()), ['preview', 'file' => $file->id], ['class' => 'preview-code', 'data-title' => $file->getRelativePath()]) ?> - operation === CodeFile::OP_OVERWRITE): ?> - $file->id], ['class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath()]) ?> - - - operation === CodeFile::OP_SKIP) { - echo 'unchanged'; - } else { - echo $file->operation; - } - ?> - - operation === CodeFile::OP_SKIP) { - echo ' '; - } else { - echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_CREATE)); - } - ?> -
      - - -
      diff --git a/framework/yii/gii/views/default/view/results.php b/framework/yii/gii/views/default/view/results.php deleted file mode 100644 index e506a4e..0000000 --- a/framework/yii/gii/views/default/view/results.php +++ /dev/null @@ -1,18 +0,0 @@ - -
      - There was something wrong when generating the code. Please check the following messages.
      '; - } else { - echo '
      ' . $generator->successMessage() . '
      '; - } - ?> -
      -
      diff --git a/framework/yii/gii/views/layouts/generator.php b/framework/yii/gii/views/layouts/generator.php deleted file mode 100644 index bdc032e..0000000 --- a/framework/yii/gii/views/layouts/generator.php +++ /dev/null @@ -1,31 +0,0 @@ -controller->module->generators; -$activeGenerator = Yii::$app->controller->generator; -?> -beginContent('@yii/gii/views/layouts/main.php'); ?> -
      -
      -
      - $generator) { - $label = '' . Html::encode($generator->getName()); - echo Html::a($label, ['default/view', 'id' => $id], [ - 'class' => $generator === $activeGenerator ? 'list-group-item active' : 'list-group-item', - ]); - } - ?> -
      -
      -
      - -
      -
      -endContent(); ?> diff --git a/framework/yii/gii/views/layouts/main.php b/framework/yii/gii/views/layouts/main.php deleted file mode 100644 index f648999..0000000 --- a/framework/yii/gii/views/layouts/main.php +++ /dev/null @@ -1,53 +0,0 @@ - -beginPage(); ?> - - - - - <?= Html::encode($this->title) ?> - head(); ?> - - -beginBody(); ?> - Html::img($asset->baseUrl . '/logo.png'), - 'brandUrl' => ['default/index'], - 'options' => ['class' => 'navbar-inverse navbar-fixed-top'], -]); -echo Nav::widget([ - 'options' => ['class' => 'nav navbar-nav pull-right'], - 'items' => [ - ['label' => 'Home', 'url' => ['default/index']], - ['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc/guide/topics.gii'], - ['label' => 'Application', 'url' => Yii::$app->homeUrl], - ], -]); -NavBar::end(); -?> - -
      - -
      - - - -endBody(); ?> - - -endPage(); ?> From 6aa45b34d0ef54fe026c2750e953bb7ec73e3e91 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 3 Nov 2013 00:25:31 -0400 Subject: [PATCH 083/203] updated dependency. --- apps/advanced/composer.json | 4 +++- apps/basic/composer.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 6318506..1400f69 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -15,7 +15,9 @@ "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master" + "yiisoft/yii2": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ diff --git a/apps/basic/composer.json b/apps/basic/composer.json index 37bae1e..6ff18b1 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -15,7 +15,9 @@ "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master" + "yiisoft/yii2": "dev-master", + "yiisoft/yii2-debug": "dev-master", + "yiisoft/yii2-gii": "dev-master" }, "scripts": { "post-create-project-cmd": [ From 83a937990c5900db3be7f27e060473a6be3379cf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 3 Nov 2013 00:40:06 -0400 Subject: [PATCH 084/203] moved bootstrap to extension --- apps/advanced/composer.json | 3 +- apps/basic/composer.json | 1 + extensions/bootstrap/Alert.php | 149 + extensions/bootstrap/BootstrapAsset.php | 22 + extensions/bootstrap/BootstrapPluginAsset.php | 27 + extensions/bootstrap/BootstrapThemeAsset.php | 27 + extensions/bootstrap/Button.php | 62 + extensions/bootstrap/ButtonDropdown.php | 109 + extensions/bootstrap/ButtonGroup.php | 97 + extensions/bootstrap/Carousel.php | 170 + extensions/bootstrap/Collapse.php | 133 + extensions/bootstrap/Dropdown.php | 92 + extensions/bootstrap/Modal.php | 227 + extensions/bootstrap/Nav.php | 214 + extensions/bootstrap/NavBar.php | 108 + extensions/bootstrap/Progress.php | 146 + extensions/bootstrap/README.md | 32 + extensions/bootstrap/Tabs.php | 199 + extensions/bootstrap/Widget.php | 81 + .../bootstrap/assets/css/bootstrap-theme.css | 384 ++ extensions/bootstrap/assets/css/bootstrap.css | 6805 ++++++++++++++++++++ .../assets/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 14079 bytes .../assets/fonts/glyphicons-halflings-regular.svg | 228 + .../assets/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 29512 bytes .../assets/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 16448 bytes extensions/bootstrap/assets/js/bootstrap.js | 1999 ++++++ extensions/bootstrap/composer.json | 22 + extensions/gii/composer.json | 3 +- extensions/jui/composer.json | 3 +- framework/yii/bootstrap/Alert.php | 149 - framework/yii/bootstrap/BootstrapAsset.php | 22 - framework/yii/bootstrap/BootstrapPluginAsset.php | 27 - framework/yii/bootstrap/BootstrapThemeAsset.php | 27 - framework/yii/bootstrap/Button.php | 62 - framework/yii/bootstrap/ButtonDropdown.php | 109 - framework/yii/bootstrap/ButtonGroup.php | 97 - framework/yii/bootstrap/Carousel.php | 170 - framework/yii/bootstrap/Collapse.php | 133 - framework/yii/bootstrap/Dropdown.php | 92 - framework/yii/bootstrap/Modal.php | 227 - framework/yii/bootstrap/Nav.php | 214 - framework/yii/bootstrap/NavBar.php | 108 - framework/yii/bootstrap/Progress.php | 146 - framework/yii/bootstrap/Tabs.php | 199 - framework/yii/bootstrap/Widget.php | 81 - .../yii/bootstrap/assets/css/bootstrap-theme.css | 384 -- framework/yii/bootstrap/assets/css/bootstrap.css | 6805 -------------------- .../assets/fonts/glyphicons-halflings-regular.eot | Bin 14079 -> 0 bytes .../assets/fonts/glyphicons-halflings-regular.svg | 228 - .../assets/fonts/glyphicons-halflings-regular.ttf | Bin 29512 -> 0 bytes .../assets/fonts/glyphicons-halflings-regular.woff | Bin 16448 -> 0 bytes framework/yii/bootstrap/assets/js/bootstrap.js | 1999 ------ 52 files changed, 11340 insertions(+), 11282 deletions(-) create mode 100644 extensions/bootstrap/Alert.php create mode 100644 extensions/bootstrap/BootstrapAsset.php create mode 100644 extensions/bootstrap/BootstrapPluginAsset.php create mode 100644 extensions/bootstrap/BootstrapThemeAsset.php create mode 100644 extensions/bootstrap/Button.php create mode 100644 extensions/bootstrap/ButtonDropdown.php create mode 100644 extensions/bootstrap/ButtonGroup.php create mode 100644 extensions/bootstrap/Carousel.php create mode 100644 extensions/bootstrap/Collapse.php create mode 100644 extensions/bootstrap/Dropdown.php create mode 100644 extensions/bootstrap/Modal.php create mode 100644 extensions/bootstrap/Nav.php create mode 100644 extensions/bootstrap/NavBar.php create mode 100644 extensions/bootstrap/Progress.php create mode 100644 extensions/bootstrap/README.md create mode 100644 extensions/bootstrap/Tabs.php create mode 100644 extensions/bootstrap/Widget.php create mode 100644 extensions/bootstrap/assets/css/bootstrap-theme.css create mode 100644 extensions/bootstrap/assets/css/bootstrap.css create mode 100644 extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.eot create mode 100644 extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.svg create mode 100644 extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf create mode 100644 extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.woff create mode 100644 extensions/bootstrap/assets/js/bootstrap.js create mode 100644 extensions/bootstrap/composer.json delete mode 100644 framework/yii/bootstrap/Alert.php delete mode 100644 framework/yii/bootstrap/BootstrapAsset.php delete mode 100644 framework/yii/bootstrap/BootstrapPluginAsset.php delete mode 100644 framework/yii/bootstrap/BootstrapThemeAsset.php delete mode 100644 framework/yii/bootstrap/Button.php delete mode 100644 framework/yii/bootstrap/ButtonDropdown.php delete mode 100644 framework/yii/bootstrap/ButtonGroup.php delete mode 100644 framework/yii/bootstrap/Carousel.php delete mode 100644 framework/yii/bootstrap/Collapse.php delete mode 100644 framework/yii/bootstrap/Dropdown.php delete mode 100644 framework/yii/bootstrap/Modal.php delete mode 100644 framework/yii/bootstrap/Nav.php delete mode 100644 framework/yii/bootstrap/NavBar.php delete mode 100644 framework/yii/bootstrap/Progress.php delete mode 100644 framework/yii/bootstrap/Tabs.php delete mode 100644 framework/yii/bootstrap/Widget.php delete mode 100644 framework/yii/bootstrap/assets/css/bootstrap-theme.css delete mode 100644 framework/yii/bootstrap/assets/css/bootstrap.css delete mode 100644 framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot delete mode 100644 framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg delete mode 100644 framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf delete mode 100644 framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff delete mode 100644 framework/yii/bootstrap/assets/js/bootstrap.js diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 1400f69..f47f438 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -16,8 +16,9 @@ "require": { "php": ">=5.4.0", "yiisoft/yii2": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", "yiisoft/yii2-debug": "dev-master", - "yiisoft/yii2-gii": "dev-master" + "yiisoft/yii2-gii": "dev-master", }, "scripts": { "post-create-project-cmd": [ diff --git a/apps/basic/composer.json b/apps/basic/composer.json index 6ff18b1..3144aa8 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -16,6 +16,7 @@ "require": { "php": ">=5.4.0", "yiisoft/yii2": "dev-master", + "yiisoft/yii2-bootstrap": "dev-master", "yiisoft/yii2-debug": "dev-master", "yiisoft/yii2-gii": "dev-master" }, diff --git a/extensions/bootstrap/Alert.php b/extensions/bootstrap/Alert.php new file mode 100644 index 0000000..29844bd --- /dev/null +++ b/extensions/bootstrap/Alert.php @@ -0,0 +1,149 @@ + 'Say hello...', + * 'closeButton' => [ + * 'label' => '×', + * 'tag' => 'a', + * ], + * ]); + * ``` + * + * The following example will show the content enclosed between the [[begin()]] + * and [[end()]] calls within the alert box: + * + * ```php + * Alert::begin([ + * 'closeButton' => ['label' => '×'], + * ]); + * + * echo 'Say hello...'; + * + * Alert::end(); + * ``` + * + * @see http://twitter.github.io/bootstrap/javascript.html#alerts + * @author Antonio Ramirez + * @since 2.0 + */ +class Alert extends Widget +{ + /** + * @var string the body content in the alert component. Note that anything between + * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated + * as the body content, and will be rendered before this. + */ + public $body; + /** + * @var array the options for rendering the close button tag. + * The close button is displayed in the header of the modal window. Clicking + * on the button will hide the modal window. If this is null, no close button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to '×'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Alert plugin help](http://twitter.github.com/bootstrap/javascript.html#alerts) + * for the supported HTML attributes. + */ + public $closeButton = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + + $this->initOptions(); + + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderBodyBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderBodyEnd(); + echo "\n" . Html::endTag('div'); + + $this->registerPlugin('alert'); + } + + /** + * Renders the close button if any before rendering the content. + * @return string the rendering result + */ + protected function renderBodyBegin() + { + return $this->renderCloseButton(); + } + + /** + * Renders the alert body (if any). + * @return string the rendering result + */ + protected function renderBodyEnd() + { + return $this->body . "\n"; + } + + /** + * Renders the close button. + * @return string the rendering result + */ + protected function renderCloseButton() + { + if ($this->closeButton !== null) { + $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->closeButton, 'label', '×'); + if ($tag === 'button' && !isset($this->closeButton['type'])) { + $this->closeButton['type'] = 'button'; + } + return Html::tag($tag, $label, $this->closeButton); + } else { + return null; + } + } + + /** + * Initializes the widget options. + * This method sets the default values for various options. + */ + protected function initOptions() + { + $this->options = array_merge(['class' => 'fade in'], $this->options); + + Html::addCssClass($this->options, 'alert'); + + if ($this->closeButton !== null) { + $this->closeButton = array_merge([ + 'data-dismiss' => 'alert', + 'aria-hidden' => 'true', + 'class' => 'close', + ], $this->closeButton); + } + } +} diff --git a/extensions/bootstrap/BootstrapAsset.php b/extensions/bootstrap/BootstrapAsset.php new file mode 100644 index 0000000..05601c6 --- /dev/null +++ b/extensions/bootstrap/BootstrapAsset.php @@ -0,0 +1,22 @@ + + * @since 2.0 + */ +class BootstrapAsset extends AssetBundle +{ + public $sourcePath = '@yii/bootstrap/assets'; + public $css = [ + 'css/bootstrap.css', + ]; +} diff --git a/extensions/bootstrap/BootstrapPluginAsset.php b/extensions/bootstrap/BootstrapPluginAsset.php new file mode 100644 index 0000000..e291ac0 --- /dev/null +++ b/extensions/bootstrap/BootstrapPluginAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class BootstrapPluginAsset extends AssetBundle +{ + public $sourcePath = '@yii/bootstrap/assets'; + public $js = [ + 'js/bootstrap.js', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/extensions/bootstrap/BootstrapThemeAsset.php b/extensions/bootstrap/BootstrapThemeAsset.php new file mode 100644 index 0000000..81cc921 --- /dev/null +++ b/extensions/bootstrap/BootstrapThemeAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class BootstrapThemeAsset extends AssetBundle +{ + public $sourcePath = '@yii/bootstrap/assets'; + public $css = [ + 'css/bootstrap-theme.css', + ]; + public $depends = [ + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/extensions/bootstrap/Button.php b/extensions/bootstrap/Button.php new file mode 100644 index 0000000..88acab7 --- /dev/null +++ b/extensions/bootstrap/Button.php @@ -0,0 +1,62 @@ + 'Action', + * 'options' => ['class' => 'btn-lg'], + * ]); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @author Antonio Ramirez + * @since 2.0 + */ +class Button extends Widget +{ + /** + * @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. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + Html::addCssClass($this->options, 'btn'); + } + + /** + * 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/extensions/bootstrap/ButtonDropdown.php b/extensions/bootstrap/ButtonDropdown.php new file mode 100644 index 0000000..1ffde7d --- /dev/null +++ b/extensions/bootstrap/ButtonDropdown.php @@ -0,0 +1,109 @@ + 'Action', + * 'dropdown' => [ + * 'items' => [ + * ['label' => 'DropdownA', 'url' => '/'], + * ['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 = 'Button'; + /** + * @var array the HTML attributes of the button. + */ + public $options = []; + /** + * @var array the configuration array for [[Dropdown]]. + */ + public $dropdown = []; + /** + * @var boolean whether to display a group of split-styled button group. + */ + public $split = false; + + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderButton() . "\n" . $this->renderDropdown(); + $this->registerPlugin('button'); + } + + /** + * Generates the button dropdown. + * @return string the rendering result. + */ + protected function renderButton() + { + Html::addCssClass($this->options, 'btn'); + if ($this->split) { + $tag = 'button'; + $options = $this->options; + $this->options['data-toggle'] = 'dropdown'; + Html::addCssClass($this->options, 'dropdown-toggle'); + $splitButton = Button::widget([ + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->options, + ]); + } else { + $tag = 'a'; + $this->label .= ' '; + $options = $this->options; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + Html::addCssClass($options, 'dropdown-toggle'); + $options['data-toggle'] = 'dropdown'; + $splitButton = ''; + } + return Button::widget([ + 'tagName' => $tag, + 'label' => $this->label, + 'options' => $options, + 'encodeLabel' => false, + ]) . "\n" . $splitButton; + } + + /** + * Generates the dropdown menu. + * @return string the rendering result. + */ + protected function renderDropdown() + { + $config = $this->dropdown; + $config['clientOptions'] = false; + return Dropdown::widget($config); + } +} diff --git a/extensions/bootstrap/ButtonGroup.php b/extensions/bootstrap/ButtonGroup.php new file mode 100644 index 0000000..4fc2eb3 --- /dev/null +++ b/extensions/bootstrap/ButtonGroup.php @@ -0,0 +1,97 @@ + [ + * ['label' => 'A'], + * ['label' => 'B'], + * ] + * ]); + * + * // button group with an item as a string + * echo ButtonGroup::widget([ + * 'buttons' => [ + * Button::widget(['label' => 'A']), + * ['label' => 'B'], + * ] + * ]); + * ``` + * @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 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. + */ + public $buttons = []; + /** + * @var boolean whether to HTML-encode the button labels. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag('div', $this->renderButtons(), $this->options); + BootstrapAsset::register($this->getView()); + } + + /** + * Generates the buttons that compound the group as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderButtons() + { + $buttons = []; + foreach ($this->buttons as $button) { + if (is_array($button)) { + $label = ArrayHelper::getValue($button, 'label'); + $options = ArrayHelper::getValue($button, 'options'); + $buttons[] = Button::widget([ + 'label' => $label, + 'options' => $options, + 'encodeLabel' => $this->encodeLabels + ]); + } else { + $buttons[] = $button; + } + } + return implode("\n", $buttons); + } +} diff --git a/extensions/bootstrap/Carousel.php b/extensions/bootstrap/Carousel.php new file mode 100644 index 0000000..8344929 --- /dev/null +++ b/extensions/bootstrap/Carousel.php @@ -0,0 +1,170 @@ + [ + * // the item contains only the image + * '', + * // equivalent to the above + * ['content' => ''], + * // the item contains both the image and the caption + * [ + * 'content' => '', + * 'caption' => '

      This is title

      This is the caption text

      ', + * 'options' => [...], + * ], + * ] + * ]); + * ``` + * + * @see http://twitter.github.io/bootstrap/javascript.html#carousel + * @author Antonio Ramirez + * @since 2.0 + */ +class Carousel extends Widget +{ + /** + * @var array|boolean the labels for the previous and the next control buttons. + * If false, it means the previous and the next control buttons should not be displayed. + */ + public $controls = ['‹', '›']; + /** + * @var array list of slides in the carousel. Each array element represents a single + * slide with the following structure: + * + * ```php + * [ + * // required, slide content (HTML), such as an image tag + * 'content' => '', + * // optional, the caption (HTML) of the slide + * 'caption' => '

      This is title

      This is the caption text

      ', + * // optional the HTML attributes of the slide container + * 'options' => [], + * ] + * ``` + */ + public $items = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'carousel'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderIndicators() . "\n"; + echo $this->renderItems() . "\n"; + echo $this->renderControls() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('carousel'); + } + + /** + * Renders carousel indicators. + * @return string the rendering result + */ + public function renderIndicators() + { + $indicators = []; + for ($i = 0, $count = count($this->items); $i < $count; $i++) { + $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i]; + if ($i === 0) { + Html::addCssClass($options, 'active'); + } + $indicators[] = Html::tag('li', '', $options); + } + return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']); + } + + /** + * Renders carousel items as specified on [[items]]. + * @return string the rendering result + */ + public function renderItems() + { + $items = []; + for ($i = 0, $count = count($this->items); $i < $count; $i++) { + $items[] = $this->renderItem($this->items[$i], $i); + } + return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']); + } + + /** + * Renders a single carousel item + * @param string|array $item a single item from [[items]] + * @param integer $index the item index as the first item should be set to `active` + * @return string the rendering result + * @throws InvalidConfigException if the item is invalid + */ + public function renderItem($item, $index) + { + if (is_string($item)) { + $content = $item; + $caption = null; + $options = []; + } elseif (isset($item['content'])) { + $content = $item['content']; + $caption = ArrayHelper::getValue($item, 'caption'); + if ($caption !== null) { + $caption = Html::tag('div', $caption, ['class' => 'carousel-caption']); + } + $options = ArrayHelper::getValue($item, 'options', []); + } else { + throw new InvalidConfigException('The "content" option is required.'); + } + + Html::addCssClass($options, 'item'); + if ($index === 0) { + Html::addCssClass($options, 'active'); + } + + return Html::tag('div', $content . "\n" . $caption, $options); + } + + /** + * Renders previous and next control buttons. + * @throws InvalidConfigException if [[controls]] is invalid. + */ + public function renderControls() + { + if (isset($this->controls[0], $this->controls[1])) { + return Html::a($this->controls[0], '#' . $this->options['id'], [ + 'class' => 'left carousel-control', + 'data-slide' => 'prev', + ]) . "\n" + . Html::a($this->controls[1], '#' . $this->options['id'], [ + 'class' => 'right carousel-control', + 'data-slide' => 'next', + ]); + } elseif ($this->controls === false) { + return ''; + } else { + throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.'); + } + } +} diff --git a/extensions/bootstrap/Collapse.php b/extensions/bootstrap/Collapse.php new file mode 100644 index 0000000..08bde22 --- /dev/null +++ b/extensions/bootstrap/Collapse.php @@ -0,0 +1,133 @@ + [ + * // equivalent to the above + * 'Collapsible Group Item #1' => [ + * 'content' => 'Anim pariatur cliche...', + * // open its content by default + * 'contentOptions' => ['class' => 'in'] + * ], + * // another group item + * 'Collapsible Group Item #2' => [ + * 'content' => 'Anim pariatur cliche...', + * 'contentOptions' => [...], + * 'options' => [...], + * ], + * ] + * ]); + * ``` + * + * @see http://twitter.github.io/bootstrap/javascript.html#collapse + * @author Antonio Ramirez + * @since 2.0 + */ +class Collapse extends Widget +{ + /** + * @var array list of groups in the collapse widget. Each array element represents a single + * group with the following structure: + * + * ```php + * // item key is the actual group header + * 'Collapsible Group Item #1' => [ + * // required, the content (HTML) of the group + * 'content' => 'Anim pariatur cliche...', + * // optional the HTML attributes of the content group + * 'contentOptions' => [], + * // optional the HTML attributes of the group + * 'options' => [], + * ] + * ``` + */ + public $items = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'accordion'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('collapse'); + } + + /** + * Renders collapsible items as specified on [[items]]. + * @return string the rendering result + */ + public function renderItems() + { + $items = []; + $index = 0; + foreach ($this->items as $header => $item) { + $options = ArrayHelper::getValue($item, 'options', []); + Html::addCssClass($options, 'accordion-group'); + $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options); + } + + return implode("\n", $items); + } + + /** + * Renders a single collapsible item group + * @param string $header a label of the item group [[items]] + * @param array $item a single item from [[items]] + * @param integer $index the item index as each item group content must have an id + * @return string the rendering result + * @throws InvalidConfigException + */ + public function renderItem($header, $item, $index) + { + if (isset($item['content'])) { + $id = $this->options['id'] . '-collapse' . $index; + $options = ArrayHelper::getValue($item, 'contentOptions', []); + $options['id'] = $id; + Html::addCssClass($options, 'accordion-body collapse'); + + $header = Html::a($header, '#' . $id, [ + 'class' => 'accordion-toggle', + 'data-toggle' => 'collapse', + 'data-parent' => '#' . $this->options['id'] + ]) . "\n"; + + $content = Html::tag('div', $item['content'], ['class' => 'accordion-inner']) . "\n"; + } else { + throw new InvalidConfigException('The "content" option is required.'); + } + $group = []; + + $group[] = Html::tag('div', $header, ['class' => 'accordion-heading']); + $group[] = Html::tag('div', $content, $options); + + return implode("\n", $group); + } +} diff --git a/extensions/bootstrap/Dropdown.php b/extensions/bootstrap/Dropdown.php new file mode 100644 index 0000000..fecfb0b --- /dev/null +++ b/extensions/bootstrap/Dropdown.php @@ -0,0 +1,92 @@ + + * @since 2.0 + */ +class Dropdown extends Widget +{ + /** + * @var array list of menu items in the dropdown. Each array element can be either an HTML string, + * or an array representing 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 "#". + * - visible: boolean, optional, whether this menu item is visible. Defaults to true. + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * + * To insert divider use ``. + */ + public $items = []; + /** + * @var boolean whether the labels for header 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(); + Html::addCssClass($this->options, 'dropdown-menu'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems($this->items); + $this->registerPlugin('dropdown'); + } + + /** + * Renders menu items. + * @param array $items the menu items to be rendered + * @return string the rendering result. + * @throws InvalidConfigException if the label option is not specified in one of the items. + */ + protected function renderItems($items) + { + $lines = []; + foreach ($items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + if (is_string($item)) { + $lines[] = $item; + continue; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; + $options = ArrayHelper::getValue($item, 'options', []); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); + $linkOptions['tabindex'] = '-1'; + $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); + $lines[] = Html::tag('li', $content, $options); + } + + return Html::tag('ul', implode("\n", $lines), $this->options); + } +} diff --git a/extensions/bootstrap/Modal.php b/extensions/bootstrap/Modal.php new file mode 100644 index 0000000..94a3997 --- /dev/null +++ b/extensions/bootstrap/Modal.php @@ -0,0 +1,227 @@ + '

      Hello world

      ', + * 'toggleButton' => ['label' => 'click me'], + * ]); + * + * echo 'Say hello...'; + * + * Modal::end(); + * ~~~ + * + * @see http://twitter.github.io/bootstrap/javascript.html#modals + * @author Antonio Ramirez + * @author Qiang Xue + * @since 2.0 + */ +class Modal extends Widget +{ + /** + * @var string the header content in the modal window. + */ + public $header; + /** + * @var string the footer content in the modal window. + */ + public $footer; + /** + * @var array the options for rendering the close button tag. + * The close button is displayed in the header of the modal window. Clicking + * on the button will hide the modal window. If this is null, no close button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to '×'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals) + * for the supported HTML attributes. + */ + public $closeButton = []; + /** + * @var array the options for rendering the toggle button tag. + * The toggle button is used to toggle the visibility of the modal window. + * If this property is null, no toggle button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to 'Show'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals) + * for the supported HTML attributes. + */ + public $toggleButton; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + + $this->initOptions(); + + echo $this->renderToggleButton() . "\n"; + echo Html::beginTag('div', $this->options) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-dialog']) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-content']) . "\n"; + echo $this->renderHeader() . "\n"; + echo $this->renderBodyBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderBodyEnd(); + echo "\n" . $this->renderFooter(); + echo "\n" . Html::endTag('div'); // modal-content + echo "\n" . Html::endTag('div'); // modal-dialog + echo "\n" . Html::endTag('div'); + + $this->registerPlugin('modal'); + } + + /** + * Renders the header HTML markup of the modal + * @return string the rendering result + */ + protected function renderHeader() + { + $button = $this->renderCloseButton(); + if ($button !== null) { + $this->header = $button . "\n" . $this->header; + } + if ($this->header !== null) { + return Html::tag('div', "\n" . $this->header . "\n", ['class' => 'modal-header']); + } else { + return null; + } + } + + /** + * Renders the opening tag of the modal body. + * @return string the rendering result + */ + protected function renderBodyBegin() + { + return Html::beginTag('div', ['class' => 'modal-body']); + } + + /** + * Renders the closing tag of the modal body. + * @return string the rendering result + */ + protected function renderBodyEnd() + { + return Html::endTag('div'); + } + + /** + * Renders the HTML markup for the footer of the modal + * @return string the rendering result + */ + protected function renderFooter() + { + if ($this->footer !== null) { + return Html::tag('div', "\n" . $this->footer . "\n", ['class' => 'modal-footer']); + } else { + return null; + } + } + + /** + * Renders the toggle button. + * @return string the rendering result + */ + protected function renderToggleButton() + { + if ($this->toggleButton !== null) { + $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show'); + if ($tag === 'button' && !isset($this->toggleButton['type'])) { + $this->toggleButton['type'] = 'button'; + } + return Html::tag($tag, $label, $this->toggleButton); + } else { + return null; + } + } + + /** + * Renders the close button. + * @return string the rendering result + */ + protected function renderCloseButton() + { + if ($this->closeButton !== null) { + $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->closeButton, 'label', '×'); + if ($tag === 'button' && !isset($this->closeButton['type'])) { + $this->closeButton['type'] = 'button'; + } + return Html::tag($tag, $label, $this->closeButton); + } else { + return null; + } + } + + /** + * Initializes the widget options. + * This method sets the default values for various options. + */ + protected function initOptions() + { + $this->options = array_merge([ + 'class' => 'fade', + 'role' => 'dialog', + 'tabindex' => -1, + ], $this->options); + Html::addCssClass($this->options, 'modal'); + + if ($this->clientOptions !== false) { + $this->clientOptions = array_merge(['show' => false], $this->clientOptions); + } + + if ($this->closeButton !== null) { + $this->closeButton = array_merge([ + 'data-dismiss' => 'modal', + 'aria-hidden' => 'true', + 'class' => 'close', + ], $this->closeButton); + } + + if ($this->toggleButton !== null) { + $this->toggleButton = array_merge([ + 'data-toggle' => 'modal', + ], $this->toggleButton); + if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { + $this->toggleButton['data-target'] = '#' . $this->options['id']; + } + } + } +} diff --git a/extensions/bootstrap/Nav.php b/extensions/bootstrap/Nav.php new file mode 100644 index 0000000..298f9b8 --- /dev/null +++ b/extensions/bootstrap/Nav.php @@ -0,0 +1,214 @@ + [ + * [ + * 'label' => 'Home', + * 'url' => ['site/index'], + * 'linkOptions' => [...], + * ], + * [ + * 'label' => 'Dropdown', + * 'items' => [ + * [ + * 'label' => 'Level 1 -DropdownA', + * 'url' => '#', + * 'items' => [ + * ['label' => 'Level 2 -DropdownA', 'url' => '#'], + * ], + * ], + * ['label' => 'Level 1 -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 which can be either a string or an array with the following structure: + * + * - label: string, required, the nav item label. + * - url: optional, the item's URL. Defaults to "#". + * - visible: boolean, optional, whether this menu item is visible. Defaults to true. + * - 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|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. + * + * If a menu item is a string, it will be rendered directly without HTML encoding. + */ + public $items = []; + /** + * @var boolean whether the nav items labels should be HTML-encoded. + */ + public $encodeLabels = true; + /** + * @var boolean whether to automatically activate items according to whether their route setting + * matches the currently requested route. + * @see isItemActive + */ + public $activateItems = true; + /** + * @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. + * @see params + * @see isItemActive + */ + public $route; + /** + * @var array the parameters used to determine if a menu item is active or not. + * If not set, it will use `$_GET`. + * @see route + * @see isItemActive + */ + public $params; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + if ($this->route === null && Yii::$app->controller !== null) { + $this->route = Yii::$app->controller->getRoute(); + } + if ($this->params === null) { + $this->params = $_GET; + } + Html::addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + BootstrapAsset::register($this->getView()); + } + + /** + * Renders widget items. + */ + public function renderItems() + { + $items = []; + foreach ($this->items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + $items[] = $this->renderItem($item); + } + + return Html::tag('ul', implode("\n", $items), $this->options); + } + + /** + * Renders a widget's item. + * @param string|array $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 = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; + $options = ArrayHelper::getValue($item, 'options', []); + $items = ArrayHelper::getValue($item, 'items'); + $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); + + if (isset($item['active'])) { + $active = ArrayHelper::remove($item, 'active', false); + } else { + $active = $this->isItemActive($item); + } + + if ($active) { + Html::addCssClass($options, 'active'); + } + + if ($items !== null) { + $linkOptions['data-toggle'] = 'dropdown'; + Html::addCssClass($options, 'dropdown'); + Html::addCssClass($urlOptions, 'dropdown-toggle'); + $label .= ' ' . Html::tag('b', '', ['class' => 'caret']); + if (is_array($items)) { + $items = Dropdown::widget([ + 'items' => $items, + 'encodeLabels' => $this->encodeLabels, + 'clientOptions' => false, + ]); + } + } + + return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options); + } + + + /** + * Checks whether a menu item is active. + * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item. + * When the `url` option of a menu item is specified in terms of an array, its first element is treated + * as the route for the item and the rest of the elements are the associated parameters. + * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item + * be considered active. + * @param array $item the menu item to be checked + * @return boolean whether the menu item is active + */ + protected function isItemActive($item) + { + if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) { + $route = $item['url'][0]; + if ($route[0] !== '/' && Yii::$app->controller) { + $route = Yii::$app->controller->module->getUniqueId() . '/' . $route; + } + if (ltrim($route, '/') !== $this->route) { + return false; + } + unset($item['url']['#']); + if (count($item['url']) > 1) { + foreach (array_splice($item['url'], 1) as $name => $value) { + if (!isset($this->params[$name]) || $this->params[$name] != $value) { + return false; + } + } + } + return true; + } + return false; + } +} diff --git a/extensions/bootstrap/NavBar.php b/extensions/bootstrap/NavBar.php new file mode 100644 index 0000000..620f51e --- /dev/null +++ b/extensions/bootstrap/NavBar.php @@ -0,0 +1,108 @@ + 'NavBar Test']); + * echo Nav::widget([ + * 'items' => [ + * ['label' => 'Home', 'url' => ['/site/index']], + * ['label' => 'About', 'url' => ['/site/about']], + * ], + * ]); + * NavBar::end(); + * ``` + * + * @see http://twitter.github.io/bootstrap/components.html#navbar + * @author Antonio Ramirez + * @since 2.0 + */ +class NavBar extends Widget +{ + /** + * @var string the text of the brand. Note that this is not HTML-encoded. + * @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 $brandUrl = '/'; + /** + * @var array the HTML attributes of the brand link. + */ + public $brandOptions = []; + + public $screenReaderToggleText = 'Toggle navigation'; + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + Html::addCssClass($this->options, 'navbar navbar-default'); + Html::addCssClass($this->brandOptions, 'navbar-brand'); + if (empty($this->options['role'])) { + $this->options['role'] = 'navigation'; + } + + echo Html::beginTag('nav', $this->options); + echo Html::beginTag('div', ['class' => 'container']); + + echo Html::beginTag('div', ['class' => 'navbar-header']); + echo $this->renderToggleButton(); + if ($this->brandLabel !== null) { + echo Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions); + } + echo Html::endTag('div'); + + echo Html::beginTag('div', ['class' => 'collapse navbar-collapse navbar-ex1-collapse']); + } + + /** + * Renders the widget. + */ + public function run() + { + + echo Html::endTag('div'); + echo Html::endTag('div'); + echo Html::endTag('nav'); + BootstrapPluginAsset::register($this->getView()); + } + + /** + * Renders collapsible toggle button. + * @return string the rendering toggle button. + */ + protected function renderToggleButton() + { + $bar = Html::tag('span', '', ['class' => 'icon-bar']); + $screenReader = ''.$this->screenReaderToggleText.''; + return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [ + 'class' => 'navbar-toggle', + 'data-toggle' => 'collapse', + 'data-target' => '.navbar-ex1-collapse', + ]); + } +} diff --git a/extensions/bootstrap/Progress.php b/extensions/bootstrap/Progress.php new file mode 100644 index 0000000..ed2d37d --- /dev/null +++ b/extensions/bootstrap/Progress.php @@ -0,0 +1,146 @@ + 60, + * 'label' => 'test', + * ]); + * + * // styled + * echo Progress::widget([ + * 'percent' => 65, + * 'barOptions' => ['class' => 'bar-danger'] + * ]); + * + * // striped + * echo Progress::widget([ + * 'percent' => 70, + * 'barOptions' => ['class' => 'bar-warning'], + * 'options' => ['class' => 'progress-striped'] + * ]); + * + * // striped animated + * echo Progress::widget([ + * 'percent' => 70, + * 'barOptions' => ['class' => 'bar-success'], + * 'options' => ['class' => 'active progress-striped'] + * ]); + * + * // stacked bars + * echo Progress::widget([ + * 'bars' => [ + * ['percent' => 30, 'options' => ['class' => 'bar-danger']], + * ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'bar-success']], + * ['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 = []; + /** + * @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 + * [ + * // 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' => [], + * ] + */ + public $bars; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'progress'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderProgress() . "\n"; + echo Html::endTag('div') . "\n"; + BootstrapAsset::register($this->getView()); + } + + /** + * Renders the progress. + * @return string the rendering result. + * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar. + */ + protected function renderProgress() + { + if (empty($this->bars)) { + return $this->renderBar($this->percent, $this->label, $this->barOptions); + } + $bars = []; + 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($bar, 'options', []); + $bars[] = $this->renderBar($bar['percent'], $label, $options); + } + return implode("\n", $bars); + } + + /** + * 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 = []) + { + Html::addCssClass($options, 'bar'); + $options['style'] = "width:{$percent}%"; + return Html::tag('div', $label, $options); + } +} diff --git a/extensions/bootstrap/README.md b/extensions/bootstrap/README.md new file mode 100644 index 0000000..365f48f --- /dev/null +++ b/extensions/bootstrap/README.md @@ -0,0 +1,32 @@ +Twitter Bootstrap Extension for Yii 2 +===================================== + +This is the Twitter Bootstrap extension for Yii 2. It encapsulates Bootstrap components +and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins +in Yii applications extremely easy. For example, the following +single line of code in a view file would render a Bootstrap Progress plugin: + +```php + 60, 'label' => 'test']) ?> +``` + + +Installation +------------ + +The preferred way to install this extension is [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-bootstrap "*" +``` + +or add + +``` +"yiisoft/yii2-bootstrap": "*" +``` + +to the require section of your `composer.json` file. + diff --git a/extensions/bootstrap/Tabs.php b/extensions/bootstrap/Tabs.php new file mode 100644 index 0000000..2e76398 --- /dev/null +++ b/extensions/bootstrap/Tabs.php @@ -0,0 +1,199 @@ + [ + * [ + * 'label' => 'One', + * 'content' => 'Anim pariatur cliche...', + * 'active' => true + * ], + * [ + * 'label' => 'Two', + * 'content' => 'Anim pariatur cliche...', + * 'headerOptions' => [...], + * 'options' => ['id' => 'myveryownID'], + * ], + * [ + * 'label' => 'Dropdown', + * 'items' => [ + * [ + * 'label' => 'DropdownA', + * 'content' => 'DropdownA, Anim pariatur cliche...', + * ], + * [ + * 'label' => 'DropdownB', + * 'content' => 'DropdownB, 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: + * + * - 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. + * - 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. 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. + */ + public $items = []; + /** + * @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 = []; + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = []; + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; + /** + * @var string, specifies the Bootstrap tab styling. + */ + public $navType = 'nav-tabs'; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'nav ' . $this->navType); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + $this->registerPlugin('tab'); + } + + /** + * Renders tab items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + protected function renderItems() + { + $headers = []; + $panes = []; + foreach ($this->items as $n => $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); + + if (isset($item['items'])) { + $label .= ' '; + Html::addCssClass($headerOptions, 'dropdown'); + + if ($this->renderDropdown($item['items'], $panes)) { + Html::addCssClass($headerOptions, 'active'); + } + + $header = Html::a($label, "#", ['class' => 'dropdown-toggle', 'data-toggle' => 'dropdown']) . "\n" + . Dropdown::widget(['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); + + Html::addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + Html::addCssClass($options, 'active'); + Html::addCssClass($headerOptions, 'active'); + } + $header = Html::a($label, '#' . $options['id'], ['data-toggle' => 'tab']); + $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, $headerOptions); + } + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" + . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']); + } + + /** + * 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 renderDropdown(&$items, &$panes) + { + $itemActive = false; + + foreach ($items as $n => &$item) { + if (is_string($item)) { + continue; + } + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + + $content = ArrayHelper::remove($item, 'content'); + $options = ArrayHelper::remove($item, 'contentOptions', []); + Html::addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + Html::addCssClass($options, 'active'); + Html::addCssClass($item['options'], 'active'); + $itemActive = true; + } + + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n); + $item['url'] = '#' . $options['id']; + $item['linkOptions']['data-toggle'] = 'tab'; + + $panes[] = Html::tag('div', $content, $options); + + unset($item); + } + return $itemActive; + } +} diff --git a/extensions/bootstrap/Widget.php b/extensions/bootstrap/Widget.php new file mode 100644 index 0000000..ff4084d --- /dev/null +++ b/extensions/bootstrap/Widget.php @@ -0,0 +1,81 @@ + + * @author Qiang Xue + * @since 2.0 + */ +class Widget extends \yii\base\Widget +{ + /** + * @var array the HTML attributes for the widget container tag. + */ + public $options = []; + /** + * @var array the options for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible options. + * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows + * how to use the "Modal" plugin and the supported options (e.g. "remote"). + */ + public $clientOptions = []; + /** + * @var array the event handlers for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible events. + * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows + * how to use the "Modal" plugin and the supported events (e.g. "shown"). + */ + public $clientEvents = []; + + + /** + * Initializes the widget. + * This method will register the bootstrap asset bundle. If you override this method, + * make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + } + + /** + * Registers a specific Bootstrap plugin and the related events + * @param string $name the name of the Bootstrap plugin + */ + protected function registerPlugin($name) + { + $view = $this->getView(); + + BootstrapPluginAsset::register($view); + + $id = $this->options['id']; + + if ($this->clientOptions !== false) { + $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); + $js = "jQuery('#$id').$name($options);"; + $view->registerJs($js); + } + + if (!empty($this->clientEvents)) { + $js = []; + foreach ($this->clientEvents as $event => $handler) { + $js[] = "jQuery('#$id').on('$event', $handler);"; + } + $view->registerJs(implode("\n", $js)); + } + } +} diff --git a/extensions/bootstrap/assets/css/bootstrap-theme.css b/extensions/bootstrap/assets/css/bootstrap-theme.css new file mode 100644 index 0000000..ad11735 --- /dev/null +++ b/extensions/bootstrap/assets/css/bootstrap-theme.css @@ -0,0 +1,384 @@ +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn:active, +.btn.active { + background-image: none; +} + +.btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); + background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); + background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); + background-repeat: repeat-x; + border-color: #e0e0e0; + border-color: #ccc; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); +} + +.btn-default:active, +.btn-default.active { + background-color: #e6e6e6; + border-color: #e0e0e0; +} + +.btn-primary { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + background-repeat: repeat-x; + border-color: #2d6ca2; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); +} + +.btn-primary:active, +.btn-primary.active { + background-color: #3071a9; + border-color: #2d6ca2; +} + +.btn-success { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); + background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); + background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + background-repeat: repeat-x; + border-color: #419641; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); +} + +.btn-success:active, +.btn-success.active { + background-color: #449d44; + border-color: #419641; +} + +.btn-warning { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); + background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); + background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + background-repeat: repeat-x; + border-color: #eb9316; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); +} + +.btn-warning:active, +.btn-warning.active { + background-color: #ec971f; + border-color: #eb9316; +} + +.btn-danger { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); + background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); + background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + background-repeat: repeat-x; + border-color: #c12e2a; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); +} + +.btn-danger:active, +.btn-danger.active { + background-color: #c9302c; + border-color: #c12e2a; +} + +.btn-info { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); + background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); + background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + background-repeat: repeat-x; + border-color: #2aabd2; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); +} + +.btn-info:active, +.btn-info.active { + background-color: #31b0d5; + border-color: #2aabd2; +} + +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #357ebd; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); +} + +.navbar { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); + background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); + background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); + background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); + background-repeat: repeat-x; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); +} + +.navbar .navbar-nav > .active > a { + background-color: #f8f8f8; +} + +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); +} + +.navbar-inverse { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); + background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); + background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); +} + +.navbar-inverse .navbar-nav > .active > a { + background-color: #222222; +} + +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} + +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} + +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.alert-success { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); + background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); + background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + background-repeat: repeat-x; + border-color: #b2dba1; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); +} + +.alert-info { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); + background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); + background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + background-repeat: repeat-x; + border-color: #9acfea; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); +} + +.alert-warning { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); + background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); + background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + background-repeat: repeat-x; + border-color: #f5e79e; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); +} + +.alert-danger { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); + background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); + background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + background-repeat: repeat-x; + border-color: #dca7a7; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); +} + +.progress { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); + background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); + background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); +} + +.progress-bar { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); +} + +.progress-bar-success { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); + background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); + background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); +} + +.progress-bar-info { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); + background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); + background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); +} + +.progress-bar-warning { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); + background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); + background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); +} + +.progress-bar-danger { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); + background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); + background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); +} + +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #3071a9; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); + background-repeat: repeat-x; + border-color: #3278b3; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); +} + +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.panel-default > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); + background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); + background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); +} + +.panel-primary > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); + background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); + background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); +} + +.panel-success > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); + background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); + background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); +} + +.panel-info > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); + background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); + background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); +} + +.panel-warning > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); + background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); + background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); +} + +.panel-danger > .panel-heading { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); + background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); + background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); +} + +.well { + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); + background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); + background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + background-repeat: repeat-x; + border-color: #dcdcdc; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); +} \ No newline at end of file diff --git a/extensions/bootstrap/assets/css/bootstrap.css b/extensions/bootstrap/assets/css/bootstrap.css new file mode 100644 index 0000000..bbda4ee --- /dev/null +++ b/extensions/bootstrap/assets/css/bootstrap.css @@ -0,0 +1,6805 @@ +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +audio, +canvas, +video { + display: inline-block; +} + +audio:not([controls]) { + display: none; + height: 0; +} + +[hidden] { + display: none; +} + +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +a:focus { + outline: thin dotted; +} + +a:active, +a:hover { + outline: 0; +} + +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +mark { + color: #000; + background: #ff0; +} + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +pre { + white-space: pre-wrap; +} + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +img { + border: 0; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 0; +} + +fieldset { + padding: 0.35em 0.625em 0.75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} + +legend { + padding: 0; + border: 0; +} + +button, +input, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: 100%; +} + +button, +input { + line-height: normal; +} + +button, +select { + text-transform: none; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +button[disabled], +html input[disabled] { + cursor: default; +} + +input[type="checkbox"], +input[type="radio"] { + padding: 0; + box-sizing: border-box; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +textarea { + overflow: auto; + vertical-align: top; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 2cm .5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.428571429; + color: #333333; + background-color: #ffffff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input, +select[multiple], +textarea { + background-image: none; +} + +a { + color: #428bca; + text-decoration: none; +} + +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +img { + vertical-align: middle; +} + +.img-responsive { + display: block; + height: auto; + max-width: 100%; +} + +.img-rounded { + border-radius: 6px; +} + +.img-thumbnail { + display: inline-block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.img-circle { + border-radius: 50%; +} + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + border: 0; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 16.099999999999998px; + font-weight: 200; + line-height: 1.4; +} + +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} + +small { + font-size: 85%; +} + +cite { + font-style: normal; +} + +.text-muted { + color: #999999; +} + +.text-primary { + color: #428bca; +} + +.text-warning { + color: #c09853; +} + +.text-danger { + color: #b94a48; +} + +.text-success { + color: #468847; +} + +.text-info { + color: #3a87ad; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small { + font-weight: normal; + line-height: 1; + color: #999999; +} + +h1, +h2, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} + +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} + +h1, +.h1 { + font-size: 36px; +} + +h2, +.h2 { + font-size: 30px; +} + +h3, +.h3 { + font-size: 24px; +} + +h4, +.h4 { + font-size: 18px; +} + +h5, +.h5 { + font-size: 14px; +} + +h6, +.h6 { + font-size: 12px; +} + +h1 small, +.h1 small { + font-size: 24px; +} + +h2 small, +.h2 small { + font-size: 18px; +} + +h3 small, +.h3 small, +h4 small, +.h4 small { + font-size: 14px; +} + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} + +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} + +dl { + margin-bottom: 20px; +} + +dt, +dd { + line-height: 1.428571429; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} + +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + border-left: 5px solid #eeeeee; +} + +blockquote p { + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +blockquote small { + display: block; + line-height: 1.428571429; + color: #999999; +} + +blockquote small:before { + content: '\2014 \00A0'; +} + +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; +} + +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} + +blockquote.pull-right small:before { + content: ''; +} + +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} + +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 1.428571429; +} + +code, +pre { + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; +} + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + white-space: nowrap; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.428571429; + color: #333333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} + +pre.prettyprint { + margin-bottom: 20px; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.row { + margin-right: -15px; + margin-left: -15px; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11 { + float: left; +} + +.col-xs-1 { + width: 8.333333333333332%; +} + +.col-xs-2 { + width: 16.666666666666664%; +} + +.col-xs-3 { + width: 25%; +} + +.col-xs-4 { + width: 33.33333333333333%; +} + +.col-xs-5 { + width: 41.66666666666667%; +} + +.col-xs-6 { + width: 50%; +} + +.col-xs-7 { + width: 58.333333333333336%; +} + +.col-xs-8 { + width: 66.66666666666666%; +} + +.col-xs-9 { + width: 75%; +} + +.col-xs-10 { + width: 83.33333333333334%; +} + +.col-xs-11 { + width: 91.66666666666666%; +} + +.col-xs-12 { + width: 100%; +} + +@media (min-width: 768px) { + .container { + max-width: 750px; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11 { + float: left; + } + .col-sm-1 { + width: 8.333333333333332%; + } + .col-sm-2 { + width: 16.666666666666664%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-4 { + width: 33.33333333333333%; + } + .col-sm-5 { + width: 41.66666666666667%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-7 { + width: 58.333333333333336%; + } + .col-sm-8 { + width: 66.66666666666666%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-10 { + width: 83.33333333333334%; + } + .col-sm-11 { + width: 91.66666666666666%; + } + .col-sm-12 { + width: 100%; + } + .col-sm-push-1 { + left: 8.333333333333332%; + } + .col-sm-push-2 { + left: 16.666666666666664%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-4 { + left: 33.33333333333333%; + } + .col-sm-push-5 { + left: 41.66666666666667%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-7 { + left: 58.333333333333336%; + } + .col-sm-push-8 { + left: 66.66666666666666%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-10 { + left: 83.33333333333334%; + } + .col-sm-push-11 { + left: 91.66666666666666%; + } + .col-sm-pull-1 { + right: 8.333333333333332%; + } + .col-sm-pull-2 { + right: 16.666666666666664%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-4 { + right: 33.33333333333333%; + } + .col-sm-pull-5 { + right: 41.66666666666667%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-7 { + right: 58.333333333333336%; + } + .col-sm-pull-8 { + right: 66.66666666666666%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-10 { + right: 83.33333333333334%; + } + .col-sm-pull-11 { + right: 91.66666666666666%; + } + .col-sm-offset-1 { + margin-left: 8.333333333333332%; + } + .col-sm-offset-2 { + margin-left: 16.666666666666664%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-4 { + margin-left: 33.33333333333333%; + } + .col-sm-offset-5 { + margin-left: 41.66666666666667%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-7 { + margin-left: 58.333333333333336%; + } + .col-sm-offset-8 { + margin-left: 66.66666666666666%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-10 { + margin-left: 83.33333333333334%; + } + .col-sm-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 992px) { + .container { + max-width: 970px; + } + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11 { + float: left; + } + .col-md-1 { + width: 8.333333333333332%; + } + .col-md-2 { + width: 16.666666666666664%; + } + .col-md-3 { + width: 25%; + } + .col-md-4 { + width: 33.33333333333333%; + } + .col-md-5 { + width: 41.66666666666667%; + } + .col-md-6 { + width: 50%; + } + .col-md-7 { + width: 58.333333333333336%; + } + .col-md-8 { + width: 66.66666666666666%; + } + .col-md-9 { + width: 75%; + } + .col-md-10 { + width: 83.33333333333334%; + } + .col-md-11 { + width: 91.66666666666666%; + } + .col-md-12 { + width: 100%; + } + .col-md-push-0 { + left: auto; + } + .col-md-push-1 { + left: 8.333333333333332%; + } + .col-md-push-2 { + left: 16.666666666666664%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-4 { + left: 33.33333333333333%; + } + .col-md-push-5 { + left: 41.66666666666667%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-7 { + left: 58.333333333333336%; + } + .col-md-push-8 { + left: 66.66666666666666%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-10 { + left: 83.33333333333334%; + } + .col-md-push-11 { + left: 91.66666666666666%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-pull-1 { + right: 8.333333333333332%; + } + .col-md-pull-2 { + right: 16.666666666666664%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-4 { + right: 33.33333333333333%; + } + .col-md-pull-5 { + right: 41.66666666666667%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-7 { + right: 58.333333333333336%; + } + .col-md-pull-8 { + right: 66.66666666666666%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-10 { + right: 83.33333333333334%; + } + .col-md-pull-11 { + right: 91.66666666666666%; + } + .col-md-offset-0 { + margin-left: 0; + } + .col-md-offset-1 { + margin-left: 8.333333333333332%; + } + .col-md-offset-2 { + margin-left: 16.666666666666664%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-4 { + margin-left: 33.33333333333333%; + } + .col-md-offset-5 { + margin-left: 41.66666666666667%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-7 { + margin-left: 58.333333333333336%; + } + .col-md-offset-8 { + margin-left: 66.66666666666666%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-10 { + margin-left: 83.33333333333334%; + } + .col-md-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1170px; + } + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11 { + float: left; + } + .col-lg-1 { + width: 8.333333333333332%; + } + .col-lg-2 { + width: 16.666666666666664%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-4 { + width: 33.33333333333333%; + } + .col-lg-5 { + width: 41.66666666666667%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-7 { + width: 58.333333333333336%; + } + .col-lg-8 { + width: 66.66666666666666%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-10 { + width: 83.33333333333334%; + } + .col-lg-11 { + width: 91.66666666666666%; + } + .col-lg-12 { + width: 100%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-push-1 { + left: 8.333333333333332%; + } + .col-lg-push-2 { + left: 16.666666666666664%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-4 { + left: 33.33333333333333%; + } + .col-lg-push-5 { + left: 41.66666666666667%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-7 { + left: 58.333333333333336%; + } + .col-lg-push-8 { + left: 66.66666666666666%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-10 { + left: 83.33333333333334%; + } + .col-lg-push-11 { + left: 91.66666666666666%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-pull-1 { + right: 8.333333333333332%; + } + .col-lg-pull-2 { + right: 16.666666666666664%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-4 { + right: 33.33333333333333%; + } + .col-lg-pull-5 { + right: 41.66666666666667%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-7 { + right: 58.333333333333336%; + } + .col-lg-pull-8 { + right: 66.66666666666666%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-10 { + right: 83.33333333333334%; + } + .col-lg-pull-11 { + right: 91.66666666666666%; + } + .col-lg-offset-0 { + margin-left: 0; + } + .col-lg-offset-1 { + margin-left: 8.333333333333332%; + } + .col-lg-offset-2 { + margin-left: 16.666666666666664%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-4 { + margin-left: 33.33333333333333%; + } + .col-lg-offset-5 { + margin-left: 41.66666666666667%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-7 { + margin-left: 58.333333333333336%; + } + .col-lg-offset-8 { + margin-left: 66.66666666666666%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-10 { + margin-left: 83.33333333333334%; + } + .col-lg-offset-11 { + margin-left: 91.66666666666666%; + } +} + +table { + max-width: 100%; + background-color: transparent; +} + +th { + text-align: left; +} + +.table { + width: 100%; + margin-bottom: 20px; +} + +.table thead > tr > th, +.table tbody > tr > th, +.table tfoot > tr > th, +.table thead > tr > td, +.table tbody > tr > td, +.table tfoot > tr > td { + padding: 8px; + line-height: 1.428571429; + vertical-align: top; + border-top: 1px solid #dddddd; +} + +.table thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} + +.table caption + thead tr:first-child th, +.table colgroup + thead tr:first-child th, +.table thead:first-child tr:first-child th, +.table caption + thead tr:first-child td, +.table colgroup + thead tr:first-child td, +.table thead:first-child tr:first-child td { + border-top: 0; +} + +.table tbody + tbody { + border-top: 2px solid #dddddd; +} + +.table .table { + background-color: #ffffff; +} + +.table-condensed thead > tr > th, +.table-condensed tbody > tr > th, +.table-condensed tfoot > tr > th, +.table-condensed thead > tr > td, +.table-condensed tbody > tr > td, +.table-condensed tfoot > tr > td { + padding: 5px; +} + +.table-bordered { + border: 1px solid #dddddd; +} + +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #dddddd; +} + +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} + +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} + +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} + +table col[class*="col-"] { + display: table-column; + float: none; +} + +table td[class*="col-"], +table th[class*="col-"] { + display: table-cell; + float: none; +} + +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} + +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td { + background-color: #d0e9c6; + border-color: #c9e2b3; +} + +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; + border-color: #eed3d7; +} + +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td { + background-color: #ebcccc; + border-color: #e6c1c7; +} + +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td { + background-color: #faf2cc; + border-color: #f8e5be; +} + +@media (max-width: 768px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + background-color: #fff; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > thead > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > thead > tr:last-child > td, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + + line-height: normal; +} + +input[type="file"] { + display: block; +} + +select[multiple], +select[size] { + height: auto; +} + +select optgroup { + font-family: inherit; + font-size: inherit; + font-style: inherit; +} + +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + height: auto; +} + +.form-control:-moz-placeholder { + color: #999999; +} + +.form-control::-moz-placeholder { + color: #999999; +} + +.form-control:-ms-input-placeholder { + color: #999999; +} + +.form-control::-webkit-input-placeholder { + color: #999999; +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.428571429; + color: #555555; + vertical-align: middle; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eeeeee; +} + +textarea.form-control { + height: auto; +} + +.form-group { + margin-bottom: 15px; +} + +.radio, +.checkbox { + display: block; + min-height: 20px; + padding-left: 20px; + margin-top: 10px; + margin-bottom: 10px; + vertical-align: middle; +} + +.radio label, +.checkbox label { + display: inline; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} + +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} + +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} + +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-sm { + height: 30px; + line-height: 30px; +} + +textarea.input-sm { + height: auto; +} + +.input-lg { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-lg { + height: 45px; + line-height: 45px; +} + +textarea.input-lg { + height: auto; +} + +.has-warning .help-block, +.has-warning .control-label { + color: #c09853; +} + +.has-warning .form-control { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-warning .form-control:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} + +.has-warning .input-group-addon { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.has-error .help-block, +.has-error .control-label { + color: #b94a48; +} + +.has-error .form-control { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-error .form-control:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} + +.has-error .input-group-addon { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.has-success .help-block, +.has-success .control-label { + color: #468847; +} + +.has-success .form-control { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-success .form-control:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} + +.has-success .input-group-addon { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +.form-control-static { + padding-top: 7px; + margin-bottom: 0; +} + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} + +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} + +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.428571429; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + border: 1px solid transparent; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; +} + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333333; + background-color: #ebebeb; + border-color: #adadad; +} + +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} + +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-primary { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #3276b1; + border-color: #285e8e; +} + +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} + +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} + +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ed9c28; + border-color: #d58512; +} + +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} + +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; +} + +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} + +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #47a447; + border-color: #398439; +} + +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} + +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #39b3d7; + border-color: #269abc; +} + +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} + +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} + +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} + +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999999; + text-decoration: none; +} + +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-sm, +.btn-xs { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-xs { + padding: 1px 5px; +} + +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; +} + +.btn-block + .btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.in { + display: block; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + -webkit-font-smoothing: antialiased; + font-style: normal; + font-weight: normal; + line-height: 1; +} + +.glyphicon-asterisk:before { + content: "\2a"; +} + +.glyphicon-plus:before { + content: "\2b"; +} + +.glyphicon-euro:before { + content: "\20ac"; +} + +.glyphicon-minus:before { + content: "\2212"; +} + +.glyphicon-cloud:before { + content: "\2601"; +} + +.glyphicon-envelope:before { + content: "\2709"; +} + +.glyphicon-pencil:before { + content: "\270f"; +} + +.glyphicon-glass:before { + content: "\e001"; +} + +.glyphicon-music:before { + content: "\e002"; +} + +.glyphicon-search:before { + content: "\e003"; +} + +.glyphicon-heart:before { + content: "\e005"; +} + +.glyphicon-star:before { + content: "\e006"; +} + +.glyphicon-star-empty:before { + content: "\e007"; +} + +.glyphicon-user:before { + content: "\e008"; +} + +.glyphicon-film:before { + content: "\e009"; +} + +.glyphicon-th-large:before { + content: "\e010"; +} + +.glyphicon-th:before { + content: "\e011"; +} + +.glyphicon-th-list:before { + content: "\e012"; +} + +.glyphicon-ok:before { + content: "\e013"; +} + +.glyphicon-remove:before { + content: "\e014"; +} + +.glyphicon-zoom-in:before { + content: "\e015"; +} + +.glyphicon-zoom-out:before { + content: "\e016"; +} + +.glyphicon-off:before { + content: "\e017"; +} + +.glyphicon-signal:before { + content: "\e018"; +} + +.glyphicon-cog:before { + content: "\e019"; +} + +.glyphicon-trash:before { + content: "\e020"; +} + +.glyphicon-home:before { + content: "\e021"; +} + +.glyphicon-file:before { + content: "\e022"; +} + +.glyphicon-time:before { + content: "\e023"; +} + +.glyphicon-road:before { + content: "\e024"; +} + +.glyphicon-download-alt:before { + content: "\e025"; +} + +.glyphicon-download:before { + content: "\e026"; +} + +.glyphicon-upload:before { + content: "\e027"; +} + +.glyphicon-inbox:before { + content: "\e028"; +} + +.glyphicon-play-circle:before { + content: "\e029"; +} + +.glyphicon-repeat:before { + content: "\e030"; +} + +.glyphicon-refresh:before { + content: "\e031"; +} + +.glyphicon-list-alt:before { + content: "\e032"; +} + +.glyphicon-flag:before { + content: "\e034"; +} + +.glyphicon-headphones:before { + content: "\e035"; +} + +.glyphicon-volume-off:before { + content: "\e036"; +} + +.glyphicon-volume-down:before { + content: "\e037"; +} + +.glyphicon-volume-up:before { + content: "\e038"; +} + +.glyphicon-qrcode:before { + content: "\e039"; +} + +.glyphicon-barcode:before { + content: "\e040"; +} + +.glyphicon-tag:before { + content: "\e041"; +} + +.glyphicon-tags:before { + content: "\e042"; +} + +.glyphicon-book:before { + content: "\e043"; +} + +.glyphicon-print:before { + content: "\e045"; +} + +.glyphicon-font:before { + content: "\e047"; +} + +.glyphicon-bold:before { + content: "\e048"; +} + +.glyphicon-italic:before { + content: "\e049"; +} + +.glyphicon-text-height:before { + content: "\e050"; +} + +.glyphicon-text-width:before { + content: "\e051"; +} + +.glyphicon-align-left:before { + content: "\e052"; +} + +.glyphicon-align-center:before { + content: "\e053"; +} + +.glyphicon-align-right:before { + content: "\e054"; +} + +.glyphicon-align-justify:before { + content: "\e055"; +} + +.glyphicon-list:before { + content: "\e056"; +} + +.glyphicon-indent-left:before { + content: "\e057"; +} + +.glyphicon-indent-right:before { + content: "\e058"; +} + +.glyphicon-facetime-video:before { + content: "\e059"; +} + +.glyphicon-picture:before { + content: "\e060"; +} + +.glyphicon-map-marker:before { + content: "\e062"; +} + +.glyphicon-adjust:before { + content: "\e063"; +} + +.glyphicon-tint:before { + content: "\e064"; +} + +.glyphicon-edit:before { + content: "\e065"; +} + +.glyphicon-share:before { + content: "\e066"; +} + +.glyphicon-check:before { + content: "\e067"; +} + +.glyphicon-move:before { + content: "\e068"; +} + +.glyphicon-step-backward:before { + content: "\e069"; +} + +.glyphicon-fast-backward:before { + content: "\e070"; +} + +.glyphicon-backward:before { + content: "\e071"; +} + +.glyphicon-play:before { + content: "\e072"; +} + +.glyphicon-pause:before { + content: "\e073"; +} + +.glyphicon-stop:before { + content: "\e074"; +} + +.glyphicon-forward:before { + content: "\e075"; +} + +.glyphicon-fast-forward:before { + content: "\e076"; +} + +.glyphicon-step-forward:before { + content: "\e077"; +} + +.glyphicon-eject:before { + content: "\e078"; +} + +.glyphicon-chevron-left:before { + content: "\e079"; +} + +.glyphicon-chevron-right:before { + content: "\e080"; +} + +.glyphicon-plus-sign:before { + content: "\e081"; +} + +.glyphicon-minus-sign:before { + content: "\e082"; +} + +.glyphicon-remove-sign:before { + content: "\e083"; +} + +.glyphicon-ok-sign:before { + content: "\e084"; +} + +.glyphicon-question-sign:before { + content: "\e085"; +} + +.glyphicon-info-sign:before { + content: "\e086"; +} + +.glyphicon-screenshot:before { + content: "\e087"; +} + +.glyphicon-remove-circle:before { + content: "\e088"; +} + +.glyphicon-ok-circle:before { + content: "\e089"; +} + +.glyphicon-ban-circle:before { + content: "\e090"; +} + +.glyphicon-arrow-left:before { + content: "\e091"; +} + +.glyphicon-arrow-right:before { + content: "\e092"; +} + +.glyphicon-arrow-up:before { + content: "\e093"; +} + +.glyphicon-arrow-down:before { + content: "\e094"; +} + +.glyphicon-share-alt:before { + content: "\e095"; +} + +.glyphicon-resize-full:before { + content: "\e096"; +} + +.glyphicon-resize-small:before { + content: "\e097"; +} + +.glyphicon-exclamation-sign:before { + content: "\e101"; +} + +.glyphicon-gift:before { + content: "\e102"; +} + +.glyphicon-leaf:before { + content: "\e103"; +} + +.glyphicon-eye-open:before { + content: "\e105"; +} + +.glyphicon-eye-close:before { + content: "\e106"; +} + +.glyphicon-warning-sign:before { + content: "\e107"; +} + +.glyphicon-plane:before { + content: "\e108"; +} + +.glyphicon-random:before { + content: "\e110"; +} + +.glyphicon-comment:before { + content: "\e111"; +} + +.glyphicon-magnet:before { + content: "\e112"; +} + +.glyphicon-chevron-up:before { + content: "\e113"; +} + +.glyphicon-chevron-down:before { + content: "\e114"; +} + +.glyphicon-retweet:before { + content: "\e115"; +} + +.glyphicon-shopping-cart:before { + content: "\e116"; +} + +.glyphicon-folder-close:before { + content: "\e117"; +} + +.glyphicon-folder-open:before { + content: "\e118"; +} + +.glyphicon-resize-vertical:before { + content: "\e119"; +} + +.glyphicon-resize-horizontal:before { + content: "\e120"; +} + +.glyphicon-hdd:before { + content: "\e121"; +} + +.glyphicon-bullhorn:before { + content: "\e122"; +} + +.glyphicon-certificate:before { + content: "\e124"; +} + +.glyphicon-thumbs-up:before { + content: "\e125"; +} + +.glyphicon-thumbs-down:before { + content: "\e126"; +} + +.glyphicon-hand-right:before { + content: "\e127"; +} + +.glyphicon-hand-left:before { + content: "\e128"; +} + +.glyphicon-hand-up:before { + content: "\e129"; +} + +.glyphicon-hand-down:before { + content: "\e130"; +} + +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} + +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} + +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} + +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} + +.glyphicon-globe:before { + content: "\e135"; +} + +.glyphicon-tasks:before { + content: "\e137"; +} + +.glyphicon-filter:before { + content: "\e138"; +} + +.glyphicon-fullscreen:before { + content: "\e140"; +} + +.glyphicon-dashboard:before { + content: "\e141"; +} + +.glyphicon-heart-empty:before { + content: "\e143"; +} + +.glyphicon-link:before { + content: "\e144"; +} + +.glyphicon-phone:before { + content: "\e145"; +} + +.glyphicon-usd:before { + content: "\e148"; +} + +.glyphicon-gbp:before { + content: "\e149"; +} + +.glyphicon-sort:before { + content: "\e150"; +} + +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} + +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} + +.glyphicon-sort-by-order:before { + content: "\e153"; +} + +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} + +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} + +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} + +.glyphicon-unchecked:before { + content: "\e157"; +} + +.glyphicon-expand:before { + content: "\e158"; +} + +.glyphicon-collapse-down:before { + content: "\e159"; +} + +.glyphicon-collapse-up:before { + content: "\e160"; +} + +.glyphicon-log-in:before { + content: "\e161"; +} + +.glyphicon-flash:before { + content: "\e162"; +} + +.glyphicon-log-out:before { + content: "\e163"; +} + +.glyphicon-new-window:before { + content: "\e164"; +} + +.glyphicon-record:before { + content: "\e165"; +} + +.glyphicon-save:before { + content: "\e166"; +} + +.glyphicon-open:before { + content: "\e167"; +} + +.glyphicon-saved:before { + content: "\e168"; +} + +.glyphicon-import:before { + content: "\e169"; +} + +.glyphicon-export:before { + content: "\e170"; +} + +.glyphicon-send:before { + content: "\e171"; +} + +.glyphicon-floppy-disk:before { + content: "\e172"; +} + +.glyphicon-floppy-saved:before { + content: "\e173"; +} + +.glyphicon-floppy-remove:before { + content: "\e174"; +} + +.glyphicon-floppy-save:before { + content: "\e175"; +} + +.glyphicon-floppy-open:before { + content: "\e176"; +} + +.glyphicon-credit-card:before { + content: "\e177"; +} + +.glyphicon-transfer:before { + content: "\e178"; +} + +.glyphicon-cutlery:before { + content: "\e179"; +} + +.glyphicon-header:before { + content: "\e180"; +} + +.glyphicon-compressed:before { + content: "\e181"; +} + +.glyphicon-earphone:before { + content: "\e182"; +} + +.glyphicon-phone-alt:before { + content: "\e183"; +} + +.glyphicon-tower:before { + content: "\e184"; +} + +.glyphicon-stats:before { + content: "\e185"; +} + +.glyphicon-sd-video:before { + content: "\e186"; +} + +.glyphicon-hd-video:before { + content: "\e187"; +} + +.glyphicon-subtitles:before { + content: "\e188"; +} + +.glyphicon-sound-stereo:before { + content: "\e189"; +} + +.glyphicon-sound-dolby:before { + content: "\e190"; +} + +.glyphicon-sound-5-1:before { + content: "\e191"; +} + +.glyphicon-sound-6-1:before { + content: "\e192"; +} + +.glyphicon-sound-7-1:before { + content: "\e193"; +} + +.glyphicon-copyright-mark:before { + content: "\e194"; +} + +.glyphicon-registration-mark:before { + content: "\e195"; +} + +.glyphicon-cloud-download:before { + content: "\e197"; +} + +.glyphicon-cloud-upload:before { + content: "\e198"; +} + +.glyphicon-tree-conifer:before { + content: "\e199"; +} + +.glyphicon-tree-deciduous:before { + content: "\e200"; +} + +.glyphicon-briefcase:before { + content: "\1f4bc"; +} + +.glyphicon-calendar:before { + content: "\1f4c5"; +} + +.glyphicon-pushpin:before { + content: "\1f4cc"; +} + +.glyphicon-paperclip:before { + content: "\1f4ce"; +} + +.glyphicon-camera:before { + content: "\1f4f7"; +} + +.glyphicon-lock:before { + content: "\1f512"; +} + +.glyphicon-bell:before { + content: "\1f514"; +} + +.glyphicon-bookmark:before { + content: "\1f516"; +} + +.glyphicon-fire:before { + content: "\1f525"; +} + +.glyphicon-wrench:before { + content: "\1f527"; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-bottom: 0 dotted; + border-left: 4px solid transparent; + content: ""; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle:focus { + outline: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open > .dropdown-menu { + display: block; +} + +.open > a { + outline: 0; +} + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.428571429; + color: #999999; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0 dotted; + border-bottom: 4px solid #000000; + content: ""; +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} + +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } +} + +.btn-default .caret { + border-top-color: #333333; +} + +.btn-primary .caret, +.btn-success .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret { + border-top-color: #fff; +} + +.dropup .btn-default .caret { + border-bottom-color: #333333; +} + +.dropup .btn-primary .caret, +.dropup .btn-success .caret, +.dropup .btn-warning .caret, +.dropup .btn-danger .caret, +.dropup .btn-info .caret { + border-bottom-color: #fff; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} + +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: none; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar .btn-group { + float: left; +} + +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group, +.btn-toolbar > .btn-group + .btn-group { + margin-left: 5px; +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn-group:last-child > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group-xs > .btn { + padding: 5px 10px; + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} + +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn .caret { + margin-left: 0; +} + +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} + +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} + +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + display: block; + float: none; + width: 100%; + max-width: 100%; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group > .btn { + float: none; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-right-radius: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 0; +} + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group-vertical > .btn-group:first-child > .btn:last-child, +.btn-group-vertical > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn-group:last-child > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.btn-group-justified { + display: table; + width: 100%; + border-collapse: separate; + table-layout: fixed; +} + +.btn-group-justified .btn { + display: table-cell; + float: none; + width: 1%; +} + +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} + +.input-group { + position: relative; + display: table; + border-collapse: separate; +} + +.input-group.col { + float: none; + padding-right: 0; + padding-left: 0; +} + +.input-group .form-control { + width: 100%; + margin-bottom: 0; +} + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 45px; + line-height: 45px; +} + +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn { + height: auto; +} + +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} + +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn { + height: auto; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-radius: 4px; +} + +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} + +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:first-child { + border-right: 0; +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.input-group-addon:last-child { + border-left: 0; +} + +.input-group-btn { + position: relative; + white-space: nowrap; +} + +.input-group-btn > .btn { + position: relative; +} + +.input-group-btn > .btn + .btn { + margin-left: -4px; +} + +.input-group-btn > .btn:hover, +.input-group-btn > .btn:active { + z-index: 2; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav > li { + position: relative; + display: block; +} + +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} + +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.nav > li.disabled > a { + color: #999999; +} + +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999999; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} + +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #428bca; +} + +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.nav > li > a > img { + max-width: none; +} + +.nav-tabs { + border-bottom: 1px solid #dddddd; +} + +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} + +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.428571429; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} + +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; +} + +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} + +.nav-tabs.nav-justified > li { + float: none; +} + +.nav-tabs.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs.nav-justified > .active > a { + border-bottom-color: #ffffff; +} + +.nav-pills > li { + float: left; +} + +.nav-pills > li > a { + border-radius: 5px; +} + +.nav-pills > li + li { + margin-left: 2px; +} + +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #428bca; +} + +.nav-stacked > li { + float: none; +} + +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} + +.nav-justified { + width: 100%; +} + +.nav-justified > li { + float: none; +} + +.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs-justified { + border-bottom: 0; +} + +.nav-tabs-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs-justified > .active > a { + border-bottom-color: #ffffff; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} + +.tab-content > .active, +.pill-content > .active { + display: block; +} + +.nav .caret { + border-top-color: #428bca; + border-bottom-color: #428bca; +} + +.nav a:hover .caret { + border-top-color: #2a6496; + border-bottom-color: #2a6496; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar { + position: relative; + z-index: 1000; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} + +.navbar-collapse { + max-height: 340px; + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.navbar-collapse.in { + overflow-y: auto; +} + +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-collapse .navbar-nav.navbar-left:first-child { + margin-left: -15px; + } + .navbar-collapse .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } + .navbar-collapse .navbar-text:last-child { + margin-right: 0; + } +} + +.container > .navbar-header, +.container > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} + +@media (min-width: 768px) { + .container > .navbar-header, + .container > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} + +.navbar-static-top { + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + z-index: 1030; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; +} + +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} + +@media (min-width: 768px) { + .navbar > .container .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} + +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} + +.navbar-nav { + margin: 7.5px -15px; +} + +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} + +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} + +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} + +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} + +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); +} + +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} + +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} + +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.navbar-nav.pull-right > li > .dropdown-menu, +.navbar-nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} + +.navbar-text { + float: left; + margin-top: 15px; + margin-bottom: 15px; +} + +@media (min-width: 768px) { + .navbar-text { + margin-right: 15px; + margin-left: 15px; + } +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +.navbar-default .navbar-brand { + color: #777777; +} + +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} + +.navbar-default .navbar-text { + color: #777777; +} + +.navbar-default .navbar-nav > li > a { + color: #777777; +} + +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} + +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} + +.navbar-default .navbar-toggle { + border-color: #dddddd; +} + +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #cccccc; +} + +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e6e6e6; +} + +.navbar-default .navbar-nav > .dropdown > a:hover .caret, +.navbar-default .navbar-nav > .dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} + +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .open > a .caret, +.navbar-default .navbar-nav > .open > a:hover .caret, +.navbar-default .navbar-nav > .open > a:focus .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.navbar-default .navbar-nav > .dropdown > a .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} + +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} + +.navbar-default .navbar-link { + color: #777777; +} + +.navbar-default .navbar-link:hover { + color: #333333; +} + +.navbar-inverse { + background-color: #222222; + border-color: #080808; +} + +.navbar-inverse .navbar-brand { + color: #999999; +} + +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-text { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} + +.navbar-inverse .navbar-toggle { + border-color: #333333; +} + +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} + +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .dropdown > a:hover .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .navbar-nav > .dropdown > a .caret { + border-top-color: #999999; + border-bottom-color: #999999; +} + +.navbar-inverse .navbar-nav > .open > a .caret, +.navbar-inverse .navbar-nav > .open > a:hover .caret, +.navbar-inverse .navbar-nav > .open > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999999; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} + +.navbar-inverse .navbar-link { + color: #999999; +} + +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} + +.breadcrumb > li { + display: inline-block; +} + +.breadcrumb > li + li:before { + padding: 0 5px; + color: #cccccc; + content: "/\00a0"; +} + +.breadcrumb > .active { + color: #999999; +} + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} + +.pagination > li { + display: inline; +} + +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.428571429; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} + +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + background-color: #eeeeee; +} + +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #ffffff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} + +.pagination > .disabled > span, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999999; + cursor: not-allowed; + background-color: #ffffff; + border-color: #dddddd; +} + +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} + +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} + +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} + +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} + +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager li { + display: inline; +} + +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} + +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.pager .next > a, +.pager .next > span { + float: right; +} + +.pager .previous > a, +.pager .previous > span { + float: left; +} + +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999999; + cursor: not-allowed; + background-color: #ffffff; +} + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} + +.label[href]:hover, +.label[href]:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.label:empty { + display: none; +} + +.label-default { + background-color: #999999; +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #808080; +} + +.label-primary { + background-color: #428bca; +} + +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} + +.label-success { + background-color: #5cb85c; +} + +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} + +.label-info { + background-color: #5bc0de; +} + +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} + +.label-warning { + background-color: #f0ad4e; +} + +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} + +.label-danger { + background-color: #d9534f; +} + +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; + border-radius: 10px; +} + +.badge:empty { + display: none; +} + +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.btn .badge { + position: relative; + top: -1px; +} + +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #ffffff; +} + +.nav-pills > li > a > .badge { + margin-left: 3px; +} + +.jumbotron { + padding: 30px; + margin-bottom: 30px; + font-size: 21px; + font-weight: 200; + line-height: 2.1428571435; + color: inherit; + background-color: #eeeeee; +} + +.jumbotron h1 { + line-height: 1; + color: inherit; +} + +.jumbotron p { + line-height: 1.4; +} + +.container .jumbotron { + border-radius: 6px; +} + +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1 { + font-size: 63px; + } +} + +.thumbnail { + display: inline-block; + display: block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.thumbnail > img { + display: block; + height: auto; + max-width: 100%; +} + +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #428bca; +} + +.thumbnail > img { + margin-right: auto; + margin-left: auto; +} + +.thumbnail .caption { + padding: 9px; + color: #333333; +} + +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert h4 { + margin-top: 0; + color: inherit; +} + +.alert .alert-link { + font-weight: bold; +} + +.alert > p, +.alert > ul { + margin-bottom: 0; +} + +.alert > p + p { + margin-top: 5px; +} + +.alert-dismissable { + padding-right: 35px; +} + +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success hr { + border-top-color: #c9e2b3; +} + +.alert-success .alert-link { + color: #356635; +} + +.alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info hr { + border-top-color: #a6e1ec; +} + +.alert-info .alert-link { + color: #2d6987; +} + +.alert-warning { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.alert-warning hr { + border-top-color: #f8e5be; +} + +.alert-warning .alert-link { + color: #a47e3c; +} + +.alert-danger { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.alert-danger hr { + border-top-color: #e6c1c7; +} + +.alert-danger .alert-link { + color: #953b39; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-striped .progress-bar { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} + +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-bar-success { + background-color: #5cb85c; +} + +.progress-striped .progress-bar-success { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-info { + background-color: #5bc0de; +} + +.progress-striped .progress-bar-info { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-warning { + background-color: #f0ad4e; +} + +.progress-striped .progress-bar-warning { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-danger { + background-color: #d9534f; +} + +.progress-striped .progress-bar-danger { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.media, +.media-body { + overflow: hidden; + zoom: 1; +} + +.media, +.media .media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-object { + display: block; +} + +.media-heading { + margin: 0 0 5px; +} + +.media > .pull-left { + margin-right: 10px; +} + +.media > .pull-right { + margin-left: 10px; +} + +.media-list { + padding-left: 0; + list-style: none; +} + +.list-group { + padding-left: 0; + margin-bottom: 20px; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.list-group-item > .badge { + float: right; +} + +.list-group-item > .badge + .badge { + margin-right: 5px; +} + +a.list-group-item { + color: #555555; +} + +a.list-group-item .list-group-item-heading { + color: #333333; +} + +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} + +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} + +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.panel-body { + padding: 15px; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel > .list-group { + margin-bottom: 0; +} + +.panel > .list-group .list-group-item { + border-width: 1px 0; +} + +.panel > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.panel > .list-group .list-group-item:last-child { + border-bottom: 0; +} + +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} + +.panel > .table { + margin-bottom: 0; +} + +.panel > .panel-body + .table { + border-top: 1px solid #dddddd; +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; +} + +.panel-title > a { + color: inherit; +} + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel-group .panel { + margin-bottom: 0; + overflow: hidden; + border-radius: 4px; +} + +.panel-group .panel + .panel { + margin-top: 5px; +} + +.panel-group .panel-heading { + border-bottom: 0; +} + +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #dddddd; +} + +.panel-group .panel-footer { + border-top: 0; +} + +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} + +.panel-default { + border-color: #dddddd; +} + +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} + +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #dddddd; +} + +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #dddddd; +} + +.panel-primary { + border-color: #428bca; +} + +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #428bca; +} + +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #428bca; +} + +.panel-success { + border-color: #d6e9c6; +} + +.panel-success > .panel-heading { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} + +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} + +.panel-warning { + border-color: #fbeed5; +} + +.panel-warning > .panel-heading { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #fbeed5; +} + +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #fbeed5; +} + +.panel-danger { + border-color: #eed3d7; +} + +.panel-danger > .panel-heading { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #eed3d7; +} + +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #eed3d7; +} + +.panel-info { + border-color: #bce8f1; +} + +.panel-info > .panel-heading { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} + +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} + +.well-lg { + padding: 24px; + border-radius: 6px; +} + +.well-sm { + padding: 9px; + border-radius: 3px; +} + +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +body.modal-open, +.modal-open .navbar-fixed-top, +.modal-open .navbar-fixed-bottom { + margin-right: 15px; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: auto; + overflow-y: scroll; +} + +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} + +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-dialog { + z-index: 1050; + width: auto; + padding: 10px; + margin-right: auto; + margin-left: auto; +} + +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; + background-color: #000000; +} + +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} + +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} + +.modal-header { + min-height: 16.428571429px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} + +.modal-header .close { + margin-top: -2px; +} + +.modal-title { + margin: 0; + line-height: 1.428571429; +} + +.modal-body { + position: relative; + padding: 20px; +} + +.modal-footer { + padding: 19px 20px 20px; + margin-top: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} + +@media screen and (min-width: 768px) { + .modal-dialog { + right: auto; + left: 50%; + width: 600px; + padding-top: 30px; + padding-bottom: 30px; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } +} + +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); + visibility: visible; +} + +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000000; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000000; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover .arrow { + border-width: 11px; +} + +.popover .arrow:after { + border-width: 10px; + content: ""; +} + +.popover.top .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} + +.popover.top .arrow:after { + bottom: 1px; + margin-left: -10px; + border-top-color: #ffffff; + border-bottom-width: 0; + content: " "; +} + +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} + +.popover.right .arrow:after { + bottom: -10px; + left: 1px; + border-right-color: #ffffff; + border-left-width: 0; + content: " "; +} + +.popover.bottom .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + border-top-width: 0; +} + +.popover.bottom .arrow:after { + top: 1px; + margin-left: -10px; + border-bottom-color: #ffffff; + border-top-width: 0; + content: " "; +} + +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); + border-right-width: 0; +} + +.popover.left .arrow:after { + right: 1px; + bottom: -10px; + border-left-color: #ffffff; + border-right-width: 0; + content: " "; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} + +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + height: auto; + max-width: 100%; + line-height: 1; +} + +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} + +.carousel-inner > .active { + left: 0; +} + +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner > .next { + left: 100%; +} + +.carousel-inner > .prev { + left: -100%; +} + +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} + +.carousel-inner > .active.left { + left: -100%; +} + +.carousel-inner > .active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + opacity: 0.5; + filter: alpha(opacity=50); +} + +.carousel-control.left { + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} + +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} + +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} + +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + left: 50%; + z-index: 5; + display: inline-block; +} + +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} + +.carousel-control .icon-prev:before { + content: '\2039'; +} + +.carousel-control .icon-next:before { + content: '\203a'; +} + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} + +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + border: 1px solid #ffffff; + border-radius: 10px; +} + +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #ffffff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} + +.carousel-caption .btn { + text-shadow: none; +} + +@media screen and (min-width: 768px) { + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} + +.clearfix:before, +.clearfix:after { + display: table; + content: " "; +} + +.clearfix:after { + clear: both; +} + +.pull-right { + float: right !important; +} + +.pull-left { + float: left !important; +} + +.hide { + display: none !important; +} + +.show { + display: block !important; +} + +.invisible { + visibility: hidden; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.affix { + position: fixed; +} + +@-ms-viewport { + width: device-width; +} + +@media screen and (max-width: 400px) { + @-ms-viewport { + width: 320px; + } +} + +.hidden { + display: none !important; + visibility: hidden !important; +} + +.visible-xs { + display: none !important; +} + +tr.visible-xs { + display: none !important; +} + +th.visible-xs, +td.visible-xs { + display: none !important; +} + +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-xs.visible-sm { + display: block !important; + } + tr.visible-xs.visible-sm { + display: table-row !important; + } + th.visible-xs.visible-sm, + td.visible-xs.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-xs.visible-md { + display: block !important; + } + tr.visible-xs.visible-md { + display: table-row !important; + } + th.visible-xs.visible-md, + td.visible-xs.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-xs.visible-lg { + display: block !important; + } + tr.visible-xs.visible-lg { + display: table-row !important; + } + th.visible-xs.visible-lg, + td.visible-xs.visible-lg { + display: table-cell !important; + } +} + +.visible-sm { + display: none !important; +} + +tr.visible-sm { + display: none !important; +} + +th.visible-sm, +td.visible-sm { + display: none !important; +} + +@media (max-width: 767px) { + .visible-sm.visible-xs { + display: block !important; + } + tr.visible-sm.visible-xs { + display: table-row !important; + } + th.visible-sm.visible-xs, + td.visible-sm.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-sm.visible-md { + display: block !important; + } + tr.visible-sm.visible-md { + display: table-row !important; + } + th.visible-sm.visible-md, + td.visible-sm.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-sm.visible-lg { + display: block !important; + } + tr.visible-sm.visible-lg { + display: table-row !important; + } + th.visible-sm.visible-lg, + td.visible-sm.visible-lg { + display: table-cell !important; + } +} + +.visible-md { + display: none !important; +} + +tr.visible-md { + display: none !important; +} + +th.visible-md, +td.visible-md { + display: none !important; +} + +@media (max-width: 767px) { + .visible-md.visible-xs { + display: block !important; + } + tr.visible-md.visible-xs { + display: table-row !important; + } + th.visible-md.visible-xs, + td.visible-md.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-md.visible-sm { + display: block !important; + } + tr.visible-md.visible-sm { + display: table-row !important; + } + th.visible-md.visible-sm, + td.visible-md.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-md.visible-lg { + display: block !important; + } + tr.visible-md.visible-lg { + display: table-row !important; + } + th.visible-md.visible-lg, + td.visible-md.visible-lg { + display: table-cell !important; + } +} + +.visible-lg { + display: none !important; +} + +tr.visible-lg { + display: none !important; +} + +th.visible-lg, +td.visible-lg { + display: none !important; +} + +@media (max-width: 767px) { + .visible-lg.visible-xs { + display: block !important; + } + tr.visible-lg.visible-xs { + display: table-row !important; + } + th.visible-lg.visible-xs, + td.visible-lg.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-lg.visible-sm { + display: block !important; + } + tr.visible-lg.visible-sm { + display: table-row !important; + } + th.visible-lg.visible-sm, + td.visible-lg.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-lg.visible-md { + display: block !important; + } + tr.visible-lg.visible-md { + display: table-row !important; + } + th.visible-lg.visible-md, + td.visible-lg.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} + +.hidden-xs { + display: block !important; +} + +tr.hidden-xs { + display: table-row !important; +} + +th.hidden-xs, +td.hidden-xs { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } + tr.hidden-xs { + display: none !important; + } + th.hidden-xs, + td.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-xs.hidden-sm { + display: none !important; + } + tr.hidden-xs.hidden-sm { + display: none !important; + } + th.hidden-xs.hidden-sm, + td.hidden-xs.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-xs.hidden-md { + display: none !important; + } + tr.hidden-xs.hidden-md { + display: none !important; + } + th.hidden-xs.hidden-md, + td.hidden-xs.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-xs.hidden-lg { + display: none !important; + } + tr.hidden-xs.hidden-lg { + display: none !important; + } + th.hidden-xs.hidden-lg, + td.hidden-xs.hidden-lg { + display: none !important; + } +} + +.hidden-sm { + display: block !important; +} + +tr.hidden-sm { + display: table-row !important; +} + +th.hidden-sm, +td.hidden-sm { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-sm.hidden-xs { + display: none !important; + } + tr.hidden-sm.hidden-xs { + display: none !important; + } + th.hidden-sm.hidden-xs, + td.hidden-sm.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } + tr.hidden-sm { + display: none !important; + } + th.hidden-sm, + td.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-sm.hidden-md { + display: none !important; + } + tr.hidden-sm.hidden-md { + display: none !important; + } + th.hidden-sm.hidden-md, + td.hidden-sm.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-sm.hidden-lg { + display: none !important; + } + tr.hidden-sm.hidden-lg { + display: none !important; + } + th.hidden-sm.hidden-lg, + td.hidden-sm.hidden-lg { + display: none !important; + } +} + +.hidden-md { + display: block !important; +} + +tr.hidden-md { + display: table-row !important; +} + +th.hidden-md, +td.hidden-md { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-md.hidden-xs { + display: none !important; + } + tr.hidden-md.hidden-xs { + display: none !important; + } + th.hidden-md.hidden-xs, + td.hidden-md.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-md.hidden-sm { + display: none !important; + } + tr.hidden-md.hidden-sm { + display: none !important; + } + th.hidden-md.hidden-sm, + td.hidden-md.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } + tr.hidden-md { + display: none !important; + } + th.hidden-md, + td.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-md.hidden-lg { + display: none !important; + } + tr.hidden-md.hidden-lg { + display: none !important; + } + th.hidden-md.hidden-lg, + td.hidden-md.hidden-lg { + display: none !important; + } +} + +.hidden-lg { + display: block !important; +} + +tr.hidden-lg { + display: table-row !important; +} + +th.hidden-lg, +td.hidden-lg { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-lg.hidden-xs { + display: none !important; + } + tr.hidden-lg.hidden-xs { + display: none !important; + } + th.hidden-lg.hidden-xs, + td.hidden-lg.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-lg.hidden-sm { + display: none !important; + } + tr.hidden-lg.hidden-sm { + display: none !important; + } + th.hidden-lg.hidden-sm, + td.hidden-lg.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-lg.hidden-md { + display: none !important; + } + tr.hidden-lg.hidden-md { + display: none !important; + } + th.hidden-lg.hidden-md, + td.hidden-lg.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } + tr.hidden-lg { + display: none !important; + } + th.hidden-lg, + td.hidden-lg { + display: none !important; + } +} + +.visible-print { + display: none !important; +} + +tr.visible-print { + display: none !important; +} + +th.visible-print, +td.visible-print { + display: none !important; +} + +@media print { + .visible-print { + display: block !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } + .hidden-print { + display: none !important; + } + tr.hidden-print { + display: none !important; + } + th.hidden-print, + td.hidden-print { + display: none !important; + } +} \ No newline at end of file diff --git a/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.eot b/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..87eaa434234e2a984c261e0450a2f4ad837aa7b4 GIT binary patch literal 14079 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 literal 0 HcmV?d00001 diff --git a/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.svg b/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..5fee068 --- /dev/null +++ b/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf b/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..be784dc1d5bcb92ab155f578f3723524a3dd9688 GIT binary patch literal 29512 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 DlhOCh literal 0 HcmV?d00001 diff --git a/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.woff b/extensions/bootstrap/assets/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..2cc3e4852a5a42e6aadd6284e067b66e14a57bc7 GIT binary patch literal 16448 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 (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || $active[type]() + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var fallback = type == 'next' ? 'first' : 'last' + var that = this + + if (!$next.length) { + if (!this.options.wrap) return + $next = this.$element.find('.item')[fallback]() + } + + this.sliding = true + + isCycling && this.pause() + + var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) + + if ($next.hasClass('active')) return + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + .emulateTransitionEnd(600) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + }) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + $carousel.carousel($carousel.data()) + }) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#collapse + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.transitioning = null + + if (this.options.parent) this.$parent = $(this.options.parent) + if (this.options.toggle) this.toggle() + } + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var actives = this.$parent && this.$parent.find('> .panel > .in') + + if (actives && actives.length) { + var hasData = actives.data('bs.collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing') + [dimension](0) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('in') + [dimension]('auto') + this.transitioning = 0 + this.$element.trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + [dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element + [dimension](this.$element[dimension]()) + [0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse') + .removeClass('in') + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .trigger('hidden.bs.collapse') + .removeClass('collapsing') + .addClass('collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + var target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + var $target = $(target) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + var parent = $this.attr('data-parent') + var $parent = parent && $(parent) + + if (!data || !data.transitioning) { + if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') + $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + } + + $target.collapse(option) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#dropdowns + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle=dropdown]' + var Dropdown = function (element) { + var $el = $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we we use a backdrop because click events don't delegate + $('