Unyson - Version 2.7.2

Version Description

  • Bug fixes
Download this release

Release Info

Developer Unyson
Plugin Icon 128x128 Unyson
Version 2.7.2
Comparing to
See all releases

Code changes from version 2.6.16 to 2.7.2

Files changed (60) hide show
  1. framework/core/components/backend.php +46 -3
  2. framework/core/extends/class-fw-container-type.php +1 -1
  3. framework/core/extends/class-fw-option-type.php +37 -2
  4. framework/helpers/general.php +7 -33
  5. framework/includes/customizer/class--fw-customizer-setting-option.php +4 -0
  6. framework/includes/option-types/addable-box/class-fw-option-type-addable-box.php +4 -0
  7. framework/includes/option-types/addable-box/static/js/scripts.js +77 -6
  8. framework/includes/option-types/addable-box/view.php +3 -3
  9. framework/includes/option-types/addable-option/class-fw-option-type-addable-option.php +4 -0
  10. framework/includes/option-types/addable-option/static/js/scripts.js +67 -2
  11. framework/includes/option-types/addable-option/view.php +3 -3
  12. framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php +5 -1
  13. framework/includes/option-types/addable-popup/static/css/styles.css +14 -1
  14. framework/includes/option-types/addable-popup/static/js/scripts.js +90 -6
  15. framework/includes/option-types/addable-popup/view.php +4 -2
  16. framework/includes/option-types/background-image/class-fw-option-type-background-image.php +4 -0
  17. framework/includes/option-types/background-image/static/js/scripts.js +87 -17
  18. framework/includes/option-types/datetime-picker/class-fw-option-type-datetime-picker.php +10 -4
  19. framework/includes/option-types/datetime-picker/static/js/script.js +21 -1
  20. framework/includes/option-types/datetime-range/class-fw-option-type-datetime-range.php +5 -1
  21. framework/includes/option-types/datetime-range/static/css/styles.css +3 -6
  22. framework/includes/option-types/datetime-range/static/js/script.js +42 -2
  23. framework/includes/option-types/icon-v2/class-fw-option-type-icon-v2.php +5 -1
  24. framework/includes/option-types/icon-v2/static/js/icon-picker-v2.js +2 -2
  25. framework/includes/option-types/icon-v2/static/js/render-icon-previews.js +28 -11
  26. framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php +5 -1
  27. framework/includes/option-types/image-picker/static/js/scripts.js +14 -0
  28. framework/includes/option-types/map/class-fw-option-type-map.php +12 -6
  29. framework/includes/option-types/map/static/js/scripts.js +311 -204
  30. framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php +80 -39
  31. framework/includes/option-types/multi-picker/static/js/multi-picker.js +166 -70
  32. framework/includes/option-types/multi-select/class-fw-option-type-multi-select.php +194 -69
  33. framework/includes/option-types/multi-select/static/css/style.css +19 -0
  34. framework/includes/option-types/multi-select/static/js/scripts.js +23 -10
  35. framework/includes/option-types/multi-upload/class-fw-option-type-multi-upload.php +5 -1
  36. framework/includes/option-types/multi-upload/static/js/any-files.js +23 -1
  37. framework/includes/option-types/multi-upload/static/js/images-only.js +45 -1
  38. framework/includes/option-types/multi/class-fw-option-type-multi.php +9 -1
  39. framework/includes/option-types/popup/class-fw-option-type-popup.php +5 -1
  40. framework/includes/option-types/popup/static/js/popup.js +17 -0
  41. framework/includes/option-types/range-slider/class-fw-option-type-range-slider.php +5 -1
  42. framework/includes/option-types/range-slider/static/js/scripts.js +29 -1
  43. framework/includes/option-types/rgba-color-picker/static/js/scripts.js +263 -282
  44. framework/includes/option-types/simple.php +47 -3
  45. framework/includes/option-types/slider/class-fw-option-type-slider.php +5 -1
  46. framework/includes/option-types/slider/static/js/scripts.js +1 -1
  47. framework/includes/option-types/switch/class-fw-option-type-switch.php +6 -1
  48. framework/includes/option-types/switch/static/js/scripts.js +17 -1
  49. framework/includes/option-types/typography-v2/class-fw-option-type-typography-v2.php +4 -1
  50. framework/includes/option-types/upload/class-fw-option-type-upload.php +5 -1
  51. framework/includes/option-types/upload/static/js/any-files.js +17 -0
  52. framework/includes/option-types/upload/static/js/images-only.js +54 -0
  53. framework/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php +5 -1
  54. framework/includes/option-types/wp-editor/static/scripts.js +19 -0
  55. framework/manifest.php +1 -1
  56. framework/static/js/fw-events.js +201 -88
  57. framework/views/backend-option-design-customizer.php +4 -2
  58. framework/views/backend-option-design-default.php +5 -2
  59. readme.txt +13 -3
  60. unyson.php +1 -1
framework/core/components/backend.php CHANGED
@@ -294,7 +294,7 @@ final class _FW_Component_Backend {
294
  wp_register_script(
295
  'fw-events',
296
  fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
297
- array( 'backbone' ),
298
  fw()->manifest->get_version(),
299
  true
300
  );
@@ -335,6 +335,45 @@ final class _FW_Component_Backend {
335
  fw()->manifest->get_version()
336
  );
337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  wp_register_script(
339
  'fw',
340
  fw_get_framework_directory_uri( '/static/js/fw.js' ),
@@ -381,7 +420,7 @@ final class _FW_Component_Backend {
381
  wp_register_script(
382
  'fw-backend-options',
383
  fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
384
- array( 'fw', 'fw-events', 'postbox', 'jquery-ui-tabs' ),
385
  fw()->manifest->get_version(),
386
  true
387
  );
@@ -1147,7 +1186,11 @@ final class _FW_Component_Backend {
1147
  ) );
1148
  }
1149
 
1150
- $options = json_decode( FW_Request::POST( 'options' ), true );
 
 
 
 
1151
 
1152
  if ( ! $options ) {
1153
  wp_send_json_error( array(
294
  wp_register_script(
295
  'fw-events',
296
  fw_get_framework_directory_uri( '/static/js/fw-events.js' ),
297
+ array(),
298
  fw()->manifest->get_version(),
299
  true
300
  );
335
  fw()->manifest->get_version()
336
  );
337
 
338
+ wp_register_script(
339
+ 'fw-reactive-options-registry',
340
+ fw_get_framework_directory_uri(
341
+ '/static/js/fw-reactive-options-registry.js'
342
+ ),
343
+ array('fw', 'fw-events'),
344
+ false
345
+ );
346
+
347
+ wp_register_script(
348
+ 'fw-reactive-options-simple-options',
349
+ fw_get_framework_directory_uri(
350
+ '/static/js/fw-reactive-options-simple-options.js'
351
+ ),
352
+ array('fw', 'fw-events', 'fw-reactive-options-undefined-option'),
353
+ false
354
+ );
355
+
356
+ wp_register_script(
357
+ 'fw-reactive-options-undefined-option',
358
+ fw_get_framework_directory_uri(
359
+ '/static/js/fw-reactive-options-undefined-option.js'
360
+ ),
361
+ array(
362
+ 'fw', 'fw-events', 'fw-reactive-options-registry'
363
+ ),
364
+ false
365
+ );
366
+
367
+ wp_register_script(
368
+ 'fw-reactive-options',
369
+ fw_get_framework_directory_uri('/static/js/fw-reactive-options.js'),
370
+ array(
371
+ 'fw', 'fw-events', 'fw-reactive-options-undefined-option',
372
+ 'fw-reactive-options-simple-options'
373
+ ),
374
+ false
375
+ );
376
+
377
  wp_register_script(
378
  'fw',
379
  fw_get_framework_directory_uri( '/static/js/fw.js' ),
420
  wp_register_script(
421
  'fw-backend-options',
422
  fw_get_framework_directory_uri( '/static/js/backend-options.js' ),
423
+ array( 'fw', 'fw-events', 'fw-reactive-options', 'postbox', 'jquery-ui-tabs' ),
424
  fw()->manifest->get_version(),
425
  true
426
  );
1186
  ) );
1187
  }
1188
 
1189
+ $options = FW_Request::POST( 'options' );
1190
+
1191
+ if (is_string( $options )) {
1192
+ $options = json_decode( FW_Request::POST( 'options' ), true );
1193
+ }
1194
 
1195
  if ( ! $options ) {
1196
  wp_send_json_error( array(
framework/core/extends/class-fw-container-type.php CHANGED
@@ -230,4 +230,4 @@ abstract class FW_Container_Type
230
 
231
  fw()->backend->_register_container_type($registration_access_key, $container_type_class);
232
  }
233
- }
230
 
231
  fw()->backend->_register_container_type($registration_access_key, $container_type_class);
232
  }
233
+ }
framework/core/extends/class-fw-option-type.php CHANGED
@@ -68,6 +68,15 @@ abstract class FW_Option_Type
68
  */
69
  abstract protected function _get_defaults();
70
 
 
 
 
 
 
 
 
 
 
71
  /**
72
  * Prevent execute enqueue multiple times
73
  * @var bool
@@ -188,7 +197,23 @@ abstract class FW_Option_Type
188
 
189
  $this->enqueue_static($id, $option, $data);
190
 
191
- return $this->_render( $id, $this->load_callbacks( $option ), $data );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
  /**
@@ -232,7 +257,7 @@ abstract class FW_Option_Type
232
  wp_enqueue_script(
233
  'fw-option-types',
234
  fw_get_framework_directory_uri('/static/js/option-types.js'),
235
- array('fw-events', 'qtip'),
236
  fw()->manifest->get_version(),
237
  true
238
  );
@@ -313,6 +338,16 @@ abstract class FW_Option_Type
313
  return 'fixed';
314
  }
315
 
 
 
 
 
 
 
 
 
 
 
316
  /**
317
  * Use this method to register a new option type
318
  *
68
  */
69
  abstract protected function _get_defaults();
70
 
71
+ /**
72
+ * Put data for to be accessed in JavaScript for each option type instance
73
+ */
74
+ protected function _get_data_for_js($id, $option, $data = array()) {
75
+ return array(
76
+ 'option' => $option
77
+ );
78
+ }
79
+
80
  /**
81
  * Prevent execute enqueue multiple times
82
  * @var bool
197
 
198
  $this->enqueue_static($id, $option, $data);
199
 
200
+ $html_attributes = array(
201
+ 'class' => 'fw-backend-option-descriptor',
202
+ 'data-fw-option-id' => $id,
203
+ 'data-fw-option-type' => $option['type']
204
+ );
205
+
206
+ $data_for_js = $this->_get_data_for_js($id, $option, $data);
207
+
208
+ if ($data_for_js) {
209
+ $html_attributes['data-fw-for-js'] = json_encode($data_for_js);
210
+ }
211
+
212
+ return fw_html_tag(
213
+ 'div',
214
+ $html_attributes,
215
+ $this->_render( $id, $this->load_callbacks( $option ), $data )
216
+ );
217
  }
218
 
219
  /**
257
  wp_enqueue_script(
258
  'fw-option-types',
259
  fw_get_framework_directory_uri('/static/js/option-types.js'),
260
+ array('fw-events', 'qtip', 'fw-reactive-options'),
261
  fw()->manifest->get_version(),
262
  true
263
  );
338
  return 'fixed';
339
  }
340
 
341
+ /**
342
+ * a general purpose 'label' => false | true from options.php
343
+ * @return bool | string
344
+ *
345
+ * @since 2.7.1
346
+ */
347
+ public function _default_label($id, $option) {
348
+ return fw_id_to_title($id);
349
+ }
350
+
351
  /**
352
  * Use this method to register a new option type
353
  *
framework/helpers/general.php CHANGED
@@ -1313,42 +1313,16 @@ function fw_get_google_fonts_v2() {
1313
  */
1314
  function fw_current_url() {
1315
  static $url = null;
1316
-
1317
  if ( $url === null ) {
1318
- $url = 'http://';
1319
-
1320
- //https://github.com/ThemeFuse/Unyson/issues/2442
1321
- $server_wildcard_or_regex = preg_match( '/(^~\^|^\*\.|\.\*$)/', $_SERVER['SERVER_NAME'] );
1322
-
1323
- if (
1324
- $_SERVER['SERVER_NAME'] === '_'
1325
- ||
1326
- 1 === $server_wildcard_or_regex
1327
- ) { // https://github.com/ThemeFuse/Unyson/issues/126
1328
- $url .= $_SERVER['HTTP_HOST'];
1329
  } else {
1330
- $url .= $_SERVER['SERVER_NAME'];
1331
  }
1332
-
1333
- if ( ! in_array( intval( $_SERVER['SERVER_PORT'] ), array( 80, 443 ) ) ) {
1334
- $url .= ':' . $_SERVER['SERVER_PORT'];
1335
- }
1336
-
1337
- $url .= $_SERVER['REQUEST_URI'];
1338
-
1339
  $url = set_url_scheme( $url ); // https fix
1340
-
1341
- if ( is_multisite() ) {
1342
- if ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) {
1343
- $site_url = parse_url( $url );
1344
-
1345
- if ( isset( $site_url['query'] ) ) {
1346
- $url = home_url( $site_url['path'] . '?' . $site_url['query'] );
1347
- } else {
1348
- $url = home_url( $site_url['path'] );
1349
- }
1350
- }
1351
- }
1352
  }
1353
 
1354
  return $url;
@@ -1989,4 +1963,4 @@ function fw_is_callback( $value ) {
1989
  */
1990
  function fw_is_cli() {
1991
  return ( php_sapi_name() === 'cli' ) && defined( 'WP_CLI' );
1992
- }
1313
  */
1314
  function fw_current_url() {
1315
  static $url = null;
 
1316
  if ( $url === null ) {
1317
+ if ( is_multisite() && ! ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) ) {
1318
+ switch_to_blog( 1 );
1319
+ $url = get_option( 'home' );
1320
+ restore_current_blog();
 
 
 
 
 
 
 
1321
  } else {
1322
+ $url = get_option( 'home' );
1323
  }
1324
+ $url .= fw_akg( 'REQUEST_URI', $_SERVER, '/' );
 
 
 
 
 
 
1325
  $url = set_url_scheme( $url ); // https fix
 
 
 
 
 
 
 
 
 
 
 
 
1326
  }
1327
 
1328
  return $url;
1963
  */
1964
  function fw_is_cli() {
1965
  return ( php_sapi_name() === 'cli' ) && defined( 'WP_CLI' );
1966
+ }
framework/includes/customizer/class--fw-customizer-setting-option.php CHANGED
@@ -18,6 +18,10 @@ class _FW_Customizer_Setting_Option extends WP_Customize_Setting {
18
  }
19
 
20
  public function sanitize($value) {
 
 
 
 
21
  $value = json_decode($value, true);
22
 
23
  if (is_null($value) || !is_array($value)) {
18
  }
19
 
20
  public function sanitize($value) {
21
+ if ( is_array( $value ) ) {
22
+ return null;
23
+ }
24
+
25
  $value = json_decode($value, true);
26
 
27
  if (is_null($value) || !is_array($value)) {
framework/includes/option-types/addable-box/class-fw-option-type-addable-box.php CHANGED
@@ -10,6 +10,10 @@ class FW_Option_Type_Addable_Box extends FW_Option_Type
10
  return 'addable-box';
11
  }
12
 
 
 
 
 
13
  /**
14
  * @internal
15
  * {@inheritdoc}
10
  return 'addable-box';
11
  }
12
 
13
+ protected function _get_data_for_js($id, $option, $data = array()) {
14
+ return false;
15
+ }
16
+
17
  /**
18
  * @internal
19
  * {@inheritdoc}
framework/includes/option-types/addable-box/static/js/scripts.js CHANGED
@@ -55,10 +55,15 @@ jQuery(document).ready(function ($) {
55
  }
56
  },
57
  update: function(){
58
- $(this).closest(optionTypeClass).trigger('change'); // for customizer
 
 
 
 
59
  }
60
  });
61
  },
 
62
  /** Init boxes controls */
63
  initControls: function ($boxes) {
64
  $boxes
@@ -78,6 +83,9 @@ jQuery(document).ready(function ($) {
78
 
79
  methods.checkLimit($option);
80
  methods.updateHasBoxesClass($option);
 
 
 
81
  break;
82
  default:
83
  // custom control. trigger event for others to handle this
@@ -227,6 +235,23 @@ jQuery(document).ready(function ($) {
227
  fwEvents.on('fw:options:init', function (data) {
228
  var $elements = data.$elements.find(optionTypeClass +':not(.fw-option-initialized)');
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  /** Init Add button */
231
  $elements.on('click', '> .fw-option-boxes-controls > .fw-option-boxes-add-button', function(){
232
  var $button = $(this);
@@ -250,7 +275,6 @@ jQuery(document).ready(function ($) {
250
  }, 300);
251
  }
252
 
253
-
254
  $boxes.append($newBox);
255
 
256
  // Re-render wp-editor
@@ -259,10 +283,14 @@ jQuery(document).ready(function ($) {
259
  &&
260
  $newBox.find('.fw-option-type-wp-editor:first').length
261
  ) {
262
- fwWpEditorRefreshIds(
263
- $newBox.find('.fw-option-type-wp-editor textarea:first').attr('id'),
264
- $newBox
265
- );
 
 
 
 
266
  }
267
 
268
  methods.initControls($newBox);
@@ -282,6 +310,8 @@ jQuery(document).ready(function ($) {
282
 
283
  methods.checkLimit($option);
284
  methods.updateHasBoxesClass($option);
 
 
285
  });
286
 
287
  // close postboxes and attach event listener
@@ -326,4 +356,45 @@ jQuery(document).ready(function ($) {
326
 
327
  titleUpdater.update();
328
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  });
55
  }
56
  },
57
  update: function(){
58
+ var optionType = $(this).closest(optionTypeClass);
59
+
60
+ optionType.trigger('change'); // for customizer
61
+
62
+ fw.options.trigger.changeForEl(optionType);
63
  }
64
  });
65
  },
66
+
67
  /** Init boxes controls */
68
  initControls: function ($boxes) {
69
  $boxes
83
 
84
  methods.checkLimit($option);
85
  methods.updateHasBoxesClass($option);
86
+
87
+ fw.options.trigger.changeForEl($option);
88
+
89
  break;
90
  default:
91
  // custom control. trigger event for others to handle this
235
  fwEvents.on('fw:options:init', function (data) {
236
  var $elements = data.$elements.find(optionTypeClass +':not(.fw-option-initialized)');
237
 
238
+ $elements.toArray().map(function (el) {
239
+ fw.options.on.change(function (data) {
240
+ if (! $(data.context).is(
241
+ '[data-fw-option-type="addable-box"] .fw-option-boxes > .fw-option-box'
242
+ )) {
243
+ return;
244
+ }
245
+
246
+ // Listen to just its own virtual contexts
247
+ if (! el.contains(data.context)) {
248
+ return;
249
+ }
250
+
251
+ fw.options.trigger.changeForEl(el);
252
+ });
253
+ });
254
+
255
  /** Init Add button */
256
  $elements.on('click', '> .fw-option-boxes-controls > .fw-option-boxes-add-button', function(){
257
  var $button = $(this);
275
  }, 300);
276
  }
277
 
 
278
  $boxes.append($newBox);
279
 
280
  // Re-render wp-editor
283
  &&
284
  $newBox.find('.fw-option-type-wp-editor:first').length
285
  ) {
286
+ $newBox.find(
287
+ '.fw-option-type-wp-editor textarea'
288
+ ).toArray().map(function (textarea) {
289
+ fwWpEditorRefreshIds(
290
+ $(textarea).attr('id'),
291
+ $newBox
292
+ );
293
+ });
294
  }
295
 
296
  methods.initControls($newBox);
310
 
311
  methods.checkLimit($option);
312
  methods.updateHasBoxesClass($option);
313
+
314
+ fw.options.trigger.changeForEl($boxes);
315
  });
316
 
317
  // close postboxes and attach event listener
356
 
357
  titleUpdater.update();
358
  });
359
+
360
+ fw.options.register('addable-box', {
361
+ startListeningForChanges: $.noop,
362
+ getValue: function (optionDescriptor) {
363
+ var promise = $.Deferred();
364
+
365
+ // TODO: refactor that!!!
366
+ if (jQuery.when.all===undefined) {
367
+ jQuery.when.all = function(deferreds) {
368
+ var deferred = new jQuery.Deferred();
369
+ $.when.apply(jQuery, deferreds).then(
370
+ function() {
371
+ deferred.resolve(Array.prototype.slice.call(arguments));
372
+ },
373
+ function() {
374
+ deferred.fail(Array.prototype.slice.call(arguments));
375
+ });
376
+
377
+ return deferred;
378
+ }
379
+ }
380
+
381
+ jQuery.when.all(
382
+ $(optionDescriptor.el).find(
383
+ '.fw-option-boxes'
384
+ ).first().find(
385
+ '> .fw-option-box.fw-backend-options-virtual-context'
386
+ ).toArray().map(fw.options.getContextValue)
387
+ ).then(function (valuesAsArray) {
388
+ promise.resolve({
389
+ value: valuesAsArray.map(function (singleContextValue) {
390
+ return singleContextValue.value;
391
+ }),
392
+
393
+ optionDescriptor: optionDescriptor
394
+ })
395
+ });
396
+
397
+ return promise;
398
+ }
399
+ })
400
  });
framework/includes/option-types/addable-box/view.php CHANGED
@@ -42,7 +42,7 @@ if (!empty($data['value'])) {
42
  <div class="fw-option-boxes metabox-holder">
43
  <?php foreach ($data['value'] as $value_index => &$values): ?>
44
  <?php $i++; ?>
45
- <div class="fw-option-box" data-name-prefix="<?php echo fw_htmlspecialchars($data['name_prefix'] .'['. $id .']['. $i .']') ?>" data-values="<?php echo fw_htmlspecialchars(json_encode($values)) ?>">
46
  <?php ob_start() ?>
47
  <div class="fw-option-box-options fw-force-xs">
48
  <?php
@@ -80,7 +80,7 @@ if (!empty($data['value'])) {
80
  $increment_placeholder = '###-addable-box-increment-'. fw_rand_md5() .'-###';
81
 
82
  echo fw_htmlspecialchars(
83
- '<div class="fw-option-box" data-name-prefix="'. fw_htmlspecialchars($data['name_prefix'] .'['. $id .']['. $increment_placeholder .']') .'">'.
84
  fw()->backend->render_box(
85
  $data['id_prefix'] . $id .'-'. $increment_placeholder .'-box',
86
  '&nbsp;',
@@ -109,4 +109,4 @@ if (!empty($data['value'])) {
109
  ), fw_htmlspecialchars($option['add-button-text']));
110
  ?>
111
  </div>
112
- </div>
42
  <div class="fw-option-boxes metabox-holder">
43
  <?php foreach ($data['value'] as $value_index => &$values): ?>
44
  <?php $i++; ?>
45
+ <div class="fw-option-box fw-backend-options-virtual-context" data-name-prefix="<?php echo fw_htmlspecialchars($data['name_prefix'] .'['. $id .']['. $i .']') ?>" data-values="<?php echo fw_htmlspecialchars(json_encode($values)) ?>">
46
  <?php ob_start() ?>
47
  <div class="fw-option-box-options fw-force-xs">
48
  <?php
80
  $increment_placeholder = '###-addable-box-increment-'. fw_rand_md5() .'-###';
81
 
82
  echo fw_htmlspecialchars(
83
+ '<div class="fw-option-box fw-backend-options-virtual-context" data-name-prefix="'. fw_htmlspecialchars($data['name_prefix'] .'['. $id .']['. $increment_placeholder .']') .'">'.
84
  fw()->backend->render_box(
85
  $data['id_prefix'] . $id .'-'. $increment_placeholder .'-box',
86
  '&nbsp;',
109
  ), fw_htmlspecialchars($option['add-button-text']));
110
  ?>
111
  </div>
112
+ </div>
framework/includes/option-types/addable-option/class-fw-option-type-addable-option.php CHANGED
@@ -28,6 +28,10 @@ class FW_Option_Type_Addable_Option extends FW_Option_Type
28
  );
29
  }
30
 
 
 
 
 
31
  /**
32
  * @internal
33
  * {@inheritdoc}
28
  );
29
  }
30
 
31
+ protected function _get_data_for_js($id, $option, $data = array()) {
32
+ return false;
33
+ }
34
+
35
  /**
36
  * @internal
37
  * {@inheritdoc}
framework/includes/option-types/addable-option/static/js/scripts.js CHANGED
@@ -8,7 +8,7 @@ jQuery(document).ready(function ($) {
8
  // happens when sortable was not initialized before
9
  }
10
 
11
- if (!$options.first().closest(optionClass).hasClass('is-sortable')) {
12
  return false;
13
  }
14
 
@@ -34,6 +34,7 @@ jQuery(document).ready(function ($) {
34
  },
35
  update: function(){
36
  $(this).closest(optionClass).trigger('change'); // for customizer
 
37
  }
38
  });
39
  }
@@ -48,6 +49,24 @@ jQuery(document).ready(function ($) {
48
  fwEvents.on('fw:options:init', function (data) {
49
  var $elements = data.$elements.find(optionClass +':not(.fw-option-initialized)');
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  /** Init Add button */
52
  $elements.on('click', optionClass +'-add', function(){
53
  var $button = $(this);
@@ -91,10 +110,15 @@ jQuery(document).ready(function ($) {
91
  fwEvents.trigger('fw:options:init', {$elements: $newOption});
92
 
93
  $option.trigger(methods.makeEventName('option:init'), {$option: $newOption});
 
94
  });
95
 
96
  /** Init Remove button */
97
  $elements.on('click', optionClass +'-remove', function(){
 
 
 
 
98
  $(this).closest(optionClass +'-option').remove();
99
  });
100
 
@@ -104,4 +128,45 @@ jQuery(document).ready(function ($) {
104
 
105
  $elements.addClass('fw-option-initialized');
106
  });
107
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  // happens when sortable was not initialized before
9
  }
10
 
11
+ if (! $options.first().closest(optionClass).hasClass('is-sortable')) {
12
  return false;
13
  }
14
 
34
  },
35
  update: function(){
36
  $(this).closest(optionClass).trigger('change'); // for customizer
37
+ fw.options.trigger.changeForEl($(this).closest(optionClass));
38
  }
39
  });
40
  }
