Formidable Forms – Form Builder for WordPress - Version 5.1

Version Description

  • Updated Bootstrap Multiselect to version 1.1.1, fixing issues with the accessibility of backend multiselect dropdowns for blind users.
  • New: Inputs with errors will now add the aria-describedby attribute during JavaScript validation for more accessible errors.
  • New: Form errors will now always include the role="alert" attribute for more accessible errors. New fields will now also include role="alert" in custom field HTML.
  • New: Added a new frm_entries_column_value filter hook.
Download this release

Release Info

Developer formidableforms
Plugin Icon 128x128 Formidable Forms – Form Builder for WordPress
Version 5.1
Comparing to
See all releases

Code changes from version 5.0.17 to 5.1

classes/controllers/FrmAppController.php CHANGED
@@ -460,7 +460,7 @@ class FrmAppController {
460
 
461
  // load multselect js
462
  $depends_on = array( 'jquery', 'bootstrap_tooltip' );
463
- wp_register_script( 'bootstrap-multiselect', FrmAppHelper::plugin_url() . '/js/bootstrap-multiselect.js', $depends_on, '0.9.8', true );
464
 
465
  $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
466
  $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
460
 
461
  // load multselect js
462
  $depends_on = array( 'jquery', 'bootstrap_tooltip' );
463
+ wp_register_script( 'bootstrap-multiselect', FrmAppHelper::plugin_url() . '/js/bootstrap-multiselect.js', $depends_on, '1.1.1', true );
464
 
465
  $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
466
  $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
classes/controllers/FrmFieldsController.php CHANGED
@@ -743,7 +743,9 @@ class FrmFieldsController {
743
  $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
744
  $default_html = array(
745
  '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
 
746
  '<div class="frm_error">[error]</div>',
 
747
  );
748
 
749
  if ( in_array( $error_body, $default_html, true ) ) {
743
  $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
744
  $default_html = array(
745
  '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
746
+ '<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>',
747
  '<div class="frm_error">[error]</div>',
748
+ '<div class="frm_error" role="alert">[error]</div>',
749
  );
750
 
751
  if ( in_array( $error_body, $default_html, true ) ) {
classes/controllers/FrmFormsController.php CHANGED
@@ -685,7 +685,7 @@ class FrmFormsController {
685
  /**
686
  * Check the page being loaded, determine if this is a page that should include the form popup.
687
  *
688
- * @since x.x
689
  *
690
  * @return bool
691
  */
685
  /**
686
  * Check the page being loaded, determine if this is a page that should include the form popup.
687
  *
688
+ * @since 5.0.14
689
  *
690
  * @return bool
691
  */
classes/helpers/FrmAppHelper.php CHANGED
@@ -11,7 +11,7 @@ class FrmAppHelper {
11
  /**
12
  * @since 2.0
13
  */
14
- public static $plug_version = '5.0.17';
15
 
16
  /**
17
  * @since 1.07.02
@@ -2934,9 +2934,11 @@ class FrmAppHelper {
2934
 
2935
  /**
2936
  * Output HTML containing reference text for accessibility
 
 
2937
  */
2938
  public static function multiselect_accessibility() {
2939
- include_once self::plugin_path() . '/classes/views/frm-forms/multiselect-accessibility.php';
2940
  }
2941
 
2942
  public static function get_menu_icon_class() {
11
  /**
12
  * @since 2.0
13
  */
14
+ public static $plug_version = '5.1';
15
 
16
  /**
17
  * @since 1.07.02
2934
 
2935
  /**
2936
  * Output HTML containing reference text for accessibility
2937
+ *
2938
+ * @deprecated 5.1
2939
  */
2940
  public static function multiselect_accessibility() {
2941
+ _deprecated_function( __METHOD__, '5.1' );
2942
  }
2943
 
2944
  public static function get_menu_icon_class() {
classes/helpers/FrmEntriesListHelper.php CHANGED
@@ -291,6 +291,16 @@ class FrmEntriesListHelper extends FrmListHelper {
291
  if ( $val === false ) {
292
  $this->get_column_value( $item, $val );
293
  }
 
 
 
 
 
 
 
 
 
 
294
  }
295
 
296
  return $val;
291
  if ( $val === false ) {
292
  $this->get_column_value( $item, $val );
293
  }
294
+
295
+ /**
296
+ * Allows changing entries list column value.
297
+ *
298
+ * @since 5.1
299
+ *
300
+ * @param mixed $val Column value.
301
+ * @param array $args Contains `item` and `col_name`.
302
+ */
303
+ $val = apply_filters( 'frm_entries_column_value', $val, compact( 'item', 'col_name' ) );
304
  }
305
 
306
  return $val;
classes/models/FrmAntiSpam.php CHANGED
@@ -8,7 +8,7 @@ if ( ! defined( 'ABSPATH' ) ) {
8
  *
9
  * This token class generates tokens that are used in our Anti-Spam checking.
10
  *
11
- * @since xx.xx
12
  */
13
  class FrmAntiSpam extends FrmValidate {
14
 
@@ -32,7 +32,7 @@ class FrmAntiSpam extends FrmValidate {
32
  /**
33
  * Initialise the actions for the Anti-spam.
34
  *
35
- * @since xx.xx
36
  */
37
  public function init() {
38
  add_filter( 'frm_form_attributes', array( $this, 'add_token_to_form' ), 10, 1 );
@@ -42,7 +42,7 @@ class FrmAntiSpam extends FrmValidate {
42
  /**
43
  * Return a valid token.
44
  *
45
- * @since xx.xx
46
  *
47
  * @param mixed $current True to use current time, otherwise a timestamp string.
48
  *
@@ -91,7 +91,7 @@ class FrmAntiSpam extends FrmValidate {
91
  * 'frm_form_token_check_before_today'
92
  * 'frm_form_token_check_after_today'
93
  *
94
- * @since xx.xx
95
  *
96
  * @return array Array of all valid tokens to check against.
97
  */
@@ -143,7 +143,7 @@ class FrmAntiSpam extends FrmValidate {
143
  * and frm_token_validity_in_days to extend the validation period).
144
  * By default tokens are valid for day.
145
  *
146
- * @since xx.xx
147
  *
148
  * @param string $token Token to validate.
149
  *
@@ -157,7 +157,7 @@ class FrmAntiSpam extends FrmValidate {
157
  /**
158
  * Add the token field to the form.
159
  *
160
- * @since xx.xx
161
  *
162
  * @param string $attributes
163
  *
@@ -188,7 +188,7 @@ class FrmAntiSpam extends FrmValidate {
188
  /**
189
  * Validate Anti-spam if enabled.
190
  *
191
- * @since xx.xx
192
  *
193
  * @return bool|string True or a string with the error.
194
  */
@@ -235,7 +235,7 @@ class FrmAntiSpam extends FrmValidate {
235
  /**
236
  * Helper to run our filter on all the responses for the antispam checks.
237
  *
238
- * @since xx.xx
239
  *
240
  * @param bool|string $is_valid Is valid entry or not.
241
  *
@@ -248,7 +248,7 @@ class FrmAntiSpam extends FrmValidate {
248
  /**
249
  * Helper to get the missing token message.
250
  *
251
- * @since xx.xx
252
  *
253
  * @return string missing token message.
254
  */
@@ -259,7 +259,7 @@ class FrmAntiSpam extends FrmValidate {
259
  /**
260
  * Helper to get the invalid token message.
261
  *
262
- * @since xx.xx
263
  *
264
  * @return string Invalid token message.
265
  */
@@ -270,7 +270,7 @@ class FrmAntiSpam extends FrmValidate {
270
  /**
271
  * If a user is a super admin, add a support link to the message.
272
  *
273
- * @since xx.xx
274
  *
275
  * @return string Support text if super admin, empty string if not.
276
  */
8
  *
9
  * This token class generates tokens that are used in our Anti-Spam checking.
10
  *
11
+ * @since 4.11
12
  */
13
  class FrmAntiSpam extends FrmValidate {
14
 
32
  /**
33
  * Initialise the actions for the Anti-spam.
34
  *
35
+ * @since 4.11
36
  */
37
  public function init() {
38
  add_filter( 'frm_form_attributes', array( $this, 'add_token_to_form' ), 10, 1 );
42
  /**
43
  * Return a valid token.
44
  *
45
+ * @since 4.11
46
  *
47
  * @param mixed $current True to use current time, otherwise a timestamp string.
48
  *
91
  * 'frm_form_token_check_before_today'
92
  * 'frm_form_token_check_after_today'
93
  *
94
+ * @since 4.11
95
  *
96
  * @return array Array of all valid tokens to check against.
97
  */
143
  * and frm_token_validity_in_days to extend the validation period).
144
  * By default tokens are valid for day.
145
  *
146
+ * @since 4.11
147
  *
148
  * @param string $token Token to validate.
149
  *
157
  /**
158
  * Add the token field to the form.
159
  *
160
+ * @since 4.11
161
  *
162
  * @param string $attributes
163
  *
188
  /**
189
  * Validate Anti-spam if enabled.
190
  *
191
+ * @since 4.11
192
  *
193
  * @return bool|string True or a string with the error.
194
  */
235
  /**
236
  * Helper to run our filter on all the responses for the antispam checks.
237
  *
238
+ * @since 4.11
239
  *
240
  * @param bool|string $is_valid Is valid entry or not.
241
  *
248
  /**
249
  * Helper to get the missing token message.
250
  *
251
+ * @since 4.11
252
  *
253
  * @return string missing token message.
254
  */
259
  /**
260
  * Helper to get the invalid token message.
261
  *
262
+ * @since 4.11
263
  *
264
  * @return string Invalid token message.
265
  */
270
  /**
271
  * If a user is a super admin, add a support link to the message.
272
  *
273
+ * @since 4.11
274
  *
275
  * @return string Support text if super admin, empty string if not.
276
  */
classes/models/FrmFieldFormHtml.php CHANGED
@@ -230,9 +230,39 @@ class FrmFieldFormHtml {
230
  private function replace_error_shortcode() {
231
  $this->maybe_add_error_id();
232
  $error = isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ? $this->pass_args['errors'][ 'field' . $this->field_id ] : false;
 
 
 
 
 
 
 
 
 
233
  FrmShortcodeHelper::remove_inline_conditions( ! empty( $error ), 'error', $error, $this->html );
234
  }
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  /**
237
  * Add an ID to the error message for aria-describedby.
238
  * This ID was added to the HTML in v3.06.02.
230
  private function replace_error_shortcode() {
231
  $this->maybe_add_error_id();
232
  $error = isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ? $this->pass_args['errors'][ 'field' . $this->field_id ] : false;
233
+
234
+ if ( ! empty( $error ) && false === strpos( $this->html, 'role="alert"' ) ) {
235
+ $error_body = self::get_error_body( $this->html );
236
+ if ( is_string( $error_body ) && false === strpos( $error_body, 'role=' ) ) {
237
+ $new_error_body = preg_replace( '/class="frm_error/', 'role="alert" class="frm_error', $error_body, 1 );
238
+ $this->html = str_replace( '[if error]' . $error_body . '[/if error]', '[if error]' . $new_error_body . '[/if error]', $this->html );
239
+ }
240
+ }
241
+
242
  FrmShortcodeHelper::remove_inline_conditions( ! empty( $error ), 'error', $error, $this->html );
243
  }
244
 
245
+ /**
246
+ * Pull the HTML between [if error] and [/if error] shortcodes.
247
+ *
248
+ * @param string $html
249
+ * @return string|false
250
+ */
251
+ private static function get_error_body( $html ) {
252
+ $start = strpos( $html, '[if error]' );
253
+ if ( false === $start ) {
254
+ return false;
255
+ }
256
+
257
+ $end = strpos( $html, '[/if error]', $start );
258
+ if ( false === $end ) {
259
+ return false;
260
+ }
261
+
262
+ $error_body = substr( $html, $start + 10, $end - $start - 10 );
263
+ return $error_body;
264
+ }
265
+
266
  /**
267
  * Add an ID to the error message for aria-describedby.
268
  * This ID was added to the HTML in v3.06.02.
classes/models/fields/FrmFieldType.php CHANGED
@@ -177,7 +177,7 @@ abstract class FrmFieldType {
177
  </$label>
178
  $input
179
  [if description]<div class="frm_description" id="frm_desc_field_[key]">[description]</div>[/if description]
180
- [if error]<div class="frm_error" id="frm_error_field_[key]">[error]</div>[/if error]
181
  </div>
182
  DEFAULT_HTML;
183
 
177
  </$label>
178
  $input
179
  [if description]<div class="frm_description" id="frm_desc_field_[key]">[description]</div>[/if description]
180
+ [if error]<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>[/if error]
181
  </div>
182
  DEFAULT_HTML;
183
 
classes/views/frm-fields/front-end/combo-field/combo-field.php CHANGED
@@ -80,7 +80,7 @@ $inputs_attrs = $this->get_inputs_container_attrs();
80
  // Don't show individual field errors when there is a combo field error.
81
  if ( ! empty( $errors ) && isset( $errors[ 'field' . $field_id . '-' . $name ] ) && ! isset( $errors[ 'field' . $field_id ] ) ) {
82
  ?>
83
- <div class="frm_error"><?php echo esc_html( $errors[ 'field' . $field_id . '-' . $name ] ); ?></div>
84
  <?php } ?>
85
  </div>
86
  <?php } ?>
80
  // Don't show individual field errors when there is a combo field error.
81
  if ( ! empty( $errors ) && isset( $errors[ 'field' . $field_id . '-' . $name ] ) && ! isset( $errors[ 'field' . $field_id ] ) ) {
82
  ?>
83
+ <div class="frm_error" role="alert"><?php echo esc_html( $errors[ 'field' . $field_id . '-' . $name ] ); ?></div>
84
  <?php } ?>
85
  </div>
86
  <?php } ?>
classes/views/frm-forms/form.php CHANGED
@@ -64,4 +64,3 @@ if ( ! defined( 'ABSPATH' ) ) {
64
  </div>
65
  <?php
66
  FrmFieldsHelper::bulk_options_overlay();
67
- FrmAppHelper::multiselect_accessibility();
64
  </div>
65
  <?php
66
  FrmFieldsHelper::bulk_options_overlay();
 
classes/views/frm-forms/multiselect-accessibility.php CHANGED
@@ -2,9 +2,4 @@
2
  if ( ! defined( 'ABSPATH' ) ) {
3
  die( 'You are not allowed to call this page directly.' );
4
  }
5
- ?>
6
- <div class="hidden">
7
- <div id="frm_press_space_checked"><?php esc_html_e( 'Checked. To uncheck this option, press Space or Enter', 'formidable' ); ?></div>
8
- <div id="frm_press_space_unchecked"><?php esc_html_e( 'Unchecked. To check this option, press Space or Enter', 'formidable' ); ?></div>
9
- <div id="frm_multiselect_button"><?php esc_html_e( 'You are on a Custom List of Checkboxes. To open, press Enter. Use Up and Down arrow keys to switch between options', 'formidable' ); ?></div>
10
- </div>
2
  if ( ! defined( 'ABSPATH' ) ) {
3
  die( 'You are not allowed to call this page directly.' );
4
  }
5
+ _deprecated_file( esc_html( basename( __FILE__ ) ), 'x.x' );
 
 
 
 
 
classes/views/frm-forms/settings.php CHANGED
@@ -64,6 +64,3 @@ if ( ! defined( 'ABSPATH' ) ) {
64
  </div>
65
  </form>
66
  </div>
67
-
68
- <?php
69
- FrmAppHelper::multiselect_accessibility();
64
  </div>
65
  </form>
66
  </div>
 
 
 
classes/views/frm-settings/permissions.php CHANGED
@@ -14,12 +14,9 @@ if ( ! defined( 'ABSPATH' ) ) {
14
  ?>
15
  <tr>
16
  <td class="frm_left_label">
17
- <label id="for_<?php echo esc_attr( str_replace( '[]', '', $role_field_name ) ); ?>"><?php echo esc_html( $frm_role_description ); ?></label>
18
  </td>
19
  <td><?php FrmAppHelper::wp_roles_dropdown( $role_field_name, $frm_settings->$frm_role, 'multiple' ); ?></td>
20
  </tr>
21
  <?php } ?>
22
  </table>
23
-
24
- <?php
25
- FrmAppHelper::multiselect_accessibility();
14
  ?>
15
  <tr>
16
  <td class="frm_left_label">
17
+ <label id="for_<?php echo esc_attr( str_replace( '[]', '', $role_field_name ) ); ?>" for="<?php echo esc_attr( $role_field_name ); ?>"><?php echo esc_html( $frm_role_description ); ?></label>
18
  </td>
19
  <td><?php FrmAppHelper::wp_roles_dropdown( $role_field_name, $frm_settings->$frm_role, 'multiple' ); ?></td>
20
  </tr>
21
  <?php } ?>
22
  </table>
 
 
 
css/frm_admin.css CHANGED
@@ -2883,51 +2883,29 @@ a.frm_option_icon:hover::before {
2883
  font-family: "s11-fp" !important;
2884
  }
2885
 
2886
- .multiselect-container.frm-dropdown-menu input[type=radio] {
2887
- display: none;
2888
- }
2889
-
2890
  .multiselect-container {
2891
  position: absolute;
2892
  list-style-type: none;
2893
  margin: 0;
2894
- padding: 0
2895
- }
2896
-
2897
- .multiselect-container .input-group {
2898
- margin: 5px
2899
- }
2900
-
2901
- .multiselect-container > li {
2902
  padding: 0;
2903
- margin: 0;
2904
- }
2905
-
2906
- .multiselect-container > li > a.multiselect-all label {
2907
- font-weight: 700
2908
- }
2909
-
2910
- .multiselect-container > li > label.multiselect-group {
2911
- margin: 0;
2912
- padding: 3px 20px;
2913
- height: 100%;
2914
- font-weight: 700
2915
  }
2916
 
2917
- .frm-dropdown-menu.multiselect-container > li > a {
2918
- padding: 0
 
 
 
 
 
2919
  }
2920
 
2921
- .multiselect-container > li > a > label {
2922
- margin: 0;
2923
- padding: 3px 25px;
2924
- height: 100%;
2925
- cursor: pointer;
2926
- font-weight: 400;
2927
- display: block;
2928
  }
2929
 
2930
- .accordion-container .multiselect-container > li > a > label {
2931
  padding: 3px 19px 3px 7px;
2932
  }
2933
 
@@ -2936,17 +2914,6 @@ a.frm_option_icon:hover::before {
2936
  border-bottom-left-radius: 4px
2937
  }
2938
 
2939
- .form-inline .multiselect-container label.checkbox,
2940
- .form-inline .multiselect-container label.radio {
2941
- padding: 3px 20px;
2942
- }
2943
-
2944
- .form-inline .multiselect-container li a label.checkbox input[type=checkbox],
2945
- .form-inline .multiselect-container li a label.radio input[type=radio] {
2946
- margin-left: -20px;
2947
- margin-right: 0;
2948
- }
2949
-
2950
  .frm-btn-group.btn-group, .frm-btn-group.btn-group-vertical {
2951
  display: block;
2952
  vertical-align: middle;
2883
  font-family: "s11-fp" !important;
2884
  }
2885
 
 
 
 
 
2886
  .multiselect-container {
2887
  position: absolute;
2888
  list-style-type: none;
2889
  margin: 0;
 
 
 
 
 
 
 
 
2890
  padding: 0;
2891
+ width: 100%;
2892
+ max-width: 250px;
 
 
 
 
 
 
 
 
 
 
2893
  }
2894
 
2895
+ .multiselect-container button.multiselect-option {
2896
+ display: block;
2897
+ width: 100%;
2898
+ text-align: left;
2899
+ background: none;
2900
+ border: none;
2901
+ padding: 5px 0 5px 15px;
2902
  }
2903
 
2904
+ .multiselect-container button.multiselect-option label {
2905
+ margin-left: 5px;
 
 
 
 
 
2906
  }
2907
 
2908
+ .accordion-container .multiselect-container label {
2909
  padding: 3px 19px 3px 7px;
2910
  }
2911
 
2914
  border-bottom-left-radius: 4px
2915
  }
2916
 
 
 
 
 
 
 
 
 
 
 
 
2917
  .frm-btn-group.btn-group, .frm-btn-group.btn-group-vertical {
2918
  display: block;
2919
  vertical-align: middle;
formidable.php CHANGED
@@ -2,7 +2,7 @@
2
  /*
3
  Plugin Name: Formidable Forms
4
  Description: Quickly and easily create drag-and-drop forms
5
- Version: 5.0.17
6
  Plugin URI: https://formidableforms.com/
7
  Author URI: https://formidableforms.com/
8
  Author: Strategy11
2
  /*
3
  Plugin Name: Formidable Forms
4
  Description: Quickly and easily create drag-and-drop forms
5
+ Version: 5.1
6
  Plugin URI: https://formidableforms.com/
7
  Author URI: https://formidableforms.com/
8
  Author: Strategy11
js/bootstrap-multiselect.js CHANGED
@@ -2,7 +2,7 @@
2
  * Bootstrap Multiselect (http://davidstutz.de/bootstrap-multiselect/)
3
  *
4
  * Apache License, Version 2.0:
5
- * Copyright (c) 2012 - 2018 David Stutz
6
  *
7
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
  * use this file except in compliance with the License. You may obtain a
@@ -15,7 +15,7 @@
15
  * under the License.
16
  *
17
  * BSD 3-Clause License:
18
- * Copyright (c) 2012 - 2018 David Stutz
19
  * All rights reserved.
20
  *
21
  * Redistribution and use in source and binary forms, with or without
@@ -41,14 +41,25 @@
41
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43
  */
44
- !function ($) {
 
 
 
 
 
 
 
 
 
 
 
45
  "use strict";// jshint ;_;
46
 
47
  if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
48
  ko.bindingHandlers.multiselect = {
49
  after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
50
 
51
- init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
52
  var $element = $(element);
53
  var config = ko.toJS(valueAccessor());
54
 
@@ -58,9 +69,9 @@
58
  var options = allBindings.get('options');
59
  if (ko.isObservable(options)) {
60
  ko.computed({
61
- read: function() {
62
  options();
63
- setTimeout(function() {
64
  var ms = $element.data('multiselect');
65
  if (ms)
66
  ms.updateOriginalOptions();//Not sure how beneficial this is.
@@ -79,9 +90,9 @@
79
  var value = allBindings.get('value');
80
  if (ko.isObservable(value)) {
81
  ko.computed({
82
- read: function() {
83
  value();
84
- setTimeout(function() {
85
  $element.multiselect('refresh');
86
  }, 1);
87
  },
@@ -96,9 +107,9 @@
96
  var selectedOptions = allBindings.get('selectedOptions');
97
  if (ko.isObservable(selectedOptions)) {
98
  ko.computed({
99
- read: function() {
100
  selectedOptions();
101
- setTimeout(function() {
102
  $element.multiselect('refresh');
103
  }, 1);
104
  },
@@ -144,12 +155,12 @@
144
  }
145
  }
146
 
147
- ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
148
  $element.multiselect('destroy');
149
  });
150
  },
151
 
152
- update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
153
  var $element = $(element);
154
  var config = ko.toJS(valueAccessor());
155
 
@@ -208,6 +219,7 @@
208
  this.buildSelectAll();
209
  this.buildDropdownOptions();
210
  this.buildFilter();
 
211
 
212
  this.updateButtonText();
213
  this.updateSelectAll(true);
@@ -217,11 +229,17 @@
217
  }
218
 
219
  this.options.wasDisabled = this.$select.prop('disabled');
220
- if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
221
- this.disable();
222
  }
223
 
224
  this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container);
 
 
 
 
 
 
225
  this.options.onInitialized(this.$select, this.$container);
226
  }
227
 
@@ -237,35 +255,33 @@
237
  * @param {jQuery} select
238
  * @returns {String}
239
  */
240
- buttonText: function(options, select) {
241
- if (this.disabledText.length > 0
242
- && (select.prop('disabled') || (options.length == 0 && this.disableIfEmpty))) {
243
-
244
  return this.disabledText;
245
  }
246
- else if (options.length === 0) {
247
  return this.nonSelectedText;
248
  }
249
  else if (this.allSelectedText
250
- && options.length === $('option', $(select)).length
251
- && $('option', $(select)).length !== 1
252
- && this.multiple) {
253
 
254
  if (this.selectAllNumber) {
255
- return this.allSelectedText + ' (' + options.length + ')';
256
  }
257
  else {
258
  return this.allSelectedText;
259
  }
260
  }
261
- else if (this.numberDisplayed != 0 && options.length > this.numberDisplayed) {
262
- return options.length + ' ' + this.nSelectedText;
263
  }
264
  else {
265
  var selected = '';
266
  var delimiter = this.delimiterText;
267
 
268
- options.each(function() {
269
  var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
270
  selected += label + delimiter;
271
  });
@@ -280,7 +296,7 @@
280
  * @param {jQuery} select
281
  * @returns {@exp;selected@call;substr}
282
  */
283
- buttonTitle: function(options, select) {
284
  if (options.length === 0) {
285
  return this.nonSelectedText;
286
  }
@@ -295,7 +311,7 @@
295
  return selected.substr(0, selected.length - this.delimiterText.length);
296
  }
297
  },
298
- checkboxName: function(option) {
299
  return false; // no checkbox name
300
  },
301
  /**
@@ -304,7 +320,7 @@
304
  * @param {jQuery} element
305
  * @returns {String}
306
  */
307
- optionLabel: function(element){
308
  return $(element).attr('label') || $(element).text();
309
  },
310
  /**
@@ -313,7 +329,7 @@
313
  * @param {jQuery} element
314
  * @returns {String}
315
  */
316
- optionClass: function(element) {
317
  return $(element).attr('class') || '';
318
  },
319
  /**
@@ -324,7 +340,7 @@
324
  * @param {jQuery} option
325
  * @param {Boolean} checked
326
  */
327
- onChange : function(option, checked) {
328
 
329
  },
330
  /**
@@ -332,7 +348,7 @@
332
  *
333
  * @param {jQuery} event
334
  */
335
- onDropdownShow: function(event) {
336
 
337
  },
338
  /**
@@ -340,7 +356,7 @@
340
  *
341
  * @param {jQuery} event
342
  */
343
- onDropdownHide: function(event) {
344
 
345
  },
346
  /**
@@ -348,7 +364,7 @@
348
  *
349
  * @param {jQuery} event
350
  */
351
- onDropdownShown: function(event) {
352
 
353
  },
354
  /**
@@ -356,19 +372,19 @@
356
  *
357
  * @param {jQuery} event
358
  */
359
- onDropdownHidden: function(event) {
360
 
361
  },
362
  /**
363
  * Triggered on select all.
364
  */
365
- onSelectAll: function() {
366
 
367
  },
368
  /**
369
  * Triggered on deselect all.
370
  */
371
- onDeselectAll: function() {
372
 
373
  },
374
  /**
@@ -377,7 +393,7 @@
377
  * @param {jQuery} $select
378
  * @param {jQuery} $container
379
  */
380
- onInitialized: function($select, $container) {
381
 
382
  },
383
  /**
@@ -385,11 +401,11 @@
385
  *
386
  * @param {jQuery} $filter
387
  */
388
- onFiltering: function($filter) {
389
 
390
  },
391
  enableHTML: false,
392
- buttonClass: 'btn btn-default',
393
  inheritClass: false,
394
  buttonWidth: 'auto',
395
  buttonContainer: '<div class="btn-group" />',
@@ -420,6 +436,7 @@
420
  nonSelectedText: 'None selected',
421
  nSelectedText: 'selected',
422
  allSelectedText: 'All selected',
 
423
  numberDisplayed: 3,
424
  disableIfEmpty: false,
425
  disabledText: '',
@@ -427,15 +444,21 @@
427
  includeResetOption: false,
428
  includeResetDivider: false,
429
  resetText: 'Reset',
 
 
 
 
 
430
  templates: {
431
- button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
432
- ul: '<ul class="multiselect-container dropdown-menu"></ul>',
433
- filter: '<li class="multiselect-item multiselect-filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text" /></div></li>',
434
- filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button"><i class="glyphicon glyphicon-remove-circle"></i></button></span>',
435
- li: '<li><a tabindex="0"><label></label></a></li>',
436
- divider: '<li class="multiselect-item divider"></li>',
437
- liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>',
438
- resetButton: '<li class="multiselect-reset text-center"><div class="input-group"><a class="btn btn-default btn-block"></a></div></li>'
 
439
  }
440
  },
441
 
@@ -444,9 +467,18 @@
444
  /**
445
  * Builds the container of the multiselect.
446
  */
447
- buildContainer: function() {
448
  this.$container = $(this.options.buttonContainer);
449
- this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
 
 
 
 
 
 
 
 
 
450
  this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
451
  this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
452
  this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
@@ -455,7 +487,7 @@
455
  /**
456
  * Builds the button of the multiselect.
457
  */
458
- buildButton: function() {
459
  this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
460
  if (this.$select.attr('class') && this.options.inheritClass) {
461
  this.$button.addClass(this.$select.attr('class'));
@@ -471,15 +503,27 @@
471
  // Manually add button width if set.
472
  if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
473
  this.$button.css({
474
- 'width' : '100%', //this.options.buttonWidth,
475
- 'overflow' : 'hidden',
476
- 'text-overflow' : 'ellipsis'
477
  });
478
  this.$container.css({
479
  'width': this.options.buttonWidth
480
  });
481
  }
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  // Keep the tab index from the select.
484
  var tabindex = this.$select.attr('tabindex');
485
  if (tabindex) {
@@ -490,41 +534,59 @@
490
  },
491
 
492
  /**
493
- * Builds the ul representing the dropdown menu.
494
  */
495
- buildDropdown: function() {
496
 
497
- // Build ul.
498
- this.$ul = $(this.options.templates.ul);
499
 
500
  if (this.options.dropRight) {
501
- this.$ul.addClass('pull-right');
 
 
 
502
  }
503
 
504
  // Set max height of dropdown menu to activate auto scrollbar.
505
  if (this.options.maxHeight) {
506
  // TODO: Add a class for this option to move the css declarations.
507
- this.$ul.css({
508
  'max-height': this.options.maxHeight + 'px',
509
  'overflow-y': 'auto',
510
  'overflow-x': 'hidden'
511
  });
512
  }
513
 
514
- if (this.options.dropUp) {
 
 
515
 
516
- var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0));
517
- var moveCalc = height + 34;
 
518
 
519
- this.$ul.css({
520
- 'max-height': height + 'px',
521
- 'overflow-y': 'auto',
522
- 'overflow-x': 'hidden',
523
- 'margin-top': "-" + moveCalc + 'px'
524
- });
525
  }
526
 
527
- this.$container.append(this.$ul);
 
 
 
 
 
 
 
 
 
 
 
 
528
  },
529
 
530
  /**
@@ -532,9 +594,9 @@
532
  *
533
  * Uses createDivider and createOptionValue to create the necessary options.
534
  */
535
- buildDropdownOptions: function() {
536
 
537
- this.$select.children().each($.proxy(function(index, element) {
538
 
539
  var $element = $(element);
540
  // Support optgroups and options without a group simultaneously.
@@ -554,7 +616,7 @@
554
  this.createDivider();
555
  }
556
  else {
557
- this.createOptionValue(element);
558
  }
559
 
560
  }
@@ -563,8 +625,8 @@
563
  }, this));
564
 
565
  // Bind the change event on the dropdown elements.
566
- $(this.$ul).off('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]');
567
- $(this.$ul).on('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]', $.proxy(function(event) {
568
  var $target = $(event.target);
569
 
570
  var checked = $target.prop('checked') || false;
@@ -573,11 +635,11 @@
573
  // Apply or unapply the configured selected class.
574
  if (this.options.selectedClass) {
575
  if (checked) {
576
- $target.closest('li')
577
  .addClass(this.options.selectedClass);
578
  }
579
  else {
580
- $target.closest('li')
581
  .removeClass(this.options.selectedClass);
582
  }
583
  }
@@ -609,7 +671,7 @@
609
  else {
610
  // Unselect all other options and corresponding checkboxes.
611
  if (this.options.selectedClass) {
612
- $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
613
  }
614
 
615
  $($checkboxesNotThis).prop('checked', false);
@@ -620,7 +682,7 @@
620
  }
621
 
622
  if (this.options.selectedClass === "active") {
623
- $optionsNotThis.closest("a").css("outline", "");
624
  }
625
  }
626
  else {
@@ -642,34 +704,36 @@
642
  this.$select.change();
643
  this.updateButtonText();
644
 
645
- if(this.options.preventInputChangeEvent) {
646
  return false;
647
  }
648
  }, this));
649
 
650
- $('li a', this.$ul).on('mousedown', function(e) {
 
651
  if (e.shiftKey) {
652
  // Prevent selecting text by Shift+click
653
  return false;
654
  }
655
  });
656
 
657
- $(this.$ul).on('touchstart click', 'li a', $.proxy(function(event) {
 
658
  event.stopPropagation();
659
 
660
  var $target = $(event.target);
661
 
662
  if (event.shiftKey && this.options.multiple) {
663
- if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
664
  event.preventDefault();
665
- $target = $target.find("input");
666
  $target.prop("checked", !$target.prop("checked"));
667
  }
668
  var checked = $target.prop('checked') || false;
669
 
670
  if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
671
- var from = this.$ul.find("li:visible").index($target.parents("li"));
672
- var to = this.$ul.find("li:visible").index(this.lastToggledInput.parents("li"));
673
 
674
  if (from > to) { // Swap the indices
675
  var tmp = to;
@@ -681,12 +745,12 @@
681
  ++to;
682
 
683
  // Change the checkboxes and underlying options
684
- var range = this.$ul.find("li").not(".multiselect-filter-hidden").slice(from, to).find("input");
685
 
686
  range.prop('checked', checked);
687
 
688
  if (this.options.selectedClass) {
689
- range.closest('li')
690
  .toggleClass(this.options.selectedClass, checked);
691
  }
692
 
@@ -702,100 +766,124 @@
702
  // Trigger the select "change" event
703
  $target.trigger("change");
704
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
 
706
  // Remembers last clicked option
707
- if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
 
708
  this.lastToggledInput = $target;
709
  }
 
 
 
710
 
711
- $target.trigger( 'blur' );
712
  }, this));
713
 
714
- // Keyboard support.
715
- this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
716
- if ($('input[type="text"]', this.$container).is(':focus')) {
717
- return;
718
- }
719
 
720
- if (event.keyCode === 9 && this.$container.hasClass('open')) {
 
721
  this.$button.click();
722
  }
723
- else {
724
- var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
725
-
726
- if (!$items.length) {
727
- return;
728
- }
729
-
730
- var index = $items.index($items.filter(':focus'));
731
-
732
- // Navigation up.
733
- if (event.keyCode === 38 && index > 0) {
734
- index--;
 
735
  }
736
- // Navigate down.
737
- else if (event.keyCode === 40 && index < $items.length - 1) {
738
- index++;
 
 
 
 
 
 
739
  }
740
- else if (!~index) {
741
- index = 0;
 
 
742
  }
743
-
744
- var $current = $items.eq(index);
745
- $current.trigger( 'focus' );
746
-
747
- if (event.keyCode === 32 || event.keyCode === 13) {
748
- var $checkbox = $current.find('input');
749
-
750
- $checkbox.prop("checked", !$checkbox.prop("checked"));
751
- $checkbox.change();
752
- }
753
-
754
- event.stopPropagation();
755
- event.preventDefault();
756
  }
757
  }, this));
758
 
759
  if (this.options.enableClickableOptGroups && this.options.multiple) {
760
- $("li.multiselect-group input", this.$ul).on("change", $.proxy(function(event) {
 
761
  event.stopPropagation();
762
 
763
  var $target = $(event.target);
764
  var checked = $target.prop('checked') || false;
765
 
766
- var $li = $(event.target).closest('li');
767
- var $group = $li.nextUntil("li.multiselect-group")
768
  .not('.multiselect-filter-hidden')
769
  .not('.disabled');
770
 
771
  var $inputs = $group.find("input");
772
 
773
- var values = [];
774
  var $options = [];
775
 
776
  if (this.options.selectedClass) {
777
  if (checked) {
778
- $li.addClass(this.options.selectedClass);
779
  }
780
  else {
781
- $li.removeClass(this.options.selectedClass);
782
  }
783
  }
784
 
785
- $.each($inputs, $.proxy(function(index, input) {
786
- var value = $(input).val();
 
787
  var $option = this.getOptionByValue(value);
788
 
789
  if (checked) {
790
- $(input).prop('checked', true);
791
- $(input).closest('li')
792
  .addClass(this.options.selectedClass);
793
 
794
  $option.prop('selected', true);
795
  }
796
  else {
797
- $(input).prop('checked', false);
798
- $(input).closest('li')
799
  .removeClass(this.options.selectedClass);
800
 
801
  $option.prop('selected', false);
@@ -815,13 +903,14 @@
815
  }
816
 
817
  if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
818
- $("li.multiselect-group .caret-container", this.$ul).on("click", $.proxy(function(event) {
819
- var $li = $(event.target).closest('li');
820
- var $inputs = $li.nextUntil("li.multiselect-group")
821
- .not('.multiselect-filter-hidden');
 
822
 
823
  var visible = true;
824
- $inputs.each(function() {
825
  visible = visible && !$(this).hasClass('multiselect-collapsible-hidden');
826
  });
827
 
@@ -834,11 +923,45 @@
834
  .removeClass('multiselect-collapsible-hidden');
835
  }
836
  }, this));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
 
838
- $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
839
- $("li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
840
- $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
 
 
 
841
  }
 
 
 
 
 
842
  },
843
 
844
  /**
@@ -846,7 +969,7 @@
846
  *
847
  * @param {jQuery} element
848
  */
849
- createOptionValue: function(element) {
850
  var $element = $(element);
851
  if ($element.is(':selected')) {
852
  $element.prop('selected', true);
@@ -857,61 +980,46 @@
857
  var classes = this.options.optionClass(element);
858
  var value = $element.val();
859
  var inputType = this.options.multiple ? "checkbox" : "radio";
 
860
 
861
- var $li = $(this.options.templates.li);
862
- var $label = $('label', $li);
863
- $label.addClass(inputType);
864
- $label.attr("title", label);
865
- $li.addClass(classes);
866
 
867
- // Hide all children items when collapseOptGroupsByDefault is true
868
- if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
869
- $li.addClass("multiselect-collapsible-hidden");
870
- $li.hide();
871
  }
872
 
873
- if (this.options.enableHTML) {
874
- $label.html(" " + label);
875
- }
876
- else {
877
- $label.text(" " + label);
878
  }
879
 
880
- var $checkbox = $('<input/>').attr('type', inputType);
881
-
882
  var name = this.options.checkboxName($element);
883
- if (name) {
884
- $checkbox.attr('name', name);
885
- }
886
-
887
- $label.prepend($checkbox);
888
 
889
  var selected = $element.prop('selected') || false;
890
- $checkbox.val(value);
891
 
892
  if (value === this.options.selectAllValue) {
893
- $li.addClass("multiselect-item multiselect-all");
 
894
  $checkbox.parent().parent()
895
  .addClass('multiselect-all');
896
  }
897
 
898
- $label.attr('title', $element.attr('title'));
899
-
900
- this.$ul.append($li);
901
 
902
  if ($element.is(':disabled')) {
903
  $checkbox.attr('disabled', 'disabled')
904
  .prop('disabled', true)
905
- .closest('a')
906
- .attr("tabindex", "-1")
907
- .closest('li')
908
  .addClass('disabled');
909
  }
910
 
911
  $checkbox.prop('checked', selected);
912
 
913
  if (selected && this.options.selectedClass) {
914
- $checkbox.closest('li')
915
  .addClass(this.options.selectedClass);
916
  }
917
  },
@@ -921,9 +1029,9 @@
921
  *
922
  * @param {jQuery} element
923
  */
924
- createDivider: function(element) {
925
  var $divider = $(this.options.templates.divider);
926
- this.$ul.append($divider);
927
  },
928
 
929
  /**
@@ -931,66 +1039,74 @@
931
  *
932
  * @param {jQuery} group
933
  */
934
- createOptgroup: function(group) {
935
- var label = $(group).attr("label");
936
- var value = $(group).attr("value");
937
- var $li = $('<li class="multiselect-item multiselect-group"><a href="javascript:void(0);"><label><b></b></label></a></li>');
 
938
 
939
- var classes = this.options.optionClass(group);
940
- $li.addClass(classes);
941
 
942
- if (this.options.enableHTML) {
943
- $('label b', $li).html(" " + label);
 
944
  }
945
  else {
946
- $('label b', $li).text(" " + label);
 
 
 
 
 
947
  }
948
 
949
- if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
950
- $('a', $li).append('<span class="caret-container"><b class="caret"></b></span>');
951
- }
952
 
953
- if (this.options.enableClickableOptGroups && this.options.multiple) {
954
- $('a label', $li).prepend('<input type="checkbox" value="' + value + '"/>');
 
955
  }
956
 
957
- if ($(group).is(':disabled')) {
958
- $li.addClass('disabled');
959
  }
960
 
961
- this.$ul.append($li);
962
 
963
- $("option", group).each($.proxy(function($, group) {
964
- this.createOptionValue(group);
965
- }, this))
966
  },
967
 
968
  /**
969
  * Build the reset.
970
  *
971
  */
972
- buildReset: function() {
973
  if (this.options.includeResetOption) {
974
 
975
  // Check whether to add a divider after the reset.
976
  if (this.options.includeResetDivider) {
977
- this.$ul.prepend($(this.options.templates.divider));
 
 
978
  }
979
 
980
  var $resetButton = $(this.options.templates.resetButton);
981
 
982
  if (this.options.enableHTML) {
983
- $('a', $resetButton).html(this.options.resetText);
984
  }
985
  else {
986
- $('a', $resetButton).text(this.options.resetText);
987
  }
988
 
989
- $('a', $resetButton).click($.proxy(function(){
990
  this.clearSelection();
991
  }, this));
992
 
993
- this.$ul.prepend($resetButton);
994
  }
995
  },
996
 
@@ -999,7 +1115,7 @@
999
  *
1000
  * Checks if a select all has already been created.
1001
  */
1002
- buildSelectAll: function() {
1003
  if (typeof this.options.selectAllValue === 'number') {
1004
  this.options.selectAllValue = this.options.selectAllValue.toString();
1005
  }
@@ -1007,38 +1123,21 @@
1007
  var alreadyHasSelectAll = this.hasSelectAll();
1008
 
1009
  if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
1010
- && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
1011
 
1012
  // Check whether to add a divider after the select all.
1013
  if (this.options.includeSelectAllDivider) {
1014
- this.$ul.prepend($(this.options.templates.divider));
1015
  }
1016
 
1017
- var $li = $(this.options.templates.li);
1018
- $('label', $li).addClass("checkbox");
1019
-
1020
- if (this.options.enableHTML) {
1021
- $('label', $li).html(" " + this.options.selectAllText);
1022
- }
1023
- else {
1024
- $('label', $li).text(" " + this.options.selectAllText);
1025
- }
1026
 
1027
- if (this.options.selectAllName) {
1028
- $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
1029
- }
1030
- else {
1031
- $('label', $li).prepend('<input type="checkbox" />');
1032
- }
1033
 
1034
- var $checkbox = $('input', $li);
1035
- $checkbox.val(this.options.selectAllValue);
1036
-
1037
- $li.addClass("multiselect-item multiselect-all");
1038
- $checkbox.parent().parent()
1039
- .addClass('multiselect-all');
1040
-
1041
- this.$ul.prepend($li);
1042
 
1043
  $checkbox.prop('checked', false);
1044
  }
@@ -1047,7 +1146,7 @@
1047
  /**
1048
  * Builds the filter.
1049
  */
1050
- buildFilter: function() {
1051
 
1052
  // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1053
  if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
@@ -1058,15 +1157,25 @@
1058
  this.$filter = $(this.options.templates.filter);
1059
  $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1060
 
1061
- // Adds optional filter clear button
1062
- if(this.options.includeFilterClearBtn) {
1063
- var clearBtn = $(this.options.templates.filterClearBtn);
1064
- clearBtn.on('click', $.proxy(function(event){
 
 
 
 
 
 
 
 
 
 
1065
  clearTimeout(this.searchTimeout);
1066
 
1067
  this.query = '';
1068
  this.$filter.find('.multiselect-search').val('');
1069
- $('li', this.$ul).show().removeClass('multiselect-filter-hidden');
1070
 
1071
  this.updateSelectAll();
1072
 
@@ -1075,31 +1184,39 @@
1075
  }
1076
 
1077
  }, this));
1078
- this.$filter.find('.input-group').append(clearBtn);
1079
  }
1080
 
1081
- this.$ul.prepend(this.$filter);
1082
 
1083
- this.$filter.val(this.query).on('click', function(event) {
1084
  event.stopPropagation();
1085
- }).on('input keydown', $.proxy(function(event) {
1086
  // Cancel enter key default behaviour
1087
  if (event.which === 13) {
1088
- event.preventDefault();
1089
- }
 
 
 
 
 
 
 
 
 
1090
 
1091
  // This is useful to catch "keydown" events after the browser has updated the control.
1092
  clearTimeout(this.searchTimeout);
1093
 
1094
- this.searchTimeout = this.asyncFunction($.proxy(function() {
1095
 
1096
  if (this.query !== event.target.value) {
1097
  this.query = event.target.value;
1098
 
1099
  var currentGroup, currentGroupVisible;
1100
- $.each($('li', this.$ul), $.proxy(function(index, element) {
1101
  var value = $('input', element).length > 0 ? $('input', element).val() : "";
1102
- var text = $('label', element).text();
1103
 
1104
  var filterCandidate = '';
1105
  if ((this.options.filterBehavior === 'text')) {
@@ -1134,13 +1251,13 @@
1134
  }
1135
 
1136
  // Toggle current element (group or group item) according to showElement boolean.
1137
- if(!showElement){
1138
- $(element).css('display', 'none');
1139
- $(element).addClass('multiselect-filter-hidden');
1140
  }
1141
- if(showElement){
1142
- $(element).css('display', 'block');
1143
- $(element).removeClass('multiselect-filter-hidden');
1144
  }
1145
 
1146
  // Differentiate groups and group items.
@@ -1172,6 +1289,8 @@
1172
  this.updateOptGroups();
1173
  }
1174
 
 
 
1175
  this.options.onFiltering(event.target);
1176
 
1177
  }, this), 300, this);
@@ -1180,11 +1299,67 @@
1180
  }
1181
  },
1182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1183
  /**
1184
  * Unbinds the whole plugin.
1185
  */
1186
- destroy: function() {
1187
  this.$container.remove();
 
1188
  this.$select.show();
1189
 
1190
  // reset original state
@@ -1198,8 +1373,8 @@
1198
  */
1199
  refresh: function () {
1200
  var inputs = {};
1201
- $('li input', this.$ul).each(function() {
1202
- inputs[$(this).val()] = $(this);
1203
  });
1204
 
1205
  $('option', this.$select).each($.proxy(function (index, element) {
@@ -1210,7 +1385,7 @@
1210
  $input.prop('checked', true);
1211
 
1212
  if (this.options.selectedClass) {
1213
- $input.closest('li')
1214
  .addClass(this.options.selectedClass);
1215
  }
1216
  }
@@ -1218,7 +1393,7 @@
1218
  $input.prop('checked', false);
1219
 
1220
  if (this.options.selectedClass) {
1221
- $input.closest('li')
1222
  .removeClass(this.options.selectedClass);
1223
  }
1224
  }
@@ -1226,12 +1401,12 @@
1226
  if ($elem.is(":disabled")) {
1227
  $input.attr('disabled', 'disabled')
1228
  .prop('disabled', true)
1229
- .closest('li')
1230
  .addClass('disabled');
1231
  }
1232
  else {
1233
  $input.prop('disabled', false)
1234
- .closest('li')
1235
  .removeClass('disabled');
1236
  }
1237
  }, this));
@@ -1253,8 +1428,8 @@
1253
  * @param {Array} selectValues
1254
  * @param {Boolean} triggerOnChange
1255
  */
1256
- select: function(selectValues, triggerOnChange) {
1257
- if(!$.isArray(selectValues)) {
1258
  selectValues = [selectValues];
1259
  }
1260
 
@@ -1268,22 +1443,27 @@
1268
  var $option = this.getOptionByValue(value);
1269
  var $checkbox = this.getInputByValue(value);
1270
 
1271
- if($option === undefined || $checkbox === undefined) {
1272
  continue;
1273
  }
1274
 
1275
- if (!this.options.multiple) {
1276
- this.deselectAll(false);
1277
- }
1278
-
1279
  if (this.options.selectedClass) {
1280
- $checkbox.closest('li')
1281
  .addClass(this.options.selectedClass);
1282
  }
1283
 
1284
  $checkbox.prop('checked', true);
1285
  $option.prop('selected', true);
1286
 
 
 
 
 
 
 
 
 
 
1287
  if (triggerOnChange) {
1288
  this.options.onChange($option, true);
1289
  }
@@ -1319,8 +1499,13 @@
1319
  * @param {Array} deselectValues
1320
  * @param {Boolean} triggerOnChange
1321
  */
1322
- deselect: function(deselectValues, triggerOnChange) {
1323
- if(!$.isArray(deselectValues)) {
 
 
 
 
 
1324
  deselectValues = [deselectValues];
1325
  }
1326
 
@@ -1334,12 +1519,12 @@
1334
  var $option = this.getOptionByValue(value);
1335
  var $checkbox = this.getInputByValue(value);
1336
 
1337
- if($option === undefined || $checkbox === undefined) {
1338
  continue;
1339
  }
1340
 
1341
  if (this.options.selectedClass) {
1342
- $checkbox.closest('li')
1343
  .removeClass(this.options.selectedClass);
1344
  }
1345
 
@@ -1368,40 +1553,55 @@
1368
  * @param {Boolean} triggerOnSelectAll
1369
  */
1370
  selectAll: function (justVisible, triggerOnSelectAll) {
 
 
 
 
1371
 
 
 
1372
  var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1373
- var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1374
- var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1375
 
1376
- if(justVisible) {
1377
- $('input:enabled' , visibleLis).prop('checked', true);
1378
- visibleLis.addClass(this.options.selectedClass);
 
1379
 
1380
- $('input:enabled' , visibleLis).each($.proxy(function(index, element) {
1381
  var value = $(element).val();
1382
  var option = this.getOptionByValue(value);
 
 
 
1383
  $(option).prop('selected', true);
1384
  }, this));
1385
  }
1386
  else {
1387
- $('input:enabled' , allLis).prop('checked', true);
1388
- allLis.addClass(this.options.selectedClass);
 
1389
 
1390
- $('input:enabled' , allLis).each($.proxy(function(index, element) {
1391
  var value = $(element).val();
1392
  var option = this.getOptionByValue(value);
 
 
 
1393
  $(option).prop('selected', true);
1394
  }, this));
1395
  }
1396
 
1397
- $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', true);
1398
 
1399
  if (this.options.enableClickableOptGroups && this.options.multiple) {
1400
  this.updateOptGroups();
1401
  }
1402
 
 
 
 
1403
  if (triggerOnSelectAll) {
1404
- this.options.onSelectAll();
1405
  }
1406
  },
1407
 
@@ -1413,40 +1613,55 @@
1413
  * @param {Boolean} justVisible
1414
  */
1415
  deselectAll: function (justVisible, triggerOnDeselectAll) {
 
 
 
 
1416
 
 
 
1417
  var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1418
- var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1419
- var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1420
 
1421
- if(justVisible) {
1422
- $('input[type="checkbox"]:enabled' , visibleLis).prop('checked', false);
1423
- visibleLis.removeClass(this.options.selectedClass);
 
1424
 
1425
- $('input[type="checkbox"]:enabled' , visibleLis).each($.proxy(function(index, element) {
1426
  var value = $(element).val();
1427
  var option = this.getOptionByValue(value);
 
 
 
1428
  $(option).prop('selected', false);
1429
  }, this));
1430
  }
1431
  else {
1432
- $('input[type="checkbox"]:enabled' , allLis).prop('checked', false);
1433
- allLis.removeClass(this.options.selectedClass);
 
1434
 
1435
- $('input[type="checkbox"]:enabled' , allLis).each($.proxy(function(index, element) {
1436
  var value = $(element).val();
1437
  var option = this.getOptionByValue(value);
 
 
 
1438
  $(option).prop('selected', false);
1439
  }, this));
1440
  }
1441
 
1442
- $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', false);
1443
 
1444
  if (this.options.enableClickableOptGroups && this.options.multiple) {
1445
  this.updateOptGroups();
1446
  }
1447
 
 
 
 
1448
  if (triggerOnDeselectAll) {
1449
- this.options.onDeselectAll();
1450
  }
1451
  },
1452
 
@@ -1455,8 +1670,8 @@
1455
  *
1456
  * Rebuilds the dropdown, the filter and the select all option.
1457
  */
1458
- rebuild: function() {
1459
- this.$ul.html('');
1460
 
1461
  // Important to distinguish between radios and checkboxes.
1462
  this.options.multiple = this.$select.attr('multiple') === "multiple";
@@ -1464,6 +1679,7 @@
1464
  this.buildSelectAll();
1465
  this.buildDropdownOptions();
1466
  this.buildFilter();
 
1467
 
1468
  this.updateButtonText();
1469
  this.updateSelectAll(true);
@@ -1472,22 +1688,33 @@
1472
  this.updateOptGroups();
1473
  }
1474
 
1475
- if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1476
- this.disable();
1477
- }
1478
- else {
1479
- this.enable();
 
 
 
 
1480
  }
1481
 
1482
  if (this.options.dropRight) {
1483
- this.$ul.addClass('pull-right');
 
 
 
 
 
 
 
1484
  }
1485
  },
1486
 
1487
  /**
1488
  * The provided data will be used to build the dropdown.
1489
  */
1490
- dataprovider: function(dataprovider) {
1491
 
1492
  var groupCounter = 0;
1493
  var $select = this.$select.empty();
@@ -1504,7 +1731,7 @@
1504
  value: option.value
1505
  });
1506
 
1507
- forEach(option.children, function(subOption) { // add children option tags
1508
  var attributes = {
1509
  value: subOption.value,
1510
  label: subOption.label || subOption.value,
@@ -1514,10 +1741,10 @@
1514
  };
1515
 
1516
  //Loop through attributes object and add key-value for each attribute
1517
- for (var key in subOption.attributes) {
1518
  attributes['data-' + key] = subOption.attributes[key];
1519
- }
1520
- //Append original attributes + new data attributes to option
1521
  $tag.append($('<option/>').attr(attributes));
1522
  });
1523
  }
@@ -1533,7 +1760,7 @@
1533
  };
1534
  //Loop through attributes object and add key-value for each attribute
1535
  for (var key in option.attributes) {
1536
- attributes['data-' + key] = option.attributes[key];
1537
  }
1538
  //Append original attributes + new data attributes to option
1539
  $tag = $('<option/>').attr(attributes);
@@ -1550,19 +1777,30 @@
1550
  /**
1551
  * Enable the multiselect.
1552
  */
1553
- enable: function() {
1554
  this.$select.prop('disabled', false);
1555
  this.$button.prop('disabled', false)
1556
  .removeClass('disabled');
 
 
1557
  },
1558
 
1559
  /**
1560
  * Disable the multiselect.
1561
  */
1562
- disable: function() {
1563
  this.$select.prop('disabled', true);
1564
  this.$button.prop('disabled', true)
1565
  .addClass('disabled');
 
 
 
 
 
 
 
 
 
1566
  },
1567
 
1568
  /**
@@ -1570,7 +1808,7 @@
1570
  *
1571
  * @param {Array} options
1572
  */
1573
- setOptions: function(options) {
1574
  this.options = this.mergeOptions(options);
1575
  },
1576
 
@@ -1580,7 +1818,7 @@
1580
  * @param {Array} options
1581
  * @returns {Array}
1582
  */
1583
- mergeOptions: function(options) {
1584
  return $.extend(true, {}, this.defaults, this.options, options);
1585
  },
1586
 
@@ -1589,24 +1827,24 @@
1589
  *
1590
  * @returns {Boolean}
1591
  */
1592
- hasSelectAll: function() {
1593
- return $('li.multiselect-all', this.$ul).length > 0;
1594
  },
1595
 
1596
  /**
1597
  * Update opt groups.
1598
  */
1599
- updateOptGroups: function() {
1600
- var $groups = $('li.multiselect-group', this.$ul)
1601
  var selectedClass = this.options.selectedClass;
1602
 
1603
- $groups.each(function() {
1604
- var $options = $(this).nextUntil('li.multiselect-group')
1605
  .not('.multiselect-filter-hidden')
1606
  .not('.disabled');
1607
 
1608
  var checked = true;
1609
- $options.each(function() {
1610
  var $input = $('input', this);
1611
 
1612
  if (!$input.prop('checked')) {
@@ -1630,21 +1868,21 @@
1630
  /**
1631
  * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1632
  */
1633
- updateSelectAll: function(notTriggerOnSelectAll) {
1634
  if (this.hasSelectAll()) {
1635
- var allBoxes = $("li:not(.multiselect-item):not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$ul);
1636
  var allBoxesLength = allBoxes.length;
1637
  var checkedBoxesLength = allBoxes.filter(":checked").length;
1638
- var selectAllLi = $("li.multiselect-all", this.$ul);
1639
- var selectAllInput = selectAllLi.find("input");
1640
 
1641
  if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1642
  selectAllInput.prop("checked", true);
1643
- selectAllLi.addClass(this.options.selectedClass);
1644
  }
1645
  else {
1646
  selectAllInput.prop("checked", false);
1647
- selectAllLi.removeClass(this.options.selectedClass);
1648
  }
1649
  }
1650
  },
@@ -1652,7 +1890,7 @@
1652
  /**
1653
  * Update the button text and its title based on the currently selected options.
1654
  */
1655
- updateButtonText: function() {
1656
  var options = this.getSelected();
1657
 
1658
  // First update the displayed button text.
@@ -1665,6 +1903,7 @@
1665
 
1666
  // Now update the title attribute of the button.
1667
  $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
 
1668
  },
1669
 
1670
  /**
@@ -1672,7 +1911,7 @@
1672
  *
1673
  * @returns {jQUery}
1674
  */
1675
- getSelected: function() {
1676
  return $('option', this.$select).filter(":selected");
1677
  },
1678
 
@@ -1703,7 +1942,7 @@
1703
  */
1704
  getInputByValue: function (value) {
1705
 
1706
- var checkboxes = $('li input:not(.multiselect-search)', this.$ul);
1707
  var valueToCompare = value.toString();
1708
 
1709
  for (var i = 0; i < checkboxes.length; i = i + 1) {
@@ -1717,25 +1956,36 @@
1717
  /**
1718
  * Used for knockout integration.
1719
  */
1720
- updateOriginalOptions: function() {
1721
  this.originalOptions = this.$select.clone()[0].options;
1722
  },
1723
 
1724
- asyncFunction: function(callback, timeout, self) {
1725
  var args = Array.prototype.slice.call(arguments, 3);
1726
- return setTimeout(function() {
1727
  callback.apply(self || window, args);
1728
  }, timeout);
1729
  },
1730
 
1731
- setAllSelectedText: function(allSelectedText) {
1732
  this.options.allSelectedText = allSelectedText;
1733
  this.updateButtonText();
 
 
 
 
 
 
 
 
 
 
 
1734
  }
1735
  };
1736
 
1737
- $.fn.multiselect = function(option, parameter, extraOptions) {
1738
- return this.each(function() {
1739
  var data = $(this).data('multiselect');
1740
  var options = typeof option === 'object' && option;
1741
 
@@ -1758,8 +2008,8 @@
1758
 
1759
  $.fn.multiselect.Constructor = Multiselect;
1760
 
1761
- $(function() {
1762
  $("select[data-role=multiselect]").multiselect();
1763
  });
1764
 
1765
- }(window.jQuery);
2
  * Bootstrap Multiselect (http://davidstutz.de/bootstrap-multiselect/)
3
  *
4
  * Apache License, Version 2.0:
5
+ * Copyright (c) 2012 - 2021 David Stutz
6
  *
7
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
  * use this file except in compliance with the License. You may obtain a
15
  * under the License.
16
  *
17
  * BSD 3-Clause License:
18
+ * Copyright (c) 2012 - 2021 David Stutz
19
  * All rights reserved.
20
  *
21
  * Redistribution and use in source and binary forms, with or without
41
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43
  */
44
+ (function (root, factory) {
45
+ // check to see if 'knockout' AMD module is specified if using requirejs
46
+ if (typeof define === 'function' && define.amd &&
47
+ typeof require === 'function' && typeof require.specified === 'function' && require.specified('knockout')) {
48
+
49
+ // AMD. Register as an anonymous module.
50
+ define(['jquery', 'knockout'], factory);
51
+ } else {
52
+ // Browser globals
53
+ factory(root.jQuery, root.ko);
54
+ }
55
+ })(this, function ($, ko) {
56
  "use strict";// jshint ;_;
57
 
58
  if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
59
  ko.bindingHandlers.multiselect = {
60
  after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
61
 
62
+ init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
63
  var $element = $(element);
64
  var config = ko.toJS(valueAccessor());
65
 
69
  var options = allBindings.get('options');
70
  if (ko.isObservable(options)) {
71
  ko.computed({
72
+ read: function () {
73
  options();
74
+ setTimeout(function () {
75
  var ms = $element.data('multiselect');
76
  if (ms)
77
  ms.updateOriginalOptions();//Not sure how beneficial this is.
90
  var value = allBindings.get('value');
91
  if (ko.isObservable(value)) {
92
  ko.computed({
93
+ read: function () {
94
  value();
95
+ setTimeout(function () {
96
  $element.multiselect('refresh');
97
  }, 1);
98
  },
107
  var selectedOptions = allBindings.get('selectedOptions');
108
  if (ko.isObservable(selectedOptions)) {
109
  ko.computed({
110
+ read: function () {
111
  selectedOptions();
112
+ setTimeout(function () {
113
  $element.multiselect('refresh');
114
  }, 1);
115
  },
155
  }
156
  }
157
 
158
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
159
  $element.multiselect('destroy');
160
  });
161
  },
162
 
163
+ update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
164
  var $element = $(element);
165
  var config = ko.toJS(valueAccessor());
166
 
219
  this.buildSelectAll();
220
  this.buildDropdownOptions();
221
  this.buildFilter();
222
+ this.buildButtons();
223
 
224
  this.updateButtonText();
225
  this.updateSelectAll(true);
229
  }
230
 
231
  this.options.wasDisabled = this.$select.prop('disabled');
232
+ if (this.options.disableIfEmpty && $('option', this.$select).length <= 0 && !this.options.wasDisabled) {
233
+ this.disable(true);
234
  }
235
 
236
  this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container);
237
+ this.$select.prop('tabindex', '-1');
238
+
239
+ if (this.options.widthSynchronizationMode !== 'never') {
240
+ this.synchronizeButtonAndPopupWidth();
241
+ }
242
+
243
  this.options.onInitialized(this.$select, this.$container);
244
  }
245
 
255
  * @param {jQuery} select
256
  * @returns {String}
257
  */
258
+ buttonText: function (selectedOptions, select) {
259
+ if (this.disabledText.length > 0 && select.prop('disabled')) {
 
 
260
  return this.disabledText;
261
  }
262
+ else if (selectedOptions.length === 0) {
263
  return this.nonSelectedText;
264
  }
265
  else if (this.allSelectedText
266
+ && selectedOptions.length === $('option', $(select)).length
267
+ && $('option', $(select)).length !== 1
268
+ && this.multiple) {
269
 
270
  if (this.selectAllNumber) {
271
+ return this.allSelectedText + ' (' + selectedOptions.length + ')';
272
  }
273
  else {
274
  return this.allSelectedText;
275
  }
276
  }
277
+ else if (this.numberDisplayed != 0 && selectedOptions.length > this.numberDisplayed) {
278
+ return selectedOptions.length + ' ' + this.nSelectedText;
279
  }
280
  else {
281
  var selected = '';
282
  var delimiter = this.delimiterText;
283
 
284
+ selectedOptions.each(function () {
285
  var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
286
  selected += label + delimiter;
287
  });
296
  * @param {jQuery} select
297
  * @returns {@exp;selected@call;substr}
298
  */
299
+ buttonTitle: function (options, select) {
300
  if (options.length === 0) {
301
  return this.nonSelectedText;
302
  }
311
  return selected.substr(0, selected.length - this.delimiterText.length);
312
  }
313
  },
314
+ checkboxName: function (option) {
315
  return false; // no checkbox name
316
  },
317
  /**
320
  * @param {jQuery} element
321
  * @returns {String}
322
  */
323
+ optionLabel: function (element) {
324
  return $(element).attr('label') || $(element).text();
325
  },
326
  /**
329
  * @param {jQuery} element
330
  * @returns {String}
331
  */
332
+ optionClass: function (element) {
333
  return $(element).attr('class') || '';
334
  },
335
  /**
340
  * @param {jQuery} option
341
  * @param {Boolean} checked
342
  */
343
+ onChange: function (option, checked) {
344
 
345
  },
346
  /**
348
  *
349
  * @param {jQuery} event
350
  */
351
+ onDropdownShow: function (event) {
352
 
353
  },
354
  /**
356
  *
357
  * @param {jQuery} event
358
  */
359
+ onDropdownHide: function (event) {
360
 
361
  },
362
  /**
364
  *
365
  * @param {jQuery} event
366
  */
367
+ onDropdownShown: function (event) {
368
 
369
  },
370
  /**
372
  *
373
  * @param {jQuery} event
374
  */
375
+ onDropdownHidden: function (event) {
376
 
377
  },
378
  /**
379
  * Triggered on select all.
380
  */
381
+ onSelectAll: function () {
382
 
383
  },
384
  /**
385
  * Triggered on deselect all.
386
  */
387
+ onDeselectAll: function () {
388
 
389
  },
390
  /**
393
  * @param {jQuery} $select
394
  * @param {jQuery} $container
395
  */
396
+ onInitialized: function ($select, $container) {
397
 
398
  },
399
  /**
401
  *
402
  * @param {jQuery} $filter
403
  */
404
+ onFiltering: function ($filter) {
405
 
406
  },
407
  enableHTML: false,
408
+ buttonClass: 'custom-select',
409
  inheritClass: false,
410
  buttonWidth: 'auto',
411
  buttonContainer: '<div class="btn-group" />',
436
  nonSelectedText: 'None selected',
437
  nSelectedText: 'selected',
438
  allSelectedText: 'All selected',
439
+ resetButtonText: 'Reset',
440
  numberDisplayed: 3,
441
  disableIfEmpty: false,
442
  disabledText: '',
444
  includeResetOption: false,
445
  includeResetDivider: false,
446
  resetText: 'Reset',
447
+ indentGroupOptions: true,
448
+ // possible options: 'never', 'always', 'ifPopupIsSmaller', 'ifPopupIsWider'
449
+ widthSynchronizationMode: 'never',
450
+ buttonTextAlignment: 'center',
451
+ enableResetButton: false,
452
  templates: {
453
+ button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span></button>',
454
+ popupContainer: '<div class="multiselect-container dropdown-menu"></div>',
455
+ filter: '<div class="multiselect-filter d-flex align-items-center"><i class="fas fa-sm fa-search text-muted"></i><input type="search" class="multiselect-search form-control" /></div>',
456
+ buttonGroup: '<div class="multiselect-buttons btn-group" style="display:flex;"></div>',
457
+ buttonGroupReset: '<button type="button" class="multiselect-reset btn btn-secondary btn-block"></button>',
458
+ option: '<button type="button" class="multiselect-option dropdown-item"></button>',
459
+ divider: '<div class="dropdown-divider"></div>',
460
+ optionGroup: '<button type="button" class="multiselect-group dropdown-item"></button>',
461
+ resetButton: '<div class="multiselect-reset text-center p-2"><button type="button" class="btn btn-sm btn-block btn-outline-secondary"></button></div>'
462
  }
463
  },
464
 
467
  /**
468
  * Builds the container of the multiselect.
469
  */
470
+ buildContainer: function () {
471
  this.$container = $(this.options.buttonContainer);
472
+ if (this.options.widthSynchronizationMode !== 'never') {
473
+ this.$container.on('show.bs.dropdown', $.proxy(function () {
474
+ // the width needs to be synchronized again in case the width of the button changed in between
475
+ this.synchronizeButtonAndPopupWidth();
476
+ this.options.onDropdownShow();
477
+ }, this));
478
+ }
479
+ else {
480
+ this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
481
+ }
482
  this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
483
  this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
484
  this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
487
  /**
488
  * Builds the button of the multiselect.
489
  */
490
+ buildButton: function () {
491
  this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
492
  if (this.$select.attr('class') && this.options.inheritClass) {
493
  this.$button.addClass(this.$select.attr('class'));
503
  // Manually add button width if set.
504
  if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
505
  this.$button.css({
506
+ 'width': '100%' //this.options.buttonWidth,
 
 
507
  });
508
  this.$container.css({
509
  'width': this.options.buttonWidth
510
  });
511
  }
512
 
513
+ if (this.options.buttonTextAlignment) {
514
+ switch (this.options.buttonTextAlignment) {
515
+ case 'left':
516
+ this.$button.addClass('text-left');
517
+ break;
518
+ case 'center':
519
+ this.$button.addClass('text-center');
520
+ break;
521
+ case 'right':
522
+ this.$button.addClass('text-right');
523
+ break;
524
+ }
525
+ }
526
+
527
  // Keep the tab index from the select.
528
  var tabindex = this.$select.attr('tabindex');
529
  if (tabindex) {
534
  },
535
 
536
  /**
537
+ * Builds the popup container representing the dropdown menu.
538
  */
539
+ buildDropdown: function () {
540
 
541
+ // Build popup container.
542
+ this.$popupContainer = $(this.options.templates.popupContainer);
543
 
544
  if (this.options.dropRight) {
545
+ this.$container.addClass('dropright');
546
+ }
547
+ else if (this.options.dropUp) {
548
+ this.$container.addClass("dropup");
549
  }
550
 
551
  // Set max height of dropdown menu to activate auto scrollbar.
552
  if (this.options.maxHeight) {
553
  // TODO: Add a class for this option to move the css declarations.
554
+ this.$popupContainer.css({
555
  'max-height': this.options.maxHeight + 'px',
556
  'overflow-y': 'auto',
557
  'overflow-x': 'hidden'
558
  });
559
  }
560
 
561
+ if (this.options.widthSynchronizationMode !== 'never') {
562
+ this.$popupContainer.css('overflow-x', 'hidden');
563
+ }
564
 
565
+ this.$popupContainer.on("touchstart click", function (e) {
566
+ e.stopPropagation();
567
+ });
568
 
569
+ this.$container.append(this.$popupContainer);
570
+ },
571
+
572
+ synchronizeButtonAndPopupWidth: function () {
573
+ if (!this.$popupContainer || this.options.widthSynchronizationMode === 'never') {
574
+ return;
575
  }
576
 
577
+ var buttonWidth = this.$button.outerWidth();
578
+ switch (this.options.widthSynchronizationMode) {
579
+ case 'always':
580
+ this.$popupContainer.css('min-width', buttonWidth);
581
+ this.$popupContainer.css('max-width', buttonWidth);
582
+ break;
583
+ case 'ifPopupIsSmaller':
584
+ this.$popupContainer.css('min-width', buttonWidth);
585
+ break;
586
+ case 'ifPopupIsWider':
587
+ this.$popupContainer.css('max-width', buttonWidth);
588
+ break;
589
+ }
590
  },
591
 
592
  /**
594
  *
595
  * Uses createDivider and createOptionValue to create the necessary options.
596
  */
597
+ buildDropdownOptions: function () {
598
 
599
+ this.$select.children().each($.proxy(function (index, element) {
600
 
601
  var $element = $(element);
602
  // Support optgroups and options without a group simultaneously.
616
  this.createDivider();
617
  }
618
  else {
619
+ this.createOptionValue(element, false);
620
  }
621
 
622
  }
625
  }, this));
626
 
627
  // Bind the change event on the dropdown elements.
628
+ $(this.$popupContainer).off('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]');
629
+ $(this.$popupContainer).on('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]', $.proxy(function (event) {
630
  var $target = $(event.target);
631
 
632
  var checked = $target.prop('checked') || false;
635
  // Apply or unapply the configured selected class.
636
  if (this.options.selectedClass) {
637
  if (checked) {
638
+ $target.closest('.multiselect-option')
639
  .addClass(this.options.selectedClass);
640
  }
641
  else {
642
+ $target.closest('.multiselect-option')
643
  .removeClass(this.options.selectedClass);
644
  }
645
  }
671
  else {
672
  // Unselect all other options and corresponding checkboxes.
673
  if (this.options.selectedClass) {
674
+ $($checkboxesNotThis).closest('.dropdown-item').removeClass(this.options.selectedClass);
675
  }
676
 
677
  $($checkboxesNotThis).prop('checked', false);
682
  }
683
 
684
  if (this.options.selectedClass === "active") {
685
+ $optionsNotThis.closest(".dropdown-item").css("outline", "");
686
  }
687
  }
688
  else {
704
  this.$select.change();
705
  this.updateButtonText();
706
 
707
+ if (this.options.preventInputChangeEvent) {
708
  return false;
709
  }
710
  }, this));
711
 
712
+ $('.multiselect-option', this.$popupContainer).off('mousedown');
713
+ $('.multiselect-option', this.$popupContainer).on('mousedown', function (e) {
714
  if (e.shiftKey) {
715
  // Prevent selecting text by Shift+click
716
  return false;
717
  }
718
  });
719
 
720
+ $(this.$popupContainer).off('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group');
721
+ $(this.$popupContainer).on('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group', $.proxy(function (event) {
722
  event.stopPropagation();
723
 
724
  var $target = $(event.target);
725
 
726
  if (event.shiftKey && this.options.multiple) {
727
+ if (!$target.is("input")) { // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
728
  event.preventDefault();
729
+ $target = $target.closest(".multiselect-option").find("input");
730
  $target.prop("checked", !$target.prop("checked"));
731
  }
732
  var checked = $target.prop('checked') || false;
733
 
734
  if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
735
+ var from = this.$popupContainer.find(".multiselect-option:visible").index($target.closest(".multiselect-option"));
736
+ var to = this.$popupContainer.find(".multiselect-option:visible").index(this.lastToggledInput.closest(".multiselect-option"));
737
 
738
  if (from > to) { // Swap the indices
739
  var tmp = to;
745
  ++to;
746
 
747
  // Change the checkboxes and underlying options
748
+ var range = this.$popupContainer.find(".multiselect-option:not(.multiselect-filter-hidden)").slice(from, to).find("input");
749
 
750
  range.prop('checked', checked);
751
 
752
  if (this.options.selectedClass) {
753
+ range.closest('.multiselect-option')
754
  .toggleClass(this.options.selectedClass, checked);
755
  }
756
 
766
  // Trigger the select "change" event
767
  $target.trigger("change");
768
  }
769
+ else if (!$target.is('input')) {
770
+ var $checkbox = $target.closest('.multiselect-option, .multiselect-all').find('.form-check-input');
771
+ if ($checkbox.length > 0) {
772
+ if (this.options.multiple || !$checkbox.prop('checked')) {
773
+ $checkbox.prop('checked', !$checkbox.prop('checked'));
774
+ $checkbox.change();
775
+ }
776
+ }
777
+ else if (this.options.enableClickableOptGroups && this.options.multiple && !$target.hasClass("caret-container")) {
778
+ var groupItem = $target;
779
+ if (!groupItem.hasClass("multiselect-group")) {
780
+ groupItem = $target.closest('.multiselect-group');
781
+ }
782
+ $checkbox = groupItem.find(".form-check-input");
783
+ if ($checkbox.length > 0) {
784
+ $checkbox.prop('checked', !$checkbox.prop('checked'));
785
+ $checkbox.change();
786
+ }
787
+ }
788
+
789
+ event.preventDefault();
790
+ }
791
 
792
  // Remembers last clicked option
793
+ var $input = $target.closest(".multiselect-option").find("input[type='checkbox'], input[type='radio']");
794
+ if ($input.length > 0) {
795
  this.lastToggledInput = $target;
796
  }
797
+ else {
798
+ this.lastToggledInput = null;
799
+ }
800
 
801
+ $target.blur();
802
  }, this));
803
 
804
+ //Keyboard support.
805
+ this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function (event) {
806
+ var $items = $(this.$container).find(".multiselect-option:not(.disabled), .multiselect-group:not(.disabled), .multiselect-all").filter(":visible");
807
+ var index = $items.index($items.filter(':focus'));
808
+ var $search = $('.multiselect-search', this.$container);
809
 
810
+ // keyCode 9 == Tab
811
+ if (event.keyCode === 9 && this.$container.hasClass('show')) {
812
  this.$button.click();
813
  }
814
+ // keyCode 13 = Enter
815
+ else if (event.keyCode == 13) {
816
+ var $current = $items.eq(index);
817
+ setTimeout(function () {
818
+ $current.focus();
819
+ }, 1);
820
+ }
821
+ // keyCode 38 = Arrow Up
822
+ else if (event.keyCode == 38) {
823
+ if (index == 0 && !$search.is(':focus')) {
824
+ setTimeout(function () {
825
+ $search.focus();
826
+ }, 1);
827
  }
828
+ }
829
+ // keyCode 40 = Arrow Down
830
+ else if (event.keyCode == 40) {
831
+ if ($search.is(':focus')) {
832
+ var $first = $items.eq(0);
833
+ setTimeout(function () {
834
+ $search.blur();
835
+ $first.focus();
836
+ }, 1);
837
  }
838
+ else if (index == -1) {
839
+ setTimeout(function () {
840
+ $search.focus();
841
+ }, 1);
842
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  }
844
  }, this));
845
 
846
  if (this.options.enableClickableOptGroups && this.options.multiple) {
847
+ $(".multiselect-group input", this.$popupContainer).off("change");
848
+ $(".multiselect-group input", this.$popupContainer).on("change", $.proxy(function (event) {
849
  event.stopPropagation();
850
 
851
  var $target = $(event.target);
852
  var checked = $target.prop('checked') || false;
853
 
854
+ var $item = $(event.target).closest('.dropdown-item');
855
+ var $group = $item.nextUntil(".multiselect-group")
856
  .not('.multiselect-filter-hidden')
857
  .not('.disabled');
858
 
859
  var $inputs = $group.find("input");
860
 
 
861
  var $options = [];
862
 
863
  if (this.options.selectedClass) {
864
  if (checked) {
865
+ $item.addClass(this.options.selectedClass);
866
  }
867
  else {
868
+ $item.removeClass(this.options.selectedClass);
869
  }
870
  }
871
 
872
+ $.each($inputs, $.proxy(function (index, input) {
873
+ var $input = $(input);
874
+ var value = $input.val();
875
  var $option = this.getOptionByValue(value);
876
 
877
  if (checked) {
878
+ $input.prop('checked', true);
879
+ $input.closest('.dropdown-item')
880
  .addClass(this.options.selectedClass);
881
 
882
  $option.prop('selected', true);
883
  }
884
  else {
885
+ $input.prop('checked', false);
886
+ $input.closest('.dropdown-item')
887
  .removeClass(this.options.selectedClass);
888
 
889
  $option.prop('selected', false);
903
  }
904
 
905
  if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
906
+ $(".multiselect-group .caret-container", this.$popupContainer).off("click");
907
+ $(".multiselect-group .caret-container", this.$popupContainer).on("click", $.proxy(function (event) {
908
+ var $group = $(event.target).closest('.multiselect-group');
909
+ var $inputs = $group.nextUntil(".multiselect-group")
910
+ .not('.multiselect-filter-hidden');
911
 
912
  var visible = true;
913
+ $inputs.each(function () {
914
  visible = visible && !$(this).hasClass('multiselect-collapsible-hidden');
915
  });
916
 
923
  .removeClass('multiselect-collapsible-hidden');
924
  }
925
  }, this));
926
+ }
927
+ },
928
+
929
+ /**
930
+ * Create a checkbox container with input and label based on given values
931
+ * @param {JQuery} $item
932
+ * @param {String} label
933
+ * @param {String} name
934
+ * @param {String} value
935
+ * @param {String} inputType
936
+ * @returns {JQuery}
937
+ */
938
+ createCheckbox: function ($item, labelContent, name, value, title, inputType) {
939
+ var $wrapper = $('<span />');
940
+ $wrapper.addClass("form-check");
941
+
942
+ if (this.options.enableHTML && $(labelContent).length > 0) {
943
+ var $checkboxLabel = $('<label class="form-check-label" />');
944
+ $checkboxLabel.html(labelContent);
945
+ $wrapper.append($checkboxLabel);
946
+ }
947
+ else {
948
+ var $checkboxLabel = $('<label class="form-check-label" />');
949
+ $checkboxLabel.text(labelContent);
950
+ $wrapper.append($checkboxLabel);
951
+ }
952
 
953
+ var $checkbox = $('<input class="form-check-input"/>').attr('type', inputType);
954
+ $checkbox.val(value);
955
+ $wrapper.prepend($checkbox);
956
+
957
+ if (name) {
958
+ $checkbox.attr('name', name);
959
  }
960
+
961
+ $item.prepend($wrapper);
962
+ $item.attr("title", title || labelContent);
963
+
964
+ return $checkbox;
965
  },
966
 
967
  /**
969
  *
970
  * @param {jQuery} element
971
  */
972
+ createOptionValue: function (element, isGroupOption) {
973
  var $element = $(element);
974
  if ($element.is(':selected')) {
975
  $element.prop('selected', true);
980
  var classes = this.options.optionClass(element);
981
  var value = $element.val();
982
  var inputType = this.options.multiple ? "checkbox" : "radio";
983
+ var title = $element.attr('title');
984
 
985
+ var $option = $(this.options.templates.option);
986
+ $option.addClass(classes);
 
 
 
987
 
988
+ if (isGroupOption && this.options.indentGroupOptions) {
989
+ $option.addClass("multiselect-group-option-indented")
 
 
990
  }
991
 
992
+ // Hide all children items when collapseOptGroupsByDefault is true
993
+ if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
994
+ $option.addClass("multiselect-collapsible-hidden");
995
+ $option.hide();
 
996
  }
997
 
 
 
998
  var name = this.options.checkboxName($element);
999
+ var $checkbox = this.createCheckbox($option, label, name, value, title, inputType);
 
 
 
 
1000
 
1001
  var selected = $element.prop('selected') || false;
 
1002
 
1003
  if (value === this.options.selectAllValue) {
1004
+ $option.addClass("multiselect-all");
1005
+ $option.removeClass("multiselect-option");
1006
  $checkbox.parent().parent()
1007
  .addClass('multiselect-all');
1008
  }
1009
 
1010
+ this.$popupContainer.append($option);
 
 
1011
 
1012
  if ($element.is(':disabled')) {
1013
  $checkbox.attr('disabled', 'disabled')
1014
  .prop('disabled', true)
1015
+ .closest('.dropdown-item')
 
 
1016
  .addClass('disabled');
1017
  }
1018
 
1019
  $checkbox.prop('checked', selected);
1020
 
1021
  if (selected && this.options.selectedClass) {
1022
+ $checkbox.closest('.dropdown-item')
1023
  .addClass(this.options.selectedClass);
1024
  }
1025
  },
1029
  *
1030
  * @param {jQuery} element
1031
  */
1032
+ createDivider: function (element) {
1033
  var $divider = $(this.options.templates.divider);
1034
+ this.$popupContainer.append($divider);
1035
  },
1036
 
1037
  /**
1039
  *
1040
  * @param {jQuery} group
1041
  */
1042
+ createOptgroup: function (group) {
1043
+ var $group = $(group);
1044
+ var label = $group.attr("label");
1045
+ var value = $group.attr("value");
1046
+ var title = $group.attr('title');
1047
 
1048
+ var $groupOption = $("<span class='multiselect-group dropdown-item-text'></span>");
 
1049
 
1050
+ if (this.options.enableClickableOptGroups && this.options.multiple) {
1051
+ $groupOption = $(this.options.templates.optionGroup);
1052
+ var $checkbox = this.createCheckbox($groupOption, label, null, value, title, "checkbox");
1053
  }
1054
  else {
1055
+ if (this.options.enableHTML) {
1056
+ $groupOption.html(" " + label);
1057
+ }
1058
+ else {
1059
+ $groupOption.text(" " + label);
1060
+ }
1061
  }
1062
 
1063
+ var classes = this.options.optionClass(group);
1064
+ $groupOption.addClass(classes);
 
1065
 
1066
+ if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
1067
+ $groupOption.find('.form-check').addClass('d-inline-block');
1068
+ $groupOption.append('<span class="caret-container dropdown-toggle pl-1"></span>');
1069
  }
1070
 
1071
+ if ($group.is(':disabled')) {
1072
+ $groupOption.addClass('disabled');
1073
  }
1074
 
1075
+ this.$popupContainer.append($groupOption);
1076
 
1077
+ $("option", group).each($.proxy(function ($, group) {
1078
+ this.createOptionValue(group, true);
1079
+ }, this));
1080
  },
1081
 
1082
  /**
1083
  * Build the reset.
1084
  *
1085
  */
1086
+ buildReset: function () {
1087
  if (this.options.includeResetOption) {
1088
 
1089
  // Check whether to add a divider after the reset.
1090
  if (this.options.includeResetDivider) {
1091
+ var divider = $(this.options.templates.divider);
1092
+ divider.addClass("mt-0");
1093
+ this.$popupContainer.prepend(divider);
1094
  }
1095
 
1096
  var $resetButton = $(this.options.templates.resetButton);
1097
 
1098
  if (this.options.enableHTML) {
1099
+ $('button', $resetButton).html(this.options.resetText);
1100
  }
1101
  else {
1102
+ $('button', $resetButton).text(this.options.resetText);
1103
  }
1104
 
1105
+ $('button', $resetButton).click($.proxy(function () {
1106
  this.clearSelection();
1107
  }, this));
1108
 
1109
+ this.$popupContainer.prepend($resetButton);
1110
  }
1111
  },
1112
 
1115
  *
1116
  * Checks if a select all has already been created.
1117
  */
1118
+ buildSelectAll: function () {
1119
  if (typeof this.options.selectAllValue === 'number') {
1120
  this.options.selectAllValue = this.options.selectAllValue.toString();
1121
  }
1123
  var alreadyHasSelectAll = this.hasSelectAll();
1124
 
1125
  if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
1126
+ && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
1127
 
1128
  // Check whether to add a divider after the select all.
1129
  if (this.options.includeSelectAllDivider) {
1130
+ this.$popupContainer.prepend($(this.options.templates.divider));
1131
  }
1132
 
1133
+ var $option = $(this.options.templates.li || this.options.templates.option);
1134
+ var $checkbox = this.createCheckbox($option, this.options.selectAllText, this.options.selectAllName, this.options.selectAllValue, this.options.selectAllText, "checkbox");
 
 
 
 
 
 
 
1135
 
1136
+ $option.addClass("multiselect-all");
1137
+ $option.removeClass("multiselect-option");
1138
+ $option.find(".form-check-label").addClass("font-weight-bold");
 
 
 
1139
 
1140
+ this.$popupContainer.prepend($option);
 
 
 
 
 
 
 
1141
 
1142
  $checkbox.prop('checked', false);
1143
  }
1146
  /**
1147
  * Builds the filter.
1148
  */
1149
+ buildFilter: function () {
1150
 
1151
  // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1152
  if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
1157
  this.$filter = $(this.options.templates.filter);
1158
  $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1159
 
1160
+ // Handles optional filter clear button
1161
+ if (!this.options.includeFilterClearBtn) {
1162
+ this.$filter.find(".multiselect-search").attr("type", "text");
1163
+
1164
+ // Remove clear button if the old design of the filter with input groups and separated clear button is used
1165
+ this.$filter.find(".multiselect-clear-filter").remove();
1166
+ }
1167
+ else {
1168
+ // Firefox does not support a clear button in search inputs right now therefore it must be added manually
1169
+ if (this.isFirefox() && this.$filter.find(".multiselect-clear-filter").length === 0) {
1170
+ this.$filter.append("<i class='fas fa-times text-muted multiselect-clear-filter multiselect-moz-clear-filter'></i>");
1171
+ }
1172
+
1173
+ this.$filter.find(".multiselect-clear-filter").on('click', $.proxy(function (event) {
1174
  clearTimeout(this.searchTimeout);
1175
 
1176
  this.query = '';
1177
  this.$filter.find('.multiselect-search').val('');
1178
+ $('.dropdown-item', this.$popupContainer).show().removeClass('multiselect-filter-hidden');
1179
 
1180
  this.updateSelectAll();
1181
 
1184
  }
1185
 
1186
  }, this));
 
1187
  }
1188
 
1189
+ this.$popupContainer.prepend(this.$filter);
1190
 
1191
+ this.$filter.val(this.query).on('click', function (event) {
1192
  event.stopPropagation();
1193
+ }).on('input keydown', $.proxy(function (event) {
1194
  // Cancel enter key default behaviour
1195
  if (event.which === 13) {
1196
+ event.preventDefault();
1197
+ }
1198
+
1199
+ if (this.isFirefox() && this.options.includeFilterClearBtn) {
1200
+ if (event.target.value) {
1201
+ this.$filter.find(".multiselect-moz-clear-filter").show();
1202
+ }
1203
+ else {
1204
+ this.$filter.find(".multiselect-moz-clear-filter").hide();
1205
+ }
1206
+ }
1207
 
1208
  // This is useful to catch "keydown" events after the browser has updated the control.
1209
  clearTimeout(this.searchTimeout);
1210
 
1211
+ this.searchTimeout = this.asyncFunction($.proxy(function () {
1212
 
1213
  if (this.query !== event.target.value) {
1214
  this.query = event.target.value;
1215
 
1216
  var currentGroup, currentGroupVisible;
1217
+ $.each($('.multiselect-option, .multiselect-group', this.$popupContainer), $.proxy(function (index, element) {
1218
  var value = $('input', element).length > 0 ? $('input', element).val() : "";
1219
+ var text = $('.form-check-label', element).text();
1220
 
1221
  var filterCandidate = '';
1222
  if ((this.options.filterBehavior === 'text')) {
1251
  }
1252
 
1253
  // Toggle current element (group or group item) according to showElement boolean.
1254
+ if (!showElement) {
1255
+ $(element).css('display', 'none');
1256
+ $(element).addClass('multiselect-filter-hidden');
1257
  }
1258
+ if (showElement) {
1259
+ $(element).css('display', 'block');
1260
+ $(element).removeClass('multiselect-filter-hidden');
1261
  }
1262
 
1263
  // Differentiate groups and group items.
1289
  this.updateOptGroups();
1290
  }
1291
 
1292
+ this.updatePopupPosition();
1293
+
1294
  this.options.onFiltering(event.target);
1295
 
1296
  }, this), 300, this);
1299
  }
1300
  },
1301
 
1302
+ /**
1303
+ * Builds the filter.
1304
+ */
1305
+ buildButtons: function () {
1306
+ if (this.options.enableResetButton) {
1307
+ var $buttonGroup = $(this.options.templates.buttonGroup);
1308
+ this.$buttonGroupReset = $(this.options.templates.buttonGroupReset).text(this.options.resetButtonText);
1309
+ $buttonGroup.append(this.$buttonGroupReset);
1310
+ this.$popupContainer.prepend($buttonGroup);
1311
+
1312
+ // We save all options that were previously selected.
1313
+ this.defaultSelection = {};
1314
+ $('option', this.$select).each($.proxy(function(index, element) {
1315
+ var $option = $(element);
1316
+ this.defaultSelection[$option.val()] = $option.prop('selected');
1317
+ }, this));
1318
+
1319
+ this.$buttonGroupReset.on('click', $.proxy(function(event) {
1320
+ $('option', this.$select).each($.proxy(function(index, element) {
1321
+ var $option = $(element);
1322
+ $option.prop('selected', this.defaultSelection[$option.val()]);
1323
+ }, this));
1324
+ this.refresh();
1325
+
1326
+ if (this.options.enableFiltering) {
1327
+ this.$filter.trigger('keydown');
1328
+ $('input', this.$filter).val('');
1329
+ }
1330
+ }, this));
1331
+ }
1332
+ },
1333
+
1334
+ updatePopupPosition: function() {
1335
+ // prevent gaps between popup and select when filter is used (#1199)
1336
+ var transformMatrix = this.$popupContainer.css("transform");
1337
+ var matrixType = transformMatrix.substring(0, transformMatrix.indexOf('('));
1338
+ var values = transformMatrix.substring(transformMatrix.indexOf('(') + 1, transformMatrix.length - 1);
1339
+ var valuesArray = values.split(',');
1340
+
1341
+ var valueIndex = 5;
1342
+ if(matrixType === "matrix3d") {
1343
+ valueIndex = 13;
1344
+ }
1345
+
1346
+ var yTransformation = valuesArray[valueIndex];
1347
+ // Need to check to avoid errors when testing and in some other situations.
1348
+ yTransformation = typeof yTransformation === 'undefined' ? 0 : yTransformation.trim();
1349
+ if (yTransformation < 0) {
1350
+ yTransformation = this.$popupContainer.css("height").replace('px', '') * -1;
1351
+ valuesArray[valueIndex] = yTransformation;
1352
+ transformMatrix = matrixType + '(' + valuesArray.join(',') + ')';
1353
+ this.$popupContainer.css("transform", transformMatrix);
1354
+ }
1355
+ },
1356
+
1357
  /**
1358
  * Unbinds the whole plugin.
1359
  */
1360
+ destroy: function () {
1361
  this.$container.remove();
1362
+ this.$select.unwrap();
1363
  this.$select.show();
1364
 
1365
  // reset original state
1373
  */
1374
  refresh: function () {
1375
  var inputs = {};
1376
+ $('.multiselect-option input', this.$popupContainer).each(function () {
1377
+ inputs[$(this).val()] = $(this);
1378
  });
1379
 
1380
  $('option', this.$select).each($.proxy(function (index, element) {
1385
  $input.prop('checked', true);
1386
 
1387
  if (this.options.selectedClass) {
1388
+ $input.closest('.multiselect-option')
1389
  .addClass(this.options.selectedClass);
1390
  }
1391
  }
1393
  $input.prop('checked', false);
1394
 
1395
  if (this.options.selectedClass) {
1396
+ $input.closest('.multiselect-option')
1397
  .removeClass(this.options.selectedClass);
1398
  }
1399
  }
1401
  if ($elem.is(":disabled")) {
1402
  $input.attr('disabled', 'disabled')
1403
  .prop('disabled', true)
1404
+ .closest('.multiselect-option')
1405
  .addClass('disabled');
1406
  }
1407
  else {
1408
  $input.prop('disabled', false)
1409
+ .closest('.multiselect-option')
1410
  .removeClass('disabled');
1411
  }
1412
  }, this));
1428
  * @param {Array} selectValues
1429
  * @param {Boolean} triggerOnChange
1430
  */
1431
+ select: function (selectValues, triggerOnChange) {
1432
+ if (!$.isArray(selectValues)) {
1433
  selectValues = [selectValues];
1434
  }
1435
 
1443
  var $option = this.getOptionByValue(value);
1444
  var $checkbox = this.getInputByValue(value);
1445
 
1446
+ if ($option === undefined || $checkbox === undefined) {
1447
  continue;
1448
  }
1449
 
 
 
 
 
1450
  if (this.options.selectedClass) {
1451
+ $checkbox.closest('.dropdown-item')
1452
  .addClass(this.options.selectedClass);
1453
  }
1454
 
1455
  $checkbox.prop('checked', true);
1456
  $option.prop('selected', true);
1457
 
1458
+ if (!this.options.multiple) {
1459
+ var $checkboxesNotThis = $('input', this.$container).not($checkbox);
1460
+ $($checkboxesNotThis).prop('checked', false);
1461
+ $($checkboxesNotThis).closest('.multiselect-option').removeClass("active")
1462
+
1463
+ var $optionsNotThis = $('option', this.$select).not($option);
1464
+ $optionsNotThis.prop('selected', false);
1465
+ }
1466
+
1467
  if (triggerOnChange) {
1468
  this.options.onChange($option, true);
1469
  }
1499
  * @param {Array} deselectValues
1500
  * @param {Boolean} triggerOnChange
1501
  */
1502
+ deselect: function (deselectValues, triggerOnChange) {
1503
+ if (!this.options.multiple) {
1504
+ // In single selection mode at least on option needs to be selected
1505
+ return;
1506
+ }
1507
+
1508
+ if (!$.isArray(deselectValues)) {
1509
  deselectValues = [deselectValues];
1510
  }
1511
 
1519
  var $option = this.getOptionByValue(value);
1520
  var $checkbox = this.getInputByValue(value);
1521
 
1522
+ if ($option === undefined || $checkbox === undefined) {
1523
  continue;
1524
  }
1525
 
1526
  if (this.options.selectedClass) {
1527
+ $checkbox.closest('.dropdown-item')
1528
  .removeClass(this.options.selectedClass);
1529
  }
1530
 
1553
  * @param {Boolean} triggerOnSelectAll
1554
  */
1555
  selectAll: function (justVisible, triggerOnSelectAll) {
1556
+ if (!this.options.multiple) {
1557
+ // In single selection mode only one option can be selected at a time
1558
+ return;
1559
+ }
1560
 
1561
+ // Record all changes, i.e., options selected that were not selected before.
1562
+ var selected = [];
1563
  var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
 
 
1564
 
1565
+ if (justVisible) {
1566
+ var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer);
1567
+ $('input:enabled', visibleOptions).prop('checked', true);
1568
+ visibleOptions.addClass(this.options.selectedClass);
1569
 
1570
+ $('input:enabled', visibleOptions).each($.proxy(function (index, element) {
1571
  var value = $(element).val();
1572
  var option = this.getOptionByValue(value);
1573
+ if (!$(option).prop('selected')) {
1574
+ selected.push(option);
1575
+ }
1576
  $(option).prop('selected', true);
1577
  }, this));
1578
  }
1579
  else {
1580
+ var allOptions = $(".multiselect-option:not(.disabled)", this.$popupContainer);
1581
+ $('input:enabled', allOptions).prop('checked', true);
1582
+ allOptions.addClass(this.options.selectedClass);
1583
 
1584
+ $('input:enabled', allOptions).each($.proxy(function (index, element) {
1585
  var value = $(element).val();
1586
  var option = this.getOptionByValue(value);
1587
+ if (!$(option).prop('selected')) {
1588
+ selected.push(option);
1589
+ }
1590
  $(option).prop('selected', true);
1591
  }, this));
1592
  }
1593
 
1594
+ $('.multiselect-option input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', true);
1595
 
1596
  if (this.options.enableClickableOptGroups && this.options.multiple) {
1597
  this.updateOptGroups();
1598
  }
1599
 
1600
+ this.updateButtonText();
1601
+ this.updateSelectAll();
1602
+
1603
  if (triggerOnSelectAll) {
1604
+ this.options.onSelectAll(selected);
1605
  }
1606
  },
1607
 
1613
  * @param {Boolean} justVisible
1614
  */
1615
  deselectAll: function (justVisible, triggerOnDeselectAll) {
1616
+ if (!this.options.multiple) {
1617
+ // In single selection mode at least on option needs to be selected
1618
+ return;
1619
+ }
1620
 
1621
+ // Record changes, i.e., those options that are deselected but were not deselected before.
1622
+ var deselected = [];
1623
  var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
 
 
1624
 
1625
+ if (justVisible) {
1626
+ var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer);
1627
+ $('input[type="checkbox"]:enabled', visibleOptions).prop('checked', false);
1628
+ visibleOptions.removeClass(this.options.selectedClass);
1629
 
1630
+ $('input[type="checkbox"]:enabled', visibleOptions).each($.proxy(function (index, element) {
1631
  var value = $(element).val();
1632
  var option = this.getOptionByValue(value);
1633
+ if ($(option).prop('selected')) {
1634
+ deselected.push(option);
1635
+ }
1636
  $(option).prop('selected', false);
1637
  }, this));
1638
  }
1639
  else {
1640
+ var allOptions = $(".multiselect-option:not(.disabled):not(.multiselect-group)", this.$popupContainer);
1641
+ $('input[type="checkbox"]:enabled', allOptions).prop('checked', false);
1642
+ allOptions.removeClass(this.options.selectedClass);
1643
 
1644
+ $('input[type="checkbox"]:enabled', allOptions).each($.proxy(function (index, element) {
1645
  var value = $(element).val();
1646
  var option = this.getOptionByValue(value);
1647
+ if ($(option).prop('selected')) {
1648
+ deselected.push(option);
1649
+ }
1650
  $(option).prop('selected', false);
1651
  }, this));
1652
  }
1653
 
1654
+ $('.multiselect-all input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', false);
1655
 
1656
  if (this.options.enableClickableOptGroups && this.options.multiple) {
1657
  this.updateOptGroups();
1658
  }
1659
 
1660
+ this.updateButtonText();
1661
+ this.updateSelectAll();
1662
+
1663
  if (triggerOnDeselectAll) {
1664
+ this.options.onDeselectAll(deselected);
1665
  }
1666
  },
1667
 
1670
  *
1671
  * Rebuilds the dropdown, the filter and the select all option.
1672
  */
1673
+ rebuild: function () {
1674
+ this.$popupContainer.html('');
1675
 
1676
  // Important to distinguish between radios and checkboxes.
1677
  this.options.multiple = this.$select.attr('multiple') === "multiple";
1679
  this.buildSelectAll();
1680
  this.buildDropdownOptions();
1681
  this.buildFilter();
1682
+ this.buildButtons();
1683
 
1684
  this.updateButtonText();
1685
  this.updateSelectAll(true);
1688
  this.updateOptGroups();
1689
  }
1690
 
1691
+ if (this.options.disableIfEmpty) {
1692
+ if ($('option', this.$select).length <= 0) {
1693
+ if (!this.$select.prop('disabled')) {
1694
+ this.disable(true);
1695
+ }
1696
+ }
1697
+ else if (this.$select.data("disabled-by-option")) {
1698
+ this.enable();
1699
+ }
1700
  }
1701
 
1702
  if (this.options.dropRight) {
1703
+ this.$container.addClass('dropright');
1704
+ }
1705
+ else if (this.options.dropUp) {
1706
+ this.$container.addClass('dropup');
1707
+ }
1708
+
1709
+ if (this.options.widthSynchronizationMode !== 'never') {
1710
+ this.synchronizeButtonAndPopupWidth();
1711
  }
1712
  },
1713
 
1714
  /**
1715
  * The provided data will be used to build the dropdown.
1716
  */
1717
+ dataprovider: function (dataprovider) {
1718
 
1719
  var groupCounter = 0;
1720
  var $select = this.$select.empty();
1731
  value: option.value
1732
  });
1733
 
1734
+ forEach(option.children, function (subOption) { // add children option tags
1735
  var attributes = {
1736
  value: subOption.value,
1737
  label: subOption.label || subOption.value,
1741
  };
1742
 
1743
  //Loop through attributes object and add key-value for each attribute
1744
+ for (var key in subOption.attributes) {
1745
  attributes['data-' + key] = subOption.attributes[key];
1746
+ }
1747
+ //Append original attributes + new data attributes to option
1748
  $tag.append($('<option/>').attr(attributes));
1749
  });
1750
  }
1760
  };
1761
  //Loop through attributes object and add key-value for each attribute
1762
  for (var key in option.attributes) {
1763
+ attributes['data-' + key] = option.attributes[key];
1764
  }
1765
  //Append original attributes + new data attributes to option
1766
  $tag = $('<option/>').attr(attributes);
1777
  /**
1778
  * Enable the multiselect.
1779
  */
1780
+ enable: function () {
1781
  this.$select.prop('disabled', false);
1782
  this.$button.prop('disabled', false)
1783
  .removeClass('disabled');
1784
+
1785
+ this.updateButtonText();
1786
  },
1787
 
1788
  /**
1789
  * Disable the multiselect.
1790
  */
1791
+ disable: function (disableByOption) {
1792
  this.$select.prop('disabled', true);
1793
  this.$button.prop('disabled', true)
1794
  .addClass('disabled');
1795
+
1796
+ if (disableByOption) {
1797
+ this.$select.data("disabled-by-option", true);
1798
+ }
1799
+ else {
1800
+ this.$select.data("disabled-by-option", null);
1801
+ }
1802
+
1803
+ this.updateButtonText();
1804
  },
1805
 
1806
  /**
1808
  *
1809
  * @param {Array} options
1810
  */
1811
+ setOptions: function (options) {
1812
  this.options = this.mergeOptions(options);
1813
  },
1814
 
1818
  * @param {Array} options
1819
  * @returns {Array}
1820
  */
1821
+ mergeOptions: function (options) {
1822
  return $.extend(true, {}, this.defaults, this.options, options);
1823
  },
1824
 
1827
  *
1828
  * @returns {Boolean}
1829
  */
1830
+ hasSelectAll: function () {
1831
+ return $('.multiselect-all', this.$popupContainer).length > 0;
1832
  },
1833
 
1834
  /**
1835
  * Update opt groups.
1836
  */
1837
+ updateOptGroups: function () {
1838
+ var $groups = $('.multiselect-group', this.$popupContainer)
1839
  var selectedClass = this.options.selectedClass;
1840
 
1841
+ $groups.each(function () {
1842
+ var $options = $(this).nextUntil('.multiselect-group')
1843
  .not('.multiselect-filter-hidden')
1844
  .not('.disabled');
1845
 
1846
  var checked = true;
1847
+ $options.each(function () {
1848
  var $input = $('input', this);
1849
 
1850
  if (!$input.prop('checked')) {
1868
  /**
1869
  * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1870
  */
1871
+ updateSelectAll: function (notTriggerOnSelectAll) {
1872
  if (this.hasSelectAll()) {
1873
+ var allBoxes = $(".multiselect-option:not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$popupContainer);
1874
  var allBoxesLength = allBoxes.length;
1875
  var checkedBoxesLength = allBoxes.filter(":checked").length;
1876
+ var selectAllItem = $(".multiselect-all", this.$popupContainer);
1877
+ var selectAllInput = selectAllItem.find("input");
1878
 
1879
  if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1880
  selectAllInput.prop("checked", true);
1881
+ selectAllItem.addClass(this.options.selectedClass);
1882
  }
1883
  else {
1884
  selectAllInput.prop("checked", false);
1885
+ selectAllItem.removeClass(this.options.selectedClass);
1886
  }
1887
  }
1888
  },
1890
  /**
1891
  * Update the button text and its title based on the currently selected options.
1892
  */
1893
+ updateButtonText: function () {
1894
  var options = this.getSelected();
1895
 
1896
  // First update the displayed button text.
1903
 
1904
  // Now update the title attribute of the button.
1905
  $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
1906
+ this.$button.trigger('change');
1907
  },
1908
 
1909
  /**
1911
  *
1912
  * @returns {jQUery}
1913
  */
1914
+ getSelected: function () {
1915
  return $('option', this.$select).filter(":selected");
1916
  },
1917
 
1942
  */
1943
  getInputByValue: function (value) {
1944
 
1945
+ var checkboxes = $('.multiselect-option input:not(.multiselect-search)', this.$popupContainer);
1946
  var valueToCompare = value.toString();
1947
 
1948
  for (var i = 0; i < checkboxes.length; i = i + 1) {
1956
  /**
1957
  * Used for knockout integration.
1958
  */
1959
+ updateOriginalOptions: function () {
1960
  this.originalOptions = this.$select.clone()[0].options;
1961
  },
1962
 
1963
+ asyncFunction: function (callback, timeout, self) {
1964
  var args = Array.prototype.slice.call(arguments, 3);
1965
+ return setTimeout(function () {
1966
  callback.apply(self || window, args);
1967
  }, timeout);
1968
  },
1969
 
1970
+ setAllSelectedText: function (allSelectedText) {
1971
  this.options.allSelectedText = allSelectedText;
1972
  this.updateButtonText();
1973
+ },
1974
+
1975
+ isFirefox: function () {
1976
+ var firefoxIdentifier = 'firefox';
1977
+ var valueNotFoundIndex = -1;
1978
+
1979
+ if (navigator && navigator.userAgent) {
1980
+ return navigator.userAgent.toLocaleLowerCase().indexOf(firefoxIdentifier) > valueNotFoundIndex;
1981
+ }
1982
+
1983
+ return false;
1984
  }
1985
  };
1986
 
1987
+ $.fn.multiselect = function (option, parameter, extraOptions) {
1988
+ return this.each(function () {
1989
  var data = $(this).data('multiselect');
1990
  var options = typeof option === 'object' && option;
1991
 
2008
 
2009
  $.fn.multiselect.Constructor = Multiselect;
2010
 
2011
+ $(function () {
2012
  $("select[data-role=multiselect]").multiselect();
2013
  });
2014
 
2015
+ });
js/formidable.js CHANGED
@@ -760,7 +760,7 @@ function frmFrontFormJS() {
760
  jsErrors[key]
761
  );
762
  } else {
763
- $fieldCont.append( '<div class="frm_error" id="' + id + '">' + jsErrors[key] + '</div>' );
764
  }
765
 
766
  if ( typeof describedBy === 'undefined' ) {
@@ -771,6 +771,7 @@ function frmFrontFormJS() {
771
  input.attr( 'aria-describedby', describedBy );
772
  }
773
  input.attr( 'aria-invalid', true );
 
774
 
775
  jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
776
  }
@@ -785,6 +786,7 @@ function frmFrontFormJS() {
785
  $fieldCont.removeClass( 'frm_blank_field has-error' );
786
  errorMessage.remove();
787
  input.attr( 'aria-invalid', false );
 
788
 
789
  if ( typeof describedBy !== 'undefined' ) {
790
  describedBy = describedBy.replace( errorId, '' );
760
  jsErrors[key]
761
  );
762
  } else {
763
+ $fieldCont.append( '<div class="frm_error" role="alert" id="' + id + '">' + jsErrors[key] + '</div>' );
764
  }
765
 
766
  if ( typeof describedBy === 'undefined' ) {
771
  input.attr( 'aria-describedby', describedBy );
772
  }
773
  input.attr( 'aria-invalid', true );
774
+ input.attr( 'aria-describedby', id );
775
 
776
  jQuery( document ).trigger( 'frmAddFieldError', [ $fieldCont, key, jsErrors ]);
777
  }
786
  $fieldCont.removeClass( 'frm_blank_field has-error' );
787
  errorMessage.remove();
788
  input.attr( 'aria-invalid', false );
789
+ input.removeAttr( 'aria-describedby' );
790
 
791
  if ( typeof describedBy !== 'undefined' ) {
792
  describedBy = describedBy.replace( errorId, '' );
js/formidable.min.js CHANGED
@@ -28,30 +28,31 @@ else grecaptcha.reset()});jQuery(document).trigger("frmFormErrors",[object,respo
28
  jQuery(response.content).find(".frm_message");if(formCompleted.length)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function removeAddedScripts(formContainer,formID){var endReplace=jQuery(".frm_end_ajax_"+formID);if(endReplace.length){formContainer.nextUntil(".frm_end_ajax_"+formID).remove();endReplace.remove()}}function maybeSlideOut(oldContent,newContent){var c,newClass="frm_slideout";if(newContent.indexOf(" frm_slide")!==
29
  -1){c=oldContent.children();if(newContent.indexOf(" frm_going_back")!==-1)newClass+=" frm_going_back";c.removeClass("frm_going_back");c.addClass(newClass);return 300}return 0}function addUrlParam(response){var url;if(history.pushState&&typeof response.page!=="undefined"){url=addQueryVar("frm_page",response.page);window.history.pushState({"html":response.html},"","?"+url)}}function addQueryVar(key,value){var kvp,i,x;key=encodeURI(key);value=encodeURI(value);kvp=document.location.search.substr(1).split("&");
30
  i=kvp.length;while(i--){x=kvp[i].split("=");if(x[0]==key){x[1]=value;kvp[i]=x.join("=");break}}if(i<0)kvp[kvp.length]=[key,value].join("=");return kvp.join("&")}function addFieldError($fieldCont,key,jsErrors){var input,id,describedBy;if($fieldCont.length&&$fieldCont.is(":visible")){$fieldCont.addClass("frm_blank_field");input=$fieldCont.find("input, select, textarea");id="frm_error_field_"+key;describedBy=input.attr("aria-describedby");if(typeof frmThemeOverride_frmPlaceError==="function")frmThemeOverride_frmPlaceError(key,
31
- jsErrors);else{if(-1!==jsErrors[key].indexOf("<div"))$fieldCont.append(jsErrors[key]);else $fieldCont.append('<div class="frm_error" id="'+id+'">'+jsErrors[key]+"</div>");if(typeof describedBy==="undefined")describedBy=id;else if(describedBy.indexOf(id)===-1)describedBy=describedBy+" "+id;input.attr("aria-describedby",describedBy)}input.attr("aria-invalid",true);jQuery(document).trigger("frmAddFieldError",[$fieldCont,key,jsErrors])}}function removeFieldError($fieldCont){var errorMessage=$fieldCont.find(".frm_error"),
32
- errorId=errorMessage.attr("id"),input=$fieldCont.find("input, select, textarea"),describedBy=input.attr("aria-describedby");$fieldCont.removeClass("frm_blank_field has-error");errorMessage.remove();input.attr("aria-invalid",false);if(typeof describedBy!=="undefined"){describedBy=describedBy.replace(errorId,"");input.attr("aria-describedby",describedBy)}}function removeAllErrors(){jQuery(".form-field").removeClass("frm_blank_field has-error");jQuery(".form-field .frm_error").replaceWith("");jQuery(".frm_error_style").remove()}
33
- function scrollToFirstField(object){var field=jQuery(object).find(".frm_blank_field").first();if(field.length)frmFrontForm.scrollMsg(field,object,true)}function showSubmitLoading($object){showLoadingIndicator($object);disableSubmitButton($object);disableSaveDraft($object)}function showLoadingIndicator($object){if(!$object.hasClass("frm_loading_form")&&!$object.hasClass("frm_loading_prev")){addLoadingClass($object);$object.trigger("frmStartFormLoading")}}function addLoadingClass($object){var loadingClass=
34
- isGoingToPrevPage($object)?"frm_loading_prev":"frm_loading_form";$object.addClass(loadingClass)}function isGoingToPrevPage($object){return typeof frmProForm!=="undefined"&&frmProForm.goingToPreviousPage($object)}function removeSubmitLoading($object,enable,processesRunning){var loadingForm;if(processesRunning>0)return;loadingForm=jQuery(".frm_loading_form");loadingForm.removeClass("frm_loading_form");loadingForm.removeClass("frm_loading_prev");loadingForm.trigger("frmEndFormLoading");if(enable==="enable"){enableSubmitButton(loadingForm);
35
- enableSaveDraft(loadingForm)}}function showFileLoading(object){var fileval,loading=document.getElementById("frm_loading");if(loading!==null){fileval=jQuery(object).find("input[type=file]").val();if(typeof fileval!=="undefined"&&fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}}function clearDefault(){toggleDefault(jQuery(this),"clear")}function replaceDefault(){toggleDefault(jQuery(this),"replace")}function toggleDefault($thisField,e){var thisVal,v=$thisField.data("frmval").replace(/(\n|\r\n)/g,
36
- "\r");if(v===""||typeof v==="undefined")return false;thisVal=$thisField.val().replace(/(\n|\r\n)/g,"\r");if("replace"===e){if(thisVal==="")$thisField.addClass("frm_default").val(v)}else if(thisVal==v)$thisField.removeClass("frm_default").val("")}function resendEmail(){var $link=jQuery(this),entryId=this.getAttribute("data-eid"),formId=this.getAttribute("data-fid"),label=$link.find(".frm_link_label");if(label.length<1)label=$link;label.append('<span class="frm-wait"></span>');jQuery.ajax({type:"POST",
37
- url:frm_js.ajax_url,data:{action:"frm_entries_send_email",entry_id:entryId,form_id:formId,nonce:frm_js.nonce},success:function(msg){var admin=document.getElementById("wpbody");if(admin===null)label.html(msg);else{label.html("");$link.after(msg)}}});return false}function confirmClick(){var message=jQuery(this).data("frmconfirm");return confirm(message)}function toggleDiv(){var div=jQuery(this).data("frmtoggle");if(jQuery(div).is(":visible"))jQuery(div).slideUp("fast");else jQuery(div).slideDown("fast");
38
- return false}function addIndexOfFallbackForIE8(){var len,from;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(elt){len=this.length>>>0;from=Number(arguments[1])||0;from=from<0?Math.ceil(from):Math.floor(from);if(from<0)from+=len;for(;from<len;from++)if(from in this&&this[from]===elt)return from;return-1}}function addTrimFallbackForIE8(){if(typeof String.prototype.trim!=="function")String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}}function addFilterFallbackForIE8(){var t,
39
- len,res,thisp,i,val;if(!Array.prototype.filter)Array.prototype.filter=function(fun){if(this===void 0||this===null)throw new TypeError;t=Object(this);len=t.length>>>0;if(typeof fun!=="function")throw new TypeError;res=[];thisp=arguments[1];for(i=0;i<len;i++)if(i in t){val=t[i];if(fun.call(thisp,val,i,t))res.push(val)}return res}}function addKeysFallbackForIE8(){var keys,i;if(!Object.keys)Object.keys=function(obj){keys=[];for(i in obj)if(obj.hasOwnProperty(i))keys.push(i);return keys}}function onHoneypotFieldChange(){var css=
40
- jQuery(this).css("box-shadow");if(css.match(/inset/))this.parentNode.removeChild(this)}function changeFocusWhenClickComboFieldLabel(){var label;var comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");if(!label)return;label.addEventListener("click",function(e){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})}
41
- function checkForErrorsAndMaybeSetFocus(){var errors,element,timeoutCallback;errors=document.querySelectorAll(".frm_form_field .frm_error");if(!errors.length)return;element=errors[0];do{element=element.previousSibling;if(-1!==["input","select","textarea"].indexOf(element.nodeName.toLowerCase())){element.focus();break}if("undefined"!==typeof element.classList){if(element.classList.contains("html-active"))timeoutCallback=function(){var textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};
42
- else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()};if("function"===typeof timeoutCallback){setTimeout(timeoutCallback,0);break}}}while(element.previousSibling)}return{init:function(){jQuery(document).off("submit.formidable",".frm-show-form");jQuery(document).on("submit.formidable",".frm-show-form",frmFrontForm.submitForm);jQuery(".frm-show-form input[onblur], .frm-show-form textarea[onblur]").each(function(){if(jQuery(this).val()==="")jQuery(this).trigger("blur")});
43
- jQuery(document).on("focus",".frm_toggle_default",clearDefault);jQuery(document).on("blur",".frm_toggle_default",replaceDefault);jQuery(".frm_toggle_default").trigger("blur");jQuery(document.getElementById("frm_resend_email")).on("click",resendEmail);jQuery(document).on("change",'.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]',frmFrontForm.fieldValueChanged);jQuery(document).on("change keyup",".frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea",
44
- maybeShowLabel);jQuery(document).on("change","[id^=frm_email_]",onHoneypotFieldChange);jQuery(document).on("click","a[data-frmconfirm]",confirmClick);jQuery("a[data-frmtoggle]").on("click",toggleDiv);checkForErrorsAndMaybeSetFocus();changeFocusWhenClickComboFieldLabel();addIndexOfFallbackForIE8();addTrimFallbackForIE8();addFilterFallbackForIE8();addKeysFallbackForIE8()},getFieldId:function(field,fullID){return getFieldId(field,fullID)},renderRecaptcha:function(captcha){var formID,recaptchaID,size=
45
- captcha.getAttribute("data-size"),rendered=captcha.getAttribute("data-rid")!==null,params={"sitekey":captcha.getAttribute("data-sitekey"),"size":size,"theme":captcha.getAttribute("data-theme")};if(rendered)return;if(size==="invisible"){formID=jQuery(captcha).closest("form").find('input[name="form_id"]').val();jQuery(captcha).closest(".frm_form_field .frm_primary_label").hide();params.callback=function(token){frmFrontForm.afterRecaptcha(token,formID)}}recaptchaID=grecaptcha.render(captcha.id,params);
46
- captcha.setAttribute("data-rid",recaptchaID)},afterSingleRecaptcha:function(){var object=jQuery(".frm-show-form .g-recaptcha").closest("form")[0];frmFrontForm.submitFormNow(object)},afterRecaptcha:function(token,formID){var object=jQuery("#frm_form_"+formID+"_container form")[0];frmFrontForm.submitFormNow(object)},submitForm:function(e){frmFrontForm.submitFormManual(e,this)},submitFormManual:function(e,object){var isPro,errors,invisibleRecaptcha=hasInvisibleRecaptcha(object),classList=object.className.trim().split(/\s+/gi);
47
- if(classList&&invisibleRecaptcha.length<1){isPro=classList.indexOf("frm_pro_form")>-1;if(!isPro)return}if(jQuery("body").hasClass("wp-admin")&&jQuery(object).closest(".frmapi-form").length<1)return;e.preventDefault();if(typeof frmProForm!=="undefined"&&typeof frmProForm.submitAllowed==="function")if(!frmProForm.submitAllowed(object))return;if(invisibleRecaptcha.length){showLoadingIndicator(jQuery(object));executeInvisibleRecaptcha(invisibleRecaptcha)}else{errors=frmFrontForm.validateFormSubmit(object);
48
- if(Object.keys(errors).length===0){showSubmitLoading(jQuery(object));frmFrontForm.submitFormNow(object,classList)}}},submitFormNow:function(object){var hasFileFields,antispamInput,classList=object.className.trim().split(/\s+/gi);if(object.hasAttribute("data-token")&&null===object.querySelector('[name="antispam_token"]')){antispamInput=document.createElement("input");antispamInput.type="hidden";antispamInput.name="antispam_token";antispamInput.value=object.getAttribute("data-token");object.appendChild(antispamInput)}if(classList.indexOf("frm_ajax_submit")>
49
- -1){hasFileFields=jQuery(object).find('input[type="file"]').filter(function(){return!!this.value}).length;if(hasFileFields<1){action=jQuery(object).find('input[name="frm_action"]').val();frmFrontForm.checkFormErrors(object,action)}else object.submit()}else object.submit()},validateFormSubmit:function(object){if(typeof tinyMCE!=="undefined"&&jQuery(object).find(".wp-editor-wrap").length)tinyMCE.triggerSave();jsErrors=[];if(shouldJSValidate(object)){frmFrontForm.getAjaxFormErrors(object);if(Object.keys(jsErrors).length)frmFrontForm.addAjaxFormErrors(object)}return jsErrors},
50
- getAjaxFormErrors:function(object){var customErrors,key;jsErrors=validateForm(object);if(typeof frmThemeOverride_jsErrors==="function"){action=jQuery(object).find('input[name="frm_action"]').val();customErrors=frmThemeOverride_jsErrors(action,object);if(Object.keys(customErrors).length)for(key in customErrors)jsErrors[key]=customErrors[key]}return jsErrors},addAjaxFormErrors:function(object){var key,$fieldCont;removeAllErrors();for(key in jsErrors){$fieldCont=jQuery(object).find("#frm_field_"+key+
51
- "_container");if($fieldCont.length)addFieldError($fieldCont,key,jsErrors);else delete jsErrors[key]}scrollToFirstField(object);checkForErrorsAndMaybeSetFocus()},checkFormErrors:function(object,action){getFormErrors(object,action)},checkRequiredField:function(field,errors){return checkRequiredField(field,errors)},showSubmitLoading:function($object){showSubmitLoading($object)},removeSubmitLoading:function($object,enable,processesRunning){removeSubmitLoading($object,enable,processesRunning)},scrollToID:function(id){var object=
52
- jQuery(document.getElementById(id));frmFrontForm.scrollMsg(object,false)},scrollMsg:function(id,object,animate){var newPos,m,b,screenTop,screenBottom,scrollObj="";if(typeof object==="undefined"){scrollObj=jQuery(document.getElementById("frm_form_"+id+"_container"));if(scrollObj.length<1)return}else if(typeof id==="string")scrollObj=jQuery(object).find("#frm_field_"+id+"_container");else scrollObj=id;jQuery(scrollObj).trigger("focus");newPos=scrollObj.offset().top;if(!newPos||frm_js.offset==="-1")return;
53
- newPos=newPos-frm_js.offset;m=jQuery("html").css("margin-top");b=jQuery("body").css("margin-top");if(m||b)newPos=newPos-parseInt(m)-parseInt(b);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos<screenTop){if(typeof animate==="undefined")jQuery(window).scrollTop(newPos);else jQuery("html,body").animate({scrollTop:newPos},500);return false}}},fieldValueChanged:function(e){var fieldId=
54
- frmFrontForm.getFieldId(this,false);if(!fieldId||typeof fieldId==="undefined")return;if(e.frmTriggered&&e.frmTriggered==fieldId)return;jQuery(document).trigger("frmFieldChanged",[this,fieldId,e]);if(e.selfTriggered!==true)maybeValidateChange(this)},savingDraft:function(object){console.warn("DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft");if(typeof frmProForm!=="undefined")return frmProForm.savingDraft(object)},goingToPreviousPage:function(object){console.warn("DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage");
 
55
  if(typeof frmProForm!=="undefined")return frmProForm.goingToPreviousPage(object)},hideOrShowFields:function(){console.warn("DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields");if(typeof frmProForm!=="undefined")frmProForm.hideOrShowFields()},hidePreviouslyHiddenFields:function(){console.warn("DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields");if(typeof frmProForm!=="undefined")frmProForm.hidePreviouslyHiddenFields()},
56
  checkDependentDynamicFields:function(ids){console.warn("DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields");if(typeof frmProForm!=="undefined")frmProForm.checkDependentDynamicFields(ids)},checkDependentLookupFields:function(ids){console.warn("DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields");if(typeof frmProForm!=="undefined")frmProForm.checkDependentLookupFields(ids)},loadGoogle:function(){console.warn("DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle");
57
  frmProForm.loadGoogle()},escapeHtml:function(text){return text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")},invisible:function(classes){jQuery(classes).css("visibility","hidden")},visible:function(classes){jQuery(classes).css("visibility","visible")}}}frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});
28
  jQuery(response.content).find(".frm_message");if(formCompleted.length)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function removeAddedScripts(formContainer,formID){var endReplace=jQuery(".frm_end_ajax_"+formID);if(endReplace.length){formContainer.nextUntil(".frm_end_ajax_"+formID).remove();endReplace.remove()}}function maybeSlideOut(oldContent,newContent){var c,newClass="frm_slideout";if(newContent.indexOf(" frm_slide")!==
29
  -1){c=oldContent.children();if(newContent.indexOf(" frm_going_back")!==-1)newClass+=" frm_going_back";c.removeClass("frm_going_back");c.addClass(newClass);return 300}return 0}function addUrlParam(response){var url;if(history.pushState&&typeof response.page!=="undefined"){url=addQueryVar("frm_page",response.page);window.history.pushState({"html":response.html},"","?"+url)}}function addQueryVar(key,value){var kvp,i,x;key=encodeURI(key);value=encodeURI(value);kvp=document.location.search.substr(1).split("&");
30
  i=kvp.length;while(i--){x=kvp[i].split("=");if(x[0]==key){x[1]=value;kvp[i]=x.join("=");break}}if(i<0)kvp[kvp.length]=[key,value].join("=");return kvp.join("&")}function addFieldError($fieldCont,key,jsErrors){var input,id,describedBy;if($fieldCont.length&&$fieldCont.is(":visible")){$fieldCont.addClass("frm_blank_field");input=$fieldCont.find("input, select, textarea");id="frm_error_field_"+key;describedBy=input.attr("aria-describedby");if(typeof frmThemeOverride_frmPlaceError==="function")frmThemeOverride_frmPlaceError(key,
31
+ jsErrors);else{if(-1!==jsErrors[key].indexOf("<div"))$fieldCont.append(jsErrors[key]);else $fieldCont.append('<div class="frm_error" role="alert" id="'+id+'">'+jsErrors[key]+"</div>");if(typeof describedBy==="undefined")describedBy=id;else if(describedBy.indexOf(id)===-1)describedBy=describedBy+" "+id;input.attr("aria-describedby",describedBy)}input.attr("aria-invalid",true);input.attr("aria-describedby",id);jQuery(document).trigger("frmAddFieldError",[$fieldCont,key,jsErrors])}}function removeFieldError($fieldCont){var errorMessage=
32
+ $fieldCont.find(".frm_error"),errorId=errorMessage.attr("id"),input=$fieldCont.find("input, select, textarea"),describedBy=input.attr("aria-describedby");$fieldCont.removeClass("frm_blank_field has-error");errorMessage.remove();input.attr("aria-invalid",false);input.removeAttr("aria-describedby");if(typeof describedBy!=="undefined"){describedBy=describedBy.replace(errorId,"");input.attr("aria-describedby",describedBy)}}function removeAllErrors(){jQuery(".form-field").removeClass("frm_blank_field has-error");
33
+ jQuery(".form-field .frm_error").replaceWith("");jQuery(".frm_error_style").remove()}function scrollToFirstField(object){var field=jQuery(object).find(".frm_blank_field").first();if(field.length)frmFrontForm.scrollMsg(field,object,true)}function showSubmitLoading($object){showLoadingIndicator($object);disableSubmitButton($object);disableSaveDraft($object)}function showLoadingIndicator($object){if(!$object.hasClass("frm_loading_form")&&!$object.hasClass("frm_loading_prev")){addLoadingClass($object);
34
+ $object.trigger("frmStartFormLoading")}}function addLoadingClass($object){var loadingClass=isGoingToPrevPage($object)?"frm_loading_prev":"frm_loading_form";$object.addClass(loadingClass)}function isGoingToPrevPage($object){return typeof frmProForm!=="undefined"&&frmProForm.goingToPreviousPage($object)}function removeSubmitLoading($object,enable,processesRunning){var loadingForm;if(processesRunning>0)return;loadingForm=jQuery(".frm_loading_form");loadingForm.removeClass("frm_loading_form");loadingForm.removeClass("frm_loading_prev");
35
+ loadingForm.trigger("frmEndFormLoading");if(enable==="enable"){enableSubmitButton(loadingForm);enableSaveDraft(loadingForm)}}function showFileLoading(object){var fileval,loading=document.getElementById("frm_loading");if(loading!==null){fileval=jQuery(object).find("input[type=file]").val();if(typeof fileval!=="undefined"&&fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}}function clearDefault(){toggleDefault(jQuery(this),"clear")}function replaceDefault(){toggleDefault(jQuery(this),
36
+ "replace")}function toggleDefault($thisField,e){var thisVal,v=$thisField.data("frmval").replace(/(\n|\r\n)/g,"\r");if(v===""||typeof v==="undefined")return false;thisVal=$thisField.val().replace(/(\n|\r\n)/g,"\r");if("replace"===e){if(thisVal==="")$thisField.addClass("frm_default").val(v)}else if(thisVal==v)$thisField.removeClass("frm_default").val("")}function resendEmail(){var $link=jQuery(this),entryId=this.getAttribute("data-eid"),formId=this.getAttribute("data-fid"),label=$link.find(".frm_link_label");
37
+ if(label.length<1)label=$link;label.append('<span class="frm-wait"></span>');jQuery.ajax({type:"POST",url:frm_js.ajax_url,data:{action:"frm_entries_send_email",entry_id:entryId,form_id:formId,nonce:frm_js.nonce},success:function(msg){var admin=document.getElementById("wpbody");if(admin===null)label.html(msg);else{label.html("");$link.after(msg)}}});return false}function confirmClick(){var message=jQuery(this).data("frmconfirm");return confirm(message)}function toggleDiv(){var div=jQuery(this).data("frmtoggle");
38
+ if(jQuery(div).is(":visible"))jQuery(div).slideUp("fast");else jQuery(div).slideDown("fast");return false}function addIndexOfFallbackForIE8(){var len,from;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(elt){len=this.length>>>0;from=Number(arguments[1])||0;from=from<0?Math.ceil(from):Math.floor(from);if(from<0)from+=len;for(;from<len;from++)if(from in this&&this[from]===elt)return from;return-1}}function addTrimFallbackForIE8(){if(typeof String.prototype.trim!=="function")String.prototype.trim=
39
+ function(){return this.replace(/^\s+|\s+$/g,"")}}function addFilterFallbackForIE8(){var t,len,res,thisp,i,val;if(!Array.prototype.filter)Array.prototype.filter=function(fun){if(this===void 0||this===null)throw new TypeError;t=Object(this);len=t.length>>>0;if(typeof fun!=="function")throw new TypeError;res=[];thisp=arguments[1];for(i=0;i<len;i++)if(i in t){val=t[i];if(fun.call(thisp,val,i,t))res.push(val)}return res}}function addKeysFallbackForIE8(){var keys,i;if(!Object.keys)Object.keys=function(obj){keys=
40
+ [];for(i in obj)if(obj.hasOwnProperty(i))keys.push(i);return keys}}function onHoneypotFieldChange(){var css=jQuery(this).css("box-shadow");if(css.match(/inset/))this.parentNode.removeChild(this)}function changeFocusWhenClickComboFieldLabel(){var label;var comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");
41
+ if(!label)return;label.addEventListener("click",function(e){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})}function checkForErrorsAndMaybeSetFocus(){var errors,element,timeoutCallback;errors=document.querySelectorAll(".frm_form_field .frm_error");if(!errors.length)return;element=errors[0];do{element=element.previousSibling;if(-1!==["input","select","textarea"].indexOf(element.nodeName.toLowerCase())){element.focus();
42
+ break}if("undefined"!==typeof element.classList){if(element.classList.contains("html-active"))timeoutCallback=function(){var textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()};if("function"===typeof timeoutCallback){setTimeout(timeoutCallback,0);break}}}while(element.previousSibling)}return{init:function(){jQuery(document).off("submit.formidable",".frm-show-form");
43
+ jQuery(document).on("submit.formidable",".frm-show-form",frmFrontForm.submitForm);jQuery(".frm-show-form input[onblur], .frm-show-form textarea[onblur]").each(function(){if(jQuery(this).val()==="")jQuery(this).trigger("blur")});jQuery(document).on("focus",".frm_toggle_default",clearDefault);jQuery(document).on("blur",".frm_toggle_default",replaceDefault);jQuery(".frm_toggle_default").trigger("blur");jQuery(document.getElementById("frm_resend_email")).on("click",resendEmail);jQuery(document).on("change",
44
+ '.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]',frmFrontForm.fieldValueChanged);jQuery(document).on("change keyup",".frm-show-form .frm_inside_container input, .frm-show-form .frm_inside_container select, .frm-show-form .frm_inside_container textarea",maybeShowLabel);jQuery(document).on("change","[id^=frm_email_]",onHoneypotFieldChange);jQuery(document).on("click","a[data-frmconfirm]",confirmClick);jQuery("a[data-frmtoggle]").on("click",
45
+ toggleDiv);checkForErrorsAndMaybeSetFocus();changeFocusWhenClickComboFieldLabel();addIndexOfFallbackForIE8();addTrimFallbackForIE8();addFilterFallbackForIE8();addKeysFallbackForIE8()},getFieldId:function(field,fullID){return getFieldId(field,fullID)},renderRecaptcha:function(captcha){var formID,recaptchaID,size=captcha.getAttribute("data-size"),rendered=captcha.getAttribute("data-rid")!==null,params={"sitekey":captcha.getAttribute("data-sitekey"),"size":size,"theme":captcha.getAttribute("data-theme")};
46
+ if(rendered)return;if(size==="invisible"){formID=jQuery(captcha).closest("form").find('input[name="form_id"]').val();jQuery(captcha).closest(".frm_form_field .frm_primary_label").hide();params.callback=function(token){frmFrontForm.afterRecaptcha(token,formID)}}recaptchaID=grecaptcha.render(captcha.id,params);captcha.setAttribute("data-rid",recaptchaID)},afterSingleRecaptcha:function(){var object=jQuery(".frm-show-form .g-recaptcha").closest("form")[0];frmFrontForm.submitFormNow(object)},afterRecaptcha:function(token,
47
+ formID){var object=jQuery("#frm_form_"+formID+"_container form")[0];frmFrontForm.submitFormNow(object)},submitForm:function(e){frmFrontForm.submitFormManual(e,this)},submitFormManual:function(e,object){var isPro,errors,invisibleRecaptcha=hasInvisibleRecaptcha(object),classList=object.className.trim().split(/\s+/gi);if(classList&&invisibleRecaptcha.length<1){isPro=classList.indexOf("frm_pro_form")>-1;if(!isPro)return}if(jQuery("body").hasClass("wp-admin")&&jQuery(object).closest(".frmapi-form").length<
48
+ 1)return;e.preventDefault();if(typeof frmProForm!=="undefined"&&typeof frmProForm.submitAllowed==="function")if(!frmProForm.submitAllowed(object))return;if(invisibleRecaptcha.length){showLoadingIndicator(jQuery(object));executeInvisibleRecaptcha(invisibleRecaptcha)}else{errors=frmFrontForm.validateFormSubmit(object);if(Object.keys(errors).length===0){showSubmitLoading(jQuery(object));frmFrontForm.submitFormNow(object,classList)}}},submitFormNow:function(object){var hasFileFields,antispamInput,classList=
49
+ object.className.trim().split(/\s+/gi);if(object.hasAttribute("data-token")&&null===object.querySelector('[name="antispam_token"]')){antispamInput=document.createElement("input");antispamInput.type="hidden";antispamInput.name="antispam_token";antispamInput.value=object.getAttribute("data-token");object.appendChild(antispamInput)}if(classList.indexOf("frm_ajax_submit")>-1){hasFileFields=jQuery(object).find('input[type="file"]').filter(function(){return!!this.value}).length;if(hasFileFields<1){action=
50
+ jQuery(object).find('input[name="frm_action"]').val();frmFrontForm.checkFormErrors(object,action)}else object.submit()}else object.submit()},validateFormSubmit:function(object){if(typeof tinyMCE!=="undefined"&&jQuery(object).find(".wp-editor-wrap").length)tinyMCE.triggerSave();jsErrors=[];if(shouldJSValidate(object)){frmFrontForm.getAjaxFormErrors(object);if(Object.keys(jsErrors).length)frmFrontForm.addAjaxFormErrors(object)}return jsErrors},getAjaxFormErrors:function(object){var customErrors,key;
51
+ jsErrors=validateForm(object);if(typeof frmThemeOverride_jsErrors==="function"){action=jQuery(object).find('input[name="frm_action"]').val();customErrors=frmThemeOverride_jsErrors(action,object);if(Object.keys(customErrors).length)for(key in customErrors)jsErrors[key]=customErrors[key]}return jsErrors},addAjaxFormErrors:function(object){var key,$fieldCont;removeAllErrors();for(key in jsErrors){$fieldCont=jQuery(object).find("#frm_field_"+key+"_container");if($fieldCont.length)addFieldError($fieldCont,
52
+ key,jsErrors);else delete jsErrors[key]}scrollToFirstField(object);checkForErrorsAndMaybeSetFocus()},checkFormErrors:function(object,action){getFormErrors(object,action)},checkRequiredField:function(field,errors){return checkRequiredField(field,errors)},showSubmitLoading:function($object){showSubmitLoading($object)},removeSubmitLoading:function($object,enable,processesRunning){removeSubmitLoading($object,enable,processesRunning)},scrollToID:function(id){var object=jQuery(document.getElementById(id));
53
+ frmFrontForm.scrollMsg(object,false)},scrollMsg:function(id,object,animate){var newPos,m,b,screenTop,screenBottom,scrollObj="";if(typeof object==="undefined"){scrollObj=jQuery(document.getElementById("frm_form_"+id+"_container"));if(scrollObj.length<1)return}else if(typeof id==="string")scrollObj=jQuery(object).find("#frm_field_"+id+"_container");else scrollObj=id;jQuery(scrollObj).trigger("focus");newPos=scrollObj.offset().top;if(!newPos||frm_js.offset==="-1")return;newPos=newPos-frm_js.offset;m=
54
+ jQuery("html").css("margin-top");b=jQuery("body").css("margin-top");if(m||b)newPos=newPos-parseInt(m)-parseInt(b);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos<screenTop){if(typeof animate==="undefined")jQuery(window).scrollTop(newPos);else jQuery("html,body").animate({scrollTop:newPos},500);return false}}},fieldValueChanged:function(e){var fieldId=frmFrontForm.getFieldId(this,
55
+ false);if(!fieldId||typeof fieldId==="undefined")return;if(e.frmTriggered&&e.frmTriggered==fieldId)return;jQuery(document).trigger("frmFieldChanged",[this,fieldId,e]);if(e.selfTriggered!==true)maybeValidateChange(this)},savingDraft:function(object){console.warn("DEPRECATED: function frmFrontForm.savingDraft in v3.0 use frmProForm.savingDraft");if(typeof frmProForm!=="undefined")return frmProForm.savingDraft(object)},goingToPreviousPage:function(object){console.warn("DEPRECATED: function frmFrontForm.goingToPreviousPage in v3.0 use frmProForm.goingToPreviousPage");
56
  if(typeof frmProForm!=="undefined")return frmProForm.goingToPreviousPage(object)},hideOrShowFields:function(){console.warn("DEPRECATED: function frmFrontForm.hideOrShowFields in v3.0 use frmProForm.hideOrShowFields");if(typeof frmProForm!=="undefined")frmProForm.hideOrShowFields()},hidePreviouslyHiddenFields:function(){console.warn("DEPRECATED: function frmFrontForm.hidePreviouslyHiddenFields in v3.0 use frmProForm.hidePreviouslyHiddenFields");if(typeof frmProForm!=="undefined")frmProForm.hidePreviouslyHiddenFields()},
57
  checkDependentDynamicFields:function(ids){console.warn("DEPRECATED: function frmFrontForm.checkDependentDynamicFields in v3.0 use frmProForm.checkDependentDynamicFields");if(typeof frmProForm!=="undefined")frmProForm.checkDependentDynamicFields(ids)},checkDependentLookupFields:function(ids){console.warn("DEPRECATED: function frmFrontForm.checkDependentLookupFields in v3.0 use frmProForm.checkDependentLookupFields");if(typeof frmProForm!=="undefined")frmProForm.checkDependentLookupFields(ids)},loadGoogle:function(){console.warn("DEPRECATED: function frmFrontForm.loadGoogle in v3.0 use frmProForm.loadGoogle");
58
  frmProForm.loadGoogle()},escapeHtml:function(text){return text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")},invisible:function(classes){jQuery(classes).css("visibility","hidden")},visible:function(classes){jQuery(classes).css("visibility","visible")}}}frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});
js/formidable_admin.js CHANGED
@@ -5401,7 +5401,7 @@ function frmAdminBuildJS() {
5401
 
5402
  function onEveryoneOptionSelected( $select ) {
5403
  $select.val( '' );
5404
- $select.next( '.btn-group' ).find( '.multiselect-container li input[value!=""]' ).prop( 'checked', false );
5405
  }
5406
 
5407
  function unselectEveryoneOptionIfSelected( $select ) {
@@ -5409,7 +5409,7 @@ function frmAdminBuildJS() {
5409
  index;
5410
 
5411
  if ( selectedValues === null ) {
5412
- $select.next( '.btn-group' ).find( '.multiselect-container li input[value=""]' ).prop( 'checked', true );
5413
  onEveryoneOptionSelected( $select );
5414
  return;
5415
  }
@@ -5418,7 +5418,7 @@ function frmAdminBuildJS() {
5418
  if ( index >= 0 ) {
5419
  selectedValues.splice( index, 1 );
5420
  $select.val( selectedValues );
5421
- $select.next( '.btn-group' ).find( '.multiselect-container li input[value=""]' ).prop( 'checked', false );
5422
  }
5423
  }
5424
 
@@ -7609,9 +7609,9 @@ function frmAdminBuildJS() {
7609
  labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : '';
7610
  $select.multiselect({
7611
  templates: {
7612
- ul: '<ul class="multiselect-container frm-dropdown-menu"></ul>',
7613
- li: '<li><a tabindex="0"><label></label></a></li>',
7614
- button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown" aria-describedby="frm_multiselect_button" ' + labelledBy + '><span class="multiselect-selected-text"></span> <b class="caret"></b></button>'
7615
  },
7616
  buttonContainer: '<div class="btn-group frm-btn-group dropdown" />',
7617
  nonSelectedText: '',
@@ -8653,6 +8653,33 @@ function frmAdminBuildJS() {
8653
  w.off( 'beforeunload.edit-post' );
8654
  }
8655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8656
  function maybeChangeEmbedFormMsg() {
8657
  var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
8658
  var fieldItem = document.getElementById( 'frm_field_id_' + fieldId );
@@ -8983,6 +9010,8 @@ function frmAdminBuildJS() {
8983
 
8984
  // prevent annoying confirmation message from WordPress
8985
  jQuery( 'button, input[type=submit]' ).on( 'click', removeWPUnload );
 
 
8986
  },
8987
 
8988
  buildInit: function() {
5401
 
5402
  function onEveryoneOptionSelected( $select ) {
5403
  $select.val( '' );
5404
+ $select.next( '.btn-group' ).find( '.multiselect-container input[value!=""]' ).prop( 'checked', false );
5405
  }
5406
 
5407
  function unselectEveryoneOptionIfSelected( $select ) {
5409
  index;
5410
 
5411
  if ( selectedValues === null ) {
5412
+ $select.next( '.btn-group' ).find( '.multiselect-container input[value=""]' ).prop( 'checked', true );
5413
  onEveryoneOptionSelected( $select );
5414
  return;
5415
  }
5418
  if ( index >= 0 ) {
5419
  selectedValues.splice( index, 1 );
5420
  $select.val( selectedValues );
5421
+ $select.next( '.btn-group' ).find( '.multiselect-container input[value=""]' ).prop( 'checked', false );
5422
  }
5423
  }
5424
 
7609
  labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : '';
7610
  $select.multiselect({
7611
  templates: {
7612
+ popupContainer: '<div class="multiselect-container frm-dropdown-menu"></div>',
7613
+ option: '<button type="button" class="multiselect-option dropdown-item frm_no_style_button"></button>',
7614
+ button: '<button type="button" class="multiselect dropdown-toggle btn" data-toggle="dropdown" ' + labelledBy + '><span class="multiselect-selected-text"></span> <b class="caret"></b></button>'
7615
  },
7616
  buttonContainer: '<div class="btn-group frm-btn-group dropdown" />',
7617
  nonSelectedText: '',
8653
  w.off( 'beforeunload.edit-post' );
8654
  }
8655
 
8656
+ function addMultiselectLabelListener() {
8657
+ const clickListener = ( e ) => {
8658
+ if ( 'LABEL' !== e.target.nodeName ) {
8659
+ return;
8660
+ }
8661
+
8662
+ const labelFor = e.target.getAttribute( 'for' );
8663
+ if ( ! labelFor ) {
8664
+ return;
8665
+ }
8666
+
8667
+ const input = document.getElementById( labelFor );
8668
+ if ( ! input || ! input.nextElementSibling ) {
8669
+ return;
8670
+ }
8671
+
8672
+ const buttonToggle = input.nextElementSibling.querySelector( 'button.dropdown-toggle.multiselect' );
8673
+ if ( ! buttonToggle ) {
8674
+ return;
8675
+ }
8676
+
8677
+ const triggerMultiselectClick = () => buttonToggle.click();
8678
+ setTimeout( triggerMultiselectClick, 0 );
8679
+ };
8680
+ document.addEventListener( 'click', clickListener );
8681
+ }
8682
+
8683
  function maybeChangeEmbedFormMsg() {
8684
  var fieldId = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' );
8685
  var fieldItem = document.getElementById( 'frm_field_id_' + fieldId );
9010
 
9011
  // prevent annoying confirmation message from WordPress
9012
  jQuery( 'button, input[type=submit]' ).on( 'click', removeWPUnload );
9013
+
9014
+ addMultiselectLabelListener();
9015
  },
9016
 
9017
  buildInit: function() {
languages/formidable.pot CHANGED
@@ -2,14 +2,14 @@
2
  # This file is distributed under the same license as the Formidable Forms plugin.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Formidable Forms 5.0.17\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/formidable\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "POT-Creation-Date: 2022-01-13T12:59:24+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: formidable\n"
@@ -1648,19 +1648,19 @@ msgstr ""
1648
  msgid "Vietnamese"
1649
  msgstr ""
1650
 
1651
- #: classes/helpers/FrmAppHelper.php:3238
1652
  msgid "Form Landing Pages"
1653
  msgstr ""
1654
 
1655
- #: classes/helpers/FrmAppHelper.php:3239
1656
  msgid "Easily manage a landing page for your form. Upgrade to get form landing pages."
1657
  msgstr ""
1658
 
1659
- #: classes/helpers/FrmAppHelper.php:3326
1660
  msgid "Your account has expired"
1661
  msgstr ""
1662
 
1663
- #: classes/helpers/FrmAppHelper.php:3329
1664
  msgid "Renew Now"
1665
  msgstr ""
1666
 
@@ -1773,16 +1773,16 @@ msgstr ""
1773
  msgid "Yes"
1774
  msgstr ""
1775
 
1776
- #: classes/helpers/FrmEntriesListHelper.php:304
1777
  #: classes/views/frm-entries/sidebar-shared.php:43
1778
  msgid "View"
1779
  msgstr ""
1780
 
1781
- #: classes/helpers/FrmEntriesListHelper.php:309
1782
  msgid "Permanently delete this entry?"
1783
  msgstr ""
1784
 
1785
- #: classes/helpers/FrmEntriesListHelper.php:309
1786
  #: classes/helpers/FrmFormsHelper.php:1166
1787
  #: classes/helpers/FrmFormsListHelper.php:133
1788
  #: classes/views/frm-form-actions/form_action.php:25
@@ -1790,7 +1790,7 @@ msgstr ""
1790
  msgid "Delete"
1791
  msgstr ""
1792
 
1793
- #: classes/helpers/FrmEntriesListHelper.php:358
1794
  msgid "ALL selected entries in this form will be permanently deleted. Want to proceed?"
1795
  msgstr ""
1796
 
@@ -5074,18 +5074,6 @@ msgstr ""
5074
  msgid "Templates"
5075
  msgstr ""
5076
 
5077
- #: classes/views/frm-forms/multiselect-accessibility.php:7
5078
- msgid "Checked. To uncheck this option, press Space or Enter"
5079
- msgstr ""
5080
-
5081
- #: classes/views/frm-forms/multiselect-accessibility.php:8
5082
- msgid "Unchecked. To check this option, press Space or Enter"
5083
- msgstr ""
5084
-
5085
- #: classes/views/frm-forms/multiselect-accessibility.php:9
5086
- msgid "You are on a Custom List of Checkboxes. To open, press Enter. Use Up and Down arrow keys to switch between options"
5087
- msgstr ""
5088
-
5089
  #: classes/views/frm-forms/new-form-overlay.php:11
5090
  msgid "Back"
5091
  msgstr ""
2
  # This file is distributed under the same license as the Formidable Forms plugin.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Formidable Forms 5.1\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/formidable\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "POT-Creation-Date: 2022-01-27T13:54:08+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: formidable\n"
1648
  msgid "Vietnamese"
1649
  msgstr ""
1650
 
1651
+ #: classes/helpers/FrmAppHelper.php:3240
1652
  msgid "Form Landing Pages"
1653
  msgstr ""
1654
 
1655
+ #: classes/helpers/FrmAppHelper.php:3241
1656
  msgid "Easily manage a landing page for your form. Upgrade to get form landing pages."
1657
  msgstr ""
1658
 
1659
+ #: classes/helpers/FrmAppHelper.php:3328
1660
  msgid "Your account has expired"
1661
  msgstr ""
1662
 
1663
+ #: classes/helpers/FrmAppHelper.php:3331
1664
  msgid "Renew Now"
1665
  msgstr ""
1666
 
1773
  msgid "Yes"
1774
  msgstr ""
1775
 
1776
+ #: classes/helpers/FrmEntriesListHelper.php:314
1777
  #: classes/views/frm-entries/sidebar-shared.php:43
1778
  msgid "View"
1779
  msgstr ""
1780
 
1781
+ #: classes/helpers/FrmEntriesListHelper.php:319
1782
  msgid "Permanently delete this entry?"
1783
  msgstr ""
1784
 
1785
+ #: classes/helpers/FrmEntriesListHelper.php:319
1786
  #: classes/helpers/FrmFormsHelper.php:1166
1787
  #: classes/helpers/FrmFormsListHelper.php:133
1788
  #: classes/views/frm-form-actions/form_action.php:25
1790
  msgid "Delete"
1791
  msgstr ""
1792
 
1793
+ #: classes/helpers/FrmEntriesListHelper.php:368
1794
  msgid "ALL selected entries in this form will be permanently deleted. Want to proceed?"
1795
  msgstr ""
1796
 
5074
  msgid "Templates"
5075
  msgstr ""
5076
 
 
 
 
 
 
 
 
 
 
 
 
 
5077
  #: classes/views/frm-forms/new-form-overlay.php:11
5078
  msgid "Back"
5079
  msgstr ""
readme.txt CHANGED
@@ -3,9 +3,9 @@ Plugin Name: Formidable Forms - Contact Form, Survey & Quiz Forms Plugin for Wor
3
  Contributors: formidableforms, sswells, srwells
4
  Tags: forms, contact form, form builder, survey, free, form maker, form creator, paypal form, paypal, stripe, stripe form, aweber, aweber form, getresponse, getresponse form, calculator, price calculator, quote form, contact button, form manager, Akismet, payment form, survey form, donation form, email subscription, contact form widget, user registration form, wordpress registration, wordpress login form, constant contact, mailpoet, active campaign, salesforce, hubspot, campaign monitor, quiz builder, quiz, feedback form, mailchimp form
5
  Requires at least: 5.0
6
- Tested up to: 5.8.3
7
  Requires PHP: 5.6
8
- Stable tag: 5.0.17
9
 
10
  The most advanced WordPress forms plugin. Go beyond contact forms with our drag & drop form builder for surveys, quizzes, and more.
11
 
@@ -438,6 +438,12 @@ Using our Zapier integration, you can easily connect Formidable with over 1000+
438
  See all <a href="https://zapier.com/apps/formidable/integrations">Formidable Zapier Integrations</a>.
439
 
440
  == Changelog ==
 
 
 
 
 
 
441
  = 5.0.17 =
442
  - The embedded CodeMirror code for compatibility with versions of WordPress before 4.9 has been removed.
443
  - New: The ctype PHP extension is no longer a requirement.
@@ -456,13 +462,4 @@ See all <a href="https://zapier.com/apps/formidable/integrations">Formidable Zap
456
  - Fix: Radio buttons and checkboxes were appearing overlapped with labels when using the H-Code theme.
457
  - Fix: Field pop ups were displaying upgrade messages even for licenses that had access to the add on.
458
 
459
- = 5.0.15 =
460
- - New: Added a v3 reCAPTCHA type option and reCAPTCHA threshold slider to global reCAPTCHA settings. When using v3 the score will be compared to the threshold and marked as spam if it is lower than the threshold. The default value is 0.5. For more information on setting a score, see https://developers.google.com/recaptcha/docs/v3#interpreting_the_score
461
-
462
- = 5.0.14 =
463
- - New: HTML field descriptions now use a rich text editor instead of a plain textarea.
464
- - New: Added a new array_separator option to entry shortcodes. This can be used with the [default-message] like [default-message array_separator="<br/>"] shortcode to change the separator used for multiple checkbox or dropdown values. It also works with the [frm-show-entry] shortcode in pro.
465
- - New: Added tooltips to honeypot and JavaScript token spam settings.
466
- - Fix: The adjusted color used for .frm-alt-table stripes was not calculating properly when the border color was set as an RGBA value in the style manager.
467
-
468
  <a href="https://raw.githubusercontent.com/Strategy11/formidable-forms/master/changelog.txt">See changelog for all versions</a>
3
  Contributors: formidableforms, sswells, srwells
4
  Tags: forms, contact form, form builder, survey, free, form maker, form creator, paypal form, paypal, stripe, stripe form, aweber, aweber form, getresponse, getresponse form, calculator, price calculator, quote form, contact button, form manager, Akismet, payment form, survey form, donation form, email subscription, contact form widget, user registration form, wordpress registration, wordpress login form, constant contact, mailpoet, active campaign, salesforce, hubspot, campaign monitor, quiz builder, quiz, feedback form, mailchimp form
5
  Requires at least: 5.0
6
+ Tested up to: 5.9
7
  Requires PHP: 5.6
8
+ Stable tag: 5.1
9
 
10
  The most advanced WordPress forms plugin. Go beyond contact forms with our drag & drop form builder for surveys, quizzes, and more.
11
 
438
  See all <a href="https://zapier.com/apps/formidable/integrations">Formidable Zapier Integrations</a>.
439
 
440
  == Changelog ==
441
+ = 5.1 =
442
+ - Updated Bootstrap Multiselect to version 1.1.1, fixing issues with the accessibility of backend multiselect dropdowns for blind users.
443
+ - New: Inputs with errors will now add the aria-describedby attribute during JavaScript validation for more accessible errors.
444
+ - New: Form errors will now always include the role="alert" attribute for more accessible errors. New fields will now also include role="alert" in custom field HTML.
445
+ - New: Added a new frm_entries_column_value filter hook.
446
+
447
  = 5.0.17 =
448
  - The embedded CodeMirror code for compatibility with versions of WordPress before 4.9 has been removed.
449
  - New: The ctype PHP extension is no longer a requirement.
462
  - Fix: Radio buttons and checkboxes were appearing overlapped with labels when using the H-Code theme.
463
  - Fix: Field pop ups were displaying upgrade messages even for licenses that had access to the add on.
464
 
 
 
 
 
 
 
 
 
 
465
  <a href="https://raw.githubusercontent.com/Strategy11/formidable-forms/master/changelog.txt">See changelog for all versions</a>