Commit 8ac2f37e12b40b8a887d8aff7c3ee73c00f3c5db

Authored by Yarik
2 parents fa67310a f0f915df

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	backend/web/js/option.js
#	frontend/views/accounts/general.php
#	frontend/views/layouts/admin.php
backend/views/specialization/_form.php
... ... @@ -17,9 +17,11 @@ use yii\helpers\ArrayHelper;
17 17  
18 18 <?php
19 19  
20   - echo $form->field($model, 'background')->textInput();
  20 +
21 21  
22 22 if($model->specialization_pid == 0 && !empty($model->specialization_id) ){
  23 + echo $form->field($model, 'background')->textInput();
  24 +
23 25 echo \common\widgets\ImageUploader::widget([
24 26 'model'=> $model,
25 27 'field'=>'image',
... ...
console/migrations/m160204_151615_cities.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Migration;
  4 +
  5 +class m160204_151615_cities extends Migration
  6 +{
  7 + public function up()
  8 + {
  9 + $tableOptions = null;
  10 +
  11 + $this->createTable('{{%cities}}', [
  12 + 'id' => $this->primaryKey(),
  13 + 'name' => $this->string(255),
  14 + 'is_active' => $this->smallInteger(),
  15 + 'parent' => $this->integer()
  16 + ], $tableOptions);
  17 +
  18 + }
  19 +
  20 + public function down()
  21 + {
  22 + $this->dropTable('{{%cities}}');
  23 + }
  24 +}
... ...
frontend/config/main.php
... ... @@ -47,6 +47,7 @@ return [
47 47 'showScriptName' => false,
48 48 'rules' => [
49 49 'landing/<view:[\w-]+>' => 'landing/view',
  50 + 'site/city/<query:[\d]+>' => 'site/city',
50 51 ]
51 52 ],
52 53 ],
... ...
frontend/controllers/SiteController.php
... ... @@ -12,6 +12,7 @@ use frontend\models\ContactForm;
12 12 use frontend\models\Options;
13 13 use frontend\models\OptionValues;
14 14 use yii\base\InvalidParamException;
  15 +use yii\db\Query;
15 16 use yii\web\BadRequestHttpException;
16 17 use yii\web\Controller;
17 18 use yii\filters\VerbFilter;
... ... @@ -48,6 +49,23 @@ class SiteController extends Controller
48 49  
49 50  
50 51  
  52 + public function actionCity($query){
  53 + \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  54 + $out = ['results' => ['id' => '', 'text' => '']];
  55 + if (!is_null($query)) {
  56 + $query = new Query();
  57 + $query->select('id, name AS text')
  58 + ->from('city')
  59 + ->where(['like', 'name', $query])
  60 + ->limit(20);
  61 + $command = $query->createCommand();
  62 + $data = $command->queryAll();
  63 + $out['results'] = array_values($data);
  64 + }
  65 +
  66 + return $out;
  67 + }
  68 +
51 69  
52 70 public function actionFormsModal()
53 71 {
... ...
frontend/models/SignupForm.php
... ... @@ -13,6 +13,10 @@ class SignupForm extends Model
13 13 public $username;
14 14 public $email;
15 15 public $password;
  16 + public $firstname;
  17 + public $lastname;
  18 + public $verifyCode;
  19 + public $location;
16 20  
17 21 /**
18 22 * @inheritdoc
... ... @@ -25,12 +29,16 @@ class SignupForm extends Model
25 29 ['username', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This username has already been taken.'],
26 30 ['username', 'string', 'min' => 2, 'max' => 255],
27 31  
  32 + ['location', 'string', 'min' => 2, 'max' => 255],
  33 +
28 34 ['email', 'filter', 'filter' => 'trim'],
29 35 ['email', 'required'],
30 36 ['email', 'email'],
31 37 ['email', 'string', 'max' => 255],
32 38 ['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'],
33 39  
  40 + ['verifyCode', 'captcha'],
  41 +
34 42 ['password', 'required'],
35 43 ['password', 'string', 'min' => 6],
36 44 ];
... ...
frontend/views/accounts/general.php
... ... @@ -5,101 +5,171 @@
5 5 * @var CompanyInfo $company_info
6 6 */
7 7 use common\models\CompanyInfo;
  8 + use common\models\Option;
8 9 use common\models\User;
9 10 use common\models\UserInfo;
10 11 use common\widgets\ImageUploader;
11 12 use yii\helpers\Html;
12 13 use yii\widgets\ActiveForm;
  14 + use \common\widgets\MultiLangForm;
13 15  
14 16 $this->title = 'Учетные данные';
15   - $this->params[ 'breadcrumbs' ][] = $this->title;
  17 + $this->params['breadcrumbs'][] = $this->title;
16 18 ?>
17   -<h1><?= $this->title ?></h1>
  19 +<div class="login-left-column-title"><?= $this->title ?></div>
  20 +
18 21 <div class="" id="form_definition">
19   - <?php
20   - $form = ActiveForm::begin();
21   - ?>
22   - <?= $form->field($user, 'isPerformer', [ 'template' => "{label}:\n{input}\n{hint}\n{error}" ])
23   - ->label('<span></span>Я - исполнитель')
24   - ->hint('Отображается если указать специализации услуг в личном кабинете.')
25   - ->checkbox([ ], false) ?>
26   - <?= $form->field($user, 'isCustomer', [ 'template' => "{label}:\n{input}\n{hint}\n{error}" ])
27   - ->label('Я - заказчик')
28   - ->hint('Отображается если созданы заказы.')
29   - ->checkbox([ ], false) ?>
30   - <?= $form->field($user, 'type')
31   - ->label('Кто вы')
32   - ->radioList([
33   - 1 => 'Частное лицо',
34   - 2 => 'Компания',
35   - ]) ?>
36   - <?= $form->field($company_info, 'name', [ 'options' => [ 'class' => 'form-group company_info' ] ])
37   - ->label('Название компании')
38   - ->textInput() ?>
39   - <?= $form->field($company_info, 'staff', [ 'options' => [ 'class' => 'form-group company_info' ] ])
40   - ->label('Количество сотрудников')
41   - ->input('number') ?>
42   - <div class="company_info">Контакты представителя</div>
43   - <?= $form->field($user, 'lastname')
44   - ->label('Фамилия')
45   - ->textInput() ?>
46   - <?= $form->field($user, 'firstname')
47   - ->label('Имя')
48   - ->textInput() ?>
49   - <?= $form->field($user_info, 'country')
50   - ->label('Ваша страна')
51   - ->textInput() ?>
52   - <?= $form->field($user_info, 'city')
53   - ->label('Ваш город')
54   - ->textInput() ?>
55   - <?= $form->field($company_info, 'street', [ 'options' => [ 'class' => 'form-group company_info' ] ])
56   - ->label('Улица')
57   - ->textInput() ?>
58   - <?= $form->field($company_info, 'house', [ 'options' => [ 'class' => 'form-group company_info' ] ])
59   - ->label('Дом')
60   - ->textInput() ?>
61   - <?= $form->field($user, 'email')
62   - ->label('Email')
63   - ->textInput([ 'disabled' => 'disabled' ]) ?>
64   - <?= $form->field($company_info, 'hide_mail', [
65   - 'options' => [ 'class' => 'form-group company_info' ],
66   - 'template' => "{input}{label}\n{hint}\n{error}",
67   - ])
68   - ->label('Не публиковать Email')
69   - ->checkbox([ ], false) ?>
70   - <?= $form->field($user_info, 'busy')
71   - ->label('Статус')
72   - ->radioList([
73   - 0 => 'Свободен',
74   - 1 => 'Занят',
75   - ]) ?>
76   - <?= $form->field($user_info, 'member')
77   - ->label('Членство в МФП')
78   - ->hint('Выберите если хотите стать членом МФП и наш менеджер свяжется с Вами.')
79   - ->radioList([
80   - 0 => 'Не хочу',
81   - 1 => 'Хочу стать',
82   - ]) ?>
83   - <?= ImageUploader::widget([
84   - 'model' => $user_info,
85   - 'field' => 'image',
86   - 'width' => 100,
87   - 'height' => 100,
88   - 'multi' => false,
89   - 'gallery' => $user_info->image,
90   - 'name' => 'Загрузить аватар',
91   - ]) ?>
92   - <?= ImageUploader::widget([
93   - 'model' => $user_info,
94   - 'field' => 'poster',
95   - 'width' => 1200,
96   - 'height' => 600,
97   - 'multi' => false,
98   - 'gallery' => $user_info->poster,
99   - 'name' => 'Загрузить постер',
100   - ]) ?>
101   - <?= Html::submitButton('Обновить', [ 'class' => 'btn btn-primary' ]) ?>
102   - <?php
103   - $form->end();
104   - ?>
  22 + <?php $form = ActiveForm::begin (); ?>
  23 +
  24 +
  25 +
  26 +
  27 +<!--// $form->field ($user, 'isPerformer', ['template' => "{label}:\n{input}\n{hint}\n{error}"])-->
  28 +<!--// ->label ('<span></span>Я - исполнитель')-->
  29 +<!--// ->hint ('Отображается если указать специализации услуг в личном кабинете.')-->
  30 +<!--// ->checkbox (['class'=> 'test', 'disabled'=>'disabled'], false);-->
  31 +<!--// $form->field ($user, 'isCustomer', ['template' => "{label}:\n{input}\n{hint}\n{error}"])-->
  32 +<!--// ->label ('Я - заказчик')-->
  33 +<!--// ->hint ('Отображается если созданы заказы.')-->
  34 +<!--// ->checkbox ([], false);-->
  35 +<!--// $form->field ($user, 'type')-->
  36 +<!--// ->label ('Кто вы')-->
  37 +<!--// ->radioList ([1 => 'Частное лицо', 2 => 'Компания'],['class'=>'test']);-->
  38 +
  39 +
  40 +
  41 +
  42 +
  43 +
  44 + <div class="general-check-wr style">
  45 + <div class="general-check">
  46 +
  47 + <div class="general-check-left">
  48 + <?= $form->field ($user, 'isPerformer', ['template' => "{input}\n{label}\n{error}"])
  49 + ->label ('<span></span>Я - исполнитель')
  50 + ->checkbox (['class'=> 'custom-check disabled admin-check', 'disabled'=>'disabled'], false);
  51 + ?>
  52 + </div>
  53 + <div class="general-check-right">
  54 + <div class="general-check-right-txt">Обязательно должны быть указаны специализации услуг, что бы вы попали в рейтинг исполнителей</div>
  55 + </div>
  56 + </div>
  57 +
  58 + <div class="general-check">
  59 + <div class="general-check-left">
  60 + <?= $form->field ($user, 'isCustomer', ['template' => "{input}\n{label}\n{error}"])
  61 + ->label ('<span></span>Я - заказчик')
  62 + ->checkbox (['class'=> 'custom-check disabled admin-check', 'disabled'=>'disabled'], false);
  63 + ?>
  64 + </div>
  65 + <div class="general-check-right">
  66 + <div class="general-check-right-txt">Обязательно должны быть созданы проекты вами, что бы вы попали в рейтинг исполнителей</div>
  67 + </div>
  68 + </div>
  69 +
  70 + </div>
  71 + <div class="general-who style border-general">
  72 + <div class="general-who-title gen-admin-title">Кто вы:</div>
  73 +
  74 + <?= $form->field ($user, 'type')
  75 + ->label (false)
  76 + ->radioList (
  77 + [1 => 'Частное лицо', 2 => 'Компания'],
  78 + [
  79 + 'item' => function($index, $label, $name, $checked, $value) {
  80 + $return = '<label>';
  81 + $return .= '<input type="radio" name="' . $name . '" value="' . $value . '" '.($checked ? "checked" :"").' >';
  82 + $return .= '<span></span>' . ucwords($label);
  83 + $return .= '</label>';
  84 +
  85 + return $return;
  86 + }
  87 + ]
  88 + );
  89 + ?>
  90 +
  91 +
  92 + </div>
  93 +
  94 + <?= $form->field ($company_info, 'name', ['options' => ['class' => 'form-group company_info']])
  95 + ->label ('Название компании')
  96 + ->textInput ();
  97 + ?>
  98 + <?= $form->field ($company_info, 'staff', ['options' => ['class' => 'form-group company_info']])
  99 + ->label ('Количество сотрудников')
  100 + ->input ('number');
  101 + ?>
  102 + <?= '<div class="company_info">Контакты представителя</div>';
  103 + ?>
  104 +
  105 +
  106 + <div class="input-blocks-wrapper">
  107 + <div class="input-blocks">
  108 + <?= $form->field ($user, 'firstname')
  109 + ->label ('Имя')
  110 + ->textInput ();
  111 + ?>
  112 + </div>
  113 + </div>
  114 +
  115 + <?= $form->field ($user, 'lastname')
  116 + ->label ('Фамилия')
  117 + ->textInput ();
  118 + ?>
  119 + <?= $form->field ($user_info, 'country')
  120 + ->label ('Ваша страна')
  121 + ->textInput ();
  122 + ?>
  123 + <?= $form->field ($user_info, 'city')
  124 + ->label ('Ваш город')
  125 + ->textInput ();
  126 + ?>
  127 + <?= $form->field ($company_info, 'street', ['options' => ['class' => 'form-group company_info']])
  128 + ->label ('Улица')
  129 + ->textInput ();
  130 + ?>
  131 + <?= $form->field ($company_info, 'house', ['options' => ['class' => 'form-group company_info']])
  132 + ->label ('Дом')
  133 + ->textInput ();
  134 + ?>
  135 + <?= $form->field ($user, 'email')
  136 + ->label ('Email')
  137 + ->textInput (['disabled' => 'disabled']);
  138 + ?>
  139 + <?= $form->field ($company_info, 'hide_mail', ['options' => ['class' => 'form-group company_info'], 'template' => "{input}{label}\n{hint}\n{error}"])
  140 + ->label ('Не публиковать Email')
  141 + ->checkbox (['checked'=>'checked'], false);
  142 + ?>
  143 + <?= $form->field ($user_info, 'busy')
  144 + ->label ('Статус')
  145 + ->radioList ([0 => 'Свободен', 1 => 'Занят']);
  146 + ?>
  147 + <?= $form->field ($user_info, 'member')
  148 + ->label ('Членство в МФП')
  149 + ->hint ('Выберите если хотите стать членом МФП и наш менеджер свяжется с Вами.')
  150 + ->radioList ([0 => 'Не хочу', 1 => 'Хочу стать']);
  151 + ?>
  152 + <?= ImageUploader::widget([
  153 + 'model'=> $user_info,
  154 + 'field'=>'image',
  155 + 'width'=>100,
  156 + 'height'=>100,
  157 + 'multi'=>false,
  158 + 'gallery' =>$user_info->image,
  159 + 'name' => 'Загрузить аватар'
  160 + ]);
  161 + ?>
  162 + <?= ImageUploader::widget([
  163 + 'model'=> $user_info,
  164 + 'field'=>'poster',
  165 + 'width'=>1200,
  166 + 'height'=>600,
  167 + 'multi'=>false,
  168 + 'gallery' =>$user_info->poster,
  169 + 'name' => 'Загрузить постер'
  170 + ]);
  171 + ?>
  172 + <?= Html::submitButton('Обновить', ['class' => 'btn btn-primary']);
  173 + $form->end ();
  174 + ?>
105 175 </div>
... ...
frontend/views/layouts/admin.php
... ... @@ -7,39 +7,24 @@ use yii\widgets\Menu;
7 7 /* @var $content string */
8 8 $this->beginContent('@app/views/layouts/main.php');
9 9 ?>
10   - <div class="wrap">
11   - <div class="container">
12   - <div class="right_block">
13   - <?= Breadcrumbs::widget([
14   - 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
15   - ]) ?>
16   - <div class="section-box menu-content-wr">
  10 +
  11 +<div class="section-box admin-page">
17 12 <div class="box-wr">
18 13 <div class="box-all">
19   - <?php
  14 + <div class="login-right-column">
  15 + <div class="admin-my-page">Моя страница</div>
  16 + <?php
20 17 echo Menu::widget([
21 18 'options' => [
22   - 'class' => 'menu-content',
  19 + 'class' => 'menu-admin',
23 20 ],
24   - 'activeCssClass' => 'active-menu-content',
  21 + 'activeCssClass' => 'active-menu-admin',
25 22 'items' => [
26 23 [
27   - 'label' => 'Общее',
  24 + 'label' => 'Учетные данные',
28 25 'url' => ['accounts/general'],
29 26 ],
30 27 [
31   - 'label' => 'Описание',
32   - 'url' => ['accounts/description'],
33   - ],
34   - [
35   - 'label' => 'Портфолио',
36   - 'url' => ['accounts/portfolio'],
37   - ],
38   - [
39   - 'label' => 'Настройка аккаунта',
40   - 'url' => ['accounts/setting'],
41   - ],
42   - [
43 28 'label' => 'Контакты',
44 29 'url' => ['accounts/contacts'],
45 30 ],
... ... @@ -48,37 +33,81 @@ $this-&gt;beginContent(&#39;@app/views/layouts/main.php&#39;);
48 33 'url' => ['accounts/service'],
49 34 ],
50 35 [
  36 + 'label' => 'Трудовой стаж',
  37 + 'url' => ['accounts/'],
  38 + ],
  39 + [
51 40 'label' => 'Дополнительные навыки',
52 41 'url' => ['accounts/add-skills'],
53 42 ],
54 43 [
55   - 'label' => 'Трудовой стаж',
56   - 'url' => ['accounts/employment'],
  44 + 'label' => 'Описание',
  45 + 'url' => ['accounts/'],
  46 + ],
  47 +
  48 + [
  49 + 'label' => 'Команда',
  50 + 'url' => ['accounts/'],
  51 + ],
  52 +
  53 + [
  54 + 'label' => 'Вакансии',
  55 + 'url' => ['accounts/'],
  56 + ],
  57 + [
  58 + 'label' => 'Ваши проекты',
  59 + 'url' => ['accounts/'],
57 60 ],
58 61 [
59   - 'label' => 'Проекты',
60   - 'url' => ['accounts/projects'],
  62 + 'label' => 'Портфолио',
  63 + 'url' => ['accounts/portfolio'],
  64 + ],
  65 +
  66 + [
  67 + 'label' => 'Блог',
  68 + 'url' => ['accounts/'],
61 69 ],
62 70 [
63 71 'label' => 'Галерея',
64 72 'url' => ['accounts/gallery'],
65 73 ],
66 74 [
  75 + 'label' => 'Сообщения',
  76 + 'url' => ['accounts/'],
  77 + ],
  78 + [
  79 + 'label' => 'Уведомления о проектах',
  80 + 'url' => ['accounts/'],
  81 + ],
  82 + [
  83 + 'label' => 'Закладки',
  84 + 'url' => ['accounts/'],
  85 + ],
  86 + [
  87 + 'label' => 'Настройка аккаунта',
  88 + 'url' => ['accounts/setting'],
  89 + ],
  90 + [
67 91 'label' => 'Выход',
  92 + 'options' => ['class'=>'logout-li'],
68 93 'url' => ['/site/logout']
69 94 ],
  95 +// [
  96 +// 'label' => 'Общее',
  97 +// 'options' => ['class'=>'test'],
  98 +// 'url' => ['accounts/general'],
  99 +// ],
70 100 ],
71 101 ]);
72   - ?>
  102 + ?>
  103 + </div>
  104 +
  105 + <div class="login-left-column">
  106 + <?= $content ?>
  107 + </div>
  108 +
73 109 </div>
74 110 </div>
75 111 </div>
76   - <div class="menu-location-index">
77   - <?= $content ?>
78   -
79   - </div>
80 112  
81   - </div>
82   - </div>
83   - </div>
84 113 <?php $this->endContent() ?>
85 114 \ No newline at end of file
... ...
frontend/views/layouts/main.php
... ... @@ -120,11 +120,12 @@ AppAsset::register($this);
120 120  
121 121 <div class="search-main-menu">
122 122 <form action="">
123   - <input type="search"/>
124   - <input type="submit" value=""/>
  123 + <input value="" name="" type="search"/>
  124 + <input type="hidden" value="1" name="" />
  125 + <button type="submit" value=""></button>
125 126 <div class="search-list">
126   - <span>Исполнители</span>
127   - <ul>
  127 + <span>Проекты</span>
  128 + <ul class="search-ul">
128 129 <li>Проекты</li>
129 130 <li>Исполнители</li>
130 131 <li>Заказчики</li>
... ...
frontend/views/site/registration.php
  1 +<?php
  2 +/**
  3 + * @var $user common\models\User
  4 + * @var $user_info common\models\UserInfo
  5 + */
  6 + use yii\captcha\Captcha;
  7 + use yii\widgets\ActiveForm;
  8 + $this->registerJsFile('/js/selectize.js');
  9 +?>
1 10 <div class="section-box content">
2 11 <div class="section-box registration">
3 12 <div class="box-wr">
4 13 <div class="box-all">
5 14 <div class="registration-title style">Регистрация</div>
6 15 <div class="registration-form style">
7   - <form action="" method="">
  16 + <?php $form = ActiveForm::begin(); ?>
8 17  
9 18 <div class="input-blocks-wrapper">
10 19 <div class="input-blocks">
11   - <div class="form-group field-accountsform-email require has-erro">
12   - <label for="input-txt-1">Логин</label>
13   - <input class="custom-input-2" id="input-txt-1" type="text">
14   - <div class="help-block">Значение «E-mail (Логин)» не является правильным email адресом.</div>
15   - </div>
  20 + <?= $form->field($model, 'username')->textInput(['class'=>'custom-input-2'])?>
16 21 </div>
17 22 <div class="input-blocks-help-wr">
18 23 <div class="input-blocks-help">Логин должен содержать не менее 3-х символов, начинаться с английской буквы и заканчиваться буквой или цифрой. Допускаются английские буквы, цифры и знаки 'тире', 'подчеркивание', 'точка'</div>
... ... @@ -22,8 +27,7 @@
22 27 <div class="input-blocks-wrapper">
23 28 <div class="input-blocks">
24 29  
25   - <label for="input-txt-2">Пароль</label>
26   - <input class="custom-input-2" id="input-txt-2" type="text">
  30 + <?= $form->field($model, 'password')->passwordInput(['class'=>'custom-input-2'])?>
27 31  
28 32 </div>
29 33 <div class="input-blocks-help-wr">
... ... @@ -33,8 +37,7 @@
33 37  
34 38 <div class="input-blocks-wrapper">
35 39 <div class="input-blocks">
36   - <label for="input-txt-3">E-mail</label>
37   - <input class="custom-input-2" id="input-txt-3" type="text">
  40 + <?= $form->field($model, 'email')->textInput(['class'=>'custom-input-2'])?>
38 41 </div>
39 42 <div class="input-blocks-help-wr">
40 43 <div class="input-blocks-help">На этот адрес электронной почты будет отправлено уведомление о регистрации.</div>
... ... @@ -46,6 +49,7 @@
46 49 <div class="who-you-are-form-wr style">
47 50 <div class="who-you-are-form">
48 51 <div class="check-radio-wr">
  52 +
49 53 <div class="custom-form-buttons">
50 54 <input class="custom-radio" id="custom-radio-1" name="group-1" checked="checked" type="radio">
51 55 <label for="custom-radio-1"><span></span>Частное лицо copy</label>
... ... @@ -99,15 +103,6 @@
99 103 <option value="5">Черкасы</option>
100 104 </select>
101 105 </div>
102   -
103   - <!--<div class="input-blocks">-->
104   - <!--<select name="" id="" >-->
105   - <!--<option selected disabled></option>-->
106   - <!--<option value="">Киев</option>-->
107   - <!--<option value="">Киев</option>-->
108   - <!--<option value="">Киев</option>-->
109   - <!--</select>-->
110   - <!--</div>-->
111 106 </div>
112 107  
113 108 <div class="input-blocks-wrapper city-two">
... ... @@ -117,18 +112,21 @@
117 112 </div>
118 113 <div class="form-help-two">Если вашего города нет в списке, введите его. </div>
119 114 </div>
120   -
121   - <div class="input-blocks-wrapper captcha">
122   - <div class="input-blocks-captcha">
123   - <img id="accountsform-verifycode-image" src="/images/captcha.png" alt="">
124   - </div>
125   - </div>
126   -
127 115 <div class="input-blocks-wrapper">
128   - <div class="input-blocks">
129   - <label for="input-txt-8">Введите проверочный код</label>
130   - <input class="custom-input-2" id="input-txt-8" type="text">
131   - </div>
  116 + <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
  117 + 'template' => '
  118 + <div class="input-blocks-wrapper">
  119 + <div class="input-blocks">
  120 + <label for="input-txt-8">Введите проверочный код</label>
  121 + {input}
  122 + </div>
  123 + </div>
  124 + <div class="input-blocks-wrapper captcha">
  125 + <div class="input-blocks-captcha">
  126 + {image}
  127 + </div>
  128 + </div>',
  129 + ])->label(false) ?>
132 130 </div>
133 131 <div class="input-blocks-wrapper button">
134 132 <button type="submit" value="Submit">Зарегистрироваться</button>
... ... @@ -137,7 +135,7 @@
137 135 </div>
138 136 </div>
139 137  
140   - </form>
  138 + <?php ActiveForm::end()?>
141 139 </div>
142 140 </div>
143 141 </div>
... ...
frontend/web/css/style.css
... ... @@ -11,7 +11,10 @@ body {
11 11 img {
12 12 border: none;
13 13 }
14   -
  14 +label {
  15 + margin-bottom: 0;
  16 + font-weight: 400;
  17 +}
15 18 input::-webkit-input-placeholder{color:#777}input::-moz-placeholder{color:#777}input:-moz-placeholder{color:#777}input:-ms-input-placeholder{color:#777}textarea::-webkit-input-placeholder{color:#777}textarea::-moz-placeholder{color:#777}textarea:-moz-placeholder{color:#777}textarea:-ms-input-placeholder{color:#777}
16 19 input, textarea{color: #777}
17 20 [class*="section-box"] {
... ... @@ -153,8 +156,27 @@ ul.header-contacts-menu li:last-child a {
153 156 font-size: 13px;
154 157 transition: 0.3s;
155 158 margin-left: 14px;
  159 + width: 102px;
  160 + overflow: hidden;
  161 + position: relative;
  162 + height: 15px;
  163 + white-space: nowrap;
156 164 }
157 165 .header-cabinet-wr a:hover{opacity: 0.9}
  166 +.header-cabinet-wr a:before{
  167 + content: '';
  168 + height: 15px;
  169 + width: 15px;
  170 + background: #fff;
  171 + opacity: 0.5;
  172 + position: absolute;
  173 + top: 0;
  174 + right: 0;
  175 + background: -moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%);
  176 + background: -webkit-linear-gradient(left, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 100%);
  177 + background: linear-gradient(to right, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 100%);
  178 + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 );
  179 +}
158 180 ul.main-menu {
159 181 width: 620px;
160 182 float: left;
... ... @@ -256,9 +278,9 @@ ul.main-menu li span:after{
256 278 height: 29px;
257 279 border: none;
258 280 box-sizing: border-box;
259   - padding: 0 32px 0 10px;
  281 + padding: 0 5px 0 128px;
260 282 }
261   -.search-main-menu form input[type="submit"] {
  283 +.search-main-menu form button[type="submit"] {
262 284 width: 29px;
263 285 height: 29px;
264 286 position: absolute;
... ... @@ -4586,6 +4608,7 @@ input.custom-radio + label, input.custom-check + label {
4586 4608 font-size: 13px;
4587 4609 cursor: pointer;
4588 4610 margin-left: 6px;
  4611 + display: inline;
4589 4612 }
4590 4613 input.custom-radio:checked + label, input.custom-check:checked + label {
4591 4614 color: #0072bc;
... ... @@ -4613,6 +4636,25 @@ input.custom-check + label span {
4613 4636 input.custom-check:checked + label span, input.custom-check:checked + label:hover span {
4614 4637 background: url(/images/sets-ico/check-active.png) no-repeat;transition: .2s;
4615 4638 }
  4639 +
  4640 +input.admin-check + label span {
  4641 + width: 12px;
  4642 + height: 12px;
  4643 + background: url(/images/sets-ico/check.png) no-repeat;
  4644 + float: left;
  4645 + margin-left: 0;
  4646 +}
  4647 +input.disabled.admin-check:checked + label span, input.disabled.admin-check:checked + label:hover span {
  4648 + background: #dcdcdc url(/images/check-disable.png) no-repeat;transition: .2s;
  4649 +}
  4650 +input.disabled.admin-check + label span, input.disabled.admin-check + label:hover span {
  4651 + background: #dcdcdc;transition: .2s;
  4652 +}
  4653 +input.disabled.admin-check:checked + label, input.disabled.admin-check:checked + label:hover {
  4654 + color: inherit;
  4655 + border-bottom: none;
  4656 + cursor: default;
  4657 +}
4616 4658 .check-radio-wr {
4617 4659 width: 50%;
4618 4660 float: left;
... ... @@ -4941,18 +4983,23 @@ input.custom-check:checked + label span, input.custom-check:checked + label:hove
4941 4983 top: 0;
4942 4984 left: 0;
4943 4985 height: 29px;
  4986 + width: 118px;
4944 4987 }
4945 4988 .search-list span {
4946 4989 display: block;
4947 4990 height: 19px;
  4991 + width: 120px;
4948 4992 line-height: 19px;
4949 4993 font-size: 13px;
4950 4994 color: #b7b7b7;
4951 4995 padding-left: 10px;
  4996 + box-sizing: border-box;
4952 4997 border-right: 1px solid #b7b7b7;
4953   - margin-top: 7px;
  4998 + margin-top: 5px;
4954 4999 cursor: pointer;
4955 5000 padding-right: 28px;
  5001 + position: relative;
  5002 + z-index: 2;
4956 5003 }
4957 5004 .search-list:before {
4958 5005 position: absolute;
... ... @@ -4972,10 +5019,12 @@ input.custom-check:checked + label span, input.custom-check:checked + label:hove
4972 5019 border-left: 1px solid #b7b7b7;
4973 5020 border-right: 1px solid #b7b7b7;
4974 5021 border-bottom: 1px solid #b7b7b7;
  5022 + display: none;
4975 5023 }
  5024 +.search-list ul.active {display: block}
4976 5025 .search-list li {
4977 5026 list-style: none;
4978   - padding: 0 20px 0 10px;
  5027 + padding: 0 0 0 10px;
4979 5028 border-top: 1px solid #b7b7b7;
4980 5029 height: 20px;
4981 5030 line-height: 20px;
... ... @@ -4983,9 +5032,104 @@ input.custom-check:checked + label span, input.custom-check:checked + label:hove
4983 5032 font-size: 13px;
4984 5033 color: #b7b7b7;
4985 5034 cursor: pointer;
  5035 + width: 118px;
  5036 + box-sizing: border-box;
4986 5037 }
4987 5038 .search-list li:hover {
4988 5039 transition: 0.2s;
4989 5040 background: #62b8ef;
4990 5041 color: #fff;
4991   -}
4992 5042 \ No newline at end of file
  5043 +}
  5044 +
  5045 +/***login***/
  5046 +.section-box.admin-page {margin-top: 30px}
  5047 +.login-right-column {
  5048 + width: 210px;
  5049 + float: left;
  5050 + background: #0072bc;
  5051 + border-radius: 4px;
  5052 + margin-left: -10px;
  5053 +}
  5054 +.login-left-column {
  5055 + width: 720px;
  5056 + float: right;
  5057 +}
  5058 +.admin-my-page {
  5059 + height: 46px;
  5060 + color: #fff;
  5061 + line-height: 46px;
  5062 + box-sizing: border-box;
  5063 + padding-left: 15px;
  5064 + width: 100%;
  5065 +}
  5066 +ul.menu-admin{
  5067 + width: 100%;
  5068 + float: left;
  5069 + padding: 0 0 0 0;
  5070 + margin: 0;
  5071 +}
  5072 +ul.menu-admin li {
  5073 + width: 100%;
  5074 + float: left;
  5075 + list-style: none;
  5076 + border-top: 1px solid #62b8ef;
  5077 + box-sizing: border-box;
  5078 + padding-left: 15px;
  5079 + height: 29px;
  5080 +}
  5081 +
  5082 +ul.menu-admin li a {
  5083 + color: #fff;
  5084 + text-decoration: none;
  5085 + font-size: 13px;
  5086 + width: 100%;
  5087 + display: block;
  5088 + height: 29px;
  5089 + line-height: 29px;
  5090 + position: relative;
  5091 +}
  5092 +ul.menu-admin li:hover a{
  5093 + color: #62b8ef;
  5094 + transition: 0.2s;
  5095 +}
  5096 +ul.menu-admin li.active-menu-admin {background: #62b8ef;}
  5097 +ul.menu-admin li.active-menu-admin a{color: #fff}
  5098 +ul.menu-admin li.logout-li, ul.menu-admin li.logout-li a, ul.menu-admin li:last-child, ul.menu-admin li:last-child a{
  5099 + height: 34px;
  5100 + line-height: 34px;
  5101 +}
  5102 +
  5103 +
  5104 +.login-left-column-title {border-bottom: 1px solid #dbdbdb; margin-top: 15px; padding-bottom: 23px;}
  5105 +.login-left-column-title, .login-left-column-title h1{font-size: 18px}
  5106 +.form-group{margin-bottom: 0}
  5107 +.border-general {
  5108 + border-bottom: 1px solid #dbdbdb;
  5109 +}
  5110 +.general-check-wr {padding-top: 20px}
  5111 +.general-check-left {
  5112 + width: 120px;
  5113 + position: absolute;
  5114 + top: 50%;
  5115 + margin-top: -10px;
  5116 + left: 0;
  5117 + height: 21px;
  5118 +}
  5119 +.general-check-right {
  5120 + width: 595px;
  5121 + float: right;
  5122 +}
  5123 +.general-check { height: 30px; position: relative }
  5124 +.general-check-right-txt {
  5125 + font-size: 13px;
  5126 + color: #b7b7b7;
  5127 + display: table-cell;
  5128 + vertical-align: middle;
  5129 + height: 19px;
  5130 +}
  5131 +
  5132 +.gen-admin-title {
  5133 + font-size: 18px;
  5134 + color: inherit;
  5135 +}
  5136 +.general-who-title {margin-top: 49px}
4993 5137 \ No newline at end of file
... ...
frontend/web/js/script.js
... ... @@ -824,4 +824,30 @@ $(document).ready(function(){
824 824 })
825 825 }
826 826 }
  827 +
  828 +
  829 +
  830 + $('.search-list span').click(function(){
  831 + if($('.search-ul').hasClass('active')) {
  832 + $('.search-list ul').removeClass('active');
  833 + $('.search-main-menu form input').focus()
  834 + } else {
  835 + $('.search-list ul').addClass('active');
  836 +
  837 + }
  838 + $('.search-list ul.active li').click(function(){
  839 + $('.search-list ul').removeClass('active');
  840 + $('.search-list span').html($(this).text())
  841 + var searchListIndex = $(this).index()
  842 + searchListIndex = searchListIndex+1
  843 + $('.search-main-menu form input[type="hidden"]').val(searchListIndex)
  844 + $('.search-main-menu form input').focus()
  845 + })
  846 +
  847 +
  848 +
  849 + })
  850 +
  851 +
  852 +
827 853 });
828 854 \ No newline at end of file
... ...
frontend/web/js/selectize.js 0 → 100644
  1 +
  2 +(function(root, factory) {
  3 + if (typeof define === 'function' && define.amd) {
  4 + define('sifter', factory);
  5 + } else if (typeof exports === 'object') {
  6 + module.exports = factory();
  7 + } else {
  8 + root.Sifter = factory();
  9 + }
  10 +}(this, function() {
  11 +
  12 +
  13 + var Sifter = function(items, settings) {
  14 + this.items = items;
  15 + this.settings = settings || {diacritics: true};
  16 + };
  17 +
  18 +
  19 + Sifter.prototype.tokenize = function(query) {
  20 + query = trim(String(query || '').toLowerCase());
  21 + if (!query || !query.length) return [];
  22 +
  23 + var i, n, regex, letter;
  24 + var tokens = [];
  25 + var words = query.split(/ +/);
  26 +
  27 + for (i = 0, n = words.length; i < n; i++) {
  28 + regex = escape_regex(words[i]);
  29 + if (this.settings.diacritics) {
  30 + for (letter in DIACRITICS) {
  31 + if (DIACRITICS.hasOwnProperty(letter)) {
  32 + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
  33 + }
  34 + }
  35 + }
  36 + tokens.push({
  37 + string : words[i],
  38 + regex : new RegExp(regex, 'i')
  39 + });
  40 + }
  41 +
  42 + return tokens;
  43 + };
  44 +
  45 +
  46 + Sifter.prototype.iterator = function(object, callback) {
  47 + var iterator;
  48 + if (is_array(object)) {
  49 + iterator = Array.prototype.forEach || function(callback) {
  50 + for (var i = 0, n = this.length; i < n; i++) {
  51 + callback(this[i], i, this);
  52 + }
  53 + };
  54 + } else {
  55 + iterator = function(callback) {
  56 + for (var key in this) {
  57 + if (this.hasOwnProperty(key)) {
  58 + callback(this[key], key, this);
  59 + }
  60 + }
  61 + };
  62 + }
  63 +
  64 + iterator.apply(object, [callback]);
  65 + };
  66 +
  67 + Sifter.prototype.getScoreFunction = function(search, options) {
  68 + var self, fields, tokens, token_count;
  69 +
  70 + self = this;
  71 + search = self.prepareSearch(search, options);
  72 + tokens = search.tokens;
  73 + fields = search.options.fields;
  74 + token_count = tokens.length;
  75 +
  76 +
  77 + var scoreValue = function(value, token) {
  78 + var score, pos;
  79 +
  80 + if (!value) return 0;
  81 + value = String(value || '');
  82 + pos = value.search(token.regex);
  83 + if (pos === -1) return 0;
  84 + score = token.string.length / value.length;
  85 + if (pos === 0) score += 0.5;
  86 + return score;
  87 + };
  88 +
  89 +
  90 + var scoreObject = (function() {
  91 + var field_count = fields.length;
  92 + if (!field_count) {
  93 + return function() { return 0; };
  94 + }
  95 + if (field_count === 1) {
  96 + return function(token, data) {
  97 + return scoreValue(data[fields[0]], token);
  98 + };
  99 + }
  100 + return function(token, data) {
  101 + for (var i = 0, sum = 0; i < field_count; i++) {
  102 + sum += scoreValue(data[fields[i]], token);
  103 + }
  104 + return sum / field_count;
  105 + };
  106 + })();
  107 +
  108 + if (!token_count) {
  109 + return function() { return 0; };
  110 + }
  111 + if (token_count === 1) {
  112 + return function(data) {
  113 + return scoreObject(tokens[0], data);
  114 + };
  115 + }
  116 +
  117 + if (search.options.conjunction === 'and') {
  118 + return function(data) {
  119 + var score;
  120 + for (var i = 0, sum = 0; i < token_count; i++) {
  121 + score = scoreObject(tokens[i], data);
  122 + if (score <= 0) return 0;
  123 + sum += score;
  124 + }
  125 + return sum / token_count;
  126 + };
  127 + } else {
  128 + return function(data) {
  129 + for (var i = 0, sum = 0; i < token_count; i++) {
  130 + sum += scoreObject(tokens[i], data);
  131 + }
  132 + return sum / token_count;
  133 + };
  134 + }
  135 + };
  136 +
  137 +
  138 + Sifter.prototype.getSortFunction = function(search, options) {
  139 + var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
  140 +
  141 + self = this;
  142 + search = self.prepareSearch(search, options);
  143 + sort = (!search.query && options.sort_empty) || options.sort;
  144 +
  145 + /**
  146 + * Fetches the specified sort field value
  147 + * from a search result item.
  148 + *
  149 + * @param {string} name
  150 + * @param {object} result
  151 + * @return {mixed}
  152 + */
  153 + get_field = function(name, result) {
  154 + if (name === '$score') return result.score;
  155 + return self.items[result.id][name];
  156 + };
  157 +
  158 + // parse options
  159 + fields = [];
  160 + if (sort) {
  161 + for (i = 0, n = sort.length; i < n; i++) {
  162 + if (search.query || sort[i].field !== '$score') {
  163 + fields.push(sort[i]);
  164 + }
  165 + }
  166 + }
  167 +
  168 + // the "$score" field is implied to be the primary
  169 + // sort field, unless it's manually specified
  170 + if (search.query) {
  171 + implicit_score = true;
  172 + for (i = 0, n = fields.length; i < n; i++) {
  173 + if (fields[i].field === '$score') {
  174 + implicit_score = false;
  175 + break;
  176 + }
  177 + }
  178 + if (implicit_score) {
  179 + fields.unshift({field: '$score', direction: 'desc'});
  180 + }
  181 + } else {
  182 + for (i = 0, n = fields.length; i < n; i++) {
  183 + if (fields[i].field === '$score') {
  184 + fields.splice(i, 1);
  185 + break;
  186 + }
  187 + }
  188 + }
  189 +
  190 + multipliers = [];
  191 + for (i = 0, n = fields.length; i < n; i++) {
  192 + multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
  193 + }
  194 +
  195 + // build function
  196 + fields_count = fields.length;
  197 + if (!fields_count) {
  198 + return null;
  199 + } else if (fields_count === 1) {
  200 + field = fields[0].field;
  201 + multiplier = multipliers[0];
  202 + return function(a, b) {
  203 + return multiplier * cmp(
  204 + get_field(field, a),
  205 + get_field(field, b)
  206 + );
  207 + };
  208 + } else {
  209 + return function(a, b) {
  210 + var i, result, a_value, b_value, field;
  211 + for (i = 0; i < fields_count; i++) {
  212 + field = fields[i].field;
  213 + result = multipliers[i] * cmp(
  214 + get_field(field, a),
  215 + get_field(field, b)
  216 + );
  217 + if (result) return result;
  218 + }
  219 + return 0;
  220 + };
  221 + }
  222 + };
  223 +
  224 +
  225 + Sifter.prototype.prepareSearch = function(query, options) {
  226 + if (typeof query === 'object') return query;
  227 +
  228 + options = extend({}, options);
  229 +
  230 + var option_fields = options.fields;
  231 + var option_sort = options.sort;
  232 + var option_sort_empty = options.sort_empty;
  233 +
  234 + if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
  235 + if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
  236 + if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
  237 +
  238 + return {
  239 + options : options,
  240 + query : String(query || '').toLowerCase(),
  241 + tokens : this.tokenize(query),
  242 + total : 0,
  243 + items : []
  244 + };
  245 + };
  246 +
  247 +
  248 + Sifter.prototype.search = function(query, options) {
  249 + var self = this, value, score, search, calculateScore;
  250 + var fn_sort;
  251 + var fn_score;
  252 +
  253 + search = this.prepareSearch(query, options);
  254 + options = search.options;
  255 + query = search.query;
  256 +
  257 + // generate result scoring function
  258 + fn_score = options.score || self.getScoreFunction(search);
  259 +
  260 + // perform search and sort
  261 + if (query.length) {
  262 + self.iterator(self.items, function(item, id) {
  263 + score = fn_score(item);
  264 + if (options.filter === false || score > 0) {
  265 + search.items.push({'score': score, 'id': id});
  266 + }
  267 + });
  268 + } else {
  269 + self.iterator(self.items, function(item, id) {
  270 + search.items.push({'score': 1, 'id': id});
  271 + });
  272 + }
  273 +
  274 + fn_sort = self.getSortFunction(search, options);
  275 + if (fn_sort) search.items.sort(fn_sort);
  276 +
  277 + // apply limits
  278 + search.total = search.items.length;
  279 + if (typeof options.limit === 'number') {
  280 + search.items = search.items.slice(0, options.limit);
  281 + }
  282 +
  283 + return search;
  284 + };
  285 +
  286 + // utilities
  287 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  288 +
  289 + var cmp = function(a, b) {
  290 + if (typeof a === 'number' && typeof b === 'number') {
  291 + return a > b ? 1 : (a < b ? -1 : 0);
  292 + }
  293 + a = asciifold(String(a || ''));
  294 + b = asciifold(String(b || ''));
  295 + if (a > b) return 1;
  296 + if (b > a) return -1;
  297 + return 0;
  298 + };
  299 +
  300 + var extend = function(a, b) {
  301 + var i, n, k, object;
  302 + for (i = 1, n = arguments.length; i < n; i++) {
  303 + object = arguments[i];
  304 + if (!object) continue;
  305 + for (k in object) {
  306 + if (object.hasOwnProperty(k)) {
  307 + a[k] = object[k];
  308 + }
  309 + }
  310 + }
  311 + return a;
  312 + };
  313 +
  314 + var trim = function(str) {
  315 + return (str + '').replace(/^\s+|\s+$|/g, '');
  316 + };
  317 +
  318 + var escape_regex = function(str) {
  319 + return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
  320 + };
  321 +
  322 + var is_array = Array.isArray || ($ && $.isArray) || function(object) {
  323 + return Object.prototype.toString.call(object) === '[object Array]';
  324 + };
  325 +
  326 + var DIACRITICS = {
  327 + 'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
  328 + 'c': '[cÇçćĆčČ]',
  329 + 'd': '[dđĐďĎ]',
  330 + 'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
  331 + 'i': '[iÌÍÎÏìíîïĪī]',
  332 + 'l': '[lłŁ]',
  333 + 'n': '[nÑñňŇńŃ]',
  334 + 'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
  335 + 'r': '[rřŘ]',
  336 + 's': '[sŠšśŚ]',
  337 + 't': '[tťŤ]',
  338 + 'u': '[uÙÚÛÜùúûüůŮŪū]',
  339 + 'y': '[yŸÿýÝ]',
  340 + 'z': '[zŽžżŻźŹ]'
  341 + };
  342 +
  343 + var asciifold = (function() {
  344 + var i, n, k, chunk;
  345 + var foreignletters = '';
  346 + var lookup = {};
  347 + for (k in DIACRITICS) {
  348 + if (DIACRITICS.hasOwnProperty(k)) {
  349 + chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
  350 + foreignletters += chunk;
  351 + for (i = 0, n = chunk.length; i < n; i++) {
  352 + lookup[chunk.charAt(i)] = k;
  353 + }
  354 + }
  355 + }
  356 + var regexp = new RegExp('[' + foreignletters + ']', 'g');
  357 + return function(str) {
  358 + return str.replace(regexp, function(foreignletter) {
  359 + return lookup[foreignletter];
  360 + }).toLowerCase();
  361 + };
  362 + })();
  363 +
  364 +
  365 + // export
  366 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  367 +
  368 + return Sifter;
  369 +}));
  370 +
  371 +
  372 +
  373 +
  374 +
  375 +(function(root, factory) {
  376 + if (typeof define === 'function' && define.amd) {
  377 + define('microplugin', factory);
  378 + } else if (typeof exports === 'object') {
  379 + module.exports = factory();
  380 + } else {
  381 + root.MicroPlugin = factory();
  382 + }
  383 +}(this, function() {
  384 + var MicroPlugin = {};
  385 +
  386 + MicroPlugin.mixin = function(Interface) {
  387 + Interface.plugins = {};
  388 +
  389 + Interface.prototype.initializePlugins = function(plugins) {
  390 + var i, n, key;
  391 + var self = this;
  392 + var queue = [];
  393 +
  394 + self.plugins = {
  395 + names : [],
  396 + settings : {},
  397 + requested : {},
  398 + loaded : {}
  399 + };
  400 +
  401 + if (utils.isArray(plugins)) {
  402 + for (i = 0, n = plugins.length; i < n; i++) {
  403 + if (typeof plugins[i] === 'string') {
  404 + queue.push(plugins[i]);
  405 + } else {
  406 + self.plugins.settings[plugins[i].name] = plugins[i].options;
  407 + queue.push(plugins[i].name);
  408 + }
  409 + }
  410 + } else if (plugins) {
  411 + for (key in plugins) {
  412 + if (plugins.hasOwnProperty(key)) {
  413 + self.plugins.settings[key] = plugins[key];
  414 + queue.push(key);
  415 + }
  416 + }
  417 + }
  418 +
  419 + while (queue.length) {
  420 + self.require(queue.shift());
  421 + }
  422 + };
  423 +
  424 + Interface.prototype.loadPlugin = function(name) {
  425 + var self = this;
  426 + var plugins = self.plugins;
  427 + var plugin = Interface.plugins[name];
  428 +
  429 + if (!Interface.plugins.hasOwnProperty(name)) {
  430 + throw new Error('Unable to find "' + name + '" plugin');
  431 + }
  432 +
  433 + plugins.requested[name] = true;
  434 + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
  435 + plugins.names.push(name);
  436 + };
  437 +
  438 + Interface.prototype.require = function(name) {
  439 + var self = this;
  440 + var plugins = self.plugins;
  441 +
  442 + if (!self.plugins.loaded.hasOwnProperty(name)) {
  443 + if (plugins.requested[name]) {
  444 + throw new Error('Plugin has circular dependency ("' + name + '")');
  445 + }
  446 + self.loadPlugin(name);
  447 + }
  448 +
  449 + return plugins.loaded[name];
  450 + };
  451 +
  452 +
  453 + Interface.define = function(name, fn) {
  454 + Interface.plugins[name] = {
  455 + 'name' : name,
  456 + 'fn' : fn
  457 + };
  458 + };
  459 + };
  460 +
  461 + var utils = {
  462 + isArray: Array.isArray || function(vArg) {
  463 + return Object.prototype.toString.call(vArg) === '[object Array]';
  464 + }
  465 + };
  466 +
  467 + return MicroPlugin;
  468 +}));
  469 +
  470 +
  471 +/*jshint curly:false */
  472 +/*jshint browser:true */
  473 +
  474 +(function(root, factory) {
  475 + if (typeof define === 'function' && define.amd) {
  476 + define('selectize', ['jquery','sifter','microplugin'], factory);
  477 + } else if (typeof exports === 'object') {
  478 + module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
  479 + } else {
  480 + root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
  481 + }
  482 +}(this, function($, Sifter, MicroPlugin) {
  483 + 'use strict';
  484 +
  485 + var highlight = function($element, pattern) {
  486 + if (typeof pattern === 'string' && !pattern.length) return;
  487 + var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
  488 +
  489 + var highlight = function(node) {
  490 + var skip = 0;
  491 + if (node.nodeType === 3) {
  492 + var pos = node.data.search(regex);
  493 + if (pos >= 0 && node.data.length > 0) {
  494 + var match = node.data.match(regex);
  495 + var spannode = document.createElement('span');
  496 + spannode.className = 'highlight';
  497 + var middlebit = node.splitText(pos);
  498 + var endbit = middlebit.splitText(match[0].length);
  499 + var middleclone = middlebit.cloneNode(true);
  500 + spannode.appendChild(middleclone);
  501 + middlebit.parentNode.replaceChild(spannode, middlebit);
  502 + skip = 1;
  503 + }
  504 + } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
  505 + for (var i = 0; i < node.childNodes.length; ++i) {
  506 + i += highlight(node.childNodes[i]);
  507 + }
  508 + }
  509 + return skip;
  510 + };
  511 +
  512 + return $element.each(function() {
  513 + highlight(this);
  514 + });
  515 + };
  516 +
  517 + var MicroEvent = function() {};
  518 + MicroEvent.prototype = {
  519 + on: function(event, fct){
  520 + this._events = this._events || {};
  521 + this._events[event] = this._events[event] || [];
  522 + this._events[event].push(fct);
  523 + },
  524 + off: function(event, fct){
  525 + var n = arguments.length;
  526 + if (n === 0) return delete this._events;
  527 + if (n === 1) return delete this._events[event];
  528 +
  529 + this._events = this._events || {};
  530 + if (event in this._events === false) return;
  531 + this._events[event].splice(this._events[event].indexOf(fct), 1);
  532 + },
  533 + trigger: function(event /* , args... */){
  534 + this._events = this._events || {};
  535 + if (event in this._events === false) return;
  536 + for (var i = 0; i < this._events[event].length; i++){
  537 + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
  538 + }
  539 + }
  540 + };
  541 +
  542 +
  543 + MicroEvent.mixin = function(destObject){
  544 + var props = ['on', 'off', 'trigger'];
  545 + for (var i = 0; i < props.length; i++){
  546 + destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
  547 + }
  548 + };
  549 +
  550 + var IS_MAC = /Mac/.test(navigator.userAgent);
  551 +
  552 + var KEY_A = 65;
  553 + var KEY_COMMA = 188;
  554 + var KEY_RETURN = 13;
  555 + var KEY_ESC = 27;
  556 + var KEY_LEFT = 37;
  557 + var KEY_UP = 38;
  558 + var KEY_P = 80;
  559 + var KEY_RIGHT = 39;
  560 + var KEY_DOWN = 40;
  561 + var KEY_N = 78;
  562 + var KEY_BACKSPACE = 8;
  563 + var KEY_DELETE = 46;
  564 + var KEY_SHIFT = 16;
  565 + var KEY_CMD = IS_MAC ? 91 : 17;
  566 + var KEY_CTRL = IS_MAC ? 18 : 17;
  567 + var KEY_TAB = 9;
  568 +
  569 + var TAG_SELECT = 1;
  570 + var TAG_INPUT = 2;
  571 +
  572 + // for now, android support in general is too spotty to support validity
  573 + var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
  574 +
  575 + var isset = function(object) {
  576 + return typeof object !== 'undefined';
  577 + };
  578 +
  579 +
  580 + var hash_key = function(value) {
  581 + if (typeof value === 'undefined' || value === null) return null;
  582 + if (typeof value === 'boolean') return value ? '1' : '0';
  583 + return value + '';
  584 + };
  585 +
  586 +
  587 + var escape_html = function(str) {
  588 + return (str + '')
  589 + .replace(/&/g, '&amp;')
  590 + .replace(/</g, '&lt;')
  591 + .replace(/>/g, '&gt;')
  592 + .replace(/"/g, '&quot;');
  593 + };
  594 +
  595 +
  596 + var escape_replace = function(str) {
  597 + return (str + '').replace(/\$/g, '$$$$');
  598 + };
  599 +
  600 + var hook = {};
  601 +
  602 +
  603 + hook.before = function(self, method, fn) {
  604 + var original = self[method];
  605 + self[method] = function() {
  606 + fn.apply(self, arguments);
  607 + return original.apply(self, arguments);
  608 + };
  609 + };
  610 +
  611 +
  612 + hook.after = function(self, method, fn) {
  613 + var original = self[method];
  614 + self[method] = function() {
  615 + var result = original.apply(self, arguments);
  616 + fn.apply(self, arguments);
  617 + return result;
  618 + };
  619 + };
  620 +
  621 +
  622 + var once = function(fn) {
  623 + var called = false;
  624 + return function() {
  625 + if (called) return;
  626 + called = true;
  627 + fn.apply(this, arguments);
  628 + };
  629 + };
  630 +
  631 +
  632 + var debounce = function(fn, delay) {
  633 + var timeout;
  634 + return function() {
  635 + var self = this;
  636 + var args = arguments;
  637 + window.clearTimeout(timeout);
  638 + timeout = window.setTimeout(function() {
  639 + fn.apply(self, args);
  640 + }, delay);
  641 + };
  642 + };
  643 +
  644 +
  645 + var debounce_events = function(self, types, fn) {
  646 + var type;
  647 + var trigger = self.trigger;
  648 + var event_args = {};
  649 +
  650 + // override trigger method
  651 + self.trigger = function() {
  652 + var type = arguments[0];
  653 + if (types.indexOf(type) !== -1) {
  654 + event_args[type] = arguments;
  655 + } else {
  656 + return trigger.apply(self, arguments);
  657 + }
  658 + };
  659 +
  660 + // invoke provided function
  661 + fn.apply(self, []);
  662 + self.trigger = trigger;
  663 +
  664 + // trigger queued events
  665 + for (type in event_args) {
  666 + if (event_args.hasOwnProperty(type)) {
  667 + trigger.apply(self, event_args[type]);
  668 + }
  669 + }
  670 + };
  671 +
  672 +
  673 + var watchChildEvent = function($parent, event, selector, fn) {
  674 + $parent.on(event, selector, function(e) {
  675 + var child = e.target;
  676 + while (child && child.parentNode !== $parent[0]) {
  677 + child = child.parentNode;
  678 + }
  679 + e.currentTarget = child;
  680 + return fn.apply(this, [e]);
  681 + });
  682 + };
  683 +
  684 + var getSelection = function(input) {
  685 + var result = {};
  686 + if ('selectionStart' in input) {
  687 + result.start = input.selectionStart;
  688 + result.length = input.selectionEnd - result.start;
  689 + } else if (document.selection) {
  690 + input.focus();
  691 + var sel = document.selection.createRange();
  692 + var selLen = document.selection.createRange().text.length;
  693 + sel.moveStart('character', -input.value.length);
  694 + result.start = sel.text.length - selLen;
  695 + result.length = selLen;
  696 + }
  697 + return result;
  698 + };
  699 +
  700 + var transferStyles = function($from, $to, properties) {
  701 + var i, n, styles = {};
  702 + if (properties) {
  703 + for (i = 0, n = properties.length; i < n; i++) {
  704 + styles[properties[i]] = $from.css(properties[i]);
  705 + }
  706 + } else {
  707 + styles = $from.css();
  708 + }
  709 + $to.css(styles);
  710 + };
  711 +
  712 +
  713 + var measureString = function(str, $parent) {
  714 + if (!str) {
  715 + return 0;
  716 + }
  717 +
  718 + var $test = $('<test>').css({
  719 + position: 'absolute',
  720 + top: -99999,
  721 + left: -99999,
  722 + width: 'auto',
  723 + padding: 0,
  724 + whiteSpace: 'pre'
  725 + }).text(str).appendTo('body');
  726 +
  727 + transferStyles($parent, $test, [
  728 + 'letterSpacing',
  729 + 'fontSize',
  730 + 'fontFamily',
  731 + 'fontWeight',
  732 + 'textTransform'
  733 + ]);
  734 +
  735 + var width = $test.width();
  736 + $test.remove();
  737 +
  738 + return width;
  739 + };
  740 +
  741 +
  742 + var autoGrow = function($input) {
  743 + var currentWidth = null;
  744 +
  745 + var update = function(e, options) {
  746 + var value, keyCode, printable, placeholder, width;
  747 + var shift, character, selection;
  748 + e = e || window.event || {};
  749 + options = options || {};
  750 +
  751 + if (e.metaKey || e.altKey) return;
  752 + if (!options.force && $input.data('grow') === false) return;
  753 +
  754 + value = $input.val();
  755 + if (e.type && e.type.toLowerCase() === 'keydown') {
  756 + keyCode = e.keyCode;
  757 + printable = (
  758 + (keyCode >= 97 && keyCode <= 122) || // a-z
  759 + (keyCode >= 65 && keyCode <= 90) || // A-Z
  760 + (keyCode >= 48 && keyCode <= 57) || // 0-9
  761 + keyCode === 32 // space
  762 + );
  763 +
  764 + if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
  765 + selection = getSelection($input[0]);
  766 + if (selection.length) {
  767 + value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
  768 + } else if (keyCode === KEY_BACKSPACE && selection.start) {
  769 + value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
  770 + } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
  771 + value = value.substring(0, selection.start) + value.substring(selection.start + 1);
  772 + }
  773 + } else if (printable) {
  774 + shift = e.shiftKey;
  775 + character = String.fromCharCode(e.keyCode);
  776 + if (shift) character = character.toUpperCase();
  777 + else character = character.toLowerCase();
  778 + value += character;
  779 + }
  780 + }
  781 +
  782 + placeholder = $input.attr('placeholder');
  783 + if (!value && placeholder) {
  784 + value = placeholder;
  785 + }
  786 +
  787 + width = measureString(value, $input) + 4;
  788 + if (width !== currentWidth) {
  789 + currentWidth = width;
  790 + $input.width(width);
  791 + $input.triggerHandler('resize');
  792 + }
  793 + };
  794 +
  795 + $input.on('keydown keyup update blur', update);
  796 + update();
  797 + };
  798 +
  799 + var Selectize = function($input, settings) {
  800 + var key, i, n, dir, input, self = this;
  801 + input = $input[0];
  802 + input.selectize = self;
  803 +
  804 + // detect rtl environment
  805 + var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
  806 + dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
  807 + dir = dir || $input.parents('[dir]:first').attr('dir') || '';
  808 +
  809 + // setup default state
  810 + $.extend(self, {
  811 + order : 0,
  812 + settings : settings,
  813 + $input : $input,
  814 + tabIndex : $input.attr('tabindex') || '',
  815 + tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
  816 + rtl : /rtl/i.test(dir),
  817 +
  818 + eventNS : '.selectize' + (++Selectize.count),
  819 + highlightedValue : null,
  820 + isOpen : false,
  821 + isDisabled : false,
  822 + isRequired : $input.is('[required]'),
  823 + isInvalid : false,
  824 + isLocked : false,
  825 + isFocused : false,
  826 + isInputHidden : false,
  827 + isSetup : false,
  828 + isShiftDown : false,
  829 + isCmdDown : false,
  830 + isCtrlDown : false,
  831 + ignoreFocus : false,
  832 + ignoreBlur : false,
  833 + ignoreHover : false,
  834 + hasOptions : false,
  835 + currentResults : null,
  836 + lastValue : '',
  837 + caretPos : 0,
  838 + loading : 0,
  839 + loadedSearches : {},
  840 +
  841 + $activeOption : null,
  842 + $activeItems : [],
  843 +
  844 + optgroups : {},
  845 + options : {},
  846 + userOptions : {},
  847 + items : [],
  848 + renderCache : {},
  849 + onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
  850 + });
  851 +
  852 + // search system
  853 + self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
  854 +
  855 + // build options table
  856 + if (self.settings.options) {
  857 + for (i = 0, n = self.settings.options.length; i < n; i++) {
  858 + self.registerOption(self.settings.options[i]);
  859 + }
  860 + delete self.settings.options;
  861 + }
  862 +
  863 + // build optgroup table
  864 + if (self.settings.optgroups) {
  865 + for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
  866 + self.registerOptionGroup(self.settings.optgroups[i]);
  867 + }
  868 + delete self.settings.optgroups;
  869 + }
  870 +
  871 + // option-dependent defaults
  872 + self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
  873 + if (typeof self.settings.hideSelected !== 'boolean') {
  874 + self.settings.hideSelected = self.settings.mode === 'multi';
  875 + }
  876 +
  877 + self.initializePlugins(self.settings.plugins);
  878 + self.setupCallbacks();
  879 + self.setupTemplates();
  880 + self.setup();
  881 + };
  882 +
  883 + // mixins
  884 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  885 +
  886 + MicroEvent.mixin(Selectize);
  887 + MicroPlugin.mixin(Selectize);
  888 +
  889 + // methods
  890 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  891 +
  892 + $.extend(Selectize.prototype, {
  893 +
  894 + /**
  895 + * Creates all elements and sets up event bindings.
  896 + */
  897 + setup: function() {
  898 + var self = this;
  899 + var settings = self.settings;
  900 + var eventNS = self.eventNS;
  901 + var $window = $(window);
  902 + var $document = $(document);
  903 + var $input = self.$input;
  904 +
  905 + var $wrapper;
  906 + var $control;
  907 + var $control_input;
  908 + var $dropdown;
  909 + var $dropdown_content;
  910 + var $dropdown_parent;
  911 + var inputMode;
  912 + var timeout_blur;
  913 + var timeout_focus;
  914 + var classes;
  915 + var classes_plugins;
  916 +
  917 + inputMode = self.settings.mode;
  918 + classes = $input.attr('class') || '';
  919 +
  920 + $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
  921 + $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
  922 + $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
  923 + $dropdown_parent = $(settings.dropdownParent || $wrapper);
  924 + $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
  925 + $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
  926 +
  927 + if(self.settings.copyClassesToDropdown) {
  928 + $dropdown.addClass(classes);
  929 + }
  930 +
  931 + $wrapper.css({
  932 + width: $input[0].style.width
  933 + });
  934 +
  935 + if (self.plugins.names.length) {
  936 + classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
  937 + $wrapper.addClass(classes_plugins);
  938 + $dropdown.addClass(classes_plugins);
  939 + }
  940 +
  941 + if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
  942 + $input.attr('multiple', 'multiple');
  943 + }
  944 +
  945 + if (self.settings.placeholder) {
  946 + $control_input.attr('placeholder', settings.placeholder);
  947 + }
  948 +
  949 + // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
  950 + if (!self.settings.splitOn && self.settings.delimiter) {
  951 + var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  952 + self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
  953 + }
  954 +
  955 + if ($input.attr('autocorrect')) {
  956 + $control_input.attr('autocorrect', $input.attr('autocorrect'));
  957 + }
  958 +
  959 + if ($input.attr('autocapitalize')) {
  960 + $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
  961 + }
  962 +
  963 + self.$wrapper = $wrapper;
  964 + self.$control = $control;
  965 + self.$control_input = $control_input;
  966 + self.$dropdown = $dropdown;
  967 + self.$dropdown_content = $dropdown_content;
  968 +
  969 + $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
  970 + $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
  971 + watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
  972 + autoGrow($control_input);
  973 +
  974 + $control.on({
  975 + mousedown : function() { return self.onMouseDown.apply(self, arguments); },
  976 + click : function() { return self.onClick.apply(self, arguments); }
  977 + });
  978 +
  979 + $control_input.on({
  980 + mousedown : function(e) { e.stopPropagation(); },
  981 + keydown : function() { return self.onKeyDown.apply(self, arguments); },
  982 + keyup : function() { return self.onKeyUp.apply(self, arguments); },
  983 + keypress : function() { return self.onKeyPress.apply(self, arguments); },
  984 + resize : function() { self.positionDropdown.apply(self, []); },
  985 + blur : function() { return self.onBlur.apply(self, arguments); },
  986 + focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
  987 + paste : function() { return self.onPaste.apply(self, arguments); }
  988 + });
  989 +
  990 + $document.on('keydown' + eventNS, function(e) {
  991 + self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
  992 + self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
  993 + self.isShiftDown = e.shiftKey;
  994 + });
  995 +
  996 + $document.on('keyup' + eventNS, function(e) {
  997 + if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
  998 + if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
  999 + if (e.keyCode === KEY_CMD) self.isCmdDown = false;
  1000 + });
  1001 +
  1002 + $document.on('mousedown' + eventNS, function(e) {
  1003 + if (self.isFocused) {
  1004 + // prevent events on the dropdown scrollbar from causing the control to blur
  1005 + if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
  1006 + return false;
  1007 + }
  1008 + // blur on click outside
  1009 + if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
  1010 + self.blur(e.target);
  1011 + }
  1012 + }
  1013 + });
  1014 +
  1015 + $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
  1016 + if (self.isOpen) {
  1017 + self.positionDropdown.apply(self, arguments);
  1018 + }
  1019 + });
  1020 + $window.on('mousemove' + eventNS, function() {
  1021 + self.ignoreHover = false;
  1022 + });
  1023 +
  1024 + // store original children and tab index so that they can be
  1025 + // restored when the destroy() method is called.
  1026 + this.revertSettings = {
  1027 + $children : $input.children().detach(),
  1028 + tabindex : $input.attr('tabindex')
  1029 + };
  1030 +
  1031 + $input.attr('tabindex', -1).hide().after(self.$wrapper);
  1032 +
  1033 + if ($.isArray(settings.items)) {
  1034 + self.setValue(settings.items);
  1035 + delete settings.items;
  1036 + }
  1037 +
  1038 + // feature detect for the validation API
  1039 + if (SUPPORTS_VALIDITY_API) {
  1040 + $input.on('invalid' + eventNS, function(e) {
  1041 + e.preventDefault();
  1042 + self.isInvalid = true;
  1043 + self.refreshState();
  1044 + });
  1045 + }
  1046 +
  1047 + self.updateOriginalInput();
  1048 + self.refreshItems();
  1049 + self.refreshState();
  1050 + self.updatePlaceholder();
  1051 + self.isSetup = true;
  1052 +
  1053 + if ($input.is(':disabled')) {
  1054 + self.disable();
  1055 + }
  1056 +
  1057 + self.on('change', this.onChange);
  1058 +
  1059 + $input.data('selectize', self);
  1060 + $input.addClass('selectized');
  1061 + self.trigger('initialize');
  1062 +
  1063 + // preload options
  1064 + if (settings.preload === true) {
  1065 + self.onSearchChange('');
  1066 + }
  1067 +
  1068 + },
  1069 +
  1070 + /**
  1071 + * Sets up default rendering functions.
  1072 + */
  1073 + setupTemplates: function() {
  1074 + var self = this;
  1075 + var field_label = self.settings.labelField;
  1076 + var field_optgroup = self.settings.optgroupLabelField;
  1077 +
  1078 + var templates = {
  1079 + 'optgroup': function(data) {
  1080 + return '<div class="optgroup">' + data.html + '</div>';
  1081 + },
  1082 + 'optgroup_header': function(data, escape) {
  1083 + return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
  1084 + },
  1085 + 'option': function(data, escape) {
  1086 + return '<div class="option">' + escape(data[field_label]) + '</div>';
  1087 + },
  1088 + 'item': function(data, escape) {
  1089 + return '<div class="item">' + escape(data[field_label]) + '</div>';
  1090 + }
  1091 + //'option_create': function(data, escape) {
  1092 + // return '<div class="create">Добавить <strong>' + escape(data.input) + '</strong>&hellip;</div>';
  1093 + //}
  1094 + };
  1095 +
  1096 + self.settings.render = $.extend({}, templates, self.settings.render);
  1097 + },
  1098 +
  1099 + /**
  1100 + * Maps fired events to callbacks provided
  1101 + * in the settings used when creating the control.
  1102 + */
  1103 + setupCallbacks: function() {
  1104 + var key, fn, callbacks = {
  1105 + 'initialize' : 'onInitialize',
  1106 + 'change' : 'onChange',
  1107 + 'item_add' : 'onItemAdd',
  1108 + 'item_remove' : 'onItemRemove',
  1109 + 'clear' : 'onClear',
  1110 + 'option_add' : 'onOptionAdd',
  1111 + 'option_remove' : 'onOptionRemove',
  1112 + 'option_clear' : 'onOptionClear',
  1113 + 'optgroup_add' : 'onOptionGroupAdd',
  1114 + 'optgroup_remove' : 'onOptionGroupRemove',
  1115 + 'optgroup_clear' : 'onOptionGroupClear',
  1116 + 'dropdown_open' : 'onDropdownOpen',
  1117 + 'dropdown_close' : 'onDropdownClose',
  1118 + 'type' : 'onType',
  1119 + 'load' : 'onLoad',
  1120 + 'focus' : 'onFocus',
  1121 + 'blur' : 'onBlur'
  1122 + };
  1123 +
  1124 + for (key in callbacks) {
  1125 + if (callbacks.hasOwnProperty(key)) {
  1126 + fn = this.settings[callbacks[key]];
  1127 + if (fn) this.on(key, fn);
  1128 + }
  1129 + }
  1130 + },
  1131 +
  1132 +
  1133 + onClick: function(e) {
  1134 + var self = this;
  1135 +
  1136 + // necessary for mobile webkit devices (manual focus triggering
  1137 + // is ignored unless invoked within a click event)
  1138 + if (!self.isFocused) {
  1139 + self.focus();
  1140 + e.preventDefault();
  1141 + }
  1142 + },
  1143 +
  1144 +
  1145 + onMouseDown: function(e) {
  1146 + var self = this;
  1147 + var defaultPrevented = e.isDefaultPrevented();
  1148 + var $target = $(e.target);
  1149 +
  1150 + if (self.isFocused) {
  1151 + // retain focus by preventing native handling. if the
  1152 + // event target is the input it should not be modified.
  1153 + // otherwise, text selection within the input won't work.
  1154 + if (e.target !== self.$control_input[0]) {
  1155 + if (self.settings.mode === 'single') {
  1156 + // toggle dropdown
  1157 + self.isOpen ? self.close() : self.open();
  1158 + } else if (!defaultPrevented) {
  1159 + self.setActiveItem(null);
  1160 + }
  1161 + return false;
  1162 + }
  1163 + } else {
  1164 + // give control focus
  1165 + if (!defaultPrevented) {
  1166 + window.setTimeout(function() {
  1167 + self.focus();
  1168 + }, 0);
  1169 + }
  1170 + }
  1171 + },
  1172 +
  1173 +
  1174 + onChange: function() {
  1175 + this.$input.trigger('change');
  1176 + },
  1177 +
  1178 +
  1179 + onPaste: function(e) {
  1180 + var self = this;
  1181 + if (self.isFull() || self.isInputHidden || self.isLocked) {
  1182 + e.preventDefault();
  1183 + } else {
  1184 + // If a regex or string is included, this will split the pasted
  1185 + // input and create Items for each separate value
  1186 + if (self.settings.splitOn) {
  1187 + setTimeout(function() {
  1188 + var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
  1189 + for (var i = 0, n = splitInput.length; i < n; i++) {
  1190 + self.createItem(splitInput[i]);
  1191 + }
  1192 + }, 0);
  1193 + }
  1194 + }
  1195 + },
  1196 +
  1197 +
  1198 + onKeyPress: function(e) {
  1199 + if (this.isLocked) return e && e.preventDefault();
  1200 + var character = String.fromCharCode(e.keyCode || e.which);
  1201 + if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
  1202 + this.createItem();
  1203 + e.preventDefault();
  1204 + return false;
  1205 + }
  1206 + },
  1207 +
  1208 +
  1209 + onKeyDown: function(e) {
  1210 + var isInput = e.target === this.$control_input[0];
  1211 + var self = this;
  1212 +
  1213 + if (self.isLocked) {
  1214 + if (e.keyCode !== KEY_TAB) {
  1215 + e.preventDefault();
  1216 + }
  1217 + return;
  1218 + }
  1219 +
  1220 + switch (e.keyCode) {
  1221 + case KEY_A:
  1222 + if (self.isCmdDown) {
  1223 + self.selectAll();
  1224 + return;
  1225 + }
  1226 + break;
  1227 + case KEY_ESC:
  1228 + if (self.isOpen) {
  1229 + e.preventDefault();
  1230 + e.stopPropagation();
  1231 + self.close();
  1232 + }
  1233 + return;
  1234 + case KEY_N:
  1235 + if (!e.ctrlKey || e.altKey) break;
  1236 + case KEY_DOWN:
  1237 + if (!self.isOpen && self.hasOptions) {
  1238 + self.open();
  1239 + } else if (self.$activeOption) {
  1240 + self.ignoreHover = true;
  1241 + var $next = self.getAdjacentOption(self.$activeOption, 1);
  1242 + if ($next.length) self.setActiveOption($next, true, true);
  1243 + }
  1244 + e.preventDefault();
  1245 + return;
  1246 + case KEY_P:
  1247 + if (!e.ctrlKey || e.altKey) break;
  1248 + case KEY_UP:
  1249 + if (self.$activeOption) {
  1250 + self.ignoreHover = true;
  1251 + var $prev = self.getAdjacentOption(self.$activeOption, -1);
  1252 + if ($prev.length) self.setActiveOption($prev, true, true);
  1253 + }
  1254 + e.preventDefault();
  1255 + return;
  1256 + case KEY_RETURN:
  1257 + if (self.isOpen && self.$activeOption) {
  1258 + self.onOptionSelect({currentTarget: self.$activeOption});
  1259 + e.preventDefault();
  1260 + }
  1261 + return;
  1262 + case KEY_LEFT:
  1263 + self.advanceSelection(-1, e);
  1264 + return;
  1265 + case KEY_RIGHT:
  1266 + self.advanceSelection(1, e);
  1267 + return;
  1268 + case KEY_TAB:
  1269 + if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
  1270 + self.onOptionSelect({currentTarget: self.$activeOption});
  1271 +
  1272 + // Default behaviour is to jump to the next field, we only want this
  1273 + // if the current field doesn't accept any more entries
  1274 + if (!self.isFull()) {
  1275 + e.preventDefault();
  1276 + }
  1277 + }
  1278 + if (self.settings.create && self.createItem()) {
  1279 + e.preventDefault();
  1280 + }
  1281 + return;
  1282 + case KEY_BACKSPACE:
  1283 + case KEY_DELETE:
  1284 + self.deleteSelection(e);
  1285 + return;
  1286 + }
  1287 +
  1288 + if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
  1289 + e.preventDefault();
  1290 + return;
  1291 + }
  1292 + },
  1293 +
  1294 +
  1295 + onKeyUp: function(e) {
  1296 + var self = this;
  1297 +
  1298 + if (self.isLocked) return e && e.preventDefault();
  1299 + var value = self.$control_input.val() || '';
  1300 + if (self.lastValue !== value) {
  1301 + self.lastValue = value;
  1302 + self.onSearchChange(value);
  1303 + self.refreshOptions();
  1304 + self.trigger('type', value);
  1305 + }
  1306 + },
  1307 +
  1308 +
  1309 + onSearchChange: function(value) {
  1310 + var self = this;
  1311 + var fn = self.settings.load;
  1312 + if (!fn) return;
  1313 + if (self.loadedSearches.hasOwnProperty(value)) return;
  1314 + self.loadedSearches[value] = true;
  1315 + self.load(function(callback) {
  1316 + fn.apply(self, [value, callback]);
  1317 + });
  1318 + },
  1319 +
  1320 +
  1321 + onFocus: function(e) {
  1322 + var self = this;
  1323 + var wasFocused = self.isFocused;
  1324 +
  1325 + if (self.isDisabled) {
  1326 + self.blur();
  1327 + e && e.preventDefault();
  1328 + return false;
  1329 + }
  1330 +
  1331 + if (self.ignoreFocus) return;
  1332 + self.isFocused = true;
  1333 + if (self.settings.preload === 'focus') self.onSearchChange('');
  1334 +
  1335 + if (!wasFocused) self.trigger('focus');
  1336 +
  1337 + if (!self.$activeItems.length) {
  1338 + self.showInput();
  1339 + self.setActiveItem(null);
  1340 + self.refreshOptions(!!self.settings.openOnFocus);
  1341 + }
  1342 +
  1343 + self.refreshState();
  1344 + },
  1345 +
  1346 +
  1347 + onBlur: function(e, dest) {
  1348 + var self = this;
  1349 + if (!self.isFocused) return;
  1350 + self.isFocused = false;
  1351 +
  1352 + if (self.ignoreFocus) {
  1353 + return;
  1354 + } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
  1355 + // necessary to prevent IE closing the dropdown when the scrollbar is clicked
  1356 + self.ignoreBlur = true;
  1357 + self.onFocus(e);
  1358 + return;
  1359 + }
  1360 +
  1361 + var deactivate = function() {
  1362 + self.close();
  1363 + self.setTextboxValue('');
  1364 + self.setActiveItem(null);
  1365 + self.setActiveOption(null);
  1366 + self.setCaret(self.items.length);
  1367 + self.refreshState();
  1368 +
  1369 + // IE11 bug: element still marked as active
  1370 + (dest || document.body).focus();
  1371 +
  1372 + self.ignoreFocus = false;
  1373 + self.trigger('blur');
  1374 + };
  1375 +
  1376 + self.ignoreFocus = true;
  1377 + if (self.settings.create && self.settings.createOnBlur) {
  1378 + self.createItem(null, false, deactivate);
  1379 + } else {
  1380 + deactivate();
  1381 + }
  1382 + },
  1383 +
  1384 +
  1385 + onOptionHover: function(e) {
  1386 + if (this.ignoreHover) return;
  1387 + this.setActiveOption(e.currentTarget, false);
  1388 + },
  1389 +
  1390 +
  1391 + onOptionSelect: function(e) {
  1392 + var value, $target, $option, self = this;
  1393 +
  1394 + if (e.preventDefault) {
  1395 + e.preventDefault();
  1396 + e.stopPropagation();
  1397 + }
  1398 +
  1399 + $target = $(e.currentTarget);
  1400 + if ($target.hasClass('create')) {
  1401 + self.createItem(null, function() {
  1402 + if (self.settings.closeAfterSelect) {
  1403 + self.close();
  1404 + }
  1405 + });
  1406 + } else {
  1407 + value = $target.attr('data-value');
  1408 + if (typeof value !== 'undefined') {
  1409 + self.lastQuery = null;
  1410 + self.setTextboxValue('');
  1411 + self.addItem(value);
  1412 + if (self.settings.closeAfterSelect) {
  1413 + self.close();
  1414 + } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
  1415 + self.setActiveOption(self.getOption(value));
  1416 + }
  1417 + }
  1418 + }
  1419 + },
  1420 +
  1421 +
  1422 + onItemSelect: function(e) {
  1423 + var self = this;
  1424 +
  1425 + if (self.isLocked) return;
  1426 + if (self.settings.mode === 'multi') {
  1427 + e.preventDefault();
  1428 + self.setActiveItem(e.currentTarget, e);
  1429 + }
  1430 + },
  1431 +
  1432 +
  1433 + load: function(fn) {
  1434 + var self = this;
  1435 + var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
  1436 +
  1437 + self.loading++;
  1438 + fn.apply(self, [function(results) {
  1439 + self.loading = Math.max(self.loading - 1, 0);
  1440 + if (results && results.length) {
  1441 + self.addOption(results);
  1442 + self.refreshOptions(self.isFocused && !self.isInputHidden);
  1443 + }
  1444 + if (!self.loading) {
  1445 + $wrapper.removeClass(self.settings.loadingClass);
  1446 + }
  1447 + self.trigger('load', results);
  1448 + }]);
  1449 + },
  1450 +
  1451 +
  1452 + setTextboxValue: function(value) {
  1453 + var $input = this.$control_input;
  1454 + var changed = $input.val() !== value;
  1455 + if (changed) {
  1456 + $input.val(value).triggerHandler('update');
  1457 + this.lastValue = value;
  1458 + }
  1459 + },
  1460 +
  1461 +
  1462 + getValue: function() {
  1463 + if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
  1464 + return this.items;
  1465 + } else {
  1466 + return this.items.join(this.settings.delimiter);
  1467 + }
  1468 + },
  1469 +
  1470 +
  1471 + setValue: function(value, silent) {
  1472 + var events = silent ? [] : ['change'];
  1473 +
  1474 + debounce_events(this, events, function() {
  1475 + this.clear(silent);
  1476 + this.addItems(value, silent);
  1477 + });
  1478 + },
  1479 +
  1480 +
  1481 + setActiveItem: function($item, e) {
  1482 + var self = this;
  1483 + var eventName;
  1484 + var i, idx, begin, end, item, swap;
  1485 + var $last;
  1486 +
  1487 + if (self.settings.mode === 'single') return;
  1488 + $item = $($item);
  1489 +
  1490 + // clear the active selection
  1491 + if (!$item.length) {
  1492 + $(self.$activeItems).removeClass('active');
  1493 + self.$activeItems = [];
  1494 + if (self.isFocused) {
  1495 + self.showInput();
  1496 + }
  1497 + return;
  1498 + }
  1499 +
  1500 + // modify selection
  1501 + eventName = e && e.type.toLowerCase();
  1502 +
  1503 + if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
  1504 + $last = self.$control.children('.active:last');
  1505 + begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
  1506 + end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
  1507 + if (begin > end) {
  1508 + swap = begin;
  1509 + begin = end;
  1510 + end = swap;
  1511 + }
  1512 + for (i = begin; i <= end; i++) {
  1513 + item = self.$control[0].childNodes[i];
  1514 + if (self.$activeItems.indexOf(item) === -1) {
  1515 + $(item).addClass('active');
  1516 + self.$activeItems.push(item);
  1517 + }
  1518 + }
  1519 + e.preventDefault();
  1520 + } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
  1521 + if ($item.hasClass('active')) {
  1522 + idx = self.$activeItems.indexOf($item[0]);
  1523 + self.$activeItems.splice(idx, 1);
  1524 + $item.removeClass('active');
  1525 + } else {
  1526 + self.$activeItems.push($item.addClass('active')[0]);
  1527 + }
  1528 + } else {
  1529 + $(self.$activeItems).removeClass('active');
  1530 + self.$activeItems = [$item.addClass('active')[0]];
  1531 + }
  1532 +
  1533 + // ensure control has focus
  1534 + self.hideInput();
  1535 + if (!this.isFocused) {
  1536 + self.focus();
  1537 + }
  1538 + },
  1539 +
  1540 +
  1541 + setActiveOption: function($option, scroll, animate) {
  1542 + var height_menu, height_item, y;
  1543 + var scroll_top, scroll_bottom;
  1544 + var self = this;
  1545 +
  1546 + if (self.$activeOption) self.$activeOption.removeClass('active');
  1547 + self.$activeOption = null;
  1548 +
  1549 + $option = $($option);
  1550 + if (!$option.length) return;
  1551 +
  1552 + self.$activeOption = $option.addClass('active');
  1553 +
  1554 + if (scroll || !isset(scroll)) {
  1555 +
  1556 + height_menu = self.$dropdown_content.height();
  1557 + height_item = self.$activeOption.outerHeight(true);
  1558 + scroll = self.$dropdown_content.scrollTop() || 0;
  1559 + y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
  1560 + scroll_top = y;
  1561 + scroll_bottom = y - height_menu + height_item;
  1562 +
  1563 + if (y + height_item > height_menu + scroll) {
  1564 + self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
  1565 + } else if (y < scroll) {
  1566 + self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
  1567 + }
  1568 +
  1569 + }
  1570 + },
  1571 +
  1572 +
  1573 + selectAll: function() {
  1574 + var self = this;
  1575 + if (self.settings.mode === 'single') return;
  1576 +
  1577 + self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
  1578 + if (self.$activeItems.length) {
  1579 + self.hideInput();
  1580 + self.close();
  1581 + }
  1582 + self.focus();
  1583 + },
  1584 +
  1585 +
  1586 + hideInput: function() {
  1587 + var self = this;
  1588 + self.setTextboxValue('');
  1589 + self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
  1590 + self.isInputHidden = true;
  1591 + },
  1592 +
  1593 + showInput: function() {
  1594 + this.$control_input.css({opacity: 1, position: 'relative', left: 0});
  1595 + this.isInputHidden = false;
  1596 + },
  1597 +
  1598 +
  1599 + focus: function() {
  1600 + var self = this;
  1601 + if (self.isDisabled) return;
  1602 +
  1603 + self.ignoreFocus = true;
  1604 + self.$control_input[0].focus();
  1605 + window.setTimeout(function() {
  1606 + self.ignoreFocus = false;
  1607 + self.onFocus();
  1608 + }, 0);
  1609 + },
  1610 +
  1611 +
  1612 + blur: function(dest) {
  1613 + this.$control_input[0].blur();
  1614 + this.onBlur(null, dest);
  1615 + },
  1616 +
  1617 +
  1618 + getScoreFunction: function(query) {
  1619 + return this.sifter.getScoreFunction(query, this.getSearchOptions());
  1620 + },
  1621 +
  1622 +
  1623 + getSearchOptions: function() {
  1624 + var settings = this.settings;
  1625 + var sort = settings.sortField;
  1626 + if (typeof sort === 'string') {
  1627 + sort = [{field: sort}];
  1628 + }
  1629 +
  1630 + return {
  1631 + fields : settings.searchField,
  1632 + conjunction : settings.searchConjunction,
  1633 + sort : sort
  1634 + };
  1635 + },
  1636 +
  1637 +
  1638 + search: function(query) {
  1639 + var i, value, score, result, calculateScore;
  1640 + var self = this;
  1641 + var settings = self.settings;
  1642 + var options = this.getSearchOptions();
  1643 +
  1644 + // validate user-provided result scoring function
  1645 + if (settings.score) {
  1646 + calculateScore = self.settings.score.apply(this, [query]);
  1647 + if (typeof calculateScore !== 'function') {
  1648 + throw new Error('Selectize "score" setting must be a function that returns a function');
  1649 + }
  1650 + }
  1651 +
  1652 + // perform search
  1653 + if (query !== self.lastQuery) {
  1654 + self.lastQuery = query;
  1655 + result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
  1656 + self.currentResults = result;
  1657 + } else {
  1658 + result = $.extend(true, {}, self.currentResults);
  1659 + }
  1660 +
  1661 + // filter out selected items
  1662 + if (settings.hideSelected) {
  1663 + for (i = result.items.length - 1; i >= 0; i--) {
  1664 + if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
  1665 + result.items.splice(i, 1);
  1666 + }
  1667 + }
  1668 + }
  1669 +
  1670 + return result;
  1671 + },
  1672 +
  1673 +
  1674 + refreshOptions: function(triggerDropdown) {
  1675 + var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
  1676 + var $active, $active_before, $create;
  1677 +
  1678 + if (typeof triggerDropdown === 'undefined') {
  1679 + triggerDropdown = true;
  1680 + }
  1681 +
  1682 + var self = this;
  1683 + var query = $.trim(self.$control_input.val());
  1684 + var results = self.search(query);
  1685 + var $dropdown_content = self.$dropdown_content;
  1686 + var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
  1687 +
  1688 + // build markup
  1689 + n = results.items.length;
  1690 + if (typeof self.settings.maxOptions === 'number') {
  1691 + n = Math.min(n, self.settings.maxOptions);
  1692 + }
  1693 +
  1694 + // render and group available options individually
  1695 + groups = {};
  1696 + groups_order = [];
  1697 +
  1698 + for (i = 0; i < n; i++) {
  1699 + option = self.options[results.items[i].id];
  1700 + option_html = self.render('option', option);
  1701 + optgroup = option[self.settings.optgroupField] || '';
  1702 + optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
  1703 +
  1704 + for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
  1705 + optgroup = optgroups[j];
  1706 + if (!self.optgroups.hasOwnProperty(optgroup)) {
  1707 + optgroup = '';
  1708 + }
  1709 + if (!groups.hasOwnProperty(optgroup)) {
  1710 + groups[optgroup] = [];
  1711 + groups_order.push(optgroup);
  1712 + }
  1713 + groups[optgroup].push(option_html);
  1714 + }
  1715 + }
  1716 +
  1717 + // sort optgroups
  1718 + if (this.settings.lockOptgroupOrder) {
  1719 + groups_order.sort(function(a, b) {
  1720 + var a_order = self.optgroups[a].$order || 0;
  1721 + var b_order = self.optgroups[b].$order || 0;
  1722 + return a_order - b_order;
  1723 + });
  1724 + }
  1725 +
  1726 + // render optgroup headers & join groups
  1727 + html = [];
  1728 + for (i = 0, n = groups_order.length; i < n; i++) {
  1729 + optgroup = groups_order[i];
  1730 + if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
  1731 + // render the optgroup header and options within it,
  1732 + // then pass it to the wrapper template
  1733 + html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
  1734 + html_children += groups[optgroup].join('');
  1735 + html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
  1736 + html: html_children
  1737 + })));
  1738 + } else {
  1739 + html.push(groups[optgroup].join(''));
  1740 + }
  1741 + }
  1742 +
  1743 + $dropdown_content.html(html.join(''));
  1744 +
  1745 + // highlight matching terms inline
  1746 + if (self.settings.highlight && results.query.length && results.tokens.length) {
  1747 + for (i = 0, n = results.tokens.length; i < n; i++) {
  1748 + highlight($dropdown_content, results.tokens[i].regex);
  1749 + }
  1750 + }
  1751 +
  1752 + // add "selected" class to selected options
  1753 + if (!self.settings.hideSelected) {
  1754 + for (i = 0, n = self.items.length; i < n; i++) {
  1755 + self.getOption(self.items[i]).addClass('selected');
  1756 + }
  1757 + }
  1758 +
  1759 + // add create option
  1760 + has_create_option = self.canCreate(query);
  1761 + if (has_create_option) {
  1762 + $dropdown_content.prepend(self.render('option_create', {input: query}));
  1763 + $create = $($dropdown_content[0].childNodes[0]);
  1764 + }
  1765 +
  1766 + // activate
  1767 + self.hasOptions = results.items.length > 0 || has_create_option;
  1768 + if (self.hasOptions) {
  1769 + if (results.items.length > 0) {
  1770 + $active_before = active_before && self.getOption(active_before);
  1771 + if ($active_before && $active_before.length) {
  1772 + $active = $active_before;
  1773 + } else if (self.settings.mode === 'single' && self.items.length) {
  1774 + $active = self.getOption(self.items[0]);
  1775 + }
  1776 + if (!$active || !$active.length) {
  1777 + if ($create && !self.settings.addPrecedence) {
  1778 + $active = self.getAdjacentOption($create, 1);
  1779 + } else {
  1780 + $active = $dropdown_content.find('[data-selectable]:first');
  1781 + }
  1782 + }
  1783 + } else {
  1784 + $active = $create;
  1785 + }
  1786 + self.setActiveOption($active);
  1787 + if (triggerDropdown && !self.isOpen) { self.open(); }
  1788 + } else {
  1789 + self.setActiveOption(null);
  1790 + if (triggerDropdown && self.isOpen) { self.close(); }
  1791 + }
  1792 + },
  1793 +
  1794 +
  1795 + addOption: function(data) {
  1796 + var i, n, value, self = this;
  1797 +
  1798 + if ($.isArray(data)) {
  1799 + for (i = 0, n = data.length; i < n; i++) {
  1800 + self.addOption(data[i]);
  1801 + }
  1802 + return;
  1803 + }
  1804 +
  1805 + if (value = self.registerOption(data)) {
  1806 + self.userOptions[value] = true;
  1807 + self.lastQuery = null;
  1808 + self.trigger('option_add', value, data);
  1809 + }
  1810 + },
  1811 +
  1812 +
  1813 + registerOption: function(data) {
  1814 + var key = hash_key(data[this.settings.valueField]);
  1815 + if (!key || this.options.hasOwnProperty(key)) return false;
  1816 + data.$order = data.$order || ++this.order;
  1817 + this.options[key] = data;
  1818 + return key;
  1819 + },
  1820 +
  1821 +
  1822 + registerOptionGroup: function(data) {
  1823 + var key = hash_key(data[this.settings.optgroupValueField]);
  1824 + if (!key) return false;
  1825 +
  1826 + data.$order = data.$order || ++this.order;
  1827 + this.optgroups[key] = data;
  1828 + return key;
  1829 + },
  1830 +
  1831 +
  1832 + addOptionGroup: function(id, data) {
  1833 + data[this.settings.optgroupValueField] = id;
  1834 + if (id = this.registerOptionGroup(data)) {
  1835 + this.trigger('optgroup_add', id, data);
  1836 + }
  1837 + },
  1838 +
  1839 + /**
  1840 + * Removes an existing option group.
  1841 + *
  1842 + * @param {string} id
  1843 + */
  1844 + removeOptionGroup: function(id) {
  1845 + if (this.optgroups.hasOwnProperty(id)) {
  1846 + delete this.optgroups[id];
  1847 + this.renderCache = {};
  1848 + this.trigger('optgroup_remove', id);
  1849 + }
  1850 + },
  1851 +
  1852 +
  1853 + clearOptionGroups: function() {
  1854 + this.optgroups = {};
  1855 + this.renderCache = {};
  1856 + this.trigger('optgroup_clear');
  1857 + },
  1858 +
  1859 +
  1860 + updateOption: function(value, data) {
  1861 + var self = this;
  1862 + var $item, $item_new;
  1863 + var value_new, index_item, cache_items, cache_options, order_old;
  1864 +
  1865 + value = hash_key(value);
  1866 + value_new = hash_key(data[self.settings.valueField]);
  1867 +
  1868 + // sanity checks
  1869 + if (value === null) return;
  1870 + if (!self.options.hasOwnProperty(value)) return;
  1871 + if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
  1872 +
  1873 + order_old = self.options[value].$order;
  1874 +
  1875 + // update references
  1876 + if (value_new !== value) {
  1877 + delete self.options[value];
  1878 + index_item = self.items.indexOf(value);
  1879 + if (index_item !== -1) {
  1880 + self.items.splice(index_item, 1, value_new);
  1881 + }
  1882 + }
  1883 + data.$order = data.$order || order_old;
  1884 + self.options[value_new] = data;
  1885 +
  1886 + // invalidate render cache
  1887 + cache_items = self.renderCache['item'];
  1888 + cache_options = self.renderCache['option'];
  1889 +
  1890 + if (cache_items) {
  1891 + delete cache_items[value];
  1892 + delete cache_items[value_new];
  1893 + }
  1894 + if (cache_options) {
  1895 + delete cache_options[value];
  1896 + delete cache_options[value_new];
  1897 + }
  1898 +
  1899 + // update the item if it's selected
  1900 + if (self.items.indexOf(value_new) !== -1) {
  1901 + $item = self.getItem(value);
  1902 + $item_new = $(self.render('item', data));
  1903 + if ($item.hasClass('active')) $item_new.addClass('active');
  1904 + $item.replaceWith($item_new);
  1905 + }
  1906 +
  1907 + // invalidate last query because we might have updated the sortField
  1908 + self.lastQuery = null;
  1909 +
  1910 + // update dropdown contents
  1911 + if (self.isOpen) {
  1912 + self.refreshOptions(false);
  1913 + }
  1914 + },
  1915 +
  1916 +
  1917 + removeOption: function(value, silent) {
  1918 + var self = this;
  1919 + value = hash_key(value);
  1920 +
  1921 + var cache_items = self.renderCache['item'];
  1922 + var cache_options = self.renderCache['option'];
  1923 + if (cache_items) delete cache_items[value];
  1924 + if (cache_options) delete cache_options[value];
  1925 +
  1926 + delete self.userOptions[value];
  1927 + delete self.options[value];
  1928 + self.lastQuery = null;
  1929 + self.trigger('option_remove', value);
  1930 + self.removeItem(value, silent);
  1931 + },
  1932 +
  1933 +
  1934 + clearOptions: function() {
  1935 + var self = this;
  1936 +
  1937 + self.loadedSearches = {};
  1938 + self.userOptions = {};
  1939 + self.renderCache = {};
  1940 + self.options = self.sifter.items = {};
  1941 + self.lastQuery = null;
  1942 + self.trigger('option_clear');
  1943 + self.clear();
  1944 + },
  1945 +
  1946 +
  1947 + getOption: function(value) {
  1948 + return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
  1949 + },
  1950 +
  1951 +
  1952 + getAdjacentOption: function($option, direction) {
  1953 + var $options = this.$dropdown.find('[data-selectable]');
  1954 + var index = $options.index($option) + direction;
  1955 +
  1956 + return index >= 0 && index < $options.length ? $options.eq(index) : $();
  1957 + },
  1958 +
  1959 +
  1960 + getElementWithValue: function(value, $els) {
  1961 + value = hash_key(value);
  1962 +
  1963 + if (typeof value !== 'undefined' && value !== null) {
  1964 + for (var i = 0, n = $els.length; i < n; i++) {
  1965 + if ($els[i].getAttribute('data-value') === value) {
  1966 + return $($els[i]);
  1967 + }
  1968 + }
  1969 + }
  1970 +
  1971 + return $();
  1972 + },
  1973 +
  1974 +
  1975 + getItem: function(value) {
  1976 + return this.getElementWithValue(value, this.$control.children());
  1977 + },
  1978 +
  1979 + addItems: function(values, silent) {
  1980 + var items = $.isArray(values) ? values : [values];
  1981 + for (var i = 0, n = items.length; i < n; i++) {
  1982 + this.isPending = (i < n - 1);
  1983 + this.addItem(items[i], silent);
  1984 + }
  1985 + },
  1986 +
  1987 +
  1988 + addItem: function(value, silent) {
  1989 + var events = silent ? [] : ['change'];
  1990 +
  1991 + debounce_events(this, events, function() {
  1992 + var $item, $option, $options;
  1993 + var self = this;
  1994 + var inputMode = self.settings.mode;
  1995 + var i, active, value_next, wasFull;
  1996 + value = hash_key(value);
  1997 +
  1998 + if (self.items.indexOf(value) !== -1) {
  1999 + if (inputMode === 'single') self.close();
  2000 + return;
  2001 + }
  2002 +
  2003 + if (!self.options.hasOwnProperty(value)) return;
  2004 + if (inputMode === 'single') self.clear(silent);
  2005 + if (inputMode === 'multi' && self.isFull()) return;
  2006 +
  2007 + $item = $(self.render('item', self.options[value]));
  2008 + wasFull = self.isFull();
  2009 + self.items.splice(self.caretPos, 0, value);
  2010 + self.insertAtCaret($item);
  2011 + if (!self.isPending || (!wasFull && self.isFull())) {
  2012 + self.refreshState();
  2013 + }
  2014 +
  2015 + if (self.isSetup) {
  2016 + $options = self.$dropdown_content.find('[data-selectable]');
  2017 +
  2018 + // update menu / remove the option (if this is not one item being added as part of series)
  2019 + if (!self.isPending) {
  2020 + $option = self.getOption(value);
  2021 + value_next = self.getAdjacentOption($option, 1).attr('data-value');
  2022 + self.refreshOptions(self.isFocused && inputMode !== 'single');
  2023 + if (value_next) {
  2024 + self.setActiveOption(self.getOption(value_next));
  2025 + }
  2026 + }
  2027 +
  2028 + // hide the menu if the maximum number of items have been selected or no options are left
  2029 + if (!$options.length || self.isFull()) {
  2030 + self.close();
  2031 + } else {
  2032 + self.positionDropdown();
  2033 + }
  2034 +
  2035 + self.updatePlaceholder();
  2036 + self.trigger('item_add', value, $item);
  2037 + self.updateOriginalInput({silent: silent});
  2038 + }
  2039 + });
  2040 + },
  2041 +
  2042 +
  2043 + removeItem: function(value, silent) {
  2044 + var self = this;
  2045 + var $item, i, idx;
  2046 +
  2047 + $item = (typeof value === 'object') ? value : self.getItem(value);
  2048 + value = hash_key($item.attr('data-value'));
  2049 + i = self.items.indexOf(value);
  2050 +
  2051 + if (i !== -1) {
  2052 + $item.remove();
  2053 + if ($item.hasClass('active')) {
  2054 + idx = self.$activeItems.indexOf($item[0]);
  2055 + self.$activeItems.splice(idx, 1);
  2056 + }
  2057 +
  2058 + self.items.splice(i, 1);
  2059 + self.lastQuery = null;
  2060 + if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
  2061 + self.removeOption(value, silent);
  2062 + }
  2063 +
  2064 + if (i < self.caretPos) {
  2065 + self.setCaret(self.caretPos - 1);
  2066 + }
  2067 +
  2068 + self.refreshState();
  2069 + self.updatePlaceholder();
  2070 + self.updateOriginalInput({silent: silent});
  2071 + self.positionDropdown();
  2072 + self.trigger('item_remove', value, $item);
  2073 + }
  2074 + },
  2075 +
  2076 +
  2077 + createItem: function(input, triggerDropdown) {
  2078 + var self = this;
  2079 + var caret = self.caretPos;
  2080 + input = input || $.trim(self.$control_input.val() || '');
  2081 +
  2082 + var callback = arguments[arguments.length - 1];
  2083 + if (typeof callback !== 'function') callback = function() {};
  2084 +
  2085 + if (typeof triggerDropdown !== 'boolean') {
  2086 + triggerDropdown = true;
  2087 + }
  2088 +
  2089 + if (!self.canCreate(input)) {
  2090 + callback();
  2091 + return false;
  2092 + }
  2093 +
  2094 + self.lock();
  2095 +
  2096 + var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
  2097 + var data = {};
  2098 + data[self.settings.labelField] = input;
  2099 + data[self.settings.valueField] = input;
  2100 + return data;
  2101 + };
  2102 +
  2103 + var create = once(function(data) {
  2104 + self.unlock();
  2105 +
  2106 + if (!data || typeof data !== 'object') return callback();
  2107 + var value = hash_key(data[self.settings.valueField]);
  2108 + if (typeof value !== 'string') return callback();
  2109 +
  2110 + self.setTextboxValue('');
  2111 + self.addOption(data);
  2112 + self.setCaret(caret);
  2113 + self.addItem(value);
  2114 + self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
  2115 + callback(data);
  2116 + });
  2117 +
  2118 + var output = setup.apply(this, [input, create]);
  2119 + if (typeof output !== 'undefined') {
  2120 + create(output);
  2121 + }
  2122 +
  2123 + return true;
  2124 + },
  2125 +
  2126 +
  2127 + refreshItems: function() {
  2128 + this.lastQuery = null;
  2129 +
  2130 + if (this.isSetup) {
  2131 + this.addItem(this.items);
  2132 + }
  2133 +
  2134 + this.refreshState();
  2135 + this.updateOriginalInput();
  2136 + },
  2137 +
  2138 +
  2139 + refreshState: function() {
  2140 + var invalid, self = this;
  2141 + if (self.isRequired) {
  2142 + if (self.items.length) self.isInvalid = false;
  2143 + self.$control_input.prop('required', invalid);
  2144 + }
  2145 + self.refreshClasses();
  2146 + },
  2147 +
  2148 +
  2149 + refreshClasses: function() {
  2150 + var self = this;
  2151 + var isFull = self.isFull();
  2152 + var isLocked = self.isLocked;
  2153 +
  2154 + self.$wrapper
  2155 + .toggleClass('rtl', self.rtl);
  2156 +
  2157 + self.$control
  2158 + .toggleClass('focus', self.isFocused)
  2159 + .toggleClass('disabled', self.isDisabled)
  2160 + .toggleClass('required', self.isRequired)
  2161 + .toggleClass('invalid', self.isInvalid)
  2162 + .toggleClass('locked', isLocked)
  2163 + .toggleClass('full', isFull).toggleClass('not-full', !isFull)
  2164 + .toggleClass('input-active', self.isFocused && !self.isInputHidden)
  2165 + .toggleClass('dropdown-active', self.isOpen)
  2166 + .toggleClass('has-options', !$.isEmptyObject(self.options))
  2167 + .toggleClass('has-items', self.items.length > 0);
  2168 +
  2169 + self.$control_input.data('grow', !isFull && !isLocked);
  2170 + },
  2171 +
  2172 +
  2173 + isFull: function() {
  2174 + return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
  2175 + },
  2176 +
  2177 +
  2178 + updateOriginalInput: function(opts) {
  2179 + var i, n, options, label, self = this;
  2180 + opts = opts || {};
  2181 +
  2182 + if (self.tagType === TAG_SELECT) {
  2183 + options = [];
  2184 + for (i = 0, n = self.items.length; i < n; i++) {
  2185 + label = self.options[self.items[i]][self.settings.labelField] || '';
  2186 + options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
  2187 + }
  2188 + if (!options.length && !this.$input.attr('multiple')) {
  2189 + options.push('<option value="" selected="selected"></option>');
  2190 + }
  2191 + self.$input.html(options.join(''));
  2192 + } else {
  2193 + self.$input.val(self.getValue());
  2194 + self.$input.attr('value',self.$input.val());
  2195 + }
  2196 +
  2197 + if (self.isSetup) {
  2198 + if (!opts.silent) {
  2199 + self.trigger('change', self.$input.val());
  2200 + }
  2201 + }
  2202 + },
  2203 +
  2204 +
  2205 + updatePlaceholder: function() {
  2206 + if (!this.settings.placeholder) return;
  2207 + var $input = this.$control_input;
  2208 +
  2209 + if (this.items.length) {
  2210 + $input.removeAttr('placeholder');
  2211 + } else {
  2212 + $input.attr('placeholder', this.settings.placeholder);
  2213 + }
  2214 + $input.triggerHandler('update', {force: true});
  2215 + },
  2216 +
  2217 +
  2218 + open: function() {
  2219 + var self = this;
  2220 +
  2221 + if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
  2222 + self.focus();
  2223 + self.isOpen = true;
  2224 + self.refreshState();
  2225 + self.$dropdown.css({visibility: 'hidden', display: 'block'});
  2226 + self.positionDropdown();
  2227 + self.$dropdown.css({visibility: 'visible'});
  2228 + self.trigger('dropdown_open', self.$dropdown);
  2229 + },
  2230 +
  2231 +
  2232 + close: function() {
  2233 + var self = this;
  2234 + var trigger = self.isOpen;
  2235 +
  2236 + if (self.settings.mode === 'single' && self.items.length) {
  2237 + self.hideInput();
  2238 + }
  2239 +
  2240 + self.isOpen = false;
  2241 + self.$dropdown.hide();
  2242 + self.setActiveOption(null);
  2243 + self.refreshState();
  2244 +
  2245 + if (trigger) self.trigger('dropdown_close', self.$dropdown);
  2246 + },
  2247 +
  2248 +
  2249 + positionDropdown: function() {
  2250 + var $control = this.$control;
  2251 + var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
  2252 + offset.top += $control.outerHeight(true);
  2253 +
  2254 + this.$dropdown.css({
  2255 + width : $control.outerWidth(),
  2256 + top : offset.top,
  2257 + left : offset.left
  2258 + });
  2259 + },
  2260 +
  2261 +
  2262 + clear: function(silent) {
  2263 + var self = this;
  2264 +
  2265 + if (!self.items.length) return;
  2266 + self.$control.children(':not(input)').remove();
  2267 + self.items = [];
  2268 + self.lastQuery = null;
  2269 + self.setCaret(0);
  2270 + self.setActiveItem(null);
  2271 + self.updatePlaceholder();
  2272 + self.updateOriginalInput({silent: silent});
  2273 + self.refreshState();
  2274 + self.showInput();
  2275 + self.trigger('clear');
  2276 + },
  2277 +
  2278 + insertAtCaret: function($el) {
  2279 + var caret = Math.min(this.caretPos, this.items.length);
  2280 + if (caret === 0) {
  2281 + this.$control.prepend($el);
  2282 + } else {
  2283 + $(this.$control[0].childNodes[caret]).before($el);
  2284 + }
  2285 + this.setCaret(caret + 1);
  2286 + },
  2287 +
  2288 + deleteSelection: function(e) {
  2289 + var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
  2290 + var self = this;
  2291 +
  2292 + direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
  2293 + selection = getSelection(self.$control_input[0]);
  2294 +
  2295 + if (self.$activeOption && !self.settings.hideSelected) {
  2296 + option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
  2297 + }
  2298 +
  2299 + // determine items that will be removed
  2300 + values = [];
  2301 +
  2302 + if (self.$activeItems.length) {
  2303 + $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
  2304 + caret = self.$control.children(':not(input)').index($tail);
  2305 + if (direction > 0) { caret++; }
  2306 +
  2307 + for (i = 0, n = self.$activeItems.length; i < n; i++) {
  2308 + values.push($(self.$activeItems[i]).attr('data-value'));
  2309 + }
  2310 + if (e) {
  2311 + e.preventDefault();
  2312 + e.stopPropagation();
  2313 + }
  2314 + } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
  2315 + if (direction < 0 && selection.start === 0 && selection.length === 0) {
  2316 + values.push(self.items[self.caretPos - 1]);
  2317 + } else if (direction > 0 && selection.start === self.$control_input.val().length) {
  2318 + values.push(self.items[self.caretPos]);
  2319 + }
  2320 + }
  2321 +
  2322 + // allow the callback to abort
  2323 + if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
  2324 + return false;
  2325 + }
  2326 +
  2327 + // perform removal
  2328 + if (typeof caret !== 'undefined') {
  2329 + self.setCaret(caret);
  2330 + }
  2331 + while (values.length) {
  2332 + self.removeItem(values.pop());
  2333 + }
  2334 +
  2335 + self.showInput();
  2336 + self.positionDropdown();
  2337 + self.refreshOptions(true);
  2338 +
  2339 + // select previous option
  2340 + if (option_select) {
  2341 + $option_select = self.getOption(option_select);
  2342 + if ($option_select.length) {
  2343 + self.setActiveOption($option_select);
  2344 + }
  2345 + }
  2346 +
  2347 + return true;
  2348 + },
  2349 +
  2350 + advanceSelection: function(direction, e) {
  2351 + var tail, selection, idx, valueLength, cursorAtEdge, $tail;
  2352 + var self = this;
  2353 +
  2354 + if (direction === 0) return;
  2355 + if (self.rtl) direction *= -1;
  2356 +
  2357 + tail = direction > 0 ? 'last' : 'first';
  2358 + selection = getSelection(self.$control_input[0]);
  2359 +
  2360 + if (self.isFocused && !self.isInputHidden) {
  2361 + valueLength = self.$control_input.val().length;
  2362 + cursorAtEdge = direction < 0
  2363 + ? selection.start === 0 && selection.length === 0
  2364 + : selection.start === valueLength;
  2365 +
  2366 + if (cursorAtEdge && !valueLength) {
  2367 + self.advanceCaret(direction, e);
  2368 + }
  2369 + } else {
  2370 + $tail = self.$control.children('.active:' + tail);
  2371 + if ($tail.length) {
  2372 + idx = self.$control.children(':not(input)').index($tail);
  2373 + self.setActiveItem(null);
  2374 + self.setCaret(direction > 0 ? idx + 1 : idx);
  2375 + }
  2376 + }
  2377 + },
  2378 +
  2379 + advanceCaret: function(direction, e) {
  2380 + var self = this, fn, $adj;
  2381 +
  2382 + if (direction === 0) return;
  2383 +
  2384 + fn = direction > 0 ? 'next' : 'prev';
  2385 + if (self.isShiftDown) {
  2386 + $adj = self.$control_input[fn]();
  2387 + if ($adj.length) {
  2388 + self.hideInput();
  2389 + self.setActiveItem($adj);
  2390 + e && e.preventDefault();
  2391 + }
  2392 + } else {
  2393 + self.setCaret(self.caretPos + direction);
  2394 + }
  2395 + },
  2396 +
  2397 + setCaret: function(i) {
  2398 + var self = this;
  2399 +
  2400 + if (self.settings.mode === 'single') {
  2401 + i = self.items.length;
  2402 + } else {
  2403 + i = Math.max(0, Math.min(self.items.length, i));
  2404 + }
  2405 +
  2406 + if(!self.isPending) {
  2407 + // the input must be moved by leaving it in place and moving the
  2408 + // siblings, due to the fact that focus cannot be restored once lost
  2409 + // on mobile webkit devices
  2410 + var j, n, fn, $children, $child;
  2411 + $children = self.$control.children(':not(input)');
  2412 + for (j = 0, n = $children.length; j < n; j++) {
  2413 + $child = $($children[j]).detach();
  2414 + if (j < i) {
  2415 + self.$control_input.before($child);
  2416 + } else {
  2417 + self.$control.append($child);
  2418 + }
  2419 + }
  2420 + }
  2421 +
  2422 + self.caretPos = i;
  2423 + },
  2424 +
  2425 + lock: function() {
  2426 + this.close();
  2427 + this.isLocked = true;
  2428 + this.refreshState();
  2429 + },
  2430 +
  2431 + unlock: function() {
  2432 + this.isLocked = false;
  2433 + this.refreshState();
  2434 + },
  2435 +
  2436 + disable: function() {
  2437 + var self = this;
  2438 + self.$input.prop('disabled', true);
  2439 + self.$control_input.prop('disabled', true).prop('tabindex', -1);
  2440 + self.isDisabled = true;
  2441 + self.lock();
  2442 + },
  2443 +
  2444 + enable: function() {
  2445 + var self = this;
  2446 + self.$input.prop('disabled', false);
  2447 + self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
  2448 + self.isDisabled = false;
  2449 + self.unlock();
  2450 + },
  2451 +
  2452 + destroy: function() {
  2453 + var self = this;
  2454 + var eventNS = self.eventNS;
  2455 + var revertSettings = self.revertSettings;
  2456 +
  2457 + self.trigger('destroy');
  2458 + self.off();
  2459 + self.$wrapper.remove();
  2460 + self.$dropdown.remove();
  2461 +
  2462 + self.$input
  2463 + .html('')
  2464 + .append(revertSettings.$children)
  2465 + .removeAttr('tabindex')
  2466 + .removeClass('selectized')
  2467 + .attr({tabindex: revertSettings.tabindex})
  2468 + .show();
  2469 +
  2470 + self.$control_input.removeData('grow');
  2471 + self.$input.removeData('selectize');
  2472 +
  2473 + $(window).off(eventNS);
  2474 + $(document).off(eventNS);
  2475 + $(document.body).off(eventNS);
  2476 +
  2477 + delete self.$input[0].selectize;
  2478 + },
  2479 +
  2480 + render: function(templateName, data) {
  2481 + var value, id, label;
  2482 + var html = '';
  2483 + var cache = false;
  2484 + var self = this;
  2485 + var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
  2486 +
  2487 + if (templateName === 'option' || templateName === 'item') {
  2488 + value = hash_key(data[self.settings.valueField]);
  2489 + cache = !!value;
  2490 + }
  2491 +
  2492 + // pull markup from cache if it exists
  2493 + if (cache) {
  2494 + if (!isset(self.renderCache[templateName])) {
  2495 + self.renderCache[templateName] = {};
  2496 + }
  2497 + if (self.renderCache[templateName].hasOwnProperty(value)) {
  2498 + return self.renderCache[templateName][value];
  2499 + }
  2500 + }
  2501 +
  2502 + // render markup
  2503 + html = self.settings.render[templateName].apply(this, [data, escape_html]);
  2504 +
  2505 + // add mandatory attributes
  2506 + if (templateName === 'option' || templateName === 'option_create') {
  2507 + html = html.replace(regex_tag, '<$1 data-selectable');
  2508 + }
  2509 + if (templateName === 'optgroup') {
  2510 + id = data[self.settings.optgroupValueField] || '';
  2511 + html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
  2512 + }
  2513 + if (templateName === 'option' || templateName === 'item') {
  2514 + html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
  2515 + }
  2516 +
  2517 + // update cache
  2518 + if (cache) {
  2519 + self.renderCache[templateName][value] = html;
  2520 + }
  2521 +
  2522 + return html;
  2523 +
  2524 +
  2525 + },
  2526 +
  2527 + clearCache: function(templateName) {
  2528 + var self = this;
  2529 + if (typeof templateName === 'undefined') {
  2530 + self.renderCache = {};
  2531 + } else {
  2532 + delete self.renderCache[templateName];
  2533 + }
  2534 + },
  2535 +
  2536 + canCreate: function(input) {
  2537 + var self = this;
  2538 + if (!self.settings.create) return false;
  2539 + var filter = self.settings.createFilter;
  2540 + return input.length
  2541 + && (typeof filter !== 'function' || filter.apply(self, [input]))
  2542 + && (typeof filter !== 'string' || new RegExp(filter).test(input))
  2543 + && (!(filter instanceof RegExp) || filter.test(input));
  2544 + }
  2545 +
  2546 + });
  2547 +
  2548 +
  2549 + Selectize.count = 0;
  2550 + Selectize.defaults = {
  2551 + options: [],
  2552 + optgroups: [],
  2553 +
  2554 + plugins: [],
  2555 + delimiter: ',',
  2556 + splitOn: null, // regexp or string for splitting up values from a paste command
  2557 + persist: true,
  2558 + diacritics: true,
  2559 + create: false,
  2560 + createOnBlur: false,
  2561 + createFilter: null,
  2562 + highlight: true,
  2563 + openOnFocus: true,
  2564 + maxOptions: 10000,
  2565 + maxItems: null,
  2566 + hideSelected: null,
  2567 + addPrecedence: false,
  2568 + selectOnTab: false,
  2569 + preload: false,
  2570 + allowEmptyOption: false,
  2571 + closeAfterSelect: false,
  2572 +
  2573 + scrollDuration: 60,
  2574 + loadThrottle: 300,
  2575 + loadingClass: 'loading',
  2576 +
  2577 + dataAttr: 'data-data',
  2578 + optgroupField: 'optgroup',
  2579 + valueField: 'value',
  2580 + labelField: 'text',
  2581 + optgroupLabelField: 'label',
  2582 + optgroupValueField: 'value',
  2583 + lockOptgroupOrder: false,
  2584 +
  2585 + sortField: '$order',
  2586 + searchField: ['text'],
  2587 + searchConjunction: 'and',
  2588 +
  2589 + mode: null,
  2590 + wrapperClass: 'selectize-control',
  2591 + inputClass: 'selectize-input',
  2592 + dropdownClass: 'selectize-dropdown',
  2593 + dropdownContentClass: 'selectize-dropdown-content',
  2594 +
  2595 + dropdownParent: null,
  2596 +
  2597 + copyClassesToDropdown: true,
  2598 +
  2599 + /*
  2600 + load : null, // function(query, callback) { ... }
  2601 + score : null, // function(search) { ... }
  2602 + onInitialize : null, // function() { ... }
  2603 + onChange : null, // function(value) { ... }
  2604 + onItemAdd : null, // function(value, $item) { ... }
  2605 + onItemRemove : null, // function(value) { ... }
  2606 + onClear : null, // function() { ... }
  2607 + onOptionAdd : null, // function(value, data) { ... }
  2608 + onOptionRemove : null, // function(value) { ... }
  2609 + onOptionClear : null, // function() { ... }
  2610 + onOptionGroupAdd : null, // function(id, data) { ... }
  2611 + onOptionGroupRemove : null, // function(id) { ... }
  2612 + onOptionGroupClear : null, // function() { ... }
  2613 + onDropdownOpen : null, // function($dropdown) { ... }
  2614 + onDropdownClose : null, // function($dropdown) { ... }
  2615 + onType : null, // function(str) { ... }
  2616 + onDelete : null, // function(values) { ... }
  2617 + */
  2618 +
  2619 + render: {
  2620 + /*
  2621 + item: null,
  2622 + optgroup: null,
  2623 + optgroup_header: null,
  2624 + option: null,
  2625 + option_create: null
  2626 + */
  2627 + }
  2628 + };
  2629 +
  2630 +
  2631 + $.fn.selectize = function(settings_user) {
  2632 + var defaults = $.fn.selectize.defaults;
  2633 + var settings = $.extend({}, defaults, settings_user);
  2634 + var attr_data = settings.dataAttr;
  2635 + var field_label = settings.labelField;
  2636 + var field_value = settings.valueField;
  2637 + var field_optgroup = settings.optgroupField;
  2638 + var field_optgroup_label = settings.optgroupLabelField;
  2639 + var field_optgroup_value = settings.optgroupValueField;
  2640 +
  2641 + /**
  2642 + * Initializes selectize from a <input type="text"> element.
  2643 + *
  2644 + * @param {object} $input
  2645 + * @param {object} settings_element
  2646 + */
  2647 + var init_textbox = function($input, settings_element) {
  2648 + var i, n, values, option;
  2649 +
  2650 + var data_raw = $input.attr(attr_data);
  2651 +
  2652 + if (!data_raw) {
  2653 + var value = $.trim($input.val() || '');
  2654 + if (!settings.allowEmptyOption && !value.length) return;
  2655 + values = value.split(settings.delimiter);
  2656 + for (i = 0, n = values.length; i < n; i++) {
  2657 + option = {};
  2658 + option[field_label] = values[i];
  2659 + option[field_value] = values[i];
  2660 + settings_element.options.push(option);
  2661 + }
  2662 + settings_element.items = values;
  2663 + } else {
  2664 + settings_element.options = JSON.parse(data_raw);
  2665 + for (i = 0, n = settings_element.options.length; i < n; i++) {
  2666 + settings_element.items.push(settings_element.options[i][field_value]);
  2667 + }
  2668 + }
  2669 + };
  2670 +
  2671 + var init_select = function($input, settings_element) {
  2672 + var i, n, tagName, $children, order = 0;
  2673 + var options = settings_element.options;
  2674 + var optionsMap = {};
  2675 +
  2676 + var readData = function($el) {
  2677 + var data = attr_data && $el.attr(attr_data);
  2678 + if (typeof data === 'string' && data.length) {
  2679 + return JSON.parse(data);
  2680 + }
  2681 + return null;
  2682 + };
  2683 +
  2684 + var addOption = function($option, group) {
  2685 + $option = $($option);
  2686 +
  2687 + var value = hash_key($option.attr('value'));
  2688 + if (!value && !settings.allowEmptyOption) return;
  2689 +
  2690 + // if the option already exists, it's probably been
  2691 + // duplicated in another optgroup. in this case, push
  2692 + // the current group to the "optgroup" property on the
  2693 + // existing option so that it's rendered in both places.
  2694 + if (optionsMap.hasOwnProperty(value)) {
  2695 + if (group) {
  2696 + var arr = optionsMap[value][field_optgroup];
  2697 + if (!arr) {
  2698 + optionsMap[value][field_optgroup] = group;
  2699 + } else if (!$.isArray(arr)) {
  2700 + optionsMap[value][field_optgroup] = [arr, group];
  2701 + } else {
  2702 + arr.push(group);
  2703 + }
  2704 + }
  2705 + return;
  2706 + }
  2707 +
  2708 + var option = readData($option) || {};
  2709 + option[field_label] = option[field_label] || $option.text();
  2710 + option[field_value] = option[field_value] || value;
  2711 + option[field_optgroup] = option[field_optgroup] || group;
  2712 +
  2713 + optionsMap[value] = option;
  2714 + options.push(option);
  2715 +
  2716 + if ($option.is(':selected')) {
  2717 + settings_element.items.push(value);
  2718 + }
  2719 + };
  2720 +
  2721 + var addGroup = function($optgroup) {
  2722 + var i, n, id, optgroup, $options;
  2723 +
  2724 + $optgroup = $($optgroup);
  2725 + id = $optgroup.attr('label');
  2726 +
  2727 + if (id) {
  2728 + optgroup = readData($optgroup) || {};
  2729 + optgroup[field_optgroup_label] = id;
  2730 + optgroup[field_optgroup_value] = id;
  2731 + settings_element.optgroups.push(optgroup);
  2732 + }
  2733 +
  2734 + $options = $('option', $optgroup);
  2735 + for (i = 0, n = $options.length; i < n; i++) {
  2736 + addOption($options[i], id);
  2737 + }
  2738 + };
  2739 +
  2740 + settings_element.maxItems = $input.attr('multiple') ? null : 1;
  2741 +
  2742 + $children = $input.children();
  2743 + for (i = 0, n = $children.length; i < n; i++) {
  2744 + tagName = $children[i].tagName.toLowerCase();
  2745 + if (tagName === 'optgroup') {
  2746 + addGroup($children[i]);
  2747 + } else if (tagName === 'option') {
  2748 + addOption($children[i]);
  2749 + }
  2750 + }
  2751 + };
  2752 +
  2753 + return this.each(function() {
  2754 + if (this.selectize) return;
  2755 +
  2756 + var instance;
  2757 + var $input = $(this);
  2758 + var tag_name = this.tagName.toLowerCase();
  2759 + var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
  2760 + if (!placeholder && !settings.allowEmptyOption) {
  2761 + placeholder = $input.children('option[value=""]').text();
  2762 + }
  2763 +
  2764 + var settings_element = {
  2765 + 'placeholder' : placeholder,
  2766 + 'options' : [],
  2767 + 'optgroups' : [],
  2768 + 'items' : []
  2769 + };
  2770 +
  2771 + if (tag_name === 'select') {
  2772 + init_select($input, settings_element);
  2773 + } else {
  2774 + init_textbox($input, settings_element);
  2775 + }
  2776 +
  2777 + instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
  2778 + });
  2779 + };
  2780 +
  2781 + $.fn.selectize.defaults = Selectize.defaults;
  2782 + $.fn.selectize.support = {
  2783 + validity: SUPPORTS_VALIDITY_API
  2784 + };
  2785 +
  2786 +
  2787 + Selectize.define('drag_drop', function(options) {
  2788 + if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
  2789 + if (this.settings.mode !== 'multi') return;
  2790 + var self = this;
  2791 +
  2792 + self.lock = (function() {
  2793 + var original = self.lock;
  2794 + return function() {
  2795 + var sortable = self.$control.data('sortable');
  2796 + if (sortable) sortable.disable();
  2797 + return original.apply(self, arguments);
  2798 + };
  2799 + })();
  2800 +
  2801 + self.unlock = (function() {
  2802 + var original = self.unlock;
  2803 + return function() {
  2804 + var sortable = self.$control.data('sortable');
  2805 + if (sortable) sortable.enable();
  2806 + return original.apply(self, arguments);
  2807 + };
  2808 + })();
  2809 +
  2810 + self.setup = (function() {
  2811 + var original = self.setup;
  2812 + return function() {
  2813 + original.apply(this, arguments);
  2814 +
  2815 + var $control = self.$control.sortable({
  2816 + items: '[data-value]',
  2817 + forcePlaceholderSize: true,
  2818 + disabled: self.isLocked,
  2819 + start: function(e, ui) {
  2820 + ui.placeholder.css('width', ui.helper.css('width'));
  2821 + $control.css({overflow: 'visible'});
  2822 + },
  2823 + stop: function() {
  2824 + $control.css({overflow: 'hidden'});
  2825 + var active = self.$activeItems ? self.$activeItems.slice() : null;
  2826 + var values = [];
  2827 + $control.children('[data-value]').each(function() {
  2828 + values.push($(this).attr('data-value'));
  2829 + });
  2830 + self.setValue(values);
  2831 + self.setActiveItem(active);
  2832 + }
  2833 + });
  2834 + };
  2835 + })();
  2836 +
  2837 + });
  2838 +
  2839 + Selectize.define('dropdown_header', function(options) {
  2840 + var self = this;
  2841 +
  2842 + options = $.extend({
  2843 + title : 'Untitled',
  2844 + headerClass : 'selectize-dropdown-header',
  2845 + titleRowClass : 'selectize-dropdown-header-title',
  2846 + labelClass : 'selectize-dropdown-header-label',
  2847 + closeClass : 'selectize-dropdown-header-close',
  2848 +
  2849 + html: function(data) {
  2850 + return (
  2851 + '<div class="' + data.headerClass + '">' +
  2852 + '<div class="' + data.titleRowClass + '">' +
  2853 + '<span class="' + data.labelClass + '">' + data.title + '</span>' +
  2854 + '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
  2855 + '</div>' +
  2856 + '</div>'
  2857 + );
  2858 + }
  2859 + }, options);
  2860 +
  2861 + self.setup = (function() {
  2862 + var original = self.setup;
  2863 + return function() {
  2864 + original.apply(self, arguments);
  2865 + self.$dropdown_header = $(options.html(options));
  2866 + self.$dropdown.prepend(self.$dropdown_header);
  2867 + };
  2868 + })();
  2869 +
  2870 + });
  2871 +
  2872 + Selectize.define('optgroup_columns', function(options) {
  2873 + var self = this;
  2874 +
  2875 + options = $.extend({
  2876 + equalizeWidth : true,
  2877 + equalizeHeight : true
  2878 + }, options);
  2879 +
  2880 + this.getAdjacentOption = function($option, direction) {
  2881 + var $options = $option.closest('[data-group]').find('[data-selectable]');
  2882 + var index = $options.index($option) + direction;
  2883 +
  2884 + return index >= 0 && index < $options.length ? $options.eq(index) : $();
  2885 + };
  2886 +
  2887 + this.onKeyDown = (function() {
  2888 + var original = self.onKeyDown;
  2889 + return function(e) {
  2890 + var index, $option, $options, $optgroup;
  2891 +
  2892 + if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
  2893 + self.ignoreHover = true;
  2894 + $optgroup = this.$activeOption.closest('[data-group]');
  2895 + index = $optgroup.find('[data-selectable]').index(this.$activeOption);
  2896 +
  2897 + if(e.keyCode === KEY_LEFT) {
  2898 + $optgroup = $optgroup.prev('[data-group]');
  2899 + } else {
  2900 + $optgroup = $optgroup.next('[data-group]');
  2901 + }
  2902 +
  2903 + $options = $optgroup.find('[data-selectable]');
  2904 + $option = $options.eq(Math.min($options.length - 1, index));
  2905 + if ($option.length) {
  2906 + this.setActiveOption($option);
  2907 + }
  2908 + return;
  2909 + }
  2910 +
  2911 + return original.apply(this, arguments);
  2912 + };
  2913 + })();
  2914 +
  2915 + var getScrollbarWidth = function() {
  2916 + var div;
  2917 + var width = getScrollbarWidth.width;
  2918 + var doc = document;
  2919 +
  2920 + if (typeof width === 'undefined') {
  2921 + div = doc.createElement('div');
  2922 + div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
  2923 + div = div.firstChild;
  2924 + doc.body.appendChild(div);
  2925 + width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
  2926 + doc.body.removeChild(div);
  2927 + }
  2928 + return width;
  2929 + };
  2930 +
  2931 + var equalizeSizes = function() {
  2932 + var i, n, height_max, width, width_last, width_parent, $optgroups;
  2933 +
  2934 + $optgroups = $('[data-group]', self.$dropdown_content);
  2935 + n = $optgroups.length;
  2936 + if (!n || !self.$dropdown_content.width()) return;
  2937 +
  2938 + if (options.equalizeHeight) {
  2939 + height_max = 0;
  2940 + for (i = 0; i < n; i++) {
  2941 + height_max = Math.max(height_max, $optgroups.eq(i).height());
  2942 + }
  2943 + $optgroups.css({height: height_max});
  2944 + }
  2945 +
  2946 + if (options.equalizeWidth) {
  2947 + width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
  2948 + width = Math.round(width_parent / n);
  2949 + $optgroups.css({width: width});
  2950 + if (n > 1) {
  2951 + width_last = width_parent - width * (n - 1);
  2952 + $optgroups.eq(n - 1).css({width: width_last});
  2953 + }
  2954 + }
  2955 + };
  2956 +
  2957 + if (options.equalizeHeight || options.equalizeWidth) {
  2958 + hook.after(this, 'positionDropdown', equalizeSizes);
  2959 + hook.after(this, 'refreshOptions', equalizeSizes);
  2960 + }
  2961 +
  2962 +
  2963 + });
  2964 +
  2965 + Selectize.define('remove_button', function(options) {
  2966 + if (this.settings.mode === 'single') return;
  2967 +
  2968 + options = $.extend({
  2969 + label : '&times;',
  2970 + title : 'Remove',
  2971 + className : 'remove',
  2972 + append : true
  2973 + }, options);
  2974 +
  2975 + var self = this;
  2976 + var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
  2977 +
  2978 + var append = function(html_container, html_element) {
  2979 + var pos = html_container.search(/(<\/[^>]+>\s*)$/);
  2980 + return html_container.substring(0, pos) + html_element + html_container.substring(pos);
  2981 + };
  2982 +
  2983 + this.setup = (function() {
  2984 + var original = self.setup;
  2985 + return function() {
  2986 + // override the item rendering method to add the button to each
  2987 + if (options.append) {
  2988 + var render_item = self.settings.render.item;
  2989 + self.settings.render.item = function(data) {
  2990 + return append(render_item.apply(this, arguments), html);
  2991 + };
  2992 + }
  2993 +
  2994 + original.apply(this, arguments);
  2995 +
  2996 + // add event listener
  2997 + this.$control.on('click', '.' + options.className, function(e) {
  2998 + e.preventDefault();
  2999 + if (self.isLocked) return;
  3000 +
  3001 + var $item = $(e.currentTarget).parent();
  3002 + self.setActiveItem($item);
  3003 + if (self.deleteSelection()) {
  3004 + self.setCaret(self.items.length);
  3005 + }
  3006 + });
  3007 +
  3008 + };
  3009 + })();
  3010 +
  3011 + });
  3012 +
  3013 + Selectize.define('restore_on_backspace', function(options) {
  3014 + var self = this;
  3015 +
  3016 + options.text = options.text || function(option) {
  3017 + return option[this.settings.labelField];
  3018 + };
  3019 +
  3020 + this.onKeyDown = (function() {
  3021 + var original = self.onKeyDown;
  3022 + return function(e) {
  3023 + var index, option;
  3024 + if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
  3025 + index = this.caretPos - 1;
  3026 + if (index >= 0 && index < this.items.length) {
  3027 + option = this.options[this.items[index]];
  3028 + if (this.deleteSelection(e)) {
  3029 + this.setTextboxValue(options.text.apply(this, [option]));
  3030 + this.refreshOptions(true);
  3031 + }
  3032 + e.preventDefault();
  3033 + return;
  3034 + }
  3035 + }
  3036 + return original.apply(this, arguments);
  3037 + };
  3038 + })();
  3039 + });
  3040 +
  3041 +
  3042 + return Selectize;
  3043 +
  3044 +
  3045 +}));
  3046 +
  3047 +$(document).ready(function() {
  3048 + var $select = $('.control-group select').selectize({
  3049 + valueField: 'AuthorId',
  3050 + labelField: 'AuthorName',
  3051 + searchField: ['AuthorName'],
  3052 + maxOptions: 10,
  3053 + minimumInputLength: 2,
  3054 + //create: function (input, callback) {
  3055 + // $.ajax({
  3056 + // url: '/site/city',
  3057 + // data: { 'AuthorName': input },
  3058 + // type: 'POST',
  3059 + // dataType: 'json',
  3060 + // success: function (response) {
  3061 + // return callback(response);
  3062 + // }
  3063 + // });
  3064 + //},
  3065 + render: {
  3066 + option: function (item, escape) {
  3067 + return '<div>' + escape(item.AuthorName) + '</div>';
  3068 + }
  3069 + },
  3070 + load: function (query, callback) {
  3071 + if (!query.length) return callback();
  3072 + $.ajax({
  3073 + url: '/site/city/' + query,
  3074 + type: 'POST',
  3075 + dataType: 'json',
  3076 + data: {
  3077 + maxresults: 10
  3078 + },
  3079 + error: function () {
  3080 + callback();
  3081 + },
  3082 + success: function (res) {
  3083 + callback(res);
  3084 + }
  3085 + });
  3086 + }
  3087 +
  3088 + });
  3089 +
  3090 + $("#btnClear").on("click", function () {
  3091 + var selectize = $select[0].selectize;
  3092 + selectize.clear();
  3093 + $(this).css({display:'none'})
  3094 + $('.selectize-control input').focus()
  3095 +
  3096 + });
  3097 +
  3098 +})
  3099 +
... ...