49
  fwEvents.on('fw:options:init', function (data) {
50
  var $elements = data.$elements.find(optionClass +':not(.fw-option-initialized)');
51
 
52
+ $elements.toArray().map(function (el) {
53
+ // Trigger change when one of the underlying contexts change
54
+ fw.options.on.change(function (data) {
55
+ if (! $(data.context).is(
56
+ '[data-fw-option-type="addable-option"] tr.fw-option-type-addable-option-option'
57
+ )) {
58
+ return;
59
+ }
60
+
61
+ // Listen to just its own virtual contexts
62
+ if (! el.contains(data.context)) {
63
+ return;
64
+ }
65
+
66
+ fw.options.trigger.changeForEl(el);
67
+ });
68
+ });
69
+
70
  /** Init Add button */
71
  $elements.on('click', optionClass +'-add', function(){
72
  var $button = $(this);
110
  fwEvents.trigger('fw:options:init', {$elements: $newOption});
111
 
112
  $option.trigger(methods.makeEventName('option:init'), {$option: $newOption});
113
+ fw.options.trigger.changeForEl($option);
114
  });
115
 
116
  /** Init Remove button */
117
  $elements.on('click', optionClass +'-remove', function(){
118
+ fw.options.trigger.changeForEl($(this).closest(
119
+ '[data-fw-option-type="addable-option"]'
120
+ ));
121
+
122
  $(this).closest(optionClass +'-option').remove();
123
  });
124
 
128
 
129
  $elements.addClass('fw-option-initialized');
130
  });
131
+
132
+ fw.options.register('addable-option', {
133
+ startListeningForChanges: $.noop,
134
+ getValue: function (optionDescriptor) {
135
+ var promise = $.Deferred();
136
+
137
+ // TODO: refactor that!!!
138
+ if (jQuery.when.all===undefined) {
139
+ jQuery.when.all = function(deferreds) {
140
+ var deferred = new jQuery.Deferred();
141
+ $.when.apply(jQuery, deferreds).then(
142
+ function() {
143
+ deferred.resolve(Array.prototype.slice.call(arguments));
144
+ },
145
+ function() {
146
+ deferred.fail(Array.prototype.slice.call(arguments));
147
+ });
148
+
149
+ return deferred;
150
+ }
151
+ }
152
+
153
+ jQuery.when.all(
154
+ $(optionDescriptor.el).find(
155
+ 'table.fw-option-type-addable-option-options'
156
+ ).first().find(
157
+ '> tbody > .fw-backend-options-virtual-context'
158
+ ).toArray().map(fw.options.getContextValue)
159
+ ).then(function (valuesAsArray) {
160
+ promise.resolve({
161
+ value: valuesAsArray.map(function (singleContextValue) {
162
+ return _.values(singleContextValue.value)[0];
163
+ }),
164
+
165
+ optionDescriptor: optionDescriptor
166
+ })
167
+ });
168
+
169
+ return promise;
170
+ }
171
+ })
172
+ });
framework/includes/option-types/addable-option/view.php CHANGED
@@ -20,7 +20,7 @@ if ($option['sortable']) {
20
  <table class="fw-option-type-addable-option-options" width="100%" cellpadding="0" cellspacing="0" border="0">
21
  <?php $i = 1; ?>
22
  <?php foreach($data['value'] as $option_value): ?>
23
- <tr class="fw-option-type-addable-option-option">
24
  <td class="td-move">
25
  <img src="<?php echo esc_attr($move_img_src); ?>" width="7" />
26
  </td>
@@ -64,7 +64,7 @@ if ($option['sortable']) {
64
  $increment_placeholder = '###-addable-option-increment-'. fw_rand_md5() .'-###';
65
 
66
  echo fw_htmlspecialchars(
67
- '<tr class="fw-option-type-addable-option-option">
68
  <td class="td-move">
69
  <img src="'. $move_img_src .'" width="7" />
70
  </td>
@@ -94,4 +94,4 @@ if ($option['sortable']) {
94
  ), fw_htmlspecialchars($option['add-button-text']));
95
  ?>
96
  </div>
97
- </div>
20
  <table class="fw-option-type-addable-option-options" width="100%" cellpadding="0" cellspacing="0" border="0">
21
  <?php $i = 1; ?>
22
  <?php foreach($data['value'] as $option_value): ?>
23
+ <tr class="fw-option-type-addable-option-option fw-backend-options-virtual-context">
24
  <td class="td-move">
25
  <img src="<?php echo esc_attr($move_img_src); ?>" width="7" />
26
  </td>
64
  $increment_placeholder = '###-addable-option-increment-'. fw_rand_md5() .'-###';
65
 
66
  echo fw_htmlspecialchars(
67
+ '<tr class="fw-option-type-addable-option-option fw-backend-options-virtual-context">
68
  <td class="td-move">
69
  <img src="'. $move_img_src .'" width="7" />
70
  </td>
94
  ), fw_htmlspecialchars($option['add-button-text']));
95
  ?>
96
  </div>
97
+ </div>
framework/includes/option-types/addable-popup/class-fw-option-type-addable-popup.php CHANGED
@@ -12,6 +12,10 @@ class FW_Option_Type_Addable_Popup extends FW_Option_Type
12
  return 'fixed';
13
  }
14
 
 
 
 
 
15
  /**
16
  * @internal
17
  * {@inheritdoc}
@@ -212,4 +216,4 @@ class FW_Option_Type_Addable_Popup_Full extends FW_Option_Type_Addable_Popup
212
 
213
  return parent::_render($id, $option, $data);
214
  }
215
- }
12
  return 'fixed';
13
  }
14
 
15
+ protected function _get_data_for_js($id, $option, $data = array()) {
16
+ return false;
17
+ }
18
+
19
  /**
20
  * @internal
21
  * {@inheritdoc}
216
 
217
  return parent::_render($id, $option, $data);
218
  }
219
+ }
framework/includes/option-types/addable-popup/static/css/styles.css CHANGED
@@ -26,6 +26,18 @@
26
  margin-top: -10px;
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  .fw-option-type-addable-popup .sort-item {
30
  position: absolute;
31
  left: 11px;
@@ -35,7 +47,8 @@
35
  }
36
 
37
  .fw-option-type-addable-popup .default-item,
38
- .fw-option-type-addable-popup:not(.is-sortable) .sort-item {
 
39
  display: none;
40
  }
41
 
26
  margin-top: -10px;
27
  }
28
 
29
+ .fw-option-type-addable-popup .clone-item {
30
+ position: absolute;
31
+ right: 25px;
32
+ top:50%;
33
+ margin-top: -8px;
34
+ font-size:16px;
35
+ opacity:0.4;
36
+ }
37
+ .fw-option-type-addable-popup .clone-item:hover{
38
+ opacity:0.7;
39
+ }
40
+
41
  .fw-option-type-addable-popup .sort-item {
42
  position: absolute;
43
  left: 11px;
47
  }
48
 
49
  .fw-option-type-addable-popup .default-item,
50
+ .fw-option-type-addable-popup:not(.is-sortable) .sort-item,
51
+ .fw-option-type-addable-popup .items-wrapper.hide-clone .clone-item {
52
  display: none;
53
  }
54
 
framework/includes/option-types/addable-popup/static/js/scripts.js CHANGED
@@ -10,25 +10,32 @@
10
  return $defaultItem.clone().removeClass('default-item').addClass('item');
11
  }
12
  },
 
13
  data = JSON.parse(
14
  JSON.parse(nodes.$optionWrapper.attr('data-for-js')).join('{{') // check option php class
15
  ),
 
16
  utils = {
17
  modal: new fw.OptionsModal({
18
  title: data.title,
19
  options: data.options,
20
  size : data.size
21
  }),
 
22
  countItems: function () {
23
  return nodes.$itemsWrapper.find('> .item').length;
24
  },
 
25
  removeDefaultItem: function () {
26
  nodes.$optionWrapper.find('.default-item:first').remove();
27
  },
 
28
  toogleNodes : function(){
29
  utils.toogleItemsWrapper();
30
  utils.toogleAddButton();
 
31
  },
 
32
  toogleItemsWrapper: function () {
33
 
34
  if (utils.countItems() === 0) {
@@ -36,7 +43,9 @@
36
  } else {
37
  nodes.$itemsWrapper.show();
38
  }
 
39
  },
 
40
  toogleAddButton: function(){
41
  if(data.limit !== 0 ){
42
  (utils.countItems() >= data.limit ) ?
@@ -44,14 +53,24 @@
44
  nodes.$addButton.show();
45
  }
46
  },
 
 
 
 
 
 
 
 
 
47
  init: function () {
48
  utils.initItemsTemplates();
49
  utils.toogleNodes();
50
  utils.removeDefaultItem();
51
  utils.initSortable();
52
  },
 
53
  initSortable: function () {
54
- if (!nodes.$optionWrapper.hasClass('is-sortable')) {
55
  return false;
56
  }
57
 
@@ -63,6 +82,7 @@
63
  axis: 'y',
64
  update: function(){
65
  nodes.$optionWrapper.trigger('change'); // for customizer
 
66
  },
67
  start: function(e, ui){
68
  // Update the height of the placeholder to match the moving item.
@@ -74,31 +94,39 @@
74
  }
75
  });
76
  },
 
77
  initItemsTemplates: function () {
78
  var $items = nodes.$itemsWrapper.find('> .item');
 
79
  if ($items.length > 0) {
80
  $items.each(function () {
81
  utils.editItem($(this), JSON.parse($(this).find('input').val()));
82
  });
83
  }
84
  },
 
85
  createItem: function (values) {
86
  var $clonedItem = nodes.getDefaultItem(),
87
  $clonedInput = $clonedItem.find('.input-wrapper');
88
 
89
  var $inputTemplate = $(
90
  $.trim($clonedInput.html())
91
- .split( nodes.$addButton.attr('data-increment-placeholder') ).join(utils.countItems())
 
 
 
92
  );
93
- $inputTemplate.attr('value', JSON.stringify(values));
94
 
95
- $clonedInput.find('input').replaceWith($inputTemplate);
 
 
96
 
97
  var template = '';
98
 
99
  try {
100
  /**
101
- * may throw error in in template is used an option id added after some items was already saved
 
102
  */
103
  values._context = $clonedItem.find('.content');
104
 
@@ -119,9 +147,11 @@
119
 
120
  return $clonedItem;
121
  },
 
122
  addNewItem: function (values) {
123
  nodes.$itemsWrapper.append(utils.createItem(values));
124
  },
 
125
  editItem: function (item, values) {
126
  item.replaceWith(utils.createItem(values));
127
  }
@@ -132,8 +162,18 @@
132
  e.preventDefault();
133
  $(this).closest('.item').remove();
134
  utils.toogleNodes();
135
-
136
  nodes.$optionWrapper.trigger('change'); // for customizer
 
 
 
 
 
 
 
 
 
 
 
137
  });
138
 
139
  nodes.$itemsWrapper.on('click', '> .item', function (e) {
@@ -167,6 +207,7 @@
167
  }
168
 
169
  nodes.$optionWrapper.trigger('change'); // for customizer
 
170
  });
171
 
172
  _.map(
@@ -200,4 +241,47 @@
200
  .addClass('fw-option-initialized');
201
  });
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  })(jQuery, _, fwEvents, window);
10
  return $defaultItem.clone().removeClass('default-item').addClass('item');
11
  }
12
  },
13
+
14
  data = JSON.parse(
15
  JSON.parse(nodes.$optionWrapper.attr('data-for-js')).join('{{') // check option php class
16
  ),
17
+
18
  utils = {
19
  modal: new fw.OptionsModal({
20
  title: data.title,
21
  options: data.options,
22
  size : data.size
23
  }),
24
+
25
  countItems: function () {
26
  return nodes.$itemsWrapper.find('> .item').length;
27
  },
28
+
29
  removeDefaultItem: function () {
30
  nodes.$optionWrapper.find('.default-item:first').remove();
31
  },
32
+
33
  toogleNodes : function(){
34
  utils.toogleItemsWrapper();
35
  utils.toogleAddButton();
36
+ utils.toogleClone();
37
  },
38
+
39
  toogleItemsWrapper: function () {
40
 
41
  if (utils.countItems() === 0) {
43
  } else {
44
  nodes.$itemsWrapper.show();
45
  }
46
+
47
  },
48
+
49
  toogleAddButton: function(){
50
  if(data.limit !== 0 ){
51
  (utils.countItems() >= data.limit ) ?
53
  nodes.$addButton.show();
54
  }
55
  },
56
+
57
+ toogleClone: function(){
58
+ if(data.limit !== 0 ){
59
+ (utils.countItems() >= data.limit ) ?
60
+ nodes.$itemsWrapper.addClass('hide-clone') :
61
+ nodes.$itemsWrapper.removeClass('hide-clone');
62
+ }
63
+ },
64
+
65
  init: function () {
66
  utils.initItemsTemplates();
67
  utils.toogleNodes();
68
  utils.removeDefaultItem();
69
  utils.initSortable();
70
  },
71
+
72
  initSortable: function () {
73
+ if (! nodes.$optionWrapper.hasClass('is-sortable')) {
74
  return false;
75
  }
76
 
82
  axis: 'y',
83
  update: function(){
84
  nodes.$optionWrapper.trigger('change'); // for customizer
85
+ fw.options.trigger.changeForEl(nodes.$optionWrapper);
86
  },
87
  start: function(e, ui){
88
  // Update the height of the placeholder to match the moving item.
94
  }
95
  });
96
  },
97
+
98
  initItemsTemplates: function () {
99
  var $items = nodes.$itemsWrapper.find('> .item');
100
+
101
  if ($items.length > 0) {
102
  $items.each(function () {
103
  utils.editItem($(this), JSON.parse($(this).find('input').val()));
104
  });
105
  }
106
  },
107
+
108
  createItem: function (values) {
109
  var $clonedItem = nodes.getDefaultItem(),
110
  $clonedInput = $clonedItem.find('.input-wrapper');
111
 
112
  var $inputTemplate = $(
113
  $.trim($clonedInput.html())
114
+ .split(
115
+ nodes.$addButton.attr('data-increment-placeholder')
116
+ )
117
+ .join(utils.countItems())
118
  );
 
119
 
120
+ $inputTemplate.find('input').attr('value', JSON.stringify(values));
121
+
122
+ $clonedInput.children().first().replaceWith($inputTemplate);
123
 
124
  var template = '';
125
 
126
  try {
127
  /**
128
+ * may throw error in in template is used an option id
129
+ * added after some items was already saved
130
  */
131
  values._context = $clonedItem.find('.content');
132
 
147
 
148
  return $clonedItem;
149
  },
150
+
151
  addNewItem: function (values) {
152
  nodes.$itemsWrapper.append(utils.createItem(values));
153
  },
154
+
155
  editItem: function (item, values) {
156
  item.replaceWith(utils.createItem(values));
157
  }
162
  e.preventDefault();
163
  $(this).closest('.item').remove();
164
  utils.toogleNodes();
 
165
  nodes.$optionWrapper.trigger('change'); // for customizer
166
+ fw.options.trigger.changeForEl(nodes.$optionWrapper);
167
+ });
168
+
169
+ nodes.$itemsWrapper.on('click', '.clone-item', function (e) {
170
+ e.stopPropagation();
171
+ var $item = $(this).closest('.item');
172
+ var $vals = JSON.parse($($item).find('input').val());
173
+ utils.addNewItem($vals);
174
+ utils.toogleNodes();
175
+ nodes.$optionWrapper.trigger('change'); // for customizer
176
+ fw.options.trigger.changeForEl(nodes.$optionWrapper);
177
  });
178
 
179
  nodes.$itemsWrapper.on('click', '> .item', function (e) {
207
  }
208
 
209
  nodes.$optionWrapper.trigger('change'); // for customizer
210
+ fw.options.trigger.changeForEl(nodes.$optionWrapper);
211
  });
212
 
213
  _.map(
241
  .addClass('fw-option-initialized');
242
  });
243
 
244
+ fw.options.register('addable-popup', {
245
+ getValue: function (optionDescriptor) {
246
+ var promise = $.Deferred();
247
+
248
+ // TODO: refactor that!!!
249
+ if (jQuery.when.all===undefined) {
250
+ jQuery.when.all = function(deferreds) {
251
+ var deferred = new jQuery.Deferred();
252
+ $.when.apply(jQuery, deferreds).then(
253
+ function() {
254
+ deferred.resolve(Array.prototype.slice.call(arguments));
255
+ },
256
+ function() {
257
+ deferred.fail(Array.prototype.slice.call(arguments));
258
+ });
259
+
260
+ return deferred;
261
+ }
262
+ }
263
+
264
+ jQuery.when.all(
265
+ $(optionDescriptor.el).find(
266
+ '> .fw-option-type-addable-popup > .items-wrapper'
267
+ ).first().find(
268
+ '> .item.fw-backend-options-virtual-context'
269
+ ).toArray().map(fw.options.getContextValue)
270
+ ).then(function (valuesAsArray) {
271
+ promise.resolve({
272
+ value: _.map(
273
+ valuesAsArray,
274
+ _.compose(JSON.parse, _.first, _.values, _.property('value'))
275
+ ),
276
+
277
+ optionDescriptor: optionDescriptor
278
+ })
279
+ });
280
+
281
+ return promise;
282
+ },
283
+
284
+ startListeningForChanges: $.noop
285
+ });
286
+
287
  })(jQuery, _, fwEvents, window);
framework/includes/option-types/addable-popup/view.php CHANGED
@@ -24,7 +24,7 @@ $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
24
  )); ?>
25
  <div class="items-wrapper">
26
  <?php foreach ($data['value'] as $key => $value): ?>
27
- <div class="item">
28
  <div class="input-wrapper">
