Strong Testimonials - Version 2.51.0

Version Description

Download this release

Release Info

Developer giucu91
Plugin Icon 128x128 Strong Testimonials
Version 2.51.0
Comparing to
See all releases

Code changes from version 2.50.4 to 2.51.0

Files changed (42) hide show
  1. admin/about/class-strong-testimonials-welcome.php +24 -53
  2. admin/class-strong-testimonials-addons.php +1 -1
  3. admin/class-strong-testimonials-defaults.php +12 -2
  4. admin/class-strong-testimonials-post-editor.php +2 -1
  5. admin/class-strong-testimonials-review.php +1 -1
  6. admin/class-strong-testimonials-upsell.php +20 -20
  7. admin/custom-fields.php +3 -5
  8. admin/js/admin-compat.js +6 -6
  9. admin/js/admin-fields.js +21 -21
  10. admin/js/admin-form.js +9 -9
  11. admin/js/admin-order.js +1 -1
  12. admin/js/custom-spinner.js +2 -2
  13. admin/js/help.js +2 -2
  14. admin/js/lib/autosize/autosize.min.js +2 -2
  15. admin/js/rating-edit.js +2 -2
  16. admin/js/selectize.js +3852 -3806
  17. admin/js/view-category-filter.js +1 -1
  18. admin/js/views.js +30 -26
  19. admin/partials/views/option-pagination.php +6 -6
  20. admin/views.php +4 -3
  21. assets/css/admin-global.css +4 -0
  22. assets/css/admin-welcome.css +1 -3
  23. assets/css/admin.css +26 -0
  24. assets/js/admin-js.js +5 -5
  25. assets/js/blocks-js.js +11 -12
  26. assets/src/js/admin.js +9 -9
  27. assets/src/js/components/edit.js +1 -1
  28. assets/src/js/components/inspector.js +1 -1
  29. changelog.txt +10 -0
  30. includes/class-strong-view-display.php +13 -2
  31. includes/functions-content.php +21 -23
  32. public/js/controller.js +1 -1
  33. public/js/lib/actual/jquery-actual.js +2 -2
  34. public/js/lib/actual/jquery-actual.min.js +14 -1
  35. public/js/lib/actual/jquery.actual.min.js +0 -14
  36. public/js/lib/form-validation/form-validation.js +3 -3
  37. public/js/lib/strongpager/jquery-strongpager.js +1 -1
  38. public/js/lib/strongslider/jquery-strongslider.js +11 -3
  39. public/js/lib/validate/jquery-validate.js +162 -79
  40. public/js/lib/validate/jquery-validate.min.js +4 -1
  41. readme.txt +2 -2
  42. strong-testimonials.php +2 -2
admin/about/class-strong-testimonials-welcome.php CHANGED
@@ -78,8 +78,8 @@ class Strong_Testimonials_Welcome {
78
  </a>
79
  </div>
80
  <div class="right">
81
- <a href="https://strongtestimonials.com/docs"
82
- class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg wpmtst-btn-orange" target="_blank">
83
  <?php esc_html_e( 'Read the Docs', 'strong-testimonials' ); ?>
84
  </a>
85
  </div>
@@ -101,85 +101,85 @@ class Strong_Testimonials_Welcome {
101
  <div class="feature-block first">
102
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/1.svg">
103
  <h5><?php esc_html_e( 'Pro Templates', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
104
- <p><?php esc_html_e( 'Create beautiful testimonial designs with a number of predesigned and easy-to-use premium templates.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/pro-templates"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
105
  </div>
106
 
107
  <div class="feature-block last">
108
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/infinitescroll.svg">
109
  <h5><?php esc_html_e( 'Infinite Scroll', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
110
- <p><?php esc_html_e( 'Change properties of the testimonial post type: labels, permalink structure, admin options and post editor features.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/infinite-scroll/"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
111
  </div>
112
 
113
  <div class="feature-block first">
114
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/12.svg">
115
  <h5><?php esc_html_e( 'Testimonial Assignment', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
116
- <p><?php esc_html_e( 'Assign testimonials to custom post types for easy management and filtering.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/assignment"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
117
  </div>
118
 
119
  <div class="feature-block last">
120
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/11.svg">
121
  <h5><?php esc_html_e( 'Custom Properties', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
122
- <p><?php esc_html_e( 'Change properties of the testimonial post type: labels, permalink structure, admin options and post editor features.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/properties"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
123
  </div>
124
 
125
  <div class="feature-block first">
126
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/6.svg">
127
  <h5><?php esc_html_e( 'Advanced View Settings', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
128
- <p><?php esc_html_e( 'Customize your testimonials beyond star ratings, reorder fields and more.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/advanced-views"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
129
  </div>
130
 
131
  <div class="feature-block last">
132
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/9.svg">
133
  <h5><?php esc_html_e( 'Multiple Submission Forms', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
134
- <p><?php esc_html_e( 'Easily collect testimonials from customers by creating and customizing multiple forms at once.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/multiple-forms"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
135
  </div>
136
 
137
  <div class="feature-block first">
138
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/10.svg">
139
  <h5><?php esc_html_e( 'Custom Form Fields', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
140
- <p><?php esc_html_e( 'Enhance your submission forms to both collect and display additional information.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/custom-fields"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
141
  </div>
142
 
143
  <div class="feature-block last">
144
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/14.svg">
145
  <h5><?php esc_html_e( 'SEO-friendly Testimonials', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
146
- <p><?php esc_html_e( 'Take full advantage of your testimonials with our Schema.org Markup extension.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/review-markup"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
147
  </div>
148
 
149
  <div class="feature-block first">
150
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/13.svg">
151
  <h5><?php esc_html_e( 'Spam Control', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
152
- <p><?php esc_html_e( 'Protect your testimonial submission forms from spam and other types of automated abuse.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/captcha"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
153
  </div>
154
 
155
  <div class="feature-block last">
156
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/15.svg">
157
  <h5><?php esc_html_e( 'Testimonial Importer', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
158
- <p><?php esc_html_e( 'Import reviews from 3rd party sites like: Facebook, Google, Yelp, Zomato & WooCommerce', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/importer"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
159
  </div>
160
 
161
  <div class="feature-block first">
162
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/email.svg">
163
  <h5><?php esc_html_e( 'Enhanced Emails', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
164
- <p><?php esc_html_e( 'We added the option to send an email to the customer upon new testimonial submission. Also, to send an email to the customer when the testimonial is approved.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/enhanced-emails/"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
165
  </div>
166
 
167
  <div class="feature-block last">
168
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/filter.svg">
169
  <h5><?php esc_html_e( 'Filters', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
170
- <p><?php esc_html_e( 'Now you can use categories to group your testimonials and have your clients read reviews grouped by service/product type.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/filters/"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
171
  </div>
172
 
173
  <div class="feature-block first">
174
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/rolemanagement.svg">
175
  <h5><?php esc_html_e( 'Role Management', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
176
- <p><?php esc_html_e( 'We’re giving power back to the users and admins can decide which user roles are worthy of adding, editing, or removing testimonials.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/role-management/"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
177
  </div>
178
 
179
  <div class="feature-block last">
180
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/mailchimp.svg">
181
  <h5><?php esc_html_e( 'Mailchimp integration', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
182
- <p><?php esc_html_e( 'Now you can subscribe your customers to a Mailchimp list.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/importer"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
183
  </div>
184
 
185
  </div><!-- feature-list -->
@@ -188,51 +188,22 @@ class Strong_Testimonials_Welcome {
188
 
189
  </div><!-- features -->
190
 
 
191
  <div class="upgrade">
192
-
193
- <div class="block clear">
194
-
195
- <h1><?php esc_html_e( 'Upgrade to PRO', 'strong-testimonials' ); ?></h1>
196
-
197
- <div class="left">
198
- <ul>
199
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( '1 Year of Free Updates', 'strong-testimonials' ); ?></li>
200
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Pro Templates', 'strong-testimonials' ); ?></li>
201
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Testimonial Assignment', 'strong-testimonials' ); ?></li>
202
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Testimonial Importer', 'strong-testimonials' ); ?></li>
203
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'SEO-friendly Testimonials', 'strong-testimonials' ); ?></li>
204
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Country Selector', 'strong-testimonials' ); ?></li>
205
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Infinite Scroll', 'strong-testimonials' ); ?></li>
206
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Filters', 'strong-testimonials' ); ?></li>
207
- </ul>
208
- </div>
209
-
210
- <div class="right">
211
- <ul>
212
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( '1 Year of Support', 'strong-testimonials' ); ?></li>
213
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Spam Control', 'strong-testimonials' ); ?></li>
214
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Advanced View Settings', 'strong-testimonials' ); ?></li>
215
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Multiple Submission Forms', 'strong-testimonials' ); ?></li>
216
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Custom Form Fields', 'strong-testimonials' ); ?></li>
217
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Custom properties', 'strong-testimonials' ); ?></li>
218
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Enhanced Emails', 'strong-testimonials' ); ?></li>
219
- <li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Role Management', 'strong-testimonials' ); ?></li>
220
- </ul>
221
- </div>
222
-
223
- <a href="https://strongtestimonials.com/pricing/" target="_blank"
224
- class="wpmtst-btn wpmtst-btn-lg wpmtst-btn-white">
225
  <?php esc_html_e( 'Upgrade Now', 'strong-testimonials' ); ?>
226
  </a>
227
 
228
  </div>
 
229
 
230
- </div><!-- upgrade -->
231
 
232
  <div class="testimonials">
233
 
234
  <div class="block clear">
235
-
236
  <div class="testimonial-block left">
237
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/testimonial-image-1.jpg">
238
  <p><?php esc_html_e( 'Strong Testimonials is my new, go-to resource for creating pages with multiple staff bios or testimonials. It’s extremely easy to use, update, and customize, and that makes it an invaluable asset. Highly recommend!', 'strong-testimonials' ); ?>
@@ -258,14 +229,14 @@ class Strong_Testimonials_Welcome {
258
  <div class="button-wrap clear">
259
  <div class="left">
260
  <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=wpm-testimonial' ) ); ?>"
261
- class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg wpmtst-btn-orange">
262
  <?php esc_html_e( 'Start Adding Testimonials', 'strong-testimonials' ); ?>
263
  </a>
264
  </div>
265
  <div class="right">
266
  <a href="https://strongtestimonials.com/pricing/" target="_blank"
267
  class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg wpmtst-btn-purple">
268
- <?php esc_html_e( 'Upgrade to Pro', 'strong-testimonials' ); ?>
269
  </a>
270
  </div>
271
  </div>
78
  </a>
79
  </div>
80
  <div class="right">
81
+ <a href="https://strongtestimonials.com/docs?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"
82
+ class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg" target="_blank">
83
  <?php esc_html_e( 'Read the Docs', 'strong-testimonials' ); ?>
84
  </a>
85
  </div>
101
  <div class="feature-block first">
102
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/1.svg">
103
  <h5><?php esc_html_e( 'Pro Templates', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
104
+ <p><?php esc_html_e( 'Create beautiful testimonial designs with a number of predesigned and easy-to-use premium templates.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/pro-templates?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
105
  </div>
106
 
107
  <div class="feature-block last">
108
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/infinitescroll.svg">
109
  <h5><?php esc_html_e( 'Infinite Scroll', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
110
+ <p><?php esc_html_e( 'Change properties of the testimonial post type: labels, permalink structure, admin options and post editor features.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/infinite-scroll/?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
111
  </div>
112
 
113
  <div class="feature-block first">
114
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/12.svg">
115
  <h5><?php esc_html_e( 'Testimonial Assignment', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
116
+ <p><?php esc_html_e( 'Assign testimonials to custom post types for easy management and filtering.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/assignment?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
117
  </div>
118
 
119
  <div class="feature-block last">
120
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/11.svg">
121
  <h5><?php esc_html_e( 'Custom Properties', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
122
+ <p><?php esc_html_e( 'Change properties of the testimonial post type: labels, permalink structure, admin options and post editor features.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/properties?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
123
  </div>
124
 
125
  <div class="feature-block first">
126
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/6.svg">
127
  <h5><?php esc_html_e( 'Advanced View Settings', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
128
+ <p><?php esc_html_e( 'Customize your testimonials beyond star ratings, reorder fields and more.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/advanced-views?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
129
  </div>
130
 
131
  <div class="feature-block last">
132
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/9.svg">
133
  <h5><?php esc_html_e( 'Multiple Submission Forms', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
134
+ <p><?php esc_html_e( 'Easily collect testimonials from customers by creating and customizing multiple forms at once.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/multiple-forms?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
135
  </div>
136
 
137
  <div class="feature-block first">
138
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/10.svg">
139
  <h5><?php esc_html_e( 'Custom Form Fields', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
140
+ <p><?php esc_html_e( 'Enhance your submission forms to both collect and display additional information.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/custom-fields?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
141
  </div>
142
 
143
  <div class="feature-block last">
144
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/14.svg">
145
  <h5><?php esc_html_e( 'SEO-friendly Testimonials', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
146
+ <p><?php esc_html_e( 'Take full advantage of your testimonials with our Schema.org Markup extension.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/review-markup?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
147
  </div>
148
 
149
  <div class="feature-block first">
150
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/13.svg">
151
  <h5><?php esc_html_e( 'Spam Control', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
152
+ <p><?php esc_html_e( 'Protect your testimonial submission forms from spam and other types of automated abuse.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/captcha?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
153
  </div>
154
 
155
  <div class="feature-block last">
156
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/15.svg">
157
  <h5><?php esc_html_e( 'Testimonial Importer', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
158
+ <p><?php esc_html_e( 'Import reviews from 3rd party sites like: Facebook, Google, Yelp, Zomato & WooCommerce', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/importer?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
159
  </div>
160
 
161
  <div class="feature-block first">
162
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/email.svg">
163
  <h5><?php esc_html_e( 'Enhanced Emails', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
164
+ <p><?php esc_html_e( 'We added the option to send an email to the customer upon new testimonial submission. Also, to send an email to the customer when the testimonial is approved.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/enhanced-emails/?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
165
  </div>
166
 
167
  <div class="feature-block last">
168
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/filter.svg">
169
  <h5><?php esc_html_e( 'Filters', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
170
+ <p><?php esc_html_e( 'Now you can use categories to group your testimonials and have your clients read reviews grouped by service/product type.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/filters/?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
171
  </div>
172
 
173
  <div class="feature-block first">
174
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/rolemanagement.svg">
175
  <h5><?php esc_html_e( 'Role Management', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
176
+ <p><?php esc_html_e( 'We’re giving power back to the users and admins can decide which user roles are worthy of adding, editing, or removing testimonials.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/role-management/?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
177
  </div>
178
 
179
  <div class="feature-block last">
180
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/features/mailchimp.svg">
181
  <h5><?php esc_html_e( 'Mailchimp integration', 'strong-testimonials' ); ?><div class="pro-label">PRO</div></h5>
182
+ <p><?php esc_html_e( 'Now you can subscribe your customers to a Mailchimp list.', 'strong-testimonials' ); ?><br/><a target="_blank" href="https://strongtestimonials.com/extensions/importer?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner"><?php esc_html_e( 'Learn More', 'strong-testimonials' ); ?></a></p>
183
  </div>
184
 
185
  </div><!-- feature-list -->
188
 
189
  </div><!-- features -->
190
 
191
+
192
  <div class="upgrade">
193
+ <div class="block">
194
+ <a href="https://strongtestimonials.com/pricing/?utm_source=welcome_banner&utm_medium=backend&utm_campaign=welcome_banner" target="_blank"
195
+ class="wpmtst-btn wpmtst-btn-lg wpmtst-btn-orange">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  <?php esc_html_e( 'Upgrade Now', 'strong-testimonials' ); ?>
197
  </a>
198
 
199
  </div>
200
+ </div><!--/.upgrade-->
201
 
 
202
 
203
  <div class="testimonials">
204
 
205
  <div class="block clear">
206
+ <h1>Happy users of the Strong Testimonials - premium version</h1>
207
  <div class="testimonial-block left">
208
  <img src="<?php echo esc_attr( WPMTST_ADMIN_URL ); ?>/img/testimonial-image-1.jpg">
209
  <p><?php esc_html_e( 'Strong Testimonials is my new, go-to resource for creating pages with multiple staff bios or testimonials. It’s extremely easy to use, update, and customize, and that makes it an invaluable asset. Highly recommend!', 'strong-testimonials' ); ?>
229
  <div class="button-wrap clear">
230
  <div class="left">
231
  <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=wpm-testimonial' ) ); ?>"
232
+ class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg wpmtst-btn-purple">
233
  <?php esc_html_e( 'Start Adding Testimonials', 'strong-testimonials' ); ?>
234
  </a>
235
  </div>
236
  <div class="right">
237
  <a href="https://strongtestimonials.com/pricing/" target="_blank"
238
  class="wpmtst-btn wpmtst-btn-block wpmtst-btn-lg wpmtst-btn-purple">
239
+ <?php esc_html_e( 'Upgrade now', 'strong-testimonials' ); ?>
240
  </a>
241
  </div>
242
  </div>
admin/class-strong-testimonials-addons.php CHANGED
@@ -60,7 +60,7 @@ class Strong_Testimonials_Addons {
60
  echo '</div>';
61
  echo '</div>';
62
  echo '<div class="wpmtst-addon-actions">';
63
- echo apply_filters( 'wpmtst_addon_button_action', '<a href="' . esc_url( WPMTST_STORE_UPGRADE_URL . '?utm_source=st-lite&utm_campaign=upsell&utm_medium=' . esc_attr( $addon['slug'] ) ) . '" target="_blank" class="button primary-button">' . esc_html__( 'Upgrade to PRO', 'strong-testimonials' ) . '</a>', $addon );
64
  echo '</div>';
65
  echo '</div>';
66
  }
60
  echo '</div>';
61
  echo '</div>';
62
  echo '<div class="wpmtst-addon-actions">';
63
+ echo apply_filters( 'wpmtst_addon_button_action', '<a href="' . esc_url( WPMTST_STORE_UPGRADE_URL . '?utm_source=st-lite&utm_campaign=upsell&utm_medium=' . esc_attr( $addon['slug'] ) ) . '" target="_blank" class="button primary-button">' . esc_html__( 'Upgrade now', 'strong-testimonials' ) . '</a>', $addon );
64
  echo '</div>';
65
  echo '</div>';
66
  }
admin/class-strong-testimonials-defaults.php CHANGED
@@ -310,6 +310,14 @@ class Strong_Testimonials_Defaults {
310
  'after' => __( 'Would you like to include a photo?', 'strong-testimonials' ),
311
  'admin_table' => 1,
312
  ),
 
 
 
 
 
 
 
 
313
  ),
314
  ),
315
  );
@@ -357,9 +365,11 @@ class Strong_Testimonials_Defaults {
357
  foreach ( $form['fields'] as $key => $array ) {
358
  if ( 'post' == $array['record_type'] ) {
359
  $forms[ $form_name ]['fields'][ $key ] = array_merge( $default_fields['field_types']['post'][ $array['name'] ], $array );
360
- } else {
361
  $forms[ $form_name ]['fields'][ $key ] = array_merge( $default_fields['field_types']['custom'][ $array['input_type'] ], $array );
362
- }
 
 
363
  }
364
  }
365
 
310
  'after' => __( 'Would you like to include a photo?', 'strong-testimonials' ),
311
  'admin_table' => 1,
312
  ),
313
+ 8 => array(
314
+ 'record_type' => 'optional',
315
+ 'name' => 'star_rating',
316
+ 'label' => __( 'Star rating', 'strong-testimonials' ),
317
+ 'input_type' => 'rating',
318
+ 'required' => 0,
319
+ 'after' => __( 'Would you like to include star rating?', 'strong-testimonials' )
320
+ ),
321
  ),
322
  ),
323
  );
365
  foreach ( $form['fields'] as $key => $array ) {
366
  if ( 'post' == $array['record_type'] ) {
367
  $forms[ $form_name ]['fields'][ $key ] = array_merge( $default_fields['field_types']['post'][ $array['name'] ], $array );
368
+ } elseif ( 'custom' == $array['record_type']) {
369
  $forms[ $form_name ]['fields'][ $key ] = array_merge( $default_fields['field_types']['custom'][ $array['input_type'] ], $array );
370
+ } else {
371
+ $forms[ $form_name ]['fields'][ $key ] = array_merge( $default_fields['field_types']['optional'][ $array['input_type'] ], $array );
372
+ }
373
  }
374
  }
375
 
admin/class-strong-testimonials-post-editor.php CHANGED
@@ -52,6 +52,7 @@ class Strong_Testimonials_Post_Editor {
52
  $post = wpmtst_get_post( $post );
53
  $fields = wpmtst_get_custom_fields();
54
  $is_new = ( 'post-new.php' == $pagenow );
 
55
  ?>
56
  <?php do_action( 'wpmtst_before_client_fields_table' ); ?>
57
  <table class="options">
@@ -294,7 +295,7 @@ class Strong_Testimonials_Post_Editor {
294
  * @since 2.23.2 Delete meta record when rating is zero to allow default display value.
295
  */
296
  public static function save_details() {
297
- if ( ! isset( $_POST['custom'] ) ) {
298
  return;
299
  }
300
 
52
  $post = wpmtst_get_post( $post );
53
  $fields = wpmtst_get_custom_fields();
54
  $is_new = ( 'post-new.php' == $pagenow );
55
+ wp_nonce_field ( plugin_basename(__FILE__), 'wpmtst_metabox_nonce');
56
  ?>
57
  <?php do_action( 'wpmtst_before_client_fields_table' ); ?>
58
  <table class="options">
295
  * @since 2.23.2 Delete meta record when rating is zero to allow default display value.
296
  */
297
  public static function save_details() {
298
+ if ( ! isset( $_POST['custom'] ) || !wp_verify_nonce( $_POST['wpmtst_metabox_nonce'], plugin_basename(__FILE__))) {
299
  return;
300
  }
301
 
admin/class-strong-testimonials-review.php CHANGED
@@ -118,7 +118,7 @@ class Strong_Review {
118
  <script type="text/javascript">
119
  jQuery( document ).ready( function( $ ){
120
 
121
- $( '.strong-testimonials-review-button' ).click( function( evt ){
122
  var href = $(this).attr('href'),
123
  id = $(this).attr('id');
124
 
118
  <script type="text/javascript">
119
  jQuery( document ).ready( function( $ ){
120
 
121
+ $( '.strong-testimonials-review-button' ).on('click', function( evt ){
122
  var href = $(this).attr('href'),
123
  id = $(this).attr('id');
124
 
admin/class-strong-testimonials-upsell.php CHANGED
@@ -155,7 +155,7 @@ class Strong_Testimonials_Upsell {
155
 
156
  <div class="wpmtst-settings-upsell">
157
  <div class="wpmtst-alert">
158
- <h3>Upgrade to PRO</h3>
159
  <ul>
160
  <?php foreach ( $general_upsells as $general_upsell ) { ?>
161
  <li>
@@ -167,7 +167,7 @@ class Strong_Testimonials_Upsell {
167
  <?php } ?>
168
  </ul>
169
 
170
- <a href="<?php echo esc_url( WPMTST_STORE_URL . '/pricing?utm_source=st-lite&utm_campaign=upsell&utm_medium=general-settings-upsell' ) ?>" target="_blank" class="button button-primary button-hero" style="width:100%;display:block;margin-top:20px;text-align:center;">Upgrade to PRO</a>
171
 
172
  </div>
173
  </div>
@@ -374,7 +374,7 @@ class Strong_Testimonials_Upsell {
374
  );
375
  ?>
376
  <ul>
377
- <li><?php esc_html_e( 'With this extensions, search engines will display star ratings in search results for your site.', 'strong-testimonials' ); ?></li>
378
  </ul>
379
  <p>
380
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=views-review-markup-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
@@ -415,9 +415,9 @@ class Strong_Testimonials_Upsell {
415
 
416
  ?>
417
  <ul>
418
- <li><?php esc_html_e( 'filter & display testimonials based on their rating or on a pre-defined condition.', 'strong-testimonials' ); ?></li>
419
- <li><?php esc_html_e( 'easily define the display order of your testimonial fields. Re-order the name, image, url and testimonial content fields through drag & drop.', 'strong-testimonials' ); ?></li>
420
- <li><?php esc_html_e( 'edit, in real time, the way your testimonials will look on your site. Stop loosing clients because of poor design.', 'strong-testimonials' ); ?></li>
421
 
422
  </ul>
423
  <p>
@@ -583,9 +583,9 @@ class Strong_Testimonials_Upsell {
583
  );
584
  ?>
585
  <ul>
586
- <li><?php esc_html_e( 'send a thank you email to your client once his testimonial\'s approved', 'strong-testimonials' ); ?></li>
587
- <li><?php esc_html_e( 'increase brand loyalty by showing you really care about your clients', 'strong-testimonials' ); ?></li>
588
- <li><?php esc_html_e( 'keep your clients engaged and increase your chances of selling more', 'strong-testimonials' ); ?></li>
589
  </ul>
590
  <p>
591
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=enhanced-emails-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
@@ -626,8 +626,8 @@ class Strong_Testimonials_Upsell {
626
  );
627
  ?>
628
  <ul>
629
- <li><?php esc_html_e( 'display a fixed number of testimonials on first view and have more of them load when the user starts scrolling', 'strong-testimonials' ); ?></li>
630
- <li><?php esc_html_e( 'reduce your page\'s initial load time, making your site faster in the process and not driving clients away because of a slow loading website', 'strong-testimonials' ); ?></li>
631
  </ul>
632
  <p>
633
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=infinite-scroll-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
@@ -668,9 +668,9 @@ class Strong_Testimonials_Upsell {
668
  );
669
  ?>
670
  <ul>
671
- <li><?php esc_html_e( 'create category-like filters for your testimonials', 'strong-testimonials' ); ?></li>
672
- <li><?php esc_html_e( 'group testimonials by associated product or service', 'strong-testimonials' ); ?></li>
673
- <li><?php esc_html_e( 'help potential clients appreciate the great work you do by showcasing reviews from other clients', 'strong-testimonials' ); ?></li>
674
  </ul>
675
  <p>
676
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=filters-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
@@ -761,12 +761,12 @@ class Strong_Testimonials_Upsell {
761
  );
762
  ?>
763
  <ul>
764
- <li><?php esc_html_e( 'Display a default image when no image has been provided for the testimonial;', 'strong-testimonials' ); ?></li>
765
- <li><?php esc_html_e( 'Use author initials as the testimonial image;', 'strong-testimonials' ); ?></li>
766
- <li><?php esc_html_e( 'Choose the HTML tag you’d like to use for your testimonial titles;', 'strong-testimonials' ); ?></li>
767
- <li><?php esc_html_e( 'Choose the Single Testimonial Template settings;', 'strong-testimonials' ); ?></li>
768
- <li><?php esc_html_e( 'Prefill testimonial forms from $_GET parameters;', 'strong-testimonials' ); ?></li>
769
- <li><?php esc_html_e( 'Show testimonial form only for logged-in users.', 'strong-testimonials' ); ?></li>
770
  </ul>
771
  <p>
772
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=setting-tab-st-pro-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
155
 
156
  <div class="wpmtst-settings-upsell">
157
  <div class="wpmtst-alert">
158
+ <h3>Upgrade now</h3>
159
  <ul>
160
  <?php foreach ( $general_upsells as $general_upsell ) { ?>
161
  <li>
167
  <?php } ?>
168
  </ul>
169
 
170
+ <a href="<?php echo esc_url( WPMTST_STORE_URL . '/pricing?utm_source=st-lite&utm_campaign=upsell&utm_medium=general-settings-upsell' ) ?>" target="_blank" class="button button-primary button-hero" style="width:100%;display:block;margin-top:20px;text-align:center;">Upgrade now</a>
171
 
172
  </div>
173
  </div>
374
  );
375
  ?>
376
  <ul>
377
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'With this extensions, search engines will display star ratings in search results for your site.', 'strong-testimonials' ); ?></li>
378
  </ul>
379
  <p>
380
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=views-review-markup-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
415
 
416
  ?>
417
  <ul>
418
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'filter & display testimonials based on their rating or on a pre-defined condition.', 'strong-testimonials' ); ?></li>
419
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'easily define the display order of your testimonial fields. Re-order the name, image, url and testimonial content fields through drag & drop.', 'strong-testimonials' ); ?></li>
420
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'edit, in real time, the way your testimonials will look on your site. Stop loosing clients because of poor design.', 'strong-testimonials' ); ?></li>
421
 
422
  </ul>
423
  <p>
583
  );
584
  ?>
585
  <ul>
586
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'send a thank you email to your client once his testimonial\'s approved', 'strong-testimonials' ); ?></li>
587
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'increase brand loyalty by showing you really care about your clients', 'strong-testimonials' ); ?></li>
588
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'keep your clients engaged and increase your chances of selling more', 'strong-testimonials' ); ?></li>
589
  </ul>
590
  <p>
591
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=enhanced-emails-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
626
  );
627
  ?>
628
  <ul>
629
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'display a fixed number of testimonials on first view and have more of them load when the user starts scrolling', 'strong-testimonials' ); ?></li>
630
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'reduce your page\'s initial load time, making your site faster in the process and not driving clients away because of a slow loading website', 'strong-testimonials' ); ?></li>
631
  </ul>
632
  <p>
633
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=infinite-scroll-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
668
  );
669
  ?>
670
  <ul>
671
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'create category-like filters for your testimonials', 'strong-testimonials' ); ?></li>
672
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'group testimonials by associated product or service', 'strong-testimonials' ); ?></li>
673
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'help potential clients appreciate the great work you do by showcasing reviews from other clients', 'strong-testimonials' ); ?></li>
674
  </ul>
675
  <p>
676
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=filters-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
761
  );
762
  ?>
763
  <ul>
764
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Display a default image when no image has been provided for the testimonial;', 'strong-testimonials' ); ?></li>
765
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Use author initials as the testimonial image;', 'strong-testimonials' ); ?></li>
766
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Choose the HTML tag you’d like to use for your testimonial titles;', 'strong-testimonials' ); ?></li>
767
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Choose the Single Testimonial Template settings;', 'strong-testimonials' ); ?></li>
768
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Prefill testimonial forms from $_GET parameters;', 'strong-testimonials' ); ?></li>
769
+ <li class="wpmtst-upsell-checkmark"><?php esc_html_e( 'Show testimonial form only for logged-in users.', 'strong-testimonials' ); ?></li>
770
  </ul>
771
  <p>
772
  <a class="button button-primary" target="_blank" href="<?php echo esc_url( $this->store_upgrade_url . '&utm_medium=setting-tab-st-pro-upsell' ); ?>"><?php esc_html_e( 'Upgrade', 'strong-testimonials' ); ?></a>
admin/custom-fields.php CHANGED
@@ -82,6 +82,7 @@ function wpmtst_update_custom_fields() {
82
 
83
  $field['name'] = sanitize_text_field( $field['name'] );
84
  $field['label'] = sanitize_text_field( $field['label'] );
 
85
 
86
  // TODO Replace this special handling
87
  if ( 'checkbox' == $field['input_type'] ) {
@@ -96,11 +97,8 @@ function wpmtst_update_custom_fields() {
96
 
97
  $field['placeholder'] = sanitize_text_field( $field['placeholder'] );
98
 
99
- if ( isset( $field['text'] ) ) {
100
- $field['text'] = wp_filter_post_kses( $field['text'] );
101
- }
102
- $field['before'] = sanitize_text_field( $field['before'] );
103
- $field['after'] = sanitize_text_field( $field['after'] );
104
 
105
  $field['shortcode_on_form'] = sanitize_text_field( $field['shortcode_on_form'] );
106
  $field['shortcode_on_display'] = sanitize_text_field( $field['shortcode_on_display'] );
82
 
83
  $field['name'] = sanitize_text_field( $field['name'] );
84
  $field['label'] = sanitize_text_field( $field['label'] );
85
+ $field['text'] = sanitize_text_field( $field['text'] );
86
 
87
  // TODO Replace this special handling
88
  if ( 'checkbox' == $field['input_type'] ) {
97
 
98
  $field['placeholder'] = sanitize_text_field( $field['placeholder'] );
99
 
100
+ $field['before'] = sanitize_text_field( $field['before'] );
101
+ $field['after'] = sanitize_text_field( $field['after'] );
 
 
 
102
 
103
  $field['shortcode_on_form'] = sanitize_text_field( $field['shortcode_on_form'] );
104
  $field['shortcode_on_display'] = sanitize_text_field( $field['shortcode_on_display'] );
admin/js/admin-compat.js CHANGED
@@ -77,14 +77,14 @@
77
 
78
  // Presets
79
  function setScenario1() {
80
- $('#page-loading-general').click()
81
- $('#prerender-all').click().prop('checked', true)
82
- $('#method-universal').click().prop('checked', true)
83
  }
84
 
85
  function setScenarioDefault() {
86
- $('#prerender-current').click()
87
- $('#method-none').click()
88
  }
89
 
90
  // Listen for change
@@ -104,7 +104,7 @@
104
 
105
  // Listen for presets
106
  $('#set-scenario-1').on('click', function(e) {
107
- $(this).blur()
108
  setScenario1()
109
  e.preventDefault()
110
  })
77
 
78
  // Presets
79
  function setScenario1() {
80
+ $('#page-loading-general').trigger('click')
81
+ $('#prerender-all').trigger('click').prop('checked', true)
82
+ $('#method-universal').trigger('click').prop('checked', true)
83
  }
84
 
85
  function setScenarioDefault() {
86
+ $('#prerender-current').trigger('click')
87
+ $('#method-none').trigger('click')
88
  }
89
 
90
  // Listen for change
104
 
105
  // Listen for presets
106
  $('#set-scenario-1').on('click', function(e) {
107
+ $(this).trigger('blur')
108
  setScenario1()
109
  e.preventDefault()
110
  })
admin/js/admin-fields.js CHANGED
@@ -97,7 +97,7 @@ function sanitizeName(label) {
97
  * Disable buttons on submit.
98
  * Thanks https://stackoverflow.com/a/25651260/51600
99
  */
100
- $theForm.submit(function(){
101
  $('#field-group-actions').find('.button').each(function (index) {
102
  // Create a disabled clone of the submit button
103
  $(this).clone(false).removeAttr('id').prop('disabled', true).insertBefore($(this));
@@ -118,9 +118,9 @@ function sanitizeName(label) {
118
  $(this).parent().find('.form-error-text').show();
119
  var $parent = $(this).closest("li");
120
  if (!$parent.hasClass("open")) {
121
- $parent.find("a.field").click();
122
  }
123
- $(this).focus();
124
  e.preventDefault();
125
  } else {
126
  $(this).closest('tr').removeClass('form-error');
@@ -135,9 +135,9 @@ function sanitizeName(label) {
135
  $(this).parent().find('.field-name-help.important').addClass('form-error-text');
136
  var $parent = $(this).closest("li");
137
  if (!$parent.hasClass("open")) {
138
- $parent.find("a.field").click();
139
  }
140
- $(this).focus();
141
  e.preventDefault();
142
  } else {
143
  $(this).closest('tr').removeClass('form-error');
@@ -151,7 +151,7 @@ function sanitizeName(label) {
151
  * Cancel Changes
152
  */
153
  $('#reset').on('click',function(e){
154
- $theForm.submit();
155
  });
156
 
157
  /**
@@ -159,9 +159,9 @@ function sanitizeName(label) {
159
  */
160
  $('#restore-defaults').on('click',function(e){
161
  if (confirm("Restore the default fields?")) {
162
- $theForm.submit();
163
  } else {
164
- $(this).blur();
165
  return false;
166
  }
167
  });
@@ -202,7 +202,7 @@ function sanitizeName(label) {
202
  // fill in blank field name
203
  var $fieldName = $parent.find("input.field-name");
204
  if ('new_field' === $fieldName.val()) {
205
- $fieldName.val(getUniqueName(newLabel, fieldIndex)).change();
206
  }
207
  });
208
 
@@ -225,7 +225,7 @@ function sanitizeName(label) {
225
  if ('name' === $(this).val() || 'date' === $(this).val()) {
226
  $(this).closest('tr').addClass('form-error');
227
  $(this).parent().find('.field-name-help.important').addClass('form-error-text');
228
- $(this).focus()
229
  return false;
230
  } else {
231
  $(this).closest('tr').removeClass('form-error');
@@ -237,7 +237,7 @@ function sanitizeName(label) {
237
  * Delete field
238
  */
239
  $fieldList.on("click", ".delete-field", function () {
240
- $(this).blur();
241
  dismissNotice();
242
  var thisField = $(this).closest("li");
243
  var thisLabel = thisField.find(".field").text();
@@ -246,7 +246,7 @@ function sanitizeName(label) {
246
  $.when(thisField.remove()).then(function () {
247
  formPreview();
248
  toggleCategoryFields();
249
- $("#add-field, #submit").removeAttr("disabled");
250
  })
251
  });
252
  }
@@ -263,7 +263,7 @@ function sanitizeName(label) {
263
  /**
264
  * Add new field
265
  */
266
- $("#add-field").click(function () {
267
  dismissNotice();
268
  var keys = $fieldList.find("li").map(function () {
269
  var key_id = $(this).attr("id");
@@ -296,7 +296,7 @@ function sanitizeName(label) {
296
  toggleCategoryFields();
297
 
298
  // click it to open
299
- $li.find("span.link").click();
300
  });
301
  });
302
  });
@@ -380,7 +380,7 @@ function sanitizeName(label) {
380
  // hide help message
381
  $parent.find(".field-name-help").hide();
382
  }
383
- $fieldLabel.val(wpmtstAdmin.newField).focus().select();
384
  break;
385
 
386
  default:
@@ -388,9 +388,9 @@ function sanitizeName(label) {
388
  $parent.find('.field-name-row').show();
389
 
390
  // TODO DRY
391
- $fieldLabel.val(wpmtstAdmin.newField).focus().select();
392
  $fieldName.val(getUniqueName($fieldLabel.val(), fieldIndex));
393
- $fieldName.removeAttr('disabled');
394
  $parent.find(".field-name-help").show();
395
  }
396
 
@@ -443,7 +443,7 @@ function sanitizeName(label) {
443
  ajax3.done(function () {
444
 
445
  formPreview();
446
- $("#add-field, #submit").removeAttr("disabled");
447
 
448
  // Successfully added so show "Close" link
449
  $("span.close-field").show();
@@ -552,7 +552,7 @@ function sanitizeName(label) {
552
  } else {
553
  $options.each(function () {
554
  $(this)
555
- .removeAttr("disabled")
556
  .text($(this).data('origText'));
557
  });
558
  }
@@ -570,7 +570,7 @@ function sanitizeName(label) {
570
  .toggleClass("open")
571
  .slideToggle()
572
  .find(".first-field")
573
- .focus();
574
  }
575
 
576
  // Build a unique name
@@ -597,7 +597,7 @@ function sanitizeName(label) {
597
 
598
  // Dismiss the "Fields saved" notice.
599
  function dismissNotice() {
600
- $('.wpmtst.notice').find(".notice-dismiss").click();
601
  }
602
 
603
  })(jQuery);
97
  * Disable buttons on submit.
98
  * Thanks https://stackoverflow.com/a/25651260/51600
99
  */
100
+ $theForm.on('submit', function(){
101
  $('#field-group-actions').find('.button').each(function (index) {
102
  // Create a disabled clone of the submit button
103
  $(this).clone(false).removeAttr('id').prop('disabled', true).insertBefore($(this));
118
  $(this).parent().find('.form-error-text').show();
119
  var $parent = $(this).closest("li");
120
  if (!$parent.hasClass("open")) {
121
+ $parent.find("a.field").trigger('click');
122
  }
123
+ $(this).trigger('focus');
124
  e.preventDefault();
125
  } else {
126
  $(this).closest('tr').removeClass('form-error');
135
  $(this).parent().find('.field-name-help.important').addClass('form-error-text');
136
  var $parent = $(this).closest("li");
137
  if (!$parent.hasClass("open")) {
138
+ $parent.find("a.field").trigger('click');
139
  }
140
+ $(this).trigger('focus');
141
  e.preventDefault();
142
  } else {
143
  $(this).closest('tr').removeClass('form-error');
151
  * Cancel Changes
152
  */
153
  $('#reset').on('click',function(e){
154
+ $theForm.trigger('submit');
155
  });
156
 
157
  /**
159
  */
160
  $('#restore-defaults').on('click',function(e){
161
  if (confirm("Restore the default fields?")) {
162
+ $theForm.trigger('submit');
163
  } else {
164
+ $(this).trigger("blur");
165
  return false;
166
  }
167
  });
202
  // fill in blank field name
203
  var $fieldName = $parent.find("input.field-name");
204
  if ('new_field' === $fieldName.val()) {
205
+ $fieldName.val(getUniqueName(newLabel, fieldIndex)).trigger( 'change' );
206
  }
207
  });
208
 
225
  if ('name' === $(this).val() || 'date' === $(this).val()) {
226
  $(this).closest('tr').addClass('form-error');
227
  $(this).parent().find('.field-name-help.important').addClass('form-error-text');
228
+ $(this).trigger('focus')
229
  return false;
230
  } else {
231
  $(this).closest('tr').removeClass('form-error');
237
  * Delete field
238
  */
239
  $fieldList.on("click", ".delete-field", function () {
240
+ $(this).trigger("blur");
241
  dismissNotice();
242
  var thisField = $(this).closest("li");
243
  var thisLabel = thisField.find(".field").text();
246
  $.when(thisField.remove()).then(function () {
247
  formPreview();
248
  toggleCategoryFields();
249
+ $("#add-field, #submit").prop("disabled", false);
250
  })
251
  });
252
  }
263
  /**
264
  * Add new field
265
  */
266
+ $("#add-field").on('click', function () {
267
  dismissNotice();
268
  var keys = $fieldList.find("li").map(function () {
269
  var key_id = $(this).attr("id");
296
  toggleCategoryFields();
297
 
298
  // click it to open
299
+ $li.find("span.link").trigger('click');
300
  });
301
  });
302
  });
380
  // hide help message
381
  $parent.find(".field-name-help").hide();
382
  }
383
+ $fieldLabel.val(wpmtstAdmin.newField).trigger('focus').trigger('select');
384
  break;
385
 
386
  default:
388
  $parent.find('.field-name-row').show();
389
 
390
  // TODO DRY
391
+ $fieldLabel.val(wpmtstAdmin.newField).trigger('focus').trigger('select');
392
  $fieldName.val(getUniqueName($fieldLabel.val(), fieldIndex));
393
+ $fieldName.prop('disabled', false);
394
  $parent.find(".field-name-help").show();
395
  }
396
 
443
  ajax3.done(function () {
444
 
445
  formPreview();
446
+ $("#add-field, #submit").prop("disabled", false);
447
 
448
  // Successfully added so show "Close" link
449
  $("span.close-field").show();
552
  } else {
553
  $options.each(function () {
554
  $(this)
555
+ .prop("disabled", false)
556
  .text($(this).data('origText'));
557
  });
558
  }
570
  .toggleClass("open")
571
  .slideToggle()
572
  .find(".first-field")
573
+ .trigger('focus');
574
  }
575
 
576
  // Build a unique name
597
 
598
  // Dismiss the "Fields saved" notice.
599
  function dismissNotice() {
600
+ $('.wpmtst.notice').find(".notice-dismiss").trigger('click');
601
  }
602
 
603
  })(jQuery);
admin/js/admin-form.js CHANGED
@@ -22,14 +22,14 @@ jQuery(document).ready(function ($) {
22
  $notifyAdmin.on( 'change', function (e) {
23
  if ($(this).is(':checked')) {
24
  $notifyFields.slideDown();
25
- $(this).blur();
26
  }
27
  else {
28
  $notifyFields.slideUp();
29
  }
30
  });
31
 
32
- $('#add-recipient').click(function (e) {
33
  var $this = $(this);
34
  var key = $this.parent().siblings('.recipient').length;
35
  var data = {
@@ -37,7 +37,7 @@ jQuery(document).ready(function ($) {
37
  'key': key,
38
  };
39
  $.get(ajaxurl, data, function (response) {
40
- $this.parent().before(response).prev().find('.admin_name').first().focus();
41
  });
42
  });
43
 
@@ -66,10 +66,10 @@ jQuery(document).ready(function ($) {
66
  var sel;
67
  //IE support
68
  if (document.selection) {
69
- this.focus();
70
  sel = document.selection.createRange();
71
  sel.text = myValue;
72
- this.focus();
73
  }
74
  //MOZILLA / NETSCAPE support
75
  else if (this.selectionStart || this.selectionStart === '0') {
@@ -77,13 +77,13 @@ jQuery(document).ready(function ($) {
77
  var endPos = this.selectionEnd;
78
  var scrollTop = this.scrollTop;
79
  this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
80
- this.focus();
81
  this.selectionStart = startPos + myValue.length;
82
  this.selectionEnd = startPos + myValue.length;
83
  this.scrollTop = scrollTop;
84
  } else {
85
  this.value += myValue;
86
- this.focus();
87
  }
88
  });
89
  };
@@ -103,7 +103,7 @@ jQuery(document).ready(function ($) {
103
  /**
104
  * Restore all default messages
105
  */
106
- $('#restore-default-messages').click(function (e) {
107
  var data = {
108
  'action': 'wpmtst_restore_default_messages'
109
  };
@@ -137,7 +137,7 @@ jQuery(document).ready(function ($) {
137
  /**
138
  * Restore a single default message
139
  */
140
- $('.restore-default-message').click(function (e) {
141
  var targetId = $(e.target).data('targetId');
142
  var data = {
143
  'action': 'wpmtst_restore_default_message',
22
  $notifyAdmin.on( 'change', function (e) {
23
  if ($(this).is(':checked')) {
24
  $notifyFields.slideDown();
25
+ $(this).trigger('blur');
26
  }
27
  else {
28
  $notifyFields.slideUp();
29
  }
30
  });
31
 
32
+ $('#add-recipient').on('click', function (e) {
33
  var $this = $(this);
34
  var key = $this.parent().siblings('.recipient').length;
35
  var data = {
37
  'key': key,
38
  };
39
  $.get(ajaxurl, data, function (response) {
40
+ $this.parent().before(response).prev().find('.admin_name').first().trigger('focus');
41
  });
42
  });
43
 
66
  var sel;
67
  //IE support
68
  if (document.selection) {
69
+ this.trigger('focus');
70
  sel = document.selection.createRange();
71
  sel.text = myValue;
72
+ this.trigger('focus');
73
  }
74
  //MOZILLA / NETSCAPE support
75
  else if (this.selectionStart || this.selectionStart === '0') {
77
  var endPos = this.selectionEnd;
78
  var scrollTop = this.scrollTop;
79
  this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
80
+ this.trigger('focus');
81
  this.selectionStart = startPos + myValue.length;
82
  this.selectionEnd = startPos + myValue.length;
83
  this.scrollTop = scrollTop;
84
  } else {
85
  this.value += myValue;
86
+ this.trigger('focus');
87
  }
88
  });
89
  };
103
  /**
104
  * Restore all default messages
105
  */
106
+ $('#restore-default-messages').on('click', function (e) {
107
  var data = {
108
  'action': 'wpmtst_restore_default_messages'
109
  };
137
  /**
138
  * Restore a single default message
139
  */
140
+ $('.restore-default-message').on('click', function (e) {
141
  var targetId = $(e.target).data('targetId');
142
  var data = {
143
  'action': 'wpmtst_restore_default_message',
admin/js/admin-order.js CHANGED
@@ -117,7 +117,7 @@ jQuery(document).ready(function ($) {
117
  handles: 'td.column-handle'
118
  });
119
 
120
- $("td.column-handle").hover(
121
  function () {
122
  $(this).closest("tr").addClass("reorder-hover");
123
  },
117
  handles: 'td.column-handle'
118
  });
119
 
120
+ $("td.column-handle").on( 'mouseenter mouseleave',
121
  function () {
122
  $(this).closest("tr").addClass("reorder-hover");
123
  },
admin/js/custom-spinner.js CHANGED
@@ -46,7 +46,7 @@
46
  plus.addClass('disabled')
47
  }
48
 
49
- minus.click(function () {
50
  var input = $(this).parent().find('input')
51
  var value = +round(input.val(),1)
52
  if (+value > +min) {
@@ -62,7 +62,7 @@
62
  }
63
  })
64
 
65
- plus.click(function () {
66
  var input = $(this).parent().find('input')
67
  var value = +round(input.val(),2)
68
  if (+value < +max) {
46
  plus.addClass('disabled')
47
  }
48
 
49
+ minus.on('click', function () {
50
  var input = $(this).parent().find('input')
51
  var value = +round(input.val(),1)
52
  if (+value > +min) {
62
  }
63
  })
64
 
65
+ plus.on('click', function () {
66
  var input = $(this).parent().find('input')
67
  var value = +round(input.val(),2)
68
  if (+value < +max) {
admin/js/help.js CHANGED
@@ -10,8 +10,8 @@
10
 
11
  if ($('#screen-meta').is(':hidden')) {
12
  // If Help container is closed, open it, then select tab
13
- $('#contextual-help-link').click().promise().done(function () {
14
- tabLink.click()
15
  })
16
  }
17
 
10
 
11
  if ($('#screen-meta').is(':hidden')) {
12
  // If Help container is closed, open it, then select tab
13
+ $('#contextual-help-link').trigger('click').promise().done(function () {
14
+ tabLink.trigger('click')
15
  })
16
  }
17
 
admin/js/lib/autosize/autosize.min.js CHANGED
@@ -1,6 +1,6 @@
1
  /*!
2
- autosize 4.0.1
3
  license: MIT
4
  http://www.jacklmoore.com/autosize
5
  */
6
- !function(e,t){if("function"==typeof define&&define.amd)define(["module","exports"],t);else if("undefined"!=typeof exports)t(module,exports);else{var n={exports:{}};t(n,n.exports),e.autosize=n.exports}}(this,function(e,t){"use strict";var n,o,p="function"==typeof Map?new Map:(n=[],o=[],{has:function(e){return-1<n.indexOf(e)},get:function(e){return o[n.indexOf(e)]},set:function(e,t){-1===n.indexOf(e)&&(n.push(e),o.push(t))},delete:function(e){var t=n.indexOf(e);-1<t&&(n.splice(t,1),o.splice(t,1))}}),c=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(e){c=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}function r(r){if(r&&r.nodeName&&"TEXTAREA"===r.nodeName&&!p.has(r)){var e,n=null,o=null,i=null,d=function(){r.clientWidth!==o&&a()},l=function(t){window.removeEventListener("resize",d,!1),r.removeEventListener("input",a,!1),r.removeEventListener("keyup",a,!1),r.removeEventListener("autosize:destroy",l,!1),r.removeEventListener("autosize:update",a,!1),Object.keys(t).forEach(function(e){r.style[e]=t[e]}),p.delete(r)}.bind(r,{height:r.style.height,resize:r.style.resize,overflowY:r.style.overflowY,overflowX:r.style.overflowX,wordWrap:r.style.wordWrap});r.addEventListener("autosize:destroy",l,!1),"onpropertychange"in r&&"oninput"in r&&r.addEventListener("keyup",a,!1),window.addEventListener("resize",d,!1),r.addEventListener("input",a,!1),r.addEventListener("autosize:update",a,!1),r.style.overflowX="hidden",r.style.wordWrap="break-word",p.set(r,{destroy:l,update:a}),"vertical"===(e=window.getComputedStyle(r,null)).resize?r.style.resize="none":"both"===e.resize&&(r.style.resize="horizontal"),n="content-box"===e.boxSizing?-(parseFloat(e.paddingTop)+parseFloat(e.paddingBottom)):parseFloat(e.borderTopWidth)+parseFloat(e.borderBottomWidth),isNaN(n)&&(n=0),a()}function s(e){var t=r.style.width;r.style.width="0px",r.offsetWidth,r.style.width=t,r.style.overflowY=e}function u(){if(0!==r.scrollHeight){var e=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push({node:e.parentNode,scrollTop:e.parentNode.scrollTop}),e=e.parentNode;return t}(r),t=document.documentElement&&document.documentElement.scrollTop;r.style.height="",r.style.height=r.scrollHeight+n+"px",o=r.clientWidth,e.forEach(function(e){e.node.scrollTop=e.scrollTop}),t&&(document.documentElement.scrollTop=t)}}function a(){u();var e=Math.round(parseFloat(r.style.height)),t=window.getComputedStyle(r,null),n="content-box"===t.boxSizing?Math.round(parseFloat(t.height)):r.offsetHeight;if(n!==e?"hidden"===t.overflowY&&(s("scroll"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight):"hidden"!==t.overflowY&&(s("hidden"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight),i!==n){i=n;var o=c("autosize:resized");try{r.dispatchEvent(o)}catch(e){}}}}function i(e){var t=p.get(e);t&&t.destroy()}function d(e){var t=p.get(e);t&&t.update()}var l=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?((l=function(e){return e}).destroy=function(e){return e},l.update=function(e){return e}):((l=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return r(e)}),e}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],i),e},l.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],d),e}),t.default=l,e.exports=t.default});
1
  /*!
2
+ autosize 4.0.2
3
  license: MIT
4
  http://www.jacklmoore.com/autosize
5
  */
6
+ !function(e,t){if("function"==typeof define&&define.amd)define(["module","exports"],t);else if("undefined"!=typeof exports)t(module,exports);else{var n={exports:{}};t(n,n.exports),e.autosize=n.exports}}(this,function(e,t){"use strict";var n,o,p="function"==typeof Map?new Map:(n=[],o=[],{has:function(e){return-1<n.indexOf(e)},get:function(e){return o[n.indexOf(e)]},set:function(e,t){-1===n.indexOf(e)&&(n.push(e),o.push(t))},delete:function(e){var t=n.indexOf(e);-1<t&&(n.splice(t,1),o.splice(t,1))}}),c=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(e){c=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}function r(r){if(r&&r.nodeName&&"TEXTAREA"===r.nodeName&&!p.has(r)){var e,n=null,o=null,i=null,d=function(){r.clientWidth!==o&&a()},l=function(t){window.removeEventListener("resize",d,!1),r.removeEventListener("input",a,!1),r.removeEventListener("keyup",a,!1),r.removeEventListener("autosize:destroy",l,!1),r.removeEventListener("autosize:update",a,!1),Object.keys(t).forEach(function(e){r.style[e]=t[e]}),p.delete(r)}.bind(r,{height:r.style.height,resize:r.style.resize,overflowY:r.style.overflowY,overflowX:r.style.overflowX,wordWrap:r.style.wordWrap});r.addEventListener("autosize:destroy",l,!1),"onpropertychange"in r&&"oninput"in r&&r.addEventListener("keyup",a,!1),window.addEventListener("resize",d,!1),r.addEventListener("input",a,!1),r.addEventListener("autosize:update",a,!1),r.style.overflowX="hidden",r.style.wordWrap="break-word",p.set(r,{destroy:l,update:a}),"vertical"===(e=window.getComputedStyle(r,null)).resize?r.style.resize="none":"both"===e.resize&&(r.style.resize="horizontal"),n="content-box"===e.boxSizing?-(parseFloat(e.paddingTop)+parseFloat(e.paddingBottom)):parseFloat(e.borderTopWidth)+parseFloat(e.borderBottomWidth),isNaN(n)&&(n=0),a()}function s(e){var t=r.style.width;r.style.width="0px",r.offsetWidth,r.style.width=t,r.style.overflowY=e}function u(){if(0!==r.scrollHeight){var e=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push({node:e.parentNode,scrollTop:e.parentNode.scrollTop}),e=e.parentNode;return t}(r),t=document.documentElement&&document.documentElement.scrollTop;r.style.height="",r.style.height=r.scrollHeight+n+"px",o=r.clientWidth,e.forEach(function(e){e.node.scrollTop=e.scrollTop}),t&&(document.documentElement.scrollTop=t)}}function a(){u();var e=Math.round(parseFloat(r.style.height)),t=window.getComputedStyle(r,null),n="content-box"===t.boxSizing?Math.round(parseFloat(t.height)):r.offsetHeight;if(n<e?"hidden"===t.overflowY&&(s("scroll"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight):"hidden"!==t.overflowY&&(s("hidden"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight),i!==n){i=n;var o=c("autosize:resized");try{r.dispatchEvent(o)}catch(e){}}}}function i(e){var t=p.get(e);t&&t.destroy()}function d(e){var t=p.get(e);t&&t.update()}var l=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?((l=function(e){return e}).destroy=function(e){return e},l.update=function(e){return e}):((l=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return r(e)}),e}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],i),e},l.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],d),e}),t.default=l,e.exports=t.default});
admin/js/rating-edit.js CHANGED
@@ -31,7 +31,7 @@ jQuery(document).ready(function ($) {
31
  var field_name = name_on_form.match(/\[(.*)\]/).pop();
32
 
33
  if (new_rating === revert_e) {
34
- buttons2.children('.cancel').click();
35
  return;
36
  }
37
 
@@ -74,7 +74,7 @@ jQuery(document).ready(function ($) {
74
 
75
  buttons2.children('.zero').on("click", function () {
76
  ratingForm.find("input[value=0]").prop("checked", true);
77
- $(this).blur();
78
  return false;
79
  });
80
 
31
  var field_name = name_on_form.match(/\[(.*)\]/).pop();
32
 
33
  if (new_rating === revert_e) {
34
+ buttons2.children('.cancel').trigger('click');
35
  return;
36
  }
37
 
74
 
75
  buttons2.children('.zero').on("click", function () {
76
  ratingForm.find("input[value=0]").prop("checked", true);
77
+ $(this).trigger('blur');
78
  return false;
79
  });
80
 
admin/js/selectize.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * sifter.js
3
- * Copyright (c) 2013 Brian Reavis & contributors
4
  *
5
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6
  * file except in compliance with the License. You may obtain a copy of the License at:
@@ -15,489 +15,488 @@
15
  */
16
 
17
  (function(root, factory) {
18
- if (typeof define === 'function' && define.amd) {
19
- define('sifter', factory);
20
- } else if (typeof exports === 'object') {
21
- module.exports = factory();
22
- } else {
23
- root.Sifter = factory();
24
- }
25
  }(this, function() {
26
 
27
- /**
28
- * Textually searches arrays and hashes of objects
29
- * by property (or multiple properties). Designed
30
- * specifically for autocomplete.
31
- *
32
- * @constructor
33
- * @param {array|object} items
34
- * @param {object} items
35
- */
36
- var Sifter = function(items, settings) {
37
- this.items = items;
38
- this.settings = settings || {diacritics: true};
39
- };
40
-
41
- /**
42
- * Splits a search string into an array of individual
43
- * regexps to be used to match results.
44
- *
45
- * @param {string} query
46
- * @returns {array}
47
- */
48
- Sifter.prototype.tokenize = function(query) {
49
- query = trim(String(query || '').toLowerCase());
50
- if (!query || !query.length) return [];
51
-
52
- var i, n, regex, letter;
53
- var tokens = [];
54
- var words = query.split(/ +/);
55
-
56
- for (i = 0, n = words.length; i < n; i++) {
57
- regex = escape_regex(words[i]);
58
- if (this.settings.diacritics) {
59
- for (letter in DIACRITICS) {
60
- if (DIACRITICS.hasOwnProperty(letter)) {
61
- regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
62
- }
63
- }
64
- }
65
- tokens.push({
66
- string : words[i],
67
- regex : new RegExp(regex, 'i')
68
- });
69
- }
70
-
71
- return tokens;
72
- };
73
-
74
- /**
75
- * Iterates over arrays and hashes.
76
- *
77
- * ```
78
- * this.iterator(this.items, function(item, id) {
79
- * // invoked for each item
80
- * });
81
- * ```
82
- *
83
- * @param {array|object} object
84
- */
85
- Sifter.prototype.iterator = function(object, callback) {
86
- var iterator;
87
- if (is_array(object)) {
88
- iterator = Array.prototype.forEach || function(callback) {
89
- for (var i = 0, n = this.length; i < n; i++) {
90
- callback(this[i], i, this);
91
- }
92
- };
93
- } else {
94
- iterator = function(callback) {
95
- for (var key in this) {
96
- if (this.hasOwnProperty(key)) {
97
- callback(this[key], key, this);
98
- }
99
- }
100
- };
101
- }
102
-
103
- iterator.apply(object, [callback]);
104
- };
105
-
106
- /**
107
- * Returns a function to be used to score individual results.
108
- *
109
- * Good matches will have a higher score than poor matches.
110
- * If an item is not a match, 0 will be returned by the function.
111
- *
112
- * @param {object|string} search
113
- * @param {object} options (optional)
114
- * @returns {function}
115
- */
116
- Sifter.prototype.getScoreFunction = function(search, options) {
117
- var self, fields, tokens, token_count, nesting;
118
-
119
- self = this;
120
- search = self.prepareSearch(search, options);
121
- tokens = search.tokens;
122
- fields = search.options.fields;
123
- token_count = tokens.length;
124
- nesting = search.options.nesting;
125
-
126
- /**
127
- * Calculates how close of a match the
128
- * given value is against a search token.
129
- *
130
- * @param {mixed} value
131
- * @param {object} token
132
- * @return {number}
133
- */
134
- var scoreValue = function(value, token) {
135
- var score, pos;
136
-
137
- if (!value) return 0;
138
- value = String(value || '');
139
- pos = value.search(token.regex);
140
- if (pos === -1) return 0;
141
- score = token.string.length / value.length;
142
- if (pos === 0) score += 0.5;
143
- return score;
144
- };
145
-
146
- /**
147
- * Calculates the score of an object
148
- * against the search query.
149
- *
150
- * @param {object} token
151
- * @param {object} data
152
- * @return {number}
153
- */
154
- var scoreObject = (function() {
155
- var field_count = fields.length;
156
- if (!field_count) {
157
- return function() { return 0; };
158
- }
159
- if (field_count === 1) {
160
- return function(token, data) {
161
- return scoreValue(getattr(data, fields[0], nesting), token);
162
- };
163
- }
164
- return function(token, data) {
165
- for (var i = 0, sum = 0; i < field_count; i++) {
166
- sum += scoreValue(getattr(data, fields[i], nesting), token);
167
- }
168
- return sum / field_count;
169
- };
170
- })();
171
-
172
- if (!token_count) {
173
- return function() { return 0; };
174
- }
175
- if (token_count === 1) {
176
- return function(data) {
177
- return scoreObject(tokens[0], data);
178
- };
179
- }
180
-
181
- if (search.options.conjunction === 'and') {
182
- return function(data) {
183
- var score;
184
- for (var i = 0, sum = 0; i < token_count; i++) {
185
- score = scoreObject(tokens[i], data);
186
- if (score <= 0) return 0;
187
- sum += score;
188
- }
189
- return sum / token_count;
190
- };
191
- } else {
192
- return function(data) {
193
- for (var i = 0, sum = 0; i < token_count; i++) {
194
- sum += scoreObject(tokens[i], data);
195
- }
196
- return sum / token_count;
197
- };
198
- }
199
- };
200
-
201
- /**
202
- * Returns a function that can be used to compare two
203
- * results, for sorting purposes. If no sorting should
204
- * be performed, `null` will be returned.
205
- *
206
- * @param {string|object} search
207
- * @param {object} options
208
- * @return function(a,b)
209
- */
210
- Sifter.prototype.getSortFunction = function(search, options) {
211
- var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
212
-
213
- self = this;
214
- search = self.prepareSearch(search, options);
215
- sort = (!search.query && options.sort_empty) || options.sort;
216
-
217
- /**
218
- * Fetches the specified sort field value
219
- * from a search result item.
220
- *
221
- * @param {string} name
222
- * @param {object} result
223
- * @return {mixed}
224
- */
225
- get_field = function(name, result) {
226
- if (name === '$score') return result.score;
227
- return getattr(self.items[result.id], name, options.nesting);
228
- };
229
-
230
- // parse options
231
- fields = [];
232
- if (sort) {
233
- for (i = 0, n = sort.length; i < n; i++) {
234
- if (search.query || sort[i].field !== '$score') {
235
- fields.push(sort[i]);
236
- }
237
- }
238
- }
239
-
240
- // the "$score" field is implied to be the primary
241
- // sort field, unless it's manually specified
242
- if (search.query) {
243
- implicit_score = true;
244
- for (i = 0, n = fields.length; i < n; i++) {
245
- if (fields[i].field === '$score') {
246
- implicit_score = false;
247
- break;
248
- }
249
- }
250
- if (implicit_score) {
251
- fields.unshift({field: '$score', direction: 'desc'});
252
- }
253
- } else {
254
- for (i = 0, n = fields.length; i < n; i++) {
255
- if (fields[i].field === '$score') {
256
- fields.splice(i, 1);
257
- break;
258
- }
259
- }
260
- }
261
-
262
- multipliers = [];
263
- for (i = 0, n = fields.length; i < n; i++) {
264
- multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
265
- }
266
-
267
- // build function
268
- fields_count = fields.length;
269
- if (!fields_count) {
270
- return null;
271
- } else if (fields_count === 1) {
272
- field = fields[0].field;
273
- multiplier = multipliers[0];
274
- return function(a, b) {
275
- return multiplier * cmp(
276
- get_field(field, a),
277
- get_field(field, b)
278
- );
279
- };
280
- } else {
281
- return function(a, b) {
282
- var i, result, a_value, b_value, field;
283
- for (i = 0; i < fields_count; i++) {
284
- field = fields[i].field;
285
- result = multipliers[i] * cmp(
286
- get_field(field, a),
287
- get_field(field, b)
288
- );
289
- if (result) return result;
290
- }
291
- return 0;
292
- };
293
- }
294
- };
295
-
296
- /**
297
- * Parses a search query and returns an object
298
- * with tokens and fields ready to be populated
299
- * with results.
300
- *
301
- * @param {string} query
302
- * @param {object} options
303
- * @returns {object}
304
- */
305
- Sifter.prototype.prepareSearch = function(query, options) {
306
- if (typeof query === 'object') return query;
307
-
308
- options = extend({}, options);
309
-
310
- var option_fields = options.fields;
311
- var option_sort = options.sort;
312
- var option_sort_empty = options.sort_empty;
313
-
314
- if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
315
- if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
316
- if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
317
-
318
- return {
319
- options : options,
320
- query : String(query || '').toLowerCase(),
321
- tokens : this.tokenize(query),
322
- total : 0,
323
- items : []
324
- };
325
- };
326
-
327
- /**
328
- * Searches through all items and returns a sorted array of matches.
329
- *
330
- * The `options` parameter can contain:
331
- *
332
- * - fields {string|array}
333
- * - sort {array}
334
- * - score {function}
335
- * - filter {bool}
336
- * - limit {integer}
337
- *
338
- * Returns an object containing:
339
- *
340
- * - options {object}
341
- * - query {string}
342
- * - tokens {array}
343
- * - total {int}
344
- * - items {array}
345
- *
346
- * @param {string} query
347
- * @param {object} options
348
- * @returns {object}
349
- */
350
- Sifter.prototype.search = function(query, options) {
351
- var self = this, value, score, search, calculateScore;
352
- var fn_sort;
353
- var fn_score;
354
-
355
- search = this.prepareSearch(query, options);
356
- options = search.options;
357
- query = search.query;
358
-
359
- // generate result scoring function
360
- fn_score = options.score || self.getScoreFunction(search);
361
-
362
- // perform search and sort
363
- if (query.length) {
364
- self.iterator(self.items, function(item, id) {
365
- score = fn_score(item);
366
- if (options.filter === false || score > 0) {
367
- search.items.push({'score': score, 'id': id});
368
- }
369
- });
370
- } else {
371
- self.iterator(self.items, function(item, id) {
372
- search.items.push({'score': 1, 'id': id});
373
- });
374
- }
375
-
376
- fn_sort = self.getSortFunction(search, options);
377
- if (fn_sort) search.items.sort(fn_sort);
378
-
379
- // apply limits
380
- search.total = search.items.length;
381
- if (typeof options.limit === 'number') {
382
- search.items = search.items.slice(0, options.limit);
383
- }
384
-
385
- return search;
386
- };
387
-
388
- // utilities
389
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
390
-
391
- var cmp = function(a, b) {
392
- if (typeof a === 'number' && typeof b === 'number') {
393
- return a > b ? 1 : (a < b ? -1 : 0);
394
- }
395
- a = asciifold(String(a || ''));
396
- b = asciifold(String(b || ''));
397
- if (a > b) return 1;
398
- if (b > a) return -1;
399
- return 0;
400
- };
401
-
402
- var extend = function(a, b) {
403
- var i, n, k, object;
404
- for (i = 1, n = arguments.length; i < n; i++) {
405
- object = arguments[i];
406
- if (!object) continue;
407
- for (k in object) {
408
- if (object.hasOwnProperty(k)) {
409
- a[k] = object[k];
410
- }
411
- }
412
- }
413
- return a;
414
- };
415
-
416
- /**
417
- * A property getter resolving dot-notation
418
- * @param {Object} obj The root object to fetch property on
419
- * @param {String} name The optionally dotted property name to fetch
420
- * @param {Boolean} nesting Handle nesting or not
421
- * @return {Object} The resolved property value
422
- */
423
- var getattr = function(obj, name, nesting) {
424
- if (!obj || !name) return;
425
- if (!nesting) return obj[name];
426
- var names = name.split(".");
427
- while(names.length && (obj = obj[names.shift()]));
428
- return obj;
429
- };
430
-
431
- var trim = function(str) {
432
- return (str + '').replace(/^\s+|\s+$|/g, '');
433
- };
434
-
435
- var escape_regex = function(str) {
436
- return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
437
- };
438
-
439
- var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) {
440
- return Object.prototype.toString.call(object) === '[object Array]';
441
- };
442
-
443
- var DIACRITICS = {
444
- 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
445
- 'b': '[b␢βΒB฿𐌁ᛒ]',
446
- 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
447
- 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
448
- 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
449
- 'f': '[fƑƒḞḟ]',
450
- 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
451
- 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
452
- 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]',
453
- 'j': '[jȷĴĵɈɉʝɟʲ]',
454
- 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
455
- 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
456
- 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
457
- 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
458
- 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
459
- 'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
460
- 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
461
- 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
462
- 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
463
- 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
464
- 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
465
- 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
466
- 'x': '[xẌẍẊẋχ]',
467
- 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
468
- 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
469
- };
470
-
471
- var asciifold = (function() {
472
- var i, n, k, chunk;
473
- var foreignletters = '';
474
- var lookup = {};
475
- for (k in DIACRITICS) {
476
- if (DIACRITICS.hasOwnProperty(k)) {
477
- chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
478
- foreignletters += chunk;
479
- for (i = 0, n = chunk.length; i < n; i++) {
480
- lookup[chunk.charAt(i)] = k;
481
- }
482
- }
483
- }
484
- var regexp = new RegExp('[' + foreignletters + ']', 'g');
485
- return function(str) {
486
- return str.replace(regexp, function(foreignletter) {
487
- return lookup[foreignletter];
488
- }).toLowerCase();
489
- };
490
- })();
491
-
492
-
493
- // export
494
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
495
-
496
- return Sifter;
 
497
  }));
498
 
499
-
500
-
501
  /**
502
  * microplugin.js
503
  * Copyright (c) 2013 Brian Reavis & contributors
@@ -515,128 +514,129 @@
515
  */
516
 
517
  (function(root, factory) {
518
- if (typeof define === 'function' && define.amd) {
519
- define('microplugin', factory);
520
- } else if (typeof exports === 'object') {
521
- module.exports = factory();
522
- } else {
523
- root.MicroPlugin = factory();
524
- }
525
  }(this, function() {
526
- var MicroPlugin = {};
527
-
528
- MicroPlugin.mixin = function(Interface) {
529
- Interface.plugins = {};
530
-
531
- /**
532
- * Initializes the listed plugins (with options).
533
- * Acceptable formats:
534
- *
535
- * List (without options):
536
- * ['a', 'b', 'c']
537
- *
538
- * List (with options):
539
- * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
540
- *
541
- * Hash (with options):
542
- * {'a': { ... }, 'b': { ... }, 'c': { ... }}
543
- *
544
- * @param {mixed} plugins
545
- */
546
- Interface.prototype.initializePlugins = function(plugins) {
547
- var i, n, key;
548
- var self = this;
549
- var queue = [];
550
-
551
- self.plugins = {
552
- names : [],
553
- settings : {},
554
- requested : {},
555
- loaded : {}
556
- };
557
-
558
- if (utils.isArray(plugins)) {
559
- for (i = 0, n = plugins.length; i < n; i++) {
560
- if (typeof plugins[i] === 'string') {
561
- queue.push(plugins[i]);
562
- } else {
563
- self.plugins.settings[plugins[i].name] = plugins[i].options;
564
- queue.push(plugins[i].name);
565
- }
566
- }
567
- } else if (plugins) {
568
- for (key in plugins) {
569
- if (plugins.hasOwnProperty(key)) {
570
- self.plugins.settings[key] = plugins[key];
571
- queue.push(key);
572
- }
573
- }
574
- }
575
-
576
- while (queue.length) {
577
- self.require(queue.shift());
578
- }
579
- };
580
-
581
- Interface.prototype.loadPlugin = function(name) {
582
- var self = this;
583
- var plugins = self.plugins;
584
- var plugin = Interface.plugins[name];
585
-
586
- if (!Interface.plugins.hasOwnProperty(name)) {
587
- throw new Error('Unable to find "' + name + '" plugin');
588
- }
589
-
590
- plugins.requested[name] = true;
591
- plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
592
- plugins.names.push(name);
593
- };
594
-
595
- /**
596
- * Initializes a plugin.
597
- *
598
- * @param {string} name
599
- */
600
- Interface.prototype.require = function(name) {
601
- var self = this;
602
- var plugins = self.plugins;
603
-
604
- if (!self.plugins.loaded.hasOwnProperty(name)) {
605
- if (plugins.requested[name]) {
606
- throw new Error('Plugin has circular dependency ("' + name + '")');
607
- }
608
- self.loadPlugin(name);
609
- }
610
-
611
- return plugins.loaded[name];
612
- };
613
-
614
- /**
615
- * Registers a plugin.
616
- *
617
- * @param {string} name
618
- * @param {function} fn
619
- */
620
- Interface.define = function(name, fn) {
621
- Interface.plugins[name] = {
622
- 'name' : name,
623
- 'fn' : fn
624
- };
625
- };
626
- };
627
-
628
- var utils = {
629
- isArray: Array.isArray || function(vArg) {
630
- return Object.prototype.toString.call(vArg) === '[object Array]';
631
- }
632
- };
633
-
634
- return MicroPlugin;
635
  }));
636
 
637
  /**
638
- * selectize.js (v0.12.6)
639
  * Copyright (c) 2013–2015 Brian Reavis & contributors
 
640
  *
641
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
642
  * file except in compliance with the License. You may obtain a copy of the License at:
@@ -648,2732 +648,2762 @@
648
  * governing permissions and limitations under the License.
649
  *
650
  * @author Brian Reavis <brian@thirdroute.com>
 
651
  */
652
 
653
  /*jshint curly:false */
654
  /*jshint browser:true */
655
 
656
  (function(root, factory) {
657
- if (typeof define === 'function' && define.amd) {
658
- define('selectize', ['jquery','sifter','microplugin'], factory);
659
- } else if (typeof exports === 'object') {
660
- module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
661
- } else {
662
- root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
663
- }
664
  }(this, function($, Sifter, MicroPlugin) {
665
- 'use strict';
666
-
667
- var highlight = function($element, pattern) {
668
- if (typeof pattern === 'string' && !pattern.length) return;
669
- var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
670
-
671
- var highlight = function(node) {
672
- var skip = 0;
673
- // Wrap matching part of text node with highlighting <span>, e.g.
674
- // Soccer -> <span class="highlight">Soc</span>cer for regex = /soc/i
675
- if (node.nodeType === 3) {
676
- var pos = node.data.search(regex);
677
- if (pos >= 0 && node.data.length > 0) {
678
- var match = node.data.match(regex);
679
- var spannode = document.createElement('span');
680
- spannode.className = 'highlight';
681
- var middlebit = node.splitText(pos);
682
- var endbit = middlebit.splitText(match[0].length);
683
- var middleclone = middlebit.cloneNode(true);
684
- spannode.appendChild(middleclone);
685
- middlebit.parentNode.replaceChild(spannode, middlebit);
686
- skip = 1;
687
- }
688
- }
689
- // Recurse element node, looking for child text nodes to highlight, unless element
690
- // is childless, <script>, <style>, or already highlighted: <span class="hightlight">
691
- else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && ( node.className !== 'highlight' || node.tagName !== 'SPAN' )) {
692
- for (var i = 0; i < node.childNodes.length; ++i) {
693
- i += highlight(node.childNodes[i]);
694
- }
695
- }
696
- return skip;
697
- };
698
-
699
- return $element.each(function() {
700
- highlight(this);
701
- });
702
- };
703
-
704
- /**
705
- * removeHighlight fn copied from highlight v5 and
706
- * edited to remove with() and pass js strict mode
707
- */
708
- $.fn.removeHighlight = function() {
709
- return this.find("span.highlight").each(function() {
710
- this.parentNode.firstChild.nodeName;
711
- var parent = this.parentNode;
712
- parent.replaceChild(this.firstChild, this);
713
- parent.normalize();
714
- }).end();
715
- };
716
-
717
-
718
- var MicroEvent = function() {};
719
- MicroEvent.prototype = {
720
- on: function(event, fct){
721
- this._events = this._events || {};
722
- this._events[event] = this._events[event] || [];
723
- this._events[event].push(fct);
724
- },
725
- off: function(event, fct){
726
- var n = arguments.length;
727
- if (n === 0) return delete this._events;
728
- if (n === 1) return delete this._events[event];
729
-
730
- this._events = this._events || {};
731
- if (event in this._events === false) return;
732
- this._events[event].splice(this._events[event].indexOf(fct), 1);
733
- },
734
- trigger: function(event /* , args... */){
735
- this._events = this._events || {};
736
- if (event in this._events === false) return;
737
- for (var i = 0; i < this._events[event].length; i++){
738
- this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
739
- }
740
- }
741
- };
742
-
743
- /**
744
- * Mixin will delegate all MicroEvent.js function in the destination object.
745
- *
746
- * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
747
- *
748
- * @param {object} the object which will support MicroEvent
749
- */
750
- MicroEvent.mixin = function(destObject){
751
- var props = ['on', 'off', 'trigger'];
752
- for (var i = 0; i < props.length; i++){
753
- destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
754
- }
755
- };
756
-
757
- var IS_MAC = /Mac/.test(navigator.userAgent);
758
-
759
- var KEY_A = 65;
760
- var KEY_COMMA = 188;
761
- var KEY_RETURN = 13;
762
- var KEY_ESC = 27;
763
- var KEY_LEFT = 37;
764
- var KEY_UP = 38;
765
- var KEY_P = 80;
766
- var KEY_RIGHT = 39;
767
- var KEY_DOWN = 40;
768
- var KEY_N = 78;
769
- var KEY_BACKSPACE = 8;
770
- var KEY_DELETE = 46;
771
- var KEY_SHIFT = 16;
772
- var KEY_CMD = IS_MAC ? 91 : 17;
773
- var KEY_CTRL = IS_MAC ? 18 : 17;
774
- var KEY_TAB = 9;
775
-
776
- var TAG_SELECT = 1;
777
- var TAG_INPUT = 2;
778
-
779
- // for now, android support in general is too spotty to support validity
780
- var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity;
781
-
782
-
783
- var isset = function(object) {
784
- return typeof object !== 'undefined';
785
- };
786
-
787
- /**
788
- * Converts a scalar to its best string representation
789
- * for hash keys and HTML attribute values.
790
- *
791
- * Transformations:
792
- * 'str' -> 'str'
793
- * null -> ''
794
- * undefined -> ''
795
- * true -> '1'
796
- * false -> '0'
797
- * 0 -> '0'
798
- * 1 -> '1'
799
- *
800
- * @param {string} value
801
- * @returns {string|null}
802
- */
803
- var hash_key = function(value) {
804
- if (typeof value === 'undefined' || value === null) return null;
805
- if (typeof value === 'boolean') return value ? '1' : '0';
806
- return value + '';
807
- };
808
-
809
- /**
810
- * Escapes a string for use within HTML.
811
- *
812
- * @param {string} str
813
- * @returns {string}
814
- */
815
- var escape_html = function(str) {
816
- return (str + '')
817
- .replace(/&/g, '&amp;')
818
- .replace(/</g, '&lt;')
819
- .replace(/>/g, '&gt;')
820
- .replace(/"/g, '&quot;');
821
- };
822
-
823
- /**
824
- * Escapes "$" characters in replacement strings.
825
- *
826
- * @param {string} str
827
- * @returns {string}
828
- */
829
- var escape_replace = function(str) {
830
- return (str + '').replace(/\$/g, '$$$$');
831
- };
832
-
833
- var hook = {};
834
-
835
- /**
836
- * Wraps `method` on `self` so that `fn`
837
- * is invoked before the original method.
838
- *
839
- * @param {object} self
840
- * @param {string} method
841
- * @param {function} fn
842
- */
843
- hook.before = function(self, method, fn) {
844
- var original = self[method];
845
- self[method] = function() {
846
- fn.apply(self, arguments);
847
- return original.apply(self, arguments);
848
- };
849
- };
850
-
851
- /**
852
- * Wraps `method` on `self` so that `fn`
853
- * is invoked after the original method.
854
- *
855
- * @param {object} self
856
- * @param {string} method
857
- * @param {function} fn
858
- */
859
- hook.after = function(self, method, fn) {
860
- var original = self[method];
861
- self[method] = function() {
862
- var result = original.apply(self, arguments);
863
- fn.apply(self, arguments);
864
- return result;
865
- };
866
- };
867
-
868
- /**
869
- * Wraps `fn` so that it can only be invoked once.
870
- *
871
- * @param {function} fn
872
- * @returns {function}
873
- */
874
- var once = function(fn) {
875
- var called = false;
876
- return function() {
877
- if (called) return;
878
- called = true;
879
- fn.apply(this, arguments);
880
- };
881
- };
882
-
883
- /**
884
- * Wraps `fn` so that it can only be called once
885
- * every `delay` milliseconds (invoked on the falling edge).
886
- *
887
- * @param {function} fn
888
- * @param {int} delay
889
- * @returns {function}
890
- */
891
- var debounce = function(fn, delay) {
892
- var timeout;
893
- return function() {
894
- var self = this;
895
- var args = arguments;
896
- window.clearTimeout(timeout);
897
- timeout = window.setTimeout(function() {
898
- fn.apply(self, args);
899
- }, delay);
900
- };
901
- };
902
-
903
- /**
904
- * Debounce all fired events types listed in `types`
905
- * while executing the provided `fn`.
906
- *
907
- * @param {object} self
908
- * @param {array} types
909
- * @param {function} fn
910
- */
911
- var debounce_events = function(self, types, fn) {
912
- var type;
913
- var trigger = self.trigger;
914
- var event_args = {};
915
-
916
- // override trigger method
917
- self.trigger = function() {
918
- var type = arguments[0];
919
- if (types.indexOf(type) !== -1) {
920
- event_args[type] = arguments;
921
- } else {
922
- return trigger.apply(self, arguments);
923
- }
924
- };
925
-
926
- // invoke provided function
927
- fn.apply(self, []);
928
- self.trigger = trigger;
929
-
930
- // trigger queued events
931
- for (type in event_args) {
932
- if (event_args.hasOwnProperty(type)) {
933
- trigger.apply(self, event_args[type]);
934
- }
935
- }
936
- };
937
-
938
- /**
939
- * A workaround for http://bugs.jquery.com/ticket/6696
940
- *
941
- * @param {object} $parent - Parent element to listen on.
942
- * @param {string} event - Event name.
943
- * @param {string} selector - Descendant selector to filter by.
944
- * @param {function} fn - Event handler.
945
- */
946
- var watchChildEvent = function($parent, event, selector, fn) {
947
- $parent.on(event, selector, function(e) {
948
- var child = e.target;
949
- while (child && child.parentNode !== $parent[0]) {
950
- child = child.parentNode;
951
- }
952
- e.currentTarget = child;
953
- return fn.apply(this, [e]);
954
- });
955
- };
956
-
957
- /**
958
- * Determines the current selection within a text input control.
959
- * Returns an object containing:
960
- * - start
961
- * - length
962
- *
963
- * @param {object} input
964
- * @returns {object}
965
- */
966
- var getSelection = function(input) {
967
- var result = {};
968
- if ('selectionStart' in input) {
969
- result.start = input.selectionStart;
970
- result.length = input.selectionEnd - result.start;
971
- } else if (document.selection) {
972
- input.focus();
973
- var sel = document.selection.createRange();
974
- var selLen = document.selection.createRange().text.length;
975
- sel.moveStart('character', -input.value.length);
976
- result.start = sel.text.length - selLen;
977
- result.length = selLen;
978
- }
979
- return result;
980
- };
981
-
982
- /**
983
- * Copies CSS properties from one element to another.
984
- *
985
- * @param {object} $from
986
- * @param {object} $to
987
- * @param {array} properties
988
- */
989
- var transferStyles = function($from, $to, properties) {
990
- var i, n, styles = {};
991
- if (properties) {
992
- for (i = 0, n = properties.length; i < n; i++) {
993
- styles[properties[i]] = $from.css(properties[i]);
994
- }
995
- } else {
996
- styles = $from.css();
997
- }
998
- $to.css(styles);
999
- };
1000
-
1001
- /**
1002
- * Measures the width of a string within a
1003
- * parent element (in pixels).
1004
- *
1005
- * @param {string} str
1006
- * @param {object} $parent
1007
- * @returns {int}
1008
- */
1009
- var measureString = function(str, $parent) {
1010
- if (!str) {
1011
- return 0;
1012
- }
1013
-
1014
- if (!Selectize.$testInput) {
1015
- Selectize.$testInput = $('<span />').css({
1016
- position: 'absolute',
1017
- top: -99999,
1018
- left: -99999,
1019
- width: 'auto',
1020
- padding: 0,
1021
- whiteSpace: 'pre'
1022
- }).appendTo('body');
1023
- }
1024
-
1025
- Selectize.$testInput.text(str);
1026
-
1027
- transferStyles($parent, Selectize.$testInput, [
1028
- 'letterSpacing',
1029
- 'fontSize',
1030
- 'fontFamily',
1031
- 'fontWeight',
1032
- 'textTransform'
1033
- ]);
1034
-
1035
- return Selectize.$testInput.width();
1036
- };
1037
-
1038
- /**
1039
- * Sets up an input to grow horizontally as the user
1040
- * types. If the value is changed manually, you can
1041
- * trigger the "update" handler to resize:
1042
- *
1043
- * $input.trigger('update');
1044
- *
1045
- * @param {object} $input
1046
- */
1047
- var autoGrow = function($input) {
1048
- var currentWidth = null;
1049
-
1050
- var update = function(e, options) {
1051
- var value, keyCode, printable, placeholder, width;
1052
- var shift, character, selection;
1053
- e = e || window.event || {};
1054
- options = options || {};
1055
-
1056
- if (e.metaKey || e.altKey) return;
1057
- if (!options.force && $input.data('grow') === false) return;
1058
-
1059
- value = $input.val();
1060
- if (e.type && e.type.toLowerCase() === 'keydown') {
1061
- keyCode = e.keyCode;
1062
- printable = (
1063
- (keyCode >= 48 && keyCode <= 57) || // 0-9
1064
- (keyCode >= 65 && keyCode <= 90) || // a-z
1065
- (keyCode >= 96 && keyCode <= 111) || // numpad 0-9, numeric operators
1066
- (keyCode >= 186 && keyCode <= 222) || // semicolon, equal, comma, dash, etc.
1067
- keyCode === 32 // space
1068
- );
1069
-
1070
- if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
1071
- selection = getSelection($input[0]);
1072
- if (selection.length) {
1073
- value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
1074
- } else if (keyCode === KEY_BACKSPACE && selection.start) {
1075
- value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
1076
- } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
1077
- value = value.substring(0, selection.start) + value.substring(selection.start + 1);
1078
- }
1079
- } else if (printable) {
1080
- shift = e.shiftKey;
1081
- character = String.fromCharCode(e.keyCode);
1082
- if (shift) character = character.toUpperCase();
1083
- else character = character.toLowerCase();
1084
- value += character;
1085
- }
1086
- }
1087
-
1088
- placeholder = $input.attr('placeholder');
1089
- if (!value && placeholder) {
1090
- value = placeholder;
1091
- }
1092
-
1093
- width = measureString(value, $input) + 4;
1094
- if (width !== currentWidth) {
1095
- currentWidth = width;
1096
- $input.width(width);
1097
- $input.triggerHandler('resize');
1098
- }
1099
- };
1100
-
1101
- $input.on('keydown keyup update blur', update);
1102
- update();
1103
- };
1104
-
1105
- var domToString = function(d) {
1106
- var tmp = document.createElement('div');
1107
-
1108
- tmp.appendChild(d.cloneNode(true));
1109
-
1110
- return tmp.innerHTML;
1111
- };
1112
-
1113
- var logError = function(message, options){
1114
- if(!options) options = {};
1115
- var component = "Selectize";
1116
-
1117
- console.error(component + ": " + message)
1118
-
1119
- if(options.explanation){
1120
- // console.group is undefined in <IE11
1121
- if(console.group) console.group();
1122
- console.error(options.explanation);
1123
- if(console.group) console.groupEnd();
1124
- }
1125
- }
1126
-
1127
-
1128
- var Selectize = function($input, settings) {
1129
- var key, i, n, dir, input, self = this;
1130
- input = $input[0];
1131
- input.selectize = self;
1132
-
1133
- // detect rtl environment
1134
- var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1135
- dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
1136
- dir = dir || $input.parents('[dir]:first').attr('dir') || '';
1137
-
1138
- // setup default state
1139
- $.extend(self, {
1140
- order : 0,
1141
- settings : settings,
1142
- $input : $input,
1143
- tabIndex : $input.attr('tabindex') || '',
1144
- tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
1145
- rtl : /rtl/i.test(dir),
1146
-
1147
- eventNS : '.selectize' + (++Selectize.count),
1148
- highlightedValue : null,
1149
- isBlurring : false,
1150
- isOpen : false,
1151
- isDisabled : false,
1152
- isRequired : $input.is('[required]'),
1153
- isInvalid : false,
1154
- isLocked : false,
1155
- isFocused : false,
1156
- isInputHidden : false,
1157
- isSetup : false,
1158
- isShiftDown : false,
1159
- isCmdDown : false,
1160
- isCtrlDown : false,
1161
- ignoreFocus : false,
1162
- ignoreBlur : false,
1163
- ignoreHover : false,
1164
- hasOptions : false,
1165
- currentResults : null,
1166
- lastValue : '',
1167
- caretPos : 0,
1168
- loading : 0,
1169
- loadedSearches : {},
1170
-
1171
- $activeOption : null,
1172
- $activeItems : [],
1173
-
1174
- optgroups : {},
1175
- options : {},
1176
- userOptions : {},
1177
- items : [],
1178
- renderCache : {},
1179
- onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
1180
- });
1181
-
1182
- // search system
1183
- self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
1184
-
1185
- // build options table
1186
- if (self.settings.options) {
1187
- for (i = 0, n = self.settings.options.length; i < n; i++) {
1188
- self.registerOption(self.settings.options[i]);
1189
- }
1190
- delete self.settings.options;
1191
- }
1192
-
1193
- // build optgroup table
1194
- if (self.settings.optgroups) {
1195
- for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
1196
- self.registerOptionGroup(self.settings.optgroups[i]);
1197
- }
1198
- delete self.settings.optgroups;
1199
- }
1200
-
1201
- // option-dependent defaults
1202
- self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
1203
- if (typeof self.settings.hideSelected !== 'boolean') {
1204
- self.settings.hideSelected = self.settings.mode === 'multi';
1205
- }
1206
-
1207
- self.initializePlugins(self.settings.plugins);
1208
- self.setupCallbacks();
1209
- self.setupTemplates();
1210
- self.setup();
1211
- };
1212
-
1213
- // mixins
1214
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1215
-
1216
- MicroEvent.mixin(Selectize);
1217
-
1218
- if(typeof MicroPlugin !== "undefined"){
1219
- MicroPlugin.mixin(Selectize);
1220
- }else{
1221
- logError("Dependency MicroPlugin is missing",
1222
- {explanation:
1223
- "Make sure you either: (1) are using the \"standalone\" "+
1224
- "version of Selectize, or (2) require MicroPlugin before you "+
1225
- "load Selectize."}
1226
- );
1227
- }
1228
-
1229
-
1230
- // methods
1231
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1232
-
1233
- $.extend(Selectize.prototype, {
1234
-
1235
- /**
1236
- * Creates all elements and sets up event bindings.
1237
- */
1238
- setup: function() {
1239
- var self = this;
1240
- var settings = self.settings;
1241
- var eventNS = self.eventNS;
1242
- var $window = $(window);
1243
- var $document = $(document);
1244
- var $input = self.$input;
1245
-
1246
- var $wrapper;
1247
- var $control;
1248
- var $control_input;
1249
- var $dropdown;
1250
- var $dropdown_content;
1251
- var $dropdown_parent;
1252
- var inputMode;
1253
- var timeout_blur;
1254
- var timeout_focus;
1255
- var classes;
1256
- var classes_plugins;
1257
- var inputId;
1258
-
1259
- inputMode = self.settings.mode;
1260
- classes = $input.attr('class') || '';
1261
-
1262
- $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
1263
- $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
1264
- $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
1265
- $dropdown_parent = $(settings.dropdownParent || $wrapper);
1266
- $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
1267
- $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
1268
-
1269
- if(inputId = $input.attr('id')) {
1270
- $control_input.attr('id', inputId + '-selectized');
1271
- $("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
1272
- }
1273
-
1274
- if(self.settings.copyClassesToDropdown) {
1275
- $dropdown.addClass(classes);
1276
- }
1277
-
1278
- $wrapper.css({
1279
- width: $input[0].style.width
1280
- });
1281
-
1282
- if (self.plugins.names.length) {
1283
- classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1284
- $wrapper.addClass(classes_plugins);
1285
- $dropdown.addClass(classes_plugins);
1286
- }
1287
-
1288
- if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
1289
- $input.attr('multiple', 'multiple');
1290
- }
1291
-
1292
- if (self.settings.placeholder) {
1293
- $control_input.attr('placeholder', settings.placeholder);
1294
- }
1295
-
1296
- // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1297
- if (!self.settings.splitOn && self.settings.delimiter) {
1298
- var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1299
- self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
1300
- }
1301
-
1302
- if ($input.attr('autocorrect')) {
1303
- $control_input.attr('autocorrect', $input.attr('autocorrect'));
1304
- }
1305
-
1306
- if ($input.attr('autocapitalize')) {
1307
- $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
1308
- }
1309
- $control_input[0].type = $input[0].type;
1310
-
1311
- self.$wrapper = $wrapper;
1312
- self.$control = $control;
1313
- self.$control_input = $control_input;
1314
- self.$dropdown = $dropdown;
1315
- self.$dropdown_content = $dropdown_content;
1316
-
1317
- $dropdown.on('mouseenter mousedown click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); });
1318
- $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1319
- $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1320
- watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1321
- autoGrow($control_input);
1322
-
1323
- $control.on({
1324
- mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1325
- click : function() { return self.onClick.apply(self, arguments); }
1326
- });
1327
-
1328
- $control_input.on({
1329
- mousedown : function(e) { e.stopPropagation(); },
1330
- keydown : function() { return self.onKeyDown.apply(self, arguments); },
1331
- keyup : function() { return self.onKeyUp.apply(self, arguments); },
1332
- keypress : function() { return self.onKeyPress.apply(self, arguments); },
1333
- resize : function() { self.positionDropdown.apply(self, []); },
1334
- blur : function() { return self.onBlur.apply(self, arguments); },
1335
- focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
1336
- paste : function() { return self.onPaste.apply(self, arguments); }
1337
- });
1338
-
1339
- $document.on('keydown' + eventNS, function(e) {
1340
- self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1341
- self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1342
- self.isShiftDown = e.shiftKey;
1343
- });
1344
-
1345
- $document.on('keyup' + eventNS, function(e) {
1346
- if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1347
- if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1348
- if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1349
- });
1350
-
1351
- $document.on('mousedown' + eventNS, function(e) {
1352
- if (self.isFocused) {
1353
- // prevent events on the dropdown scrollbar from causing the control to blur
1354
- if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
1355
- return false;
1356
- }
1357
- // blur on click outside
1358
- if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
1359
- self.blur(e.target);
1360
- }
1361
- }
1362
- });
1363
-
1364
- $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1365
- if (self.isOpen) {
1366
- self.positionDropdown.apply(self, arguments);
1367
- }
1368
- });
1369
- $window.on('mousemove' + eventNS, function() {
1370
- self.ignoreHover = false;
1371
- });
1372
-
1373
- // store original children and tab index so that they can be
1374
- // restored when the destroy() method is called.
1375
- this.revertSettings = {
1376
- $children : $input.children().detach(),
1377
- tabindex : $input.attr('tabindex')
1378
- };
1379
-
1380
- $input.attr('tabindex', -1).hide().after(self.$wrapper);
1381
-
1382
- if ($.isArray(settings.items)) {
1383
- self.setValue(settings.items);
1384
- delete settings.items;
1385
- }
1386
-
1387
- // feature detect for the validation API
1388
- if (SUPPORTS_VALIDITY_API) {
1389
- $input.on('invalid' + eventNS, function(e) {
1390
- e.preventDefault();
1391
- self.isInvalid = true;
1392
- self.refreshState();
1393
- });
1394
- }
1395
-
1396
- self.updateOriginalInput();
1397
- self.refreshItems();
1398
- self.refreshState();
1399
- self.updatePlaceholder();
1400
- self.isSetup = true;
1401
-
1402
- if ($input.is(':disabled')) {
1403
- self.disable();
1404
- }
1405
-
1406
- self.on('change', this.onChange);
1407
-
1408
- $input.data('selectize', self);
1409
- $input.addClass('selectized');
1410
- self.trigger('initialize');
1411
-
1412
- // preload options
1413
- if (settings.preload === true) {
1414
- self.onSearchChange('');
1415
- }
1416
-
1417
- },
1418
-
1419
- /**
1420
- * Sets up default rendering functions.
1421
- */
1422
- setupTemplates: function() {
1423
- var self = this;
1424
- var field_label = self.settings.labelField;
1425
- var field_optgroup = self.settings.optgroupLabelField;
1426
-
1427
- var templates = {
1428
- 'optgroup': function(data) {
1429
- return '<div class="optgroup">' + data.html + '</div>';
1430
- },
1431
- 'optgroup_header': function(data, escape) {
1432
- return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1433
- },
1434
- 'option': function(data, escape) {
1435
- return '<div class="option">' + escape(data[field_label]) + '</div>';
1436
- },
1437
- 'item': function(data, escape) {
1438
- return '<div class="item">' + escape(data[field_label]) + '</div>';
1439
- },
1440
- 'option_create': function(data, escape) {
1441
- return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
1442
- }
1443
- };
1444
-
1445
- self.settings.render = $.extend({}, templates, self.settings.render);
1446
- },
1447
-
1448
- /**
1449
- * Maps fired events to callbacks provided
1450
- * in the settings used when creating the control.
1451
- */
1452
- setupCallbacks: function() {
1453
- var key, fn, callbacks = {
1454
- 'initialize' : 'onInitialize',
1455
- 'change' : 'onChange',
1456
- 'item_add' : 'onItemAdd',
1457
- 'item_remove' : 'onItemRemove',
1458
- 'clear' : 'onClear',
1459
- 'option_add' : 'onOptionAdd',
1460
- 'option_remove' : 'onOptionRemove',
1461
- 'option_clear' : 'onOptionClear',
1462
- 'optgroup_add' : 'onOptionGroupAdd',
1463
- 'optgroup_remove' : 'onOptionGroupRemove',
1464
- 'optgroup_clear' : 'onOptionGroupClear',
1465
- 'dropdown_open' : 'onDropdownOpen',
1466
- 'dropdown_close' : 'onDropdownClose',
1467
- 'type' : 'onType',
1468
- 'load' : 'onLoad',
1469
- 'focus' : 'onFocus',
1470
- 'blur' : 'onBlur'
1471
- };
1472
-
1473
- for (key in callbacks) {
1474
- if (callbacks.hasOwnProperty(key)) {
1475
- fn = this.settings[callbacks[key]];
1476
- if (fn) this.on(key, fn);
1477
- }
1478
- }
1479
- },
1480
-
1481
- /**
1482
- * Triggered when the main control element
1483
- * has a click event.
1484
- *
1485
- * @param {object} e
1486
- * @return {boolean}
1487
- */
1488
- onClick: function(e) {
1489
- var self = this;
1490
-
1491
- // necessary for mobile webkit devices (manual focus triggering
1492
- // is ignored unless invoked within a click event)
1493
- // also necessary to reopen a dropdown that has been closed by
1494
- // closeAfterSelect
1495
- if (!self.isFocused || !self.isOpen) {
1496
- self.focus();
1497
- e.preventDefault();
1498
- }
1499
- },
1500
-
1501
- /**
1502
- * Triggered when the main control element
1503
- * has a mouse down event.
1504
- *
1505
- * @param {object} e
1506
- * @return {boolean}
1507
- */
1508
- onMouseDown: function(e) {
1509
- var self = this;
1510
- var defaultPrevented = e.isDefaultPrevented();
1511
- var $target = $(e.target);
1512
-
1513
- if (self.isFocused) {
1514
- // retain focus by preventing native handling. if the
1515
- // event target is the input it should not be modified.
1516
- // otherwise, text selection within the input won't work.
1517
- if (e.target !== self.$control_input[0]) {
1518
- if (self.settings.mode === 'single') {
1519
- // toggle dropdown
1520
- self.isOpen ? self.close() : self.open();
1521
- } else if (!defaultPrevented) {
1522
- self.setActiveItem(null);
1523
- }
1524
- return false;
1525
- }
1526
- } else {
1527
- // give control focus
1528
- if (!defaultPrevented) {
1529
- window.setTimeout(function() {
1530
- self.focus();
1531
- }, 0);
1532
- }
1533
- }
1534
- },
1535
-
1536
- /**
1537
- * Triggered when the value of the control has been changed.
1538
- * This should propagate the event to the original DOM
1539
- * input / select element.
1540
- */
1541
- onChange: function() {
1542
- this.$input.trigger('change');
1543
- },
1544
-
1545
- /**
1546
- * Triggered on <input> paste.
1547
- *
1548
- * @param {object} e
1549
- * @returns {boolean}
1550
- */
1551
- onPaste: function(e) {
1552
- var self = this;
1553
-
1554
- if (self.isFull() || self.isInputHidden || self.isLocked) {
1555
- e.preventDefault();
1556
- return;
1557
- }
1558
-
1559
- // If a regex or string is included, this will split the pasted
1560
- // input and create Items for each separate value
1561
- if (self.settings.splitOn) {
1562
-
1563
- // Wait for pasted text to be recognized in value
1564
- setTimeout(function() {
1565
- var pastedText = self.$control_input.val();
1566
- if(!pastedText.match(self.settings.splitOn)){ return }
1567
-
1568
- var splitInput = $.trim(pastedText).split(self.settings.splitOn);
1569
- for (var i = 0, n = splitInput.length; i < n; i++) {
1570
- self.createItem(splitInput[i]);
1571
- }
1572
- }, 0);
1573
- }
1574
- },
1575
-
1576
- /**
1577
- * Triggered on <input> keypress.
1578
- *
1579
- * @param {object} e
1580
- * @returns {boolean}
1581
- */
1582
- onKeyPress: function(e) {
1583
- if (this.isLocked) return e && e.preventDefault();
1584
- var character = String.fromCharCode(e.keyCode || e.which);
1585
- if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1586
- this.createItem();
1587
- e.preventDefault();
1588
- return false;
1589
- }
1590
- },
1591
-
1592
- /**
1593
- * Triggered on <input> keydown.
1594
- *
1595
- * @param {object} e
1596
- * @returns {boolean}
1597
- */
1598
- onKeyDown: function(e) {
1599
- var isInput = e.target === this.$control_input[0];
1600
- var self = this;
1601
-
1602
- if (self.isLocked) {
1603
- if (e.keyCode !== KEY_TAB) {
1604
- e.preventDefault();
1605
- }
1606
- return;
1607
- }
1608
-
1609
- switch (e.keyCode) {
1610
- case KEY_A:
1611
- if (self.isCmdDown) {
1612
- self.selectAll();
1613
- return;
1614
- }
1615
- break;
1616
- case KEY_ESC:
1617
- if (self.isOpen) {
1618
- e.preventDefault();
1619
- e.stopPropagation();
1620
- self.close();
1621
- }
1622
- return;
1623
- case KEY_N:
1624
- if (!e.ctrlKey || e.altKey) break;
1625
- case KEY_DOWN:
1626
- if (!self.isOpen && self.hasOptions) {
1627
- self.open();
1628
- } else if (self.$activeOption) {
1629
- self.ignoreHover = true;
1630
- var $next = self.getAdjacentOption(self.$activeOption, 1);
1631
- if ($next.length) self.setActiveOption($next, true, true);
1632
- }
1633
- e.preventDefault();
1634
- return;
1635
- case KEY_P:
1636
- if (!e.ctrlKey || e.altKey) break;
1637
- case KEY_UP:
1638
- if (self.$activeOption) {
1639
- self.ignoreHover = true;
1640
- var $prev = self.getAdjacentOption(self.$activeOption, -1);
1641
- if ($prev.length) self.setActiveOption($prev, true, true);
1642
- }
1643
- e.preventDefault();
1644
- return;
1645
- case KEY_RETURN:
1646
- if (self.isOpen && self.$activeOption) {
1647
- self.onOptionSelect({currentTarget: self.$activeOption});
1648
- e.preventDefault();
1649
- }
1650
- return;
1651
- case KEY_LEFT:
1652
- self.advanceSelection(-1, e);
1653
- return;
1654
- case KEY_RIGHT:
1655
- self.advanceSelection(1, e);
1656
- return;
1657
- case KEY_TAB:
1658
- if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1659
- self.onOptionSelect({currentTarget: self.$activeOption});
1660
-
1661
- // Default behaviour is to jump to the next field, we only want this
1662
- // if the current field doesn't accept any more entries
1663
- if (!self.isFull()) {
1664
- e.preventDefault();
1665
- }
1666
- }
1667
- if (self.settings.create && self.createItem()) {
1668
- e.preventDefault();
1669
- }
1670
- return;
1671
- case KEY_BACKSPACE:
1672
- case KEY_DELETE:
1673
- self.deleteSelection(e);
1674
- return;
1675
- }
1676
-
1677
- if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1678
- e.preventDefault();
1679
- return;
1680
- }
1681
- },
1682
-
1683
- /**
1684
- * Triggered on <input> keyup.
1685
- *
1686
- * @param {object} e
1687
- * @returns {boolean}
1688
- */
1689
- onKeyUp: function(e) {
1690
- var self = this;
1691
-
1692
- if (self.isLocked) return e && e.preventDefault();
1693
- var value = self.$control_input.val() || '';
1694
- if (self.lastValue !== value) {
1695
- self.lastValue = value;
1696
- self.onSearchChange(value);
1697
- self.refreshOptions();
1698
- self.trigger('type', value);
1699
- }
1700
- },
1701
-
1702
- /**
1703
- * Invokes the user-provide option provider / loader.
1704
- *
1705
- * Note: this function is debounced in the Selectize
1706
- * constructor (by `settings.loadThrottle` milliseconds)
1707
- *
1708
- * @param {string} value
1709
- */
1710
- onSearchChange: function(value) {
1711
- var self = this;
1712
- var fn = self.settings.load;
1713
- if (!fn) return;
1714
- if (self.loadedSearches.hasOwnProperty(value)) return;
1715
- self.loadedSearches[value] = true;
1716
- self.load(function(callback) {
1717
- fn.apply(self, [value, callback]);
1718
- });
1719
- },
1720
-
1721
- /**
1722
- * Triggered on <input> focus.
1723
- *
1724
- * @param {object} e (optional)
1725
- * @returns {boolean}
1726
- */
1727
- onFocus: function(e) {
1728
- var self = this;
1729
- var wasFocused = self.isFocused;
1730
-
1731
- if (self.isDisabled) {
1732
- self.blur();
1733
- e && e.preventDefault();
1734
- return false;
1735
- }
1736
-
1737
- if (self.ignoreFocus) return;
1738
- self.isFocused = true;
1739
- if (self.settings.preload === 'focus') self.onSearchChange('');
1740
-
1741
- if (!wasFocused) self.trigger('focus');
1742
-
1743
- if (!self.$activeItems.length) {
1744
- self.showInput();
1745
- self.setActiveItem(null);
1746
- self.refreshOptions(!!self.settings.openOnFocus);
1747
- }
1748
-
1749
- self.refreshState();
1750
- },
1751
-
1752
- /**
1753
- * Triggered on <input> blur.
1754
- *
1755
- * @param {object} e
1756
- * @param {Element} dest
1757
- */
1758
- onBlur: function(e, dest) {
1759
- var self = this;
1760
- if (!self.isFocused) return;
1761
- self.isFocused = false;
1762
-
1763
- if (self.ignoreFocus) {
1764
- return;
1765
- } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1766
- // necessary to prevent IE closing the dropdown when the scrollbar is clicked
1767
- self.ignoreBlur = true;
1768
- self.onFocus(e);
1769
- return;
1770
- }
1771
-
1772
- var deactivate = function() {
1773
- self.close();
1774
- self.setTextboxValue('');
1775
- self.setActiveItem(null);
1776
- self.setActiveOption(null);
1777
- self.setCaret(self.items.length);
1778
- self.refreshState();
1779
-
1780
- // IE11 bug: element still marked as active
1781
- dest && dest.focus && dest.focus();
1782
-
1783
- self.isBlurring = false;
1784
- self.ignoreFocus = false;
1785
- self.trigger('blur');
1786
- };
1787
-
1788
- self.isBlurring = true;
1789
- self.ignoreFocus = true;
1790
- if (self.settings.create && self.settings.createOnBlur) {
1791
- self.createItem(null, false, deactivate);
1792
- } else {
1793
- deactivate();
1794
- }
1795
- },
1796
-
1797
- /**
1798
- * Triggered when the user rolls over
1799
- * an option in the autocomplete dropdown menu.
1800
- *
1801
- * @param {object} e
1802
- * @returns {boolean}
1803
- */
1804
- onOptionHover: function(e) {
1805
- if (this.ignoreHover) return;
1806
- this.setActiveOption(e.currentTarget, false);
1807
- },
1808
-
1809
- /**
1810
- * Triggered when the user clicks on an option
1811
- * in the autocomplete dropdown menu.
1812
- *
1813
- * @param {object} e
1814
- * @returns {boolean}
1815
- */
1816
- onOptionSelect: function(e) {
1817
- var value, $target, $option, self = this;
1818
-
1819
- if (e.preventDefault) {
1820
- e.preventDefault();
1821
- e.stopPropagation();
1822
- }
1823
-
1824
- $target = $(e.currentTarget);
1825
- if ($target.hasClass('create')) {
1826
- self.createItem(null, function() {
1827
- if (self.settings.closeAfterSelect) {
1828
- self.close();
1829
- }
1830
- });
1831
- } else {
1832
- value = $target.attr('data-value');
1833
- if (typeof value !== 'undefined') {
1834
- self.lastQuery = null;
1835
- self.setTextboxValue('');
1836
- self.addItem(value);
1837
- if (self.settings.closeAfterSelect) {
1838
- self.close();
1839
- } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1840
- self.setActiveOption(self.getOption(value));
1841
- }
1842
- }
1843
- }
1844
- },
1845
-
1846
- /**
1847
- * Triggered when the user clicks on an item
1848
- * that has been selected.
1849
- *
1850
- * @param {object} e
1851
- * @returns {boolean}
1852
- */
1853
- onItemSelect: function(e) {
1854
- var self = this;
1855
-
1856
- if (self.isLocked) return;
1857
- if (self.settings.mode === 'multi') {
1858
- e.preventDefault();
1859
- self.setActiveItem(e.currentTarget, e);
1860
- }
1861
- },
1862
-
1863
- /**
1864
- * Invokes the provided method that provides
1865
- * results to a callback---which are then added
1866
- * as options to the control.
1867
- *
1868
- * @param {function} fn
1869
- */
1870
- load: function(fn) {
1871
- var self = this;
1872
- var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1873
-
1874
- self.loading++;
1875
- fn.apply(self, [function(results) {
1876
- self.loading = Math.max(self.loading - 1, 0);
1877
- if (results && results.length) {
1878
- self.addOption(results);
1879
- self.refreshOptions(self.isFocused && !self.isInputHidden);
1880
- }
1881
- if (!self.loading) {
1882
- $wrapper.removeClass(self.settings.loadingClass);
1883
- }
1884
- self.trigger('load', results);
1885
- }]);
1886
- },
1887
-
1888
- /**
1889
- * Sets the input field of the control to the specified value.
1890
- *
1891
- * @param {string} value
1892
- */
1893
- setTextboxValue: function(value) {
1894
- var $input = this.$control_input;
1895
- var changed = $input.val() !== value;
1896
- if (changed) {
1897
- $input.val(value).triggerHandler('update');
1898
- this.lastValue = value;
1899
- }
1900
- },
1901
-
1902
- /**
1903
- * Returns the value of the control. If multiple items
1904
- * can be selected (e.g. <select multiple>), this returns
1905
- * an array. If only one item can be selected, this
1906
- * returns a string.
1907
- *
1908
- * @returns {mixed}
1909
- */
1910
- getValue: function() {
1911
- if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1912
- return this.items;
1913
- } else {
1914
- return this.items.join(this.settings.delimiter);
1915
- }
1916
- },
1917
-
1918
- /**
1919
- * Resets the selected items to the given value.
1920
- *
1921
- * @param {mixed} value
1922
- */
1923
- setValue: function(value, silent) {
1924
- var events = silent ? [] : ['change'];
1925
-
1926
- debounce_events(this, events, function() {
1927
- this.clear(silent);
1928
- this.addItems(value, silent);
1929
- });
1930
- },
1931
-
1932
- /**
1933
- * Sets the selected item.
1934
- *
1935
- * @param {object} $item
1936
- * @param {object} e (optional)
1937
- */
1938
- setActiveItem: function($item, e) {
1939
- var self = this;
1940
- var eventName;
1941
- var i, idx, begin, end, item, swap;
1942
- var $last;
1943
-
1944
- if (self.settings.mode === 'single') return;
1945
- $item = $($item);
1946
-
1947
- // clear the active selection
1948
- if (!$item.length) {
1949
- $(self.$activeItems).removeClass('active');
1950
- self.$activeItems = [];
1951
- if (self.isFocused) {
1952
- self.showInput();
1953
- }
1954
- return;
1955
- }
1956
-
1957
- // modify selection
1958
- eventName = e && e.type.toLowerCase();
1959
-
1960
- if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1961
- $last = self.$control.children('.active:last');
1962
- begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1963
- end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1964
- if (begin > end) {
1965
- swap = begin;
1966
- begin = end;
1967
- end = swap;
1968
- }
1969
- for (i = begin; i <= end; i++) {
1970
- item = self.$control[0].childNodes[i];
1971
- if (self.$activeItems.indexOf(item) === -1) {
1972
- $(item).addClass('active');
1973
- self.$activeItems.push(item);
1974
- }
1975
- }
1976
- e.preventDefault();
1977
- } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1978
- if ($item.hasClass('active')) {
1979
- idx = self.$activeItems.indexOf($item[0]);
1980
- self.$activeItems.splice(idx, 1);
1981
- $item.removeClass('active');
1982
- } else {
1983
- self.$activeItems.push($item.addClass('active')[0]);
1984
- }
1985
- } else {
1986
- $(self.$activeItems).removeClass('active');
1987
- self.$activeItems = [$item.addClass('active')[0]];
1988
- }
1989
-
1990
- // ensure control has focus
1991
- self.hideInput();
1992
- if (!this.isFocused) {
1993
- self.focus();
1994
- }
1995
- },
1996
-
1997
- /**
1998
- * Sets the selected item in the dropdown menu
1999
- * of available options.
2000
- *
2001
- * @param {object} $object
2002
- * @param {boolean} scroll
2003
- * @param {boolean} animate
2004
- */
2005
- setActiveOption: function($option, scroll, animate) {
2006
- var height_menu, height_item, y;
2007
- var scroll_top, scroll_bottom;
2008
- var self = this;
2009
-
2010
- if (self.$activeOption) self.$activeOption.removeClass('active');
2011
- self.$activeOption = null;
2012
-
2013
- $option = $($option);
2014
- if (!$option.length) return;
2015
-
2016
- self.$activeOption = $option.addClass('active');
2017
-
2018
- if (scroll || !isset(scroll)) {
2019
-
2020
- height_menu = self.$dropdown_content.height();
2021
- height_item = self.$activeOption.outerHeight(true);
2022
- scroll = self.$dropdown_content.scrollTop() || 0;
2023
- y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
2024
- scroll_top = y;
2025
- scroll_bottom = y - height_menu + height_item;
2026
-
2027
- if (y + height_item > height_menu + scroll) {
2028
- self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
2029
- } else if (y < scroll) {
2030
- self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
2031
- }
2032
-
2033
- }
2034
- },
2035
-
2036
- /**
2037
- * Selects all items (CTRL + A).
2038
- */
2039
- selectAll: function() {
2040
- var self = this;
2041
- if (self.settings.mode === 'single') return;
2042
-
2043
- self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
2044
- if (self.$activeItems.length) {
2045
- self.hideInput();
2046
- self.close();
2047
- }
2048
- self.focus();
2049
- },
2050
-
2051
- /**
2052
- * Hides the input element out of view, while
2053
- * retaining its focus.
2054
- */
2055
- hideInput: function() {
2056
- var self = this;
2057
-
2058
- self.setTextboxValue('');
2059
- self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
2060
- self.isInputHidden = true;
2061
- },
2062
-
2063
- /**
2064
- * Restores input visibility.
2065
- */
2066
- showInput: function() {
2067
- this.$control_input.css({opacity: 1, position: 'relative', left: 0});
2068
- this.isInputHidden = false;
2069
- },
2070
-
2071
- /**
2072
- * Gives the control focus.
2073
- */
2074
- focus: function() {
2075
- var self = this;
2076
- if (self.isDisabled) return;
2077
-
2078
- self.ignoreFocus = true;
2079
- self.$control_input[0].focus();
2080
- window.setTimeout(function() {
2081
- self.ignoreFocus = false;
2082
- self.onFocus();
2083
- }, 0);
2084
- },
2085
-
2086
- /**
2087
- * Forces the control out of focus.
2088
- *
2089
- * @param {Element} dest
2090
- */
2091
- blur: function(dest) {
2092
- this.$control_input[0].blur();
2093
- this.onBlur(null, dest);
2094
- },
2095
-
2096
- /**
2097
- * Returns a function that scores an object
2098
- * to show how good of a match it is to the
2099
- * provided query.
2100
- *
2101
- * @param {string} query
2102
- * @param {object} options
2103
- * @return {function}
2104
- */
2105
- getScoreFunction: function(query) {
2106
- return this.sifter.getScoreFunction(query, this.getSearchOptions());
2107
- },
2108
-
2109
- /**
2110
- * Returns search options for sifter (the system
2111
- * for scoring and sorting results).
2112
- *
2113
- * @see https://github.com/brianreavis/sifter.js
2114
- * @return {object}
2115
- */
2116
- getSearchOptions: function() {
2117
- var settings = this.settings;
2118
- var sort = settings.sortField;
2119
- if (typeof sort === 'string') {
2120
- sort = [{field: sort}];
2121
- }
2122
-
2123
- return {
2124
- fields : settings.searchField,
2125
- conjunction : settings.searchConjunction,
2126
- sort : sort,
2127
- nesting : settings.nesting
2128
- };
2129
- },
2130
-
2131
- /**
2132
- * Searches through available options and returns
2133
- * a sorted array of matches.
2134
- *
2135
- * Returns an object containing:
2136
- *
2137
- * - query {string}
2138
- * - tokens {array}
2139
- * - total {int}
2140
- * - items {array}
2141
- *
2142
- * @param {string} query
2143
- * @returns {object}
2144
- */
2145
- search: function(query) {
2146
- var i, value, score, result, calculateScore;
2147
- var self = this;
2148
- var settings = self.settings;
2149
- var options = this.getSearchOptions();
2150
-
2151
- // validate user-provided result scoring function
2152
- if (settings.score) {
2153
- calculateScore = self.settings.score.apply(this, [query]);
2154
- if (typeof calculateScore !== 'function') {
2155
- throw new Error('Selectize "score" setting must be a function that returns a function');
2156
- }
2157
- }
2158
-
2159
- // perform search
2160
- if (query !== self.lastQuery) {
2161
- self.lastQuery = query;
2162
- result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
2163
- self.currentResults = result;
2164
- } else {
2165
- result = $.extend(true, {}, self.currentResults);
2166
- }
2167
-
2168
- // filter out selected items
2169
- if (settings.hideSelected) {
2170
- for (i = result.items.length - 1; i >= 0; i--) {
2171
- if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
2172
- result.items.splice(i, 1);
2173
- }
2174
- }
2175
- }
2176
-
2177
- return result;
2178
- },
2179
-
2180
- /**
2181
- * Refreshes the list of available options shown
2182
- * in the autocomplete dropdown menu.
2183
- *
2184
- * @param {boolean} triggerDropdown
2185
- */
2186
- refreshOptions: function(triggerDropdown) {
2187
- var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
2188
- var $active, $active_before, $create;
2189
-
2190
- if (typeof triggerDropdown === 'undefined') {
2191
- triggerDropdown = true;
2192
- }
2193
-
2194
- var self = this;
2195
- var query = $.trim(self.$control_input.val());
2196
- var results = self.search(query);
2197
- var $dropdown_content = self.$dropdown_content;
2198
- var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
2199
-
2200
- // build markup
2201
- n = results.items.length;
2202
- if (typeof self.settings.maxOptions === 'number') {
2203
- n = Math.min(n, self.settings.maxOptions);
2204
- }
2205
-
2206
- // render and group available options individually
2207
- groups = {};
2208
- groups_order = [];
2209
-
2210
- for (i = 0; i < n; i++) {
2211
- option = self.options[results.items[i].id];
2212
- option_html = self.render('option', option);
2213
- optgroup = option[self.settings.optgroupField] || '';
2214
- optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
2215
-
2216
- for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2217
- optgroup = optgroups[j];
2218
- if (!self.optgroups.hasOwnProperty(optgroup)) {
2219
- optgroup = '';
2220
- }
2221
- if (!groups.hasOwnProperty(optgroup)) {
2222
- groups[optgroup] = document.createDocumentFragment();
2223
- groups_order.push(optgroup);
2224
- }
2225
- groups[optgroup].appendChild(option_html);
2226
- }
2227
- }
2228
-
2229
- // sort optgroups
2230
- if (this.settings.lockOptgroupOrder) {
2231
- groups_order.sort(function(a, b) {
2232
- var a_order = self.optgroups[a].$order || 0;
2233
- var b_order = self.optgroups[b].$order || 0;
2234
- return a_order - b_order;
2235
- });
2236
- }
2237
-
2238
- // render optgroup headers & join groups
2239
- html = document.createDocumentFragment();
2240
- for (i = 0, n = groups_order.length; i < n; i++) {
2241
- optgroup = groups_order[i];
2242
- if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
2243
- // render the optgroup header and options within it,
2244
- // then pass it to the wrapper template
2245
- html_children = document.createDocumentFragment();
2246
- html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
2247
- html_children.appendChild(groups[optgroup]);
2248
-
2249
- html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
2250
- html: domToString(html_children),
2251
- dom: html_children
2252
- })));
2253
- } else {
2254
- html.appendChild(groups[optgroup]);
2255
- }
2256
- }
2257
-
2258
- $dropdown_content.html(html);
2259
-
2260
- // highlight matching terms inline
2261
- if (self.settings.highlight) {
2262
- $dropdown_content.removeHighlight();
2263
- if (results.query.length && results.tokens.length) {
2264
- for (i = 0, n = results.tokens.length; i < n; i++) {
2265
- highlight($dropdown_content, results.tokens[i].regex);
2266
- }
2267
- }
2268
- }
2269
-
2270
- // add "selected" class to selected options
2271
- if (!self.settings.hideSelected) {
2272
- for (i = 0, n = self.items.length; i < n; i++) {
2273
- self.getOption(self.items[i]).addClass('selected');
2274
- }
2275
- }
2276
-
2277
- // add create option
2278
- has_create_option = self.canCreate(query);
2279
- if (has_create_option) {
2280
- $dropdown_content.prepend(self.render('option_create', {input: query}));
2281
- $create = $($dropdown_content[0].childNodes[0]);
2282
- }
2283
-
2284
- // activate
2285
- self.hasOptions = results.items.length > 0 || has_create_option;
2286
- if (self.hasOptions) {
2287
- if (results.items.length > 0) {
2288
- $active_before = active_before && self.getOption(active_before);
2289
- if ($active_before && $active_before.length) {
2290
- $active = $active_before;
2291
- } else if (self.settings.mode === 'single' && self.items.length) {
2292
- $active = self.getOption(self.items[0]);
2293
- }
2294
- if (!$active || !$active.length) {
2295
- if ($create && !self.settings.addPrecedence) {
2296
- $active = self.getAdjacentOption($create, 1);
2297
- } else {
2298
- $active = $dropdown_content.find('[data-selectable]:first');
2299
- }
2300
- }
2301
- } else {
2302
- $active = $create;
2303
- }
2304
- self.setActiveOption($active);
2305
- if (triggerDropdown && !self.isOpen) { self.open(); }
2306
- } else {
2307
- self.setActiveOption(null);
2308
- if (triggerDropdown && self.isOpen) { self.close(); }
2309
- }
2310
- },
2311
-
2312
- /**
2313
- * Adds an available option. If it already exists,
2314
- * nothing will happen. Note: this does not refresh
2315
- * the options list dropdown (use `refreshOptions`
2316
- * for that).
2317
- *
2318
- * Usage:
2319
- *
2320
- * this.addOption(data)
2321
- *
2322
- * @param {object|array} data
2323
- */
2324
- addOption: function(data) {
2325
- var i, n, value, self = this;
2326
-
2327
- if ($.isArray(data)) {
2328
- for (i = 0, n = data.length; i < n; i++) {
2329
- self.addOption(data[i]);
2330
- }
2331
- return;
2332
- }
2333
-
2334
- if (value = self.registerOption(data)) {
2335
- self.userOptions[value] = true;
2336
- self.lastQuery = null;
2337
- self.trigger('option_add', value, data);
2338
- }
2339
- },
2340
-
2341
- /**
2342
- * Registers an option to the pool of options.
2343
- *
2344
- * @param {object} data
2345
- * @return {boolean|string}
2346
- */
2347
- registerOption: function(data) {
2348
- var key = hash_key(data[this.settings.valueField]);
2349
- if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false;
2350
- data.$order = data.$order || ++this.order;
2351
- this.options[key] = data;
2352
- return key;
2353
- },
2354
-
2355
- /**
2356
- * Registers an option group to the pool of option groups.
2357
- *
2358
- * @param {object} data
2359
- * @return {boolean|string}
2360
- */
2361
- registerOptionGroup: function(data) {
2362
- var key = hash_key(data[this.settings.optgroupValueField]);
2363
- if (!key) return false;
2364
-
2365
- data.$order = data.$order || ++this.order;
2366
- this.optgroups[key] = data;
2367
- return key;
2368
- },
2369
-
2370
- /**
2371
- * Registers a new optgroup for options
2372
- * to be bucketed into.
2373
- *
2374
- * @param {string} id
2375
- * @param {object} data
2376
- */
2377
- addOptionGroup: function(id, data) {
2378
- data[this.settings.optgroupValueField] = id;
2379
- if (id = this.registerOptionGroup(data)) {
2380
- this.trigger('optgroup_add', id, data);
2381
- }
2382
- },
2383
-
2384
- /**
2385
- * Removes an existing option group.
2386
- *
2387
- * @param {string} id
2388
- */
2389
- removeOptionGroup: function(id) {
2390
- if (this.optgroups.hasOwnProperty(id)) {
2391
- delete this.optgroups[id];
2392
- this.renderCache = {};
2393
- this.trigger('optgroup_remove', id);
2394
- }
2395
- },
2396
-
2397
- /**
2398
- * Clears all existing option groups.
2399
- */
2400
- clearOptionGroups: function() {
2401
- this.optgroups = {};
2402
- this.renderCache = {};
2403
- this.trigger('optgroup_clear');
2404
- },
2405
-
2406
- /**
2407
- * Updates an option available for selection. If
2408
- * it is visible in the selected items or options
2409
- * dropdown, it will be re-rendered automatically.
2410
- *
2411
- * @param {string} value
2412
- * @param {object} data
2413
- */
2414
- updateOption: function(value, data) {
2415
- var self = this;
2416
- var $item, $item_new;
2417
- var value_new, index_item, cache_items, cache_options, order_old;
2418
-
2419
- value = hash_key(value);
2420
- value_new = hash_key(data[self.settings.valueField]);
2421
-
2422
- // sanity checks
2423
- if (value === null) return;
2424
- if (!self.options.hasOwnProperty(value)) return;
2425
- if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
2426
-
2427
- order_old = self.options[value].$order;
2428
-
2429
- // update references
2430
- if (value_new !== value) {
2431
- delete self.options[value];
2432
- index_item = self.items.indexOf(value);
2433
- if (index_item !== -1) {
2434
- self.items.splice(index_item, 1, value_new);
2435
- }
2436
- }
2437
- data.$order = data.$order || order_old;
2438
- self.options[value_new] = data;
2439
-
2440
- // invalidate render cache
2441
- cache_items = self.renderCache['item'];
2442
- cache_options = self.renderCache['option'];
2443
-
2444
- if (cache_items) {
2445
- delete cache_items[value];
2446
- delete cache_items[value_new];
2447
- }
2448
- if (cache_options) {
2449
- delete cache_options[value];
2450
- delete cache_options[value_new];
2451
- }
2452
-
2453
- // update the item if it's selected
2454
- if (self.items.indexOf(value_new) !== -1) {
2455
- $item = self.getItem(value);
2456
- $item_new = $(self.render('item', data));
2457
- if ($item.hasClass('active')) $item_new.addClass('active');
2458
- $item.replaceWith($item_new);
2459
- }
2460
-
2461
- // invalidate last query because we might have updated the sortField
2462
- self.lastQuery = null;
2463
-
2464
- // update dropdown contents
2465
- if (self.isOpen) {
2466
- self.refreshOptions(false);
2467
- }
2468
- },
2469
-
2470
- /**
2471
- * Removes a single option.
2472
- *
2473
- * @param {string} value
2474
- * @param {boolean} silent
2475
- */
2476
- removeOption: function(value, silent) {
2477
- var self = this;
2478
- value = hash_key(value);
2479
-
2480
- var cache_items = self.renderCache['item'];
2481
- var cache_options = self.renderCache['option'];
2482
- if (cache_items) delete cache_items[value];
2483
- if (cache_options) delete cache_options[value];
2484
-
2485
- delete self.userOptions[value];
2486
- delete self.options[value];
2487
- self.lastQuery = null;
2488
- self.trigger('option_remove', value);
2489
- self.removeItem(value, silent);
2490
- },
2491
-
2492
- /**
2493
- * Clears all options.
2494
- */
2495
- clearOptions: function() {
2496
- var self = this;
2497
-
2498
- self.loadedSearches = {};
2499
- self.userOptions = {};
2500
- self.renderCache = {};
2501
- var options = self.options;
2502
- $.each(self.options, function(key, value) {
2503
- if(self.items.indexOf(key) == -1) {
2504
- delete options[key];
2505
- }
2506
- });
2507
- self.options = self.sifter.items = options;
2508
- self.lastQuery = null;
2509
- self.trigger('option_clear');
2510
- },
2511
-
2512
- /**
2513
- * Returns the jQuery element of the option
2514
- * matching the given value.
2515
- *
2516
- * @param {string} value
2517
- * @returns {object}
2518
- */
2519
- getOption: function(value) {
2520
- return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2521
- },
2522
-
2523
- /**
2524
- * Returns the jQuery element of the next or
2525
- * previous selectable option.
2526
- *
2527
- * @param {object} $option
2528
- * @param {int} direction can be 1 for next or -1 for previous
2529
- * @return {object}
2530
- */
2531
- getAdjacentOption: function($option, direction) {
2532
- var $options = this.$dropdown.find('[data-selectable]');
2533
- var index = $options.index($option) + direction;
2534
-
2535
- return index >= 0 && index < $options.length ? $options.eq(index) : $();
2536
- },
2537
-
2538
- /**
2539
- * Finds the first element with a "data-value" attribute
2540
- * that matches the given value.
2541
- *
2542
- * @param {mixed} value
2543
- * @param {object} $els
2544
- * @return {object}
2545
- */
2546
- getElementWithValue: function(value, $els) {
2547
- value = hash_key(value);
2548
-
2549
- if (typeof value !== 'undefined' && value !== null) {
2550
- for (var i = 0, n = $els.length; i < n; i++) {
2551
- if ($els[i].getAttribute('data-value') === value) {
2552
- return $($els[i]);
2553
- }
2554
- }
2555
- }
2556
-
2557
- return $();
2558
- },
2559
-
2560
- /**
2561
- * Returns the jQuery element of the item
2562
- * matching the given value.
2563
- *
2564
- * @param {string} value
2565
- * @returns {object}
2566
- */
2567
- getItem: function(value) {
2568
- return this.getElementWithValue(value, this.$control.children());
2569
- },
2570
-
2571
- /**
2572
- * "Selects" multiple items at once. Adds them to the list
2573
- * at the current caret position.
2574
- *
2575
- * @param {string} value
2576
- * @param {boolean} silent
2577
- */
2578
- addItems: function(values, silent) {
2579
- this.buffer = document.createDocumentFragment();
2580
-
2581
- var childNodes = this.$control[0].childNodes;
2582
- for (var i = 0; i < childNodes.length; i++) {
2583
- this.buffer.appendChild(childNodes[i]);
2584
- }
2585
-
2586
- var items = $.isArray(values) ? values : [values];
2587
- for (var i = 0, n = items.length; i < n; i++) {
2588
- this.isPending = (i < n - 1);
2589
- this.addItem(items[i], silent);
2590
- }
2591
-
2592
- var control = this.$control[0];
2593
- control.insertBefore(this.buffer, control.firstChild);
2594
-
2595
- this.buffer = null;
2596
- },
2597
-
2598
- /**
2599
- * "Selects" an item. Adds it to the list
2600
- * at the current caret position.
2601
- *
2602
- * @param {string} value
2603
- * @param {boolean} silent
2604
- */
2605
- addItem: function(value, silent) {
2606
- var events = silent ? [] : ['change'];
2607
-
2608
- debounce_events(this, events, function() {
2609
- var $item, $option, $options;
2610
- var self = this;
2611
- var inputMode = self.settings.mode;
2612
- var i, active, value_next, wasFull;
2613
- value = hash_key(value);
2614
-
2615
- if (self.items.indexOf(value) !== -1) {
2616
- if (inputMode === 'single') self.close();
2617
- return;
2618
- }
2619
-
2620
- if (!self.options.hasOwnProperty(value)) return;
2621
- if (inputMode === 'single') self.clear(silent);
2622
- if (inputMode === 'multi' && self.isFull()) return;
2623
-
2624
- $item = $(self.render('item', self.options[value]));
2625
- wasFull = self.isFull();
2626
- self.items.splice(self.caretPos, 0, value);
2627
- self.insertAtCaret($item);
2628
- if (!self.isPending || (!wasFull && self.isFull())) {
2629
- self.refreshState();
2630
- }
2631
-
2632
- if (self.isSetup) {
2633
- $options = self.$dropdown_content.find('[data-selectable]');
2634
-
2635
- // update menu / remove the option (if this is not one item being added as part of series)
2636
- if (!self.isPending) {
2637
- $option = self.getOption(value);
2638
- value_next = self.getAdjacentOption($option, 1).attr('data-value');
2639
- self.refreshOptions(self.isFocused && inputMode !== 'single');
2640
- if (value_next) {
2641
- self.setActiveOption(self.getOption(value_next));
2642
- }
2643
- }
2644
-
2645
- // hide the menu if the maximum number of items have been selected or no options are left
2646
- if (!$options.length || self.isFull()) {
2647
- self.close();
2648
- } else if (!self.isPending) {
2649
- self.positionDropdown();
2650
- }
2651
-
2652
- self.updatePlaceholder();
2653
- self.trigger('item_add', value, $item);
2654
-
2655
- if (!self.isPending) {
2656
- self.updateOriginalInput({silent: silent});
2657
- }
2658
- }
2659
- });
2660
- },
2661
-
2662
- /**
2663
- * Removes the selected item matching
2664
- * the provided value.
2665
- *
2666
- * @param {string} value
2667
- */
2668
- removeItem: function(value, silent) {
2669
- var self = this;
2670
- var $item, i, idx;
2671
-
2672
- $item = (value instanceof $) ? value : self.getItem(value);
2673
- value = hash_key($item.attr('data-value'));
2674
- i = self.items.indexOf(value);
2675
-
2676
- if (i !== -1) {
2677
- $item.remove();
2678
- if ($item.hasClass('active')) {
2679
- idx = self.$activeItems.indexOf($item[0]);
2680
- self.$activeItems.splice(idx, 1);
2681
- }
2682
-
2683
- self.items.splice(i, 1);
2684
- self.lastQuery = null;
2685
- if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2686
- self.removeOption(value, silent);
2687
- }
2688
-
2689
- if (i < self.caretPos) {
2690
- self.setCaret(self.caretPos - 1);
2691
- }
2692
-
2693
- self.refreshState();
2694
- self.updatePlaceholder();
2695
- self.updateOriginalInput({silent: silent});
2696
- self.positionDropdown();
2697
- self.trigger('item_remove', value, $item);
2698
- }
2699
- },
2700
-
2701
- /**
2702
- * Invokes the `create` method provided in the
2703
- * selectize options that should provide the data
2704
- * for the new item, given the user input.
2705
- *
2706
- * Once this completes, it will be added
2707
- * to the item list.
2708
- *
2709
- * @param {string} value
2710
- * @param {boolean} [triggerDropdown]
2711
- * @param {function} [callback]
2712
- * @return {boolean}
2713
- */
2714
- createItem: function(input, triggerDropdown) {
2715
- var self = this;
2716
- var caret = self.caretPos;
2717
- input = input || $.trim(self.$control_input.val() || '');
2718
-
2719
- var callback = arguments[arguments.length - 1];
2720
- if (typeof callback !== 'function') callback = function() {};
2721
-
2722
- if (typeof triggerDropdown !== 'boolean') {
2723
- triggerDropdown = true;
2724
- }
2725
-
2726
- if (!self.canCreate(input)) {
2727
- callback();
2728
- return false;
2729
- }
2730
-
2731
- self.lock();
2732
-
2733
- var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2734
- var data = {};
2735
- data[self.settings.labelField] = input;
2736
- data[self.settings.valueField] = input;
2737
- return data;
2738
- };
2739
-
2740
- var create = once(function(data) {
2741
- self.unlock();
2742
-
2743
- if (!data || typeof data !== 'object') return callback();
2744
- var value = hash_key(data[self.settings.valueField]);
2745
- if (typeof value !== 'string') return callback();
2746
-
2747
- self.setTextboxValue('');
2748
- self.addOption(data);
2749
- self.setCaret(caret);
2750
- self.addItem(value);
2751
- self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2752
- callback(data);
2753
- });
2754
-
2755
- var output = setup.apply(this, [input, create]);
2756
- if (typeof output !== 'undefined') {
2757
- create(output);
2758
- }
2759
-
2760
- return true;
2761
- },
2762
-
2763
- /**
2764
- * Re-renders the selected item lists.
2765
- */
2766
- refreshItems: function() {
2767
- this.lastQuery = null;
2768
-
2769
- if (this.isSetup) {
2770
- this.addItem(this.items);
2771
- }
2772
-
2773
- this.refreshState();
2774
- this.updateOriginalInput();
2775
- },
2776
-
2777
- /**
2778
- * Updates all state-dependent attributes
2779
- * and CSS classes.
2780
- */
2781
- refreshState: function() {
2782
- this.refreshValidityState();
2783
- this.refreshClasses();
2784
- },
2785
-
2786
- /**
2787
- * Update the `required` attribute of both input and control input.
2788
- *
2789
- * The `required` property needs to be activated on the control input
2790
- * for the error to be displayed at the right place. `required` also
2791
- * needs to be temporarily deactivated on the input since the input is
2792
- * hidden and can't show errors.
2793
- */
2794
- refreshValidityState: function() {
2795
- if (!this.isRequired) return false;
2796
-
2797
- var invalid = !this.items.length;
2798
-
2799
- this.isInvalid = invalid;
2800
- this.$control_input.prop('required', invalid);
2801
- this.$input.prop('required', !invalid);
2802
- },
2803
-
2804
- /**
2805
- * Updates all state-dependent CSS classes.
2806
- */
2807
- refreshClasses: function() {
2808
- var self = this;
2809
- var isFull = self.isFull();
2810
- var isLocked = self.isLocked;
2811
-
2812
- self.$wrapper
2813
- .toggleClass('rtl', self.rtl);
2814
-
2815
- self.$control
2816
- .toggleClass('focus', self.isFocused)
2817
- .toggleClass('disabled', self.isDisabled)
2818
- .toggleClass('required', self.isRequired)
2819
- .toggleClass('invalid', self.isInvalid)
2820
- .toggleClass('locked', isLocked)
2821
- .toggleClass('full', isFull).toggleClass('not-full', !isFull)
2822
- .toggleClass('input-active', self.isFocused && !self.isInputHidden)
2823
- .toggleClass('dropdown-active', self.isOpen)
2824
- .toggleClass('has-options', !$.isEmptyObject(self.options))
2825
- .toggleClass('has-items', self.items.length > 0);
2826
-
2827
- self.$control_input.data('grow', !isFull && !isLocked);
2828
- },
2829
-
2830
- /**
2831
- * Determines whether or not more items can be added
2832
- * to the control without exceeding the user-defined maximum.
2833
- *
2834
- * @returns {boolean}
2835
- */
2836
- isFull: function() {
2837
- return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2838
- },
2839
-
2840
- /**
2841
- * Refreshes the original <select> or <input>
2842
- * element to reflect the current state.
2843
- */
2844
- updateOriginalInput: function(opts) {
2845
- var i, n, options, label, self = this;
2846
- opts = opts || {};
2847
-
2848
- if (self.tagType === TAG_SELECT) {
2849
- options = [];
2850
- for (i = 0, n = self.items.length; i < n; i++) {
2851
- label = self.options[self.items[i]][self.settings.labelField] || '';
2852
- options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
2853
- }
2854
- if (!options.length && !this.$input.attr('multiple')) {
2855
- options.push('<option value="" selected="selected"></option>');
2856
- }
2857
- self.$input.html(options.join(''));
2858
- } else {
2859
- self.$input.val(self.getValue());
2860
- self.$input.attr('value',self.$input.val());
2861
- }
2862
-
2863
- if (self.isSetup) {
2864
- if (!opts.silent) {
2865
- self.trigger('change', self.$input.val());
2866
- }
2867
- }
2868
- },
2869
-
2870
- /**
2871
- * Shows/hide the input placeholder depending
2872
- * on if there items in the list already.
2873
- */
2874
- updatePlaceholder: function() {
2875
- if (!this.settings.placeholder) return;
2876
- var $input = this.$control_input;
2877
-
2878
- if (this.items.length) {
2879
- $input.removeAttr('placeholder');
2880
- } else {
2881
- $input.attr('placeholder', this.settings.placeholder);
2882
- }
2883
- $input.triggerHandler('update', {force: true});
2884
- },
2885
-
2886
- /**
2887
- * Shows the autocomplete dropdown containing
2888
- * the available options.
2889
- */
2890
- open: function() {
2891
- var self = this;
2892
-
2893
- if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
2894
- self.focus();
2895
- self.isOpen = true;
2896
- self.refreshState();
2897
- self.$dropdown.css({visibility: 'hidden', display: 'block'});
2898
- self.positionDropdown();
2899
- self.$dropdown.css({visibility: 'visible'});
2900
- self.trigger('dropdown_open', self.$dropdown);
2901
- },
2902
-
2903
- /**
2904
- * Closes the autocomplete dropdown menu.
2905
- */
2906
- close: function() {
2907
- var self = this;
2908
- var trigger = self.isOpen;
2909
-
2910
- if (self.settings.mode === 'single' && self.items.length) {
2911
- self.hideInput();
2912
-
2913
- // Do not trigger blur while inside a blur event,
2914
- // this fixes some weird tabbing behavior in FF and IE.
2915
- // See #1164
2916
- if (!self.isBlurring) {
2917
- self.$control_input.blur(); // close keyboard on iOS
2918
- }
2919
- }
2920
-
2921
- self.isOpen = false;
2922
- self.$dropdown.hide();
2923
- self.setActiveOption(null);
2924
- self.refreshState();
2925
-
2926
- if (trigger) self.trigger('dropdown_close', self.$dropdown);
2927
- },
2928
-
2929
- /**
2930
- * Calculates and applies the appropriate
2931
- * position of the dropdown.
2932
- */
2933
- positionDropdown: function() {
2934
- var $control = this.$control;
2935
- var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2936
- offset.top += $control.outerHeight(true);
2937
-
2938
- this.$dropdown.css({
2939
- width : $control[0].getBoundingClientRect().width,
2940
- top : offset.top,
2941
- left : offset.left
2942
- });
2943
- },
2944
-
2945
- /**
2946
- * Resets / clears all selected items
2947
- * from the control.
2948
- *
2949
- * @param {boolean} silent
2950
- */
2951
- clear: function(silent) {
2952
- var self = this;
2953
-
2954
- if (!self.items.length) return;
2955
- self.$control.children(':not(input)').remove();
2956
- self.items = [];
2957
- self.lastQuery = null;
2958
- self.setCaret(0);
2959
- self.setActiveItem(null);
2960
- self.updatePlaceholder();
2961
- self.updateOriginalInput({silent: silent});
2962
- self.refreshState();
2963
- self.showInput();
2964
- self.trigger('clear');
2965
- },
2966
-
2967
- /**
2968
- * A helper method for inserting an element
2969
- * at the current caret position.
2970
- *
2971
- * @param {object} $el
2972
- */
2973
- insertAtCaret: function($el) {
2974
- var caret = Math.min(this.caretPos, this.items.length);
2975
- var el = $el[0];
2976
- var target = this.buffer || this.$control[0];
2977
-
2978
- if (caret === 0) {
2979
- target.insertBefore(el, target.firstChild);
2980
- } else {
2981
- target.insertBefore(el, target.childNodes[caret]);
2982
- }
2983
-
2984
- this.setCaret(caret + 1);
2985
- },
2986
-
2987
- /**
2988
- * Removes the current selected item(s).
2989
- *
2990
- * @param {object} e (optional)
2991
- * @returns {boolean}
2992
- */
2993
- deleteSelection: function(e) {
2994
- var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
2995
- var self = this;
2996
-
2997
- direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
2998
- selection = getSelection(self.$control_input[0]);
2999
-
3000
- if (self.$activeOption && !self.settings.hideSelected) {
3001
- option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
3002
- }
3003
-
3004
- // determine items that will be removed
3005
- values = [];
3006
-
3007
- if (self.$activeItems.length) {
3008
- $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
3009
- caret = self.$control.children(':not(input)').index($tail);
3010
- if (direction > 0) { caret++; }
3011
-
3012
- for (i = 0, n = self.$activeItems.length; i < n; i++) {
3013
- values.push($(self.$activeItems[i]).attr('data-value'));
3014
- }
3015
- if (e) {
3016
- e.preventDefault();
3017
- e.stopPropagation();
3018
- }
3019
- } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
3020
- if (direction < 0 && selection.start === 0 && selection.length === 0) {
3021
- values.push(self.items[self.caretPos - 1]);
3022
- } else if (direction > 0 && selection.start === self.$control_input.val().length) {
3023
- values.push(self.items[self.caretPos]);
3024
- }
3025
- }
3026
-
3027
- // allow the callback to abort
3028
- if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
3029
- return false;
3030
- }
3031
-
3032
- // perform removal
3033
- if (typeof caret !== 'undefined') {
3034
- self.setCaret(caret);
3035
- }
3036
- while (values.length) {
3037
- self.removeItem(values.pop());
3038
- }
3039
-
3040
- self.showInput();
3041
- self.positionDropdown();
3042
- self.refreshOptions(true);
3043
-
3044
- // select previous option
3045
- if (option_select) {
3046
- $option_select = self.getOption(option_select);
3047
- if ($option_select.length) {
3048
- self.setActiveOption($option_select);
3049
- }
3050
- }
3051
-
3052
- return true;
3053
- },
3054
-
3055
- /**
3056
- * Selects the previous / next item (depending
3057
- * on the `direction` argument).
3058
- *
3059
- * > 0 - right
3060
- * < 0 - left
3061
- *
3062
- * @param {int} direction
3063
- * @param {object} e (optional)
3064
- */
3065
- advanceSelection: function(direction, e) {
3066
- var tail, selection, idx, valueLength, cursorAtEdge, $tail;
3067
- var self = this;
3068
-
3069
- if (direction === 0) return;
3070
- if (self.rtl) direction *= -1;
3071
-
3072
- tail = direction > 0 ? 'last' : 'first';
3073
- selection = getSelection(self.$control_input[0]);
3074
-
3075
- if (self.isFocused && !self.isInputHidden) {
3076
- valueLength = self.$control_input.val().length;
3077
- cursorAtEdge = direction < 0
3078
- ? selection.start === 0 && selection.length === 0
3079
- : selection.start === valueLength;
3080
-
3081
- if (cursorAtEdge && !valueLength) {
3082
- self.advanceCaret(direction, e);
3083
- }
3084
- } else {
3085
- $tail = self.$control.children('.active:' + tail);
3086
- if ($tail.length) {
3087
- idx = self.$control.children(':not(input)').index($tail);
3088
- self.setActiveItem(null);
3089
- self.setCaret(direction > 0 ? idx + 1 : idx);
3090
- }
3091
- }
3092
- },
3093
-
3094
- /**
3095
- * Moves the caret left / right.
3096
- *
3097
- * @param {int} direction
3098
- * @param {object} e (optional)
3099
- */
3100
- advanceCaret: function(direction, e) {
3101
- var self = this, fn, $adj;
3102
-
3103
- if (direction === 0) return;
3104
-
3105
- fn = direction > 0 ? 'next' : 'prev';
3106
- if (self.isShiftDown) {
3107
- $adj = self.$control_input[fn]();
3108
- if ($adj.length) {
3109
- self.hideInput();
3110
- self.setActiveItem($adj);
3111
- e && e.preventDefault();
3112
- }
3113
- } else {
3114
- self.setCaret(self.caretPos + direction);
3115
- }
3116
- },
3117
-
3118
- /**
3119
- * Moves the caret to the specified index.
3120
- *
3121
- * @param {int} i
3122
- */
3123
- setCaret: function(i) {
3124
- var self = this;
3125
-
3126
- if (self.settings.mode === 'single') {
3127
- i = self.items.length;
3128
- } else {
3129
- i = Math.max(0, Math.min(self.items.length, i));
3130
- }
3131
-
3132
- if(!self.isPending) {
3133
- // the input must be moved by leaving it in place and moving the
3134
- // siblings, due to the fact that focus cannot be restored once lost
3135
- // on mobile webkit devices
3136
- var j, n, fn, $children, $child;
3137
- $children = self.$control.children(':not(input)');
3138
- for (j = 0, n = $children.length; j < n; j++) {
3139
- $child = $($children[j]).detach();
3140
- if (j < i) {
3141
- self.$control_input.before($child);
3142
- } else {
3143
- self.$control.append($child);
3144
- }
3145
- }
3146
- }
3147
-
3148
- self.caretPos = i;
3149
- },
3150
-
3151
- /**
3152
- * Disables user input on the control. Used while
3153
- * items are being asynchronously created.
3154
- */
3155
- lock: function() {
3156
- this.close();
3157
- this.isLocked = true;
3158
- this.refreshState();
3159
- },
3160
-
3161
- /**
3162
- * Re-enables user input on the control.
3163
- */
3164
- unlock: function() {
3165
- this.isLocked = false;
3166
- this.refreshState();
3167
- },
3168
-
3169
- /**
3170
- * Disables user input on the control completely.
3171
- * While disabled, it cannot receive focus.
3172
- */
3173
- disable: function() {
3174
- var self = this;
3175
- self.$input.prop('disabled', true);
3176
- self.$control_input.prop('disabled', true).prop('tabindex', -1);
3177
- self.isDisabled = true;
3178
- self.lock();
3179
- },
3180
-
3181
- /**
3182
- * Enables the control so that it can respond
3183
- * to focus and user input.
3184
- */
3185
- enable: function() {
3186
- var self = this;
3187
- self.$input.prop('disabled', false);
3188
- self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
3189
- self.isDisabled = false;
3190
- self.unlock();
3191
- },
3192
-
3193
- /**
3194
- * Completely destroys the control and
3195
- * unbinds all event listeners so that it can
3196
- * be garbage collected.
3197
- */
3198
- destroy: function() {
3199
- var self = this;
3200
- var eventNS = self.eventNS;
3201
- var revertSettings = self.revertSettings;
3202
-
3203
- self.trigger('destroy');
3204
- self.off();
3205
- self.$wrapper.remove();
3206
- self.$dropdown.remove();
3207
-
3208
- self.$input
3209
- .html('')
3210
- .append(revertSettings.$children)
3211
- .removeAttr('tabindex')
3212
- .removeClass('selectized')
3213
- .attr({tabindex: revertSettings.tabindex})
3214
- .show();
3215
-
3216
- self.$control_input.removeData('grow');
3217
- self.$input.removeData('selectize');
3218
-
3219
- if (--Selectize.count == 0 && Selectize.$testInput) {
3220
- Selectize.$testInput.remove();
3221
- Selectize.$testInput = undefined;
3222
- }
3223
-
3224
- $(window).off(eventNS);
3225
- $(document).off(eventNS);
3226
- $(document.body).off(eventNS);
3227
-
3228
- delete self.$input[0].selectize;
3229
- },
3230
-
3231
- /**
3232
- * A helper method for rendering "item" and
3233
- * "option" templates, given the data.
3234
- *
3235
- * @param {string} templateName
3236
- * @param {object} data
3237
- * @returns {string}
3238
- */
3239
- render: function(templateName, data) {
3240
- var value, id, label;
3241
- var html = '';
3242
- var cache = false;
3243
- var self = this;
3244
- var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
3245
-
3246
- if (templateName === 'option' || templateName === 'item') {
3247
- value = hash_key(data[self.settings.valueField]);
3248
- cache = !!value;
3249
- }
3250
-
3251
- // pull markup from cache if it exists
3252
- if (cache) {
3253
- if (!isset(self.renderCache[templateName])) {
3254
- self.renderCache[templateName] = {};
3255
- }
3256
- if (self.renderCache[templateName].hasOwnProperty(value)) {
3257
- return self.renderCache[templateName][value];
3258
- }
3259
- }
3260
-
3261
- // render markup
3262
- html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
3263
-
3264
- // add mandatory attributes
3265
- if (templateName === 'option' || templateName === 'option_create') {
3266
- if (!data[self.settings.disabledField]) {
3267
- html.attr('data-selectable', '');
3268
- }
3269
- }
3270
- else if (templateName === 'optgroup') {
3271
- id = data[self.settings.optgroupValueField] || '';
3272
- html.attr('data-group', id);
3273
- if(data[self.settings.disabledField]) {
3274
- html.attr('data-disabled', '');
3275
- }
3276
- }
3277
- if (templateName === 'option' || templateName === 'item') {
3278
- html.attr('data-value', value || '');
3279
- }
3280
-
3281
- // update cache
3282
- if (cache) {
3283
- self.renderCache[templateName][value] = html[0];
3284
- }
3285
-
3286
- return html[0];
3287
- },
3288
-
3289
- /**
3290
- * Clears the render cache for a template. If
3291
- * no template is given, clears all render
3292
- * caches.
3293
- *
3294
- * @param {string} templateName
3295
- */
3296
- clearCache: function(templateName) {
3297
- var self = this;
3298
- if (typeof templateName === 'undefined') {
3299
- self.renderCache = {};
3300
- } else {
3301
- delete self.renderCache[templateName];
3302
- }
3303
- },
3304
-
3305
- /**
3306
- * Determines whether or not to display the
3307
- * create item prompt, given a user input.
3308
- *
3309
- * @param {string} input
3310
- * @return {boolean}
3311
- */
3312
- canCreate: function(input) {
3313
- var self = this;
3314
- if (!self.settings.create) return false;
3315
- var filter = self.settings.createFilter;
3316
- return input.length
3317
- && (typeof filter !== 'function' || filter.apply(self, [input]))
3318
- && (typeof filter !== 'string' || new RegExp(filter).test(input))
3319
- && (!(filter instanceof RegExp) || filter.test(input));
3320
- }
3321
-
3322
- });
3323
-
3324
-
3325
- Selectize.count = 0;
3326
- Selectize.defaults = {
3327
- options: [],
3328
- optgroups: [],
3329
-
3330
- plugins: [],
3331
- delimiter: ',',
3332
- splitOn: null, // regexp or string for splitting up values from a paste command
3333
- persist: true,
3334
- diacritics: true,
3335
- create: false,
3336
- createOnBlur: false,
3337
- createFilter: null,
3338
- highlight: true,
3339
- openOnFocus: true,
3340
- maxOptions: 1000,
3341
- maxItems: null,
3342
- hideSelected: null,
3343
- addPrecedence: false,
3344
- selectOnTab: false,
3345
- preload: false,
3346
- allowEmptyOption: false,
3347
- closeAfterSelect: false,
3348
-
3349
- scrollDuration: 60,
3350
- loadThrottle: 300,
3351
- loadingClass: 'loading',
3352
-
3353
- dataAttr: 'data-data',
3354
- optgroupField: 'optgroup',
3355
- valueField: 'value',
3356
- labelField: 'text',
3357
- disabledField: 'disabled',
3358
- optgroupLabelField: 'label',
3359
- optgroupValueField: 'value',
3360
- lockOptgroupOrder: false,
3361
-
3362
- sortField: '$order',
3363
- searchField: ['text'],
3364
- searchConjunction: 'and',
3365
-
3366
- mode: null,
3367
- wrapperClass: 'selectize-control',
3368
- inputClass: 'selectize-input',
3369
- dropdownClass: 'selectize-dropdown',
3370
- dropdownContentClass: 'selectize-dropdown-content',
3371
-
3372
- dropdownParent: null,
3373
-
3374
- copyClassesToDropdown: true,
3375
-
3376
- /*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3377
  load : null, // function(query, callback) { ... }
3378
  score : null, // function(search) { ... }
3379
  onInitialize : null, // function() { ... }
@@ -3392,500 +3422,516 @@
3392
  onType : null, // function(str) { ... }
3393
  onDelete : null, // function(values) { ... }
3394
  */
3395
-
3396
- render: {
3397
- /*
3398
  item: null,
3399
  optgroup: null,
3400
  optgroup_header: null,
3401
  option: null,
3402
  option_create: null
3403
  */
3404
- }
3405
- };
3406
-
3407
-
3408
- $.fn.selectize = function(settings_user) {
3409
- var defaults = $.fn.selectize.defaults;
3410
- var settings = $.extend({}, defaults, settings_user);
3411
- var attr_data = settings.dataAttr;
3412
- var field_label = settings.labelField;
3413
- var field_value = settings.valueField;
3414
- var field_disabled = settings.disabledField;
3415
- var field_optgroup = settings.optgroupField;
3416
- var field_optgroup_label = settings.optgroupLabelField;
3417
- var field_optgroup_value = settings.optgroupValueField;
3418
-
3419
- /**
3420
- * Initializes selectize from a <input type="text"> element.
3421
- *
3422
- * @param {object} $input
3423
- * @param {object} settings_element
3424
- */
3425
- var init_textbox = function($input, settings_element) {
3426
- var i, n, values, option;
3427
-
3428
- var data_raw = $input.attr(attr_data);
3429
-
3430
- if (!data_raw) {
3431
- var value = $.trim($input.val() || '');
3432
- if (!settings.allowEmptyOption && !value.length) return;
3433
- values = value.split(settings.delimiter);
3434
- for (i = 0, n = values.length; i < n; i++) {
3435
- option = {};
3436
- option[field_label] = values[i];
3437
- option[field_value] = values[i];
3438
- settings_element.options.push(option);
3439
- }
3440
- settings_element.items = values;
3441
- } else {
3442
- settings_element.options = JSON.parse(data_raw);
3443
- for (i = 0, n = settings_element.options.length; i < n; i++) {
3444
- settings_element.items.push(settings_element.options[i][field_value]);
3445
- }
3446
- }
3447
- };
3448
-
3449
- /**
3450
- * Initializes selectize from a <select> element.
3451
- *
3452
- * @param {object} $input
3453
- * @param {object} settings_element
3454
- */
3455
- var init_select = function($input, settings_element) {
3456
- var i, n, tagName, $children, order = 0;
3457
- var options = settings_element.options;
3458
- var optionsMap = {};
3459
-
3460
- var readData = function($el) {
3461
- var data = attr_data && $el.attr(attr_data);
3462
- if (typeof data === 'string' && data.length) {
3463
- return JSON.parse(data);
3464
- }
3465
- return null;
3466
- };
3467
-
3468
- var addOption = function($option, group) {
3469
- $option = $($option);
3470
-
3471
- var value = hash_key($option.val());
3472
- if (!value && !settings.allowEmptyOption) return;
3473
-
3474
- // if the option already exists, it's probably been
3475
- // duplicated in another optgroup. in this case, push
3476
- // the current group to the "optgroup" property on the
3477
- // existing option so that it's rendered in both places.
3478
- if (optionsMap.hasOwnProperty(value)) {
3479
- if (group) {
3480
- var arr = optionsMap[value][field_optgroup];
3481
- if (!arr) {
3482
- optionsMap[value][field_optgroup] = group;
3483
- } else if (!$.isArray(arr)) {
3484
- optionsMap[value][field_optgroup] = [arr, group];
3485
- } else {
3486
- arr.push(group);
3487
- }
3488
- }
3489
- return;
3490
- }
3491
-
3492
- var option = readData($option) || {};
3493
- option[field_label] = option[field_label] || $option.text();
3494
- option[field_value] = option[field_value] || value;
3495
- option[field_disabled] = option[field_disabled] || $option.prop('disabled');
3496
- option[field_optgroup] = option[field_optgroup] || group;
3497
-
3498
- optionsMap[value] = option;
3499
- options.push(option);
3500
-
3501
- if ($option.is(':selected')) {
3502
- settings_element.items.push(value);
3503
- }
3504
- };
3505
-
3506
- var addGroup = function($optgroup) {
3507
- var i, n, id, optgroup, $options;
3508
-
3509
- $optgroup = $($optgroup);
3510
- id = $optgroup.attr('label');
3511
-
3512
- if (id) {
3513
- optgroup = readData($optgroup) || {};
3514
- optgroup[field_optgroup_label] = id;
3515
- optgroup[field_optgroup_value] = id;
3516
- optgroup[field_disabled] = $optgroup.prop('disabled');
3517
- settings_element.optgroups.push(optgroup);
3518
- }
3519
-
3520
- $options = $('option', $optgroup);
3521
- for (i = 0, n = $options.length; i < n; i++) {
3522
- addOption($options[i], id);
3523
- }
3524
- };
3525
-
3526
- settings_element.maxItems = $input.attr('multiple') ? null : 1;
3527
-
3528
- $children = $input.children();
3529
- for (i = 0, n = $children.length; i < n; i++) {
3530
- tagName = $children[i].tagName.toLowerCase();
3531
- if (tagName === 'optgroup') {
3532
- addGroup($children[i]);
3533
- } else if (tagName === 'option') {
3534
- addOption($children[i]);
3535
- }
3536
- }
3537
- };
3538
-
3539
- return this.each(function() {
3540
- if (this.selectize) return;
3541
-
3542
- var instance;
3543
- var $input = $(this);
3544
- var tag_name = this.tagName.toLowerCase();
3545
- var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
3546
- if (!placeholder && !settings.allowEmptyOption) {
3547
- placeholder = $input.children('option[value=""]').text();
3548
- }
3549
-
3550
- var settings_element = {
3551
- 'placeholder' : placeholder,
3552
- 'options' : [],
3553
- 'optgroups' : [],
3554
- 'items' : []
3555
- };
3556
-
3557
- if (tag_name === 'select') {
3558
- init_select($input, settings_element);
3559
- } else {
3560
- init_textbox($input, settings_element);
3561
- }
3562
-
3563
- instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
3564
- });
3565
- };
3566
-
3567
- $.fn.selectize.defaults = Selectize.defaults;
3568
- $.fn.selectize.support = {
3569
- validity: SUPPORTS_VALIDITY_API
3570
- };
3571
-
3572
-
3573
- Selectize.define('drag_drop', function(options) {
3574
- if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
3575
- if (this.settings.mode !== 'multi') return;
3576
- var self = this;
3577
-
3578
- self.lock = (function() {
3579
- var original = self.lock;
3580
- return function() {
3581
- var sortable = self.$control.data('sortable');
3582
- if (sortable) sortable.disable();
3583
- return original.apply(self, arguments);
3584
- };
3585
- })();
3586
-
3587
- self.unlock = (function() {
3588
- var original = self.unlock;
3589
- return function() {
3590
- var sortable = self.$control.data('sortable');
3591
- if (sortable) sortable.enable();
3592
- return original.apply(self, arguments);
3593
- };
3594
- })();
3595
-
3596
- self.setup = (function() {
3597
- var original = self.setup;
3598
- return function() {
3599
- original.apply(this, arguments);
3600
-
3601
- var $control = self.$control.sortable({
3602
- items: '[data-value]',
3603
- forcePlaceholderSize: true,
3604
- disabled: self.isLocked,
3605
- start: function(e, ui) {
3606
- ui.placeholder.css('width', ui.helper.css('width'));
3607
- $control.css({overflow: 'visible'});
3608
- },
3609
- stop: function() {
3610
- $control.css({overflow: 'hidden'});
3611
- var active = self.$activeItems ? self.$activeItems.slice() : null;
3612
- var values = [];
3613
- $control.children('[data-value]').each(function() {
3614
- values.push($(this).attr('data-value'));
3615
- });
3616
- self.setValue(values);
3617
- self.setActiveItem(active);
3618
- }
3619
- });
3620
- };
3621
- })();
3622
-
3623
- });
3624
-
3625
- Selectize.define('dropdown_header', function(options) {
3626
- var self = this;
3627
-
3628
- options = $.extend({
3629
- title : 'Untitled',
3630
- headerClass : 'selectize-dropdown-header',
3631
- titleRowClass : 'selectize-dropdown-header-title',
3632
- labelClass : 'selectize-dropdown-header-label',
3633
- closeClass : 'selectize-dropdown-header-close',
3634
-
3635
- html: function(data) {
3636
- return (
3637
- '<div class="' + data.headerClass + '">' +
3638
- '<div class="' + data.titleRowClass + '">' +
3639
- '<span class="' + data.labelClass + '">' + data.title + '</span>' +
3640
- '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
3641
- '</div>' +
3642
- '</div>'
3643
- );
3644
- }
3645
- }, options);
3646
-
3647
- self.setup = (function() {
3648
- var original = self.setup;
3649
- return function() {
3650
- original.apply(self, arguments);
3651
- self.$dropdown_header = $(options.html(options));
3652
- self.$dropdown.prepend(self.$dropdown_header);
3653
- };
3654
- })();
3655
-
3656
- });
3657
-
3658
- Selectize.define('optgroup_columns', function(options) {
3659
- var self = this;
3660
-
3661
- options = $.extend({
3662
- equalizeWidth : true,
3663
- equalizeHeight : true
3664
- }, options);
3665
-
3666
- this.getAdjacentOption = function($option, direction) {
3667
- var $options = $option.closest('[data-group]').find('[data-selectable]');
3668
- var index = $options.index($option) + direction;
3669
-
3670
- return index >= 0 && index < $options.length ? $options.eq(index) : $();
3671
- };
3672
-
3673
- this.onKeyDown = (function() {
3674
- var original = self.onKeyDown;
3675
- return function(e) {
3676
- var index, $option, $options, $optgroup;
3677
-
3678
- if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
3679
- self.ignoreHover = true;
3680
- $optgroup = this.$activeOption.closest('[data-group]');
3681
- index = $optgroup.find('[data-selectable]').index(this.$activeOption);
3682
-
3683
- if(e.keyCode === KEY_LEFT) {
3684
- $optgroup = $optgroup.prev('[data-group]');
3685
- } else {
3686
- $optgroup = $optgroup.next('[data-group]');
3687
- }
3688
-
3689
- $options = $optgroup.find('[data-selectable]');
3690
- $option = $options.eq(Math.min($options.length - 1, index));
3691
- if ($option.length) {
3692
- this.setActiveOption($option);
3693
- }
3694
- return;
3695
- }
3696
-
3697
- return original.apply(this, arguments);
3698
- };
3699
- })();
3700
-
3701
- var getScrollbarWidth = function() {
3702
- var div;
3703
- var width = getScrollbarWidth.width;
3704
- var doc = document;
3705
-
3706
- if (typeof width === 'undefined') {
3707
- div = doc.createElement('div');
3708
- div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
3709
- div = div.firstChild;
3710
- doc.body.appendChild(div);
3711
- width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
3712
- doc.body.removeChild(div);
3713
- }
3714
- return width;
3715
- };
3716
-
3717
- var equalizeSizes = function() {
3718
- var i, n, height_max, width, width_last, width_parent, $optgroups;
3719
-
3720
- $optgroups = $('[data-group]', self.$dropdown_content);
3721
- n = $optgroups.length;
3722
- if (!n || !self.$dropdown_content.width()) return;
3723
-
3724
- if (options.equalizeHeight) {
3725
- height_max = 0;
3726
- for (i = 0; i < n; i++) {
3727
- height_max = Math.max(height_max, $optgroups.eq(i).height());
3728
- }
3729
- $optgroups.css({height: height_max});
3730
- }
3731
-
3732
- if (options.equalizeWidth) {
3733
- width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
3734
- width = Math.round(width_parent / n);
3735
- $optgroups.css({width: width});
3736
- if (n > 1) {
3737
- width_last = width_parent - width * (n - 1);
3738
- $optgroups.eq(n - 1).css({width: width_last});
3739
- }
3740
- }
3741
- };
3742
-
3743
- if (options.equalizeHeight || options.equalizeWidth) {
3744
- hook.after(this, 'positionDropdown', equalizeSizes);
3745
- hook.after(this, 'refreshOptions', equalizeSizes);
3746
- }
3747
-
3748
-
3749
- });
3750
-
3751
- Selectize.define('remove_button', function(options) {
3752
- options = $.extend({
3753
- label : '&times;',
3754
- title : 'Remove',
3755
- className : 'remove',
3756
- append : true
3757
- }, options);
3758
-
3759
- var singleClose = function(thisRef, options) {
3760
-
3761
- options.className = 'remove-single';
3762
-
3763
- var self = thisRef;
3764
- var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3765
-
3766
- /**
3767
- * Appends an element as a child (with raw HTML).
3768
- *
3769
- * @param {string} html_container
3770
- * @param {string} html_element
3771
- * @return {string}
3772
- */
3773
- var append = function(html_container, html_element) {
3774
- return $('<span>').append(html_container)
3775
- .append(html_element);
3776
- };
3777
-
3778
- thisRef.setup = (function() {
3779
- var original = self.setup;
3780
- return function() {
3781
- // override the item rendering method to add the button to each
3782
- if (options.append) {
3783
- var id = $(self.$input.context).attr('id');
3784
- var selectizer = $('#'+id);
3785
-
3786
- var render_item = self.settings.render.item;
3787
- self.settings.render.item = function(data) {
3788
- return append(render_item.apply(thisRef, arguments), html);
3789
- };
3790
- }
3791
-
3792
- original.apply(thisRef, arguments);
3793
-
3794
- // add event listener
3795
- thisRef.$control.on('click', '.' + options.className, function(e) {
3796
- e.preventDefault();
3797
- if (self.isLocked) return;
3798
-
3799
- self.clear();
3800
- });
3801
-
3802
- };
3803
- })();
3804
- };
3805
-
3806
- var multiClose = function(thisRef, options) {
3807
-
3808
- var self = thisRef;
3809
- var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3810
-
3811
- /**
3812
- * Appends an element as a child (with raw HTML).
3813
- *
3814
- * @param {string} html_container
3815
- * @param {string} html_element
3816
- * @return {string}
3817
- */
3818
- var append = function(html_container, html_element) {
3819
- var pos = html_container.search(/(<\/[^>]+>\s*)$/);
3820
- return html_container.substring(0, pos) + html_element + html_container.substring(pos);
3821
- };
3822
-
3823
- thisRef.setup = (function() {
3824
- var original = self.setup;
3825
- return function() {
3826
- // override the item rendering method to add the button to each
3827
- if (options.append) {
3828
- var render_item = self.settings.render.item;
3829
- self.settings.render.item = function(data) {
3830
- return append(render_item.apply(thisRef, arguments), html);
3831
- };
3832
- }
3833
-
3834
- original.apply(thisRef, arguments);
3835
-
3836
- // add event listener
3837
- thisRef.$control.on('click', '.' + options.className, function(e) {
3838
- e.preventDefault();
3839
- if (self.isLocked) return;
3840
-
3841
- var $item = $(e.currentTarget).parent();
3842
- self.setActiveItem($item);
3843
- if (self.deleteSelection()) {
3844
- self.setCaret(self.items.length);
3845
- }
3846
- });
3847
-
3848
- };
3849
- })();
3850
- };
3851
-
3852
- if (this.settings.mode === 'single') {
3853
- singleClose(this, options);
3854
- return;
3855
- } else {
3856
- multiClose(this, options);
3857
- }
3858
- });
3859
-
3860
-
3861
- Selectize.define('restore_on_backspace', function(options) {
3862
- var self = this;
3863
-
3864
- options.text = options.text || function(option) {
3865
- return option[this.settings.labelField];
3866
- };
3867
-
3868
- this.onKeyDown = (function() {
3869
- var original = self.onKeyDown;
3870
- return function(e) {
3871
- var index, option;
3872
- if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3873
- index = this.caretPos - 1;
3874
- if (index >= 0 && index < this.items.length) {
3875
- option = this.options[this.items[index]];
3876
- if (this.deleteSelection(e)) {
3877
- this.setTextboxValue(options.text.apply(this, [option]));
3878
- this.refreshOptions(true);
3879
- }
3880
- e.preventDefault();
3881
- return;
3882
- }
3883
- }
3884
- return original.apply(this, arguments);
3885
- };
3886
- })();
3887
- });
3888
-
3889
-
3890
- return Selectize;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3891
  }));
1
  /**
2
  * sifter.js
3
+ * Copyright (c) 2013–2020 Brian Reavis & contributors
4
  *
5
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6
  * file except in compliance with the License. You may obtain a copy of the License at:
15
  */
16
 
17
  (function(root, factory) {
18
+ if (typeof define === 'function' && define.amd) {
19
+ define(factory);
20
+ } else if (typeof exports === 'object') {
21
+ module.exports = factory();
22
+ } else {
23
+ root.Sifter = factory();
24
+ }
25
  }(this, function() {
26
 
27
+ /**
28
+ * Textually searches arrays and hashes of objects
29
+ * by property (or multiple properties). Designed
30
+ * specifically for autocomplete.
31
+ *
32
+ * @constructor
33
+ * @param {array|object} items
34
+ * @param {object} items
35
+ */
36
+ var Sifter = function(items, settings) {
37
+ this.items = items;
38
+ this.settings = settings || {diacritics: true};
39
+ };
40
+
41
+ /**
42
+ * Splits a search string into an array of individual
43
+ * regexps to be used to match results.
44
+ *
45
+ * @param {string} query
46
+ * @returns {array}
47
+ */
48
+ Sifter.prototype.tokenize = function(query, respect_word_boundaries) {
49
+ query = trim(String(query || '').toLowerCase());
50
+ if (!query || !query.length) return [];
51
+
52
+ var i, n, regex, letter;
53
+ var tokens = [];
54
+ var words = query.split(/ +/);
55
+
56
+ for (i = 0, n = words.length; i < n; i++) {
57
+ regex = escape_regex(words[i]);
58
+ if (this.settings.diacritics) {
59
+ for (letter in DIACRITICS) {
60
+ if (DIACRITICS.hasOwnProperty(letter)) {
61
+ regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
62
+ }
63
+ }
64
+ }
65
+ if (respect_word_boundaries) regex = "\\b"+regex
66
+ tokens.push({
67
+ string : words[i],
68
+ regex : new RegExp(regex, 'i')
69
+ });
70
+ }
71
+
72
+ return tokens;
73
+ };
74
+
75
+ /**
76
+ * Iterates over arrays and hashes.
77
+ *
78
+ * ```
79
+ * this.iterator(this.items, function(item, id) {
80
+ * // invoked for each item
81
+ * });
82
+ * ```
83
+ *
84
+ * @param {array|object} object
85
+ */
86
+ Sifter.prototype.iterator = function(object, callback) {
87
+ var iterator;
88
+ if (is_array(object)) {
89
+ iterator = Array.prototype.forEach || function(callback) {
90
+ for (var i = 0, n = this.length; i < n; i++) {
91
+ callback(this[i], i, this);
92
+ }
93
+ };
94
+ } else {
95
+ iterator = function(callback) {
96
+ for (var key in this) {
97
+ if (this.hasOwnProperty(key)) {
98
+ callback(this[key], key, this);
99
+ }
100
+ }
101
+ };
102
+ }
103
+
104
+ iterator.apply(object, [callback]);
105
+ };
106
+
107
+ /**
108
+ * Returns a function to be used to score individual results.
109
+ *
110
+ * Good matches will have a higher score than poor matches.
111
+ * If an item is not a match, 0 will be returned by the function.
112
+ *
113
+ * @param {object|string} search
114
+ * @param {object} options (optional)
115
+ * @returns {function}
116
+ */
117
+ Sifter.prototype.getScoreFunction = function(search, options) {
118
+ var self, fields, tokens, token_count, nesting;
119
+
120
+ self = this;
121
+ search = self.prepareSearch(search, options);
122
+ tokens = search.tokens;
123
+ fields = search.options.fields;
124
+ token_count = tokens.length;
125
+ nesting = search.options.nesting;
126
+
127
+ /**
128
+ * Calculates how close of a match the
129
+ * given value is against a search token.
130
+ *
131
+ * @param {mixed} value
132
+ * @param {object} token
133
+ * @return {number}
134
+ */
135
+ var scoreValue = function(value, token) {
136
+ var score, pos;
137
+
138
+ if (!value) return 0;
139
+ value = String(value || '');
140
+ pos = value.search(token.regex);
141
+ if (pos === -1) return 0;
142
+ score = token.string.length / value.length;
143
+ if (pos === 0) score += 0.5;
144
+ return score;
145
+ };
146
+
147
+ /**
148
+ * Calculates the score of an object
149
+ * against the search query.
150
+ *
151
+ * @param {object} token
152
+ * @param {object} data
153
+ * @return {number}
154
+ */
155
+ var scoreObject = (function() {
156
+ var field_count = fields.length;
157
+ if (!field_count) {
158
+ return function() { return 0; };
159
+ }
160
+ if (field_count === 1) {
161
+ return function(token, data) {
162
+ return scoreValue(getattr(data, fields[0], nesting), token);
163
+ };
164
+ }
165
+ return function(token, data) {
166
+ for (var i = 0, sum = 0; i < field_count; i++) {
167
+ sum += scoreValue(getattr(data, fields[i], nesting), token);
168
+ }
169
+ return sum / field_count;
170
+ };
171
+ })();
172
+
173
+ if (!token_count) {
174
+ return function() { return 0; };
175
+ }
176
+ if (token_count === 1) {
177
+ return function(data) {
178
+ return scoreObject(tokens[0], data);
179
+ };
180
+ }
181
+
182
+ if (search.options.conjunction === 'and') {
183
+ return function(data) {
184
+ var score;
185
+ for (var i = 0, sum = 0; i < token_count; i++) {
186
+ score = scoreObject(tokens[i], data);
187
+ if (score <= 0) return 0;
188
+ sum += score;
189
+ }
190
+ return sum / token_count;
191
+ };
192
+ } else {
193
+ return function(data) {
194
+ for (var i = 0, sum = 0; i < token_count; i++) {
195
+ sum += scoreObject(tokens[i], data);
196
+ }
197
+ return sum / token_count;
198
+ };
199
+ }
200
+ };
201
+
202
+ /**
203
+ * Returns a function that can be used to compare two
204
+ * results, for sorting purposes. If no sorting should
205
+ * be performed, `null` will be returned.
206
+ *
207
+ * @param {string|object} search
208
+ * @param {object} options
209
+ * @return function(a,b)
210
+ */
211
+ Sifter.prototype.getSortFunction = function(search, options) {
212
+ var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
213
+
214
+ self = this;
215
+ search = self.prepareSearch(search, options);
216
+ sort = (!search.query && options.sort_empty) || options.sort;
217
+
218
+ /**
219
+ * Fetches the specified sort field value
220
+ * from a search result item.
221
+ *
222
+ * @param {string} name
223
+ * @param {object} result
224
+ * @return {mixed}
225
+ */
226
+ get_field = function(name, result) {
227
+ if (name === '$score') return result.score;
228
+ return getattr(self.items[result.id], name, options.nesting);
229
+ };
230
+
231
+ // parse options
232
+ fields = [];
233
+ if (sort) {
234
+ for (i = 0, n = sort.length; i < n; i++) {
235
+ if (search.query || sort[i].field !== '$score') {
236
+ fields.push(sort[i]);
237
+ }
238
+ }
239
+ }
240
+
241
+ // the "$score" field is implied to be the primary
242
+ // sort field, unless it's manually specified
243
+ if (search.query) {
244
+ implicit_score = true;
245
+ for (i = 0, n = fields.length; i < n; i++) {
246
+ if (fields[i].field === '$score') {
247
+ implicit_score = false;
248
+ break;
249
+ }
250
+ }
251
+ if (implicit_score) {
252
+ fields.unshift({field: '$score', direction: 'desc'});
253
+ }
254
+ } else {
255
+ for (i = 0, n = fields.length; i < n; i++) {
256
+ if (fields[i].field === '$score') {
257
+ fields.splice(i, 1);
258
+ break;
259
+ }
260
+ }
261
+ }
262
+
263
+ multipliers = [];
264
+ for (i = 0, n = fields.length; i < n; i++) {
265
+ multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
266
+ }
267
+
268
+ // build function
269
+ fields_count = fields.length;
270
+ if (!fields_count) {
271
+ return null;
272
+ } else if (fields_count === 1) {
273
+ field = fields[0].field;
274
+ multiplier = multipliers[0];
275
+ return function(a, b) {
276
+ return multiplier * cmp(
277
+ get_field(field, a),
278
+ get_field(field, b)
279
+ );
280
+ };
281
+ } else {
282
+ return function(a, b) {
283
+ var i, result, a_value, b_value, field;
284
+ for (i = 0; i < fields_count; i++) {
285
+ field = fields[i].field;
286
+ result = multipliers[i] * cmp(
287
+ get_field(field, a),
288
+ get_field(field, b)
289
+ );
290
+ if (result) return result;
291
+ }
292
+ return 0;
293
+ };
294
+ }
295
+ };
296
+
297
+ /**
298
+ * Parses a search query and returns an object
299
+ * with tokens and fields ready to be populated
300
+ * with results.
301
+ *
302
+ * @param {string} query
303
+ * @param {object} options
304
+ * @returns {object}
305
+ */
306
+ Sifter.prototype.prepareSearch = function(query, options) {
307
+ if (typeof query === 'object') return query;
308
+
309
+ options = extend({}, options);
310
+
311
+ var option_fields = options.fields;
312
+ var option_sort = options.sort;
313
+ var option_sort_empty = options.sort_empty;
314
+
315
+ if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
316
+ if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
317
+ if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
318
+
319
+ return {
320
+ options : options,
321
+ query : String(query || '').toLowerCase(),
322
+ tokens : this.tokenize(query, options.respect_word_boundaries),
323
+ total : 0,
324
+ items : []
325
+ };
326
+ };
327
+
328
+ /**
329
+ * Searches through all items and returns a sorted array of matches.
330
+ *
331
+ * The `options` parameter can contain:
332
+ *
333
+ * - fields {string|array}
334
+ * - sort {array}
335
+ * - score {function}
336
+ * - filter {bool}
337
+ * - limit {integer}
338
+ *
339
+ * Returns an object containing:
340
+ *
341
+ * - options {object}
342
+ * - query {string}
343
+ * - tokens {array}
344
+ * - total {int}
345
+ * - items {array}
346
+ *
347
+ * @param {string} query
348
+ * @param {object} options
349
+ * @returns {object}
350
+ */
351
+ Sifter.prototype.search = function(query, options) {
352
+ var self = this, value, score, search, calculateScore;
353
+ var fn_sort;
354
+ var fn_score;
355
+
356
+ search = this.prepareSearch(query, options);
357
+ options = search.options;
358
+ query = search.query;
359
+
360
+ // generate result scoring function
361
+ fn_score = options.score || self.getScoreFunction(search);
362
+
363
+ // perform search and sort
364
+ if (query.length) {
365
+ self.iterator(self.items, function(item, id) {
366
+ score = fn_score(item);
367
+ if (options.filter === false || score > 0) {
368
+ search.items.push({'score': score, 'id': id});
369
+ }
370
+ });
371
+ } else {
372
+ self.iterator(self.items, function(item, id) {
373
+ search.items.push({'score': 1, 'id': id});
374
+ });
375
+ }
376
+
377
+ fn_sort = self.getSortFunction(search, options);
378
+ if (fn_sort) search.items.sort(fn_sort);
379
+
380
+ // apply limits
381
+ search.total = search.items.length;
382
+ if (typeof options.limit === 'number') {
383
+ search.items = search.items.slice(0, options.limit);
384
+ }
385
+
386
+ return search;
387
+ };
388
+
389
+ // utilities
390
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
391
+
392
+ var cmp = function(a, b) {
393
+ if (typeof a === 'number' && typeof b === 'number') {
394
+ return a > b ? 1 : (a < b ? -1 : 0);
395
+ }
396
+ a = asciifold(String(a || ''));
397
+ b = asciifold(String(b || ''));
398
+ if (a > b) return 1;
399
+ if (b > a) return -1;
400
+ return 0;
401
+ };
402
+
403
+ var extend = function(a, b) {
404
+ var i, n, k, object;
405
+ for (i = 1, n = arguments.length; i < n; i++) {
406
+ object = arguments[i];
407
+ if (!object) continue;
408
+ for (k in object) {
409
+ if (object.hasOwnProperty(k)) {
410
+ a[k] = object[k];
411
+ }
412
+ }
413
+ }
414
+ return a;
415
+ };
416
+
417
+ /**
418
+ * A property getter resolving dot-notation
419
+ * @param {Object} obj The root object to fetch property on
420
+ * @param {String} name The optionally dotted property name to fetch
421
+ * @param {Boolean} nesting Handle nesting or not
422
+ * @return {Object} The resolved property value
423
+ */
424
+ var getattr = function(obj, name, nesting) {
425
+ if (!obj || !name) return;
426
+ if (!nesting) return obj[name];
427
+ var names = name.split(".");
428
+ while(names.length && (obj = obj[names.shift()]));
429
+ return obj;
430
+ };
431
+
432
+ var trim = function(str) {
433
+ return (str + '').replace(/^\s+|\s+$|/g, '');
434
+ };
435
+
436
+ var escape_regex = function(str) {
437
+ return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
438
+ };
439
+
440
+ var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) {
441
+ return Object.prototype.toString.call(object) === '[object Array]';
442
+ };
443
+
444
+ var DIACRITICS = {
445
+ 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
446
+ 'b': '[b␢βΒB฿𐌁ᛒ]',
447
+ 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
448
+ 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
449
+ 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
450
+ 'f': '[fƑƒḞḟ]',
451
+ 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
452
+ 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
453
+ 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]',
454
+ 'j': '[jȷĴĵɈɉʝɟʲ]',
455
+ 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
456
+ 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
457
+ 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
458
+ 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
459
+ 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
460
+ 'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
461
+ 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
462
+ 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
463
+ 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
464
+ 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
465
+ 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
466
+ 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
467
+ 'x': '[xẌẍẊẋχ]',
468
+ 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
469
+ 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
470
+ };
471
+
472
+ var asciifold = (function() {
473
+ var i, n, k, chunk;
474
+ var foreignletters = '';
475
+ var lookup = {};
476
+ for (k in DIACRITICS) {
477
+ if (DIACRITICS.hasOwnProperty(k)) {
478
+ chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
479
+ foreignletters += chunk;
480
+ for (i = 0, n = chunk.length; i < n; i++) {
481
+ lookup[chunk.charAt(i)] = k;
482
+ }
483
+ }
484
+ }
485
+ var regexp = new RegExp('[' + foreignletters + ']', 'g');
486
+ return function(str) {
487
+ return str.replace(regexp, function(foreignletter) {
488
+ return lookup[foreignletter];
489
+ }).toLowerCase();
490
+ };
491
+ })();
492
+
493
+
494
+ // export
495
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
496
+
497
+ return Sifter;
498
  }));
499
 
 
 
500
  /**
501
  * microplugin.js
502
  * Copyright (c) 2013 Brian Reavis & contributors
514
  */
515
 
516
  (function(root, factory) {
517
+ if (typeof define === 'function' && define.amd) {
518
+ define(factory);
519
+ } else if (typeof exports === 'object') {
520
+ module.exports = factory();
521
+ } else {
522
+ root.MicroPlugin = factory();
523
+ }
524
  }(this, function() {
525
+ var MicroPlugin = {};
526
+
527
+ MicroPlugin.mixin = function(Interface) {
528
+ Interface.plugins = {};
529
+
530
+ /**
531
+ * Initializes the listed plugins (with options).
532
+ * Acceptable formats:
533
+ *
534
+ * List (without options):
535
+ * ['a', 'b', 'c']
536
+ *
537
+ * List (with options):
538
+ * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
539
+ *
540
+ * Hash (with options):
541
+ * {'a': { ... }, 'b': { ... }, 'c': { ... }}
542
+ *
543
+ * @param {mixed} plugins
544
+ */
545
+ Interface.prototype.initializePlugins = function(plugins) {
546
+ var i, n, key;
547
+ var self = this;
548
+ var queue = [];
549
+
550
+ self.plugins = {
551
+ names : [],
552
+ settings : {},
553
+ requested : {},
554
+ loaded : {}
555
+ };
556
+
557
+ if (utils.isArray(plugins)) {
558
+ for (i = 0, n = plugins.length; i < n; i++) {
559
+ if (typeof plugins[i] === 'string') {
560
+ queue.push(plugins[i]);
561
+ } else {
562
+ self.plugins.settings[plugins[i].name] = plugins[i].options;
563
+ queue.push(plugins[i].name);
564
+ }
565
+ }
566
+ } else if (plugins) {
567
+ for (key in plugins) {
568
+ if (plugins.hasOwnProperty(key)) {
569
+ self.plugins.settings[key] = plugins[key];
570
+ queue.push(key);
571
+ }
572
+ }
573
+ }
574
+
575
+ while (queue.length) {
576
+ self.require(queue.shift());
577
+ }
578
+ };
579
+
580
+ Interface.prototype.loadPlugin = function(name) {
581
+ var self = this;
582
+ var plugins = self.plugins;
583
+ var plugin = Interface.plugins[name];
584
+
585
+ if (!Interface.plugins.hasOwnProperty(name)) {
586
+ throw new Error('Unable to find "' + name + '" plugin');
587
+ }
588
+
589
+ plugins.requested[name] = true;
590
+ plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
591
+ plugins.names.push(name);
592
+ };
593
+
594
+ /**
595
+ * Initializes a plugin.
596
+ *
597
+ * @param {string} name
598
+ */
599
+ Interface.prototype.require = function(name) {
600
+ var self = this;
601
+ var plugins = self.plugins;
602
+
603
+ if (!self.plugins.loaded.hasOwnProperty(name)) {
604
+ if (plugins.requested[name]) {
605
+ throw new Error('Plugin has circular dependency ("' + name + '")');
606
+ }
607
+ self.loadPlugin(name);
608
+ }
609
+
610
+ return plugins.loaded[name];
611
+ };
612
+
613
+ /**
614
+ * Registers a plugin.
615
+ *
616
+ * @param {string} name
617
+ * @param {function} fn
618
+ */
619
+ Interface.define = function(name, fn) {
620
+ Interface.plugins[name] = {
621
+ 'name' : name,
622
+ 'fn' : fn
623
+ };
624
+ };
625
+ };
626
+
627
+ var utils = {
628
+ isArray: Array.isArray || function(vArg) {
629
+ return Object.prototype.toString.call(vArg) === '[object Array]';
630
+ }
631
+ };
632
+
633
+ return MicroPlugin;
634
  }));
635
 
636
  /**
637
+ * selectize.js (v0.13.0)
638
  * Copyright (c) 2013–2015 Brian Reavis & contributors
639
+ * Copyright (c) 2020 Selectize Team & contributors
640
  *
641
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
642
  * file except in compliance with the License. You may obtain a copy of the License at:
648
  * governing permissions and limitations under the License.
649
  *
650
  * @author Brian Reavis <brian@thirdroute.com>
651
+ * @author Ris Adams <selectize@risadams.com>
652
  */
653
 
654
  /*jshint curly:false */
655
  /*jshint browser:true */
656
 
657
  (function(root, factory) {
658
+ if (typeof define === 'function' && define.amd) {
659
+ define(['jquery','sifter','microplugin'], factory);
660
+ } else if (typeof module === 'object' && typeof module.exports === 'object') {
661
+ module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
662
+ } else {
663
+ root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
664
+ }
665
  }(this, function($, Sifter, MicroPlugin) {
666
+ 'use strict';
667
+
668
+ var highlight = function($element, pattern) {
669
+ if (typeof pattern === 'string' && !pattern.length) return;
670
+ var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
671
+
672
+ var highlight = function(node) {
673
+ var skip = 0;
674
+ // Wrap matching part of text node with highlighting <span>, e.g.
675
+ // Soccer -> <span class="highlight">Soc</span>cer for regex = /soc/i
676
+ if (node.nodeType === 3) {
677
+ var pos = node.data.search(regex);
678
+ if (pos >= 0 && node.data.length > 0) {
679
+ var match = node.data.match(regex);
680
+ var spannode = document.createElement('span');
681
+ spannode.className = 'highlight';
682
+ var middlebit = node.splitText(pos);
683
+ var endbit = middlebit.splitText(match[0].length);
684
+ var middleclone = middlebit.cloneNode(true);
685
+ spannode.appendChild(middleclone);
686
+ middlebit.parentNode.replaceChild(spannode, middlebit);
687
+ skip = 1;
688
+ }
689
+ }
690
+ // Recurse element node, looking for child text nodes to highlight, unless element
691
+ // is childless, <script>, <style>, or already highlighted: <span class="highlight">
692
+ else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && ( node.className !== 'highlight' || node.tagName !== 'SPAN' )) {
693
+ for (var i = 0; i < node.childNodes.length; ++i) {
694
+ i += highlight(node.childNodes[i]);
695
+ }
696
+ }
697
+ return skip;
698
+ };
699
+
700
+ return $element.each(function() {
701
+ highlight(this);
702
+ });
703
+ };
704
+
705
+ /**
706
+ * removeHighlight fn copied from highlight v5 and
707
+ * edited to remove with() and pass js strict mode
708
+ */
709
+ $.fn.removeHighlight = function() {
710
+ return this.find("span.highlight").each(function() {
711
+ this.parentNode.firstChild.nodeName;
712
+ var parent = this.parentNode;
713
+ parent.replaceChild(this.firstChild, this);
714
+ parent.normalize();
715
+ }).end();
716
+ };
717
+
718
+
719
+ var MicroEvent = function() {};
720
+ MicroEvent.prototype = {
721
+ on: function(event, fct){
722
+ this._events = this._events || {};
723
+ this._events[event] = this._events[event] || [];
724
+ this._events[event].push(fct);
725
+ },
726
+ off: function(event, fct){
727
+ var n = arguments.length;
728
+ if (n === 0) return delete this._events;
729
+ if (n === 1) return delete this._events[event];
730
+
731
+ this._events = this._events || {};
732
+ if (event in this._events === false) return;
733
+ this._events[event].splice(this._events[event].indexOf(fct), 1);
734
+ },
735
+ trigger: function(event /* , args... */){
736
+ this._events = this._events || {};
737
+ if (event in this._events === false) return;
738
+ for (var i = 0; i < this._events[event].length; i++){
739
+ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
740
+ }
741
+ }
742
+ };
743
+
744
+ /**
745
+ * Mixin will delegate all MicroEvent.js function in the destination object.
746
+ *
747
+ * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
748
+ *
749
+ * @param {object} the object which will support MicroEvent
750
+ */
751
+ MicroEvent.mixin = function(destObject){
752
+ var props = ['on', 'off', 'trigger'];
753
+ for (var i = 0; i < props.length; i++){
754
+ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
755
+ }
756
+ };
757
+
758
+ var IS_MAC = /Mac/.test(navigator.userAgent);
759
+
760
+ var KEY_A = 65;
761
+ var KEY_COMMA = 188;
762
+ var KEY_RETURN = 13;
763
+ var KEY_ESC = 27;
764
+ var KEY_LEFT = 37;
765
+ var KEY_UP = 38;
766
+ var KEY_P = 80;
767
+ var KEY_RIGHT = 39;
768
+ var KEY_DOWN = 40;
769
+ var KEY_N = 78;
770
+ var KEY_BACKSPACE = 8;
771
+ var KEY_DELETE = 46;
772
+ var KEY_SHIFT = 16;
773
+ var KEY_CMD = IS_MAC ? 91 : 17;
774
+ var KEY_CTRL = IS_MAC ? 18 : 17;
775
+ var KEY_TAB = 9;
776
+
777
+ var TAG_SELECT = 1;
778
+ var TAG_INPUT = 2;
779
+
780
+ // for now, android support in general is too spotty to support validity
781
+ var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity;
782
+
783
+
784
+ var isset = function(object) {
785
+ return typeof object !== 'undefined';
786
+ };
787
+
788
+ /**
789
+ * Converts a scalar to its best string representation
790
+ * for hash keys and HTML attribute values.
791
+ *
792
+ * Transformations:
793
+ * 'str' -> 'str'
794
+ * null -> ''
795
+ * undefined -> ''
796
+ * true -> '1'
797
+ * false -> '0'
798
+ * 0 -> '0'
799
+ * 1 -> '1'
800
+ *
801
+ * @param {string} value
802
+ * @returns {string|null}
803
+ */
804
+ var hash_key = function(value) {
805
+ if (typeof value === 'undefined' || value === null) return null;
806
+ if (typeof value === 'boolean') return value ? '1' : '0';
807
+ return value + '';
808
+ };
809
+
810
+ /**
811
+ * Escapes a string for use within HTML.
812
+ *
813
+ * @param {string} str
814
+ * @returns {string}
815
+ */
816
+ var escape_html = function(str) {
817
+ return (str + '')
818
+ .replace(/&/g, '&amp;')
819
+ .replace(/</g, '&lt;')
820
+ .replace(/>/g, '&gt;')
821
+ .replace(/"/g, '&quot;');
822
+ };
823
+
824
+ /**
825
+ * Escapes "$" characters in replacement strings.
826
+ *
827
+ * @param {string} str
828
+ * @returns {string}
829
+ */
830
+ var escape_replace = function(str) {
831
+ return (str + '').replace(/\$/g, '$$$$');
832
+ };
833
+
834
+ var hook = {};
835
+
836
+ /**
837
+ * Wraps `method` on `self` so that `fn`
838
+ * is invoked before the original method.
839
+ *
840
+ * @param {object} self
841
+ * @param {string} method
842
+ * @param {function} fn
843
+ */
844
+ hook.before = function(self, method, fn) {
845
+ var original = self[method];
846
+ self[method] = function() {
847
+ fn.apply(self, arguments);
848
+ return original.apply(self, arguments);
849
+ };
850
+ };
851
+
852
+ /**
853
+ * Wraps `method` on `self` so that `fn`
854
+ * is invoked after the original method.
855
+ *
856
+ * @param {object} self
857
+ * @param {string} method
858
+ * @param {function} fn
859
+ */
860
+ hook.after = function(self, method, fn) {
861
+ var original = self[method];
862
+ self[method] = function() {
863
+ var result = original.apply(self, arguments);
864
+ fn.apply(self, arguments);
865
+ return result;
866
+ };
867
+ };
868
+
869
+ /**
870
+ * Wraps `fn` so that it can only be invoked once.
871
+ *
872
+ * @param {function} fn
873
+ * @returns {function}
874
+ */
875
+ var once = function(fn) {
876
+ var called = false;
877
+ return function() {
878
+ if (called) return;
879
+ called = true;
880
+ fn.apply(this, arguments);
881
+ };
882
+ };
883
+
884
+ /**
885
+ * Wraps `fn` so that it can only be called once
886
+ * every `delay` milliseconds (invoked on the falling edge).
887
+ *
888
+ * @param {function} fn
889
+ * @param {int} delay
890
+ * @returns {function}
891
+ */
892
+ var debounce = function(fn, delay) {
893
+ var timeout;
894
+ return function() {
895
+ var self = this;
896
+ var args = arguments;
897
+ window.clearTimeout(timeout);
898
+ timeout = window.setTimeout(function() {
899
+ fn.apply(self, args);
900
+ }, delay);
901
+ };
902
+ };
903
+
904
+ /**
905
+ * Debounce all fired events types listed in `types`
906
+ * while executing the provided `fn`.
907
+ *
908
+ * @param {object} self
909
+ * @param {array} types
910
+ * @param {function} fn
911
+ */
912
+ var debounce_events = function(self, types, fn) {
913
+ var type;
914
+ var trigger = self.trigger;
915
+ var event_args = {};
916
+
917
+ // override trigger method
918
+ self.trigger = function() {
919
+ var type = arguments[0];
920
+ if (types.indexOf(type) !== -1) {
921
+ event_args[type] = arguments;
922
+ } else {
923
+ return trigger.apply(self, arguments);
924
+ }
925
+ };
926
+
927
+ // invoke provided function
928
+ fn.apply(self, []);
929
+ self.trigger = trigger;
930
+
931
+ // trigger queued events
932
+ for (type in event_args) {
933
+ if (event_args.hasOwnProperty(type)) {
934
+ trigger.apply(self, event_args[type]);
935
+ }
936
+ }
937
+ };
938
+
939
+ /**
940
+ * A workaround for http://bugs.jquery.com/ticket/6696
941
+ *
942
+ * @param {object} $parent - Parent element to listen on.
943
+ * @param {string} event - Event name.
944
+ * @param {string} selector - Descendant selector to filter by.
945
+ * @param {function} fn - Event handler.
946
+ */
947
+ var watchChildEvent = function($parent, event, selector, fn) {
948
+ $parent.on(event, selector, function(e) {
949
+ var child = e.target;
950
+ while (child && child.parentNode !== $parent[0]) {
951
+ child = child.parentNode;
952
+ }
953
+ e.currentTarget = child;
954
+ return fn.apply(this, [e]);
955
+ });
956
+ };
957
+
958
+ /**
959
+ * Determines the current selection within a text input control.
960
+ * Returns an object containing:
961
+ * - start
962
+ * - length
963
+ *
964
+ * @param {object} input
965
+ * @returns {object}
966
+ */
967
+ var getSelection = function(input) {
968
+ var result = {};
969
+ if ('selectionStart' in input) {
970
+ result.start = input.selectionStart;
971
+ result.length = input.selectionEnd - result.start;
972
+ } else if (document.selection) {
973
+ input.focus();
974
+ var sel = document.selection.createRange();
975
+ var selLen = document.selection.createRange().text.length;
976
+ sel.moveStart('character', -input.value.length);
977
+ result.start = sel.text.length - selLen;
978
+ result.length = selLen;
979
+ }
980
+ return result;
981
+ };
982
+
983
+ /**
984
+ * Copies CSS properties from one element to another.
985
+ *
986
+ * @param {object} $from
987
+ * @param {object} $to
988
+ * @param {array} properties
989
+ */
990
+ var transferStyles = function($from, $to, properties) {
991
+ var i, n, styles = {};
992
+ if (properties) {
993
+ for (i = 0, n = properties.length; i < n; i++) {
994
+ styles[properties[i]] = $from.css(properties[i]);
995
+ }
996
+ } else {
997
+ styles = $from.css();
998
+ }
999
+ $to.css(styles);
1000
+ };
1001
+
1002
+ /**
1003
+ * Measures the width of a string within a
1004
+ * parent element (in pixels).
1005
+ *
1006
+ * @param {string} str
1007
+ * @param {object} $parent
1008
+ * @returns {int}
1009
+ */
1010
+ var measureString = function(str, $parent) {
1011
+ if (!str) {
1012
+ return 0;
1013
+ }
1014
+
1015
+ if (!Selectize.$testInput) {
1016
+ Selectize.$testInput = $('<span />').css({
1017
+ position: 'absolute',
1018
+ width: 'auto',
1019
+ padding: 0,
1020
+ whiteSpace: 'pre'
1021
+ });
1022
+
1023
+ $('<div />').css({
1024
+ position: 'absolute',
1025
+ width: 0,
1026
+ height: 0,
1027
+ overflow: 'hidden'
1028
+ }).append(Selectize.$testInput).appendTo('body');
1029
+ }
1030
+
1031
+ Selectize.$testInput.text(str);
1032
+
1033
+ transferStyles($parent, Selectize.$testInput, [
1034
+ 'letterSpacing',
1035
+ 'fontSize',
1036
+ 'fontFamily',
1037
+ 'fontWeight',
1038
+ 'textTransform'
1039
+ ]);
1040
+
1041
+ return Selectize.$testInput.width();
1042
+ };
1043
+
1044
+ /**
1045
+ * Sets up an input to grow horizontally as the user
1046
+ * types. If the value is changed manually, you can
1047
+ * trigger the "update" handler to resize:
1048
+ *
1049
+ * $input.trigger('update');
1050
+ *
1051
+ * @param {object} $input
1052
+ */
1053
+ var autoGrow = function($input) {
1054
+ var currentWidth = null;
1055
+
1056
+ var update = function(e, options) {
1057
+ var value, keyCode, printable, placeholder, width;
1058
+ var shift, character, selection;
1059
+ e = e || window.event || {};
1060
+ options = options || {};
1061
+
1062
+ if (e.metaKey || e.altKey) return;
1063
+ if (!options.force && $input.data('grow') === false) return;
1064
+
1065
+ value = $input.val();
1066
+ if (e.type && e.type.toLowerCase() === 'keydown') {
1067
+ keyCode = e.keyCode;
1068
+ printable = (
1069
+ (keyCode >= 48 && keyCode <= 57) || // 0-9
1070
+ (keyCode >= 65 && keyCode <= 90) || // a-z
1071
+ (keyCode >= 96 && keyCode <= 111) || // numpad 0-9, numeric operators
1072
+ (keyCode >= 186 && keyCode <= 222) || // semicolon, equal, comma, dash, etc.
1073
+ keyCode === 32 // space
1074
+ );
1075
+
1076
+ if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
1077
+ selection = getSelection($input[0]);
1078
+ if (selection.length) {
1079
+ value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
1080
+ } else if (keyCode === KEY_BACKSPACE && selection.start) {
1081
+ value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
1082
+ } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
1083
+ value = value.substring(0, selection.start) + value.substring(selection.start + 1);
1084
+ }
1085
+ } else if (printable) {
1086
+ shift = e.shiftKey;
1087
+ character = String.fromCharCode(e.keyCode);
1088
+ if (shift) character = character.toUpperCase();
1089
+ else character = character.toLowerCase();
1090
+ value += character;
1091
+ }
1092
+ }
1093
+
1094
+ placeholder = $input.attr('placeholder');
1095
+ if (!value && placeholder) {
1096
+ value = placeholder;
1097
+ }
1098
+
1099
+ width = measureString(value, $input) + 4;
1100
+ if (width !== currentWidth) {
1101
+ currentWidth = width;
1102
+ $input.width(width);
1103
+ $input.triggerHandler('resize');
1104
+ }
1105
+ };
1106
+
1107
+ $input.on('keydown keyup update blur', update);
1108
+ update();
1109
+ };
1110
+
1111
+ var domToString = function(d) {
1112
+ var tmp = document.createElement('div');
1113
+
1114
+ tmp.appendChild(d.cloneNode(true));
1115
+
1116
+ return tmp.innerHTML;
1117
+ };
1118
+
1119
+ var logError = function(message, options){
1120
+ if(!options) options = {};
1121
+ var component = "Selectize";
1122
+
1123
+ console.error(component + ": " + message)
1124
+
1125
+ if(options.explanation){
1126
+ // console.group is undefined in <IE11
1127
+ if(console.group) console.group();
1128
+ console.error(options.explanation);
1129
+ if(console.group) console.groupEnd();
1130
+ }
1131
+ }
1132
+
1133
+
1134
+ var Selectize = function($input, settings) {
1135
+ var key, i, n, dir, input, self = this;
1136
+ input = $input[0];
1137
+ input.selectize = self;
1138
+
1139
+ // detect rtl environment
1140
+ var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1141
+ dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
1142
+ dir = dir || $input.parents('[dir]:first').attr('dir') || '';
1143
+
1144
+ // setup default state
1145
+ $.extend(self, {
1146
+ order : 0,
1147
+ settings : settings,
1148
+ $input : $input,
1149
+ tabIndex : $input.attr('tabindex') || '',
1150
+ tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
1151
+ rtl : /rtl/i.test(dir),
1152
+
1153
+ eventNS : '.selectize' + (++Selectize.count),
1154
+ highlightedValue : null,
1155
+ isBlurring : false,
1156
+ isOpen : false,
1157
+ isDisabled : false,
1158
+ isRequired : $input.is('[required]'),
1159
+ isInvalid : false,
1160
+ isLocked : false,
1161
+ isFocused : false,
1162
+ isInputHidden : false,
1163
+ isSetup : false,
1164
+ isShiftDown : false,
1165
+ isCmdDown : false,
1166
+ isCtrlDown : false,
1167
+ ignoreFocus : false,
1168
+ ignoreBlur : false,
1169
+ ignoreHover : false,
1170
+ hasOptions : false,
1171
+ currentResults : null,
1172
+ lastValue : '',
1173
+ caretPos : 0,
1174
+ loading : 0,
1175
+ loadedSearches : {},
1176
+
1177
+ $activeOption : null,
1178
+ $activeItems : [],
1179
+
1180
+ optgroups : {},
1181
+ options : {},
1182
+ userOptions : {},
1183
+ items : [],
1184
+ renderCache : {},
1185
+ onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
1186
+ });
1187
+
1188
+ // search system
1189
+ self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
1190
+
1191
+ // build options table
1192
+ if (self.settings.options) {
1193
+ for (i = 0, n = self.settings.options.length; i < n; i++) {
1194
+ self.registerOption(self.settings.options[i]);
1195
+ }
1196
+ delete self.settings.options;
1197
+ }
1198
+
1199
+ // build optgroup table
1200
+ if (self.settings.optgroups) {
1201
+ for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
1202
+ self.registerOptionGroup(self.settings.optgroups[i]);
1203
+ }
1204
+ delete self.settings.optgroups;
1205
+ }
1206
+
1207
+ // option-dependent defaults
1208
+ self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
1209
+ if (typeof self.settings.hideSelected !== 'boolean') {
1210
+ self.settings.hideSelected = self.settings.mode === 'multi';
1211
+ }
1212
+
1213
+ self.initializePlugins(self.settings.plugins);
1214
+ self.setupCallbacks();
1215
+ self.setupTemplates();
1216
+ self.setup();
1217
+ };
1218
+
1219
+ // mixins
1220
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1221
+
1222
+ MicroEvent.mixin(Selectize);
1223
+
1224
+ if(typeof MicroPlugin !== "undefined"){
1225
+ MicroPlugin.mixin(Selectize);
1226
+ }else{
1227
+ logError("Dependency MicroPlugin is missing",
1228
+ {explanation:
1229
+ "Make sure you either: (1) are using the \"standalone\" "+
1230
+ "version of Selectize, or (2) require MicroPlugin before you "+
1231
+ "load Selectize."}
1232
+ );
1233
+ }
1234
+
1235
+
1236
+ // methods
1237
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1238
+
1239
+ $.extend(Selectize.prototype, {
1240
+
1241
+ /**
1242
+ * Creates all elements and sets up event bindings.
1243
+ */
1244
+ setup: function() {
1245
+ var self = this;
1246
+ var settings = self.settings;
1247
+ var eventNS = self.eventNS;
1248
+ var $window = $(window);
1249
+ var $document = $(document);
1250
+ var $input = self.$input;
1251
+
1252
+ var $wrapper;
1253
+ var $control;
1254
+ var $control_input;
1255
+ var $dropdown;
1256
+ var $dropdown_content;
1257
+ var $dropdown_parent;
1258
+ var inputMode;
1259
+ var timeout_blur;
1260
+ var timeout_focus;
1261
+ var classes;
1262
+ var classes_plugins;
1263
+ var inputId;
1264
+
1265
+ inputMode = self.settings.mode;
1266
+ classes = $input.attr('class') || '';
1267
+
1268
+ $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
1269
+ $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
1270
+ $control_input = $('<input type="text" autocomplete="new-password" autofill="no" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
1271
+ $dropdown_parent = $(settings.dropdownParent || $wrapper);
1272
+ $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
1273
+ $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
1274
+
1275
+ if(inputId = $input.attr('id')) {
1276
+ $control_input.attr('id', inputId + '-selectized');
1277
+ $("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
1278
+ }
1279
+
1280
+ if(self.settings.copyClassesToDropdown) {
1281
+ $dropdown.addClass(classes);
1282
+ }
1283
+
1284
+ $wrapper.css({
1285
+ width: $input[0].style.width
1286
+ });
1287
+
1288
+ if (self.plugins.names.length) {
1289
+ classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1290
+ $wrapper.addClass(classes_plugins);
1291
+ $dropdown.addClass(classes_plugins);
1292
+ }
1293
+
1294
+ if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
1295
+ $input.attr('multiple', 'multiple');
1296
+ }
1297
+
1298
+ if (self.settings.placeholder) {
1299
+ $control_input.attr('placeholder', settings.placeholder);
1300
+ }
1301
+
1302
+ // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1303
+ if (!self.settings.splitOn && self.settings.delimiter) {
1304
+ var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1305
+ self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
1306
+ }
1307
+
1308
+ if ($input.attr('autocorrect')) {
1309
+ $control_input.attr('autocorrect', $input.attr('autocorrect'));
1310
+ }
1311
+
1312
+ if ($input.attr('autocapitalize')) {
1313
+ $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
1314
+ }
1315
+ $control_input[0].type = $input[0].type;
1316
+
1317
+ self.$wrapper = $wrapper;
1318
+ self.$control = $control;
1319
+ self.$control_input = $control_input;
1320
+ self.$dropdown = $dropdown;
1321
+ self.$dropdown_content = $dropdown_content;
1322
+
1323
+ $dropdown.on('mouseenter mousedown click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); });
1324
+ $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1325
+ $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1326
+ watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1327
+ autoGrow($control_input);
1328
+
1329
+ $control.on({
1330
+ mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1331
+ click : function() { return self.onClick.apply(self, arguments); }
1332
+ });
1333
+
1334
+ $control_input.on({
1335
+ mousedown : function(e) { e.stopPropagation(); },
1336
+ keydown : function() { return self.onKeyDown.apply(self, arguments); },
1337
+ keyup : function() { return self.onKeyUp.apply(self, arguments); },
1338
+ keypress : function() { return self.onKeyPress.apply(self, arguments); },
1339
+ resize : function() { self.positionDropdown.apply(self, []); },
1340
+ blur : function() { return self.onBlur.apply(self, arguments); },
1341
+ focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
1342
+ paste : function() { return self.onPaste.apply(self, arguments); }
1343
+ });
1344
+
1345
+ $document.on('keydown' + eventNS, function(e) {
1346
+ self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1347
+ self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1348
+ self.isShiftDown = e.shiftKey;
1349
+ });
1350
+
1351
+ $document.on('keyup' + eventNS, function(e) {
1352
+ if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1353
+ if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1354
+ if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1355
+ });
1356
+
1357
+ $document.on('mousedown' + eventNS, function(e) {
1358
+ if (self.isFocused) {
1359
+ // prevent events on the dropdown scrollbar from causing the control to blur
1360
+ if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
1361
+ return false;
1362
+ }
1363
+ // blur on click outside
1364
+ if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
1365
+ self.blur(e.target);
1366
+ }
1367
+ }
1368
+ });
1369
+
1370
+ $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1371
+ if (self.isOpen) {
1372
+ self.positionDropdown.apply(self, arguments);
1373
+ }
1374
+ });
1375
+ $window.on('mousemove' + eventNS, function() {
1376
+ self.ignoreHover = false;
1377
+ });
1378
+
1379
+ // store original children and tab index so that they can be
1380
+ // restored when the destroy() method is called.
1381
+ this.revertSettings = {
1382
+ $children : $input.children().detach(),
1383
+ tabindex : $input.attr('tabindex')
1384
+ };
1385
+
1386
+ $input.attr('tabindex', -1).hide().after(self.$wrapper);
1387
+
1388
+ if ($.isArray(settings.items)) {
1389
+ self.setValue(settings.items);
1390
+ delete settings.items;
1391
+ }
1392
+
1393
+ // feature detect for the validation API
1394
+ if (SUPPORTS_VALIDITY_API) {
1395
+ $input.on('invalid' + eventNS, function(e) {
1396
+ e.preventDefault();
1397
+ self.isInvalid = true;
1398
+ self.refreshState();
1399
+ });
1400
+ }
1401
+
1402
+ self.updateOriginalInput();
1403
+ self.refreshItems();
1404
+ self.refreshState();
1405
+ self.updatePlaceholder();
1406
+ self.isSetup = true;
1407
+
1408
+ if ($input.is(':disabled')) {
1409
+ self.disable();
1410
+ }
1411
+
1412
+ self.on('change', this.onChange);
1413
+
1414
+ $input.data('selectize', self);
1415
+ $input.addClass('selectized');
1416
+ self.trigger('initialize');
1417
+
1418
+ // preload options
1419
+ if (settings.preload === true) {
1420
+ self.onSearchChange('');
1421
+ }
1422
+
1423
+ },
1424
+
1425
+ /**
1426
+ * Sets up default rendering functions.
1427
+ */
1428
+ setupTemplates: function() {
1429
+ var self = this;
1430
+ var field_label = self.settings.labelField;
1431
+ var field_optgroup = self.settings.optgroupLabelField;
1432
+
1433
+ var templates = {
1434
+ 'optgroup': function(data) {
1435
+ return '<div class="optgroup">' + data.html + '</div>';
1436
+ },
1437
+ 'optgroup_header': function(data, escape) {
1438
+ return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1439
+ },
1440
+ 'option': function(data, escape) {
1441
+ return '<div class="option">' + escape(data[field_label]) + '</div>';
1442
+ },
1443
+ 'item': function(data, escape) {
1444
+ return '<div class="item">' + escape(data[field_label]) + '</div>';
1445
+ },
1446
+ 'option_create': function(data, escape) {
1447
+ return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
1448
+ }
1449
+ };
1450
+
1451
+ self.settings.render = $.extend({}, templates, self.settings.render);
1452
+ },
1453
+
1454
+ /**
1455
+ * Maps fired events to callbacks provided
1456
+ * in the settings used when creating the control.
1457
+ */
1458
+ setupCallbacks: function() {
1459
+ var key, fn, callbacks = {
1460
+ 'initialize' : 'onInitialize',
1461
+ 'change' : 'onChange',
1462
+ 'item_add' : 'onItemAdd',
1463
+ 'item_remove' : 'onItemRemove',
1464
+ 'clear' : 'onClear',
1465
+ 'option_add' : 'onOptionAdd',
1466
+ 'option_remove' : 'onOptionRemove',
1467
+ 'option_clear' : 'onOptionClear',
1468
+ 'optgroup_add' : 'onOptionGroupAdd',
1469
+ 'optgroup_remove' : 'onOptionGroupRemove',
1470
+ 'optgroup_clear' : 'onOptionGroupClear',
1471
+ 'dropdown_open' : 'onDropdownOpen',
1472
+ 'dropdown_close' : 'onDropdownClose',
1473
+ 'type' : 'onType',
1474
+ 'load' : 'onLoad',
1475
+ 'focus' : 'onFocus',
1476
+ 'blur' : 'onBlur',
1477
+ 'dropdown_item_activate' : 'onDropdownItemActivate',
1478
+ 'dropdown_item_deactivate' : 'onDropdownItemDeactivate'
1479
+ };
1480
+
1481
+ for (key in callbacks) {
1482
+ if (callbacks.hasOwnProperty(key)) {
1483
+ fn = this.settings[callbacks[key]];
1484
+ if (fn) this.on(key, fn);
1485
+ }
1486
+ }
1487
+ },
1488
+
1489
+ /**
1490
+ * Triggered when the main control element
1491
+ * has a click event.
1492
+ *
1493
+ * @param {object} e
1494
+ * @return {boolean}
1495
+ */
1496
+ onClick: function(e) {
1497
+ var self = this;
1498
+
1499
+ // necessary for mobile webkit devices (manual focus triggering
1500
+ // is ignored unless invoked within a click event)
1501
+ // also necessary to reopen a dropdown that has been closed by
1502
+ // closeAfterSelect
1503
+ if (!self.isFocused || !self.isOpen) {
1504
+ self.focus();
1505
+ e.preventDefault();
1506
+ }
1507
+ },
1508
+
1509
+ /**
1510
+ * Triggered when the main control element
1511
+ * has a mouse down event.
1512
+ *
1513
+ * @param {object} e
1514
+ * @return {boolean}
1515
+ */
1516
+ onMouseDown: function(e) {
1517
+ var self = this;
1518
+ var defaultPrevented = e.isDefaultPrevented();
1519
+ var $target = $(e.target);
1520
+
1521
+ if (self.isFocused) {
1522
+ // retain focus by preventing native handling. if the
1523
+ // event target is the input it should not be modified.
1524
+ // otherwise, text selection within the input won't work.
1525
+ if (e.target !== self.$control_input[0]) {
1526
+ if (self.settings.mode === 'single') {
1527
+ // toggle dropdown
1528
+ self.isOpen ? self.close() : self.open();
1529
+ } else if (!defaultPrevented) {
1530
+ self.setActiveItem(null);
1531
+ }
1532
+ return false;
1533
+ }
1534
+ } else {
1535
+ // give control focus
1536
+ if (!defaultPrevented) {
1537
+ window.setTimeout(function() {
1538
+ self.focus();
1539
+ }, 0);
1540
+ }
1541
+ }
1542
+ },
1543
+
1544
+ /**
1545
+ * Triggered when the value of the control has been changed.
1546
+ * This should propagate the event to the original DOM
1547
+ * input / select element.
1548
+ */
1549
+ onChange: function() {
1550
+ this.$input.trigger('change');
1551
+ },
1552
+
1553
+ /**
1554
+ * Triggered on <input> paste.
1555
+ *
1556
+ * @param {object} e
1557
+ * @returns {boolean}
1558
+ */
1559
+ onPaste: function(e) {
1560
+ var self = this;
1561
+
1562
+ if (self.isFull() || self.isInputHidden || self.isLocked) {
1563
+ e.preventDefault();
1564
+ return;
1565
+ }
1566
+
1567
+ // If a regex or string is included, this will split the pasted
1568
+ // input and create Items for each separate value
1569
+ if (self.settings.splitOn) {
1570
+
1571
+ // Wait for pasted text to be recognized in value
1572
+ setTimeout(function() {
1573
+ var pastedText = self.$control_input.val();
1574
+ if(!pastedText.match(self.settings.splitOn)){ return }
1575
+
1576
+ var splitInput = $.trim(pastedText).split(self.settings.splitOn);
1577
+ for (var i = 0, n = splitInput.length; i < n; i++) {
1578
+ self.createItem(splitInput[i]);
1579
+ }
1580
+ }, 0);
1581
+ }
1582
+ },
1583
+
1584
+ /**
1585
+ * Triggered on <input> keypress.
1586
+ *
1587
+ * @param {object} e
1588
+ * @returns {boolean}
1589
+ */
1590
+ onKeyPress: function(e) {
1591
+ if (this.isLocked) return e && e.preventDefault();
1592
+ var character = String.fromCharCode(e.keyCode || e.which);
1593
+ if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1594
+ this.createItem();
1595
+ e.preventDefault();
1596
+ return false;
1597
+ }
1598
+ },
1599
+
1600
+ /**
1601
+ * Triggered on <input> keydown.
1602
+ *
1603
+ * @param {object} e
1604
+ * @returns {boolean}
1605
+ */
1606
+ onKeyDown: function(e) {
1607
+ var isInput = e.target === this.$control_input[0];
1608
+ var self = this;
1609
+
1610
+ if (self.isLocked) {
1611
+ if (e.keyCode !== KEY_TAB) {
1612
+ e.preventDefault();
1613
+ }
1614
+ return;
1615
+ }
1616
+
1617
+ switch (e.keyCode) {
1618
+ case KEY_A:
1619
+ if (self.isCmdDown) {
1620
+ self.selectAll();
1621
+ return;
1622
+ }
1623
+ break;
1624
+ case KEY_ESC:
1625
+ if (self.isOpen) {
1626
+ e.preventDefault();
1627
+ e.stopPropagation();
1628
+ self.close();
1629
+ }
1630
+ return;
1631
+ case KEY_N:
1632
+ if (!e.ctrlKey || e.altKey) break;
1633
+ case KEY_DOWN:
1634
+ if (!self.isOpen && self.hasOptions) {
1635
+ self.open();
1636
+ } else if (self.$activeOption) {
1637
+ self.ignoreHover = true;
1638
+ var $next = self.getAdjacentOption(self.$activeOption, 1);
1639
+ if ($next.length) self.setActiveOption($next, true, true);
1640
+ }
1641
+ e.preventDefault();
1642
+ return;
1643
+ case KEY_P:
1644
+ if (!e.ctrlKey || e.altKey) break;
1645
+ case KEY_UP:
1646
+ if (self.$activeOption) {
1647
+ self.ignoreHover = true;
1648
+ var $prev = self.getAdjacentOption(self.$activeOption, -1);
1649
+ if ($prev.length) self.setActiveOption($prev, true, true);
1650
+ }
1651
+ e.preventDefault();
1652
+ return;
1653
+ case KEY_RETURN:
1654
+ if (self.isOpen && self.$activeOption) {
1655
+ self.onOptionSelect({currentTarget: self.$activeOption});
1656
+ e.preventDefault();
1657
+ }
1658
+ return;
1659
+ case KEY_LEFT:
1660
+ self.advanceSelection(-1, e);
1661
+ return;
1662
+ case KEY_RIGHT:
1663
+ self.advanceSelection(1, e);
1664
+ return;
1665
+ case KEY_TAB:
1666
+ if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1667
+ self.onOptionSelect({currentTarget: self.$activeOption});
1668
+
1669
+ // Default behaviour is to jump to the next field, we only want this
1670
+ // if the current field doesn't accept any more entries
1671
+ if (!self.isFull()) {
1672
+ e.preventDefault();
1673
+ }
1674
+ }
1675
+ if (self.settings.create && self.createItem()) {
1676
+ e.preventDefault();
1677
+ }
1678
+ return;
1679
+ case KEY_BACKSPACE:
1680
+ case KEY_DELETE:
1681
+ self.deleteSelection(e);
1682
+ return;
1683
+ }
1684
+
1685
+ if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1686
+ e.preventDefault();
1687
+ return;
1688
+ }
1689
+ },
1690
+
1691
+ /**
1692
+ * Triggered on <input> keyup.
1693
+ *
1694
+ * @param {object} e
1695
+ * @returns {boolean}
1696
+ */
1697
+ onKeyUp: function(e) {
1698
+ var self = this;
1699
+
1700
+ if (self.isLocked) return e && e.preventDefault();
1701
+ var value = self.$control_input.val() || '';
1702
+ if (self.lastValue !== value) {
1703
+ self.lastValue = value;
1704
+ self.onSearchChange(value);
1705
+ self.refreshOptions();
1706
+ self.trigger('type', value);
1707
+ }
1708
+ },
1709
+
1710
+ /**
1711
+ * Invokes the user-provide option provider / loader.
1712
+ *
1713
+ * Note: this function is debounced in the Selectize
1714
+ * constructor (by `settings.loadThrottle` milliseconds)
1715
+ *
1716
+ * @param {string} value
1717
+ */
1718
+ onSearchChange: function(value) {
1719
+ var self = this;
1720
+ var fn = self.settings.load;
1721
+ if (!fn) return;
1722
+ if (self.loadedSearches.hasOwnProperty(value)) return;
1723
+ self.loadedSearches[value] = true;
1724
+ self.load(function(callback) {
1725
+ fn.apply(self, [value, callback]);
1726
+ });
1727
+ },
1728
+
1729
+ /**
1730
+ * Triggered on <input> focus.
1731
+ *
1732
+ * @param {object} e (optional)
1733
+ * @returns {boolean}
1734
+ */
1735
+ onFocus: function(e) {
1736
+ var self = this;
1737
+ var wasFocused = self.isFocused;
1738
+
1739
+ if (self.isDisabled) {
1740
+ self.blur();
1741
+ e && e.preventDefault();
1742
+ return false;
1743
+ }
1744
+
1745
+ if (self.ignoreFocus) return;
1746
+ self.isFocused = true;
1747
+ if (self.settings.preload === 'focus') self.onSearchChange('');
1748
+
1749
+ if (!wasFocused) self.trigger('focus');
1750
+
1751
+ if (!self.$activeItems.length) {
1752
+ self.showInput();
1753
+ self.setActiveItem(null);
1754
+ self.refreshOptions(!!self.settings.openOnFocus);
1755
+ }
1756
+
1757
+ self.refreshState();
1758
+ },
1759
+
1760
+ /**
1761
+ * Triggered on <input> blur.
1762
+ *
1763
+ * @param {object} e
1764
+ * @param {Element} dest
1765
+ */
1766
+ onBlur: function(e, dest) {
1767
+ var self = this;
1768
+ if (!self.isFocused) return;
1769
+ self.isFocused = false;
1770
+
1771
+ if (self.ignoreFocus) {
1772
+ return;
1773
+ } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1774
+ // necessary to prevent IE closing the dropdown when the scrollbar is clicked
1775
+ self.ignoreBlur = true;
1776
+ self.onFocus(e);
1777
+ return;
1778
+ }
1779
+
1780
+ var deactivate = function() {
1781
+ self.close();
1782
+ self.setTextboxValue('');
1783
+ self.setActiveItem(null);
1784
+ self.setActiveOption(null);
1785
+ self.setCaret(self.items.length);
1786
+ self.refreshState();
1787
+
1788
+ // IE11 bug: element still marked as active
1789
+ dest && dest.focus && dest.focus();
1790
+
1791
+ self.isBlurring = false;
1792
+ self.ignoreFocus = false;
1793
+ self.trigger('blur');
1794
+ };
1795
+
1796
+ self.isBlurring = true;
1797
+ self.ignoreFocus = true;
1798
+ if (self.settings.create && self.settings.createOnBlur) {
1799
+ self.createItem(null, false, deactivate);
1800
+ } else {
1801
+ deactivate();
1802
+ }
1803
+ },
1804
+
1805
+ /**
1806
+ * Triggered when the user rolls over
1807
+ * an option in the autocomplete dropdown menu.
1808
+ *
1809
+ * @param {object} e
1810
+ * @returns {boolean}
1811
+ */
1812
+ onOptionHover: function(e) {
1813
+ if (this.ignoreHover) return;
1814
+ this.setActiveOption(e.currentTarget, false);
1815
+ },
1816
+
1817
+ /**
1818
+ * Triggered when the user clicks on an option
1819
+ * in the autocomplete dropdown menu.
1820
+ *
1821
+ * @param {object} e
1822
+ * @returns {boolean}
1823
+ */
1824
+ onOptionSelect: function(e) {
1825
+ var value, $target, $option, self = this;
1826
+
1827
+ if (e.preventDefault) {
1828
+ e.preventDefault();
1829
+ e.stopPropagation();
1830
+ }
1831
+
1832
+ $target = $(e.currentTarget);
1833
+ if ($target.hasClass('create')) {
1834
+ self.createItem(null, function() {
1835
+ if (self.settings.closeAfterSelect) {
1836
+ self.close();
1837
+ }
1838
+ });
1839
+ } else {
1840
+ value = $target.attr('data-value');
1841
+ if (typeof value !== 'undefined') {
1842
+ self.lastQuery = null;
1843
+ self.setTextboxValue('');
1844
+ self.addItem(value);
1845
+ if (self.settings.closeAfterSelect) {
1846
+ self.close();
1847
+ } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1848
+ self.setActiveOption(self.getOption(value));
1849
+ }
1850
+ }
1851
+ }
1852
+ },
1853
+
1854
+ /**
1855
+ * Triggered when the user clicks on an item
1856
+ * that has been selected.
1857
+ *
1858
+ * @param {object} e
1859
+ * @returns {boolean}
1860
+ */
1861
+ onItemSelect: function(e) {
1862
+ var self = this;
1863
+
1864
+ if (self.isLocked) return;
1865
+ if (self.settings.mode === 'multi') {
1866
+ e.preventDefault();
1867
+ self.setActiveItem(e.currentTarget, e);
1868
+ }
1869
+ },
1870
+
1871
+ /**
1872
+ * Invokes the provided method that provides
1873
+ * results to a callback---which are then added
1874
+ * as options to the control.
1875
+ *
1876
+ * @param {function} fn
1877
+ */
1878
+ load: function(fn) {
1879
+ var self = this;
1880
+ var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1881
+
1882
+ self.loading++;
1883
+ fn.apply(self, [function(results) {
1884
+ self.loading = Math.max(self.loading - 1, 0);
1885
+ if (results && results.length) {
1886
+ self.addOption(results);
1887
+ self.refreshOptions(self.isFocused && !self.isInputHidden);
1888
+ }
1889
+ if (!self.loading) {
1890
+ $wrapper.removeClass(self.settings.loadingClass);
1891
+ }
1892
+ self.trigger('load', results);
1893
+ }]);
1894
+ },
1895
+
1896
+ /**
1897
+ * Sets the input field of the control to the specified value.
1898
+ *
1899
+ * @param {string} value
1900
+ */
1901
+ setTextboxValue: function(value) {
1902
+ var $input = this.$control_input;
1903
+ var changed = $input.val() !== value;
1904
+ if (changed) {
1905
+ $input.val(value).triggerHandler('update');
1906
+ this.lastValue = value;
1907
+ }
1908
+ },
1909
+
1910
+ /**
1911
+ * Returns the value of the control. If multiple items
1912
+ * can be selected (e.g. <select multiple>), this returns
1913
+ * an array. If only one item can be selected, this
1914
+ * returns a string.
1915
+ *
1916
+ * @returns {mixed}
1917
+ */
1918
+ getValue: function() {
1919
+ if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1920
+ return this.items;
1921
+ } else {
1922
+ return this.items.join(this.settings.delimiter);
1923
+ }
1924
+ },
1925
+
1926
+ /**
1927
+ * Resets the selected items to the given value.
1928
+ *
1929
+ * @param {mixed} value
1930
+ */
1931
+ setValue: function(value, silent) {
1932
+ var events = silent ? [] : ['change'];
1933
+
1934
+ debounce_events(this, events, function() {
1935
+ this.clear(silent);
1936
+ this.addItems(value, silent);
1937
+ });
1938
+ },
1939
+
1940
+ /**
1941
+ * Resets the number of max items to the given value
1942
+ *
1943
+ * @param {number} value
1944
+ */
1945
+ setMaxItems: function(value){
1946
+ if(value === 0) value = null; //reset to unlimited items.
1947
+ this.settings.maxItems = value;
1948
+ this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi');
1949
+ this.refreshState();
1950
+ },
1951
+
1952
+ /**
1953
+ * Sets the selected item.
1954
+ *
1955
+ * @param {object} $item
1956
+ * @param {object} e (optional)
1957
+ */
1958
+ setActiveItem: function($item, e) {
1959
+ var self = this;
1960
+ var eventName;
1961
+ var i, idx, begin, end, item, swap;
1962
+ var $last;
1963
+
1964
+ if (self.settings.mode === 'single') return;
1965
+ $item = $($item);
1966
+
1967
+ // clear the active selection
1968
+ if (!$item.length) {
1969
+ $(self.$activeItems).removeClass('active');
1970
+ self.$activeItems = [];
1971
+ if (self.isFocused) {
1972
+ self.showInput();
1973
+ }
1974
+ return;
1975
+ }
1976
+
1977
+ // modify selection
1978
+ eventName = e && e.type.toLowerCase();
1979
+
1980
+ if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1981
+ $last = self.$control.children('.active:last');
1982
+ begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1983
+ end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1984
+ if (begin > end) {
1985
+ swap = begin;
1986
+ begin = end;
1987
+ end = swap;
1988
+ }
1989
+ for (i = begin; i <= end; i++) {
1990
+ item = self.$control[0].childNodes[i];
1991
+ if (self.$activeItems.indexOf(item) === -1) {
1992
+ $(item).addClass('active');
1993
+ self.$activeItems.push(item);
1994
+ }
1995
+ }
1996
+ e.preventDefault();
1997
+ } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1998
+ if ($item.hasClass('active')) {
1999
+ idx = self.$activeItems.indexOf($item[0]);
2000
+ self.$activeItems.splice(idx, 1);
2001
+ $item.removeClass('active');
2002
+ } else {
2003
+ self.$activeItems.push($item.addClass('active')[0]);
2004
+ }
2005
+ } else {
2006
+ $(self.$activeItems).removeClass('active');
2007
+ self.$activeItems = [$item.addClass('active')[0]];
2008
+ }
2009
+
2010
+ // ensure control has focus
2011
+ self.hideInput();
2012
+ if (!this.isFocused) {
2013
+ self.focus();
2014
+ }
2015
+ },
2016
+
2017
+ /**
2018
+ * Sets the selected item in the dropdown menu
2019
+ * of available options.
2020
+ *
2021
+ * @param {object} $object
2022
+ * @param {boolean} scroll
2023
+ * @param {boolean} animate
2024
+ */
2025
+ setActiveOption: function($option, scroll, animate) {
2026
+ var height_menu, height_item, y;
2027
+ var scroll_top, scroll_bottom;
2028
+ var self = this;
2029
+
2030
+ if (self.$activeOption) {
2031
+ self.$activeOption.removeClass('active');
2032
+ self.trigger('dropdown_item_deactivate', self.$activeOption.attr('data-value'));
2033
+ }
2034
+ self.$activeOption = null;
2035
+
2036
+ $option = $($option);
2037
+ if (!$option.length) return;
2038
+
2039
+ self.$activeOption = $option.addClass('active');
2040
+ if (self.isOpen) self.trigger('dropdown_item_activate', self.$activeOption.attr('data-value'));
2041
+
2042
+ if (scroll || !isset(scroll)) {
2043
+
2044
+ height_menu = self.$dropdown_content.height();
2045
+ height_item = self.$activeOption.outerHeight(true);
2046
+ scroll = self.$dropdown_content.scrollTop() || 0;
2047
+ y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
2048
+ scroll_top = y;
2049
+ scroll_bottom = y - height_menu + height_item;
2050
+
2051
+ if (y + height_item > height_menu + scroll) {
2052
+ self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
2053
+ } else if (y < scroll) {
2054
+ self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
2055
+ }
2056
+
2057
+ }
2058
+ },
2059
+
2060
+ /**
2061
+ * Selects all items (CTRL + A).
2062
+ */
2063
+ selectAll: function() {
2064
+ var self = this;
2065
+ if (self.settings.mode === 'single') return;
2066
+
2067
+ self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
2068
+ if (self.$activeItems.length) {
2069
+ self.hideInput();
2070
+ self.close();
2071
+ }
2072
+ self.focus();
2073
+ },
2074
+
2075
+ /**
2076
+ * Hides the input element out of view, while
2077
+ * retaining its focus.
2078
+ */
2079
+ hideInput: function() {
2080
+ var self = this;
2081
+
2082
+ self.setTextboxValue('');
2083
+ self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
2084
+ self.isInputHidden = true;
2085
+ },
2086
+
2087
+ /**
2088
+ * Restores input visibility.
2089
+ */
2090
+ showInput: function() {
2091
+ this.$control_input.css({opacity: 1, position: 'relative', left: 0});
2092
+ this.isInputHidden = false;
2093
+ },
2094
+
2095
+ /**
2096
+ * Gives the control focus.
2097
+ */
2098
+ focus: function() {
2099
+ var self = this;
2100
+ if (self.isDisabled) return;
2101
+
2102
+ self.ignoreFocus = true;
2103
+ self.$control_input[0].focus();
2104
+ window.setTimeout(function() {
2105
+ self.ignoreFocus = false;
2106
+ self.onFocus();
2107
+ }, 0);
2108
+ },
2109
+
2110
+ /**
2111
+ * Forces the control out of focus.
2112
+ *
2113
+ * @param {Element} dest
2114
+ */
2115
+ blur: function(dest) {
2116
+ this.$control_input[0].blur();
2117
+ this.onBlur(null, dest);
2118
+ },
2119
+
2120
+ /**
2121
+ * Returns a function that scores an object
2122
+ * to show how good of a match it is to the
2123
+ * provided query.
2124
+ *
2125
+ * @param {string} query
2126
+ * @param {object} options
2127
+ * @return {function}
2128
+ */
2129
+ getScoreFunction: function(query) {
2130
+ return this.sifter.getScoreFunction(query, this.getSearchOptions());
2131
+ },
2132
+
2133
+ /**
2134
+ * Returns search options for sifter (the system
2135
+ * for scoring and sorting results).
2136
+ *
2137
+ * @see https://github.com/brianreavis/sifter.js
2138
+ * @return {object}
2139
+ */
2140
+ getSearchOptions: function() {
2141
+ var settings = this.settings;
2142
+ var sort = settings.sortField;
2143
+ if (typeof sort === 'string') {
2144
+ sort = [{field: sort}];
2145
+ }
2146
+
2147
+ return {
2148
+ fields : settings.searchField,
2149
+ conjunction : settings.searchConjunction,
2150
+ sort : sort,
2151
+ nesting : settings.nesting
2152
+ };
2153
+ },
2154
+
2155
+ /**
2156
+ * Searches through available options and returns
2157
+ * a sorted array of matches.
2158
+ *
2159
+ * Returns an object containing:
2160
+ *
2161
+ * - query {string}
2162
+ * - tokens {array}
2163
+ * - total {int}
2164
+ * - items {array}
2165
+ *
2166
+ * @param {string} query
2167
+ * @returns {object}
2168
+ */
2169
+ search: function(query) {
2170
+ var i, value, score, result, calculateScore;
2171
+ var self = this;
2172
+ var settings = self.settings;
2173
+ var options = this.getSearchOptions();
2174
+
2175
+ // validate user-provided result scoring function
2176
+ if (settings.score) {
2177
+ calculateScore = self.settings.score.apply(this, [query]);
2178
+ if (typeof calculateScore !== 'function') {
2179
+ throw new Error('Selectize "score" setting must be a function that returns a function');
2180
+ }
2181
+ }
2182
+
2183
+ // perform search
2184
+ if (query !== self.lastQuery) {
2185
+ self.lastQuery = query;
2186
+ result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
2187
+ self.currentResults = result;
2188
+ } else {
2189
+ result = $.extend(true, {}, self.currentResults);
2190
+ }
2191
+
2192
+ // filter out selected items
2193
+ if (settings.hideSelected) {
2194
+ for (i = result.items.length - 1; i >= 0; i--) {
2195
+ if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
2196
+ result.items.splice(i, 1);
2197
+ }
2198
+ }
2199
+ }
2200
+
2201
+ return result;
2202
+ },
2203
+
2204
+ /**
2205
+ * Refreshes the list of available options shown
2206
+ * in the autocomplete dropdown menu.
2207
+ *
2208
+ * @param {boolean} triggerDropdown
2209
+ */
2210
+ refreshOptions: function(triggerDropdown) {
2211
+ var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
2212
+ var $active, $active_before, $create;
2213
+
2214
+ if (typeof triggerDropdown === 'undefined') {
2215
+ triggerDropdown = true;
2216
+ }
2217
+
2218
+ var self = this;
2219
+ var query = $.trim(self.$control_input.val());
2220
+ var results = self.search(query);
2221
+ var $dropdown_content = self.$dropdown_content;
2222
+ var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
2223
+
2224
+ // build markup
2225
+ n = results.items.length;
2226
+ if (typeof self.settings.maxOptions === 'number') {
2227
+ n = Math.min(n, self.settings.maxOptions);
2228
+ }
2229
+
2230
+ // render and group available options individually
2231
+ groups = {};
2232
+ groups_order = [];
2233
+
2234
+ for (i = 0; i < n; i++) {
2235
+ option = self.options[results.items[i].id];
2236
+ option_html = self.render('option', option);
2237
+ optgroup = option[self.settings.optgroupField] || '';
2238
+ optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
2239
+
2240
+ for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2241
+ optgroup = optgroups[j];
2242
+ if (!self.optgroups.hasOwnProperty(optgroup)) {
2243
+ optgroup = '';
2244
+ }
2245
+ if (!groups.hasOwnProperty(optgroup)) {
2246
+ groups[optgroup] = document.createDocumentFragment();
2247
+ groups_order.push(optgroup);
2248
+ }
2249
+ groups[optgroup].appendChild(option_html);
2250
+ }
2251
+ }
2252
+
2253
+ // sort optgroups
2254
+ if (this.settings.lockOptgroupOrder) {
2255
+ groups_order.sort(function(a, b) {
2256
+ var a_order = self.optgroups[a].$order || 0;
2257
+ var b_order = self.optgroups[b].$order || 0;
2258
+ return a_order - b_order;
2259
+ });
2260
+ }
2261
+
2262
+ // render optgroup headers & join groups
2263
+ html = document.createDocumentFragment();
2264
+ for (i = 0, n = groups_order.length; i < n; i++) {
2265
+ optgroup = groups_order[i];
2266
+ if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
2267
+ // render the optgroup header and options within it,
2268
+ // then pass it to the wrapper template
2269
+ html_children = document.createDocumentFragment();
2270
+ html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
2271
+ html_children.appendChild(groups[optgroup]);
2272
+
2273
+ html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
2274
+ html: domToString(html_children),
2275
+ dom: html_children
2276
+ })));
2277
+ } else {
2278
+ html.appendChild(groups[optgroup]);
2279
+ }
2280
+ }
2281
+
2282
+ $dropdown_content.html(html);
2283
+
2284
+ // highlight matching terms inline
2285
+ if (self.settings.highlight) {
2286
+ $dropdown_content.removeHighlight();
2287
+ if (results.query.length && results.tokens.length) {
2288
+ for (i = 0, n = results.tokens.length; i < n; i++) {
2289
+ highlight($dropdown_content, results.tokens[i].regex);
2290
+ }
2291
+ }
2292
+ }
2293
+
2294
+ // add "selected" class to selected options
2295
+ if (!self.settings.hideSelected) {
2296
+ // clear selection on all previously selected elements first
2297
+ self.$dropdown.find('.selected').removeClass('selected');
2298
+
2299
+ for (i = 0, n = self.items.length; i < n; i++) {
2300
+ self.getOption(self.items[i]).addClass('selected');
2301
+ }
2302
+ }
2303
+
2304
+ // add create option
2305
+ has_create_option = self.canCreate(query);
2306
+ if (has_create_option) {
2307
+ $dropdown_content.prepend(self.render('option_create', {input: query}));
2308
+ $create = $($dropdown_content[0].childNodes[0]);
2309
+ }
2310
+
2311
+ // activate
2312
+ self.hasOptions = results.items.length > 0 || has_create_option;
2313
+ if (self.hasOptions) {
2314
+ if (results.items.length > 0) {
2315
+ $active_before = active_before && self.getOption(active_before);
2316
+ if ($active_before && $active_before.length) {
2317
+ $active = $active_before;
2318
+ } else if (self.settings.mode === 'single' && self.items.length) {
2319
+ $active = self.getOption(self.items[0]);
2320
+ }
2321
+ if (!$active || !$active.length) {
2322
+ if ($create && !self.settings.addPrecedence) {
2323
+ $active = self.getAdjacentOption($create, 1);
2324
+ } else {
2325
+ $active = $dropdown_content.find('[data-selectable]:first');
2326
+ }
2327
+ }
2328
+ } else {
2329
+ $active = $create;
2330
+ }
2331
+ self.setActiveOption($active);
2332
+ if (triggerDropdown && !self.isOpen) { self.open(); }
2333
+ } else {
2334
+ self.setActiveOption(null);
2335
+ if (triggerDropdown && self.isOpen) { self.close(); }
2336
+ }
2337
+ },
2338
+
2339
+ /**
2340
+ * Adds an available option. If it already exists,
2341
+ * nothing will happen. Note: this does not refresh
2342
+ * the options list dropdown (use `refreshOptions`
2343
+ * for that).
2344
+ *
2345
+ * Usage:
2346
+ *
2347
+ * this.addOption(data)
2348
+ *
2349
+ * @param {object|array} data
2350
+ */
2351
+ addOption: function(data) {
2352
+ var i, n, value, self = this;
2353
+
2354
+ if ($.isArray(data)) {
2355
+ for (i = 0, n = data.length; i < n; i++) {
2356
+ self.addOption(data[i]);
2357
+ }
2358
+ return;
2359
+ }
2360
+
2361
+ if (value = self.registerOption(data)) {
2362
+ self.userOptions[value] = true;
2363
+ self.lastQuery = null;
2364
+ self.trigger('option_add', value, data);
2365
+ }
2366
+ },
2367
+
2368
+ /**
2369
+ * Registers an option to the pool of options.
2370
+ *
2371
+ * @param {object} data
2372
+ * @return {boolean|string}
2373
+ */
2374
+ registerOption: function(data) {
2375
+ var key = hash_key(data[this.settings.valueField]);
2376
+ if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false;
2377
+ data.$order = data.$order || ++this.order;
2378
+ this.options[key] = data;
2379
+ return key;
2380
+ },
2381
+
2382
+ /**
2383
+ * Registers an option group to the pool of option groups.
2384
+ *
2385
+ * @param {object} data
2386
+ * @return {boolean|string}
2387
+ */
2388
+ registerOptionGroup: function(data) {
2389
+ var key = hash_key(data[this.settings.optgroupValueField]);
2390
+ if (!key) return false;
2391
+
2392
+ data.$order = data.$order || ++this.order;
2393
+ this.optgroups[key] = data;
2394
+ return key;
2395
+ },
2396
+
2397
+ /**
2398
+ * Registers a new optgroup for options
2399
+ * to be bucketed into.
2400
+ *
2401
+ * @param {string} id
2402
+ * @param {object} data
2403
+ */
2404
+ addOptionGroup: function(id, data) {
2405
+ data[this.settings.optgroupValueField] = id;
2406
+ if (id = this.registerOptionGroup(data)) {
2407
+ this.trigger('optgroup_add', id, data);
2408
+ }
2409
+ },
2410
+
2411
+ /**
2412
+ * Removes an existing option group.
2413
+ *
2414
+ * @param {string} id
2415
+ */
2416
+ removeOptionGroup: function(id) {
2417
+ if (this.optgroups.hasOwnProperty(id)) {
2418
+ delete this.optgroups[id];
2419
+ this.renderCache = {};
2420
+ this.trigger('optgroup_remove', id);
2421
+ }
2422
+ },
2423
+
2424
+ /**
2425
+ * Clears all existing option groups.
2426
+ */
2427
+ clearOptionGroups: function() {
2428
+ this.optgroups = {};
2429
+ this.renderCache = {};
2430
+ this.trigger('optgroup_clear');
2431
+ },
2432
+
2433
+ /**
2434
+ * Updates an option available for selection. If
2435
+ * it is visible in the selected items or options
2436
+ * dropdown, it will be re-rendered automatically.
2437
+ *
2438
+ * @param {string} value
2439
+ * @param {object} data
2440
+ */
2441
+ updateOption: function(value, data) {
2442
+ var self = this;
2443
+ var $item, $item_new;
2444
+ var value_new, index_item, cache_items, cache_options, order_old;
2445
+
2446
+ value = hash_key(value);
2447
+ value_new = hash_key(data[self.settings.valueField]);
2448
+
2449
+ // sanity checks
2450
+ if (value === null) return;
2451
+ if (!self.options.hasOwnProperty(value)) return;
2452
+ if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
2453
+
2454
+ order_old = self.options[value].$order;
2455
+
2456
+ // update references
2457
+ if (value_new !== value) {
2458
+ delete self.options[value];
2459
+ index_item = self.items.indexOf(value);
2460
+ if (index_item !== -1) {
2461
+ self.items.splice(index_item, 1, value_new);
2462
+ }
2463
+ }
2464
+ data.$order = data.$order || order_old;
2465
+ self.options[value_new] = data;
2466
+
2467
+ // invalidate render cache
2468
+ cache_items = self.renderCache['item'];
2469
+ cache_options = self.renderCache['option'];
2470
+
2471
+ if (cache_items) {
2472
+ delete cache_items[value];
2473
+ delete cache_items[value_new];
2474
+ }
2475
+ if (cache_options) {
2476
+ delete cache_options[value];
2477
+ delete cache_options[value_new];
2478
+ }
2479
+
2480
+ // update the item if it's selected
2481
+ if (self.items.indexOf(value_new) !== -1) {
2482
+ $item = self.getItem(value);
2483
+ $item_new = $(self.render('item', data));
2484
+ if ($item.hasClass('active')) $item_new.addClass('active');
2485
+ $item.replaceWith($item_new);
2486
+ }
2487
+
2488
+ // invalidate last query because we might have updated the sortField
2489
+ self.lastQuery = null;
2490
+
2491
+ // update dropdown contents
2492
+ if (self.isOpen) {
2493
+ self.refreshOptions(false);
2494
+ }
2495
+ },
2496
+
2497
+ /**
2498
+ * Removes a single option.
2499
+ *
2500
+ * @param {string} value
2501
+ * @param {boolean} silent
2502
+ */
2503
+ removeOption: function(value, silent) {
2504
+ var self = this;
2505
+ value = hash_key(value);
2506
+
2507
+ var cache_items = self.renderCache['item'];
2508
+ var cache_options = self.renderCache['option'];
2509
+ if (cache_items) delete cache_items[value];
2510
+ if (cache_options) delete cache_options[value];
2511
+
2512
+ delete self.userOptions[value];
2513
+ delete self.options[value];
2514
+ self.lastQuery = null;
2515
+ self.trigger('option_remove', value);
2516
+ self.removeItem(value, silent);
2517
+ },
2518
+
2519
+ /**
2520
+ * Clears all options.
2521
+ *
2522
+ * @param {boolean} silent
2523
+ */
2524
+ clearOptions: function(silent) {
2525
+ var self = this;
2526
+
2527
+ self.loadedSearches = {};
2528
+ self.userOptions = {};
2529
+ self.renderCache = {};
2530
+ var options = self.options;
2531
+ $.each(self.options, function(key, value) {
2532
+ if(self.items.indexOf(key) == -1) {
2533
+ delete options[key];
2534
+ }
2535
+ });
2536
+ self.options = self.sifter.items = options;
2537
+ self.lastQuery = null;
2538
+ self.trigger('option_clear');
2539
+ self.clear(silent);
2540
+ },
2541
+
2542
+ /**
2543
+ * Returns the jQuery element of the option
2544
+ * matching the given value.
2545
+ *
2546
+ * @param {string} value
2547
+ * @returns {object}
2548
+ */
2549
+ getOption: function(value) {
2550
+ return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2551
+ },
2552
+
2553
+ /**
2554
+ * Returns the jQuery element of the next or
2555
+ * previous selectable option.
2556
+ *
2557
+ * @param {object} $option
2558
+ * @param {int} direction can be 1 for next or -1 for previous
2559
+ * @return {object}
2560
+ */
2561
+ getAdjacentOption: function($option, direction) {
2562
+ var $options = this.$dropdown.find('[data-selectable]');
2563
+ var index = $options.index($option) + direction;
2564
+
2565
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
2566
+ },
2567
+
2568
+ /**
2569
+ * Finds the first element with a "data-value" attribute
2570
+ * that matches the given value.
2571
+ *
2572
+ * @param {mixed} value
2573
+ * @param {object} $els
2574
+ * @return {object}
2575
+ */
2576
+ getElementWithValue: function(value, $els) {
2577
+ value = hash_key(value);
2578
+
2579
+ if (typeof value !== 'undefined' && value !== null) {
2580
+ for (var i = 0, n = $els.length; i < n; i++) {
2581
+ if ($els[i].getAttribute('data-value') === value) {
2582
+ return $($els[i]);
2583
+ }
2584
+ }
2585
+ }
2586
+
2587
+ return $();
2588
+ },
2589
+
2590
+ /**
2591
+ * Returns the jQuery element of the item
2592
+ * matching the given value.
2593
+ *
2594
+ * @param {string} value
2595
+ * @returns {object}
2596
+ */
2597
+ getItem: function(value) {
2598
+ return this.getElementWithValue(value, this.$control.children());
2599
+ },
2600
+
2601
+ /**
2602
+ * "Selects" multiple items at once. Adds them to the list
2603
+ * at the current caret position.
2604
+ *
2605
+ * @param {string} value
2606
+ * @param {boolean} silent
2607
+ */
2608
+ addItems: function(values, silent) {
2609
+ this.buffer = document.createDocumentFragment();
2610
+
2611
+ var childNodes = this.$control[0].childNodes;
2612
+ for (var i = 0; i < childNodes.length; i++) {
2613
+ this.buffer.appendChild(childNodes[i]);
2614
+ }
2615
+
2616
+ var items = $.isArray(values) ? values : [values];
2617
+ for (var i = 0, n = items.length; i < n; i++) {
2618
+ this.isPending = (i < n - 1);
2619
+ this.addItem(items[i], silent);
2620
+ }
2621
+
2622
+ var control = this.$control[0];
2623
+ control.insertBefore(this.buffer, control.firstChild);
2624
+
2625
+ this.buffer = null;
2626
+ },
2627
+
2628
+ /**
2629
+ * "Selects" an item. Adds it to the list
2630
+ * at the current caret position.
2631
+ *
2632
+ * @param {string} value
2633
+ * @param {boolean} silent
2634
+ */
2635
+ addItem: function(value, silent) {
2636
+ var events = silent ? [] : ['change'];
2637
+
2638
+ debounce_events(this, events, function() {
2639
+ var $item, $option, $options;
2640
+ var self = this;
2641
+ var inputMode = self.settings.mode;
2642
+ var i, active, value_next, wasFull;
2643
+ value = hash_key(value);
2644
+
2645
+ if (self.items.indexOf(value) !== -1) {
2646
+ if (inputMode === 'single') self.close();
2647
+ return;
2648
+ }
2649
+
2650
+ if (!self.options.hasOwnProperty(value)) return;
2651
+ if (inputMode === 'single') self.clear(silent);
2652
+ if (inputMode === 'multi' && self.isFull()) return;
2653
+
2654
+ $item = $(self.render('item', self.options[value]));
2655
+ wasFull = self.isFull();
2656
+ self.items.splice(self.caretPos, 0, value);
2657
+ self.insertAtCaret($item);
2658
+ if (!self.isPending || (!wasFull && self.isFull())) {
2659
+ self.refreshState();
2660
+ }
2661
+
2662
+ if (self.isSetup) {
2663
+ $options = self.$dropdown_content.find('[data-selectable]');
2664
+
2665
+ // update menu / remove the option (if this is not one item being added as part of series)
2666
+ if (!self.isPending) {
2667
+ $option = self.getOption(value);
2668
+ value_next = self.getAdjacentOption($option, 1).attr('data-value');
2669
+ self.refreshOptions(self.isFocused && inputMode !== 'single');
2670
+ if (value_next) {
2671
+ self.setActiveOption(self.getOption(value_next));
2672
+ }
2673
+ }
2674
+
2675
+ // hide the menu if the maximum number of items have been selected or no options are left
2676
+ if (!$options.length || self.isFull()) {
2677
+ self.close();
2678
+ } else if (!self.isPending) {
2679
+ self.positionDropdown();
2680
+ }
2681
+
2682
+ self.updatePlaceholder();
2683
+ self.trigger('item_add', value, $item);
2684
+
2685
+ if (!self.isPending) {
2686
+ self.updateOriginalInput({silent: silent});
2687
+ }
2688
+ }
2689
+ });
2690
+ },
2691
+
2692
+ /**
2693
+ * Removes the selected item matching
2694
+ * the provided value.
2695
+ *
2696
+ * @param {string} value
2697
+ */
2698
+ removeItem: function(value, silent) {
2699
+ var self = this;
2700
+ var $item, i, idx;
2701
+
2702
+ $item = (value instanceof $) ? value : self.getItem(value);
2703
+ value = hash_key($item.attr('data-value'));
2704
+ i = self.items.indexOf(value);
2705
+
2706
+ if (i !== -1) {
2707
+ $item.remove();
2708
+ if ($item.hasClass('active')) {
2709
+ idx = self.$activeItems.indexOf($item[0]);
2710
+ self.$activeItems.splice(idx, 1);
2711
+ }
2712
+
2713
+ self.items.splice(i, 1);
2714
+ self.lastQuery = null;
2715
+ if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2716
+ self.removeOption(value, silent);
2717
+ }
2718
+
2719
+ if (i < self.caretPos) {
2720
+ self.setCaret(self.caretPos - 1);
2721
+ }
2722
+
2723
+ self.refreshState();
2724
+ self.updatePlaceholder();
2725
+ self.updateOriginalInput({silent: silent});
2726
+ self.positionDropdown();
2727
+ self.trigger('item_remove', value, $item);
2728
+ }
2729
+ },
2730
+
2731
+ /**
2732
+ * Invokes the `create` method provided in the
2733
+ * selectize options that should provide the data
2734
+ * for the new item, given the user input.
2735
+ *
2736
+ * Once this completes, it will be added
2737
+ * to the item list.
2738
+ *
2739
+ * @param {string} value
2740
+ * @param {boolean} [triggerDropdown]
2741
+ * @param {function} [callback]
2742
+ * @return {boolean}
2743
+ */
2744
+ createItem: function(input, triggerDropdown) {
2745
+ var self = this;
2746
+ var caret = self.caretPos;
2747
+ input = input || $.trim(self.$control_input.val() || '');
2748
+
2749
+ var callback = arguments[arguments.length - 1];
2750
+ if (typeof callback !== 'function') callback = function() {};
2751
+
2752
+ if (typeof triggerDropdown !== 'boolean') {
2753
+ triggerDropdown = true;
2754
+ }
2755
+
2756
+ if (!self.canCreate(input)) {
2757
+ callback();
2758
+ return false;
2759
+ }
2760
+
2761
+ self.lock();
2762
+
2763
+ var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2764
+ var data = {};
2765
+ data[self.settings.labelField] = input;
2766
+ data[self.settings.valueField] = input;
2767
+ return data;
2768
+ };
2769
+
2770
+ var create = once(function(data) {
2771
+ self.unlock();
2772
+
2773
+ if (!data || typeof data !== 'object') return callback();
2774
+ var value = hash_key(data[self.settings.valueField]);
2775
+ if (typeof value !== 'string') return callback();
2776
+
2777
+ self.setTextboxValue('');
2778
+ self.addOption(data);
2779
+ self.setCaret(caret);
2780
+ self.addItem(value);
2781
+ self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2782
+ callback(data);
2783
+ });
2784
+
2785
+ var output = setup.apply(this, [input, create]);
2786
+ if (typeof output !== 'undefined') {
2787
+ create(output);
2788
+ }
2789
+
2790
+ return true;
2791
+ },
2792
+
2793
+ /**
2794
+ * Re-renders the selected item lists.
2795
+ */
2796
+ refreshItems: function() {
2797
+ this.lastQuery = null;
2798
+
2799
+ if (this.isSetup) {
2800
+ this.addItem(this.items);
2801
+ }
2802
+
2803
+ this.refreshState();
2804
+ this.updateOriginalInput();
2805
+ },
2806
+
2807
+ /**
2808
+ * Updates all state-dependent attributes
2809
+ * and CSS classes.
2810
+ */
2811
+ refreshState: function() {
2812
+ this.refreshValidityState();
2813
+ this.refreshClasses();
2814
+ },
2815
+
2816
+ /**
2817
+ * Update the `required` attribute of both input and control input.
2818
+ *
2819
+ * The `required` property needs to be activated on the control input
2820
+ * for the error to be displayed at the right place. `required` also
2821
+ * needs to be temporarily deactivated on the input since the input is
2822
+ * hidden and can't show errors.
2823
+ */
2824
+ refreshValidityState: function() {
2825
+ if (!this.isRequired) return false;
2826
+
2827
+ var invalid = !this.items.length;
2828
+
2829
+ this.isInvalid = invalid;
2830
+ this.$control_input.prop('required', invalid);
2831
+ this.$input.prop('required', !invalid);
2832
+ },
2833
+
2834
+ /**
2835
+ * Updates all state-dependent CSS classes.
2836
+ */
2837
+ refreshClasses: function() {
2838
+ var self = this;
2839
+ var isFull = self.isFull();
2840
+ var isLocked = self.isLocked;
2841
+
2842
+ self.$wrapper
2843
+ .toggleClass('rtl', self.rtl);
2844
+
2845
+ self.$control
2846
+ .toggleClass('focus', self.isFocused)
2847
+ .toggleClass('disabled', self.isDisabled)
2848
+ .toggleClass('required', self.isRequired)
2849
+ .toggleClass('invalid', self.isInvalid)
2850
+ .toggleClass('locked', isLocked)
2851
+ .toggleClass('full', isFull).toggleClass('not-full', !isFull)
2852
+ .toggleClass('input-active', self.isFocused && !self.isInputHidden)
2853
+ .toggleClass('dropdown-active', self.isOpen)
2854
+ .toggleClass('has-options', !$.isEmptyObject(self.options))
2855
+ .toggleClass('has-items', self.items.length > 0);
2856
+
2857
+ self.$control_input.data('grow', !isFull && !isLocked);
2858
+ },
2859
+
2860
+ /**
2861
+ * Determines whether or not more items can be added
2862
+ * to the control without exceeding the user-defined maximum.
2863
+ *
2864
+ * @returns {boolean}
2865
+ */
2866
+ isFull: function() {
2867
+ return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2868
+ },
2869
+
2870
+ /**
2871
+ * Refreshes the original <select> or <input>
2872
+ * element to reflect the current state.
2873
+ */
2874
+ updateOriginalInput: function(opts) {
2875
+ var i, n, options, label, self = this;
2876
+ opts = opts || {};
2877
+
2878
+ if (self.tagType === TAG_SELECT) {
2879
+ options = [];
2880
+ for (i = 0, n = self.items.length; i < n; i++) {
2881
+ label = self.options[self.items[i]][self.settings.labelField] || '';
2882
+ options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
2883
+ }
2884
+ if (!options.length && !this.$input.attr('multiple')) {
2885
+ options.push('<option value="" selected="selected"></option>');
2886
+ }
2887
+ self.$input.html(options.join(''));
2888
+ } else {
2889
+ self.$input.val(self.getValue());
2890
+ self.$input.attr('value',self.$input.val());
2891
+ }
2892
+
2893
+ if (self.isSetup) {
2894
+ if (!opts.silent) {
2895
+ self.trigger('change', self.$input.val());
2896
+ }
2897
+ }
2898
+ },
2899
+
2900
+ /**
2901
+ * Shows/hide the input placeholder depending
2902
+ * on if there items in the list already.
2903
+ */
2904
+ updatePlaceholder: function() {
2905
+ if (!this.settings.placeholder) return;
2906
+ var $input = this.$control_input;
2907
+
2908
+ if (this.items.length) {
2909
+ $input.removeAttr('placeholder');
2910
+ } else {
2911
+ $input.attr('placeholder', this.settings.placeholder);
2912
+ }
2913
+ $input.triggerHandler('update', {force: true});
2914
+ },
2915
+
2916
+ /**
2917
+ * Shows the autocomplete dropdown containing
2918
+ * the available options.
2919
+ */
2920
+ open: function() {
2921
+ var self = this;
2922
+
2923
+ if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
2924
+ self.focus();
2925
+ self.isOpen = true;
2926
+ self.refreshState();
2927
+ self.$dropdown.css({visibility: 'hidden', display: 'block'});
2928
+ self.positionDropdown();
2929
+ self.$dropdown.css({visibility: 'visible'});
2930
+ self.trigger('dropdown_open', self.$dropdown);
2931
+ },
2932
+
2933
+ /**
2934
+ * Closes the autocomplete dropdown menu.
2935
+ */
2936
+ close: function() {
2937
+ var self = this;
2938
+ var trigger = self.isOpen;
2939
+
2940
+ if (self.settings.mode === 'single' && self.items.length) {
2941
+ self.hideInput();
2942
+
2943
+ // Do not trigger blur while inside a blur event,
2944
+ // this fixes some weird tabbing behavior in FF and IE.
2945
+ // See #1164
2946
+ if (!self.isBlurring) {
2947
+ self.$control_input.blur(); // close keyboard on iOS
2948
+ }
2949
+ }
2950
+
2951
+ self.isOpen = false;
2952
+ self.$dropdown.hide();
2953
+ self.setActiveOption(null);
2954
+ self.refreshState();
2955
+
2956
+ if (trigger) self.trigger('dropdown_close', self.$dropdown);
2957
+ },
2958
+
2959
+ /**
2960
+ * Calculates and applies the appropriate
2961
+ * position of the dropdown.
2962
+ */
2963
+ positionDropdown: function() {
2964
+ var $control = this.$control;
2965
+ var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2966
+ offset.top += $control.outerHeight(true);
2967
+
2968
+ this.$dropdown.css({
2969
+ width : $control[0].getBoundingClientRect().width,
2970
+ top : offset.top,
2971
+ left : offset.left
2972
+ });
2973
+ },
2974
+
2975
+ /**
2976
+ * Resets / clears all selected items
2977
+ * from the control.
2978
+ *
2979
+ * @param {boolean} silent
2980
+ */
2981
+ clear: function(silent) {
2982
+ var self = this;
2983
+
2984
+ if (!self.items.length) return;
2985
+ self.$control.children(':not(input)').remove();
2986
+ self.items = [];
2987
+ self.lastQuery = null;
2988
+ self.setCaret(0);
2989
+ self.setActiveItem(null);
2990
+ self.updatePlaceholder();
2991
+ self.updateOriginalInput({silent: silent});
2992
+ self.refreshState();
2993
+ self.showInput();
2994
+ self.trigger('clear');
2995
+ },
2996
+
2997
+ /**
2998
+ * A helper method for inserting an element
2999
+ * at the current caret position.
3000
+ *
3001
+ * @param {object} $el
3002
+ */
3003
+ insertAtCaret: function($el) {
3004
+ var caret = Math.min(this.caretPos, this.items.length);
3005
+ var el = $el[0];
3006
+ var target = this.buffer || this.$control[0];
3007
+
3008
+ if (caret === 0) {
3009
+ target.insertBefore(el, target.firstChild);
3010
+ } else {
3011
+ target.insertBefore(el, target.childNodes[caret]);
3012
+ }
3013
+
3014
+ this.setCaret(caret + 1);
3015
+ },
3016
+
3017
+ /**
3018
+ * Removes the current selected item(s).
3019
+ *
3020
+ * @param {object} e (optional)
3021
+ * @returns {boolean}
3022
+ */
3023
+ deleteSelection: function(e) {
3024
+ var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
3025
+ var self = this;
3026
+
3027
+ direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
3028
+ selection = getSelection(self.$control_input[0]);
3029
+
3030
+ if (self.$activeOption && !self.settings.hideSelected) {
3031
+ option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
3032
+ }
3033
+
3034
+ // determine items that will be removed
3035
+ values = [];
3036
+
3037
+ if (self.$activeItems.length) {
3038
+ $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
3039
+ caret = self.$control.children(':not(input)').index($tail);
3040
+ if (direction > 0) { caret++; }
3041
+
3042
+ for (i = 0, n = self.$activeItems.length; i < n; i++) {
3043
+ values.push($(self.$activeItems[i]).attr('data-value'));
3044
+ }
3045
+ if (e) {
3046
+ e.preventDefault();
3047
+ e.stopPropagation();
3048
+ }
3049
+ } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
3050
+ if (direction < 0 && selection.start === 0 && selection.length === 0) {
3051
+ values.push(self.items[self.caretPos - 1]);
3052
+ } else if (direction > 0 && selection.start === self.$control_input.val().length) {
3053
+ values.push(self.items[self.caretPos]);
3054
+ }
3055
+ }
3056
+
3057
+ // allow the callback to abort
3058
+ if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
3059
+ return false;
3060
+ }
3061
+
3062
+ // perform removal
3063
+ if (typeof caret !== 'undefined') {
3064
+ self.setCaret(caret);
3065
+ }
3066
+ while (values.length) {
3067
+ self.removeItem(values.pop());
3068
+ }
3069
+
3070
+ self.showInput();
3071
+ self.positionDropdown();
3072
+ self.refreshOptions(true);
3073
+
3074
+ // select previous option
3075
+ if (option_select) {
3076
+ $option_select = self.getOption(option_select);
3077
+ if ($option_select.length) {
3078
+ self.setActiveOption($option_select);
3079
+ }
3080
+ }
3081
+
3082
+ return true;
3083
+ },
3084
+
3085
+ /**
3086
+ * Selects the previous / next item (depending
3087
+ * on the `direction` argument).
3088
+ *
3089
+ * > 0 - right
3090
+ * < 0 - left
3091
+ *
3092
+ * @param {int} direction
3093
+ * @param {object} e (optional)
3094
+ */
3095
+ advanceSelection: function(direction, e) {
3096
+ var tail, selection, idx, valueLength, cursorAtEdge, $tail;
3097
+ var self = this;
3098
+
3099
+ if (direction === 0) return;
3100
+ if (self.rtl) direction *= -1;
3101
+
3102
+ tail = direction > 0 ? 'last' : 'first';
3103
+ selection = getSelection(self.$control_input[0]);
3104
+
3105
+ if (self.isFocused && !self.isInputHidden) {
3106
+ valueLength = self.$control_input.val().length;
3107
+ cursorAtEdge = direction < 0
3108
+ ? selection.start === 0 && selection.length === 0
3109
+ : selection.start === valueLength;
3110
+
3111
+ if (cursorAtEdge && !valueLength) {
3112
+ self.advanceCaret(direction, e);
3113
+ }
3114
+ } else {
3115
+ $tail = self.$control.children('.active:' + tail);
3116
+ if ($tail.length) {
3117
+ idx = self.$control.children(':not(input)').index($tail);
3118
+ self.setActiveItem(null);
3119
+ self.setCaret(direction > 0 ? idx + 1 : idx);
3120
+ }
3121
+ }
3122
+ },
3123
+
3124
+ /**
3125
+ * Moves the caret left / right.
3126
+ *
3127
+ * @param {int} direction
3128
+ * @param {object} e (optional)
3129
+ */
3130
+ advanceCaret: function(direction, e) {
3131
+ var self = this, fn, $adj;
3132
+
3133
+ if (direction === 0) return;
3134
+
3135
+ fn = direction > 0 ? 'next' : 'prev';
3136
+ if (self.isShiftDown) {
3137
+ $adj = self.$control_input[fn]();
3138
+ if ($adj.length) {
3139
+ self.hideInput();
3140
+ self.setActiveItem($adj);
3141
+ e && e.preventDefault();
3142
+ }
3143
+ } else {
3144
+ self.setCaret(self.caretPos + direction);
3145
+ }
3146
+ },
3147
+
3148
+ /**
3149
+ * Moves the caret to the specified index.
3150
+ *
3151
+ * @param {int} i
3152
+ */
3153
+ setCaret: function(i) {
3154
+ var self = this;
3155
+
3156
+ if (self.settings.mode === 'single') {
3157
+ i = self.items.length;
3158
+ } else {
3159
+ i = Math.max(0, Math.min(self.items.length, i));
3160
+ }
3161
+
3162
+ if(!self.isPending) {
3163
+ // the input must be moved by leaving it in place and moving the
3164
+ // siblings, due to the fact that focus cannot be restored once lost
3165
+ // on mobile webkit devices
3166
+ var j, n, fn, $children, $child;
3167
+ $children = self.$control.children(':not(input)');
3168
+ for (j = 0, n = $children.length; j < n; j++) {
3169
+ $child = $($children[j]).detach();
3170
+ if (j < i) {
3171
+ self.$control_input.before($child);
3172
+ } else {
3173
+ self.$control.append($child);
3174
+ }
3175
+ }
3176
+ }
3177
+
3178
+ self.caretPos = i;
3179
+ },
3180
+
3181
+ /**
3182
+ * Disables user input on the control. Used while
3183
+ * items are being asynchronously created.
3184
+ */
3185
+ lock: function() {
3186
+ this.close();
3187
+ this.isLocked = true;
3188
+ this.refreshState();
3189
+ },
3190
+
3191
+ /**
3192
+ * Re-enables user input on the control.
3193
+ */
3194
+ unlock: function() {
3195
+ this.isLocked = false;
3196
+ this.refreshState();
3197
+ },
3198
+
3199
+ /**
3200
+ * Disables user input on the control completely.
3201
+ * While disabled, it cannot receive focus.
3202
+ */
3203
+ disable: function() {
3204
+ var self = this;
3205
+ self.$input.prop('disabled', true);
3206
+ self.$control_input.prop('disabled', true).prop('tabindex', -1);
3207
+ self.isDisabled = true;
3208
+ self.lock();
3209
+ },
3210
+
3211
+ /**
3212
+ * Enables the control so that it can respond
3213
+ * to focus and user input.
3214
+ */
3215
+ enable: function() {
3216
+ var self = this;
3217
+ self.$input.prop('disabled', false);
3218
+ self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
3219
+ self.isDisabled = false;
3220
+ self.unlock();
3221
+ },
3222
+
3223
+ /**
3224
+ * Completely destroys the control and
3225
+ * unbinds all event listeners so that it can
3226
+ * be garbage collected.
3227
+ */
3228
+ destroy: function() {
3229
+ var self = this;
3230
+ var eventNS = self.eventNS;
3231
+ var revertSettings = self.revertSettings;
3232
+
3233
+ self.trigger('destroy');
3234
+ self.off();
3235
+ self.$wrapper.remove();
3236
+ self.$dropdown.remove();
3237
+
3238
+ self.$input
3239
+ .html('')
3240
+ .append(revertSettings.$children)
3241
+ .removeAttr('tabindex')
3242
+ .removeClass('selectized')
3243
+ .attr({tabindex: revertSettings.tabindex})
3244
+ .show();
3245
+
3246
+ self.$control_input.removeData('grow');
3247
+ self.$input.removeData('selectize');
3248
+
3249
+ if (--Selectize.count == 0 && Selectize.$testInput) {
3250
+ Selectize.$testInput.remove();
3251
+ Selectize.$testInput = undefined;
3252
+ }
3253
+
3254
+ $(window).off(eventNS);
3255
+ $(document).off(eventNS);
3256
+ $(document.body).off(eventNS);
3257
+
3258
+ delete self.$input[0].selectize;
3259
+ },
3260
+
3261
+ /**
3262
+ * A helper method for rendering "item" and
3263
+ * "option" templates, given the data.
3264
+ *
3265
+ * @param {string} templateName
3266
+ * @param {object} data
3267
+ * @returns {string}
3268
+ */
3269
+ render: function(templateName, data) {
3270
+ var value, id, label;
3271
+ var html = '';
3272
+ var cache = false;
3273
+ var self = this;
3274
+ var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
3275
+
3276
+ if (templateName === 'option' || templateName === 'item') {
3277
+ value = hash_key(data[self.settings.valueField]);
3278
+ cache = !!value;
3279
+ }
3280
+
3281
+ // pull markup from cache if it exists
3282
+ if (cache) {
3283
+ if (!isset(self.renderCache[templateName])) {
3284
+ self.renderCache[templateName] = {};
3285
+ }
3286
+ if (self.renderCache[templateName].hasOwnProperty(value)) {
3287
+ return self.renderCache[templateName][value];
3288
+ }
3289
+ }
3290
+
3291
+ // render markup
3292
+ html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
3293
+
3294
+ // add mandatory attributes
3295
+ if (templateName === 'option' || templateName === 'option_create') {
3296
+ if (!data[self.settings.disabledField]) {
3297
+ html.attr('data-selectable', '');
3298
+ }
3299
+ }
3300
+ else if (templateName === 'optgroup') {
3301
+ id = data[self.settings.optgroupValueField] || '';
3302
+ html.attr('data-group', id);
3303
+ if(data[self.settings.disabledField]) {
3304
+ html.attr('data-disabled', '');
3305
+ }
3306
+ }
3307
+ if (templateName === 'option' || templateName === 'item') {
3308
+ html.attr('data-value', value || '');
3309
+ }
3310
+
3311
+ // update cache
3312
+ if (cache) {
3313
+ self.renderCache[templateName][value] = html[0];
3314
+ }
3315
+
3316
+ return html[0];
3317
+ },
3318
+
3319
+ /**
3320
+ * Clears the render cache for a template. If
3321
+ * no template is given, clears all render
3322
+ * caches.
3323
+ *
3324
+ * @param {string} templateName
3325
+ */
3326
+ clearCache: function(templateName) {
3327
+ var self = this;
3328
+ if (typeof templateName === 'undefined') {
3329
+ self.renderCache = {};
3330
+ } else {
3331
+ delete self.renderCache[templateName];
3332
+ }
3333
+ },
3334
+
3335
+ /**
3336
+ * Determines whether or not to display the
3337
+ * create item prompt, given a user input.
3338
+ *
3339
+ * @param {string} input
3340
+ * @return {boolean}
3341
+ */
3342
+ canCreate: function(input) {
3343
+ var self = this;
3344
+ if (!self.settings.create) return false;
3345
+ var filter = self.settings.createFilter;
3346
+ return input.length
3347
+ && (typeof filter !== 'function' || filter.apply(self, [input]))
3348
+ && (typeof filter !== 'string' || new RegExp(filter).test(input))
3349
+ && (!(filter instanceof RegExp) || filter.test(input));
3350
+ }
3351
+
3352
+ });
3353
+
3354
+
3355
+ Selectize.count = 0;
3356
+ Selectize.defaults = {
3357
+ options: [],
3358
+ optgroups: [],
3359
+
3360
+ plugins: [],
3361
+ delimiter: ',',
3362
+ splitOn: null, // regexp or string for splitting up values from a paste command
3363
+ persist: true,
3364
+ diacritics: true,
3365
+ create: false,
3366
+ createOnBlur: false,
3367
+ createFilter: null,
3368
+ highlight: true,
3369
+ openOnFocus: true,
3370
+ maxOptions: 1000,
3371
+ maxItems: null,
3372
+ hideSelected: null,
3373
+ addPrecedence: false,
3374
+ selectOnTab: true,
3375
+ preload: false,
3376
+ allowEmptyOption: false,
3377
+ closeAfterSelect: false,
3378
+
3379
+ scrollDuration: 60,
3380
+ loadThrottle: 300,
3381
+ loadingClass: 'loading',
3382
+
3383
+ dataAttr: 'data-data',
3384
+ optgroupField: 'optgroup',
3385
+ valueField: 'value',
3386
+ labelField: 'text',
3387
+ disabledField: 'disabled',
3388
+ optgroupLabelField: 'label',
3389
+ optgroupValueField: 'value',
3390
+ lockOptgroupOrder: false,
3391
+
3392
+ sortField: '$order',
3393
+ searchField: ['text'],
3394
+ searchConjunction: 'and',
3395
+
3396
+ mode: null,
3397
+ wrapperClass: 'selectize-control',
3398
+ inputClass: 'selectize-input',
3399
+ dropdownClass: 'selectize-dropdown',
3400
+ dropdownContentClass: 'selectize-dropdown-content',
3401
+
3402
+ dropdownParent: null,
3403
+
3404
+ copyClassesToDropdown: true,
3405
+
3406
+ /*
3407
  load : null, // function(query, callback) { ... }
3408
  score : null, // function(search) { ... }
3409
  onInitialize : null, // function() { ... }
3422
  onType : null, // function(str) { ... }
3423
  onDelete : null, // function(values) { ... }
3424
  */
3425
+
3426
+ render: {
3427
+ /*
3428
  item: null,
3429
  optgroup: null,
3430
  optgroup_header: null,
3431
  option: null,
3432
  option_create: null
3433
  */
3434
+ }
3435
+ };
3436
+
3437
+
3438
+ $.fn.selectize = function(settings_user) {
3439
+ var defaults = $.fn.selectize.defaults;
3440
+ var settings = $.extend({}, defaults, settings_user);
3441
+ var attr_data = settings.dataAttr;
3442
+ var field_label = settings.labelField;
3443
+ var field_value = settings.valueField;
3444
+ var field_disabled = settings.disabledField;
3445
+ var field_optgroup = settings.optgroupField;
3446
+ var field_optgroup_label = settings.optgroupLabelField;
3447
+ var field_optgroup_value = settings.optgroupValueField;
3448
+
3449
+ /**
3450
+ * Initializes selectize from a <input type="text"> element.
3451
+ *
3452
+ * @param {object} $input
3453
+ * @param {object} settings_element
3454
+ */
3455
+ var init_textbox = function($input, settings_element) {
3456
+ var i, n, values, option;
3457
+
3458
+ var data_raw = $input.attr(attr_data);
3459
+
3460
+ if (!data_raw) {
3461
+ var value = $.trim($input.val() || '');
3462
+ if (!settings.allowEmptyOption && !value.length) return;
3463
+ values = value.split(settings.delimiter);
3464
+ for (i = 0, n = values.length; i < n; i++) {
3465
+ option = {};
3466
+ option[field_label] = values[i];
3467
+ option[field_value] = values[i];
3468
+ settings_element.options.push(option);
3469
+ }
3470
+ settings_element.items = values;
3471
+ } else {
3472
+ settings_element.options = JSON.parse(data_raw);
3473
+ for (i = 0, n = settings_element.options.length; i < n; i++) {
3474
+ settings_element.items.push(settings_element.options[i][field_value]);
3475
+ }
3476
+ }
3477
+ };
3478
+
3479
+ /**
3480
+ * Initializes selectize from a <select> element.
3481
+ *
3482
+ * @param {object} $input
3483
+ * @param {object} settings_element
3484
+ */
3485
+ var init_select = function($input, settings_element) {
3486
+ var i, n, tagName, $children, order = 0;
3487
+ var options = settings_element.options;
3488
+ var optionsMap = {};
3489
+
3490
+ var readData = function($el) {
3491
+ var data = attr_data && $el.attr(attr_data);
3492
+ if (typeof data === 'string' && data.length) {
3493
+ return JSON.parse(data);
3494
+ }
3495
+ return null;
3496
+ };
3497
+
3498
+ var addOption = function($option, group) {
3499
+ $option = $($option);
3500
+
3501
+ var value = hash_key($option.val());
3502
+ if (!value && !settings.allowEmptyOption) return;
3503
+
3504
+ // if the option already exists, it's probably been
3505
+ // duplicated in another optgroup. in this case, push
3506
+ // the current group to the "optgroup" property on the
3507
+ // existing option so that it's rendered in both places.
3508
+ if (optionsMap.hasOwnProperty(value)) {
3509
+ if (group) {
3510
+ var arr = optionsMap[value][field_optgroup];
3511
+ if (!arr) {
3512
+ optionsMap[value][field_optgroup] = group;
3513
+ } else if (!$.isArray(arr)) {
3514
+ optionsMap[value][field_optgroup] = [arr, group];
3515
+ } else {
3516
+ arr.push(group);
3517
+ }
3518
+ }
3519
+ return;
3520
+ }
3521
+
3522
+ var option = readData($option) || {};
3523
+ option[field_label] = option[field_label] || $option.text();
3524
+ option[field_value] = option[field_value] || value;
3525
+ option[field_disabled] = option[field_disabled] || $option.prop('disabled');
3526
+ option[field_optgroup] = option[field_optgroup] || group;
3527
+
3528
+ optionsMap[value] = option;
3529
+ options.push(option);
3530
+
3531
+ if ($option.is(':selected')) {
3532
+ settings_element.items.push(value);
3533
+ }
3534
+ };
3535
+
3536
+ var addGroup = function($optgroup) {
3537
+ var i, n, id, optgroup, $options;
3538
+
3539
+ $optgroup = $($optgroup);
3540
+ id = $optgroup.attr('label');
3541
+
3542
+ if (id) {
3543
+ optgroup = readData($optgroup) || {};
3544
+ optgroup[field_optgroup_label] = id;
3545
+ optgroup[field_optgroup_value] = id;
3546
+ optgroup[field_disabled] = $optgroup.prop('disabled');
3547
+ settings_element.optgroups.push(optgroup);
3548
+ }
3549
+
3550
+ $options = $('option', $optgroup);
3551
+ for (i = 0, n = $options.length; i < n; i++) {
3552
+ addOption($options[i], id);
3553
+ }
3554
+ };
3555
+
3556
+ settings_element.maxItems = $input.attr('multiple') ? null : 1;
3557
+
3558
+ $children = $input.children();
3559
+ for (i = 0, n = $children.length; i < n; i++) {
3560
+ tagName = $children[i].tagName.toLowerCase();
3561
+ if (tagName === 'optgroup') {
3562
+ addGroup($children[i]);
3563
+ } else if (tagName === 'option') {
3564
+ addOption($children[i]);
3565
+ }
3566
+ }
3567
+ };
3568
+
3569
+ return this.each(function() {
3570
+ if (this.selectize) return;
3571
+
3572
+ var instance;
3573
+ var $input = $(this);
3574
+ var tag_name = this.tagName.toLowerCase();
3575
+ var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
3576
+ if (!placeholder && !settings.allowEmptyOption) {
3577
+ placeholder = $input.children('option[value=""]').text();
3578
+ }
3579
+
3580
+ var settings_element = {
3581
+ 'placeholder' : placeholder,
3582
+ 'options' : [],
3583
+ 'optgroups' : [],
3584
+ 'items' : []
3585
+ };
3586
+
3587
+ if (tag_name === 'select') {
3588
+ init_select($input, settings_element);
3589
+ } else {
3590
+ init_textbox($input, settings_element);
3591
+ }
3592
+
3593
+ instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
3594
+ });
3595
+ };
3596
+
3597
+ $.fn.selectize.defaults = Selectize.defaults;
3598
+ $.fn.selectize.support = {
3599
+ validity: SUPPORTS_VALIDITY_API
3600
+ };
3601
+
3602
+
3603
+ Selectize.define("autofill_disable", function (options) {
3604
+ var self = this;
3605
+
3606
+ self.setup = (function () {
3607
+ var original = self.setup;
3608
+ return function () {
3609
+ original.apply(self, arguments);
3610
+
3611
+ // https://stackoverflow.com/questions/30053167/autocomplete-off-vs-false
3612
+ self.$control_input.attr({ autocomplete: "new-password", autofill: "no" });
3613
+ };
3614
+ })();
3615
+ });
3616
+
3617
+
3618
+ Selectize.define('drag_drop', function(options) {
3619
+ if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
3620
+ if (this.settings.mode !== 'multi') return;
3621
+ var self = this;
3622
+
3623
+ self.lock = (function() {
3624
+ var original = self.lock;
3625
+ return function() {
3626
+ var sortable = self.$control.data('sortable');
3627
+ if (sortable) sortable.disable();
3628
+ return original.apply(self, arguments);
3629
+ };
3630
+ })();
3631
+
3632
+ self.unlock = (function() {
3633
+ var original = self.unlock;
3634
+ return function() {
3635
+ var sortable = self.$control.data('sortable');
3636
+ if (sortable) sortable.enable();
3637
+ return original.apply(self, arguments);
3638
+ };
3639
+ })();
3640
+
3641
+ self.setup = (function() {
3642
+ var original = self.setup;
3643
+ return function() {
3644
+ original.apply(this, arguments);
3645
+
3646
+ var $control = self.$control.sortable({
3647
+ items: '[data-value]',
3648
+ forcePlaceholderSize: true,
3649
+ disabled: self.isLocked,
3650
+ start: function(e, ui) {
3651
+ ui.placeholder.css('width', ui.helper.css('width'));
3652
+ $control.css({overflow: 'visible'});
3653
+ },
3654
+ stop: function() {
3655
+ $control.css({overflow: 'hidden'});
3656
+ var active = self.$activeItems ? self.$activeItems.slice() : null;
3657
+ var values = [];
3658
+ $control.children('[data-value]').each(function() {
3659
+ values.push($(this).attr('data-value'));
3660
+ });
3661
+ self.setValue(values);
3662
+ self.setActiveItem(active);
3663
+ }
3664
+ });
3665
+ };
3666
+ })();
3667
+
3668
+ });
3669
+
3670
+ Selectize.define('dropdown_header', function(options) {
3671
+ var self = this;
3672
+
3673
+ options = $.extend({
3674
+ title : 'Untitled',
3675
+ headerClass : 'selectize-dropdown-header',
3676
+ titleRowClass : 'selectize-dropdown-header-title',
3677
+ labelClass : 'selectize-dropdown-header-label',
3678
+ closeClass : 'selectize-dropdown-header-close',
3679
+
3680
+ html: function(data) {
3681
+ return (
3682
+ '<div class="' + data.headerClass + '">' +
3683
+ '<div class="' + data.titleRowClass + '">' +
3684
+ '<span class="' + data.labelClass + '">' + data.title + '</span>' +
3685
+ '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
3686
+ '</div>' +
3687
+ '</div>'
3688
+ );
3689
+ }
3690
+ }, options);
3691
+
3692
+ self.setup = (function() {
3693
+ var original = self.setup;
3694
+ return function() {
3695
+ original.apply(self, arguments);
3696
+ self.$dropdown_header = $(options.html(options));
3697
+ self.$dropdown.prepend(self.$dropdown_header);
3698
+ };
3699
+ })();
3700
+
3701
+ });
3702
+
3703
+ Selectize.define('optgroup_columns', function(options) {
3704
+ var self = this;
3705
+
3706
+ options = $.extend({
3707
+ equalizeWidth : true,
3708
+ equalizeHeight : true
3709
+ }, options);
3710
+
3711
+ this.getAdjacentOption = function($option, direction) {
3712
+ var $options = $option.closest('[data-group]').find('[data-selectable]');
3713
+ var index = $options.index($option) + direction;
3714
+
3715
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
3716
+ };
3717
+
3718
+ this.onKeyDown = (function() {
3719
+ var original = self.onKeyDown;
3720
+ return function(e) {
3721
+ var index, $option, $options, $optgroup;
3722
+
3723
+ if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
3724
+ self.ignoreHover = true;
3725
+ $optgroup = this.$activeOption.closest('[data-group]');
3726
+ index = $optgroup.find('[data-selectable]').index(this.$activeOption);
3727
+
3728
+ if(e.keyCode === KEY_LEFT) {
3729
+ $optgroup = $optgroup.prev('[data-group]');
3730
+ } else {
3731
+ $optgroup = $optgroup.next('[data-group]');
3732
+ }
3733
+
3734
+ $options = $optgroup.find('[data-selectable]');
3735
+ $option = $options.eq(Math.min($options.length - 1, index));
3736
+ if ($option.length) {
3737
+ this.setActiveOption($option);
3738
+ }
3739
+ return;
3740
+ }
3741
+
3742
+ return original.apply(this, arguments);
3743
+ };
3744
+ })();
3745
+
3746
+ var getScrollbarWidth = function() {
3747
+ var div;
3748
+ var width = getScrollbarWidth.width;
3749
+ var doc = document;
3750
+
3751
+ if (typeof width === 'undefined') {
3752
+ div = doc.createElement('div');
3753
+ div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
3754
+ div = div.firstChild;
3755
+ doc.body.appendChild(div);
3756
+ width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
3757
+ doc.body.removeChild(div);
3758
+ }
3759
+ return width;
3760
+ };
3761
+
3762
+ var equalizeSizes = function() {
3763
+ var i, n, height_max, width, width_last, width_parent, $optgroups;
3764
+
3765
+ $optgroups = $('[data-group]', self.$dropdown_content);
3766
+ n = $optgroups.length;
3767
+ if (!n || !self.$dropdown_content.width()) return;
3768
+
3769
+ if (options.equalizeHeight) {
3770
+ height_max = 0;
3771
+ for (i = 0; i < n; i++) {
3772
+ height_max = Math.max(height_max, $optgroups.eq(i).height());
3773
+ }
3774
+ $optgroups.css({height: height_max});
3775
+ }
3776
+
3777
+ if (options.equalizeWidth) {
3778
+ width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
3779
+ width = Math.round(width_parent / n);
3780
+ $optgroups.css({width: width});
3781
+ if (n > 1) {
3782
+ width_last = width_parent - width * (n - 1);
3783
+ $optgroups.eq(n - 1).css({width: width_last});
3784
+ }
3785
+ }
3786
+ };
3787
+
3788
+ if (options.equalizeHeight || options.equalizeWidth) {
3789
+ hook.after(this, 'positionDropdown', equalizeSizes);
3790
+ hook.after(this, 'refreshOptions', equalizeSizes);
3791
+ }
3792
+
3793
+
3794
+ });
3795
+
3796
+ Selectize.define('remove_button', function(options) {
3797
+ options = $.extend({
3798
+ label : '&times;',
3799
+ title : 'Remove',
3800
+ className : 'remove',
3801
+ append : true
3802
+ }, options);
3803
+
3804
+ var singleClose = function(thisRef, options) {
3805
+
3806
+ options.className = 'remove-single';
3807
+
3808
+ var self = thisRef;
3809
+ var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3810
+
3811
+ /**
3812
+ * Appends an element as a child (with raw HTML).
3813
+ *
3814
+ * @param {string} html_container
3815
+ * @param {string} html_element
3816
+ * @return {string}
3817
+ */
3818
+ var append = function(html_container, html_element) {
3819
+ return $('<span>').append(html_container)
3820
+ .append(html_element);
3821
+ };
3822
+
3823
+ thisRef.setup = (function() {
3824
+ var original = self.setup;
3825
+ return function() {
3826
+ // override the item rendering method to add the button to each
3827
+ if (options.append) {
3828
+ var id = $(self.$input.context).attr('id');
3829
+ var selectizer = $('#'+id);
3830
+
3831
+ var render_item = self.settings.render.item;
3832
+ self.settings.render.item = function(data) {
3833
+ return append(render_item.apply(thisRef, arguments), html);
3834
+ };
3835
+ }
3836
+
3837
+ original.apply(thisRef, arguments);
3838
+
3839
+ // add event listener
3840
+ thisRef.$control.on('click', '.' + options.className, function(e) {
3841
+ e.preventDefault();
3842
+ if (self.isLocked) return;
3843
+
3844
+ self.clear();
3845
+ });
3846
+
3847
+ };
3848
+ })();
3849
+ };
3850
+
3851
+ var multiClose = function(thisRef, options) {
3852
+
3853
+ var self = thisRef;
3854
+ var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3855
+
3856
+ /**
3857
+ * Appends an element as a child (with raw HTML).
3858
+ *
3859
+ * @param {string} html_container
3860
+ * @param {string} html_element
3861
+ * @return {string}
3862
+ */
3863
+ var append = function(html_container, html_element) {
3864
+ var pos = html_container.search(/(<\/[^>]+>\s*)$/);
3865
+ return html_container.substring(0, pos) + html_element + html_container.substring(pos);
3866
+ };
3867
+
3868
+ thisRef.setup = (function() {
3869
+ var original = self.setup;
3870
+ return function() {
3871
+ // override the item rendering method to add the button to each
3872
+ if (options.append) {
3873
+ var render_item = self.settings.render.item;
3874
+ self.settings.render.item = function(data) {
3875
+ return append(render_item.apply(thisRef, arguments), html);
3876
+ };
3877
+ }
3878
+
3879
+ original.apply(thisRef, arguments);
3880
+
3881
+ // add event listener
3882
+ thisRef.$control.on('click', '.' + options.className, function(e) {
3883
+ e.preventDefault();
3884
+ if (self.isLocked) return;
3885
+
3886
+ var $item = $(e.currentTarget).parent();
3887
+ self.setActiveItem($item);
3888
+ if (self.deleteSelection()) {
3889
+ self.setCaret(self.items.length);
3890
+ }
3891
+ return false;
3892
+ });
3893
+
3894
+ };
3895
+ })();
3896
+ };
3897
+
3898
+ if (this.settings.mode === 'single') {
3899
+ singleClose(this, options);
3900
+ return;
3901
+ } else {
3902
+ multiClose(this, options);
3903
+ }
3904
+ });
3905
+
3906
+
3907
+ Selectize.define('restore_on_backspace', function(options) {
3908
+ var self = this;
3909
+
3910
+ options.text = options.text || function(option) {
3911
+ return option[this.settings.labelField];
3912
+ };
3913
+
3914
+ this.onKeyDown = (function() {
3915
+ var original = self.onKeyDown;
3916
+ return function(e) {
3917
+ var index, option;
3918
+ if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3919
+ index = this.caretPos - 1;
3920
+ if (index >= 0 && index < this.items.length) {
3921
+ option = this.options[this.items[index]];
3922
+ if (this.deleteSelection(e)) {
3923
+ this.setTextboxValue(options.text.apply(this, [option]));
3924
+ this.refreshOptions(true);
3925
+ }
3926
+ e.preventDefault();
3927
+ return;
3928
+ }
3929
+ }
3930
+ return original.apply(this, arguments);
3931
+ };
3932
+ })();
3933
+ });
3934
+
3935
+
3936
+ return Selectize;
3937
  }));
admin/js/view-category-filter.js CHANGED
@@ -28,7 +28,7 @@
28
  var searchTerm = event.target.value,
29
  $listItems = $(this).closest(".view-category-list-panel").find('.view-category-list li');
30
 
31
- if ($.trim(searchTerm)) {
32
 
33
  $listItems.hide().filter(function () {
34
  return $(this).text().toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
28
  var searchTerm = event.target.value,
29
  $listItems = $(this).closest(".view-category-list-panel").find('.view-category-list li');
30
 
31
+ if (searchTerm.trim()) {
32
 
33
  $listItems.hide().filter(function () {
34
  return $(this).text().toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
admin/js/views.js CHANGED
@@ -44,7 +44,7 @@ Array.max = function (array) {
44
  while (patt.test(cn)) {
45
  cn = cn.replace(patt, ' ');
46
  }
47
- it.className = $.trim(cn);
48
  });
49
 
50
  return !additions ? self : self.addClass(additions);
@@ -144,7 +144,7 @@ jQuery(document).ready(function ($) {
144
 
145
  // the shortcode code
146
  $('#view-shortcode').on('focus', function () {
147
- $(this).select();
148
  });
149
 
150
  $('.expand-cats').on('click', function () {
@@ -157,7 +157,7 @@ jQuery(document).ready(function ($) {
157
  $categoryDivs.removeClass('short-panel').addClass('tall-panel');
158
  $(this).val('collapse list');
159
  }
160
- $(this).blur();
161
  });
162
 
163
  // Masonry example
@@ -267,7 +267,7 @@ jQuery(document).ready(function ($) {
267
  } else {
268
  viewDefaultMore.removeProp('disabled');
269
  }
270
- viewDefaultMore.change();
271
 
272
  };
273
 
@@ -505,11 +505,11 @@ jQuery(document).ready(function ($) {
505
 
506
  // Force default template since we have more than one group of templates.
507
  $('input[type=radio][name=\'view[data][template]\'][value=\'default\']').prop('checked', true);
508
- templateRadios.change();
509
  $('input[type=radio][name=\'view[data][form-template]\'][value=\'default-form\']').prop('checked', true);
510
- // formTemplateRadios.change();
511
- layoutRadios.change();
512
- backgroundRadios.change();
513
  });
514
 
515
  /**
@@ -600,7 +600,7 @@ jQuery(document).ready(function ($) {
600
  $('select[id^="view-fieldtext"]').on('change', function () {
601
  if ($(this).val() === 'custom') {
602
  var key = $(this).closest('.field3').data('key');
603
- $('#view-fieldtext' + key + '-custom').focus();
604
  }
605
  });
606
  }
@@ -636,7 +636,7 @@ jQuery(document).ready(function ($) {
636
  arrayLength = response.data.length;
637
  for (var i = 0; i < arrayLength; i++) {
638
  $el = $('#' + response.data[i]);
639
- $el.prop('checked', true).change();
640
  inputName = $el.prop('name');
641
  $('input[name=\'' + inputName + '\']').prop('disabled', true).addClass('forced');
642
  }
@@ -718,7 +718,7 @@ jQuery(document).ready(function ($) {
718
  if ('view-layout-masonry' === layout) {
719
  if ($('#view-pagination').is(':checked')) {
720
  alert('Masonry is incompatible with pagination. Please disable pagination first.');
721
- $('#view-layout-normal').prop('checked', true).change();
722
  }
723
  }
724
  }
@@ -735,7 +735,7 @@ jQuery(document).ready(function ($) {
735
  // TODO DRY
736
  if ($(this).is(':checked') && 'masonry' === layoutRadios.filter(':checked').val()) {
737
  alert('Pagination is incompatible with Masonry. Please select another layout first.');
738
- $(this).prop('checked', false).change();
739
  }
740
  }
741
 
@@ -750,14 +750,14 @@ jQuery(document).ready(function ($) {
750
  function paginationTypeChangeListener () {
751
  if (this.value === 'standard' && $viewQuantity.val() === '0' && $('#view-pagination').is(':checked')) {
752
  alert('Standard pagination is incompatible with Count.');
753
- $(this).val('simple').change();
754
  }
755
  }
756
 
757
  function quantityChangeListener () {
758
  if (this.value === '0' && $viewPaginationType.val() === 'standard' && $('#view-pagination').is(':checked')) {
759
  alert('Count is incompatible with Standard pagination.');
760
- $(this).val(1).change();
761
  }
762
  }
763
 
@@ -949,9 +949,9 @@ jQuery(document).ready(function ($) {
949
  $.when(customFieldList.append(response)).then(function () {
950
  var $newField = customFieldList.find('#field-' + nextKey);
951
  $newField
952
- .find('div.link').click().end()
953
  .find('.field-dep').hide().end()
954
- .find('.first-field').focus();
955
  });
956
  });
957
  });
@@ -966,9 +966,8 @@ jQuery(document).ready(function ($) {
966
  // var key = $elParent.attr("id").split('-').slice(-1)[0];
967
  var key = $elParent.data('key');
968
  var data;
969
-
970
  switch (fieldType) {
971
-
972
  case 'link2':
973
  case 'link':
974
  // if changing to [link], add link fields
@@ -1016,8 +1015,6 @@ jQuery(document).ready(function ($) {
1016
  $elParent.find('.field-property-box').html(response);
1017
  });
1018
  break;
1019
-
1020
- case 'text':
1021
 
1022
  case 'checkbox':
1023
  // if changing to [checkbox_value]
@@ -1097,7 +1094,7 @@ jQuery(document).ready(function ($) {
1097
  $.get(ajaxurl, data, function (response) {
1098
  // Insert into placeholder div. Add hidden field because we are
1099
  // disabling the <select> so its value will not be submitted.
1100
- $elParent.find('.field-property-box').html(response); // .find("input").focus();
1101
  $el.parent().append('<input type="hidden" class="save-type" name="view[data][client_section][' + key + '][type]" value="date">');
1102
  });
1103
  break;
@@ -1150,6 +1147,13 @@ jQuery(document).ready(function ($) {
1150
  });
1151
  break;
1152
  }
 
 
 
 
 
 
 
1153
 
1154
  if ('platform' === fieldType) {
1155
  typeSelect.val('platform').prop('disabled', true);
@@ -1248,8 +1252,8 @@ jQuery(document).ready(function ($) {
1248
  $sliderType.parent().siblings('.option-desc.plural').hide();
1249
  }
1250
 
1251
- $effect.change();
1252
- $position.change();
1253
  };
1254
 
1255
  sliderTypeUpdate();
@@ -1325,7 +1329,7 @@ jQuery(document).ready(function ($) {
1325
  /**
1326
  * Restore default breakpoints
1327
  */
1328
- $('#restore-default-breakpoints').click(function (e) {
1329
  var data = {
1330
  'action': 'wpmtst_restore_default_breakpoints'
1331
  };
@@ -1398,12 +1402,12 @@ jQuery(document).ready(function ($) {
1398
  if (inp && inp.select) {
1399
 
1400
  // select text
1401
- inp.select();
1402
 
1403
  try {
1404
  // copy text
1405
  document.execCommand('copy');
1406
- inp.blur();
1407
 
1408
  //t.classList.add('copied');
1409
  //setTimeout(function() { t.classList.remove('copied'); }, 1500);
44
  while (patt.test(cn)) {
45
  cn = cn.replace(patt, ' ');
46
  }
47
+ it.className = cn.trim();
48
  });
49
 
50
  return !additions ? self : self.addClass(additions);
144
 
145
  // the shortcode code
146
  $('#view-shortcode').on('focus', function () {
147
+ $(this).trigger('select');
148
  });
149
 
150
  $('.expand-cats').on('click', function () {
157
  $categoryDivs.removeClass('short-panel').addClass('tall-panel');
158
  $(this).val('collapse list');
159
  }
160
+ $(this).trigger('blur');
161
  });
162
 
163
  // Masonry example
267
  } else {
268
  viewDefaultMore.removeProp('disabled');
269
  }
270
+ viewDefaultMore.trigger( 'change' );
271
 
272
  };
273
 
505
 
506
  // Force default template since we have more than one group of templates.
507
  $('input[type=radio][name=\'view[data][template]\'][value=\'default\']').prop('checked', true);
508
+ templateRadios.trigger( 'change' );
509
  $('input[type=radio][name=\'view[data][form-template]\'][value=\'default-form\']').prop('checked', true);
510
+ // formTemplateRadios.trigger( 'change' );
511
+ layoutRadios.trigger( 'change' );
512
+ backgroundRadios.trigger( 'change' );
513
  });
514
 
515
  /**
600
  $('select[id^="view-fieldtext"]').on('change', function () {
601
  if ($(this).val() === 'custom') {
602
  var key = $(this).closest('.field3').data('key');
603
+ $('#view-fieldtext' + key + '-custom').trigger('focus');
604
  }
605
  });
606
  }
636
  arrayLength = response.data.length;
637
  for (var i = 0; i < arrayLength; i++) {
638
  $el = $('#' + response.data[i]);
639
+ $el.prop('checked', true).trigger( 'change' );
640
  inputName = $el.prop('name');
641
  $('input[name=\'' + inputName + '\']').prop('disabled', true).addClass('forced');
642
  }
718
  if ('view-layout-masonry' === layout) {
719
  if ($('#view-pagination').is(':checked')) {
720
  alert('Masonry is incompatible with pagination. Please disable pagination first.');
721
+ $('#view-layout-normal').prop('checked', true).trigger( 'change' );
722
  }
723
  }
724
  }
735
  // TODO DRY
736
  if ($(this).is(':checked') && 'masonry' === layoutRadios.filter(':checked').val()) {
737
  alert('Pagination is incompatible with Masonry. Please select another layout first.');
738
+ $(this).prop('checked', false).trigger( 'change' );
739
  }
740
  }
741
 
750
  function paginationTypeChangeListener () {
751
  if (this.value === 'standard' && $viewQuantity.val() === '0' && $('#view-pagination').is(':checked')) {
752
  alert('Standard pagination is incompatible with Count.');
753
+ $(this).val('simple').trigger( 'change' );
754
  }
755
  }
756
 
757
  function quantityChangeListener () {
758
  if (this.value === '0' && $viewPaginationType.val() === 'standard' && $('#view-pagination').is(':checked')) {
759
  alert('Count is incompatible with Standard pagination.');
760
+ $(this).val(1).trigger( 'change' );
761
  }
762
  }
763
 
949
  $.when(customFieldList.append(response)).then(function () {
950
  var $newField = customFieldList.find('#field-' + nextKey);
951
  $newField
952
+ .find('div.link').trigger('click').end()
953
  .find('.field-dep').hide().end()
954
+ .find('.first-field').trigger('focus');
955
  });
956
  });
957
  });
966
  // var key = $elParent.attr("id").split('-').slice(-1)[0];
967
  var key = $elParent.data('key');
968
  var data;
969
+
970
  switch (fieldType) {
 
971
  case 'link2':
972
  case 'link':
973
  // if changing to [link], add link fields
1015
  $elParent.find('.field-property-box').html(response);
1016
  });
1017
  break;
 
 
1018
 
1019
  case 'checkbox':
1020
  // if changing to [checkbox_value]
1094
  $.get(ajaxurl, data, function (response) {
1095
  // Insert into placeholder div. Add hidden field because we are
1096
  // disabling the <select> so its value will not be submitted.
1097
+ $elParent.find('.field-property-box').html(response); // .find("input").trigger('focus');
1098
  $el.parent().append('<input type="hidden" class="save-type" name="view[data][client_section][' + key + '][type]" value="date">');
1099
  });
1100
  break;
1147
  });
1148
  break;
1149
  }
1150
+
1151
+ if ('video' === fieldType) {
1152
+ typeSelect.val('video').prop('disabled', true);
1153
+ typeSelectParent.append('<input type="hidden" class="save-type" name="' + source + '[client_section][' + key + '][save-type]" value="video">');
1154
+ typeSelect.parent().hide();
1155
+ break;
1156
+ }
1157
 
1158
  if ('platform' === fieldType) {
1159
  typeSelect.val('platform').prop('disabled', true);
1252
  $sliderType.parent().siblings('.option-desc.plural').hide();
1253
  }
1254
 
1255
+ $effect.trigger( 'change' );
1256
+ $position.trigger( 'change' );
1257
  };
1258
 
1259
  sliderTypeUpdate();
1329
  /**
1330
  * Restore default breakpoints
1331
  */
1332
+ $('#restore-default-breakpoints').on('click', function (e) {
1333
  var data = {
1334
  'action': 'wpmtst_restore_default_breakpoints'
1335
  };
1402
  if (inp && inp.select) {
1403
 
1404
  // select text
1405
+ inp.trigger('select');
1406
 
1407
  try {
1408
  // copy text
1409
  document.execCommand('copy');
1410
+ inp.trigger('blur');
1411
 
1412
  //t.classList.add('copied');
1413
  //setTimeout(function() { t.classList.remove('copied'); }, 1500);
admin/partials/views/option-pagination.php CHANGED
@@ -28,13 +28,13 @@ $links .= '</span>';
28
  </select>
29
  </label>
30
  </div>
31
- <div class="inline then fast then_simple then_not_standard then_not_infinitescroll" style="display: none;">
32
  <p class="description">
33
  <?php _e( 'Using JavaScript. Intended for small scale.', 'strong-testimonials' ); ?>
34
  <?php echo $links; ?>
35
  </p>
36
  </div>
37
- <div class="inline then fast then_not_simple then_standard then_not_infinitescroll" style="display: none;">
38
  <p class="description">
39
  <?php _e( 'Using paged URLs: /page/2, /page/3, etc. Best for large scale.', 'strong-testimonials' ); ?>
40
  <?php echo $links; ?>
@@ -55,7 +55,7 @@ $links .= '</span>';
55
  value="<?php echo $view['pagination_settings']['per_page']; ?>"/>
56
  </div>
57
 
58
- <div class="inline then then_simple then_standard then_not_infinitescroll">
59
  <label for="view-nav">
60
  <?php _e( 'Navigation', 'strong-testimonials' ); ?>
61
  </label>
@@ -73,7 +73,7 @@ $links .= '</span>';
73
  </div>
74
  </div>
75
 
76
- <div class="row then then_not_simple then_standard then_not_infinitescroll" style="display: none;">
77
  <div class="row-inner">
78
  <div class="inline">
79
  <label for="view-pagination-show_all">
@@ -112,7 +112,7 @@ $links .= '</span>';
112
  </div>
113
  </div>
114
 
115
- <div class="row then then_not_simple then_standard then_not_infinitescroll" style="display: none;">
116
  <div class="row-inner">
117
  <div class="inline inline-middle">
118
  <input class="if toggle checkbox" id="view-pagination-prev_next"
@@ -141,7 +141,7 @@ $links .= '</span>';
141
  </div>
142
  </div>
143
 
144
- <div class="row then then_not_simple then_standard then_not_infinitescroll" style="display: none;">
145
  <div class="row-inner">
146
  <div class="inline">
147
  <label for="view-pagination-before_page_number">
28
  </select>
29
  </label>
30
  </div>
31
+ <div class="inline then fast then_simple then_not_standard then_not_infinitescroll then_not_loadmore" style="display: none;">
32
  <p class="description">
33
  <?php _e( 'Using JavaScript. Intended for small scale.', 'strong-testimonials' ); ?>
34
  <?php echo $links; ?>
35
  </p>
36
  </div>
37
+ <div class="inline then fast then_not_simple then_standard then_not_infinitescroll then_not_loadmore" style="display: none;">
38
  <p class="description">
39
  <?php _e( 'Using paged URLs: /page/2, /page/3, etc. Best for large scale.', 'strong-testimonials' ); ?>
40
  <?php echo $links; ?>
55
  value="<?php echo $view['pagination_settings']['per_page']; ?>"/>
56
  </div>
57
 
58
+ <div class="inline then then_simple then_standard then_not_infinitescroll then_not_loadmore">
59
  <label for="view-nav">
60
  <?php _e( 'Navigation', 'strong-testimonials' ); ?>
61
  </label>
73
  </div>
74
  </div>
75
 
76
+ <div class="row then then_not_simple then_standard then_not_infinitescroll then_not_loadmore" style="display: none;">
77
  <div class="row-inner">
78
  <div class="inline">
79
  <label for="view-pagination-show_all">
112
  </div>
113
  </div>
114
 
115
+ <div class="row then then_not_simple then_standard then_not_infinitescroll then_not_loadmore" style="display: none;">
116
  <div class="row-inner">
117
  <div class="inline inline-middle">
118
  <input class="if toggle checkbox" id="view-pagination-prev_next"
141
  </div>
142
  </div>
143
 
144
+ <div class="row then then_not_simple then_standard then_not_infinitescroll then_not_loadmore" style="display: none;">
145
  <div class="row-inner">
146
  <div class="inline">
147
  <label for="view-pagination-before_page_number">
admin/views.php CHANGED
@@ -511,7 +511,8 @@ function wpmtst_view_field_inputs( $key, $field, $adding = false, $source = 'vie
511
  'rating' => __( 'rating', 'strong-testimonials' ),
512
  'platform' => __( 'platform', 'strong-testimonials' ),
513
  'shortcode' => __( 'shortcode', 'strong-testimonials' ),
514
- 'checkbox' => __('checkbox', 'strong-testimonials')
 
515
  );
516
 
517
  if ( isset( $custom_fields[ $field['field'] ] ) ) {
@@ -580,13 +581,13 @@ function wpmtst_view_field_inputs( $key, $field, $adding = false, $source = 'vie
580
  </div>
581
 
582
  <!-- FIELD TYPE -->
583
- <div class="field-property field-type field-dep" <?php if ( $adding || $field['type'] == 'checkbox' ) echo ' style="display: none;"'; ?>>
584
  <label for="client_section_<?php echo $key; ?>_type">
585
  <?php _e( 'Display Type', 'strong-testimonials' ); ?>
586
  </label>
587
  <select id="client_section_<?php echo $key; ?>_type" name="<?php echo $source ?>[client_section][<?php echo $key; ?>][type]" <?php echo ($field['type'] == 'checkbox' ? 'readonly' : '') ?>>
588
  <?php foreach ( $types as $type => $type_label ) : ?>
589
- <option value="<?php echo $type; ?>" <?php selected( $type, $field['type'] ); ?> <?php echo($type=='checkbox' ? 'style="display:none"' : '') ?>><?php echo $type_label; ?></option>
590
  <?php endforeach; ?>
591
  </select>
592
  </div>
511
  'rating' => __( 'rating', 'strong-testimonials' ),
512
  'platform' => __( 'platform', 'strong-testimonials' ),
513
  'shortcode' => __( 'shortcode', 'strong-testimonials' ),
514
+ 'checkbox' => __('checkbox', 'strong-testimonials'),
515
+ 'video' => __('video', 'strong-testimonials')
516
  );
517
 
518
  if ( isset( $custom_fields[ $field['field'] ] ) ) {
581
  </div>
582
 
583
  <!-- FIELD TYPE -->
584
+ <div class="field-property field-type field-dep" <?php if ( $adding || in_array($field['type'], array('checkbox', 'video') ) ) echo ' style="display: none;"'; ?>>
585
  <label for="client_section_<?php echo $key; ?>_type">
586
  <?php _e( 'Display Type', 'strong-testimonials' ); ?>
587
  </label>
588
  <select id="client_section_<?php echo $key; ?>_type" name="<?php echo $source ?>[client_section][<?php echo $key; ?>][type]" <?php echo ($field['type'] == 'checkbox' ? 'readonly' : '') ?>>
589
  <?php foreach ( $types as $type => $type_label ) : ?>
590
+ <option value="<?php echo $type; ?>" <?php selected( $type, $field['type'] ); ?> <?php echo(in_array($type, array('checkbox', 'video')) ? 'style="display:none"' : '') ?>><?php echo $type_label; ?></option>
591
  <?php endforeach; ?>
592
  </select>
593
  </div>
assets/css/admin-global.css CHANGED
@@ -85,3 +85,7 @@ span.wpmtst-upsell-badge {
85
  margin-left: 10px;
86
  border-radius: 10px;
87
  }
 
 
 
 
85
  margin-left: 10px;
86
  border-radius: 10px;
87
  }
88
+ .wpmtst-upsell-checkmark:before {
89
+ content: "\2713";
90
+ padding-right: 10px;
91
+ }
assets/css/admin-welcome.css CHANGED
@@ -170,7 +170,6 @@
170
  border-top-left-radius: 10px;
171
  border-top-right-radius: 10px;
172
  position: relative;
173
- box-shadow: -3px 2px 70px 0px rgba(128, 144, 174, 0.1);
174
  padding-top: 20px;
175
  }
176
 
@@ -235,8 +234,7 @@
235
 
236
  /* upgrade section */
237
  #wpmtst-welcome .upgrade {
238
- background-color: #5333ED;
239
- color: rgba(255,255,255,0.7);
240
  text-align: center;
241
  }
242
 
170
  border-top-left-radius: 10px;
171
  border-top-right-radius: 10px;
172
  position: relative;
 
173
  padding-top: 20px;
174
  }
175
 
234
 
235
  /* upgrade section */
236
  #wpmtst-welcome .upgrade {
237
+ background-color: white;
 
238
  text-align: center;
239
  }
240
 
assets/css/admin.css CHANGED
@@ -654,6 +654,32 @@ ul.standard {
654
  .edit-php.post-type-wpm-testimonial .wpmtst-notice img {
655
  max-width: 100%; }
656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  .edit-php.post-type-wpm-testimonial .wpmst-mascot {
658
  width: 120px;
659
  margin-right: 2rem;
654
  .edit-php.post-type-wpm-testimonial .wpmtst-notice img {
655
  max-width: 100%; }
656
 
657
+ #wpmtst-importer-upsell .wpmtst-alert > *:last-child {
658
+ text-align: center;
659
+ }
660
+
661
+ #wpmtst-importer-upsell .hndle {
662
+ display: none;
663
+ }
664
+
665
+ .wpmtst-alert {
666
+ padding: 20px;
667
+ background: #f4daa4;
668
+ color: #8d6e30;
669
+ position: relative;
670
+ }
671
+
672
+ .wpmtst-alert__upgrade-btn {
673
+ position: absolute;
674
+ right: 0.5rem;
675
+ top: 50%;
676
+ transform: translateY(-50%) !important;
677
+ }
678
+
679
+ .wpmtst-alert > *:last-child {
680
+ margin-bottom: 0 !important;
681
+ }
682
+
683
  .edit-php.post-type-wpm-testimonial .wpmst-mascot {
684
  width: 120px;
685
  margin-right: 2rem;
assets/js/admin-js.js CHANGED
@@ -236,24 +236,24 @@ jQuery(document).ready(function ($) {
236
  });
237
 
238
  $('ul.ui-tabs-nav li a').on('click', function () {
239
- $(this).blur();
240
  });
241
 
242
  $('.focus-next-field').on('change', function (e) {
243
  if ($(e.target).is(':checked')) {
244
- $(e.target).parent().next().find('input').focus().select();
245
  }
246
  });
247
 
248
  // toggle screenshots
249
  $('#toggle-screen-options').add('#screenshot-screen-options').on('click', function (e) {
250
- $(this).blur();
251
  $('#screenshot-screen-options').slideToggle();
252
  });
253
 
254
  // toggle screenshots
255
  $('#toggle-help').on('click', function (e) {
256
- $(this).toggleClass('closed open').blur();
257
  $('#help-section').slideToggle();
258
  });
259
 
@@ -284,7 +284,7 @@ jQuery(document).ready(function ($) {
284
  $('table.wpm-testimonial_page_testimonial-views').on('click', '.stickit', function (e) {
285
  var icon = $(this);
286
  icon.closest('.wp-list-table-wrap').find('.overlay').fadeIn(200);
287
- icon.blur().toggleClass('stuck');
288
  var id = $(this).closest('tr').find('td.id').html();
289
  var data = {
290
  'action': 'wpmtst_save_view_sticky',
236
  });
237
 
238
  $('ul.ui-tabs-nav li a').on('click', function () {
239
+ $(this).trigger('blur');
240
  });
241
 
242
  $('.focus-next-field').on('change', function (e) {
243
  if ($(e.target).is(':checked')) {
244
+ $(e.target).parent().next().find('input').focus().trigger('select');
245
  }
246
  });
247
 
248
  // toggle screenshots
249
  $('#toggle-screen-options').add('#screenshot-screen-options').on('click', function (e) {
250
+ $(this).trigger('blur');
251
  $('#screenshot-screen-options').slideToggle();
252
  });
253
 
254
  // toggle screenshots
255
  $('#toggle-help').on('click', function (e) {
256
+ $(this).toggleClass('closed open').trigger('blur');
257
  $('#help-section').slideToggle();
258
  });
259
 
284
  $('table.wpm-testimonial_page_testimonial-views').on('click', '.stickit', function (e) {
285
  var icon = $(this);
286
  icon.closest('.wp-list-table-wrap').find('.overlay').fadeIn(200);
287
+ icon.trigger('blur').toggleClass('stuck');
288
  var id = $(this).closest('tr').find('td.id').html();
289
  var data = {
290
  'action': 'wpmtst_save_view_sticky',
assets/js/blocks-js.js CHANGED
@@ -66,9 +66,8 @@
66
  /******/ return __webpack_require__(__webpack_require__.s = 4);
67
  /******/ })
68
  /************************************************************************/
69
- /******/ ({
70
-
71
- /***/ 0:
72
  /***/ (function(module, exports, __webpack_require__) {
73
 
74
  "use strict";
@@ -82,7 +81,7 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument
82
 
83
  var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
84
 
85
- var _inspector = __webpack_require__(8);
86
 
87
  var _inspector2 = _interopRequireDefault(_inspector);
88
 
@@ -131,7 +130,7 @@ var StrongTestimonialViewEdit = function (_Component) {
131
  }, {
132
  key: 'selectOptions',
133
  value: function selectOptions() {
134
- var options = [];
135
 
136
  st_views.views.forEach(function (view) {
137
  options.push({ value: view.id, label: view.name });
@@ -248,8 +247,10 @@ var StrongTestimonialViewEdit = function (_Component) {
248
  exports.default = StrongTestimonialViewEdit;
249
 
250
  /***/ }),
251
-
252
- /***/ 4:
 
 
253
  /***/ (function(module, exports, __webpack_require__) {
254
 
255
  "use strict";
@@ -321,8 +322,7 @@ var StrongTestimonialView = function () {
321
  var strongTestimonialsView = new StrongTestimonialView();
322
 
323
  /***/ }),
324
-
325
- /***/ 8:
326
  /***/ (function(module, exports, __webpack_require__) {
327
 
328
  "use strict";
@@ -370,7 +370,7 @@ var Inspector = function (_Component) {
370
  _createClass(Inspector, [{
371
  key: 'selectOptions',
372
  value: function selectOptions() {
373
- var options = [{ value: 0, label: __('none') }];
374
 
375
  this.props.attributes.views.forEach(function (view) {
376
  options.push({ value: view.id, label: view.name });
@@ -448,5 +448,4 @@ var Inspector = function (_Component) {
448
  exports.default = Inspector;
449
 
450
  /***/ })
451
-
452
- /******/ });
66
  /******/ return __webpack_require__(__webpack_require__.s = 4);
67
  /******/ })
68
  /************************************************************************/
69
+ /******/ ([
70
+ /* 0 */
 
71
  /***/ (function(module, exports, __webpack_require__) {
72
 
73
  "use strict";
81
 
82
  var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
83
 
84
+ var _inspector = __webpack_require__(5);
85
 
86
  var _inspector2 = _interopRequireDefault(_inspector);
87
 
130
  }, {
131
  key: 'selectOptions',
132
  value: function selectOptions() {
133
+ var options = [{ value: 0, label: __('None') }];
134
 
135
  st_views.views.forEach(function (view) {
136
  options.push({ value: view.id, label: view.name });
247
  exports.default = StrongTestimonialViewEdit;
248
 
249
  /***/ }),
250
+ /* 1 */,
251
+ /* 2 */,
252
+ /* 3 */,
253
+ /* 4 */
254
  /***/ (function(module, exports, __webpack_require__) {
255
 
256
  "use strict";
322
  var strongTestimonialsView = new StrongTestimonialView();
323
 
324
  /***/ }),
325
+ /* 5 */
 
326
  /***/ (function(module, exports, __webpack_require__) {
327
 
328
  "use strict";
370
  _createClass(Inspector, [{
371
  key: 'selectOptions',
372
  value: function selectOptions() {
373
+ var options = [{ value: 0, label: __('None') }];
374
 
375
  this.props.attributes.views.forEach(function (view) {
376
  options.push({ value: view.id, label: view.name });
448
  exports.default = Inspector;
449
 
450
  /***/ })
451
+ /******/ ]);
 
assets/src/js/admin.js CHANGED
@@ -34,31 +34,31 @@ jQuery(document).ready(function ($) {
34
 
35
  // Add protocol if missing
36
  // Thanks http://stackoverflow.com/a/36429927/51600
37
- $('input[type=url]').on( 'change', function () {
38
  if (this.value.length && !/^https*:\/\//.test(this.value)) {
39
  this.value = 'http://' + this.value;
40
  }
41
  });
42
 
43
  $('ul.ui-tabs-nav li a').on('click', function () {
44
- $(this).blur();
45
  });
46
 
47
- $('.focus-next-field').on( 'change', function (e) {
48
  if ($(e.target).is(':checked')) {
49
- $(e.target).parent().next().find('input').focus().select();
50
  }
51
  });
52
 
53
  // toggle screenshots
54
- $('#toggle-screen-options').add('#screenshot-screen-options').on( 'click', function (e) {
55
- $(this).blur();
56
  $('#screenshot-screen-options').slideToggle();
57
  });
58
 
59
  // toggle screenshots
60
- $('#toggle-help').on( 'click', function (e) {
61
- $(this).toggleClass('closed open').blur();
62
  $('#help-section').slideToggle();
63
  });
64
 
@@ -90,7 +90,7 @@ jQuery(document).ready(function ($) {
90
  $('table.wpm-testimonial_page_testimonial-views').on('click', '.stickit', function (e) {
91
  var icon = $(this);
92
  icon.closest('.wp-list-table-wrap').find('.overlay').fadeIn(200);
93
- icon.blur().toggleClass('stuck');
94
  var id = $(this).closest('tr').find('td.id').html();
95
  var data = {
96
  'action': 'wpmtst_save_view_sticky',
34
 
35
  // Add protocol if missing
36
  // Thanks http://stackoverflow.com/a/36429927/51600
37
+ $('input[type=url]').on('change', function () {
38
  if (this.value.length && !/^https*:\/\//.test(this.value)) {
39
  this.value = 'http://' + this.value;
40
  }
41
  });
42
 
43
  $('ul.ui-tabs-nav li a').on('click', function () {
44
+ $(this).trigger('blur');
45
  });
46
 
47
+ $('.focus-next-field').on('change', function (e) {
48
  if ($(e.target).is(':checked')) {
49
+ $(e.target).parent().next().find('input').focus().trigger('select');
50
  }
51
  });
52
 
53
  // toggle screenshots
54
+ $('#toggle-screen-options').add('#screenshot-screen-options').on('click', function (e) {
55
+ $(this).trigger('blur');
56
  $('#screenshot-screen-options').slideToggle();
57
  });
58
 
59
  // toggle screenshots
60
+ $('#toggle-help').on('click', function (e) {
61
+ $(this).toggleClass('closed open').trigger('blur');
62
  $('#help-section').slideToggle();
63
  });
64
 
90
  $('table.wpm-testimonial_page_testimonial-views').on('click', '.stickit', function (e) {
91
  var icon = $(this);
92
  icon.closest('.wp-list-table-wrap').find('.overlay').fadeIn(200);
93
+ icon.trigger('blur').toggleClass('stuck');
94
  var id = $(this).closest('tr').find('td.id').html();
95
  var data = {
96
  'action': 'wpmtst_save_view_sticky',
assets/src/js/components/edit.js CHANGED
@@ -22,7 +22,7 @@ export default class StrongTestimonialViewEdit extends Component {
22
  }
23
 
24
  selectOptions() {
25
- let options = [];
26
 
27
  st_views.views.forEach(function(view) {
28
  options.push({ value: view.id, label: view.name });
22
  }
23
 
24
  selectOptions() {
25
+ let options = [{ value: 0, label: __('None')}];
26
 
27
  st_views.views.forEach(function(view) {
28
  options.push({ value: view.id, label: view.name });
assets/src/js/components/inspector.js CHANGED
@@ -15,7 +15,7 @@ export default class Inspector extends Component {
15
  }
16
 
17
  selectOptions() {
18
- let options = [ { value: 0, label: __('none') } ];
19
 
20
  this.props.attributes.views.forEach(function(view) {
21
  options.push({ value: view.id, label: view.name });
15
  }
16
 
17
  selectOptions() {
18
+ let options = [ { value: 0, label: __('None') } ];
19
 
20
  this.props.attributes.views.forEach(function(view) {
21
  options.push({ value: view.id, label: view.name });
changelog.txt CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  = 2.50.4 =
2
  * Added Welcome Banner when Strong Testimonials is activated.
3
  * Removed uninstall message that was appearing in WP dashboard when using multisite network.
1
+ = v2.51.0 - 18/02/2021 =
2
+ - Changed: Added by default review field to ST form ( https://github.com/WPChill/strong-testimonials/issues/199 )
3
+ - Changed: Improved security (https://github.com/WPChill/strong-testimonials/issues/200)
4
+ - Changed: Upsell texts & design (https://github.com/WPChill/strong-testimonials/issues/204 / https://github.com/WPChill/strong-testimonials/issues/201)
5
+ - Fixed: Addon page design (https://github.com/WPChill/strong-testimonials/issues/202)
6
+ - Fixed: How excerpt were handled, now when you click read more the excerpt will hide and we will show only the full content ( https://github.com/WPChill/strong-testimonials/issues/207 )
7
+ - Fixed: Show on Checked Value string without escape characters(/) ( https://github.com/WPChill/strong-testimonials/issues/209 )
8
+ - Fixed: Keep HTML markup in a testimonial post intact ( https://github.com/WPChill/strong-testimonials/issues/206 )
9
+ - Fixed: How RTL work with our slideshow ( https://github.com/WPChill/strong-testimonials/issues/203 )
10
+
11
  = 2.50.4 =
12
  * Added Welcome Banner when Strong Testimonials is activated.
13
  * Removed uninstall message that was appearing in WP dashboard when using multisite network.
includes/class-strong-view-display.php CHANGED
@@ -76,7 +76,7 @@ class Strong_View_Display extends Strong_View {
76
  * @return mixed
77
  */
78
  public function query_infinitescroll( $args ) {
79
- if ( $this->atts['pagination'] && 'infinitescroll' == $this->atts['pagination_settings']['type'] && $this->atts['mode'] != 'slideshow') {
80
  // Limit is not compatible with standard pagination.
81
  $this->atts['count'] = -1;
82
  $args['posts_per_page'] = $this->atts['pagination_settings']['per_page'];
@@ -268,7 +268,7 @@ class Strong_View_Display extends Strong_View {
268
  // For Post Types Order plugin
269
  $args['ignore_custom_sort'] = true;
270
 
271
- if ( $this->atts['pagination'] && 'infinitescroll' == $this->atts['pagination_settings']['type'] ) {
272
  if (empty($this->atts['paged'])) {
273
  $this->atts['paged'] = 1;
274
  }
@@ -397,6 +397,17 @@ class Strong_View_Display extends Strong_View {
397
  public function is_infinitescroll() {
398
  return ( $this->atts['pagination'] && 'infinitescroll' == $this->atts['pagination_settings']['type'] );
399
  }
 
 
 
 
 
 
 
 
 
 
 
400
 
401
  /**
402
  * Return true if using Masonry.
76
  * @return mixed
77
  */
78
  public function query_infinitescroll( $args ) {
79
+ if ( $this->atts['pagination'] && in_array($this->atts['pagination_settings']['type'], array('infinitescroll', 'loadmore')) && $this->atts['mode'] != 'slideshow') {
80
  // Limit is not compatible with standard pagination.
81
  $this->atts['count'] = -1;
82
  $args['posts_per_page'] = $this->atts['pagination_settings']['per_page'];
268
  // For Post Types Order plugin
269
  $args['ignore_custom_sort'] = true;
270
 
271
+ if ( $this->atts['pagination'] && in_array($this->atts['pagination_settings']['type'], array('infinitescroll', 'loadmore')) ) {
272
  if (empty($this->atts['paged'])) {
273
  $this->atts['paged'] = 1;
274
  }
397
  public function is_infinitescroll() {
398
  return ( $this->atts['pagination'] && 'infinitescroll' == $this->atts['pagination_settings']['type'] );
399
  }
400
+
401
+ /**
402
+ * Return true if using load more button pagination (JavaScript).
403
+ *
404
+ * @since 2.28.0
405
+ *
406
+ * @return bool
407
+ */
408
+ public function is_loadmore() {
409
+ return ( $this->atts['pagination'] && 'loadmore' == $this->atts['pagination_settings']['type'] );
410
+ }
411
 
412
  /**
413
  * Return true if using Masonry.
includes/functions-content.php CHANGED
@@ -82,23 +82,17 @@ function wpmtst_trim_excerpt( $excerpt = '' ) {
82
  */
83
  $hybrid = apply_filters( 'wpmtst_is_hybrid_content', false );
84
 
85
- if ( '' == $excerpt ) {
86
-
87
- $text = wpmtst_get_the_prepared_text();
88
-
89
- // Create excerpt if post has no manual excerpt.
90
- $excerpt_length = apply_filters( 'excerpt_length', 55 );
91
- $excerpt_more = apply_filters( 'excerpt_more', ' [&hellip;]' );
92
- $excerpt = wpmtst_trim_words( $text, $excerpt_length, $excerpt_more, $hybrid );
93
-
94
- } elseif ( $hybrid ) {
95
-
96
- $text = wpmtst_get_the_prepared_text( true );
97
-
98
- // Append hybrid content as hidden span to the manual excerpt.
99
- $excerpt .= wpmtst_trim_words( $text, 0, '', true );
100
-
101
- }
102
 
103
  /**
104
  * Filters the trimmed excerpt string.
@@ -170,12 +164,12 @@ function wpmtst_get_excerpt_more_link() {
170
  *
171
  * @return string
172
  */
173
- function wpmtst_trim_words( $text, $num_words = 55, $more = null, $hybrid = false ) {
174
  if ( null === $more ) {
175
  $more = __( '&hellip;', 'strong-testimonials' );
176
  }
177
- $full_text = strip_tags( $text, '<br><img><b><strong><i><em><ul><ol><li><del><a>' );
178
- $text = strip_tags( $text, '<br>' );
179
 
180
  /*
181
  * translators: If your word count is based on single characters (e.g. East Asian characters),
@@ -195,7 +189,7 @@ function wpmtst_trim_words( $text, $num_words = 55, $more = null, $hybrid = fals
195
 
196
  if ( count( $words_array ) > $num_words ) {
197
  if ( $hybrid ) {
198
- $text = wpmtst_assemble_hybrid( $words_array, $num_words, $sep, $more, $full_text );
199
  } else {
200
  $text = wpmtst_assemble_excerpt( $words_array, $sep, $more );
201
  }
@@ -233,13 +227,17 @@ function wpmtst_assemble_excerpt( $words_array, $sep, $more ) {
233
  *
234
  * @return string
235
  */
236
- function wpmtst_assemble_hybrid( $words_array, $num_words, $sep, $more, $full_text ) {
237
  $ellipsis = wpmtst_ellipsis();
238
  if ( $ellipsis ) {
239
  $ellipsis = '<span class="ellipsis">' . $ellipsis . ' </span>';
240
  /* ! This space is important: ^ */
241
  }
242
- $first_half = implode( $sep, array_slice( $words_array, 0, $num_words ) );
 
 
 
 
243
  $second_half = implode( $sep, array_slice( $words_array, $num_words ) );
244
  $wrap_open_class = '';
245
  if ( WPMST()->atts( 'html_content' ) ) {
82
  */
83
  $hybrid = apply_filters( 'wpmtst_is_hybrid_content', false );
84
 
85
+ $text = wpmtst_get_the_prepared_text();
86
+ $excerpt_length = 0;
87
+ $excerpt_more = '';
88
+
89
+ // Create excerpt if post has no manual excerpt.
90
+ if (empty($excerpt)) {
91
+ $excerpt_length = apply_filters( 'excerpt_length', 55 );
92
+ $excerpt_more = apply_filters( 'excerpt_more', ' [&hellip;]' );
93
+ }
94
+
95
+ $excerpt = wpmtst_trim_words( $text, $excerpt_length, $excerpt_more, $hybrid, $excerpt );
 
 
 
 
 
 
96
 
97
  /**
98
  * Filters the trimmed excerpt string.
164
  *
165
  * @return string
166
  */
167
+ function wpmtst_trim_words( $text, $num_words = 55, $more = null, $hybrid = false, $excerpt = '' ) {
168
  if ( null === $more ) {
169
  $more = __( '&hellip;', 'strong-testimonials' );
170
  }
171
+ $full_text = strip_tags( $text, '<br><img><b><strong><i><em><ul><ol><li><del><a><sup>' );
172
+ $text = strip_tags( $text, '<br><br><img><b><strong><i><em><ul><ol><li><del><a><sup>' );
173
 
174
  /*
175
  * translators: If your word count is based on single characters (e.g. East Asian characters),
189
 
190
  if ( count( $words_array ) > $num_words ) {
191
  if ( $hybrid ) {
192
+ $text = wpmtst_assemble_hybrid( $words_array, $num_words, $sep, $more, $full_text, $excerpt );
193
  } else {
194
  $text = wpmtst_assemble_excerpt( $words_array, $sep, $more );
195
  }
227
  *
228
  * @return string
229
  */
230
+ function wpmtst_assemble_hybrid( $words_array, $num_words, $sep, $more, $full_text, $excerpt = '' ) {
231
  $ellipsis = wpmtst_ellipsis();
232
  if ( $ellipsis ) {
233
  $ellipsis = '<span class="ellipsis">' . $ellipsis . ' </span>';
234
  /* ! This space is important: ^ */
235
  }
236
+ if (!empty($excerpt)) {
237
+ $first_half = $excerpt;
238
+ } else {
239
+ $first_half = implode( $sep, array_slice( $words_array, 0, $num_words ) );
240
+ }
241
  $second_half = implode( $sep, array_slice( $words_array, $num_words ) );
242
  $wrap_open_class = '';
243
  if ( WPMST()->atts( 'html_content' ) ) {
public/js/controller.js CHANGED
@@ -267,7 +267,7 @@ var strongController = {
267
  strongController.listenForIframeReady();
268
  });
269
 
270
- jQuery('textarea.max-length, input.text.max-length').keyup(function() {
271
  var maxLength = jQuery(this).attr('maxlength');
272
  var textLength = jQuery(this).val().length;
273
  if (maxLength !== null) {
267
  strongController.listenForIframeReady();
268
  });
269
 
270
+ jQuery('textarea.max-length, input.text.max-length').on('keyup', function() {
271
  var maxLength = jQuery(this).attr('maxlength');
272
  var textLength = jQuery(this).val().length;
273
  if (maxLength !== null) {
public/js/lib/actual/jquery-actual.js CHANGED
@@ -1,7 +1,7 @@
1
  /*! Copyright 2012, Ben Lin (http://dreamerslab.com/)
2
  * Licensed under the MIT License (LICENSE.txt).
3
  *
4
- * Version: 1.0.16
5
  *
6
  * Requires: jQuery >= 1.2.3
7
  */
@@ -104,4 +104,4 @@ if ( typeof define === 'function' && define.amd ) {
104
  return actual;
105
  }
106
  });
107
- }));
1
  /*! Copyright 2012, Ben Lin (http://dreamerslab.com/)
2
  * Licensed under the MIT License (LICENSE.txt).
3
  *
4
+ * Version: 1.0.19
5
  *
6
  * Requires: jQuery >= 1.2.3
7
  */
104
  return actual;
105
  }
106
  });
107
+ }));
public/js/lib/actual/jquery-actual.min.js CHANGED
@@ -1 +1,14 @@
1
- !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){t.fn.addBack=t.fn.addBack||t.fn.andSelf,t.fn.extend({actual:function(e,n){if(!this[e])throw'$.actual => The jQuery method "'+e+'" you called does not exist';var i,a,o=t.extend({absolute:!1,clone:!1,includeMargin:!1,display:"block"},n),d=this.eq(0);if(!0===o.clone)i=function(){d=d.clone().attr("style","position: absolute !important; top: -1000 !important; ").appendTo("body")},a=function(){d.remove()};else{var r,l=[],s="";i=function(){r=d.parents().addBack().filter(":hidden"),s+="visibility: hidden !important; display: "+o.display+" !important; ",!0===o.absolute&&(s+="position: absolute !important; "),r.each(function(){var e=t(this),n=e.attr("style");l.push(n),e.attr("style",n?n+";"+s:s)})},a=function(){r.each(function(e){var n=t(this),i=l[e];void 0===i?n.removeAttr("style"):n.attr("style",i)})}}i();var u=/(outer)/.test(e)?d[e](o.includeMargin):d[e]();return a(),u}})});
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! Copyright 2012, Ben Lin (http://dreamerslab.com/)
2
+ * Licensed under the MIT License (LICENSE.txt).
3
+ *
4
+ * Version: 1.0.19
5
+ *
6
+ * Requires: jQuery >= 1.2.3
7
+ */
8
+ (function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a);
9
+ }else{a(jQuery);}}(function(a){a.fn.addBack=a.fn.addBack||a.fn.andSelf;a.fn.extend({actual:function(b,l){if(!this[b]){throw'$.actual => The jQuery method "'+b+'" you called does not exist';
10
+ }var f={absolute:false,clone:false,includeMargin:false,display:"block"};var i=a.extend(f,l);var e=this.eq(0);var h,j;if(i.clone===true){h=function(){var m="position: absolute !important; top: -1000 !important; ";
11
+ e=e.clone().attr("style",m).appendTo("body");};j=function(){e.remove();};}else{var g=[];var d="";var c;h=function(){c=e.parents().addBack().filter(":hidden");
12
+ d+="visibility: hidden !important; display: "+i.display+" !important; ";if(i.absolute===true){d+="position: absolute !important; ";}c.each(function(){var m=a(this);
13
+ var n=m.attr("style");g.push(n);m.attr("style",n?n+";"+d:d);});};j=function(){c.each(function(m){var o=a(this);var n=g[m];if(n===undefined){o.removeAttr("style");
14
+ }else{o.attr("style",n);}});};}h();var k=/(outer)/.test(b)?e[b](i.includeMargin):e[b]();j();return k;}});}));
public/js/lib/actual/jquery.actual.min.js DELETED
@@ -1,14 +0,0 @@
1
- /*! Copyright 2012, Ben Lin (http://dreamerslab.com/)
2
- * Licensed under the MIT License (LICENSE.txt).
3
- *
4
- * Version: 1.0.16
5
- *
6
- * Requires: jQuery >= 1.2.3
7
- */
8
- (function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a);
9
- }else{a(jQuery);}}(function(a){a.fn.addBack=a.fn.addBack||a.fn.andSelf;a.fn.extend({actual:function(b,l){if(!this[b]){throw'$.actual => The jQuery method "'+b+'" you called does not exist';
10
- }var f={absolute:false,clone:false,includeMargin:false,display:"block"};var i=a.extend(f,l);var e=this.eq(0);var h,j;if(i.clone===true){h=function(){var m="position: absolute !important; top: -1000 !important; ";
11
- e=e.clone().attr("style",m).appendTo("body");};j=function(){e.remove();};}else{var g=[];var d="";var c;h=function(){c=e.parents().addBack().filter(":hidden");
12
- d+="visibility: hidden !important; display: "+i.display+" !important; ";if(i.absolute===true){d+="position: absolute !important; ";}c.each(function(){var m=a(this);
13
- var n=m.attr("style");g.push(n);m.attr("style",n?n+";"+d:d);});};j=function(){c.each(function(m){var o=a(this);var n=g[m];if(n===undefined){o.removeAttr("style");
14
- }else{o.attr("style",n);}});};}h();var k=/(outer)/.test(b)?e[b](i.includeMargin):e[b]();j();return k;}});}));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/js/lib/form-validation/form-validation.js CHANGED
@@ -76,7 +76,7 @@ var strongValidation = {
76
 
77
  // Add protocol if missing
78
  // Thanks http://stackoverflow.com/a/36429927/51600
79
- jQuery('input[type=url]').on( 'change', function () {
80
  if (this.value.length && !/^https*:\/\//.test(this.value)) {
81
  this.value = 'https://' + this.value;
82
  }
@@ -98,7 +98,7 @@ var strongValidation = {
98
  // If key 0-5 fired the event, trigger click on that star (including hidden zero).
99
  if (e.keyCode >= 48 && e.keyCode <= 53) {
100
  var key = e.keyCode - 48;
101
- jQuery(this).find('input[type="radio"][value=' + key + ']').click();
102
  }
103
  },
104
 
@@ -170,7 +170,7 @@ var strongValidation = {
170
 
171
  } else {
172
 
173
- form.submit();
174
 
175
  }
176
  },
76
 
77
  // Add protocol if missing
78
  // Thanks http://stackoverflow.com/a/36429927/51600
79
+ jQuery('input[type=url]').on('change', function () {
80
  if (this.value.length && !/^https*:\/\//.test(this.value)) {
81
  this.value = 'https://' + this.value;
82
  }
98
  // If key 0-5 fired the event, trigger click on that star (including hidden zero).
99
  if (e.keyCode >= 48 && e.keyCode <= 53) {
100
  var key = e.keyCode - 48;
101
+ jQuery(this).find('input[type="radio"][value=' + key + ']').trigger('click');
102
  }
103
  },
104
 
170
 
171
  } else {
172
 
173
+ form.trigger('submit');
174
 
175
  }
176
  },
public/js/lib/strongpager/jquery-strongpager.js CHANGED
@@ -165,7 +165,7 @@
165
  * Navigation behavior
166
  */
167
  var navigationHandler = function () {
168
- el.find('.simplePagerNav a').click(function (e) {
169
  var $this = $(e.target);
170
  var container;
171
 
165
  * Navigation behavior
166
  */
167
  var navigationHandler = function () {
168
+ el.find('.simplePagerNav a').on('click', function (e) {
169
  var $this = $(e.target);
170
  var container;
171
 
public/js/lib/strongslider/jquery-strongslider.js CHANGED
@@ -554,7 +554,7 @@
554
 
555
  // if keyboardEnabled is true, setup the keyboard events
556
  if (slider.settings.keyboardEnabled) {
557
- $(document).keydown(keyPress);
558
  }
559
  };
560
 
@@ -1101,7 +1101,11 @@
1101
  if (slider.debug) console.log(slider.logAs, 'stop on navigation');
1102
  el.stopAuto();
1103
  }
1104
- el.goToNextSlide();
 
 
 
 
1105
  };
1106
 
1107
  /**
@@ -1121,7 +1125,11 @@
1121
  if (slider.debug) console.log(slider.logAs, 'stop on navigation');
1122
  el.stopAuto();
1123
  }
1124
- el.goToPrevSlide();
 
 
 
 
1125
  };
1126
 
1127
  /**
554
 
555
  // if keyboardEnabled is true, setup the keyboard events
556
  if (slider.settings.keyboardEnabled) {
557
+ $(document).trigger('keydown', keyPress);
558
  }
559
  };
560
 
1101
  if (slider.debug) console.log(slider.logAs, 'stop on navigation');
1102
  el.stopAuto();
1103
  }
1104
+ if ($('.strong-view').hasClass('rtl')) {
1105
+ el.goToPrevSlide();
1106
+ } else {
1107
+ el.goToNextSlide();
1108
+ }
1109
  };
1110
 
1111
  /**
1125
  if (slider.debug) console.log(slider.logAs, 'stop on navigation');
1126
  el.stopAuto();
1127
  }
1128
+ if ($('.strong-view').hasClass('rtl')) {
1129
+ el.goToNextSlide();
1130
+ } else {
1131
+ el.goToPrevSlide();
1132
+ }
1133
  };
1134
 
1135
  /**
public/js/lib/validate/jquery-validate.js CHANGED
@@ -1,9 +1,9 @@
1
  /*!
2
- * jQuery Validation Plugin v1.16.0
3
  *
4
- * http://jqueryvalidation.org/
5
  *
6
- * Copyright (c) 2016 Jörn Zaefferer
7
  * Released under the MIT license
8
  */
9
  (function( factory ) {
@@ -18,7 +18,7 @@
18
 
19
  $.extend( $.fn, {
20
 
21
- // http://jqueryvalidation.org/validate/
22
  validate: function( options ) {
23
 
24
  // If nothing is selected, return nothing; can't chain anyway
@@ -44,9 +44,10 @@ $.extend( $.fn, {
44
  if ( validator.settings.onsubmit ) {
45
 
46
  this.on( "click.validate", ":submit", function( event ) {
47
- if ( validator.settings.submitHandler ) {
48
- validator.submitButton = event.target;
49
- }
 
50
 
51
  // Allow suppressing validation by adding a cancel class to the submit button
52
  if ( $( this ).hasClass( "cancel" ) ) {
@@ -66,19 +67,25 @@ $.extend( $.fn, {
66
  // Prevent form submit to be able to see console output
67
  event.preventDefault();
68
  }
 
69
  function handle() {
70
  var hidden, result;
71
- if ( validator.settings.submitHandler ) {
72
- if ( validator.submitButton ) {
73
-
74
- // Insert a hidden input as a replacement for the missing submit button
75
- hidden = $( "<input type='hidden'/>" )
76
- .attr( "name", validator.submitButton.name )
77
- .val( $( validator.submitButton ).val() )
78
- .appendTo( validator.currentForm );
79
- }
 
 
 
 
 
80
  result = validator.settings.submitHandler.call( validator, validator.currentForm, event );
81
- if ( validator.submitButton ) {
82
 
83
  // And clean up afterwards; thanks to no-block-scope, hidden can be referenced
84
  hidden.remove();
@@ -112,7 +119,7 @@ $.extend( $.fn, {
112
  return validator;
113
  },
114
 
115
- // http://jqueryvalidation.org/valid/
116
  valid: function() {
117
  var valid, validator, errorList;
118
 
@@ -133,13 +140,23 @@ $.extend( $.fn, {
133
  return valid;
134
  },
135
 
136
- // http://jqueryvalidation.org/rules/
137
  rules: function( command, argument ) {
138
  var element = this[ 0 ],
 
139
  settings, staticRules, existingRules, data, param, filtered;
140
 
141
  // If nothing is selected, return empty object; can't chain anyway
142
- if ( element == null || element.form == null ) {
 
 
 
 
 
 
 
 
 
143
  return;
144
  }
145
 
@@ -167,9 +184,6 @@ $.extend( $.fn, {
167
  $.each( argument.split( /\s/ ), function( index, method ) {
168
  filtered[ method ] = existingRules[ method ];
169
  delete existingRules[ method ];
170
- if ( method === "required" ) {
171
- $( element ).removeAttr( "aria-required" );
172
- }
173
  } );
174
  return filtered;
175
  }
@@ -189,7 +203,6 @@ $.extend( $.fn, {
189
  param = data.required;
190
  delete data.required;
191
  data = $.extend( { required: param }, data );
192
- $( element ).attr( "aria-required", "true" );
193
  }
194
 
195
  // Make sure remote is at back
@@ -203,21 +216,28 @@ $.extend( $.fn, {
203
  }
204
  } );
205
 
 
 
 
 
 
 
 
206
  // Custom selectors
207
  $.extend( $.expr.pseudos || $.expr[ ":" ], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support
208
 
209
- // http://jqueryvalidation.org/blank-selector/
210
  blank: function( a ) {
211
- return !$.trim( "" + $( a ).val() );
212
  },
213
 
214
- // http://jqueryvalidation.org/filled-selector/
215
  filled: function( a ) {
216
  var val = $( a ).val();
217
- return val !== null && !!$.trim( "" + val );
218
  },
219
 
220
- // http://jqueryvalidation.org/unchecked-selector/
221
  unchecked: function( a ) {
222
  return !$( a ).prop( "checked" );
223
  }
@@ -230,7 +250,7 @@ $.validator = function( options, form ) {
230
  this.init();
231
  };
232
 
233
- // http://jqueryvalidation.org/jQuery.validator.format/
234
  $.validator.format = function( source, params ) {
235
  if ( arguments.length === 1 ) {
236
  return function() {
@@ -343,7 +363,7 @@ $.extend( $.validator, {
343
  }
344
  },
345
 
346
- // http://jqueryvalidation.org/jQuery.validator.setDefaults/
347
  setDefaults: function( settings ) {
348
  $.extend( $.validator.defaults, settings );
349
  },
@@ -382,7 +402,8 @@ $.extend( $.validator, {
382
  this.invalid = {};
383
  this.reset();
384
 
385
- var groups = ( this.groups = {} ),
 
386
  rules;
387
  $.each( this.settings.groups, function( key, value ) {
388
  if ( typeof value === "string" ) {
@@ -398,10 +419,18 @@ $.extend( $.validator, {
398
  } );
399
 
400
  function delegate( event ) {
 
401
 
402
  // Set form expando on contenteditable
403
- if ( !this.form && this.hasAttribute( "contenteditable" ) ) {
404
  this.form = $( this ).closest( "form" )[ 0 ];
 
 
 
 
 
 
 
405
  }
406
 
407
  var validator = $.data( this.form, "validator" ),
@@ -426,13 +455,9 @@ $.extend( $.validator, {
426
  if ( this.settings.invalidHandler ) {
427
  $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler );
428
  }
429
-
430
- // Add aria-required to any Static/Data/Class required fields before first validation
431
- // Screen readers require this attribute to be present before the initial submission http://www.w3.org/TR/WCAG-TECHS/ARIA2.html
432
- $( this.currentForm ).find( "[required], [data-rule-required], .required" ).attr( "aria-required", "true" );
433
  },
434
 
435
- // http://jqueryvalidation.org/Validator.form/
436
  form: function() {
437
  this.checkForm();
438
  $.extend( this.submitted, this.errorMap );
@@ -452,7 +477,7 @@ $.extend( $.validator, {
452
  return this.valid();
453
  },
454
 
455
- // http://jqueryvalidation.org/Validator.element/
456
  element: function( element ) {
457
  var cleanElement = this.clean( element ),
458
  checkElement = this.validationTargetFor( cleanElement ),
@@ -503,7 +528,7 @@ $.extend( $.validator, {
503
  return result;
504
  },
505
 
506
- // http://jqueryvalidation.org/Validator.showErrors/
507
  showErrors: function( errors ) {
508
  if ( errors ) {
509
  var validator = this;
@@ -529,7 +554,7 @@ $.extend( $.validator, {
529
  }
530
  },
531
 
532
- // http://jqueryvalidation.org/Validator.resetForm/
533
  resetForm: function() {
534
  if ( $.fn.resetForm ) {
535
  $( this.currentForm ).resetForm();
@@ -570,7 +595,10 @@ $.extend( $.validator, {
570
  var count = 0,
571
  i;
572
  for ( i in obj ) {
573
- if ( obj[ i ] ) {
 
 
 
574
  count++;
575
  }
576
  }
@@ -599,7 +627,7 @@ $.extend( $.validator, {
599
  try {
600
  $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] )
601
  .filter( ":visible" )
602
- .focus()
603
 
604
  // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
605
  .trigger( "focusin" );
@@ -628,13 +656,21 @@ $.extend( $.validator, {
628
  .not( this.settings.ignore )
629
  .filter( function() {
630
  var name = this.name || $( this ).attr( "name" ); // For contenteditable
 
 
631
  if ( !name && validator.settings.debug && window.console ) {
632
  console.error( "%o has no name assigned", this );
633
  }
634
 
635
  // Set form expando on contenteditable
636
- if ( this.hasAttribute( "contenteditable" ) ) {
637
  this.form = $( this ).closest( "form" )[ 0 ];
 
 
 
 
 
 
638
  }
639
 
640
  // Select only the first element for each name, and only those with rules specified
@@ -682,6 +718,7 @@ $.extend( $.validator, {
682
  elementValue: function( element ) {
683
  var $element = $( element ),
684
  type = element.type,
 
685
  val, idx;
686
 
687
  if ( type === "radio" || type === "checkbox" ) {
@@ -690,7 +727,7 @@ $.extend( $.validator, {
690
  return element.validity.badInput ? "NaN" : $element.val();
691
  }
692
 
693
- if ( element.hasAttribute( "contenteditable" ) ) {
694
  val = $element.text();
695
  } else {
696
  val = $element.val();
@@ -735,21 +772,23 @@ $.extend( $.validator, {
735
  } ).length,
736
  dependencyMismatch = false,
737
  val = this.elementValue( element ),
738
- result, method, rule;
739
 
740
- // If a normalizer is defined for this element, then
741
- // call it to retreive the changed value instead
742
- // of using the real one.
743
- // Note that `this` in the normalizer is `element`.
744
  if ( typeof rules.normalizer === "function" ) {
745
- val = rules.normalizer.call( element, val );
 
 
 
746
 
747
- if ( typeof val !== "string" ) {
748
- throw new TypeError( "The normalizer should return a string value." );
749
- }
 
 
750
 
751
- // Delete the normalizer from rules to avoid treating
752
- // it as a pre-defined method.
753
  delete rules.normalizer;
754
  }
755
 
@@ -1089,6 +1128,15 @@ $.extend( $.validator, {
1089
  $( element ).removeClass( this.settings.pendingClass );
1090
  if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) {
1091
  $( this.currentForm ).submit();
 
 
 
 
 
 
 
 
 
1092
  this.formSubmitted = false;
1093
  } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) {
1094
  $( this.currentForm ).triggerHandler( "invalid-form", [ this ] );
@@ -1115,7 +1163,19 @@ $.extend( $.validator, {
1115
  .removeData( "validator" )
1116
  .find( ".validate-equalTo-blur" )
1117
  .off( ".validate-equalTo" )
1118
- .removeClass( "validate-equalTo-blur" );
 
 
 
 
 
 
 
 
 
 
 
 
1119
  }
1120
 
1121
  },
@@ -1219,6 +1279,12 @@ $.extend( $.validator, {
1219
 
1220
  for ( method in $.validator.methods ) {
1221
  value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() );
 
 
 
 
 
 
1222
  this.normalizeAttributeRule( rules, type, method, value );
1223
  }
1224
  return rules;
@@ -1316,7 +1382,7 @@ $.extend( $.validator, {
1316
  return data;
1317
  },
1318
 
1319
- // http://jqueryvalidation.org/jQuery.validator.addMethod/
1320
  addMethod: function( name, method, message ) {
1321
  $.validator.methods[ name ] = method;
1322
  $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ];
@@ -1325,10 +1391,10 @@ $.extend( $.validator, {
1325
  }
1326
  },
1327
 
1328
- // http://jqueryvalidation.org/jQuery.validator.methods/
1329
  methods: {
1330
 
1331
- // http://jqueryvalidation.org/required-method/
1332
  required: function( value, element, param ) {
1333
 
1334
  // Check if dependency is met
@@ -1344,10 +1410,10 @@ $.extend( $.validator, {
1344
  if ( this.checkable( element ) ) {
1345
  return this.getLength( value, element ) > 0;
1346
  }
1347
- return value.length > 0;
1348
  },
1349
 
1350
- // http://jqueryvalidation.org/email-method/
1351
  email: function( value, element ) {
1352
 
1353
  // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
@@ -1357,7 +1423,7 @@ $.extend( $.validator, {
1357
  return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value );
1358
  },
1359
 
1360
- // http://jqueryvalidation.org/url-method/
1361
  url: function( value, element ) {
1362
 
1363
  // Copyright (c) 2010-2013 Diego Perini, MIT licensed
@@ -1367,60 +1433,77 @@ $.extend( $.validator, {
1367
  return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value );
1368
  },
1369
 
1370
- // http://jqueryvalidation.org/date-method/
1371
- date: function( value, element ) {
1372
- return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() );
1373
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1374
 
1375
- // http://jqueryvalidation.org/dateISO-method/
1376
  dateISO: function( value, element ) {
1377
  return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value );
1378
  },
1379
 
1380
- // http://jqueryvalidation.org/number-method/
1381
  number: function( value, element ) {
1382
  return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value );
1383
  },
1384
 
1385
- // http://jqueryvalidation.org/digits-method/
1386
  digits: function( value, element ) {
1387
  return this.optional( element ) || /^\d+$/.test( value );
1388
  },
1389
 
1390
- // http://jqueryvalidation.org/minlength-method/
1391
  minlength: function( value, element, param ) {
1392
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1393
  return this.optional( element ) || length >= param;
1394
  },
1395
 
1396
- // http://jqueryvalidation.org/maxlength-method/
1397
  maxlength: function( value, element, param ) {
1398
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1399
  return this.optional( element ) || length <= param;
1400
  },
1401
 
1402
- // http://jqueryvalidation.org/rangelength-method/
1403
  rangelength: function( value, element, param ) {
1404
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1405
  return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] );
1406
  },
1407
 
1408
- // http://jqueryvalidation.org/min-method/
1409
  min: function( value, element, param ) {
1410
  return this.optional( element ) || value >= param;
1411
  },
1412
 
1413
- // http://jqueryvalidation.org/max-method/
1414
  max: function( value, element, param ) {
1415
  return this.optional( element ) || value <= param;
1416
  },
1417
 
1418
- // http://jqueryvalidation.org/range-method/
1419
  range: function( value, element, param ) {
1420
  return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] );
1421
  },
1422
 
1423
- // http://jqueryvalidation.org/step-method/
1424
  step: function( value, element, param ) {
1425
  var type = $( element ).attr( "type" ),
1426
  errorMessage = "Step attribute on input type " + type + " is not supported.",
@@ -1458,7 +1541,7 @@ $.extend( $.validator, {
1458
  return this.optional( element ) || valid;
1459
  },
1460
 
1461
- // http://jqueryvalidation.org/equalTo-method/
1462
  equalTo: function( value, element, param ) {
1463
 
1464
  // Bind to the blur event of the target in order to revalidate whenever the target field is updated
@@ -1471,7 +1554,7 @@ $.extend( $.validator, {
1471
  return value === target.val();
1472
  },
1473
 
1474
- // http://jqueryvalidation.org/remote-method/
1475
  remote: function( value, element, param, method ) {
1476
  if ( this.optional( element ) ) {
1477
  return "dependency-mismatch";
1
  /*!
2
+ * jQuery Validation Plugin v1.19.2
3
  *
4
+ * https://jqueryvalidation.org/
5
  *
6
+ * Copyright (c) 2020 Jörn Zaefferer
7
  * Released under the MIT license
8
  */
9
  (function( factory ) {
18
 
19
  $.extend( $.fn, {
20
 
21
+ // https://jqueryvalidation.org/validate/
22
  validate: function( options ) {
23
 
24
  // If nothing is selected, return nothing; can't chain anyway
44
  if ( validator.settings.onsubmit ) {
45
 
46
  this.on( "click.validate", ":submit", function( event ) {
47
+
48
+ // Track the used submit button to properly handle scripted
49
+ // submits later.
50
+ validator.submitButton = event.currentTarget;
51
 
52
  // Allow suppressing validation by adding a cancel class to the submit button
53
  if ( $( this ).hasClass( "cancel" ) ) {
67
  // Prevent form submit to be able to see console output
68
  event.preventDefault();
69
  }
70
+
71
  function handle() {
72
  var hidden, result;
73
+
74
+ // Insert a hidden input as a replacement for the missing submit button
75
+ // The hidden input is inserted in two cases:
76
+ // - A user defined a `submitHandler`
77
+ // - There was a pending request due to `remote` method and `stopRequest()`
78
+ // was called to submit the form in case it's valid
79
+ if ( validator.submitButton && ( validator.settings.submitHandler || validator.formSubmitted ) ) {
80
+ hidden = $( "<input type='hidden'/>" )
81
+ .attr( "name", validator.submitButton.name )
82
+ .val( $( validator.submitButton ).val() )
83
+ .appendTo( validator.currentForm );
84
+ }
85
+
86
+ if ( validator.settings.submitHandler && !validator.settings.debug ) {
87
  result = validator.settings.submitHandler.call( validator, validator.currentForm, event );
88
+ if ( hidden ) {
89
 
90
  // And clean up afterwards; thanks to no-block-scope, hidden can be referenced
91
  hidden.remove();
119
  return validator;
120
  },
121
 
122
+ // https://jqueryvalidation.org/valid/
123
  valid: function() {
124
  var valid, validator, errorList;
125
 
140
  return valid;
141
  },
142
 
143
+ // https://jqueryvalidation.org/rules/
144
  rules: function( command, argument ) {
145
  var element = this[ 0 ],
146
+ isContentEditable = typeof this.attr( "contenteditable" ) !== "undefined" && this.attr( "contenteditable" ) !== "false",
147
  settings, staticRules, existingRules, data, param, filtered;
148
 
149
  // If nothing is selected, return empty object; can't chain anyway
150
+ if ( element == null ) {
151
+ return;
152
+ }
153
+
154
+ if ( !element.form && isContentEditable ) {
155
+ element.form = this.closest( "form" )[ 0 ];
156
+ element.name = this.attr( "name" );
157
+ }
158
+
159
+ if ( element.form == null ) {
160
  return;
161
  }
162
 
184
  $.each( argument.split( /\s/ ), function( index, method ) {
185
  filtered[ method ] = existingRules[ method ];
186
  delete existingRules[ method ];
 
 
 
187
  } );
188
  return filtered;
189
  }
203
  param = data.required;
204
  delete data.required;
205
  data = $.extend( { required: param }, data );
 
206
  }
207
 
208
  // Make sure remote is at back
216
  }
217
  } );
218
 
219
+ // JQuery trim is deprecated, provide a trim method based on String.prototype.trim
220
+ var trim = function( str ) {
221
+
222
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim#Polyfill
223
+ return str.replace( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "" );
224
+ };
225
+
226
  // Custom selectors
227
  $.extend( $.expr.pseudos || $.expr[ ":" ], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support
228
 
229
+ // https://jqueryvalidation.org/blank-selector/
230
  blank: function( a ) {
231
+ return !trim( "" + $( a ).val() );
232
  },
233
 
234
+ // https://jqueryvalidation.org/filled-selector/
235
  filled: function( a ) {
236
  var val = $( a ).val();
237
+ return val !== null && !!trim( "" + val );
238
  },
239
 
240
+ // https://jqueryvalidation.org/unchecked-selector/
241
  unchecked: function( a ) {
242
  return !$( a ).prop( "checked" );
243
  }
250
  this.init();
251
  };
252
 
253
+ // https://jqueryvalidation.org/jQuery.validator.format/
254
  $.validator.format = function( source, params ) {
255
  if ( arguments.length === 1 ) {
256
  return function() {
363
  }
364
  },
365
 
366
+ // https://jqueryvalidation.org/jQuery.validator.setDefaults/
367
  setDefaults: function( settings ) {
368
  $.extend( $.validator.defaults, settings );
369
  },
402
  this.invalid = {};
403
  this.reset();
404
 
405
+ var currentForm = this.currentForm,
406
+ groups = ( this.groups = {} ),
407
  rules;
408
  $.each( this.settings.groups, function( key, value ) {
409
  if ( typeof value === "string" ) {
419
  } );
420
 
421
  function delegate( event ) {
422
+ var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false";
423
 
424
  // Set form expando on contenteditable
425
+ if ( !this.form && isContentEditable ) {
426
  this.form = $( this ).closest( "form" )[ 0 ];
427
+ this.name = $( this ).attr( "name" );
428
+ }
429
+
430
+ // Ignore the element if it belongs to another form. This will happen mainly
431
+ // when setting the `form` attribute of an input to the id of another form.
432
+ if ( currentForm !== this.form ) {
433
+ return;
434
  }
435
 
436
  var validator = $.data( this.form, "validator" ),
455
  if ( this.settings.invalidHandler ) {
456
  $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler );
457
  }
 
 
 
 
458
  },
459
 
460
+ // https://jqueryvalidation.org/Validator.form/
461
  form: function() {
462
  this.checkForm();
463
  $.extend( this.submitted, this.errorMap );
477
  return this.valid();
478
  },
479
 
480
+ // https://jqueryvalidation.org/Validator.element/
481
  element: function( element ) {
482
  var cleanElement = this.clean( element ),
483
  checkElement = this.validationTargetFor( cleanElement ),
528
  return result;
529
  },
530
 
531
+ // https://jqueryvalidation.org/Validator.showErrors/
532
  showErrors: function( errors ) {
533
  if ( errors ) {
534
  var validator = this;
554
  }
555
  },
556
 
557
+ // https://jqueryvalidation.org/Validator.resetForm/
558
  resetForm: function() {
559
  if ( $.fn.resetForm ) {
560
  $( this.currentForm ).resetForm();
595
  var count = 0,
596
  i;
597
  for ( i in obj ) {
598
+
599
+ // This check allows counting elements with empty error
600
+ // message as invalid elements
601
+ if ( obj[ i ] !== undefined && obj[ i ] !== null && obj[ i ] !== false ) {
602
  count++;
603
  }
604
  }
627
  try {
628
  $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] )
629
  .filter( ":visible" )
630
+ .trigger( "focus" )
631
 
632
  // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
633
  .trigger( "focusin" );
656
  .not( this.settings.ignore )
657
  .filter( function() {
658
  var name = this.name || $( this ).attr( "name" ); // For contenteditable
659
+ var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false";
660
+
661
  if ( !name && validator.settings.debug && window.console ) {
662
  console.error( "%o has no name assigned", this );
663
  }
664
 
665
  // Set form expando on contenteditable
666
+ if ( isContentEditable ) {
667
  this.form = $( this ).closest( "form" )[ 0 ];
668
+ this.name = name;
669
+ }
670
+
671
+ // Ignore elements that belong to other/nested forms
672
+ if ( this.form !== validator.currentForm ) {
673
+ return false;
674
  }
675
 
676
  // Select only the first element for each name, and only those with rules specified
718
  elementValue: function( element ) {
719
  var $element = $( element ),
720
  type = element.type,
721
+ isContentEditable = typeof $element.attr( "contenteditable" ) !== "undefined" && $element.attr( "contenteditable" ) !== "false",
722
  val, idx;
723
 
724
  if ( type === "radio" || type === "checkbox" ) {
727
  return element.validity.badInput ? "NaN" : $element.val();
728
  }
729
 
730
+ if ( isContentEditable ) {
731
  val = $element.text();
732
  } else {
733
  val = $element.val();
772
  } ).length,
773
  dependencyMismatch = false,
774
  val = this.elementValue( element ),
775
+ result, method, rule, normalizer;
776
 
777
+ // Prioritize the local normalizer defined for this element over the global one
778
+ // if the former exists, otherwise user the global one in case it exists.
 
 
779
  if ( typeof rules.normalizer === "function" ) {
780
+ normalizer = rules.normalizer;
781
+ } else if ( typeof this.settings.normalizer === "function" ) {
782
+ normalizer = this.settings.normalizer;
783
+ }
784
 
785
+ // If normalizer is defined, then call it to retreive the changed value instead
786
+ // of using the real one.
787
+ // Note that `this` in the normalizer is `element`.
788
+ if ( normalizer ) {
789
+ val = normalizer.call( element, val );
790
 
791
+ // Delete the normalizer from rules to avoid treating it as a pre-defined method.
 
792
  delete rules.normalizer;
793
  }
794
 
1128
  $( element ).removeClass( this.settings.pendingClass );
1129
  if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) {
1130
  $( this.currentForm ).submit();
1131
+
1132
+ // Remove the hidden input that was used as a replacement for the
1133
+ // missing submit button. The hidden input is added by `handle()`
1134
+ // to ensure that the value of the used submit button is passed on
1135
+ // for scripted submits triggered by this method
1136
+ if ( this.submitButton ) {
1137
+ $( "input:hidden[name='" + this.submitButton.name + "']", this.currentForm ).remove();
1138
+ }
1139
+
1140
  this.formSubmitted = false;
1141
  } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) {
1142
  $( this.currentForm ).triggerHandler( "invalid-form", [ this ] );
1163
  .removeData( "validator" )
1164
  .find( ".validate-equalTo-blur" )
1165
  .off( ".validate-equalTo" )
1166
+ .removeClass( "validate-equalTo-blur" )
1167
+ .find( ".validate-lessThan-blur" )
1168
+ .off( ".validate-lessThan" )
1169
+ .removeClass( "validate-lessThan-blur" )
1170
+ .find( ".validate-lessThanEqual-blur" )
1171
+ .off( ".validate-lessThanEqual" )
1172
+ .removeClass( "validate-lessThanEqual-blur" )
1173
+ .find( ".validate-greaterThanEqual-blur" )
1174
+ .off( ".validate-greaterThanEqual" )
1175
+ .removeClass( "validate-greaterThanEqual-blur" )
1176
+ .find( ".validate-greaterThan-blur" )
1177
+ .off( ".validate-greaterThan" )
1178
+ .removeClass( "validate-greaterThan-blur" );
1179
  }
1180
 
1181
  },
1279
 
1280
  for ( method in $.validator.methods ) {
1281
  value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() );
1282
+
1283
+ // Cast empty attributes like `data-rule-required` to `true`
1284
+ if ( value === "" ) {
1285
+ value = true;
1286
+ }
1287
+
1288
  this.normalizeAttributeRule( rules, type, method, value );
1289
  }
1290
  return rules;
1382
  return data;
1383
  },
1384
 
1385
+ // https://jqueryvalidation.org/jQuery.validator.addMethod/
1386
  addMethod: function( name, method, message ) {
1387
  $.validator.methods[ name ] = method;
1388
  $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ];
1391
  }
1392
  },
1393
 
1394
+ // https://jqueryvalidation.org/jQuery.validator.methods/
1395
  methods: {
1396
 
1397
+ // https://jqueryvalidation.org/required-method/
1398
  required: function( value, element, param ) {
1399
 
1400
  // Check if dependency is met
1410
  if ( this.checkable( element ) ) {
1411
  return this.getLength( value, element ) > 0;
1412
  }
1413
+ return value !== undefined && value !== null && value.length > 0;
1414
  },
1415
 
1416
+ // https://jqueryvalidation.org/email-method/
1417
  email: function( value, element ) {
1418
 
1419
  // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
1423
  return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value );
1424
  },
1425
 
1426
+ // https://jqueryvalidation.org/url-method/
1427
  url: function( value, element ) {
1428
 
1429
  // Copyright (c) 2010-2013 Diego Perini, MIT licensed
1433
  return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value );
1434
  },
1435
 
1436
+ // https://jqueryvalidation.org/date-method/
1437
+ date: ( function() {
1438
+ var called = false;
1439
+
1440
+ return function( value, element ) {
1441
+ if ( !called ) {
1442
+ called = true;
1443
+ if ( this.settings.debug && window.console ) {
1444
+ console.warn(
1445
+ "The `date` method is deprecated and will be removed in version '2.0.0'.\n" +
1446
+ "Please don't use it, since it relies on the Date constructor, which\n" +
1447
+ "behaves very differently across browsers and locales. Use `dateISO`\n" +
1448
+ "instead or one of the locale specific methods in `localizations/`\n" +
1449
+ "and `additional-methods.js`."
1450
+ );
1451
+ }
1452
+ }
1453
+
1454
+ return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() );
1455
+ };
1456
+ }() ),
1457
 
1458
+ // https://jqueryvalidation.org/dateISO-method/
1459
  dateISO: function( value, element ) {
1460
  return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value );
1461
  },
1462
 
1463
+ // https://jqueryvalidation.org/number-method/
1464
  number: function( value, element ) {
1465
  return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value );
1466
  },
1467
 
1468
+ // https://jqueryvalidation.org/digits-method/
1469
  digits: function( value, element ) {
1470
  return this.optional( element ) || /^\d+$/.test( value );
1471
  },
1472
 
1473
+ // https://jqueryvalidation.org/minlength-method/
1474
  minlength: function( value, element, param ) {
1475
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1476
  return this.optional( element ) || length >= param;
1477
  },
1478
 
1479
+ // https://jqueryvalidation.org/maxlength-method/
1480
  maxlength: function( value, element, param ) {
1481
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1482
  return this.optional( element ) || length <= param;
1483
  },
1484
 
1485
+ // https://jqueryvalidation.org/rangelength-method/
1486
  rangelength: function( value, element, param ) {
1487
  var length = $.isArray( value ) ? value.length : this.getLength( value, element );
1488
  return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] );
1489
  },
1490
 
1491
+ // https://jqueryvalidation.org/min-method/
1492
  min: function( value, element, param ) {
1493
  return this.optional( element ) || value >= param;
1494
  },
1495
 
1496
+ // https://jqueryvalidation.org/max-method/
1497
  max: function( value, element, param ) {
1498
  return this.optional( element ) || value <= param;
1499
  },
1500
 
1501
+ // https://jqueryvalidation.org/range-method/
1502
  range: function( value, element, param ) {
1503
  return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] );
1504
  },
1505
 
1506
+ // https://jqueryvalidation.org/step-method/
1507
  step: function( value, element, param ) {
1508
  var type = $( element ).attr( "type" ),
1509
  errorMessage = "Step attribute on input type " + type + " is not supported.",
1541
  return this.optional( element ) || valid;
1542
  },
1543
 
1544
+ // https://jqueryvalidation.org/equalTo-method/
1545
  equalTo: function( value, element, param ) {
1546
 
1547
  // Bind to the blur event of the target in order to revalidate whenever the target field is updated
1554
  return value === target.val();
1555
  },
1556
 
1557
+ // https://jqueryvalidation.org/remote-method/
1558
  remote: function( value, element, param, method ) {
1559
  if ( this.optional( element ) ) {
1560
  return "dependency-mismatch";
public/js/lib/validate/jquery-validate.min.js CHANGED
@@ -1 +1,4 @@
1
- !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(jQuery)}(function(t){t.extend(t.fn,{validate:function(e){if(this.length){var i=t.data(this[0],"validator");return i||(this.attr("novalidate","novalidate"),i=new t.validator(e,this[0]),t.data(this[0],"validator",i),i.settings.onsubmit&&(this.on("click.validate",":submit",function(e){i.settings.submitHandler&&(i.submitButton=e.target),t(this).hasClass("cancel")&&(i.cancelSubmit=!0),void 0!==t(this).attr("formnovalidate")&&(i.cancelSubmit=!0)}),this.on("submit.validate",function(e){function s(){var s,r;return!i.settings.submitHandler||(i.submitButton&&(s=t("<input type='hidden'/>").attr("name",i.submitButton.name).val(t(i.submitButton).val()).appendTo(i.currentForm)),r=i.settings.submitHandler.call(i,i.currentForm,e),i.submitButton&&s.remove(),void 0!==r&&r)}return i.settings.debug&&e.preventDefault(),i.cancelSubmit?(i.cancelSubmit=!1,s()):i.form()?i.pendingRequest?(i.formSubmitted=!0,!1):s():(i.focusInvalid(),!1)})),i)}e&&e.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing.")},valid:function(){var e,i,s;return t(this[0]).is("form")?e=this.validate().form():(s=[],e=!0,i=t(this[0].form).validate(),this.each(function(){(e=i.element(this)&&e)||(s=s.concat(i.errorList))}),i.errorList=s),e},rules:function(e,i){var s,r,n,a,o,l,h=this[0];if(null!=h&&null!=h.form){if(e)switch(r=(s=t.data(h.form,"validator").settings).rules,n=t.validator.staticRules(h),e){case"add":t.extend(n,t.validator.normalizeRule(i)),delete n.messages,r[h.name]=n,i.messages&&(s.messages[h.name]=t.extend(s.messages[h.name],i.messages));break;case"remove":return i?(l={},t.each(i.split(/\s/),function(e,i){l[i]=n[i],delete n[i],"required"===i&&t(h).removeAttr("aria-required")}),l):(delete r[h.name],n)}return(a=t.validator.normalizeRules(t.extend({},t.validator.classRules(h),t.validator.attributeRules(h),t.validator.dataRules(h),t.validator.staticRules(h)),h)).required&&(o=a.required,delete a.required,a=t.extend({required:o},a),t(h).attr("aria-required","true")),a.remote&&(o=a.remote,delete a.remote,a=t.extend(a,{remote:o})),a}}}),t.extend(t.expr.pseudos||t.expr[":"],{blank:function(e){return!t.trim(""+t(e).val())},filled:function(e){var i=t(e).val();return null!==i&&!!t.trim(""+i)},unchecked:function(e){return!t(e).prop("checked")}}),t.validator=function(e,i){this.settings=t.extend(!0,{},t.validator.defaults,e),this.currentForm=i,this.init()},t.validator.format=function(e,i){return 1===arguments.length?function(){var i=t.makeArray(arguments);return i.unshift(e),t.validator.format.apply(this,i)}:void 0===i?e:(arguments.length>2&&i.constructor!==Array&&(i=t.makeArray(arguments).slice(1)),i.constructor!==Array&&(i=[i]),t.each(i,function(t,i){e=e.replace(new RegExp("\\{"+t+"\\}","g"),function(){return i})}),e)},t.extend(t.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:t([]),errorLabelContainer:t([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(t){this.lastActive=t,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,t,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(t)))},onfocusout:function(t){this.checkable(t)||!(t.name in this.submitted)&&this.optional(t)||this.element(t)},onkeyup:function(e,i){9===i.which&&""===this.elementValue(e)||-1!==t.inArray(i.keyCode,[16,17,18,20,35,36,37,38,39,40,45,144,225])||(e.name in this.submitted||e.name in this.invalid)&&this.element(e)},onclick:function(t){t.name in this.submitted?this.element(t):t.parentNode.name in this.submitted&&this.element(t.parentNode)},highlight:function(e,i,s){"radio"===e.type?this.findByName(e.name).addClass(i).removeClass(s):t(e).addClass(i).removeClass(s)},unhighlight:function(e,i,s){"radio"===e.type?this.findByName(e.name).removeClass(i).addClass(s):t(e).removeClass(i).addClass(s)}},setDefaults:function(e){t.extend(t.validator.defaults,e)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:t.validator.format("Please enter no more than {0} characters."),minlength:t.validator.format("Please enter at least {0} characters."),rangelength:t.validator.format("Please enter a value between {0} and {1} characters long."),range:t.validator.format("Please enter a value between {0} and {1}."),max:t.validator.format("Please enter a value less than or equal to {0}."),min:t.validator.format("Please enter a value greater than or equal to {0}."),step:t.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){this.labelContainer=t(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||t(this.currentForm),this.containers=t(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var e,i=this.groups={};function s(e){!this.form&&this.hasAttribute("contenteditable")&&(this.form=t(this).closest("form")[0]);var i=t.data(this.form,"validator"),s="on"+e.type.replace(/^validate/,""),r=i.settings;r[s]&&!t(this).is(r.ignore)&&r[s].call(i,this,e)}t.each(this.settings.groups,function(e,s){"string"==typeof s&&(s=s.split(/\s/)),t.each(s,function(t,s){i[s]=e})}),e=this.settings.rules,t.each(e,function(i,s){e[i]=t.validator.normalizeRule(s)}),t(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",s).on("click.validate","select, option, [type='radio'], [type='checkbox']",s),this.settings.invalidHandler&&t(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),t(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),t.extend(this.submitted,this.errorMap),this.invalid=t.extend({},this.errorMap),this.valid()||t(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var t=0,e=this.currentElements=this.elements();e[t];t++)this.check(e[t]);return this.valid()},element:function(e){var i,s,r=this.clean(e),n=this.validationTargetFor(r),a=this,o=!0;return void 0===n?delete this.invalid[r.name]:(this.prepareElement(n),this.currentElements=t(n),(s=this.groups[n.name])&&t.each(this.groups,function(t,e){e===s&&t!==n.name&&(r=a.validationTargetFor(a.clean(a.findByName(t))))&&r.name in a.invalid&&(a.currentElements.push(r),o=a.check(r)&&o)}),i=!1!==this.check(n),o=o&&i,this.invalid[n.name]=!i,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),t(e).attr("aria-invalid",!i)),o},showErrors:function(e){if(e){var i=this;t.extend(this.errorMap,e),this.errorList=t.map(this.errorMap,function(t,e){return{message:t,element:i.findByName(e)[0]}}),this.successList=t.grep(this.successList,function(t){return!(t.name in e)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){t.fn.resetForm&&t(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var e=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(e)},resetElements:function(t){var e;if(this.settings.unhighlight)for(e=0;t[e];e++)this.settings.unhighlight.call(this,t[e],this.settings.errorClass,""),this.findByName(t[e].name).removeClass(this.settings.validClass);else t.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(t){var e,i=0;for(e in t)t[e]&&i++;return i},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(t){t.not(this.containers).text(""),this.addWrapper(t).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{t(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(t){}},findLastActive:function(){var e=this.lastActive;return e&&1===t.grep(this.errorList,function(t){return t.element.name===e.name}).length&&e},elements:function(){var e=this,i={};return t(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var s=this.name||t(this).attr("name");return!s&&e.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.hasAttribute("contenteditable")&&(this.form=t(this).closest("form")[0]),!(s in i||!e.objectLength(t(this).rules()))&&(i[s]=!0,!0)})},clean:function(e){return t(e)[0]},errors:function(){var e=this.settings.errorClass.split(" ").join(".");return t(this.settings.errorElement+"."+e,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=t([]),this.toHide=t([])},reset:function(){this.resetInternals(),this.currentElements=t([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(t){this.reset(),this.toHide=this.errorsFor(t)},elementValue:function(e){var i,s,r=t(e),n=e.type;return"radio"===n||"checkbox"===n?this.findByName(e.name).filter(":checked").val():"number"===n&&void 0!==e.validity?e.validity.badInput?"NaN":r.val():(i=e.hasAttribute("contenteditable")?r.text():r.val(),"file"===n?"C:\\fakepath\\"===i.substr(0,12)?i.substr(12):(s=i.lastIndexOf("/"))>=0?i.substr(s+1):(s=i.lastIndexOf("\\"))>=0?i.substr(s+1):i:"string"==typeof i?i.replace(/\r/g,""):i)},check:function(e){e=this.validationTargetFor(this.clean(e));var i,s,r,n=t(e).rules(),a=t.map(n,function(t,e){return e}).length,o=!1,l=this.elementValue(e);if("function"==typeof n.normalizer){if("string"!=typeof(l=n.normalizer.call(e,l)))throw new TypeError("The normalizer should return a string value.");delete n.normalizer}for(s in n){r={method:s,parameters:n[s]};try{if("dependency-mismatch"===(i=t.validator.methods[s].call(this,l,e,r.parameters))&&1===a){o=!0;continue}if(o=!1,"pending"===i)return void(this.toHide=this.toHide.not(this.errorsFor(e)));if(!i)return this.formatAndAdd(e,r),!1}catch(t){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+e.id+", check the '"+r.method+"' method.",t),t instanceof TypeError&&(t.message+=". Exception occurred when checking element "+e.id+", check the '"+r.method+"' method."),t}}if(!o)return this.objectLength(n)&&this.successList.push(e),!0},customDataMessage:function(e,i){return t(e).data("msg"+i.charAt(0).toUpperCase()+i.substring(1).toLowerCase())||t(e).data("msg")},customMessage:function(t,e){var i=this.settings.messages[t];return i&&(i.constructor===String?i:i[e])},findDefined:function(){for(var t=0;t<arguments.length;t++)if(void 0!==arguments[t])return arguments[t]},defaultMessage:function(e,i){"string"==typeof i&&(i={method:i});var s=this.findDefined(this.customMessage(e.name,i.method),this.customDataMessage(e,i.method),!this.settings.ignoreTitle&&e.title||void 0,t.validator.messages[i.method],"<strong>Warning: No message defined for "+e.name+"</strong>"),r=/\$?\{(\d+)\}/g;return"function"==typeof s?s=s.call(this,i.parameters,e):r.test(s)&&(s=t.validator.format(s.replace(r,"{$1}"),i.parameters)),s},formatAndAdd:function(t,e){var i=this.defaultMessage(t,e);this.errorList.push({message:i,element:t,method:e.method}),this.errorMap[t.name]=i,this.submitted[t.name]=i},addWrapper:function(t){return this.settings.wrapper&&(t=t.add(t.parent(this.settings.wrapper))),t},defaultShowErrors:function(){var t,e,i;for(t=0;this.errorList[t];t++)i=this.errorList[t],this.settings.highlight&&this.settings.highlight.call(this,i.element,this.settings.errorClass,this.settings.validClass),this.showLabel(i.element,i.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(t=0;this.successList[t];t++)this.showLabel(this.successList[t]);if(this.settings.unhighlight)for(t=0,e=this.validElements();e[t];t++)this.settings.unhighlight.call(this,e[t],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return t(this.errorList).map(function(){return this.element})},showLabel:function(e,i){var s,r,n,a,o=this.errorsFor(e),l=this.idOrName(e),h=t(e).attr("aria-describedby");o.length?(o.removeClass(this.settings.validClass).addClass(this.settings.errorClass),o.html(i)):(s=o=t("<"+this.settings.errorElement+">").attr("id",l+"-error").addClass(this.settings.errorClass).html(i||""),this.settings.wrapper&&(s=o.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(s):this.settings.errorPlacement?this.settings.errorPlacement.call(this,s,t(e)):s.insertAfter(e),o.is("label")?o.attr("for",l):0===o.parents("label[for='"+this.escapeCssMeta(l)+"']").length&&(n=o.attr("id"),h?h.match(new RegExp("\\b"+this.escapeCssMeta(n)+"\\b"))||(h+=" "+n):h=n,t(e).attr("aria-describedby",h),(r=this.groups[e.name])&&(a=this,t.each(a.groups,function(e,i){i===r&&t("[name='"+a.escapeCssMeta(e)+"']",a.currentForm).attr("aria-describedby",o.attr("id"))})))),!i&&this.settings.success&&(o.text(""),"string"==typeof this.settings.success?o.addClass(this.settings.success):this.settings.success(o,e)),this.toShow=this.toShow.add(o)},errorsFor:function(e){var i=this.escapeCssMeta(this.idOrName(e)),s=t(e).attr("aria-describedby"),r="label[for='"+i+"'], label[for='"+i+"'] *";return s&&(r=r+", #"+this.escapeCssMeta(s).replace(/\s+/g,", #")),this.errors().filter(r)},escapeCssMeta:function(t){return t.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(t){return this.groups[t.name]||(this.checkable(t)?t.name:t.id||t.name)},validationTargetFor:function(e){return this.checkable(e)&&(e=this.findByName(e.name)),t(e).not(this.settings.ignore)[0]},checkable:function(t){return/radio|checkbox/i.test(t.type)},findByName:function(e){return t(this.currentForm).find("[name='"+this.escapeCssMeta(e)+"']")},getLength:function(e,i){switch(i.nodeName.toLowerCase()){case"select":return t("option:selected",i).length;case"input":if(this.checkable(i))return this.findByName(i.name).filter(":checked").length}return e.length},depend:function(t,e){return!this.dependTypes[typeof t]||this.dependTypes[typeof t](t,e)},dependTypes:{boolean:function(t){return t},string:function(e,i){return!!t(e,i.form).length},function:function(t,e){return t(e)}},optional:function(e){var i=this.elementValue(e);return!t.validator.methods.required.call(this,i,e)&&"dependency-mismatch"},startRequest:function(e){this.pending[e.name]||(this.pendingRequest++,t(e).addClass(this.settings.pendingClass),this.pending[e.name]=!0)},stopRequest:function(e,i){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[e.name],t(e).removeClass(this.settings.pendingClass),i&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(t(this.currentForm).submit(),this.formSubmitted=!1):!i&&0===this.pendingRequest&&this.formSubmitted&&(t(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(e,i){return i="string"==typeof i&&i||"remote",t.data(e,"previousValue")||t.data(e,"previousValue",{old:null,valid:!0,message:this.defaultMessage(e,{method:i})})},destroy:function(){this.resetForm(),t(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(e,i){e.constructor===String?this.classRuleSettings[e]=i:t.extend(this.classRuleSettings,e)},classRules:function(e){var i={},s=t(e).attr("class");return s&&t.each(s.split(" "),function(){this in t.validator.classRuleSettings&&t.extend(i,t.validator.classRuleSettings[this])}),i},normalizeAttributeRule:function(t,e,i,s){/min|max|step/.test(i)&&(null===e||/number|range|text/.test(e))&&(s=Number(s),isNaN(s)&&(s=void 0)),s||0===s?t[i]=s:e===i&&"range"!==e&&(t[i]=!0)},attributeRules:function(e){var i,s,r={},n=t(e),a=e.getAttribute("type");for(i in t.validator.methods)"required"===i?(""===(s=e.getAttribute(i))&&(s=!0),s=!!s):s=n.attr(i),this.normalizeAttributeRule(r,a,i,s);return r.maxlength&&/-1|2147483647|524288/.test(r.maxlength)&&delete r.maxlength,r},dataRules:function(e){var i,s,r={},n=t(e),a=e.getAttribute("type");for(i in t.validator.methods)s=n.data("rule"+i.charAt(0).toUpperCase()+i.substring(1).toLowerCase()),this.normalizeAttributeRule(r,a,i,s);return r},staticRules:function(e){var i={},s=t.data(e.form,"validator");return s.settings.rules&&(i=t.validator.normalizeRule(s.settings.rules[e.name])||{}),i},normalizeRules:function(e,i){return t.each(e,function(s,r){if(!1!==r){if(r.param||r.depends){var n=!0;switch(typeof r.depends){case"string":n=!!t(r.depends,i.form).length;break;case"function":n=r.depends.call(i,i)}n?e[s]=void 0===r.param||r.param:(t.data(i.form,"validator").resetElements(t(i)),delete e[s])}}else delete e[s]}),t.each(e,function(s,r){e[s]=t.isFunction(r)&&"normalizer"!==s?r(i):r}),t.each(["minlength","maxlength"],function(){e[this]&&(e[this]=Number(e[this]))}),t.each(["rangelength","range"],function(){var i;e[this]&&(t.isArray(e[this])?e[this]=[Number(e[this][0]),Number(e[this][1])]:"string"==typeof e[this]&&(i=e[this].replace(/[\[\]]/g,"").split(/[\s,]+/),e[this]=[Number(i[0]),Number(i[1])]))}),t.validator.autoCreateRanges&&(null!=e.min&&null!=e.max&&(e.range=[e.min,e.max],delete e.min,delete e.max),null!=e.minlength&&null!=e.maxlength&&(e.rangelength=[e.minlength,e.maxlength],delete e.minlength,delete e.maxlength)),e},normalizeRule:function(e){if("string"==typeof e){var i={};t.each(e.split(/\s/),function(){i[this]=!0}),e=i}return e},addMethod:function(e,i,s){t.validator.methods[e]=i,t.validator.messages[e]=void 0!==s?s:t.validator.messages[e],i.length<3&&t.validator.addClassRules(e,t.validator.normalizeRule(e))},methods:{required:function(e,i,s){if(!this.depend(s,i))return"dependency-mismatch";if("select"===i.nodeName.toLowerCase()){var r=t(i).val();return r&&r.length>0}return this.checkable(i)?this.getLength(e,i)>0:e.length>0},email:function(t,e){return this.optional(e)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(t)},url:function(t,e){return this.optional(e)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(t)},date:function(t,e){return this.optional(e)||!/Invalid|NaN/.test(new Date(t).toString())},dateISO:function(t,e){return this.optional(e)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(t)},number:function(t,e){return this.optional(e)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(t)},digits:function(t,e){return this.optional(e)||/^\d+$/.test(t)},minlength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(e,i);return this.optional(i)||r>=s},maxlength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(e,i);return this.optional(i)||r<=s},rangelength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(e,i);return this.optional(i)||r>=s[0]&&r<=s[1]},min:function(t,e,i){return this.optional(e)||t>=i},max:function(t,e,i){return this.optional(e)||t<=i},range:function(t,e,i){return this.optional(e)||t>=i[0]&&t<=i[1]},step:function(e,i,s){var r,n=t(i).attr("type"),a="Step attribute on input type "+n+" is not supported.",o=new RegExp("\\b"+n+"\\b"),l=function(t){var e=(""+t).match(/(?:\.(\d+))?$/);return e&&e[1]?e[1].length:0},h=function(t){return Math.round(t*Math.pow(10,r))},u=!0;if(n&&!o.test(["text","number","range"].join()))throw new Error(a);return r=l(s),(l(e)>r||h(e)%h(s)!=0)&&(u=!1),this.optional(i)||u},equalTo:function(e,i,s){var r=t(s);return this.settings.onfocusout&&r.not(".validate-equalTo-blur").length&&r.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){t(i).valid()}),e===r.val()},remote:function(e,i,s,r){if(this.optional(i))return"dependency-mismatch";r="string"==typeof r&&r||"remote";var n,a,o,l=this.previousValue(i,r);return this.settings.messages[i.name]||(this.settings.messages[i.name]={}),l.originalMessage=l.originalMessage||this.settings.messages[i.name][r],this.settings.messages[i.name][r]=l.message,s="string"==typeof s&&{url:s}||s,o=t.param(t.extend({data:e},s.data)),l.old===o?l.valid:(l.old=o,n=this,this.startRequest(i),(a={})[i.name]=e,t.ajax(t.extend(!0,{mode:"abort",port:"validate"+i.name,dataType:"json",data:a,context:n.currentForm,success:function(t){var s,a,o,h=!0===t||"true"===t;n.settings.messages[i.name][r]=l.originalMessage,h?(o=n.formSubmitted,n.resetInternals(),n.toHide=n.errorsFor(i),n.formSubmitted=o,n.successList.push(i),n.invalid[i.name]=!1,n.showErrors()):(s={},a=t||n.defaultMessage(i,{method:r,parameters:e}),s[i.name]=l.message=a,n.invalid[i.name]=!0,n.showErrors(s)),l.valid=h,n.stopRequest(i,h)}},s)),"pending")}}});var e,i={};return t.ajaxPrefilter?t.ajaxPrefilter(function(t,e,s){var r=t.port;"abort"===t.mode&&(i[r]&&i[r].abort(),i[r]=s)}):(e=t.ajax,t.ajax=function(s){var r=("mode"in s?s:t.ajaxSettings).mode,n=("port"in s?s:t.ajaxSettings).port;return"abort"===r?(i[n]&&i[n].abort(),i[n]=e.apply(this,arguments),i[n]):e.apply(this,arguments)}),t});
 
 
 
1
+ /*! jQuery Validation Plugin - v1.19.2 - 5/23/2020
2
+ * https://jqueryvalidation.org/
3
+ * Copyright (c) 2020 Jörn Zaefferer; Licensed MIT */
4
+ !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("<input type='hidden'/>").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!(c.settings.submitHandler&&!c.settings.debug)||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0],k="undefined"!=typeof this.attr("contenteditable")&&"false"!==this.attr("contenteditable");if(null!=j&&(!j.form&&k&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}});var b=function(a){return a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")};a.extend(a.expr.pseudos||a.expr[":"],{blank:function(c){return!b(""+a(c).val())},filled:function(c){var d=a(c).val();return null!==d&&!!b(""+d)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");if(!this.form&&c&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name")),d===this.form){var e=a.data(this.form,"validator"),f="on"+b.type.replace(/^validate/,""),g=e.settings;g[f]&&!a(this).is(g.ignore)&&g[f].call(e,this,b)}}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.currentForm,e=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){e[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").trigger("focus").trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name"),e="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),e&&(this.form=a(this).closest("form")[0],this.name=d),this.form===b.currentForm&&(!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0))})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type,g="undefined"!=typeof e.attr("contenteditable")&&"false"!==e.attr("contenteditable");return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=g?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);"function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f&&(j=f.call(b,j),delete g.normalizer);for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]},defaultMessage:function(b,c){"string"==typeof c&&(c={method:c});var d=this.findDefined(this.customMessage(b.name,c.method),this.customDataMessage(b,c.method),!this.settings.ignoreTitle&&b.title||void 0,a.validator.messages[c.method],"<strong>Warning: No message defined for "+b.name+"</strong>"),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur").find(".validate-lessThan-blur").off(".validate-lessThan").removeClass("validate-lessThan-blur").find(".validate-lessThanEqual-blur").off(".validate-lessThanEqual").removeClass("validate-lessThanEqual-blur").find(".validate-greaterThanEqual-blur").off(".validate-greaterThanEqual").removeClass("validate-greaterThanEqual-blur").find(".validate-greaterThan-blur").off(".validate-greaterThan").removeClass("validate-greaterThan-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),""===d&&(d=!0),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:void 0!==b&&null!==b&&b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(a)},date:function(){var a=!1;return function(b,c){return a||(a=!0,this.settings.debug&&window.console&&console.warn("The `date` method is deprecated and will be removed in version '2.0.0'.\nPlease don't use it, since it relies on the Date constructor, which\nbehaves very differently across browsers and locales. Use `dateISO`\ninstead or one of the locale specific methods in `localizations/`\nand `additional-methods.js`.")),this.optional(c)||!/Invalid|NaN/.test(new Date(b).toString())}}(),dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var c,d={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,c){var e=a.port;"abort"===a.mode&&(d[e]&&d[e].abort(),d[e]=c)}):(c=a.ajax,a.ajax=function(b){var e=("mode"in b?b:a.ajaxSettings).mode,f=("port"in b?b:a.ajaxSettings).port;return"abort"===e?(d[f]&&d[f].abort(),d[f]=c.apply(this,arguments),d[f]):c.apply(this,arguments)}),a});
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: wpchill,silkalns,cdillon27
3
  Tags: testimonials, testimonial slider, testimonial form, star ratings
4
  Requires at least: 5.2
5
  Requires PHP: 5.6
6
- Tested up to: 5.6
7
- Stable tag: 2.50.4
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
3
  Tags: testimonials, testimonial slider, testimonial form, star ratings
4
  Requires at least: 5.2
5
  Requires PHP: 5.6
6
+ Tested up to: 5.5
7
+ Stable tag: 2.51.0
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
strong-testimonials.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: Collect and display your testimonials or reviews.
6
  * Author: WPChill
7
  * Author URI: https://wpchill.com/
8
- * Version: 2.50.4
9
  * Text Domain: strong-testimonials
10
  * Domain Path: /languages
11
  * Requires: 4.6 or higher
@@ -45,7 +45,7 @@ if ( ! defined( 'ABSPATH' ) ) {
45
  exit;
46
  }
47
 
48
- define( 'WPMTST_VERSION', '2.50.4' );
49
  define( 'WPMTST_PLUGIN', plugin_basename( __FILE__ ) ); // strong-testimonials/strong-testimonials.php
50
  define( 'WPMTST', dirname( WPMTST_PLUGIN ) ); // strong-testimonials
51
  defined( 'WPMTST_STORE_URL' ) || define( 'WPMTST_STORE_URL', 'https://strongtestimonials.com' );
5
  * Description: Collect and display your testimonials or reviews.
6
  * Author: WPChill
7
  * Author URI: https://wpchill.com/
8
+ * Version: 2.51.0
9
  * Text Domain: strong-testimonials
10
  * Domain Path: /languages
11
  * Requires: 4.6 or higher
45
  exit;
46
  }
47
 
48
+ define( 'WPMTST_VERSION', '2.51.0' );
49
  define( 'WPMTST_PLUGIN', plugin_basename( __FILE__ ) ); // strong-testimonials/strong-testimonials.php
50
  define( 'WPMTST', dirname( WPMTST_PLUGIN ) ); // strong-testimonials
51
  defined( 'WPMTST_STORE_URL' ) || define( 'WPMTST_STORE_URL', 'https://strongtestimonials.com' );