29
  <?php echo fw()->backend->option_type('hidden')->render('', array('value' => json_encode($value)), array(
30
  'id_prefix' => $data['id_prefix'] . $id . '-' . $key . '-',
@@ -35,10 +35,11 @@ $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
35
 
36
  <div class="content"><!-- will be populated from js --></div>
37
  <a href="#" class="dashicons fw-x delete-item"></a>
 
38
  </div>
39
  <?php endforeach; ?>
40
  </div>
41
- <div class="default-item">
42
  <div class="input-wrapper">
43
  <?php echo fw()->backend->option_type('hidden')->render('', array('value' => '[]'), array(
44
  'id_prefix' => $data['id_prefix'] . $id . '-' . $increment_placeholder,
@@ -49,6 +50,7 @@ $increment_placeholder = '###-addable-popup-increment-'. fw_rand_md5() .'-###';
49
 
50
  <div class="content"></div>
51
  <a href="#" class="dashicons fw-x delete-item"></a>
 
52
  </div>
53
  <?php
54
  echo fw_html_tag('button', array(
24
  )); ?>
25
  <div class="items-wrapper">
26
  <?php foreach ($data['value'] as $key => $value): ?>
27
+ <div class="item fw-backend-options-virtual-context">
28
  <div class="input-wrapper">
29
  <?php echo fw()->backend->option_type('hidden')->render('', array('value' => json_encode($value)), array(
30
  'id_prefix' => $data['id_prefix'] . $id . '-' . $key . '-',
35
 
36
  <div class="content"><!-- will be populated from js --></div>
37
  <a href="#" class="dashicons fw-x delete-item"></a>
38
+ <small class="dashicons dashicons-admin-page clone-item" title="<?php echo __('Clone','fw') ?>"></small>
39
  </div>
40
  <?php endforeach; ?>
41
  </div>
42
+ <div class="default-item fw-backend-options-virtual-context">
43
  <div class="input-wrapper">
44
  <?php echo fw()->backend->option_type('hidden')->render('', array('value' => '[]'), array(
45
  'id_prefix' => $data['id_prefix'] . $id . '-' . $increment_placeholder,
50
 
51
  <div class="content"></div>
52
  <a href="#" class="dashicons fw-x delete-item"></a>
53
+ <small class="dashicons dashicons-admin-page clone-item" title="<?php echo __('Clone','fw') ?>"></small>
54
  </div>
55
  <?php
56
  echo fw_html_tag('button', array(
framework/includes/option-types/background-image/class-fw-option-type-background-image.php CHANGED
@@ -21,6 +21,10 @@ class FW_Option_Type_Background_Image extends FW_Option_Type {
21
  );
22
  }
23
 
 
 
 
 
24
  /**
25
  * @internal
26
  * {@inheritdoc}
21
  );
22
  }
23
 
24
+ protected function _get_data_for_js($id, $option, $data = array()) {
25
+ return false;
26
+ }
27
+
28
  /**
29
  * @internal
30
  * {@inheritdoc}
framework/includes/option-types/background-image/static/js/scripts.js CHANGED
@@ -2,34 +2,104 @@ jQuery(document).ready(function ($) {
2
  var optionTypeClass = 'fw-option-type-background-image';
3
  var eventNamePrefix = 'fw:option-type:background-image:';
4
 
 
 
 
 
 
 
 
 
 
 
5
  fwEvents.on('fw:options:init', function (data) {
6
  var $options = data.$elements.find('.'+ optionTypeClass +':not(.initialized)');
7
 
8
- $options.find('.fw-option-type-radio').on('change', function (e) {
9
- var $predefined = jQuery(this).closest('.fw-inner').find('.predefined');
10
- var $custom = jQuery(this).closest('.fw-inner').find('.custom');
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- if (e.target.value === 'custom') {
13
- $predefined.hide();
14
- $custom.show();
15
- } else {
16
- $predefined.show();
17
- $custom.hide();
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  });
20
 
21
  // route inner image-picker events as this option events
22
  {
23
- $options.on('fw:option-type:image-picker:clicked', '.fw-option-type-image-picker', function(e, data) {
24
- jQuery(this).trigger(eventNamePrefix +'clicked', data);
25
- });
 
 
 
 
26
 
27
- $options.on('fw:option-type:image-picker:changed', '.fw-option-type-image-picker', function(e, data) {
28
- jQuery(this).trigger(eventNamePrefix +'changed', data);
29
- });
 
 
 
 
30
  }
31
 
32
  $options.addClass('initialized');
 
 
 
 
 
 
 
 
 
 
33
  });
34
 
35
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  var optionTypeClass = 'fw-option-type-background-image';
3
  var eventNamePrefix = 'fw:option-type:background-image:';
4
 
5
+ fw.options.register('background-image', {
6
+ startListeningForChanges: jQuery.noop,
7
+ getValue: function (optionDescriptor) {
8
+ return {
9
+ value: getValueForEl(optionDescriptor.el),
10
+ optionDescriptor: optionDescriptor
11
+ }
12
+ }
13
+ });
14
+
15
  fwEvents.on('fw:options:init', function (data) {
16
  var $options = data.$elements.find('.'+ optionTypeClass +':not(.initialized)');
17
 
18
+ $options.toArray().map(function (el) {
19
+ /**
20
+ * Here we start listening to events triggered by inner option
21
+ * types. We may receive events from 3 nested option types here:
22
+ *
23
+ * 1. radio
24
+ * 2. image-picker
25
+ * 3. upload
26
+ */
27
+ fw.options.on.changeByContext(el, function (optionDescriptor) {
28
+ if (optionDescriptor.type === 'radio') {
29
+ var $predefined = $(
30
+ optionDescriptor.el
31
+ ).closest('.fw-inner').find('.predefined');
32
 
33
+ var $custom = $(
34
+ optionDescriptor.el
35
+ ).closest('.fw-inner').find('.custom');
36
+
37
+ getValueForEl(el).then(function (value) {
38
+ var type = value.type
39
+
40
+ if (type === 'custom') {
41
+ $predefined.hide();
42
+ $custom.show();
43
+ } else {
44
+ $predefined.show();
45
+ $custom.hide();
46
+ }
47
+ })
48
+
49
+ }
50
+
51
+ triggerChangeAndInferValueFor(
52
+ // Here we refer to the optionDescriptor.context
53
+ // as to the `background-image` option type container
54
+ optionDescriptor.context
55
+ )
56
+ });
57
  });
58
 
59
  // route inner image-picker events as this option events
60
  {
61
+ $options.on(
62
+ 'fw:option-type:image-picker:clicked',
63
+ '.fw-option-type-image-picker',
64
+ function(e, data) {
65
+ jQuery(this).trigger(eventNamePrefix + 'clicked', data);
66
+ }
67
+ );
68
 
69
+ $options.on(
70
+ 'fw:option-type:image-picker:changed',
71
+ '.fw-option-type-image-picker',
72
+ function(e, data) {
73
+ jQuery(this).trigger(eventNamePrefix + 'changed', data);
74
+ }
75
+ );
76
  }
77
 
78
  $options.addClass('initialized');
79
+
80
+ function triggerChangeAndInferValueFor (el) {
81
+ getValueForEl(el).then(function (value) {
82
+ fw.options.trigger.changeForEl(el, {
83
+ value: value
84
+ });
85
+ })
86
+
87
+ }
88
+
89
  });
90
 
91
+ function getValueForEl (el) {
92
+ var promise = $.Deferred();
93
+
94
+ var optionDescriptor = fw.options.getOptionDescriptor(el);
95
+
96
+ fw.options.getContextValue(
97
+ optionDescriptor.el
98
+ ).then(function (value) {
99
+ promise.resolve(value.value);
100
+ });
101
+
102
+ return promise;
103
+ }
104
+
105
+ });
framework/includes/option-types/datetime-picker/class-fw-option-type-datetime-picker.php CHANGED
@@ -11,6 +11,10 @@ class FW_Option_Type_Datetime_Picker extends FW_Option_Type {
11
  return 'fixed';
12
  }
13
 
 
 
 
 
14
  /**
15
  * Detetime-picker options on http://xdsoft.net/jqplugins/datetimepicker/ excepts: [value]
16
  * Additional options:
@@ -62,9 +66,11 @@ class FW_Option_Type_Datetime_Picker extends FW_Option_Type {
62
  $option['datetime-picker']['lang'] = substr(get_locale(), 0, 2);
63
  $option['attr']['data-moment-format'] = $moment_format;
64
 
65
- echo '<div ' . fw_attr_to_html($wrapper_attr) .' >';
66
- echo fw()->backend->option_type( 'text' )->render( $id, $option, $data );
67
- echo '</div>';
 
 
68
  }
69
 
70
  /**
@@ -153,4 +159,4 @@ class FW_Option_Type_Datetime_Picker extends FW_Option_Type {
153
  return false;
154
  }
155
 
156
- }
11
  return 'fixed';
12
  }
13
 
14
+ protected function _get_data_for_js($id, $option, $data = array()) {
15
+ return false;
16
+ }
17
+
18
  /**
19
  * Detetime-picker options on http://xdsoft.net/jqplugins/datetimepicker/ excepts: [value]
20
  * Additional options:
66
  $option['datetime-picker']['lang'] = substr(get_locale(), 0, 2);
67
  $option['attr']['data-moment-format'] = $moment_format;
68
 
69
+ $html = '<div ' . fw_attr_to_html($wrapper_attr) .' >';
70
+ $html .= fw()->backend->option_type( 'text' )->render( $id, $option, $data );
71
+ $html .= '</div>';
72
+
73
+ return $html;
74
  }
75
 
76
  /**
159
  return false;
160
  }
161
 
162
+ }
framework/includes/option-types/datetime-picker/static/js/script.js CHANGED
@@ -11,9 +11,29 @@
11
  };
12
 
13
  fwe.trigger('fw:options:datetime-picker:before-init', data);
14
- $input.datetimepicker(data.options);
 
 
 
 
 
 
 
 
15
  };
16
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  fwe.on('fw:options:init', function(data) {
18
  data.$elements
19
  .find('.fw-option-type-datetime-picker').each(init)
11
  };
12
 
13
  fwe.trigger('fw:options:datetime-picker:before-init', data);
14
+
15
+ $input.datetimepicker(data.options)
16
+ .on('change', function (e) {
17
+ fw.options.trigger.changeForEl(
18
+ jQuery(e.target).closest('[data-fw-option-type="datetime-picker"]'), {
19
+ value: e.target.value
20
+ }
21
+ )
22
+ });
23
  };
24
 
25
+ fw.options.register('datetime-picker', {
26
+ startListeningForChanges: $.noop,
27
+ getValue: function (optionDescriptor) {
28
+ return {
29
+ value: $(optionDescriptor.el).find(
30
+ '[data-fw-option-type="text"]'
31
+ ).find('> input').val(),
32
+ optionDescriptor: optionDescriptor
33
+ }
34
+ }
35
+ })
36
+
37
  fwe.on('fw:options:init', function(data) {
38
  data.$elements
39
  .find('.fw-option-type-datetime-picker').each(init)
framework/includes/option-types/datetime-range/class-fw-option-type-datetime-range.php CHANGED
@@ -15,6 +15,10 @@ class FW_Option_Type_Datetime_Range extends FW_Option_Type {
15
  return 'auto';
16
  }
17
 
 
 
 
 
18
  /**
19
  * Avaible options on http://xdsoft.net/jqplugins/datetimepicker/ excepts: [value, format]
20
  * @internal
@@ -114,4 +118,4 @@ class FW_Option_Type_Datetime_Range extends FW_Option_Type {
114
  }
115
 
116
 
117
- }
15
  return 'auto';
16
  }
17
 
18
+ protected function _get_data_for_js($id, $option, $data = array()) {
19
+ return false;
20
+ }
21
+
22
  /**
23
  * Avaible options on http://xdsoft.net/jqplugins/datetimepicker/ excepts: [value, format]
24
  * @internal
118
  }
119
 
120
 
121
+ }
framework/includes/option-types/datetime-range/static/css/styles.css CHANGED
@@ -3,12 +3,9 @@
3
  margin: 0 10px;
4
  }
5
 
6
- .fw-option-type-datetime-range .fw-option-type-datetime-picker.from,
7
- .fw-option-type-datetime-range .fw-option-type-datetime-picker.to,
8
- .fw-option-type-datetime-range .delimiter,
9
- .fw-backend-option-input-type-datetime-range .fw-option-type-datetime-picker.from,
10
- .fw-backend-option-input-type-datetime-range .fw-option-type-datetime-picker.to,
11
- .fw-backend-option-input-type-datetime-range .delimiter {
12
  display: inline-block;
13
  }
14
 
3
  margin: 0 10px;
4
  }
5
 
6
+ .fw-option-type-datetime-range .fw-backend-option-descriptor[data-fw-option-id=from],
7
+ .fw-option-type-datetime-range .fw-backend-option-descriptor[data-fw-option-id=to],
8
+ .fw-option-type-datetime-range .delimiter {
 
 
 
9
  display: inline-block;
10
  }
11
 
framework/includes/option-types/datetime-range/static/js/script.js CHANGED
@@ -158,8 +158,15 @@
158
  //fwe.trigger('fw:datetime-range:first:open', { dateTimePicker: dateTimeFirstPicker, dateTimeInput: $dateTimeFirstInput }); ????
159
  });
160
 
161
- $dateTimeFirstInput.on('change', setMaxTimeLimit );
162
- $dateTimeLastInput.on('change', setMinTimeLimit );
 
 
 
 
 
 
 
163
 
164
  dateTimeLastPicker.on('open.xdsoft', function(e){
165
  var firstInputMomentFormat = $dateTimeFirstInput.data('moment-format'),
@@ -265,6 +272,39 @@
265
  .addClass('fw-option-initialized');
266
  });
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  });
269
 
270
  })(jQuery, fwEvents);
158
  //fwe.trigger('fw:datetime-range:first:open', { dateTimePicker: dateTimeFirstPicker, dateTimeInput: $dateTimeFirstInput }); ????
159
  });
160
 
161
+ $dateTimeFirstInput.on('change', function () {
162
+ setMaxTimeLimit();
163
+ triggerChangeFor(this);
164
+ });
165
+
166
+ $dateTimeLastInput.on('change', function () {
167
+ setMinTimeLimit();
168
+ triggerChangeFor(this);
169
+ });
170
 
171
  dateTimeLastPicker.on('open.xdsoft', function(e){
172
  var firstInputMomentFormat = $dateTimeFirstInput.data('moment-format'),
272
  .addClass('fw-option-initialized');
273
  });
274
 
275
+ fw.options.register('datetime-range', {
276
+ startListeningForChanges: $.noop,
277
+ getValue: function (optionDescriptor) {
278
+ return {
279
+ value: getValueForEl(optionDescriptor.el),
280
+ optionDescriptor: optionDescriptor
281
+ };
282
+ }
283
+ });
284
+
285
+ function triggerChangeFor ($container) {
286
+ $container = $($container).closest(
287
+ '[data-fw-option-type="datetime-range"]'
288
+ );
289
+
290
+ fw.options.trigger.changeForEl($container, {
291
+ value: getValueForEl($container)
292
+ });
293
+ }
294
+
295
+ function getValueForEl (el) {
296
+ return {
297
+ from: $(el).find(
298
+ '[data-fw-option-id="from"] input'
299
+ ).val(),
300
+
301
+ to: $(el).find(
302
+ '[data-fw-option-id="to"] input'
303
+ ).val()
304
+ }
305
+ }
306
+
307
+
308
  });
309
 
310
  })(jQuery, fwEvents);
framework/includes/option-types/icon-v2/class-fw-option-type-icon-v2.php CHANGED
@@ -12,6 +12,10 @@ class FW_Option_Type_Icon_v2 extends FW_Option_Type
12
  return 'icon-v2';
13
  }
14
 
 
 
 
 
15
  public function _init()
16
  {
17
  /**
@@ -32,7 +36,7 @@ class FW_Option_Type_Icon_v2 extends FW_Option_Type
32
  protected function _enqueue_static($id, $option, $data)
33
  {
34
  add_action(
35
- 'admin_print_scripts',
36
  array($this, 'load_templates')
37
  );
38
 
12
  return 'icon-v2';
13
  }
14
 
15
+ protected function _get_data_for_js($id, $option, $data = array()) {
16
+ return false;
17
+ }
18
+
19
  public function _init()
20
  {
21
  /**
36
  protected function _enqueue_static($id, $option, $data)
37
  {
38
  add_action(
39
+ 'admin_print_footer_scripts',
40
  array($this, 'load_templates')
41
  );
42
 
framework/includes/option-types/icon-v2/static/js/icon-picker-v2.js CHANGED
@@ -214,8 +214,8 @@ window.fwOptionTypeIconV2Picker = fw.Modal.extend({
214
  },
215
 
216
  attachEvents: function() {
217
- fwEvents.on('fw:option-type:upload:change', this.setAttachment, this);
218
- fwEvents.on('fw:option-type:upload:clear', this.setAttachment, this);
219
  },
220
 
221
  setHtml: function() {
214
  },
215
 
216
  attachEvents: function() {
217
+ fwEvents.on('fw:option-type:upload:change', _.bind(this.setAttachment, this));
218
+ fwEvents.on('fw:option-type:upload:clear', _.bind(this.setAttachment, this));
219
  },
220
 
221
  setHtml: function() {
framework/includes/option-types/icon-v2/static/js/render-icon-previews.js CHANGED
@@ -199,22 +199,39 @@
199
  function setDataForRoot ($root, data) {
200
  var currentData = getDataForRoot($root);
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  $root.find('input').val(
203
- JSON.stringify(
204
- _.omit(
205
- _.extend(
206
- {},
207
- currentData,
208
- data
209
- ),
210
-
211
- 'attachment'
212
- )
213
- )
214
  ).trigger('change');
215
 
216
  refreshSinglePreview($root);
217
  }
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  }(jQuery));
220
 
199
  function setDataForRoot ($root, data) {
200
  var currentData = getDataForRoot($root);
201
 
202
+ var actualValue = _.omit(
203
+ _.extend(
204
+ {},
205
+ currentData,
206
+ data
207
+ ),
208
+
209
+ 'attachment'
210
+ );
211
+
212
+ fw.options.trigger.changeForEl($root, {
213
+ value: actualValue
214
+ });
215
+
216
  $root.find('input').val(
217
+ JSON.stringify(actualValue)
 
 
 
 
 
 
 
 
 
 
218
  ).trigger('change');
219
 
220
  refreshSinglePreview($root);
221
  }
222
 
223
+ fw.options.register('icon-v2', {
224
+ startListeningForChanges: $.noop,
225
+ getValue: function (optionDescriptor) {
226
+ return {
227
+ value: JSON.parse(
228
+ $(optionDescriptor.el).find('input').val()
229
+ ),
230
+
231
+ optionDescriptor: optionDescriptor
232
+ }
233
+ }
234
+ })
235
+
236
  }(jQuery));
237
 
framework/includes/option-types/image-picker/class-fw-option-type-image-picker.php CHANGED
@@ -41,6 +41,10 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
41
  );
42
  }
43
 
 
 
 
 
44
  /**
45
  * @internal
46
  * {@inheritdoc}
@@ -201,4 +205,4 @@ class Fw_Option_Type_Image_Picker extends FW_Option_Type
201
  {
202
  return 'auto';
203
  }
204
- }
41
  );
42
  }
43
 
44
+ protected function _get_data_for_js($id, $option, $data = array()) {
45
+ return false;
46
+ }
47
+
48
  /**
49
  * @internal
50
  * {@inheritdoc}
205
  {
206
  return 'auto';
207
  }
208
+ }
framework/includes/option-types/image-picker/static/js/scripts.js CHANGED
@@ -2,6 +2,16 @@
2
  var optionTypeClass = 'fw-option-type-image-picker';
3
  var eventNamePrefix = 'fw:option-type:image-picker:';
4
 
 
 
 
 
 
 
 
 
 
 
5
  jQuery(document).ready(function ($) {
6
  /** Init image_picker options */
7
  fwEvents.on('fw:options:init', function (data) {
@@ -27,6 +37,10 @@
27
  changed: function (oldValues, newValues) {
28
  var $this = $(this);
29
 
 
 
 
 
30
  $this.closest('.'+ optionTypeClass).trigger(eventNamePrefix +'changed', {
31
  oldValues : oldValues,
32
  newValues : newValues
2
  var optionTypeClass = 'fw-option-type-image-picker';
3
  var eventNamePrefix = 'fw:option-type:image-picker:';
4
 
5
+ fw.options.register('image-picker', {
6
+ startListeningForChanges: jQuery.noop,
7
+ getValue: function (optionDescriptor) {
8
+ return {
9
+ value: optionDescriptor.el.querySelector('select').value,
10
+ optionDescriptor: optionDescriptor
11
+ }
12
+ }
13
+ });
14
+
15
  jQuery(document).ready(function ($) {
16
  /** Init image_picker options */
17
  fwEvents.on('fw:options:init', function (data) {
37
  changed: function (oldValues, newValues) {
38
  var $this = $(this);
39
 
40
+ fw.options.trigger.changeForEl($this[0], {
41
+ value: newValues[0]
42
+ });
43
+
44
  $this.closest('.'+ optionTypeClass).trigger(eventNamePrefix +'changed', {
45
  oldValues : oldValues,
46
  newValues : newValues
framework/includes/option-types/map/class-fw-option-type-map.php CHANGED
@@ -27,6 +27,7 @@ class FW_Option_Type_Map extends FW_Option_Type {
27
  '1.0',
28
  true
29
  );
 
30
  wp_localize_script(
31
  $this->get_type() . '-scripts',
32
  '_fw_option_type_map',
@@ -49,21 +50,26 @@ class FW_Option_Type_Map extends FW_Option_Type {
49
  ? json_encode($data['value']['coordinates'])
50
  : '';
51
 
52
- $path = fw_get_framework_directory( '/includes/option-types/' . $this->get_type() . '/views/view.php' );
 
 
53
 
54
- return fw_render_view( $path, array(
55
  'id' => $id,
56
  'option' => $option,
57
  'data' => $data
58
- ) );
 
 
 
 
59
  }
60
 
61
  /**
62
  * @internal
63
  */
64
  protected function _get_value_from_input( $option, $input_value ) {
65
-
66
- if ( ! is_array( $input_value ) || empty( $input_value ) ) {
67
  $input_value = $option['value'];
68
  }
69
 
@@ -102,4 +108,4 @@ class FW_Option_Type_Map extends FW_Option_Type {
102
  public static function api_key() {
103
  return FW_Option_Type_GMap_Key::get_key();
104
  }
105
- }
27
  '1.0',
28
  true
29
  );
30
+
31
  wp_localize_script(
32
  $this->get_type() . '-scripts',
33
  '_fw_option_type_map',
50
  ? json_encode($data['value']['coordinates'])
51
  : '';
52
 
53
+ $path = fw_get_framework_directory(
54
+ '/includes/option-types/' . $this->get_type() . '/views/view.php'
55
+ );
56
 
57
+ return fw_render_view($path, array(
58
  'id' => $id,
59
  'option' => $option,
60
  'data' => $data
61
+ ));
62
+ }
63
+
64
+ public function _get_data_for_js($id, $option, $data = array()) {
65
+ return false;
66
  }
67
 
68
  /**
69
  * @internal
70
  */
71
  protected function _get_value_from_input( $option, $input_value ) {
72
+ if (! is_array( $input_value ) || empty( $input_value )) {
 
73
  $input_value = $option['value'];
74
  }
75
 
108
  public static function api_key() {
109
  return FW_Option_Type_GMap_Key::get_key();
110
  }
111
+ }
framework/includes/option-types/map/static/js/scripts.js CHANGED
@@ -1,26 +1,22 @@
1
- /**
2
- * Script file that will manage the "map" option
3
- */
4
 
5
- "use strict";
6
- (function($, _, fwe, localized){
7
- jQuery( document ).ready( function( ) {
8
  $.fn.pressEnter = function(fn) {
9
  return this.each(function() {
10
  $(this).bind('enterPress', fn);
11
- $(this).keyup(function(e){
12
- if(e.keyCode == 13)
13
- {
14
- $(this).trigger("enterPress");
15
  }
16
- })
17
  });
18
  };
19
 
20
- function fw_option_map_initialize( data ) {
21
-
22
- if( typeof google === 'undefined' ){
23
- data.find( '.fw-option-map-inputs').attr( 'readonly', 'readonly' );
24
  return;
25
  }
26
 
@@ -28,271 +24,354 @@
28
 
29
  // Define map option
30
  var option = {
31
- fields : {
32
- location : {
33
- element : data.find( '.map-location' ),
34
- value : data.find( '.map-location').attr('value')
35
  },
36
- venue : {
37
- element : data.find( '.map-venue' ),
38
- value : data.find( '.map-venue').attr('value')
39
  },
40
- address : {
41
- element : data.find( '.map-address' ),
42
- value : data.find( '.map-address').attr('value')
43
  },
44
- city : {
45
- element : data.find( '.map-city' ),
46
- value : data.find( '.map-city').attr('value')
47
  },
48
- state : {
49
- element : data.find( '.map-state' ),
50
- value : data.find( '.map-state').attr('value')
51
  },
52
- country : {
53
- element : data.find( '.map-country' ),
54
- value : data.find( '.map-country').attr('value')
55
  },
56
- zipCode : {
57
- element : data.find( '.map-zip' ),
58
- value : data.find( '.map-zip').attr('value')
 
 
 
 
 
 
59
  },
60
- coordinates : {
61
- element : data.find( '.map-coordinates' ),
62
- value : jQuery.parseJSON( data.find( '.map-coordinates').attr('value') )
63
- }
64
  },
65
- map : {
66
- container : data.find( '.map-googlemap' ),
67
- object : {
68
- map : {},
69
- marker : {}
70
- }
 
71
  },
72
- toggles : {
73
- expand : data.find('.fw-option-maps-expand'),
74
- reset : data.find('.fw-option-maps-close')
 
75
  },
76
- tabs : {
77
- first : data.find('.fw-option-maps-tab.first'),
78
- second : data.find('.fw-option-maps-tab.second')
 
79
  },
80
 
81
  getComputedLongAddress: function() {
82
  var longAddress = '';
83
- if ( option.getFreshValue('zipCode') || option.getFreshValue('venue') || option.getFreshValue('address') || option.getFreshValue('city') || option.getFreshValue('state') || option.getFreshValue('country') ){
 
 
 
 
 
 
 
 
84
  //join array without empty fields
85
- longAddress = _.reduce([option.getFreshValue('venue'), option.getFreshValue('address'), option.getFreshValue('city'), option.getFreshValue('state'), option.getFreshValue('country'), option.getFreshValue('zipCode')],
 
 
 
 
 
 
 
 
86
  function(a, b) {
87
- return b = b.trim(), b && (a = a ? a + ", " + b : b), a
88
- }, "");
 
 
 
89
  }
90
 
91
  return longAddress;
92
  },
93
 
94
- refreshMap: function()
95
- {
96
- var googleMapsPos = ( typeof this.fields.coordinates.value === "object" && this.fields.coordinates.value != null ) ?
97
- new google.maps.LatLng( this.fields.coordinates.value.lat, this.fields.coordinates.value.lng ) :
98
- new google.maps.LatLng( -34, 150 );
 
 
 
 
99
 
100
  var mapOptions = {
101
- center : googleMapsPos,
102
- zoom : 15,
103
  mapTypeControl: false,
104
- streetViewControl: false
105
  };
106
 
107
- if (_.isEmpty(this.map.object.map)){
108
- this.map.object.map = new google.maps.Map( option.map.container[0], mapOptions );
 
 
 
109
  }
110
 
111
- if (_.isEmpty(this.map.object.marker)){
112
  this.map.object.marker = new google.maps.Marker({
113
  position: googleMapsPos,
114
  map: option.map.object.map,
115
- draggable: true
116
  });
117
  }
118
 
119
  this.map.object.map.setCenter(googleMapsPos);
120
 
121
- google.maps.event.addListener( this.map.object.marker, 'dragend', function(){
122
- geocoder.geocode({
123
- latLng: this.getPosition()
124
- }, function(responses) {
125
- if (responses && responses.length > 0) {
126
- option.updateFields( responses[0] );
127
- }
128
- });
129
- });
 
 
 
 
 
 
 
130
  },
131
- setValue : function( property, value, encode ) {
132
- this.fields[ property ].value = value;
133
- if( encode )
134
- this.fields[ property ].element.val(JSON.stringify(value));
135
- else
136
- this.fields[ property ].element.val( value );
 
 
137
  },
 
138
  updateMapCoords: function(geoObject) {
139
- if( geoObject == null )
140
- return;
141
 
142
  option.map.object.map.panTo(geoObject.geometry.location);
143
- option.map.object.marker.setPosition( geoObject.geometry.location );
144
- option.setValue( 'coordinates', {
145
- lat: geoObject.geometry.location.lat(),
146
- lng: geoObject.geometry.location.lng()
147
- }, true );
 
 
 
 
 
 
148
  },
149
- updateFields : function( geoObject ){
150
-
151
- option.setValue( 'location', '' );
152
- option.setValue( 'state', '' );
153
- option.setValue( 'country', '');
154
- option.setValue( 'city', '' );
155
- option.setValue( "address", '' );
156
- option.setValue( 'zipCode', '' );
157
- option.setValue( 'venue', '');
158
- option.setValue( 'coordinates', '');
159
-
160
- if( geoObject == null )
161
- return;
162
 
163
  option.updateMapCoords(geoObject);
164
 
165
- for( var i = 0; i < geoObject.address_components.length; i++ ){
 
 
 
 
166
  var current = geoObject.address_components[i];
167
 
168
-
169
  /*if ( current.types[0] == "establishment" )
170
  option.setValue( 'venue', current.long_name );*/
171
 
172
- if ( current.types[0] == "administrative_area_level_1" )
173
- option.setValue( 'state', current.long_name.trim() );
174
-
175
- if ( current.types[0] == "country" )
176
- option.setValue( 'country', current.long_name.trim() );
177
-
178
- if ( current.types[0] == "locality" )
179
- option.setValue( 'city', current.long_name.trim() );
180
-
181
- if ( current.types[0] == "route" )
182
- option.setValue( "address", current.long_name.trim() );
183
-
184
- if ( current.types[0] == "postal_code" )
185
- option.setValue( 'zipCode', current.long_name.trim() );
 
 
 
 
 
 
 
 
 
186
  }
187
 
188
  //option.setValue('location', geoObject.formatted_address.trim() );
189
- option.setValue('location', option.getComputedLongAddress());
190
-
 
 
191
 
192
- if ( typeof geoObject.name != 'undefined' ){
193
  option.setValue('venue', geoObject.name);
194
  }
195
  },
196
- getFreshValue: function( property ) {
197
- return this.fields[ property ].element.val( );
198
- }
 
199
  };
200
 
201
- //Create google map
202
  var geocoder = new google.maps.Geocoder();
203
  option.refreshMap();
204
 
205
- data.on('blur', '.map-city, .map-address, .map-state, .map-country, .map-zip, .map-venue', function(){
206
- var address = option.getComputedLongAddress();
207
- handleGeoCoder(address);
208
- });
 
 
 
 
209
 
210
  // Define autocomplete
211
- var autocomplete = new google.maps.places.Autocomplete( option.fields.location.element[0] );
 
 
 
212
  autocomplete.bindTo('bounds', option.map.object.map);
213
 
214
  // Add events
215
- google.maps.event.addListener( autocomplete, 'place_changed', function(){
216
- var place = autocomplete.getPlace();
217
- if (!place.geometry) {
218
- return;
 
 
 
 
 
 
 
 
 
219
  }
220
- used_autocomplete = true;
221
- option.updateFields( place );
222
- setTimeout( function() {
223
- option.toggles.expand.trigger( 'click' );
224
- }, 200 );
225
- });
226
 
227
- $( option.fields.location.element).keydown(function (e) {
228
- if(e.keyCode === 13){
229
  return false;
230
  }
231
  });
232
 
233
  var handleGeoCoder = function(address) {
234
- "undefined" == typeof geocoder && (geocoder = new google.maps.Geocoder());
235
- geocoder.geocode({
236
- address: address
237
- }, function(responses, status) {
238
-
239
- if ( responses.length > 0 && status === 'OK') {
240
-
241
- option.updateMapCoords( responses[0] );
242
- option.setValue('location', option.getComputedLongAddress());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- } else {
245
- option.setValue( 'coordinates', {
246
- lat: 0,
247
- lng: 0
248
- }, true );
249
  }
 
 
250
 
251
- setTimeout( function() {
252
- option.refreshMap();
253
- }, 200 );
254
-
255
- });
256
- }
257
-
258
- $( option.fields.location.element).pressEnter( function(e) {
259
  var address = option.getFreshValue('location');
260
- geocoder.geocode({
261
- address: address
262
- }, function(responses) {
263
- if ( responses.length > 0) {
264
- if( used_autocomplete ){
265
- used_autocomplete = false;
266
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  }
268
- setTimeout( function() {
269
- option.toggles.expand.trigger( 'click' );
270
- }, 200 );
271
- option.updateFields( responses[0] );
272
- setTimeout( function() {
273
- option.setValue('location', option.getComputedLongAddress());
274
- }, 200 );
275
-
276
  }
277
- });
278
  return false;
279
  });
280
 
281
- option.toggles.expand.on( 'click', function( e ){
282
  e.preventDefault();
283
  option.tabs.first.hide().addClass('closed');
284
  option.tabs.second.show().removeClass('closed');
285
  google.maps.event.trigger(option.map.object.map, 'resize');
286
  option.refreshMap();
287
  });
288
- option.toggles.reset.on( 'click', function( e ){
289
  e.preventDefault();
290
- option.updateFields( null );
291
  option.tabs.second.hide().addClass('closed');
292
  option.tabs.first.show().removeClass('closed');
293
  });
294
 
295
- if (option.fields.location.value){
296
  //open map
297
  option.toggles.expand.trigger('click');
298
  }
@@ -300,16 +379,21 @@
300
 
301
  var pendingInit = [];
302
 
303
- fwe.on('fw:options:init', function (data) {
304
-
305
- var obj = data.$elements.find('.fw-option-type-map:not(.initialized)');
 
306
 
307
  if (!obj.length) {
308
  return;
309
  }
310
 
311
- if (typeof google == 'undefined' || typeof google.maps == 'undefined') {
312
- if (pendingInit.length) { // already in process of loading the script
 
 
 
 
313
  pendingInit.push(obj);
314
  } else {
315
  pendingInit.push(obj);
@@ -319,24 +403,27 @@
319
  * Fixes https://github.com/ThemeFuse/Unyson/issues/1675
320
  */
321
  $.ajax({
322
- type: "GET",
323
  url: localized.google_maps_js_uri,
324
- dataType: "script",
325
- cache: true
326
- }).done(function(){
327
- $.each(pendingInit, function(i, obj){
328
- obj.each(function(){
329
- fw_option_map_initialize($(this));
 
 
330
  });
 
 
 
 
 
 
331
  });
332
- pendingInit = [];
333
- }).fail(function(){
334
- console.error('Failed to load Google Maps script');
335
- pendingInit = [];
336
- });
337
  }
338
  } else {
339
- obj.each(function(){
340
  fw_option_map_initialize($(this));
341
  });
342
  }
@@ -345,4 +432,24 @@
345
  });
346
  });
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  })(jQuery, _, fwEvents, _fw_option_type_map);
1
+ 'use strict';
 
 
2
 
3
+ (function($, _, fwe, localized) {
4
+ jQuery(document).ready(function() {
 
5
  $.fn.pressEnter = function(fn) {
6
  return this.each(function() {
7
  $(this).bind('enterPress', fn);
8
+
9
+ $(this).keyup(function(e) {
10
+ if (e.keyCode == 13) {
11
+ $(this).trigger('enterPress');
12
  }
13
+ });
14
  });
15
  };
16
 
17
+ function fw_option_map_initialize(data) {
18
+ if (typeof google === 'undefined') {
19
+ data.find('.fw-option-map-inputs').attr('readonly', 'readonly');
 
20
  return;
21
  }
22
 
24
 
25
  // Define map option
26
  var option = {
27
+ fields: {
28
+ location: {
29
+ element: data.find('.map-location'),
30
+ value: data.find('.map-location').attr('value'),
31
  },
32
+ venue: {
33
+ element: data.find('.map-venue'),
34
+ value: data.find('.map-venue').attr('value'),
35
  },
36
+ address: {
37
+ element: data.find('.map-address'),
38
+ value: data.find('.map-address').attr('value'),
39
  },
40
+ city: {
41
+ element: data.find('.map-city'),
42
+ value: data.find('.map-city').attr('value'),
43
  },
44
+ state: {
45
+ element: data.find('.map-state'),
46
+ value: data.find('.map-state').attr('value'),
47
  },
48
+ country: {
49
+ element: data.find('.map-country'),
50
+ value: data.find('.map-country').attr('value'),
51
  },
52
+ zipCode: {
53
+ element: data.find('.map-zip'),
54
+ value: data.find('.map-zip').attr('value'),
55
+ },
56
+ coordinates: {
57
+ element: data.find('.map-coordinates'),
58
+ value: jQuery.parseJSON(
59
+ data.find('.map-coordinates').attr('value')
60
+ ),
61
  },
 
 
 
 
62
  },
63
+
64
+ map: {
65
+ container: data.find('.map-googlemap'),
66
+ object: {
67
+ map: {},
68
+ marker: {},
69
+ },
70
  },
71
+
72
+ toggles: {
73
+ expand: data.find('.fw-option-maps-expand'),
74
+ reset: data.find('.fw-option-maps-close'),
75
  },
76
+
77
+ tabs: {
78
+ first: data.find('.fw-option-maps-tab.first'),
79
+ second: data.find('.fw-option-maps-tab.second'),
80
  },
81
 
82
  getComputedLongAddress: function() {
83
  var longAddress = '';
84
+
85
+ if (
86
+ option.getFreshValue('zipCode') ||
87
+ option.getFreshValue('venue') ||
88
+ option.getFreshValue('address') ||
89
+ option.getFreshValue('city') ||
90
+ option.getFreshValue('state') ||
91
+ option.getFreshValue('country')
92
+ ) {
93
  //join array without empty fields
94
+ longAddress = _.reduce(
95
+ [
96
+ option.getFreshValue('venue'),
97
+ option.getFreshValue('address'),
98
+ option.getFreshValue('city'),
99
+ option.getFreshValue('state'),
100
+ option.getFreshValue('country'),
101
+ option.getFreshValue('zipCode'),
102
+ ],
103
  function(a, b) {
104
+ return (b = b.trim()), b &&
105
+ (a = a ? a + ', ' + b : b), a;
106
+ },
107
+ ''
108
+ );
109
  }
110
 
111
  return longAddress;
112
  },
113
 
114
+ refreshMap: function() {
115
+ var googleMapsPos =
116
+ typeof this.fields.coordinates.value === 'object' &&
117
+ this.fields.coordinates.value != null
118
+ ? new google.maps.LatLng(
119
+ this.fields.coordinates.value.lat,
120
+ this.fields.coordinates.value.lng
121
+ )
122
+ : new google.maps.LatLng(-34, 150);
123
 
124
  var mapOptions = {
125
+ center: googleMapsPos,
126
+ zoom: 15,
127
  mapTypeControl: false,
128
+ streetViewControl: false,
129
  };
130
 
131
+ if (_.isEmpty(this.map.object.map)) {
132
+ this.map.object.map = new google.maps.Map(
133
+ option.map.container[0],
134
+ mapOptions
135
+ );
136
  }
137
 
138
+ if (_.isEmpty(this.map.object.marker)) {
139
  this.map.object.marker = new google.maps.Marker({
140
  position: googleMapsPos,
141
  map: option.map.object.map,
142
+ draggable: true,
143
  });
144
  }
145
 
146
  this.map.object.map.setCenter(googleMapsPos);
147
 
148
+ google.maps.event.addListener(
149
+ this.map.object.marker,
150
+ 'dragend',
151
+ function() {
152
+ geocoder.geocode(
153
+ {
154
+ latLng: this.getPosition(),
155
+ },
156
+ function(responses) {
157
+ if (responses && responses.length > 0) {
158
+ option.updateFields(responses[0]);
159
+ }
160
+ }
161
+ );
162
+ }
163
+ );
164
  },
165
+
166
+ setValue: function(property, value, encode) {
167
+ this.fields[property].value = value;
168
+ if (encode)
169
+ this.fields[property].element.val(
170
+ JSON.stringify(value)
171
+ );
172
+ else this.fields[property].element.val(value);
173
  },
174
+
175
  updateMapCoords: function(geoObject) {
176
+ if (geoObject == null) return;
 
177
 
178
  option.map.object.map.panTo(geoObject.geometry.location);
179
+ option.map.object.marker.setPosition(
180
+ geoObject.geometry.location
181
+ );
182
+ option.setValue(
183
+ 'coordinates',
184
+ {
185
+ lat: geoObject.geometry.location.lat(),
186
+ lng: geoObject.geometry.location.lng(),
187
+ },
188
+ true
189
+ );
190
  },
191
+
192
+ updateFields: function(geoObject) {
193
+ option.setValue('location', '');
194
+ option.setValue('state', '');
195
+ option.setValue('country', '');
196
+ option.setValue('city', '');
197
+ option.setValue('address', '');
198
+ option.setValue('zipCode', '');
199
+ option.setValue('venue', '');
200
+ option.setValue('coordinates', '');
201
+
202
+ if (geoObject == null) return;
 
203
 
204
  option.updateMapCoords(geoObject);
205
 
206
+ for (
207
+ var i = 0;
208
+ i < geoObject.address_components.length;
209
+ i++
210
+ ) {
211
  var current = geoObject.address_components[i];
212
 
 
213
  /*if ( current.types[0] == "establishment" )
214
  option.setValue( 'venue', current.long_name );*/
215
 
216
+ if (current.types[0] == 'administrative_area_level_1')
217
+ option.setValue('state', current.long_name.trim());
218
+
219
+ if (current.types[0] == 'country')
220
+ option.setValue(
221
+ 'country',
222
+ current.long_name.trim()
223
+ );
224
+
225
+ if (current.types[0] == 'locality')
226
+ option.setValue('city', current.long_name.trim());
227
+
228
+ if (current.types[0] == 'route')
229
+ option.setValue(
230
+ 'address',
231
+ current.long_name.trim()
232
+ );
233
+
234
+ if (current.types[0] == 'postal_code')
235
+ option.setValue(
236
+ 'zipCode',
237
+ current.long_name.trim()
238
+ );
239
  }
240
 
241
  //option.setValue('location', geoObject.formatted_address.trim() );
242
+ option.setValue(
243
+ 'location',
244
+ option.getComputedLongAddress()
245
+ );
246
 
247
+ if (typeof geoObject.name != 'undefined') {
248
  option.setValue('venue', geoObject.name);
249
  }
250
  },
251
+
252
+ getFreshValue: function(property) {
253
+ return this.fields[property].element.val();
254
+ },
255
  };
256
 
257
+ // Create google map
258
  var geocoder = new google.maps.Geocoder();
259
  option.refreshMap();
260
 
261
+ data.on(
262
+ 'blur',
263
+ '.map-city, .map-address, .map-state, .map-country, .map-zip, .map-venue',
264
+ function() {
265
+ var address = option.getComputedLongAddress();
266
+ handleGeoCoder(address);
267
+ }
268
+ );
269
 
270
  // Define autocomplete
271
+ var autocomplete = new google.maps.places.Autocomplete(
272
+ option.fields.location.element[0]
273
+ );
274
+
275
  autocomplete.bindTo('bounds', option.map.object.map);
276
 
277
  // Add events
278
+ google.maps.event.addListener(
279
+ autocomplete,
280
+ 'place_changed',
281
+ function() {
282
+ var place = autocomplete.getPlace();
283
+ if (!place.geometry) {
284
+ return;
285
+ }
286
+ used_autocomplete = true;
287
+ option.updateFields(place);
288
+ setTimeout(function() {
289
+ option.toggles.expand.trigger('click');
290
+ }, 200);
291
  }
292
+ );
 
 
 
 
 
293
 
294
+ $(option.fields.location.element).keydown(function(e) {
295
+ if (e.keyCode === 13) {
296
  return false;
297
  }
298
  });
299
 
300
  var handleGeoCoder = function(address) {
301
+ 'undefined' == typeof geocoder &&
302
+ (geocoder = new google.maps.Geocoder());
303
+ geocoder.geocode(
304
+ {
305
+ address: address,
306
+ },
307
+ function(responses, status) {
308
+ if (responses.length > 0 && status === 'OK') {
309
+ option.updateMapCoords(responses[0]);
310
+ option.setValue(
311
+ 'location',
312
+ option.getComputedLongAddress()
313
+ );
314
+ } else {
315
+ option.setValue(
316
+ 'coordinates',
317
+ {
318
+ lat: 0,
319
+ lng: 0,
320
+ },
321
+ true
322
+ );
323
+ }
324
 
325
+ setTimeout(function() {
326
+ option.refreshMap();
327
+ }, 200);
 
 
328
  }
329
+ );
330
+ };
331
 
332
+ $(option.fields.location.element).pressEnter(function(e) {
 
 
 
 
 
 
 
333
  var address = option.getFreshValue('location');
334
+ geocoder.geocode(
335
+ {
336
+ address: address,
337
+ },
338
+ function(responses) {
339
+ if (responses.length > 0) {
340
+ if (used_autocomplete) {
341
+ used_autocomplete = false;
342
+ return;
343
+ }
344
+ setTimeout(function() {
345
+ option.toggles.expand.trigger('click');
346
+ }, 200);
347
+ option.updateFields(responses[0]);
348
+ setTimeout(function() {
349
+ option.setValue(
350
+ 'location',
351
+ option.getComputedLongAddress()
352
+ );
353
+ }, 200);
354
  }
 
 
 
 
 
 
 
 
355
  }
356
+ );
357
  return false;
358
  });
359
 
360
+ option.toggles.expand.on('click', function(e) {
361
  e.preventDefault();
362
  option.tabs.first.hide().addClass('closed');
363
  option.tabs.second.show().removeClass('closed');
364
  google.maps.event.trigger(option.map.object.map, 'resize');
365
  option.refreshMap();
366
  });
367
+ option.toggles.reset.on('click', function(e) {
368
  e.preventDefault();
369
+ option.updateFields(null);
370
  option.tabs.second.hide().addClass('closed');
371
  option.tabs.first.show().removeClass('closed');
372
  });
373
 
374
+ if (option.fields.location.value) {
375
  //open map
376
  option.toggles.expand.trigger('click');
377
  }
379
 
380
  var pendingInit = [];
381
 
382
+ fwe.on('fw:options:init', function(data) {
383
+ var obj = data.$elements.find(
384
+ '.fw-option-type-map:not(.initialized)'
385
+ );
386
 
387
  if (!obj.length) {
388
  return;
389
  }
390
 
391
+ if (
392
+ typeof google == 'undefined' ||
393
+ typeof google.maps == 'undefined'
394
+ ) {
395
+ if (pendingInit.length) {
396
+ // already in process of loading the script
397
  pendingInit.push(obj);
398
  } else {
399
  pendingInit.push(obj);
403
  * Fixes https://github.com/ThemeFuse/Unyson/issues/1675
404
  */
405
  $.ajax({
406
+ type: 'GET',
407
  url: localized.google_maps_js_uri,
408
+ dataType: 'script',
409
+ cache: true,
410
+ })
411
+ .done(function() {
412
+ $.each(pendingInit, function(i, obj) {
413
+ obj.each(function() {
414
+ fw_option_map_initialize($(this));
415
+ });
416
  });
417
+
418
+ pendingInit = [];
419
+ })
420
+ .fail(function() {
421
+ console.error('Failed to load Google Maps script');
422
+ pendingInit = [];
423
  });
 
 
 
 
 
424
  }
425
  } else {
426
+ obj.each(function() {
427
  fw_option_map_initialize($(this));
428
  });
429
  }
432
  });
433
  });
434
 
435
+ fw.options.register('map', {
436
+ getValue: function (optionDescriptor) {
437
+ var promise = $.Deferred()
438
+
439
+ fw.options
440
+ .getContextValue(optionDescriptor.el)
441
+ .then(function (result) {
442
+ result.value.coordinates = JSON.parse(
443
+ result.value.coordinates
444
+ );
445
+
446
+ promise.resolve({
447
+ value: result.value,
448
+ optionDescriptor: optionDescriptor
449
+ });
450
+ });
451
+
452
+ return promise;
453
+ }
454
+ })
455
  })(jQuery, _, fwEvents, _fw_option_type_map);
framework/includes/option-types/multi-picker/class-fw-option-type-multi-picker.php CHANGED
@@ -65,6 +65,17 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
65
  return true;
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * @internal
70
  * {@inheritdoc}
@@ -81,12 +92,22 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
81
  $option['attr']['class'] .= ' fw-option-type-multi-picker-without-borders';
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
84
  /**
85
  * Leave only select choice options to be rendered in the browser
86
  * the rest move to attr[data-options-template] to be rendered on choice change.
87
  * This should improve page loading speed.
88
  */
89
- {
90
  {
91
  reset($option['picker']);
92
  $picker_key = key($option['picker']);
@@ -157,7 +178,9 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
157
  * @param string $picker_type
158
  * @return array( 'choice_id' => array( Choice Options ) )
159
  */
160
- private function get_picker_choices($option, $picker, $picker_type) {
 
 
161
  switch($picker_type) {
162
  case 'switch':
163
  $picker_choices = array_intersect_key($option['choices'], array(
@@ -220,17 +243,9 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
220
  */
221
  $option = $this->prepare_choices($option);
222
 
223
- {
224
- reset($option['picker']);
225
- $picker_key = key($option['picker']);
226
- $picker = $option['picker'][$picker_key];
227
- $picker_type = $picker['type'];
228
- }
229
 
230
  $picker_choices = $this->get_picker_choices(
231
- $option,
232
- $picker,
233
- $picker_type
234
  );
235
 
236
  $hide_picker = '';
@@ -255,6 +270,7 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
255
  }
256
 
257
  $choices_groups = array();
 
258
  foreach ($picker_choices as $key => $set) {
259
  if (!empty($set)) {
260
  $choices_groups[$id . '-' . $key] = array(
@@ -276,17 +292,29 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
276
  }
277
  }
278
 
279
- $picker_group = array(
280
- $id . '-picker' => array(
281
- 'type' => 'group',
282
- 'desc' => false,
283
- 'label' => false,
284
- 'attr' => array('class' => $show_borders .' '. $hide_picker .' picker-group picker-type-'. $picker_type),
285
- 'options' => array($picker_key => $picker)
286
- )
287
- );
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
- return array_merge($picker_group, $choices_groups);
290
  }
291
 
292
  /**
@@ -306,6 +334,7 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
306
  if (is_array($settings['for'])) {
307
  // Insert location: after/before.
308
  $location = fw_akg('location', $settings, 'before');
 
309
  foreach ($settings['for'] as $name) {
310
  if (isset($choices[$name])) {
311
  if ('before' === $location) {
@@ -318,7 +347,19 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
318
  );
319
  }
320
  } else {
321
- $result[$name] = $settings['options'];
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
  }
324
  }
@@ -330,7 +371,9 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
330
  }
331
  }
332
 
 
333
  fw_aks('choices', $result, $option);
 
334
  return $option;
335
  }
336
 
@@ -339,13 +382,6 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
339
  */
340
  protected function _get_value_from_input($option, $input_value)
341
  {
342
- {
343
- reset($option['picker']);
344
- $picker_key = key($option['picker']);
345
- $picker_type = $option['picker'][$picker_key]['type'];
346
- $picker = $option['picker'][$picker_key];
347
- }
348
-
349
  $value = array();
350
 
351
  /**
@@ -353,20 +389,25 @@ class FW_Option_Type_Multi_Picker extends FW_Option_Type
353
  */
354
  $option = $this->prepare_choices($option);
355
 
356
- if (is_null($input_value) && isset($option['value'][$picker_key])) {
357
- $value[$picker_key] = $option['value'][$picker_key];
358
- } else {
359
- $value[$picker_key] = fw()->backend->option_type($picker_type)->get_value_from_input(
360
- $picker,
361
- isset($input_value[$picker_key]) ? $input_value[$picker_key] : null
362
- );
 
 
 
 
 
 
 
363
  }
364
 
365
  foreach (
366
  $this->get_picker_choices(
367
- $option,
368
- $picker,
369
- $picker_type
370
  )
371
  as $choice_id => $choice_options
372
  ) {
65
  return true;
66
  }
67
 
68
+ public function _get_data_for_js($id, $option, $data = array()) {
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * Hide label for each multi-picker by default
74
+ */
75
+ public function _default_label($id, $option) {
76
+ return false;
77
+ }
78
+
79
  /**
80
  * @internal
81
  * {@inheritdoc}
92
  $option['attr']['class'] .= ' fw-option-type-multi-picker-without-borders';
93
  }
94
 
95
+ $option['attr']['class'] .= is_array(
96
+ $option['picker']
97
+ ) ? '' : ' fw-option-type-multi-picker-dynamic';
98
+
99
+ // Allow picker to be another option in the same context
100
+ // JS will watch its changes accordingly
101
+ if (is_string($option['picker'])) {
102
+ $option['attr']['data-fw-dynamic-picker-path'] = $option['picker'];
103
+ }
104
+
105
  /**
106
  * Leave only select choice options to be rendered in the browser
107
  * the rest move to attr[data-options-template] to be rendered on choice change.
108
  * This should improve page loading speed.
109
  */
110
+ if (is_array($option['picker'])) {
111
  {
112
  reset($option['picker']);
113
  $picker_key = key($option['picker']);
178
  * @param string $picker_type
179
  * @return array( 'choice_id' => array( Choice Options ) )
180
  */
181
+ private function get_picker_choices($option) {
182
+ return $option['choices'];
183
+
184
  switch($picker_type) {
185
  case 'switch':
186
  $picker_choices = array_intersect_key($option['choices'], array(
243
  */
244
  $option = $this->prepare_choices($option);
245
 
 
 
 
 
 
 
246
 
247
  $picker_choices = $this->get_picker_choices(
248
+ $option
 
 
249
  );
250
 
251
  $hide_picker = '';
270
  }
271
 
272
  $choices_groups = array();
273
+
274
  foreach ($picker_choices as $key => $set) {
275
  if (!empty($set)) {
276
  $choices_groups[$id . '-' . $key] = array(
292
  }
293
  }
294
 
295
+ $picker_group = null;
296
+
297
+ if (is_array($option['picker'])) {
298
+ {
299
+ reset($option['picker']);
300
+ $picker_key = key($option['picker']);
301
+ $picker = $option['picker'][$picker_key];
302
+ $picker_type = $picker['type'];
303
+ }
304
+
305
+ $picker_group = array(
306
+ $id . '-picker' => array(
307
+ 'type' => 'group',
308
+ 'desc' => false,
309
+ 'label' => false,
310
+ 'attr' => array('class' => $show_borders .' '. $hide_picker .' picker-group picker-type-'. $picker_type),
311
+ 'options' => array($picker_key => $picker)
312
+ )
313
+ );
314
+
315
+ }
316
 
317
+ return $picker_group ? array_merge($picker_group, $choices_groups) : $choices_groups;
318
  }
319
 
320
  /**
334
  if (is_array($settings['for'])) {
335
  // Insert location: after/before.
336
  $location = fw_akg('location', $settings, 'before');
337
+
338
  foreach ($settings['for'] as $name) {
339
  if (isset($choices[$name])) {
340
  if ('before' === $location) {
347
  );
348
  }
349
  } else {
350
+ if (isset($result[$name])) {
351
+ if ('before' === $location) {
352
+ $result[$name] = array_merge(
353
+ $settings['options'], $result[$name]
354
+ );
355
+ } else {
356
+ $result[$name] = array_merge(
357
+ $result[$name], $settings['options']
358
+ );
359
+ }
360
+ } else {
361
+ $result[$name] = $settings['options'];
362
+ }
363
  }
364
  }
365
  }
371
  }
372
  }
373
 
374
+ // Replace old `choices` with new structure.
375
  fw_aks('choices', $result, $option);
376
+
377
  return $option;
378
  }
379
 
382
  */
383
  protected function _get_value_from_input($option, $input_value)
384
  {
 
 
 
 
 
 
 
385
  $value = array();
386
 
387
  /**
389
  */
390
  $option = $this->prepare_choices($option);
391
 
392
+ if (is_array($option['picker'])) {
393
+ reset($option['picker']);
394
+ $picker_key = key($option['picker']);
395
+ $picker_type = $option['picker'][$picker_key]['type'];
396
+ $picker = $option['picker'][$picker_key];
397
+
398
+ if (is_null($input_value) && isset($option['value'][$picker_key])) {
399
+ $value[$picker_key] = $option['value'][$picker_key];
400
+ } else {
401
+ $value[$picker_key] = fw()->backend->option_type($picker_type)->get_value_from_input(
402
+ $picker,
403
+ isset($input_value[$picker_key]) ? $input_value[$picker_key] : null
404
+ );
405
+ }
406
  }
407
 
408
  foreach (
409
  $this->get_picker_choices(
410
+ $option
 
 
411
  )
412
  as $choice_id => $choice_options
413
  ) {
framework/includes/option-types/multi-picker/static/js/multi-picker.js CHANGED
@@ -1,78 +1,176 @@
1
  (function($, fwe) {
2
- var init = function() {
3
- var $this = $(this),
4
- elements = {
5
- $pickerGroup: $this.find('> .picker-group'),
6
- $choicesGroups: $this.find('> .choice-group')
7
- },
8
- chooseGroup = function(groupId) {
9
- var $choicesToReveal = elements.$choicesGroups.filter('.choice-group[data-choice-key="'+ groupId +'"]');
10
-
11
- /**
12
- * The group options html was rendered in an attribute to make page load faster.
13
- * Move the html from attribute in group and init options with js.
14
- */
15
- if ($choicesToReveal.attr('data-options-template')) {
16
- $choicesToReveal.html(
17
- $choicesToReveal.attr('data-options-template')
18
- );
19
-
20
- $choicesToReveal.removeAttr('data-options-template');
21
-
22
- fwEvents.trigger('fw:options:init', {
23
- $elements: $choicesToReveal
24
- });
 
 
 
 
 
 
 
 
 
 
 
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- elements.$choicesGroups.removeClass('chosen');
28
  $choicesToReveal.addClass('chosen');
29
 
30
  if ($choicesToReveal.length) {
31
- $this.addClass('has-choice');
32
  } else {
33
- $this.removeClass('has-choice');
34
- }
35
- },
36
- pickerType = elements.$pickerGroup.attr('class').match(/picker-type-(\S+)/)[1],
37
- flows = {
38
- 'switch': function() {
39
- elements.$pickerGroup.find(':checkbox').on('change', function() {
40
- var $this = $(this),
41
- checked = $(this).is(':checked'),
42
- value = JSON.parse($this.attr('data-switch-'+ (checked ? 'right' : 'left') +'-value-json'));
43
-
44
- chooseGroup(value);
45
- }).trigger('change');
46
- },
47
- 'select': function() {
48
- elements.$pickerGroup.find('select').on('change', function() {
49
- chooseGroup(this.value);
50
- }).trigger('change');
51
- },
52
- 'short-select': function() {
53
- this.select();
54
- },
55
- 'radio': function() {
56
- elements.$pickerGroup.find(':radio').on('change', function() {
57
- chooseGroup(this.value);
58
- }).filter(':checked').trigger('change');
59
- },
60
- 'image-picker': function() {
61
- elements.$pickerGroup.find('select').on('change', function() {
62
- chooseGroup(this.value);
63
- }).trigger('change');
64
- },
65
- 'icon-v2': function () {
66
- var iconV2Selector = '.fw-option-type-icon-v2 > input';
67
-
68
- elements.$pickerGroup.find(iconV2Selector).on('change', function() {
69
- var type = JSON.parse(this.value)['type'];
70
- chooseGroup(type);
71
- }).trigger('change');
72
  }
73
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- if (!pickerType) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  console.error('unknown multi-picker type:', pickerType);
77
  } else {
78
  if (flows[pickerType]) {
@@ -92,9 +190,7 @@
92
  }
93
  };
94
 
95
- fwe.on('fw:options:init', function(data) {
96
- data.$elements
97
- .find('.fw-option-type-multi-picker:not(.fw-option-initialized)').each(init)
98
- .addClass('fw-option-initialized');
99
- });
100
  })(jQuery, fwEvents);
1
  (function($, fwe) {
2
+
3
+ fwe.on('fw:options:init', function(data) {
4
+
5
+ data.$elements
6
+ .find(
7
+ '.fw-option-type-multi-picker:not(.fw-option-initialized)'
8
+ )
9
+ .not(
10
+ '.fw-option-type-multi-picker-dynamic'
11
+ )
12
+ .each(initSimpleMultiPicker)
13
+ .addClass('fw-option-initialized');
14
+
15
+ data.$elements
16
+ .find(
17
+ '.fw-option-type-multi-picker.fw-option-type-multi-picker-dynamic'
18
+ )
19
+ .not(
20
+ '.fw-option-initialized'
21
+ )
22
+ .each(initDynamicMultiPicker)
23
+ .addClass('fw-option-initialized');
24
+
25
+ });
26
+
27
+ fwe.on('fw:options:teardown', function (data) {
28
+
29
+ data.$elements
30
+ .find(
31
+ '.fw-option-type-multi-picker.fw-option-type-multi-picker-dynamic'
32
+ ).filter('.fw-option-initialized')
33
+ .each(function () {
34
+ if ($(this).data().fwPickerListener) {
35
+ fw.options.off.change($(this).data().fwPickerListener);
36
  }
37
+ })
38
+ })
39
+
40
+ function initDynamicMultiPicker () {
41
+ var $container = $(this);
42
+
43
+ $container.addClass('fw-option-initialized');
44
+
45
+ var optionDescriptor = fw.options.getOptionDescriptor($container[0]);
46
+
47
+ var pickerDescriptor = fw.options.findOptionInSameContextFor(
48
+ optionDescriptor.el,
49
+ $container.attr('data-fw-dynamic-picker-path')
50
+ );
51
+
52
+ $container.find('> .choice-group').first().addClass('chosen');
53
+
54
+ $container.data('fw-picker-listener', handleChange);
55
+
56
+ fw.options.on.change($container.data().fwPickerListener);
57
+
58
+ chooseGroupForOptionDescriptor(pickerDescriptor);
59
+
60
+ function handleChange (optionDescriptor) {
61
+ if (pickerDescriptor.el === optionDescriptor.el) {
62
+ chooseGroupForOptionDescriptor(optionDescriptor);
63
+ }
64
+ }
65
+
66
+ function chooseGroupForOptionDescriptor (optionDescriptor) {
67
+ fw.options.getValueForEl(pickerDescriptor.el).then(function (value) {
68
+ if (! _.isString(value.value)) {
69
+ throw "Your picker returned a non-string value. In order for it to work with multi-pickers it should yield string values";
70
+ }
71
+
72
+ chooseGroup(value.value);
73
+ });
74
+
75
+ function chooseGroup(groupId) {
76
+ var $choicesGroups = $container.find('> .choice-group');
77
+
78
+ var $choicesToReveal = $container.find(
79
+ '.choice-group[data-choice-key="'+ groupId +'"]'
80
+ );
81
 
82
+ $choicesGroups.removeClass('chosen');
83
  $choicesToReveal.addClass('chosen');
84
 
85
  if ($choicesToReveal.length) {
86
+ $container.addClass('has-choice');
87
  } else {
88
+ $container.removeClass('has-choice');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
  };
91
+ }
92
+
93
+ }
94
+
95
+ function initSimpleMultiPicker() {
96
+ var $this = $(this);
97
+
98
+ var elements = {
99
+ $pickerGroup: $this.find('> .picker-group'),
100
+ $choicesGroups: $this.find('> .choice-group')
101
+ };
102
+
103
+ var chooseGroup = function(groupId) {
104
+ var $choicesToReveal = elements.$choicesGroups.filter('.choice-group[data-choice-key="'+ groupId +'"]');
105
+
106
+ /**
107
+ * The group options html was rendered in an attribute to make page load faster.
108
+ * Move the html from attribute in group and init options with js.
109
+ */
110
+ if ($choicesToReveal.attr('data-options-template')) {
111
+ $choicesToReveal.html(
112
+ $choicesToReveal.attr('data-options-template')
113
+ );
114
+
115
+ $choicesToReveal.removeAttr('data-options-template');
116
+
117
+ fwEvents.trigger('fw:options:init', {
118
+ $elements: $choicesToReveal
119
+ });
120
+ }
121
+
122
+ elements.$choicesGroups.removeClass('chosen');
123
+ $choicesToReveal.addClass('chosen');
124
+
125
+ if ($choicesToReveal.length) {
126
+ $this.addClass('has-choice');
127
+ } else {
128
+ $this.removeClass('has-choice');
129
+ }
130
+ };
131
 
132
+
133
+ var pickerType = elements.$pickerGroup.attr('class').match(/picker-type-(\S+)/)[1];
134
+
135
+ var flows = {
136
+ 'switch': function() {
137
+ elements.$pickerGroup.find(':checkbox').on('change', function() {
138
+ var $this = $(this),
139
+ checked = $(this).is(':checked'),
140
+ value = JSON.parse($this.attr('data-switch-'+ (checked ? 'right' : 'left') +'-value-json'));
141
+
142
+ chooseGroup(value);
143
+ }).trigger('change');
144
+ },
145
+ 'select': function() {
146
+ elements.$pickerGroup.find('select').on('change', function() {
147
+ chooseGroup(this.value);
148
+ }).trigger('change');
149
+ },
150
+ 'short-select': function() {
151
+ this.select();
152
+ },
153
+ 'radio': function() {
154
+ elements.$pickerGroup.find(':radio').on('change', function() {
155
+ chooseGroup(this.value);
156
+ }).filter(':checked').trigger('change');
157
+ },
158
+ 'image-picker': function() {
159
+ elements.$pickerGroup.find('select').on('change', function() {
160
+ chooseGroup(this.value);
161
+ }).trigger('change');
162
+ },
163
+ 'icon-v2': function () {
164
+ var iconV2Selector = '.fw-option-type-icon-v2 > input';
165
+
166
+ elements.$pickerGroup.find(iconV2Selector).on('change', function() {
167
+ var type = JSON.parse(this.value)['type'];
168
+ chooseGroup(type);
169
+ }).trigger('change');
170
+ }
171
+ };
172
+
173
+ if (! pickerType) {
174
  console.error('unknown multi-picker type:', pickerType);
175
  } else {
176
  if (flows[pickerType]) {
190
  }
191
  };
192
 
193
+ fw.options.register('multi-picker', {
194
+ getValue: fw.options.get('multi').getValue
195
+ })
 
 
196
  })(jQuery, fwEvents);
framework/includes/option-types/multi-select/class-fw-option-type-multi-select.php CHANGED
@@ -48,6 +48,10 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
48
  * Set maximum items number that can be selected
49
  */
50
  'limit' => 100,
 
 
 
 
51
  );
52
  }
53
 
@@ -68,41 +72,31 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
68
  );
69
  }
70
 
71
- private static function query_posts(array $limits) {
72
  $limits = array_merge(array(
73
  'type' => array(
74
  'post' => true,
75
  'page' => true,
76
  ),
77
  'title' => '',
78
- 'id' => array( /* 1, 7, 120 */ ),
79
- 'limit' => 100,
80
- ), $limits);
81
-
82
- $limits['limit'] = max($limits['limit'], 1);
83
 
84
  /** @var WPDB $wpdb */
85
  global $wpdb;
86
 
87
- $sql = "SELECT ID val, post_title title"
88
- ." FROM $wpdb->posts"
89
- ." WHERE post_status IN ( 'publish', 'private' )";
90
- //." AND NULLIF(post_password, '') IS NULL"; todo: review
91
 
92
  {
93
  $prepare = array();
94
 
95
- if ($limits['id']) {
96
- $sql .= " AND ID IN ( "
97
- . implode( ', ', array_fill( 1, count( $limits['id'] ), '%d' ) )
98
- . " ) ";
99
- $prepare = array_merge($prepare, $limits['id']);
100
- }
101
-
102
  if ($limits['type']) {
103
  $sql .= " AND post_type IN ( "
104
- . implode( ', ', array_fill( 1, count( $limits['type'] ), '%s' ) )
105
- . " ) ";
106
  $prepare = array_merge($prepare, array_keys($limits['type']));
107
  }
108
 
@@ -112,17 +106,45 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
112
  }
113
  }
114
 
115
- $sql .= " LIMIT ". intval($limits['limit']);
116
-
117
- return $wpdb->get_results(
118
  $prepare
119
  ? $wpdb->prepare($sql, $prepare)
120
  : $sql,
121
  ARRAY_A
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  );
123
  }
124
 
125
- private static function query_terms(array $limits) {
126
  $limits = array_merge(array(
127
  'taxonomy' => array(
128
  'category' => true,
@@ -130,31 +152,23 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
130
  'title' => '',
131
  'id' => array( /* 1, 7, 120 */ ),
132
  'limit' => 100,
133
- ), $limits);
134
-
135
- $limits['limit'] = max($limits['limit'], 1);
136
 
137
  /** @var WPDB $wpdb */
138
  global $wpdb;
139
 
140
- $sql = "SELECT terms.term_id val, terms.name title"
141
- ." FROM $wpdb->terms AS terms, $wpdb->term_taxonomy AS taxonomies"
142
- ." WHERE terms.term_id = taxonomies.term_id AND taxonomies.term_id = taxonomies.term_taxonomy_id";
143
 
144
  {
145
  $prepare = array();
146
 
147
- if ($limits['id']) {
148
- $sql .= " AND terms.term_id IN ( "
149
- . implode( ', ', array_fill( 1, count( $limits['id'] ), '%d' ) )
150
- . " ) ";
151
- $prepare = array_merge($prepare, $limits['id']);
152
- }
153
-
154
  if ($limits['taxonomy']) {
155
  $sql .= " AND taxonomies.taxonomy IN ( "
156
- . implode( ', ', array_fill( 1, count( $limits['taxonomy'] ), '%s' ) )
157
- . " ) ";
158
  $prepare = array_merge($prepare, array_keys($limits['taxonomy']));
159
  }
160
 
@@ -164,13 +178,51 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
164
  }
165
  }
166
 
167
- $sql .= " LIMIT ". intval($limits['limit']);
 
 
 
 
 
 
 
 
168
 
169
- return $wpdb->get_results(
170
- $prepare
171
- ? $wpdb->prepare($sql, $prepare)
172
- : $sql,
173
- ARRAY_A
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  );
175
  }
176
 
@@ -190,25 +242,25 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
190
  global $wpdb;
191
 
192
  $sql = "SELECT DISTINCT users.ID AS val, users.user_nicename AS title"
193
- ." FROM $wpdb->users AS users, $wpdb->usermeta AS usermeta"
194
- ." WHERE usermeta.user_id = users.ID";
195
 
196
  {
197
  $prepare = array();
198
 
199
  if ($limits['id']) {
200
  $sql .= " AND users.ID IN ( "
201
- . implode( ', ', array_fill( 1, count( $limits['id'] ), '%d' ) )
202
- . " ) ";
203
  $prepare = array_merge($prepare, $limits['id']);
204
  }
205
 
206
  if ($limits['role']) {
207
  $sql .= " AND usermeta.meta_key = '{$wpdb->prefix}capabilities' "
208
- . "AND ( "
209
- . implode( ' OR ',
210
  array_fill( 1, count( $limits['role'] ), 'usermeta.meta_value LIKE %s' ) ) .
211
- " ) ";
212
 
213
  foreach ( $limits['role'] as $name => $filter_by ) {
214
  $prepare[] = ( $filter_by ) ? '%' . $wpdb->esc_like( $name ) . '%' : '';
@@ -242,21 +294,33 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
242
  $type = FW_Request::POST( 'data/type' );
243
  $names = ($names = json_decode( FW_Request::POST( 'data/names' ), true )) ? $names : array();
244
  $title = FW_Request::POST( 'data/string' );
 
245
 
246
  $items = array();
247
 
248
  switch ( $type ) {
249
  case 'posts':
250
- $items = self::query_posts(array(
251
- 'type' => array_fill_keys($names, true),
252
  'title' => $title,
253
- ));
 
 
 
 
 
254
  break;
255
  case 'taxonomy':
256
  $items = self::query_terms(array(
257
  'taxonomy' => array_fill_keys($names, true),
258
  'title' => $title,
259
  ));
 
 
 
 
 
 
260
  break;
261
  case 'users':
262
  $items = self::query_users(array(
@@ -283,6 +347,7 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
283
  $population = $option['population'];
284
  $source = array();
285
  $items = array();
 
286
 
287
  if ( isset( $option['population'] ) ) {
288
  switch ( $option['population'] ) {
@@ -299,22 +364,61 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
299
  case 'posts' :
300
  if ( isset( $option['source'] ) ) {
301
  $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
302
- $items = self::query_posts(array(
303
- 'type' => array_fill_keys($source, true),
304
- 'id' => $data['value'],
305
- 'limit' => $option['prepopulate'],
306
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  }
 
 
 
 
 
308
  break;
309
  case 'taxonomy' :
310
  if ( isset( $option['source'] ) ) {
311
  $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
312
- $items = self::query_terms(array(
313
- 'taxonomy' => array_fill_keys($source, true),
314
- 'id' => $data['value'],
315
- 'limit' => $option['prepopulate'],
316
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  }
 
 
 
 
 
 
318
  break;
319
  case 'users' :
320
  if ( isset( $option['source'] ) && ! empty( $option['source'] ) ) {
@@ -333,10 +437,11 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
333
  return '(Invalid <code>population</code> parameter)';
334
  }
335
 
336
- $option['attr']['data-options'] = json_encode( $items );
337
- $option['attr']['data-population'] = $population;
338
- $option['attr']['data-source'] = json_encode( $source );
339
- $option['attr']['data-limit'] = ( intval( $option['limit'] ) > 0 ) ? $option['limit'] : 0;
 
340
  } else {
341
  return '(The <code>population</code> parameter is required)';
342
  }
@@ -384,8 +489,28 @@ if ( ! class_exists( 'FW_Option_Type_Multi_Select' ) ):
384
 
385
  return empty( $input_value ) ? array() : $value;
386
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  }
388
 
389
  add_action( 'wp_ajax_fw_option_type_multi_select_autocomplete',
390
  array( "FW_Option_Type_Multi_Select", '_ajax_autocomplete' ) );
391
- endif;
48
  * Set maximum items number that can be selected
49
  */
50
  'limit' => 100,
51
+ /**
52
+ * Show the post type or term taxonomy
53
+ */
54
+ 'show-type' => false
55
  );
56
  }
57
 
72
  );
73
  }
74
 
75
+ private static function query_posts(array $options) {
76
  $limits = array_merge(array(
77
  'type' => array(
78
  'post' => true,
79
  'page' => true,
80
  ),
81
  'title' => '',
82
+ ), $options);
83
+ fw_aku('limit', $limits);
 
 
 
84
 
85
  /** @var WPDB $wpdb */
86
  global $wpdb;
87
 
88
+ $sql = "SELECT ID"
89
+ ." FROM $wpdb->posts"
90
+ ." WHERE post_status IN ( 'publish', 'private' )";
91
+ //." AND NULLIF(post_password, '') IS NULL"; todo: review
92
 
93
  {
94
  $prepare = array();
95
 
 
 
 
 
 
 
 
96
  if ($limits['type']) {
97
  $sql .= " AND post_type IN ( "
98
+ . implode( ', ', array_fill( 1, count( $limits['type'] ), '%s' ) )
99
+ . " ) ";
100
  $prepare = array_merge($prepare, array_keys($limits['type']));
101
  }
102
 
106
  }
107
  }
108
 
109
+ $ids = wp_list_pluck($wpdb->get_results(
 
 
110
  $prepare
111
  ? $wpdb->prepare($sql, $prepare)
112
  : $sql,
113
  ARRAY_A
114
+ ), 'ID');
115
+
116
+ return self::get_posts( $ids, max( fw_akg( 'limit', $options, 100 ), 1 ) );
117
+ }
118
+
119
+ protected static function get_posts( $ids, $limit = 100 ) {
120
+ if ( empty( $ids ) ) {
121
+ return array();
122
+ }
123
+
124
+ $query = new WP_Query( array(
125
+ 'post_type' => 'any',
126
+ 'post__in' => $ids,
127
+ 'posts_per_page' => $limit,
128
+ 'fields' => 'ids'
129
+ ) );
130
+
131
+ return $query->get_posts();
132
+ }
133
+
134
+ protected static function build_post( $id, $show_type ) {
135
+ $title = get_the_title( $id );
136
+
137
+ return $show_type ? array(
138
+ 'val' => $id,
139
+ 'title' => $title,
140
+ 'type' => self::get_post_type_name( get_post_type( $id ) ),
141
+ ) : array(
142
+ 'val' => $id,
143
+ 'title' => $title
144
  );
145
  }
146
 
147
+ private static function query_terms(array $options) {
148
  $limits = array_merge(array(
149
  'taxonomy' => array(
150
  'category' => true,
152
  'title' => '',
153
  'id' => array( /* 1, 7, 120 */ ),
154
  'limit' => 100,
155
+ ), $options);
156
+ fw_aku('limit', $limits);
 
157
 
158
  /** @var WPDB $wpdb */
159
  global $wpdb;
160
 
161
+ $sql = "SELECT terms.term_id"
162
+ ." FROM $wpdb->terms AS terms, $wpdb->term_taxonomy AS taxonomies"
163
+ ." WHERE terms.term_id = taxonomies.term_id AND taxonomies.term_id = taxonomies.term_taxonomy_id";
164
 
165
  {
166
  $prepare = array();
167
 
 
 
 
 
 
 
 
168
  if ($limits['taxonomy']) {
169
  $sql .= " AND taxonomies.taxonomy IN ( "
170
+ . implode( ', ', array_fill( 1, count( $limits['taxonomy'] ), '%s' ) )
171
+ . " ) ";
172
  $prepare = array_merge($prepare, array_keys($limits['taxonomy']));
173
  }
174
 
178
  }
179
  }
180
 
181
+ $ids = wp_list_pluck(
182
+ $wpdb->get_results(
183
+ $prepare
184
+ ? $wpdb->prepare( $sql, $prepare )
185
+ : $sql,
186
+ ARRAY_A
187
+ ),
188
+ 'term_id'
189
+ );
190
 
191
+ return self::get_terms( $ids, array_keys($limits['taxonomy']), max( fw_akg( 'limit', $options, 100 ), 1 ) );
192
+ }
193
+
194
+ protected static function get_terms( $ids, $taxonomy = array(), $limit = 100 ) {
195
+ if ( empty( $ids ) ) {
196
+ return array();
197
+ }
198
+
199
+ $terms = get_terms( array(
200
+ 'taxonomy' => $taxonomy,
201
+ 'hide_empty' => false,
202
+ 'include' => $ids,
203
+ 'number' => $limit,
204
+ 'fields' => 'ids'
205
+ ) );
206
+
207
+ return is_wp_error( $terms ) ? array() : $terms;
208
+ }
209
+
210
+ protected static function build_term( $id, $show_type ) {
211
+ $term = get_term( $id );
212
+
213
+ if ( is_wp_error( $term ) ) {
214
+ return null;
215
+ }
216
+
217
+ $title = $term->name;
218
+
219
+ return $show_type ? array(
220
+ 'val' => $id,
221
+ 'title' => $title,
222
+ 'type' => self::get_tax_name( $term->taxonomy ),
223
+ ) : array(
224
+ 'val' => $id,
225
+ 'title' => $title
226
  );
227
  }
228
 
242
  global $wpdb;
243
 
244
  $sql = "SELECT DISTINCT users.ID AS val, users.user_nicename AS title"
245
+ ." FROM $wpdb->users AS users, $wpdb->usermeta AS usermeta"
246
+ ." WHERE usermeta.user_id = users.ID";
247
 
248
  {
249
  $prepare = array();
250
 
251
  if ($limits['id']) {
252
  $sql .= " AND users.ID IN ( "
253
+ . implode( ', ', array_fill( 1, count( $limits['id'] ), '%d' ) )
254
+ . " ) ";
255
  $prepare = array_merge($prepare, $limits['id']);
256
  }
257
 
258
  if ($limits['role']) {
259
  $sql .= " AND usermeta.meta_key = '{$wpdb->prefix}capabilities' "
260
+ . "AND ( "
261
+ . implode( ' OR ',
262
  array_fill( 1, count( $limits['role'] ), 'usermeta.meta_value LIKE %s' ) ) .
263
+ " ) ";
264
 
265
  foreach ( $limits['role'] as $name => $filter_by ) {
266
  $prepare[] = ( $filter_by ) ? '%' . $wpdb->esc_like( $name ) . '%' : '';
294
  $type = FW_Request::POST( 'data/type' );
295
  $names = ($names = json_decode( FW_Request::POST( 'data/names' ), true )) ? $names : array();
296
  $title = FW_Request::POST( 'data/string' );
297
+ $show = FW_Request::POST( 'data/show-type', false );
298
 
299
  $items = array();
300
 
301
  switch ( $type ) {
302
  case 'posts':
303
+ $items = self::query_posts( array(
304
+ 'type' => array_fill_keys( $names, true ),
305
  'title' => $title,
306
+ ) );
307
+ $items = array_map(
308
+ array( __CLASS__, 'build_post' ),
309
+ $items,
310
+ array_fill( 0, count($items), $show )
311
+ );
312
  break;
313
  case 'taxonomy':
314
  $items = self::query_terms(array(
315
  'taxonomy' => array_fill_keys($names, true),
316
  'title' => $title,
317
  ));
318
+
319
+ $items = array_map(
320
+ array( __CLASS__, 'build_term' ),
321
+ $items,
322
+ array_fill( 1, count( $items ), $show )
323
+ );
324
  break;
325
  case 'users':
326
  $items = self::query_users(array(
347
  $population = $option['population'];
348
  $source = array();
349
  $items = array();
350
+ $show = fw_akg( 'show-type', $option, false );
351
 
352
  if ( isset( $option['population'] ) ) {
353
  switch ( $option['population'] ) {
364
  case 'posts' :
365
  if ( isset( $option['source'] ) ) {
366
  $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
367
+
368
+ $items = self::get_posts( $data['value'] );
369
+
370
+ if ( count( $items ) < $option['prepopulate'] ) {
371
+ foreach (
372
+ self::query_posts( array(
373
+ 'type' => array_fill_keys( $source, true ),
374
+ 'limit' => $option['prepopulate'],
375
+ ) ) as $item
376
+ ) {
377
+ if ( ! in_array( $item, $items ) ) {
378
+ array_push( $items, $item );
379
+ }
380
+
381
+ if ( count( $items ) == $option['prepopulate'] ) {
382
+ break;
383
+ }
384
+ }
385
+ }
386
  }
387
+ $items = array_map(
388
+ array( $this, 'build_post' ),
389
+ $items,
390
+ array_fill( 1, count( $items ), $show )
391
+ );
392
  break;
393
  case 'taxonomy' :
394
  if ( isset( $option['source'] ) ) {
395
  $source = is_array( $option['source'] ) ? $option['source'] : array( $option['source'] );
396
+
397
+ $items = self::get_terms( $data['value'], $source );
398
+
399
+ if ( count( $items ) < $option['prepopulate'] ) {
400
+ foreach (
401
+ self::query_terms( array(
402
+ 'taxonomy' => array_fill_keys( $source, true ),
403
+ 'limit' => $option['prepopulate'],
404
+ ) ) as $item
405
+ ) {
406
+ if ( ! in_array( $item, $items ) ) {
407
+ array_push( $items, $item );
408
+ }
409
+
410
+ if ( count( $items ) == $option['prepopulate'] ) {
411
+ break;
412
+ }
413
+ }
414
+ }
415
  }
416
+
417
+ $items = array_map(
418
+ array( $this, 'build_term' ),
419
+ $items,
420
+ array_fill( 1, count( $items ), $show )
421
+ );
422
  break;
423
  case 'users' :
424
  if ( isset( $option['source'] ) && ! empty( $option['source'] ) ) {
437
  return '(Invalid <code>population</code> parameter)';
438
  }
439
 
440
+ $option['attr']['data-options'] = json_encode( $items );
441
+ $option['attr']['data-population'] = $population;
442
+ $option['attr']['data-show-type'] = (int)fw_akg( 'show-type', $option, false );
443
+ $option['attr']['data-source'] = json_encode( $source );
444
+ $option['attr']['data-limit'] = ( intval( $option['limit'] ) > 0 ) ? $option['limit'] : 0;
445
  } else {
446
  return '(The <code>population</code> parameter is required)';
447
  }
489
 
490
  return empty( $input_value ) ? array() : $value;
491
  }
492
+
493
+ private static function get_post_type_name( $type ) {
494
+ static $names = array();
495
+
496
+ if ( ! isset( $names[ $type ] ) ) {
497
+ $names[$type]=fw_akg( 'labels/name', get_post_type_object( $type ), _x( 'Unknown', 'unknown-post-type', 'fw' ) );
498
+ }
499
+
500
+ return $names[ $type ];
501
+ }
502
+
503
+ private static function get_tax_name( $tax ) {
504
+ static $names = array();
505
+
506
+ if ( ! isset( $names[ $tax ] ) ) {
507
+ $names[$tax]=fw_akg( 'labels/name', get_taxonomy( $tax ), _x( 'Unknown', 'unknown-post-type', 'fw' ) );
508
+ }
509
+
510
+ return $names[ $tax ];
511
+ }
512
  }
513
 
514
  add_action( 'wp_ajax_fw_option_type_multi_select_autocomplete',
515
  array( "FW_Option_Type_Multi_Select", '_ajax_autocomplete' ) );
516
+ endif;
framework/includes/option-types/multi-select/static/css/style.css CHANGED
@@ -9,4 +9,23 @@
9
 
10
  .fw-backend-option-input-type-multi-select .fw-option-help-in-input {
11
  top: 4px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
9
 
10
  .fw-backend-option-input-type-multi-select .fw-option-help-in-input {
11
  top: 4px !important;
12
+ }
13
+
14
+ .fw-backend-option-type-multi-select .selectize-control .selectize-dropdown .selectize-dropdown-content div > .type:before,
15
+ .fw-backend-option-type-multi-select .selectize-control .selectize-input > div > .type:before {
16
+ content: "(";
17
+ margin-right: 1px;
18
+ }
19
+
20
+ .fw-backend-option-type-multi-select .selectize-control .selectize-dropdown .selectize-dropdown-content div > .type:after,
21
+ .fw-backend-option-type-multi-select .selectize-control .selectize-input > div > .type:after {
22
+ content: ")";
23
+ margin-left: 1px;
24
+ }
25
+
26
+ .fw-backend-option-type-multi-select .selectize-control .selectize-dropdown .selectize-dropdown-content div > .type,
27
+ .fw-backend-option-type-multi-select .selectize-control .selectize-input > div > .type {
28
+ opacity: .5;
29
+ font-size: 80%;
30
+ margin-left: 5px;
31
  }
framework/includes/option-types/multi-select/static/js/scripts.js CHANGED
@@ -1,7 +1,7 @@
1
  (function ($) {
2
  var xhr,
3
  optionsCache = {},
4
- ajaxAutocompleteCallback = _.throttle(function(selectize, value, population, source, hash){
5
  selectize.load(function(callback){
6
  xhr && xhr.abort();
7
 
@@ -12,7 +12,8 @@
12
  data: {
13
  string: value,
14
  type: population,
15
- names: source
 
16
  }
17
  },
18
  function (response) {
@@ -24,10 +25,10 @@
24
 
25
  callback(response.data);
26
 
27
- optionsCache[hash] = []; // transform object to array
28
- $.each(selectize.options, function(i, o){
29
- optionsCache[hash].push(o);
30
- });
31
  }
32
  );
33
  });
@@ -35,12 +36,12 @@
35
 
36
  function init() {
37
  var $this = $(this);
38
-
39
  $this.one('fw:option-type:multi-select:init', function () {
40
  var population = $this.attr('data-population'),
41
  source = $this.attr('data-source'),
 
42
  limit = parseInt($this.attr('data-limit')),
43
- hash = fw.md5(JSON.stringify([population, source])),
44
  options = (
45
  typeof optionsCache[hash] == 'undefined'
46
  ? JSON.parse($this.attr('data-options'))
@@ -55,12 +56,24 @@
55
  searchField: 'title',
56
  options: options,
57
  create: false,
 
 
 
 
 
 
 
 
 
 
 
 
58
  onType: function (value) {
59
  if (population == 'array' || value.length < 2) {
60
  return;
61
  }
62
 
63
- ajaxAutocompleteCallback(this, value, population, source, hash);
64
  }
65
  });
66
 
@@ -73,7 +86,7 @@
73
  });
74
  });
75
 
76
- if ($this.val().length) { // there are values that needs to be show right away
77
  $this.trigger('fw:option-type:multi-select:init');
78
  } else {
79
  $this.one('focus', function(){
1
  (function ($) {
2
  var xhr,
3
  optionsCache = {},
4
+ ajaxAutocompleteCallback = _.throttle(function(selectize, value, population, source, show_type, hash){
5
  selectize.load(function(callback){
6
  xhr && xhr.abort();
7
 
12
  data: {
13
  string: value,
14
  type: population,
15
+ names: source,
16
+ "show-type": show_type,
17
  }
18
  },
19
  function (response) {
25
 
26
  callback(response.data);
27
 
28
+ optionsCache[hash] = [] // transform object to array
29
+ $.each(selectize.options, function (i, o) {
30
+ optionsCache[hash].push(o)
31
+ })
32
  }
33
  );
34
  });
36
 
37
  function init() {
38
  var $this = $(this);
 
39
  $this.one('fw:option-type:multi-select:init', function () {
40
  var population = $this.attr('data-population'),
41
  source = $this.attr('data-source'),
42
+ show_type = !!parseInt($this.attr('data-show-type')),
43
  limit = parseInt($this.attr('data-limit')),
44
+ hash = fw.md5(JSON.stringify([population, source, show_type])),
45
  options = (
46
  typeof optionsCache[hash] == 'undefined'
47
  ? JSON.parse($this.attr('data-options'))
56
  searchField: 'title',
57
  options: options,
58
  create: false,
59
+ render: {
60
+ option: function (item) {
61
+ var title = '<span class="title">' + item.title + '</span>'
62
+ var type = item.type !== undefined ? '<span class="type">' + item.type + '</span>' : ''
63
+ return '<div>' + title + type + '</div>'
64
+ },
65
+ item: function (item) {
66
+ var title = '<span class="title">' + item.title + '</span>'
67
+ var type = item.type !== undefined ? '<span class="type">' + item.type + '</span>' : ''
68
+ return '<div>' + title + type + '</div>'
69
+ }
70
+ },
71
  onType: function (value) {
72
  if (population == 'array' || value.length < 2) {
73
  return;
74
  }
75
 
76
+ ajaxAutocompleteCallback(this, value, population, source, show_type, hash);
77
  }
78
  });
79
 
86
  });
87
  });
88
 
89
+ if ($this.val().length || $this.is(':focus')) { // there are values that needs to be show right away
90
  $this.trigger('fw:option-type:multi-select:init');
91
  } else {
92
  $this.one('focus', function(){
framework/includes/option-types/multi-upload/class-fw-option-type-multi-upload.php CHANGED
@@ -33,6 +33,10 @@ class FW_Option_Type_Multi_Upload extends FW_Option_Type
33
  return 'auto';
34
  }
35
 
 
 
 
 
36
  /**
37
  * @internal
38
  */
@@ -245,4 +249,4 @@ class FW_Option_Type_Multi_Upload extends FW_Option_Type
245
  }
246
  return $return_arr;
247
  }
248
- }
33
  return 'auto';
34
  }
35
 
36
+ protected function _get_data_for_js($id, $option, $data = array()) {
37
+ return false;
38
+ }
39
+
40
  /**
41
  * @internal
42
  */
249
  }
250
  return $return_arr;
251
  }
252
+ }
framework/includes/option-types/multi-upload/static/js/any-files.js CHANGED
@@ -101,9 +101,12 @@
101
  $element: elements.$container,
102
  attachments: attachments
103
  });
 
104
  elements.$container.trigger('fw:option-type:multi-upload:change', {
105
  attachments: attachments
106
  });
 
 
107
  });
108
  };
109
 
@@ -125,6 +128,11 @@
125
  fwe.trigger('fw:option-type:multi-upload:clear', {$element: elements.$container});
126
  elements.$container.trigger('fw:option-type:multi-upload:clear');
127
 
 
 
 
 
 
128
  e.preventDefault();
129
  });
130
  };
@@ -135,4 +143,18 @@
135
  .addClass('fw-option-initialized');
136
  });
137
 
138
- })(jQuery, fwEvents);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  $element: elements.$container,
102
  attachments: attachments
103
  });
104
+
105
  elements.$container.trigger('fw:option-type:multi-upload:change', {
106
  attachments: attachments
107
  });
108
+
109
+ triggerChangeForIds(elements.$container, ids);
110
  });
111
  };
112
 
128
  fwe.trigger('fw:option-type:multi-upload:clear', {$element: elements.$container});
129
  elements.$container.trigger('fw:option-type:multi-upload:clear');
130
 
131
+ triggerChangeForIds(elements.$container, {
132
+ attachments: [],
133
+ value: {}
134
+ });
135
+
136
  e.preventDefault();
137
  });
138
  };
143
  .addClass('fw-option-initialized');
144
  });
145
 
146
+ function triggerChangeForIds ($container, attachment_ids) {
147
+ fw.options.trigger.changeForEl($container, {
148
+ attachments: attachment_ids.map(wp.media.attachment),
149
+ value: attachment_ids.map(extractSingleAttachmentData)
150
+ });
151
+
152
+ function extractSingleAttachmentData (attachment_id) {
153
+ return {
154
+ attachment_id: attachment_id,
155
+ url: wp.media.attachment(attachment_id).get('url')
156
+ };
157
+ }
158
+ }
159
+
160
+ })(jQuery, fwEvents);
framework/includes/option-types/multi-upload/static/js/images-only.js CHANGED
@@ -132,9 +132,12 @@
132
  $element: elements.$container,
133
  attachments: attachments
134
  });
 
135
  elements.$container.trigger('fw:option-type:multi-upload:change', {
136
  attachments: attachments
137
  });
 
 
138
  });
139
  };
140
 
@@ -170,6 +173,8 @@
170
  elements.$container.trigger('fw:option-type:multi-upload:clear');
171
  }
172
 
 
 
173
  e.preventDefault();
174
  });
175
 
@@ -178,6 +183,7 @@
178
  update: function () {
179
  var ids = collectThumbsIds();
180
  elements.$input.val(JSON.stringify(ids));
 
181
  }
182
  });
183
  };
@@ -188,4 +194,42 @@
188
  .addClass('fw-option-initialized');
189
  });
190
 
191
- })(jQuery, _, fwEvents);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  $element: elements.$container,
133
  attachments: attachments
134
  });
135
+
136
  elements.$container.trigger('fw:option-type:multi-upload:change', {
137
  attachments: attachments
138
  });
139
+
140
+ triggerChangeForIds(elements.$container, ids);
141
  });
142
  };
143
 
173
  elements.$container.trigger('fw:option-type:multi-upload:clear');
174
  }
175
 
176
+ triggerChangeForIds(elements.$container, ids);
177
+
178
  e.preventDefault();
179
  });
180
 
183
  update: function () {
184
  var ids = collectThumbsIds();
185
  elements.$input.val(JSON.stringify(ids));
186
+ triggerChangeForIds(elements.$container, ids);
187
  }
188
  });
189
  };
194
  .addClass('fw-option-initialized');
195
  });
196
 
197
+ fw.options.register('multi-upload', {
198
+ startListeningForChanges: $.noop,
199
+ getValue: function (optionDescriptor) {
200
+ var ids = [];
201
+
202
+ $(optionDescriptor.el).find('.thumb').each(function () {
203
+ ids.push($(this).data('attid'));
204
+ });
205
+
206
+ var value = getValueForIds(ids);
207
+ value.optionDescriptor = optionDescriptor;
208
+
209
+ return value;
210
+ }
211
+ });
212
+
213
+ function getValueForIds (ids) {
214
+ return {
215
+ attachments: ids.map(wp.media.attachment),
216
+ value: ids.map(extractSingleAttachmentData)
217
+ };
218
+
219
+ function extractSingleAttachmentData (attachment_id) {
220
+ return {
221
+ attachment_id: attachment_id,
222
+ url: wp.media.attachment(attachment_id).get('url')
223
+ };
224
+ }
225
+ }
226
+
227
+ function triggerChangeForIds ($container, attachment_ids) {
228
+ fw.options.trigger.changeForEl(
229
+ $container,
230
+ getValueForIds(attachment_ids)
231
+ );
232
+ }
233
+
234
+
235
+ })(jQuery, _, fwEvents);
framework/includes/option-types/multi/class-fw-option-type-multi.php CHANGED
@@ -54,6 +54,14 @@ class FW_Option_Type_Multi extends FW_Option_Type
54
  '</div>';
55
  }
56
 
 
 
 
 
 
 
 
 
57
  /**
58
  * @internal
59
  */
@@ -95,4 +103,4 @@ class FW_Option_Type_Multi extends FW_Option_Type
95
  'value' => array(),
96
  );
97
  }
98
- }
54
  '</div>';
55
  }
56
 
57
+ public function _get_data_for_js($id, $option, $data = array()) {
58
+ return false;
59
+ }
60
+
61
+ public function _default_label($id, $option) {
62
+ return false;
63
+ }
64
+
65
  /**
66
  * @internal
67
  */
103
  'value' => array(),
104
  );
105
  }
106
+ }
framework/includes/option-types/popup/class-fw-option-type-popup.php CHANGED
@@ -12,6 +12,10 @@ class FW_Option_Type_Popup extends FW_Option_Type {
12
  return 'fixed';
13
  }
14
 
 
 
 
 
15
  /**
16
  * @internal
17
  * {@inheritdoc}
@@ -194,4 +198,4 @@ class FW_Option_Type_Popup extends FW_Option_Type {
194
  );
195
  }
196
 
197
- }
12
  return 'fixed';
13
  }
14
 
15
+ protected function _get_data_for_js($id, $option, $data = array()) {
16
+ return false;
17
+ }
18
+
19
  /**
20
  * @internal
21
  * {@inheritdoc}
198
  );
199
  }
200
 
201
+ }
framework/includes/option-types/popup/static/js/popup.js CHANGED
@@ -51,6 +51,10 @@
51
  'change:values': function (modal, values) {
52
  utils.editItem(utils.modal.get('itemRef'), values);
53
 
 
 
 
 
54
  fwEvents.trigger('fw:option-type:popup:change', {
55
  element: $this,
56
  values: values
@@ -94,4 +98,17 @@
94
  .find('.fw-option-type-popup:not(.fw-option-initialized)').each(popup)
95
  .addClass('fw-option-initialized');
96
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  })(jQuery, _, fwEvents, window);
51
  'change:values': function (modal, values) {
52
  utils.editItem(utils.modal.get('itemRef'), values);
53
 
54
+ fw.options.trigger.changeForEl(utils.modal.get('itemRef'), {
55
+ value: values
56
+ });
57
+
58
  fwEvents.trigger('fw:option-type:popup:change', {
59
  element: $this,
60
  values: values
98
  .find('.fw-option-type-popup:not(.fw-option-initialized)').each(popup)
99
  .addClass('fw-option-initialized');
100
  });
101
+
102
+ fw.options.register('popup', {
103
+ startListeningForChanges: $.noop,
104
+ getValue: function (optionDescriptor) {
105
+ return {
106
+ value: JSON.parse(
107
+ $(optionDescriptor.el).find('[type="hidden"]').val()
108
+ ),
109
+
110
+ optionDescriptor: optionDescriptor
111
+ }
112
+ }
113
+ });
114
  })(jQuery, _, fwEvents, window);
framework/includes/option-types/range-slider/class-fw-option-type-range-slider.php CHANGED
@@ -46,6 +46,10 @@ class FW_Option_Type_Range_Slider extends FW_Option_Type {
46
  return 'range-slider';
47
  }
48
 
 
 
 
 
49
  /**
50
  * @internal
51
  */
@@ -149,4 +153,4 @@ class FW_Option_Type_Range_Slider extends FW_Option_Type {
149
  return $option;
150
  }
151
 
152
- }
46
  return 'range-slider';
47
  }
48
 
49
+ protected function _get_data_for_js($id, $option, $data = array()) {
50
+ return false;
51
+ }
52
+
53
  /**
54
  * @internal
55
  */
153
  return $option;
154
  }
155
 
156
+ }
framework/includes/option-types/range-slider/static/js/scripts.js CHANGED
@@ -7,7 +7,35 @@
7
  data.$elements.find('.fw-option-type-range-slider:not(.initialized)').each(function () {
8
  var options = JSON.parse($(this).attr('data-fw-irs-options'));
9
  $(this).find('.fw-irs-range-slider').ionRangeSlider(_.defaults(options, defaults));
 
 
 
 
 
 
10
  }).addClass('initialized');
11
  });
12
 
13
- })(jQuery, fwEvents);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  data.$elements.find('.fw-option-type-range-slider:not(.initialized)').each(function () {
8
  var options = JSON.parse($(this).attr('data-fw-irs-options'));
9
  $(this).find('.fw-irs-range-slider').ionRangeSlider(_.defaults(options, defaults));
10
+
11
+ $(this).find('.fw-irs-range-slider').on('change', _.throttle(function (e) {
12
+ fw.options.trigger.changeForEl(e.target, {
13
+ value: getValueForEl(e.target)
14
+ })
15
+ }, 300));
16
  }).addClass('initialized');
17
  });
18
 
19
+ fw.options.register('range-slider', {
20
+ startListeningForChanges: $.noop,
21
+ getValue: function (optionDescriptor) {
22
+ return {
23
+ value: getValueForEl(
24
+ $(optionDescriptor.el).find('[type="text"]')[0]
25
+ ),
26
+
27
+ optionDescriptor: optionDescriptor
28
+ }
29
+ }
30
+ });
31
+
32
+ function getValueForEl (el) {
33
+ var rangeArray = el.value.split(';');
34
+
35
+ return {
36
+ from: rangeArray[0],
37
+ to: rangeArray[1]
38
+ }
39
+ }
40
+
41
+ })(jQuery, fwEvents);
framework/includes/option-types/rgba-color-picker/static/js/scripts.js CHANGED
@@ -1,285 +1,266 @@
1
  jQuery(function($){
2
- /**
3
- * fixme: maybe make these simple functions and do not clog Color.prototype
4
- */
5
- {
6
- Color.prototype.toString = function (remove_alpha) {
7
- if (remove_alpha == 'no-alpha') {
8
- return this.toCSS('rgba', '1').replace(/\s+/g, '');
9
- }
10
- if (this._alpha < 1) {
11
- return this.toCSS('rgba', this._alpha).replace(/\s+/g, '');
12
- }
13
- var hex = parseInt(this._color, 10).toString(16);
14
- if (this.error) return '';
15
- if (hex.length < 6) {
16
- for (var i = 6 - hex.length - 1; i >= 0; i--) {
17
- hex = '0' + hex;
18
- }
19
- }
20
- return '#' + hex;
21
- };
22
-
23
- Color.prototype.toHex = function () {
24
- var hex = parseInt(this._color, 10).toString(16);
25
- if (this.error) return '';
26
- if (hex.length < 6) {
27
- for (var i = 6 - hex.length - 1; i >= 0; i--) {
28
- hex = '0' + hex;
29
- }
30
- }
31
- return '#' + hex;
32
- };
33
- }
34
-
35
- var helpers = {
36
- optionClass: 'fw-option-type-rgba-color-picker',
37
- eventNamespace: '.fwOptionTypeRgbaColorPicker',
38
- hexColorRegex: /^#([a-f0-9]{3}){1,2}$/i,
39
- localized: window._fw_option_type_rgba_color_picker_localized,
40
- increment: 0,
41
- isColorDark: function(rgbaColor) {
42
- var r, g, b, o = 1;
43
-
44
- if (this.hexColorRegex.test(rgbaColor)) {
45
- var color = rgbaColor.substring(1); // remove #
46
-
47
- r = parseInt(color.substr(0,2),16);
48
- g = parseInt(color.substr(2,2),16);
49
- b = parseInt(color.substr(4,2),16);
50
- } else {
51
- var rgba = rgbaColor
52
- .replace(/^(rgb|rgba)\(/, '')
53
- .replace(/\)$/, '')
54
- .replace(/\s/g, '')
55
- .split(',');
56
-
57
- r = rgba[0];
58
- g = rgba[1];
59
- b = rgba[2];
60
- o = rgba[3];
61
- }
62
-
63
- var yiq = ((r*299)+(g*587)+(b*114))/1000;
64
-
65
- return yiq < 128 && o > 0.4;
66
- },
67
- isColorValid: function(rgbaColor) {
68
- return !Color(rgbaColor).error;
69
- },
70
- getInstance: function ($iris) {
71
- return $iris.data('a8cIris');
72
- },
73
- updatePreview: function ($input, color) {
74
- if (this.isColorValid(color)) {
75
- $input.css({
76
- 'background-color': color,
77
- 'color': this.isColorDark(color) ? '#FFFFFF' : '#000000'
78
- });
79
- } else {
80
- $input.css({
81
- 'background-color': '',
82
- 'color': ''
83
- });
84
- }
85
- }
86
- };
87
-
88
- fwEvents.on('fw:options:init', function (data) {
89
- data.$elements.find('input.'+ helpers.optionClass +':not(.initialized)').each(function () {
90
- var $input = $(this),
91
- changeTimeoutId = 0,
92
- eventNamespace = helpers.eventNamespace +'_'+ (++helpers.increment);
93
-
94
- /**
95
- * Improvement: Initialized picker only on first focus
96
- * Do not initialize all pickers on the page, for performance reasons, maybe none of them will be opened
97
- */
98
- $input.one('focus', function(){
99
- if (!$.trim($input.val()).length) { // If the input value is empty, there a glitches with opacity slider
100
- $input.val('rgba(255,255,255,1)');
101
- }
102
-
103
- $input.iris({
104
- palettes: JSON.parse($input.attr('data-palettes')),
105
- defaultColor: false,
106
- change: function (event, ui) {
107
- var $transparency = $input.next('.iris-picker').find('.transparency');
108
- $transparency.css('backgroundColor', ui.color.toString('no-alpha'));
109
-
110
- $alphaSlider.slider("option", "value", ui.color._alpha * 100);
111
-
112
- clearTimeout(changeTimeoutId);
113
- changeTimeoutId = setTimeout(function(){
114
- $input.trigger('fw:option-type:rgba-color-picker:change', {
115
- $element: $input,
116
- iris: $input.data('a8cIris'),
117
- alphaSlider: $alphaSlider.data('uiSlider')
118
- });
119
- $input.trigger('change');
120
- }, 12);
121
- }
122
- });
123
-
124
- var $picker = helpers.getInstance($input).picker;
125
-
126
- $picker.addClass(helpers.optionClass +'-iris');
127
-
128
- /**
129
- * Hide if clicked outside option
130
- */
131
- {
132
- $input.parent().attr('id', 'fw-rgba-color-picker-r-'+ (++helpers.increment));
133
-
134
- var originalShowCallback = helpers.getInstance($input).show;
135
-
136
- helpers.getInstance($input).show = function () {
137
- $(document.body)
138
- .off('click'+ eventNamespace)
139
- .on('click'+ eventNamespace, function(e){
140
- if (!$(e.target).closest('#'+ $input.parent().attr('id')).length) {
141
- $(document.body).off('click'+ eventNamespace);
142
- $input.iris('hide');
143
- }
144
- });
145
-
146
- originalShowCallback.apply(this);
147
- };
148
- }
149
-
150
- /**
151
- * After the second hide the picker is not showing on the next focus (I don't know why)
152
- * Show it manually
153
- */
154
- $input.on('focus', function(){
155
- if (!$picker.is(':visible')) {
156
- $input.iris('show');
157
- }
158
- });
159
-
160
- $input.on('change keyup blur', function () {
161
- // iris::change is not triggered when the input is empty or color is wrong
162
- helpers.updatePreview($input, $input.val());
163
- });
164
-
165
- $(''
166
- + '<div class="fw-alpha-container">'
167
- + /**/'<div class="slider-alpha"></div>'
168
- + /**/'<div class="transparency"></div>'
169
- + '</div>'
170
- ).appendTo($input.next('.iris-picker'));
171
-
172
- var $alphaSlider = $input.next('.iris-picker:first').find('.slider-alpha');
173
-
174
- $alphaSlider.slider({
175
- value: Color($input.val())._alpha * 100,
176
- range: "max",
177
- step: 1,
178
- min: 0,
179
- max: 100,
180
- slide: function (event, ui) {
181
- $(this).find('.ui-slider-handle').text(ui.value);
182
-
183
- $input.data('a8cIris')._color._alpha = parseFloat(ui.value) / 100.0;
184
-
185
- var color = $input.iris('color', true),
186
- cssColor = (
187
- (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex()
188
- ).replace(/\s/g, '');
189
-
190
- $input.val(cssColor);
191
-
192
- clearTimeout(changeTimeoutId);
193
- changeTimeoutId = setTimeout(function(){
194
- $input.trigger('fw:option-type:rgba-color-picker:change', {
195
- $element: $input,
196
- iris: $input.data('a8cIris'),
197
- alphaSlider: $alphaSlider.data('uiSlider')
198
- });
199
- $input.trigger('change');
200
- }, 12);
201
- },
202
- create: function (event, ui) {
203
- var v = $(this).slider('value'),
204
- $transparency = $input.next('.iris-picker:first').find('.transparency');
205
-
206
- $(this).find('.ui-slider-handle').text(v);
207
-
208
- $transparency.css('backgroundColor', Color($input.val()).toCSS('rgb', 1));
209
- },
210
- change: function (event, ui) {
211
- $(this).find('.ui-slider-handle').text(ui.value);
212
-
213
- $input.data('a8cIris')._color._alpha = parseFloat(ui.value) / 100.0;
214
-
215
- var color = $input.iris('color', true),
216
- cssColor = (
217
- (ui.value < 100) ? color.toCSS('rgba', ui.value / 100) : color.toHex()
218
- ).replace(/\s/g, '');
219
-
220
- $input.val(cssColor);
221
-
222
- clearTimeout(changeTimeoutId);
223
- changeTimeoutId = setTimeout(function(){
224
- $input.trigger('fw:option-type:rgba-color-picker:change', {
225
- $element: $input,
226
- iris: $input.data('a8cIris'),
227
- alphaSlider: $alphaSlider.data('uiSlider')
228
- });
229
- $input.trigger('change');
230
- }, 12);
231
- }
232
- });
233
-
234
- var $firstPalette = $picker.find('.iris-palette-container > .iris-palette:first-child');
235
-
236
- /**
237
- * "Reset" color button
238
- */
239
- $.each([{
240
- color: $input.attr('data-default'),
241
- text: helpers.localized.l10n.reset_to_default
242
- },{
243
- color: $input.val(),
244
- text: helpers.localized.l10n.reset_to_initial
245
- }], function(i, data){
246
- if (data.color && helpers.isColorValid(data.color)) {
247
- $picker.find('> .iris-picker-inner').append(''
248
- + '<div class="' + helpers.optionClass + '-reset-default fw-pull-left">'
249
- + /**/'<a class="iris-palette" style="'
250
- + /**//**/'background-color:'+ data.color +';'
251
- + /**//**/'height:' + $firstPalette.css('height') + ';'
252
- + /**//**/'width:' + $firstPalette.css('width') + ';'
253
- + /**//**/'"></a>'
254
- + /**/'<span>' + data.text + '</span>'
255
- + '</div>'
256
- );
257
-
258
- $picker
259
- .on(
260
- 'click',
261
- '.' + helpers.optionClass + '-reset-default',
262
- function(){
263
- $input.iris('color', $(this).find('.iris-palette').css('background-color'));
264
- }
265
- )
266
- .on('remove', function(){
267
- $(document.body).off(eventNamespace);
268
- })
269
- .addClass(helpers.optionClass + '-with-reset-default')
270
- .css('height', parseFloat($picker.css('height')) + 17);
271
-
272
- return false;
273
- }
274
- });
275
-
276
- $input.iris('show');
277
- });
278
-
279
- helpers.updatePreview($input, $input.val());
280
-
281
- $input.addClass('initialized');
282
- });
283
- });
284
 
285
  });
1
  jQuery(function($){
2
+ Color.prototype.toStr = function (remove_alpha) {
3
+ if (remove_alpha == 'no-alpha') {
4
+ return this.toRgbaCSS(1).replace(/\s+/g, '');
5
+ }
6
+
7
+ return this.toRgbaCSS(this._alpha).replace(/\s+/g, '');
8
+ };
9
+ Color.prototype.toRgba = function (a) {
10
+ var rgb = this.toRgb();
11
+ rgb['a'] = a || 1
12
+ return rgb;
13
+ }
14
+ Color.prototype.toRgbaCSS = function (a) {
15
+ var rgba = this.toRgba(a);
16
+ return 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a +')';
17
+ }
18
+
19
+ var helpers = {
20
+ optionClass: 'fw-option-type-rgba-color-picker',
21
+ eventNamespace: '.fwOptionTypeRgbaColorPicker',
22
+ hexColorRegex: /^#([a-f0-9]{3}){1,2}$/i,
23
+ localized: window._fw_option_type_rgba_color_picker_localized,
24
+ increment: 0,
25
+ isColorDark: function(rgbaColor) {
26
+ var r, g, b, o = 1;
27
+
28
+ if (this.hexColorRegex.test(rgbaColor)) {
29
+ var color = rgbaColor.substring(1); // remove #
30
+
31
+ r = parseInt(color.substr(0,2),16);
32
+ g = parseInt(color.substr(2,2),16);
33
+ b = parseInt(color.substr(4,2),16);
34
+ } else {
35
+ var rgba = rgbaColor
36
+ .replace(/^(rgb|rgba)\(/, '')
37
+ .replace(/\)$/, '')
38
+ .replace(/\s/g, '')
39
+ .split(',');
40
+
41
+ r = rgba[0];
42
+ g = rgba[1];
43
+ b = rgba[2];
44
+ o = rgba[3] || 1;
45
+ }
46
+
47
+ var yiq = ((r*299)+(g*587)+(b*114))/1000;
48
+
49
+ return yiq < 128 && o > 0.4;
50
+ },
51
+ isColorValid: function(rgbaColor) {
52
+ return !Color(rgbaColor).error;
53
+ },
54
+ getInstance: function ($iris) {
55
+ return $iris.data('a8cIris');
56
+ },
57
+ updatePreview: function ($input, color) {
58
+ if (this.isColorValid(color)) {
59
+ $input.css({
60
+ 'background-color': color,
61
+ 'color': this.isColorDark(color) ? 'rgb(255,255,255)' : 'rgb(0,0,0)'
62
+ });
63
+ } else {
64
+ $input.css({
65
+ 'background-color': '',
66
+ 'color': ''
67
+ });
68
+ }
69
+ }
70
+ };
71
+
72
+ Color.prototype.toRgba()
73
+
74
+ fwEvents.on('fw:options:init', function (data) {
75
+ data.$elements.find('input.'+ helpers.optionClass +':not(.initialized)').each(function () {
76
+ var $input = $(this),
77
+ changeTimeoutId = 0,
78
+ eventNamespace = helpers.eventNamespace +'_'+ (++helpers.increment);
79
+
80
+ /**
81
+ * Improvement: Initialized picker only on first focus
82
+ * Do not initialize all pickers on the page, for performance reasons, maybe none of them will be opened
83
+ */
84
+ $input.one('focus', function(){
85
+ if (!$.trim($input.val()).length) { // If the input value is empty, there a glitches with opacity slider
86
+ $input.val('rgba(255,255,255,1)');
87
+ }
88
+ $input.iris({
89
+ palettes: JSON.parse($input.attr('data-palettes')),
90
+ defaultColor: false,
91
+ create: function () {
92
+ $(this).val(Color($(this).val()).toStr())
93
+ },
94
+ change: function (event, ui) {
95
+ var $transparency = $input.next('.iris-picker').find('.transparency');
96
+ $transparency.css('backgroundColor', ui.color.toStr('no-alpha'));
97
+
98
+ $alphaSlider.slider("option", "value", ui.color._alpha * 100);
99
+
100
+ clearTimeout(changeTimeoutId);
101
+ changeTimeoutId = setTimeout(function(){
102
+ $input.trigger('fw:option-type:rgba-color-picker:change', {
103
+ $element: $input,
104
+ iris: $input.data('a8cIris'),
105
+ alphaSlider: $alphaSlider.data('uiSlider')
106
+ });
107
+ $input.trigger('change');
108
+ }, 12);
109
+ }
110
+ });
111
+
112
+ var $picker = helpers.getInstance($input).picker;
113
+
114
+ $picker.addClass(helpers.optionClass +'-iris');
115
+
116
+ /**
117
+ * Hide if clicked outside option
118
+ */
119
+ {
120
+ $input.parent().attr('id', 'fw-rgba-color-picker-r-'+ (++helpers.increment));
121
+
122
+ var originalShowCallback = helpers.getInstance($input).show;
123
+
124
+ helpers.getInstance($input).show = function () {
125
+ $(document.body)
126
+ .off('click'+ eventNamespace)
127
+ .on('click'+ eventNamespace, function(e){
128
+ if (!$(e.target).closest('#'+ $input.parent().attr('id')).length) {
129
+ $(document.body).off('click'+ eventNamespace);
130
+ $input.iris('hide');
131
+ }
132
+ });
133
+
134
+ originalShowCallback.apply(this);
135
+ };
136
+ }
137
+
138
+ /**
139
+ * After the second hide the picker is not showing on the next focus (I don't know why)
140
+ * Show it manually
141
+ */
142
+ $input.on('focus', function(){
143
+ if (!$picker.is(':visible')) {
144
+ $input.iris('show');
145
+ }
146
+ });
147
+
148
+ $input.on('change keyup blur', function () {
149
+ // iris::change is not triggered when the input is empty or color is wrong
150
+ $(this).val(Color($(this).val()).toStr())
151
+ helpers.updatePreview($input, $input.val());
152
+ });
153
+
154
+ $(''
155
+ + '<div class="fw-alpha-container">'
156
+ + /**/'<div class="slider-alpha"></div>'
157
+ + /**/'<div class="transparency"></div>'
158
+ + '</div>'
159
+ ).appendTo($input.next('.iris-picker'));
160
+
161
+ var $alphaSlider = $input.next('.iris-picker:first').find('.slider-alpha');
162
+
163
+ $alphaSlider.slider({
164
+ value: Color($input.val())._alpha * 100,
165
+ range: "max",
166
+ step: 1,
167
+ min: 0,
168
+ max: 100,
169
+ slide: function (event, ui) {
170
+ $(this).find('.ui-slider-handle').text(ui.value);
171
+
172
+ $input.data('a8cIris')._color._alpha = parseFloat(ui.value) / 100.0;
173
+
174
+ var color = $input.iris('color', true)
175
+ var cssColor = color.toCSS('rgba', ui.value / 100)
176
+
177
+ $input.val(cssColor);
178
+
179
+ clearTimeout(changeTimeoutId);
180
+ changeTimeoutId = setTimeout(function(){
181
+ $input.trigger('fw:option-type:rgba-color-picker:change', {
182
+ $element: $input,
183
+ iris: $input.data('a8cIris'),
184
+ alphaSlider: $alphaSlider.data('uiSlider')
185
+ });
186
+ $input.trigger('change');
187
+ }, 12);
188
+ },
189
+ create: function (event, ui) {
190
+ var v = $(this).slider('value'),
191
+ $transparency = $input.next('.iris-picker:first').find('.transparency');
192
+
193
+ $(this).find('.ui-slider-handle').text(v);
194
+
195
+ $transparency.css('backgroundColor', Color($input.val()).toRgbaCSS(1));
196
+ },
197
+ change: function (event, ui) {
198
+ $(this).find('.ui-slider-handle').text(ui.value);
199
+
200
+ $input.data('a8cIris')._color._alpha = parseFloat(ui.value) / 100.0;
201
+ $input.val($input.iris('color', true).toRgbaCSS(ui.value / 100))
202
+
203
+ clearTimeout(changeTimeoutId);
204
+ changeTimeoutId = setTimeout(function(){
205
+ $input.trigger('fw:option-type:rgba-color-picker:change', {
206
+ $element: $input,
207
+ iris: $input.data('a8cIris'),
208
+ alphaSlider: $alphaSlider.data('uiSlider')
209
+ });
210
+ $input.trigger('change');
211
+ }, 12);
212
+ }
213
+ });
214
+
215
+ var $firstPalette = $picker.find('.iris-palette-container > .iris-palette:first-child');
216
+
217
+ /**
218
+ * "Reset" color button
219
+ */
220
+ $.each([{
221
+ color: $input.attr('data-default'),
222
+ text: helpers.localized.l10n.reset_to_default
223
+ },{
224
+ color: $input.val(),
225
+ text: helpers.localized.l10n.reset_to_initial
226
+ }], function(i, data){
227
+ if (data.color && helpers.isColorValid(data.color)) {
228
+ $picker.find('> .iris-picker-inner').append(''
229
+ + '<div class="' + helpers.optionClass + '-reset-default fw-pull-left">'
230
+ + /**/'<a class="iris-palette" style="'
231
+ + /**//**/'background-color:'+ data.color +';'
232
+ + /**//**/'height:' + $firstPalette.css('height') + ';'
233
+ + /**//**/'width:' + $firstPalette.css('width') + ';'
234
+ + /**//**/'"></a>'
235
+ + /**/'<span>' + data.text + '</span>'
236
+ + '</div>'
237
+ );
238
+
239
+ $picker
240
+ .on(
241
+ 'click',
242
+ '.' + helpers.optionClass + '-reset-default',
243
+ function(){
244
+ $input.iris('color', $(this).find('.iris-palette').css('background-color'));
245
+ }
246
+ )
247
+ .on('remove', function(){
248
+ $(document.body).off(eventNamespace);
249
+ })
250
+ .addClass(helpers.optionClass + '-with-reset-default')
251
+ .css('height', parseFloat($picker.css('height')) + 17);
252
+
253
+ return false;
254
+ }
255
+ });
256
+
257
+ $input.iris('show');
258
+ });
259
+
260
+ helpers.updatePreview($input, $input.val());
261
+
262
+ $input.addClass('initialized');
263
+ });
264
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
  });
framework/includes/option-types/simple.php CHANGED
@@ -12,6 +12,10 @@ class FW_Option_Type_Hidden extends FW_Option_Type {
12
  return 'hidden';
13
  }
14
 
 
 
 
 
15
  /**
16
  * @internal
17
  * {@inheritdoc}
@@ -75,6 +79,10 @@ class FW_Option_Type_Text extends FW_Option_Type {
75
  protected function _enqueue_static( $id, $option, $data ) {
76
  }
77
 
 
 
 
 
78
  /**
79
  * @param string $id
80
  * @param array $option
@@ -117,6 +125,10 @@ class FW_Option_Type_Short_Text extends FW_Option_Type_Text {
117
  return 'short-text';
118
  }
119
 
 
 
 
 
120
  /**
121
  * @param string $id
122
  * @param array $option
@@ -153,6 +165,10 @@ class FW_Option_Type_Password extends FW_Option_Type {
153
  protected function _enqueue_static( $id, $option, $data ) {
154
  }
155
 
 
 
 
 
156
  /**
157
  * @param string $id
158
  * @param array $option
@@ -202,6 +218,10 @@ class FW_Option_Type_Textarea extends FW_Option_Type {
202
  protected function _enqueue_static( $id, $option, $data ) {
203
  }
204
 
 
 
 
 
205
  /**
206
  * @param string $id
207
  * @param array $option
@@ -258,6 +278,10 @@ class FW_Option_Type_Html extends FW_Option_Type {
258
  protected function _enqueue_static( $id, $option, $data ) {
259
  }
260
 
 
 
 
 
261
  /**
262
  * @param string $id
263
  * @param array $option
@@ -365,6 +389,10 @@ class FW_Option_Type_Checkbox extends FW_Option_Type {
365
  protected function _enqueue_static( $id, $option, $data ) {
366
  }
367
 
 
 
 
 
368
  /**
369
  * @param string $id
370
  * @param array $option
@@ -456,6 +484,10 @@ class FW_Option_Type_Checkboxes extends FW_Option_Type {
456
  protected function _enqueue_static( $id, $option, $data ) {
457
  }
458
 
 
 
 
 
459
  /**
460
  * @param string $id
461
  * @param array $option
@@ -496,6 +528,7 @@ class FW_Option_Type_Checkboxes extends FW_Option_Type {
496
  'name' => $option['attr']['name'] . '[' . $value . ']',
497
  'value' => 'true',
498
  'id' => $option['attr']['id'] . '-' . $value,
 
499
  ),
500
  isset( $option['value'][ $value ] ) && $option['value'][ $value ]
501
  ? array('checked' => 'checked') : array()
@@ -586,6 +619,10 @@ class FW_Option_Type_Radio extends FW_Option_Type {
586
  protected function _enqueue_static( $id, $option, $data ) {
587
  }
588
 
 
 
 
 
589
  /**
590
  * @param string $id
591
  * @param array $option
@@ -704,6 +741,10 @@ class FW_Option_Type_Select extends FW_Option_Type {
704
  protected function _enqueue_static( $id, $option, $data ) {
705
  }
706
 
 
 
 
 
707
  /**
708
  * @param string $id
709
  * @param array $option
@@ -995,8 +1036,7 @@ class FW_Option_Type_Select_Multiple extends FW_Option_Type_Select {
995
  }
996
  }
997
 
998
- class FW_Option_Type_Unique extends FW_Option_Type
999
- {
1000
  private static $ids = array();
1001
  private static $should_do_regeneration = true;
1002
 
@@ -1005,6 +1045,10 @@ class FW_Option_Type_Unique extends FW_Option_Type
1005
  return 'unique';
1006
  }
1007
 
 
 
 
 
1008
  protected function _get_defaults()
1009
  {
1010
  return array(
@@ -1205,4 +1249,4 @@ class FW_Option_Type_GMap_Key extends FW_Option_Type_Text {
1205
  }
1206
  return parent::_storage_save( $id, $option, $value, $params );
1207
  }
1208
- }
12
  return 'hidden';
13
  }
14
 
15
+ protected function _get_data_for_js($id, $option, $data = array()) {
16
+ return false;
17
+ }
18
+
19
  /**
20
  * @internal
21
  * {@inheritdoc}
79
  protected function _enqueue_static( $id, $option, $data ) {
80
  }
81
 
82
+ protected function _get_data_for_js($id, $option, $data = array()) {
83
+ return false;
84
+ }
85
+
86
  /**
87
  * @param string $id
88
  * @param array $option
125
  return 'short-text';
126
  }
127
 
128
+ protected function _get_data_for_js($id, $option, $data = array()) {
129
+ return false;
130
+ }
131
+
132
  /**
133
  * @param string $id
134
  * @param array $option
165
  protected function _enqueue_static( $id, $option, $data ) {
166
  }
167
 
168
+ protected function _get_data_for_js($id, $option, $data = array()) {
169
+ return false;
170
+ }
171
+
172
  /**
173
  * @param string $id
174
  * @param array $option
218
  protected function _enqueue_static( $id, $option, $data ) {
219
  }
220
 
221
+ protected function _get_data_for_js($id, $option, $data = array()) {
222
+ return false;
223
+ }
224
+
225
  /**
226
  * @param string $id
227
  * @param array $option
278
  protected function _enqueue_static( $id, $option, $data ) {
279
  }
280
 
281
+ protected function _get_data_for_js($id, $option, $data = array()) {
282
+ return false;
283
+ }
284
+
285
  /**
286
  * @param string $id
287
  * @param array $option
389
  protected function _enqueue_static( $id, $option, $data ) {
390
  }
391
 
392
+ protected function _get_data_for_js($id, $option, $data = array()) {
393
+ return false;
394
+ }
395
+
396
  /**
397
  * @param string $id
398
  * @param array $option
484
  protected function _enqueue_static( $id, $option, $data ) {
485
  }
486
 
487
+ protected function _get_data_for_js($id, $option, $data = array()) {
488
+ return false;
489
+ }
490
+
491
  /**
492
  * @param string $id
493
  * @param array $option
528
  'name' => $option['attr']['name'] . '[' . $value . ']',
529
  'value' => 'true',
530
  'id' => $option['attr']['id'] . '-' . $value,
531
+ 'data-fw-checkbox-id' => $value
532
  ),
533
  isset( $option['value'][ $value ] ) && $option['value'][ $value ]
534
  ? array('checked' => 'checked') : array()
619
  protected function _enqueue_static( $id, $option, $data ) {
620
  }
621
 
622
+ protected function _get_data_for_js($id, $option, $data = array()) {
623
+ return false;
624
+ }
625
+
626
  /**
627
  * @param string $id
628
  * @param array $option
741
  protected function _enqueue_static( $id, $option, $data ) {
742
  }
743
 
744
+ protected function _get_data_for_js($id, $option, $data = array()) {
745
+ return false;
746
+ }
747
+
748
  /**
749
  * @param string $id
750
  * @param array $option
1036
  }
1037
  }
1038
 
1039
+ class FW_Option_Type_Unique extends FW_Option_Type {
 
1040
  private static $ids = array();
1041
  private static $should_do_regeneration = true;
1042
 
1045
  return 'unique';
1046
  }
1047
 
1048
+ protected function _get_data_for_js($id, $option, $data = array()) {
1049
+ return false;
1050
+ }
1051
+
1052
  protected function _get_defaults()
1053
  {
1054
  return array(
1249
  }
1250
  return parent::_storage_save( $id, $option, $value, $params );
1251
  }
1252
+ }
framework/includes/option-types/slider/class-fw-option-type-slider.php CHANGED
@@ -17,6 +17,10 @@ class FW_Option_Type_Slider extends FW_Option_Type {
17
  return 'slider';
18
  }
19
 
 
 
 
 
20
  /**
21
  * @internal
22
  * {@inheritdoc}
@@ -111,4 +115,4 @@ class FW_Option_Type_Slider extends FW_Option_Type {
111
  }
112
  }
113
 
114
- }
17
  return 'slider';
18
  }
19
 
20
+ protected function _get_data_for_js($id, $option, $data = array()) {
21
+ return false;
22
+ }
23
+
24
  /**
25
  * @internal
26
  * {@inheritdoc}
115
  }
116
  }
117
 
118
+ }
framework/includes/option-types/slider/static/js/scripts.js CHANGED
@@ -10,4 +10,4 @@
10
  }).addClass('initialized');
11
  });
12
 
13
- })(jQuery, fwEvents);
10
  }).addClass('initialized');
11
  });
12
 
13
+ })(jQuery, fwEvents);
framework/includes/option-types/switch/class-fw-option-type-switch.php CHANGED
@@ -12,6 +12,10 @@ class FW_Option_Type_Switch extends FW_Option_Type
12
  return 'switch';
13
  }
14
 
 
 
 
 
15
  /**
16
  * @internal
17
  * {@inheritdoc}
@@ -101,6 +105,7 @@ class FW_Option_Type_Switch extends FW_Option_Type
101
 
102
  return '<div '. fw_attr_to_html($option['attr']) .'>'.
103
  '<!-- note: value is json encoded, if want to use it in js, do: var val = JSON.parse($input.val()); -->'.
 
104
  ($checked ? '' : fw_html_tag('input', array(
105
  'type' => 'hidden',
106
  'name' => $input_attr['name'],
@@ -167,4 +172,4 @@ class FW_Option_Type_Switch extends FW_Option_Type
167
  ),
168
  );
169
  }
170
- }
12
  return 'switch';
13
  }
14
 
15
+ protected function _get_data_for_js($id, $option, $data = array()) {
16
+ return false;
17
+ }
18
+
19
  /**
20
  * @internal
21
  * {@inheritdoc}
105
 
106
  return '<div '. fw_attr_to_html($option['attr']) .'>'.
107
  '<!-- note: value is json encoded, if want to use it in js, do: var val = JSON.parse($input.val()); -->'.
108
+ '<!-- deprecated: use reactive options for extracting the current value of the switch -->'.
109
  ($checked ? '' : fw_html_tag('input', array(
110
  'type' => 'hidden',
111
  'name' => $input_attr['name'],
172
  ),
173
  );
174
  }
175
+ }
framework/includes/option-types/switch/static/js/scripts.js CHANGED
@@ -29,6 +29,10 @@ jQuery(document).ready(function ($) {
29
  $this.closest('.'+ optionTypeClass).trigger(customEventPrefix +'change', {
30
  value: JSON.parse(value)
31
  });
 
 
 
 
32
  })
33
  .on('change update:color', function(){
34
  var $this = $(this),
@@ -42,4 +46,16 @@ jQuery(document).ready(function ($) {
42
  .adaptiveSwitch()
43
  .trigger('update:color');
44
  });
45
- });
 
 
 
 
 
 
 
 
 
 
 
 
29
  $this.closest('.'+ optionTypeClass).trigger(customEventPrefix +'change', {
30
  value: JSON.parse(value)
31
  });
32
+
33
+ fw.options.trigger.changeForEl(
34
+ $this.closest('.' + optionTypeClass)
35
+ );
36
  })
37
  .on('change update:color', function(){
38
  var $this = $(this),
46
  .adaptiveSwitch()
47
  .trigger('update:color');
48
  });
49
+
50
+ fw.options.register('switch', {
51
+ startListeningForChanges: $.noop,
52
+ getValue: function (optionDescriptor) {
53
+ return {
54
+ value: JSON.parse(
55
+ $(optionDescriptor.el).find('[type="checkbox"]').val()
56
+ ),
57
+ optionDescriptor: optionDescriptor
58
+ }
59
+ }
60
+ })
61
+ });
framework/includes/option-types/typography-v2/class-fw-option-type-typography-v2.php CHANGED
@@ -71,7 +71,10 @@ class FW_Option_Type_Typography_v2 extends FW_Option_Type {
71
  "Impact",
72
  "Serif"
73
  ) ),
74
- 'google' => json_decode(fw_get_google_fonts_v2(), true)
 
 
 
75
  );
76
 
77
  FW_Cache::set($cache_key, $fonts);
71
  "Impact",
72
  "Serif"
73
  ) ),
74
+ 'google' => apply_filters(
75
+ 'fw_option_type_typography_v2_google_fonts',
76
+ json_decode( fw_get_google_fonts_v2(), true )
77
+ )
78
  );
79
 
80
  FW_Cache::set($cache_key, $fonts);
framework/includes/option-types/upload/class-fw-option-type-upload.php CHANGED
@@ -25,6 +25,10 @@ class FW_Option_Type_Upload extends FW_Option_Type
25
  );
26
  }
27
 
 
 
 
 
28
  /**
29
  * @internal
30
  */
@@ -230,4 +234,4 @@ class FW_Option_Type_Upload extends FW_Option_Type
230
  return $defaults['value'];
231
  }
232
  }
233
- }
25
  );
26
  }
27
 
28
+ protected function _get_data_for_js($id, $option, $data = array()) {
29
+ return false;
30
+ }
31
+
32
  /**
33
  * @internal
34
  */
234
  return $defaults['value'];
235
  }
236
  }
237
+ }
framework/includes/option-types/upload/static/js/any-files.js CHANGED
@@ -73,9 +73,11 @@
73
 
74
  frame.on('select', function() {
75
  var attachment = frame.state().get('selection').first();
 
76
  elements.$input
77
  .val(attachment.id)
78
  .trigger('change'); // trigger Customizer update
 
79
  performSelection(attachment);
80
  });
81
  };
@@ -124,6 +126,14 @@
124
 
125
  fwe.trigger('fw:option-type:upload:clear', {$element: elements.$container});
126
  elements.$container.trigger('fw:option-type:upload:clear');
 
 
 
 
 
 
 
 
127
  }
128
 
129
  function performSelection (attachment) {
@@ -140,6 +150,13 @@
140
  elements.$container.trigger('fw:option-type:upload:change', {
141
  attachment: attachment
142
  });
 
 
 
 
 
 
 
143
  }
144
  };
145
 
73
 
74
  frame.on('select', function() {
75
  var attachment = frame.state().get('selection').first();
76
+
77
  elements.$input
78
  .val(attachment.id)
79
  .trigger('change'); // trigger Customizer update
80
+
81
  performSelection(attachment);
82
  });
83
  };
126
 
127
  fwe.trigger('fw:option-type:upload:clear', {$element: elements.$container});
128
  elements.$container.trigger('fw:option-type:upload:clear');
129
+
130
+ fw.options.trigger.changeForEl(elements.$container, {
131
+ value: {}
132
+ });
133
+
134
+ fw.options.trigger.scopedByType('clear', elements.$container, {
135
+ value: {}
136
+ });
137
  }
138
 
139
  function performSelection (attachment) {
150
  elements.$container.trigger('fw:option-type:upload:change', {
151
  attachment: attachment
152
  });
153
+
154
+ fw.options.trigger.changeForEl(elements.$container, {
155
+ value: {
156
+ attachment_id: attachment.get('id'),
157
+ url: attachment.get('url')
158
+ }
159
+ });
160
  }
161
  };
162
 
framework/includes/option-types/upload/static/js/images-only.js CHANGED
@@ -143,6 +143,14 @@
143
 
144
  fwe.trigger('fw:option-type:upload:clear', {$element: elements.$container});
145
  elements.$container.trigger('fw:option-type:upload:clear');
 
 
 
 
 
 
 
 
146
  }
147
 
148
  function performSelection (attachment) {
@@ -186,6 +194,13 @@
186
  elements.$container.trigger('fw:option-type:upload:change', {
187
  attachment: attachment
188
  });
 
 
 
 
 
 
 
189
  }
190
  };
191
 
@@ -195,4 +210,43 @@
195
  .addClass('fw-option-initialized');
196
  });
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  })(jQuery, _, fwEvents);
143
 
144
  fwe.trigger('fw:option-type:upload:clear', {$element: elements.$container});
145
  elements.$container.trigger('fw:option-type:upload:clear');
146
+
147
+ fw.options.trigger.changeForEl(elements.$container, {
148
+ value: {}
149
+ });
150
+
151
+ fw.options.trigger.scopedByType('clear', elements.$container, {
152
+ value: {}
153
+ });
154
  }
155
 
156
  function performSelection (attachment) {
194
  elements.$container.trigger('fw:option-type:upload:change', {
195
  attachment: attachment
196
  });
197
+
198
+ fw.options.trigger.changeForEl(elements.$container, {
199
+ value: {
200
+ attachment_id: attachment.get('id'),
201
+ url: attachment.get('url')
202
+ }
203
+ });
204
  }
205
  };
206
 
210
  .addClass('fw-option-initialized');
211
  });
212
 
213
+ fw.options.register('upload', {
214
+ startListeningForChanges: jQuery.noop,
215
+ getValue: function (optionDescriptor) {
216
+ var deferred = $.Deferred();
217
+
218
+ var attachmentId = $(optionDescriptor.el).find(
219
+ '[type="hidden"][name*="' + optionDescriptor.id + '"]'
220
+ ).val()
221
+
222
+ if (! attachmentId) {
223
+ deferred.resolve({
224
+ value: {},
225
+ optionDescriptor: optionDescriptor
226
+ });
227
+ } else {
228
+ var attachment = wp.media.attachment(attachmentId);
229
+
230
+ if (! attachment.get('url')) {
231
+ attachment.fetch().then(function () {
232
+ resolveWithAttachment(attachment);
233
+ })
234
+ } else {
235
+ resolveWithAttachment(attachment)
236
+ }
237
+ }
238
+
239
+ return deferred;
240
+
241
+ function resolveWithAttachment (attachment) {
242
+ deferred.resolve({
243
+ value: {
244
+ attachment_id: attachmentId,
245
+ url: attachment.get('url')
246
+ }
247
+ });
248
+ }
249
+ }
250
+ });
251
+
252
  })(jQuery, _, fwEvents);
framework/includes/option-types/wp-editor/class-fw-option-type-wp-editor.php CHANGED
@@ -9,6 +9,10 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type {
9
  return 'wp-editor';
10
  }
11
 
 
 
 
 
12
  /**
13
  * @internal
14
  */
@@ -167,4 +171,4 @@ class FW_Option_Type_Wp_Editor extends FW_Option_Type {
167
  public function _get_backend_width_type() {
168
  return 'auto';
169
  }
170
- }
9
  return 'wp-editor';
10
  }
11
 
12
+ protected function _get_data_for_js($id, $option, $data = array()) {
13
+ return false;
14
+ }
15
+
16
  /**
17
  * @internal
18
  */
171
  public function _get_backend_width_type() {
172
  return 'auto';
173
  }
174
+ }
framework/includes/option-types/wp-editor/static/scripts.js CHANGED
@@ -153,6 +153,25 @@
153
  .addClass('fw-option-initialized');
154
  });
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  })(jQuery, fwEvents);
157
 
158
  /**
153
  .addClass('fw-option-initialized');
154
  });
155
 
156
+ fw.options.register('wp-editor', {
157
+ startListeningForChanges: function (optionDescriptor) {
158
+ $(optionDescriptor.el).find('textarea.wp-editor-area')
159
+ .on('change', function (e) {
160
+ fw.options.trigger.changeForEl(e.target);
161
+ });
162
+ },
163
+
164
+ getValue: function (optionDescriptor) {
165
+ return {
166
+ value: $(optionDescriptor.el).find(
167
+ 'textarea.wp-editor-area'
168
+ ).val(),
169
+
170
+ optionDescriptor: optionDescriptor
171
+ }
172
+ }
173
+ });
174
+
175
  })(jQuery, fwEvents);
176
 
177
  /**
framework/manifest.php CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
- $manifest['version'] = '2.6.16';
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
+ $manifest['version'] = '2.7.2';
framework/static/js/fw-events.js CHANGED
@@ -2,43 +2,12 @@
2
  * Listen and trigger custom events to communicate between javascript components
3
  */
4
  var fwEvents = new (function(){
5
- {
6
- var eventsBox = _.extend({}, Backbone.Events);
 
7
 
8
- var debug = false;
9
-
10
- var log = function(message, data) {
11
- if (!debug) {
12
- return;
13
- }
14
-
15
- if (typeof data != 'undefined') {
16
- console.log('[Event] ' + getIndentation() + message, '─', data);
17
- } else {
18
- console.log('[Event] ' + getIndentation() + message);
19
- }
20
- };
21
-
22
- /**
23
- * Indent logs that happens inside another event
24
- */
25
- {
26
- var getIndentation = function() {
27
- return new Array(currentIndentation).join('│ ');
28
- };
29
-
30
- var currentIndentation = 1;
31
-
32
- var changeIndentation = function(increment) {
33
- if (typeof increment != 'undefined') {
34
- currentIndentation += (increment > 0 ? +1 : -1);
35
- }
36
-
37
- if (currentIndentation < 0) {
38
- currentIndentation = 0;
39
- }
40
- };
41
- }
42
  }
43
 
44
  /**
@@ -61,21 +30,26 @@ var fwEvents = new (function(){
61
 
62
  /**
63
  * Add event listener
 
 
 
 
 
 
 
 
64
  */
65
- this.on = function(event, callback, context) {
66
- eventsBox.on(event, callback, context);
67
-
68
- if (debug) {
69
- if (typeof event == 'string') {
70
- // .on('event:name', callback)
71
- log('✚ '+ event);
72
- } else {
73
- // .on({'event:name': callback})
74
- _.each(event, function(_callback, _event){
75
- log('✚ '+ _event);
76
- });
77
  }
78
- }
79
 
80
  return this;
81
  };
@@ -83,64 +57,134 @@ var fwEvents = new (function(){
83
  /**
84
  * Same as .on(), but callback will executed only once
85
  */
86
- this.one = function(event, callback, context) {
87
- eventsBox.once(event, callback);
88
-
89
- if (debug) {
90
- if (typeof event == 'string') {
91
- // .one('event:name', callback)
92
- log('✚ ['+ event +']');
93
- } else {
94
- // .one({'event:name': callback})
95
- _.each(event, function(_callback, _event){
96
- log('✚ ['+ _event +']');
97
- });
98
  }
99
- }
100
 
101
  return this;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  };
103
 
104
  /**
105
- * Remove event listener
 
 
 
 
 
106
  */
107
- this.off = function(event, callback, context) {
108
- eventsBox.off(event, callback, context);
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- if (debug) {
111
- log('✖ '+ event);
112
- }
 
113
 
114
  return this;
115
  };
116
 
117
  /**
118
- * Trigger event
 
 
119
  *
120
- * @public
121
- * @param {String} event
122
- * @param {Object} [data]
123
  */
124
- this.trigger = function(event, data) {
125
- log('╭─ '+ event, data);
 
 
 
126
 
127
- changeIndentation(+1);
128
 
129
- try {
130
- eventsBox.trigger(event, data);
131
- } catch (e) {
132
- console.log('[Events] Exception ', {exception: e});
 
 
 
 
133
 
134
- if (console.trace) {
135
- console.trace();
136
- }
137
- }
 
 
 
 
 
 
 
 
 
 
138
 
139
- changeIndentation(-1);
140
 
141
- log('╰─ '+ event, data);
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  return this;
 
 
 
 
 
 
 
 
 
 
144
  };
145
 
146
  /**
@@ -148,11 +192,80 @@ var fwEvents = new (function(){
148
  * @param {String} [event]
149
  * @return {Boolean}
150
  */
151
- this.hasListeners = function(event) {
152
- if (!eventsBox._events) {
153
  return false;
154
  }
155
 
156
- return !!eventsBox._events[event];
157
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  })();
2
  * Listen and trigger custom events to communicate between javascript components
3
  */
4
  var fwEvents = new (function(){
5
+ var _events = {};
6
+ var currentIndentation = 1;
7
+ var debug = false;
8
 
9
+ this.countAll = function (topic) {
10
+ return _events[topic];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  }
12
 
13
  /**
30
 
31
  /**
32
  * Add event listener
33
+ *
34
+ * @param event {String | Object}
35
+ * Can be a:
36
+ * - single event: 'event1'
37
+ * - space separated event list: 'event1 event2 event2'
38
+ * - an object: {event1: function () {}, event2: function () {}}
39
+ *
40
+ * @param callback {Function}
41
  */
42
+ this.on = function(topicStringOrObject, listener) {
43
+ objectMap(
44
+ splitTopicStringOrObject(topicStringOrObject, listener),
45
+ function (eventName, listener) {
46
+ (_events[eventName] || (_events[eventName] = [])).push(
47
+ listener
48
+ );
49
+
50
+ debug && log('' + eventName);
 
 
 
51
  }
52
+ );
53
 
54
  return this;
55
  };
57
  /**
58
  * Same as .on(), but callback will executed only once
59
  */
60
+ this.one = function(topicStringOrObject, listener) {
61
+ objectMap(
62
+ splitTopicStringOrObject(topicStringOrObject, listener),
63
+ function (eventName, listener) {
64
+ (_events[eventName] || (_events[eventName] = [])).push(
65
+ once(listener)
66
+ );
67
+
68
+ debug && log('✚ [' + eventName +']');
 
 
 
69
  }
70
+ );
71
 
72
  return this;
73
+
74
+ // https://github.com/jashkenas/underscore/blob/8fc7032295d60aff3620ef85d4aa6549a55688a0/underscore.js#L946
75
+ function once(func) {
76
+ var memo;
77
+
78
+ var times = 2;
79
+
80
+ return function() {
81
+ if (--times > 0) {
82
+ memo = func.apply(this, arguments);
83
+ }
84
+
85
+ if (times <= 1) func = null;
86
+
87
+ return memo;
88
+ };
89
+ };
90
  };
91
 
92
  /**
93
+ * In order to remove one single listener you should give as an argument
94
+ * the same callback function. If you want to remove *all* listeners from
95
+ * a particular event you should not pass the second argument.
96
+ *
97
+ * @param topicStringOrObject {String | Object}
98
+ * @param listener {Function | false}
99
  */
100
+ this.off = function(topicStringOrObject, listener) {
101
+ objectMap(
102
+ splitTopicStringOrObject(topicStringOrObject, listener),
103
+ function (eventName, listener) {
104
+ if (_events[eventName]) {
105
+ if (listener) {
106
+ _events[eventName].splice(
107
+ _events[eventName].indexOf(listener) >>> 0,
108
+ 1
109
+ );
110
+ } else {
111
+ _events[eventName] = [];
112
+ }
113
 
114
+ debug && log('✖ ' + eventName);
115
+ }
116
+ }
117
+ );
118
 
119
  return this;
120
  };
121
 
122
  /**
123
+ * Trigger an event. In case you provide multiple events via space-separated
124
+ * string or an object of events it will execute listeners for each event
125
+ * separatedly. You can use the "all" event to trigger all events.
126
  *
127
+ * @param topicStringOrObject {String | Object}
128
+ * @param data {Object}
 
129
  */
130
+ this.trigger = function(eventName, data) {
131
+ objectMap(
132
+ splitTopicStringOrObject(eventName),
133
+ function (eventName) {
134
+ log('╭─ '+ eventName, data);
135
 
136
+ changeIndentation(+1);
137
 
138
+ try {
139
+ // TODO: REFACTOR THAT!!!!!!!!!
140
+ // Maybe this is an occasion for using 'all' event???
141
+ if (eventName === 'fw:options:init') {
142
+ fw.options.startListeningToEvents(
143
+ data.$elements || document.body
144
+ )
145
+ }
146
 
147
+ (_events[eventName] || []).map(dispatchSingleEvent);
148
+ (_events['all'] || []).map(dispatchSingleEvent);
149
+ } catch (e) {
150
+ console.log(
151
+ "%c [Events] Exception raised. Please contact support in https://github.com/ThemeFuse/Unyson/issues/new. Don't forget to attach this stack trace to the issue.",
152
+ "color: red; font-weight: bold;"
153
+ );
154
+
155
+ if (typeof console !== 'undefined') {
156
+ console.error(e)
157
+ } else {
158
+ throw e;
159
+ }
160
+ }
161
 
162
+ changeIndentation(-1);
163
 
164
+ log('╰─ '+ eventName, data);
165
+
166
+ function dispatchSingleEvent (listenerDescriptor) {
167
+ if (! listenerDescriptor) return;
168
+
169
+ listenerDescriptor.call(
170
+ window,
171
+ data
172
+ );
173
+ }
174
+ }
175
+ );
176
 
177
  return this;
178
+
179
+ function changeIndentation(increment) {
180
+ if (typeof increment != 'undefined') {
181
+ currentIndentation += (increment > 0 ? +1 : -1);
182
+ }
183
+
184
+ if (currentIndentation < 0) {
185
+ currentIndentation = 0;
186
+ }
187
+ }
188
  };
189
 
190
  /**
192
  * @param {String} [event]
193
  * @return {Boolean}
194
  */
195
+ this.hasListeners = function(eventName) {
196
+ if (! _events) {
197
  return false;
198
  }
199
 
200
+ return (_events[eventName] || []).length > 0;
201
  };
202
+
203
+ /**
204
+ * Probably split string into general purpose object representation for
205
+ * event names and listeners. This function leaves objects un-modified.
206
+ *
207
+ * @param topicStringOrObject {String | Object}
208
+ * @param listener {Function | false}
209
+ *
210
+ * @returns {Object} {
211
+ * eventname: listener,
212
+ * otherevent: listener
213
+ * }
214
+ */
215
+ function splitTopicStringOrObject (topicStringOrObject, listener) {
216
+ if (typeof topicStringOrObject !== 'string') {
217
+ return topicStringOrObject;
218
+ }
219
+
220
+ var arrayOfEvents = topicStringOrObject.replace(
221
+ /\s\s+/g, ' '
222
+ ).trim().split(' ');
223
+
224
+ var len = arrayOfEvents.length;
225
+
226
+ var listenerDescriptor = Object.create(null);
227
+
228
+ for (var i = 0; i < len; i++) {
229
+ listenerDescriptor[arrayOfEvents[i]] = listener;
230
+ }
231
+
232
+ return listenerDescriptor;
233
+ }
234
+
235
+ /**
236
+ * returns a new object with the predicate applied to each value
237
+ * objectMap({a: 3, b: 5, c: 9}, (key, value) => value + 1); // {a: 4, b: 6, c: 10}
238
+ * objectMap({a: 3, b: 5, c: 9}, (key, value) => key); // {a: 'a', b: 'b', c: 'c'}
239
+ * objectMap({a: 3, b: 5, c: 9}, (key, value) => key + value); // {a: 'a3', b: 'b5', c: 'c9'}
240
+ *
241
+ * https://github.com/angus-c/just/tree/master/packages/object-map
242
+ */
243
+ function objectMap(obj, predicate) {
244
+ var result = {};
245
+ var keys = Object.keys(obj);
246
+ var len = keys.length;
247
+
248
+ for (var i = 0; i < len; i++) {
249
+ var key = keys[i];
250
+ result[key] = predicate(key, obj[key]);
251
+ }
252
+
253
+ return result;
254
+ }
255
+
256
+ function log(message, data) {
257
+ if (! debug) {
258
+ return;
259
+ }
260
+
261
+ if (typeof data != 'undefined') {
262
+ console.log('[Event] ' + getIndentation() + message, '─', data);
263
+ } else {
264
+ console.log('[Event] ' + getIndentation() + message);
265
+ }
266
+
267
+ function getIndentation() {
268
+ return new Array(currentIndentation).join('│ ');
269
+ }
270
+ }
271
  })();
framework/views/backend-option-design-customizer.php CHANGED
@@ -7,7 +7,9 @@
7
 
8
  {
9
  if (!isset($option['label'])) {
10
- $option['label'] = fw_id_to_title($id);
 
 
11
  }
12
 
13
  if (!isset($option['desc'])) {
@@ -121,4 +123,4 @@
121
  </div>
122
  </div>
123
  </div>
124
- </div>
7
 
8
  {
9
  if (!isset($option['label'])) {
10
+ $option['label'] = fw()->backend->option_type($option['type'])->_default_label(
11
+ $id, $option
12
+ );
13
  }
14
 
15
  if (!isset($option['desc'])) {
123
  </div>
124
  </div>
125
  </div>
126
+ </div>
framework/views/backend-option-design-default.php CHANGED
@@ -7,7 +7,9 @@
7
 
8
  {
9
  if (!isset($option['label'])) {
10
- $option['label'] = fw_id_to_title($id);
 
 
11
  }
12
 
13
  if (!isset($option['desc'])) {
@@ -133,6 +135,7 @@ try {
133
  $desc_under_label = apply_filters('fw:backend-option-view:design-default:desc-under-label', false)
134
  );
135
  }
 
136
  ?>
137
  <div class="<?php echo esc_attr($classes['option']) ?>" id="fw-backend-option-<?php echo esc_attr($data['id_prefix'] . $id) ?>">
138
  <?php if ($option['label'] !== false): ?>
@@ -157,4 +160,4 @@ try {
157
  <div class="fw-inner"><?php echo ($option['desc'] ? $option['desc'] : '') ?></div>
158
  </div>
159
  <?php endif; ?>
160
- </div>
7
 
8
  {
9
  if (!isset($option['label'])) {
10
+ $option['label'] = fw()->backend->option_type($option['type'])->_default_label(
11
+ $id, $option
12
+ );
13
  }
14
 
15
  if (!isset($option['desc'])) {
135
  $desc_under_label = apply_filters('fw:backend-option-view:design-default:desc-under-label', false)
136
  );
137
  }
138
+
139
  ?>
140
  <div class="<?php echo esc_attr($classes['option']) ?>" id="fw-backend-option-<?php echo esc_attr($data['id_prefix'] . $id) ?>">
141
  <?php if ($option['label'] !== false): ?>
160
  <div class="fw-inner"><?php echo ($option['desc'] ? $option['desc'] : '') ?></div>
161
  </div>
162
  <?php endif; ?>
163
+ </div>
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: unyson
3
  Tags: page builder, shortcodes, backup, seo, breadcrumbs, portfolio, framework
4
  Requires at least: 4.4
5
- Tested up to: 4.7
6
- Stable tag: 2.6.16
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -85,7 +85,17 @@ Yes; Unyson will work with any theme.
85
 
86
  == Changelog ==
87
 
88
- = 2.6.16 =
 
 
 
 
 
 
 
 
 
 
89
  * Fixed [#2490](https://github.com/ThemeFuse/Unyson/issues/2490), [#2450](https://github.com/ThemeFuse/Unyson/issues/2450), [#326](https://github.com/ThemeFuse/Unyson/issues/326)
90
 
91
  = 2.6.15 =
2
  Contributors: unyson
3
  Tags: page builder, shortcodes, backup, seo, breadcrumbs, portfolio, framework
4
  Requires at least: 4.4
5
+ Tested up to: 4.8
6
+ Stable tag: 2.7.2
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
85
 
86
  == Changelog ==
87
 
88
+ = 2.7.2 =
89
+ * Bug fixes
90
+
91
+ = 2.7.1 =
92
+ * Bug fixes
93
+
94
+ = 2.7.0 =
95
+ * Fixed [#2460](https://github.com/ThemeFuse/Unyson/issues/2460),[#1775](https://github.com/ThemeFuse/Unyson/issues/1775#issuecomment-293581057),[#2516](https://github.com/ThemeFuse/Unyson/issues/2516)[#2536](https://github.com/ThemeFuse/Unyson/pull/2536)[#1419](https://github.com/ThemeFuse/Unyson/issues/1419)[#2563](https://github.com/ThemeFuse/Unyson/issues/2563)[#2587](https://github.com/ThemeFuse/Unyson/issues/2587)[#2591](https://github.com/ThemeFuse/Unyson/issues/2591)[#2604](https://github.com/ThemeFuse/Unyson/issues/2604)
96
+ * New feature. Introducing Reactive Option Types
97
+
98
+ = 2.6.15 =
99
  * Fixed [#2490](https://github.com/ThemeFuse/Unyson/issues/2490), [#2450](https://github.com/ThemeFuse/Unyson/issues/2450), [#326](https://github.com/ThemeFuse/Unyson/issues/326)
100
 
101
  = 2.6.15 =
unyson.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.io/
5
  * Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
6
- * Version: 2.6.16
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+
3
  * Plugin Name: Unyson
4
  * Plugin URI: http://unyson.io/
5
  * Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
6
+ * Version: 2.7.2
7
  * Author: ThemeFuse
8
  * Author URI: http://themefuse.com
9
  * License: GPL2+