PluginProbe
Customify / 2.6.0
Customify v2.6.0
2.10.9 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.7.1 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.6.0 1.6.0.1 1.6.5 1.7.0 1.7.1 All 77 releases
customify / js / customizer.js

customizer.js in Customify 2.6.0, at js/customizer.js

1,166 lines 36.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (
2 function ($, exports, wp) {
3 var api = wp.customize
4 var $window = $(window)
5
6 // when the customizer is ready prepare our fields events
7 wp.customize.bind('ready', function () {
8 var timeout = null
9
10 // Create a stack of callbacks bound to parent settings to be able to unbind them
11 // when altering the connected_fields attribute.
12 if (typeof window.connectedFieldsCallbacks === 'undefined') {
13 window.connectedFieldsCallbacks = {}
14 }
15
16 // add ace editors
17 $('.customify_ace_editor').each(function (key, el) {
18 const id = $(this).attr('id'),
19 css_editor = ace.edit(id)
20
21 const editor_type = $(this).data('editor_type')
22 // init the ace editor
23 css_editor.setTheme('ace/theme/github')
24 css_editor.getSession().setMode('ace/mode/' + editor_type)
25
26 // hide the textarea and enable the ace editor
27 const textarea = $('#' + id + '_textarea').hide()
28 css_editor.getSession().setValue(textarea.val())
29
30 // each time a change is triggered start a timeout of 1,5s and when is finished refresh the previewer
31 // if the user types faster than this delay then reset it
32 css_editor.getSession().on('change', function (e) {
33 if (timeout !== null) {
34 clearTimeout(timeout)
35 timeout = null
36 } else {
37 timeout = setTimeout(function () {
38 //var state = css_editor.session.getState();
39 textarea.val(css_editor.getSession().getValue())
40 textarea.trigger('change')
41 }, 1500)
42 }
43 })
44 })
45
46 // simple select2 field
47 $('.customify_select2').select2()
48
49 setTimeout(function () {
50 CustomifyFontSelectFields.init()
51 }, 333)
52
53 prepare_typography_field()
54
55 /**
56 * Make the customizer save on CMD/CTRL+S action
57 * This is awesome!!!
58 */
59 $(window).bind('keydown', function (event) {
60 if (event.ctrlKey || event.metaKey) {
61 switch (String.fromCharCode(event.which).toLowerCase()) {
62 case 's':
63 event.preventDefault()
64 api.previewer.save()
65 break
66 }
67 }
68 })
69
70 // for each range input add a value preview output
71 $('.accordion-section-content[id*="' + customify_settings.options_name + '"], #sub-accordion-section-style_manager_section').each(function () {
72
73 // Initialize range fields logic
74 customifyHandleRangeFields(this)
75 })
76
77 $(document).on('change', '.customify_typography_font_subsets', function (ev) {
78
79 const $input = $(this).parents('.options').siblings('.customify_typography').children('.customify_typography_values');
80 let current_val = $input.val()
81
82 current_val = JSON.parse(decodeURIComponent(current_val))
83
84 //maybe the selected option holds a JSON in its value
85 current_val.selected_subsets = maybeJsonParse($(this).val())
86
87 $input.val(encodeURIComponent(JSON.stringify(current_val)))
88
89 $input.trigger('change')
90 })
91
92 $(document).on('change', '.customify_typography_font_weight', function (ev) {
93
94 const $input = $(this).parents('.options').siblings('.customify_typography').children('.customify_typography_values');
95 let current_val = $input.val();
96
97 current_val = maybeJsonParse(current_val)
98 // @todo currently the font weight selector works for one value only
99 // maybe make this a multiselect
100
101 //maybe the selected option holds a JSON in its value
102 current_val.selected_variants = {0: maybeJsonParse($(this).val())}
103
104 $input.val(encodeURIComponent(JSON.stringify(current_val)))
105 $input.trigger('change')
106 })
107
108 $('body').on('customify:preset-change', function (e) {
109 const data = $(e.target).data('options')
110
111 if (!_.isUndefined(data)) {
112 $.each(data, function (setting_id, value) {
113 api_set_setting_value(setting_id, value)
114 })
115 }
116 })
117
118 $(document).on('change', 'select.js-customify-preset', function () {
119 const $source = $(this)
120 const $target = $source.children('[value="' + $source.val() + '"]')
121 $target.trigger('customify:preset-change')
122 })
123
124 $(document).on('click', '.js-customify-preset input', function () {
125 $(this).trigger('customify:preset-change')
126 })
127
128 customifyBackgroundJsControl.init()
129
130 // sometimes a php save may be needed
131 if (getUrlVars('save_customizer_once')) {
132 api.previewer.save()
133 }
134
135 setTimeout(function () {
136 customifyFoldingFields()
137 }, 1000);
138
139 // Handle reset buttons
140 handleResetButtons();
141
142 // Handle the section tabs (ex: Layout | Fonts | Colors)
143 handleSectionTabs();
144 handleFontPopupToggle();
145
146 // Bind any connected fields, except those in the Style Manager.
147 // Those are handled by the appropriate Style Manager component (Color Palettes, Font Palettes, etc ).
148 bindConnectedFields()
149 });
150
151 function handleResetButtons() {
152 var showResetButtons = $( 'button[data-action="reset_customify"]' ).length > 0;
153
154 if ( showResetButtons ) {
155 createResetPanelButtons();
156 createResetSectionButtons();
157
158 $( document ).on( 'click', '.js-reset-panel', onResetPanel );
159 $( document ).on( 'click', '.js-reset-section', onResetSection );
160 $( document ).on( 'click', '#customize-control-reset_customify button', onReset );
161 }
162 }
163
164 function handleFontPopupToggle() {
165 var $allCheckboxes = $( '.js-font-option-toggle' );
166 // Close a font field when clicking on another field
167 $allCheckboxes.on( 'click', function() {
168 var $checkbox = $( this );
169 if ( $checkbox.prop( 'checked' ) === true ) {
170 $allCheckboxes.not( $checkbox ).prop( 'checked', false );
171 }
172 } )
173 }
174
175 function createResetPanelButtons() {
176
177 $( '.panel-meta' ).each( function( i, obj ) {
178 var $this = $( obj )
179 var container = $this.parents('.control-panel');
180 var id = container.attr('id');
181
182 if ( typeof id !== 'undefined' ) {
183 id = id.replace( 'sub-accordion-panel-', '' );
184 id = id.replace( 'accordion-panel-', '' );
185 var $buttonWrapper = $( '<li class="customize-control customize-control-reset"></li>' );
186 var $button = $( '<button class="button js-reset-panel" data-panel="' + id + '"></button>' );
187
188 $button.text( "Panel's defaults" ).appendTo( $buttonWrapper );
189 $this.parent().append( $buttonWrapper );
190 }
191 })
192 }
193
194 function createResetSectionButtons() {
195 $( '.accordion-section-content' ).each( function( el, key ) {
196 var $this = $( this );
197 var section_id = $this.attr( 'id' );
198
199 if ( _.isUndefined( section_id ) || section_id.indexOf( customify_settings.options_name ) === - 1 ) {
200 return;
201 }
202
203 var id = section_id.replace( 'sub-accordion-section-', '' );
204 var $button = $( '<button class="button js-reset-section" data-section="' + id + '"></button>' );
205 var $buttonWrapper = $( '<li class="customize-control customize-control-reset"></li>' );
206
207 $button.text( 'Reset All Options for This Section' );
208 $buttonWrapper.append( $button );
209
210 $this.append( $buttonWrapper );
211 } );
212 }
213
214 function onReset(ev) {
215 ev.preventDefault()
216
217 var iAgree = confirm( 'Do you really want to reset to defaults all the fields? Watch out, this will reset all your Customify options and will save them!' )
218
219 if ( ! iAgree ) {
220 return
221 }
222
223 $.each( api.settings.controls, function( key, ctrl ) {
224 const setting_id = key.replace( '_control', '' )
225 const setting = customify_settings.settings[setting_id]
226
227 if ( !_.isUndefined( setting ) && !_.isUndefined( setting.default ) ) {
228 api_set_setting_value( setting_id, setting.default )
229 }
230 } );
231
232 api.previewer.save();
233 }
234
235 function onResetPanel(e) {
236 e.preventDefault()
237
238 const panel_id = $(this).data('panel'),
239 panel = api.panel(panel_id),
240 sections = panel.sections(),
241 iAgree = confirm('Do you really want to reset ' + panel.params.title + '?')
242
243 if (!iAgree) {
244 return
245 }
246 if (sections.length > 0) {
247 $.each(sections, function () {
248 //var settings = this.settings();
249 const controls = this.controls()
250
251 if (controls.length > 0) {
252 $.each(controls, function (key, ctrl) {
253 const setting_id = ctrl.id.replace('_control', ''),
254 setting = customify_settings.settings[setting_id]
255
256 if (!_.isUndefined(setting) && !_.isUndefined(setting.default)) {
257 api_set_setting_value(setting_id, setting.default)
258 }
259 })
260 }
261 })
262 }
263 }
264
265 function onResetSection(e) {
266 e.preventDefault()
267
268 const section_id = $(this).data('section'),
269 section = api.section(section_id),
270 controls = section.controls()
271
272 const iAgree = confirm('Do you really want to reset ' + section.params.title + '?')
273
274 if (!iAgree) {
275 return
276 }
277
278 if (controls.length > 0) {
279 $.each(controls, function (key, ctrl) {
280 const setting_id = ctrl.id.replace('_control', ''),
281 setting = customify_settings.settings[setting_id]
282
283 if (!_.isUndefined(setting) && !_.isUndefined(setting.default)) {
284 api_set_setting_value(setting_id, setting.default)
285 }
286 })
287 }
288 }
289
290 function handleSectionTabs() {
291 const $navs = $( '.js-section-navigation' );
292
293 $navs.each( function() {
294 var $nav = $( this )
295 var $title = $nav.parents( '.accordion-section-content' ).find( '.customize-section-title' );
296 var $parent = $nav.closest( '.customize-control' );
297
298 $nav.appendTo( $title );
299 $title.parent().addClass( 'has-nav' );
300 $parent.addClass( 'screen-reader-text' );
301 } );
302
303 $( '.js-section-navigation a' ).on( 'click', function( e ) {
304 e.preventDefault();
305
306 var $this = $( this );
307 var $sidebar = $this.parents( '.wp-full-overlay-sidebar-content' );
308 var $parent = $this.parents( '.accordion-section-content' );
309 var href = $this.attr('href' );
310
311 if ( href != '#' ) {
312 var actionsHeight = $( '#customize-header-actions' ).outerHeight();
313 var titleHeight = $parent.find( '.customize-section-title' ).outerHeight();
314 var $target = $( href );
315 var offset = $target.position().top;
316
317 $sidebar.animate( { scrollTop: offset - titleHeight - actionsHeight }, 500 );
318 }
319 } );
320 }
321
322 const getConnectedFieldsCallback = function (parent_setting_data, parent_setting_id) {
323 return function (new_value, old_value) {
324 _.each(parent_setting_data.connected_fields, function (connected_field_data) {
325 if (_.isUndefined(connected_field_data) || _.isUndefined(connected_field_data.setting_id) || !_.isString(connected_field_data.setting_id)) {
326 return
327 }
328 const setting = wp.customize(connected_field_data.setting_id)
329 if (_.isUndefined(setting)) {
330 return
331 }
332 setting.set(new_value)
333 })
334 }
335 }
336
337 const bindConnectedFields = function () {
338 _.each(wp.customize.settings.settings, function (parent_setting_data, parent_setting_id) {
339 // We don't want to handle the binding of the Style Manager settings
340 if (typeof ColorPalettes !== 'undefined'
341 && typeof ColorPalettes.masterSettingIds !== 'undefined'
342 && _.contains(ColorPalettes.masterSettingIds, parent_setting_id)) {
343 return
344 }
345 if (typeof FontPalettes !== 'undefined'
346 && typeof FontPalettes.masterSettingIds !== 'undefined'
347 && _.contains(FontPalettes.masterSettingIds, parent_setting_id)) {
348 return
349 }
350
351 let parent_setting = wp.customize(parent_setting_id)
352 if (typeof parent_setting_data.connected_fields !== 'undefined') {
353 connectedFieldsCallbacks[parent_setting_id] = getConnectedFieldsCallback(parent_setting_data, parent_setting_id)
354 parent_setting.bind(connectedFieldsCallbacks[parent_setting_id])
355 }
356 })
357 }
358
359 const unbindConnectedFields = function () {
360 _.each(wp.customize.settings.settings, function (parent_setting_data, parent_setting_id) {
361 // We don't want to handle the binding of the Style Manager settings
362 if (typeof ColorPalettes !== 'undefined'
363 && typeof ColorPalettes.masterSettingIds !== 'undefined'
364 && _.contains(ColorPalettes.masterSettingIds, parent_setting_id)) {
365 return
366 }
367 if (typeof FontPalettes !== 'undefined'
368 && typeof FontPalettes.masterSettingIds !== 'undefined'
369 && _.contains(FontPalettes.masterSettingIds, parent_setting_id)) {
370 return
371 }
372
373 let parent_setting = wp.customize(parent_setting_id)
374 if (typeof parent_setting_data.connected_fields !== 'undefined' && typeof connectedFieldsCallbacks[parent_setting_id] !== 'undefined') {
375 parent_setting.unbind(connectedFieldsCallbacks[parent_setting_id])
376 }
377 delete connectedFieldsCallbacks[parent_setting_id]
378 })
379 }
380
381 const customifyHandleRangeFields = function (el) {
382
383 // For each range input add a number field (for preview mainly - but it can also be used for input)
384 $( el ).find( 'input[type="range"]' ).each( function() {
385 var $range = $( this ),
386 $number = $range.siblings( '.range-value' );
387
388 if ( ! $number.length ) {
389 $number = $range.clone();
390
391 $number
392 .attr( 'type', 'number' )
393 .attr( 'class', 'range-value' )
394 .removeAttr( 'data-field' )
395 .insertAfter( $range );
396 }
397
398 function hasValidValue( $input ) {
399 var min = $input.attr( 'min' );
400 var max = $input.attr( 'max' );
401 var value = $input.val();
402
403 if ( typeof min !== "undefined" && parseFloat( min ) > parseFloat( value ) ) {
404 return false;
405 }
406
407 if ( typeof max !== "undefined" && parseFloat( max ) < value ) {
408 return false;
409 }
410
411 return true;
412 }
413
414 $range.on( 'input', function() {
415 $number.val( $range.val() );
416 } );
417
418 $number.on( 'blur', function() {
419 if ( ! hasValidValue( $number ) ) {
420 $number.val( $range.val() );
421 shake( $number );
422 } else {
423 $range.val( $number.val() );
424 }
425 } );
426
427 function shake( $field ) {
428 $field.addClass( 'input-shake input-error' );
429 $field.one( 'animationend', function() {
430 $field.removeClass( 'input-shake input-error' );
431 } );
432 }
433 })
434 }
435
436 /**
437 * This function will search for all the interdependend fields and make a bound between them.
438 * So whenever a target is changed, it will take actions to the dependent fields.
439 * @TODO this is still written in a barbaric way, refactor when needed
440 */
441 const customifyFoldingFields = function () {
442
443 if (_.isUndefined(customify_settings) || _.isUndefined(customify_settings.settings)) {
444 return // bail
445 }
446
447 /**
448 * Let's iterate through all the customify settings and gather all the fields that have a "show_if"
449 * property set.
450 *
451 * At the end `targets` will hold a list of [ target : [field, field,...], ... ]
452 * so when a target is changed we will change all the fields.
453 */
454 var targets = {}
455
456 $.fn.reactor.defaults.compliant = function () {
457 $(this).slideDown()
458 // $(this).animate({opacity: 1});
459 $(this).find(':disabled').attr({disabled: false})
460 }
461
462 $.fn.reactor.defaults.uncompliant = function () {
463 $(this).slideUp()
464 // $(this).animate({opacity: 0.25});
465 $(this).find(':enabled').attr({disabled: true})
466 }
467
468 let IS = $.extend({}, $.fn.reactor.helpers)
469
470 let bind_folding_events = function (parent_id, field, relation) {
471
472 let key = null
473
474 if (_.isString(field)) {
475 key = field
476 } else if (!_.isUndefined(field.id)) {
477 key = field.id
478 } else if (isString(field[0])) {
479 key = field[0]
480 } else {
481 return // no key, no fun
482 }
483
484 let value = 1, // by default we use 1 the most used value for checkboxes or inputs
485 compare = '==', // ... ye
486 action = 'show',
487 between = [0, 1] // can only be `show` or `hide`
488
489 const target_key = customify_settings.options_name + '[' + key + ']'
490 const target_type = customify_settings.settings[target_key].type
491
492 // we support the usual syntax like a config array like `array( 'id' => $id, 'value' => $value, 'compare' => $compare )`
493 // but we also support a non-associative array like `array( $id, $value, $compare )`
494 if (!_.isUndefined(field.value)) {
495 value = field.value
496 } else if (!_.isUndefined(field[1]) && !_.isString(field[1])) {
497 value = field[1]
498 }
499
500 if (!_.isUndefined(field.compare)) {
501 compare = field.compare
502 } else if (!_.isUndefined(field[2])) {
503 compare = field[2]
504 }
505
506 if (!_.isUndefined(field.action)) {
507 action = field.action
508 } else if (!_.isUndefined(field[3])) {
509 action = field[3]
510 }
511
512 // a field can also overwrite the parent relation
513 if (!_.isUndefined(field.relation)) {
514 action = field.relation
515 } else if (!_.isUndefined(field[4])) {
516 action = field[4]
517 }
518
519 if (!_.isUndefined(field.between)) {
520 between = field.between
521 }
522
523 /**
524 * Now for each target we have, we will bind a change event to hide or show the dependent fields
525 */
526 const target_selector = '[data-customize-setting-link="' + customify_settings.options_name + '[' + key + ']"]'
527
528 switch (target_type) {
529 case 'checkbox':
530 $(parent_id).reactIf(target_selector, function () {
531 return $(this).is(':checked') == value
532 })
533 break
534
535 case 'radio':
536 case 'sm_radio':
537 case 'sm_switch':
538 case 'radio_image':
539 case 'radio_html':
540
541 // in case of an array of values we use the ( val in array) condition
542 if (_.isObject(value)) {
543 $(parent_id).reactIf(target_selector, function () {
544 return (
545 value.indexOf($(target_selector + ':checked').val()) !== -1
546 )
547 })
548 } else { // in any other case we use a simple == comparison
549 $(parent_id).reactIf(target_selector, function () {
550 return $(target_selector + ':checked').val() == value
551 })
552 }
553 break
554
555 case 'range':
556 const x = IS.Between(between[0], between[1])
557
558 $(parent_id).reactIf(target_selector, x)
559 break
560
561 default:
562 // in case of an array of values we use the ( val in array) condition
563 if (_.isObject(value)) {
564 $(parent_id).reactIf(target_selector, function () {
565 return (
566 value.indexOf($(target_selector).val()) !== -1
567 )
568 })
569 } else { // in any other case we use a simple == comparison
570 $(parent_id).reactIf(target_selector, function () {
571 return $(target_selector).val() == value
572 })
573 }
574 break
575 }
576
577 $(target_selector).trigger('change')
578 $('.reactor').trigger('change.reactor') // triggers all events on load
579 }
580
581 $.each(customify_settings.settings, function (id, field) {
582 /**
583 * Here we have the id of the fields. but we know for sure that we just need his parent selector
584 * So we just create it
585 */
586 let parent_id = id.replace('[', '-')
587 parent_id = parent_id.replace(']', '')
588 parent_id = '#customize-control-' + parent_id + '_control'
589
590 // get only the fields that have a 'show_if' property
591 if (field.hasOwnProperty('show_if')) {
592 let relation = 'AND'
593
594 if (!_.isUndefined(field.show_if.relation)) {
595 relation = field.show_if.relation
596 // remove the relation property, we need the config to be array based only
597 delete field.show_if.relation
598 }
599
600 /**
601 * The 'show_if' can be a simple array with one target like: [ id, value, comparison, action ]
602 * Or it could be an array of multiple targets and we need to process both cases
603 */
604
605 if (!_.isUndefined(field.show_if.id)) {
606 bind_folding_events(parent_id, field.show_if, relation)
607 } else if (_.isObject(field.show_if)) {
608 $.each(field.show_if, function (i, j) {
609 bind_folding_events(parent_id, j, relation)
610 })
611 }
612 }
613 })
614 }
615
616 const get_typography_font_family = function ($el) {
617
618 let font_family_value = $el.val()
619 // first time this will not be a json so catch that error
620 try {
621 font_family_value = JSON.parse(font_family_value)
622 } catch (e) {
623 return {font_family: font_family_value}
624 }
625
626 if (!_.isUndefined(font_family_value.font_family)) {
627 return font_family_value.font_family
628 }
629
630 return false
631 }
632
633 // get each typography field and bind events
634 // @todo Are we still using the typography field since we have the font field?
635 // Yes we do, in older themes.
636 const prepare_typography_field = function () {
637
638 const $typos = $('.customify_typography_font_family')
639
640 $typos.each(function () {
641 var font_family_select = this,
642 $input = $(font_family_select).siblings('.customify_typography_values')
643 // on change
644 $(font_family_select).on('change', function () {
645 update_siblings_selects(font_family_select)
646 $input.trigger('change')
647 })
648 update_siblings_selects(font_family_select)
649 })
650 }
651
652 const api_set_setting_value = function (setting_id, value) {
653 let setting = api(setting_id),
654 field = $('[data-customize-setting-link="' + setting_id + '"]'),
655 field_class = $(field).parent().attr('class')
656
657 // Legacy field type
658 if (!_.isUndefined(field_class) && field_class === 'customify_typography') {
659
660 let family_select = field.siblings('select')
661
662 if (_.isString(value)) {
663 let this_option = family_select.find('option[value="' + value + '"]')
664 $(this_option[0]).attr('selected', 'selected')
665 update_siblings_selects(family_select)
666 } else if (_.isObject(value)) {
667 let this_family_option = family_select.find('option[value="' + value['font_family'] + '"]')
668
669 $(this_family_option[0]).attr('selected', 'selected')
670
671 update_siblings_selects(this_family_option)
672
673 setTimeout(function () {
674 let weight_select = field.parent().siblings('.options').find('.customify_typography_font_weight'),
675 this_weight_option = weight_select.find('option[value="' + value['selected_variants'] + '"]')
676
677 $(this_weight_option[0]).attr('selected', 'selected')
678
679 update_siblings_selects(this_family_option)
680
681 weight_select.trigger('change')
682 }, 300)
683 }
684
685 family_select.trigger('change')
686
687 } else if (!_.isUndefined(field_class) && field_class === 'font-options__wrapper') {
688
689 // if the value is a simple string it must be the font family
690 if (_.isString(value)) {
691 let option = field.parent().find('option[value="' + value + '"]')
692
693 option.attr('selected', 'selected')
694 // option.parents('select').trigger('change');
695 } else if (_.isObject(value)) {
696 // Find the options list wrapper
697 let optionsList = field.parent().children('.font-options__options-list')
698
699 if (optionsList.length) {
700 // We will process each font property and update it
701 _.each(value, function (val, key) {
702 // We need to map the keys to the data attributes we are using - I know :(
703 let mappedKey = key
704 switch (key) {
705 case 'font-family':
706 mappedKey = 'font_family'
707 break
708 case 'font-size':
709 mappedKey = 'font_size'
710 break
711 case 'font-weight':
712 mappedKey = 'selected_variants'
713 break
714 case 'letter-spacing':
715 mappedKey = 'letter_spacing'
716 break
717 case 'text-transform':
718 mappedKey = 'text_transform'
719 break
720 default:
721 break
722 }
723 let subField = optionsList.find('[data-field="' + mappedKey + '"]')
724 if (subField.length) {
725 subField.val(val)
726 subField.trigger('change')
727 }
728 })
729 }
730 }
731
732 } else {
733 setting.set(value)
734 }
735 }
736
737 const update_siblings_selects = function (font_select) {
738 let selected_font = $(font_select).val(),
739 $input = $(font_select).siblings('.customify_typography_values'),
740 current_val = $input.attr('value')
741
742 if (current_val === '[object Object]') {
743 current_val = $input.data('default')
744 } else if (_.isString(current_val) && !isJsonString(current_val)) {
745 // a rare case when the value isn't a json but is a representative string like [family,weight]
746 current_val = current_val.split(',')
747 let new_current_value = {}
748 if (!_.isUndefined(current_val[0])) {
749 new_current_value['font_family'] = current_val[0]
750 }
751
752 if (!_.isUndefined(current_val[1])) {
753 new_current_value['selected_variants'] = current_val[1]
754 }
755
756 current_val = JSON.stringify(new_current_value)
757 }
758
759 let $font_weight = $(font_select).parent().siblings('ul.options').find('.customify_typography_font_weight'),
760 $font_subsets = $(font_select).parent().siblings('ul.options').find('.customify_typography_font_subsets')
761
762 try {
763 current_val = JSON.parse(decodeURIComponent(current_val))
764 } catch (e) {
765
766 // in case of an error, force the rebuild of the json
767 if (_.isUndefined($(font_select).data('bound_once'))) {
768
769 $(font_select).data('bound_once', true)
770
771 $(font_select).change()
772 $font_weight.change()
773 $font_subsets.change()
774 }
775 }
776
777 // first try to get the font from sure sources, not from the recommended list.
778 let option_data = $(font_select).find(':not(optgroup[label=Recommended]) option[value="' + selected_font + '"]')
779 // however, if there isn't an option found, get what you can
780 if (option_data.length < 1) {
781 option_data = $(font_select).find('option[value="' + selected_font + '"]')
782 }
783
784 if (option_data.length > 0) {
785
786 let font_type = option_data.data('type'),
787 value_to_add = {'type': font_type, 'font_family': selected_font},
788 variants = null,
789 subsets = null
790
791 if (font_type == 'std') {
792 variants = {
793 0: '100',
794 1: '200',
795 3: '300',
796 4: '400',
797 5: '500',
798 6: '600',
799 7: '700',
800 8: '800',
801 9: '900'
802 }
803 if (!_.isUndefined($(option_data[0]).data('variants'))) {
804 //maybe the variants are a JSON
805 variants = maybeJsonParse($(option_data[0]).data('variants'))
806 }
807 } else {
808 //maybe the variants are a JSON
809 variants = maybeJsonParse($(option_data[0]).data('variants'))
810
811 //maybe the subsets are a JSON
812 subsets = maybeJsonParse($(option_data[0]).data('subsets'))
813 }
814
815 // make the variants selector
816 if (!_.isUndefined(variants) && !_.isNull(variants) && !_.isEmpty(variants)) {
817
818 value_to_add['variants'] = variants
819 // when a font is selected force the first weight to load
820 value_to_add['selected_variants'] = {0: variants[0]}
821
822 let variants_options = '',
823 count_weights = 0
824
825 if (_.isArray(variants) || _.isObject(variants)) {
826 // Take each variant and produce the option markup
827 $.each(variants, function (key, el) {
828 let is_selected = ''
829 if (_.isObject(current_val.selected_variants) && inObject(el, current_val.selected_variants)) {
830 is_selected = ' selected="selected"'
831 } else if (_.isString(current_val.selected_variants) && el === current_val.selected_variants) {
832 is_selected = ' selected="selected"'
833 }
834
835 // initialize
836 let variant_option_value = el,
837 variant_option_display = el
838
839 // If we are dealing with a object variant then it means things get tricky (probably it's our fault but bear with us)
840 // This probably comes from our Fonto plugin - a font with individually named variants - hence each has its own font-family
841 if (_.isObject(el)) {
842 //put the entire object in the variation value - we will need it when outputting the custom CSS
843 variant_option_value = encodeURIComponent(JSON.stringify(el))
844 variant_option_display = ''
845
846 //if we have weight and style then "compose" them into something standard
847 if (!_.isUndefined(el['font-weight'])) {
848 variant_option_display += el['font-weight']
849 }
850
851 if (_.isString(el['font-style']) && $.inArray(el['font-style'].toLowerCase(), [
852 'normal',
853 'regular'
854 ]) < 0) { //this comparison means it hasn't been found
855 variant_option_display += el['font-style']
856 }
857 }
858
859 variants_options += '<option value="' + variant_option_value + '"' + is_selected + '>' + variant_option_display + '</option>'
860 count_weights++
861 })
862 }
863
864 if (!_.isUndefined($font_weight)) {
865 $font_weight.html(variants_options)
866 // if there is no weight or just 1 we hide the weight select ... cuz is useless
867 if ($(font_select).data('load_all_weights') === true || count_weights <= 1) {
868 $font_weight.parent().css('display', 'none')
869 } else {
870 $font_weight.parent().css('display', 'inline-block')
871 }
872 }
873 } else if (!_.isUndefined($font_weight)) {
874 $font_weight.parent().css('display', 'none')
875 }
876
877 // make the subsets selector
878 if (!_.isUndefined(subsets) && !_.isNull(subsets) && !_.isEmpty(subsets)) {
879
880 value_to_add['subsets'] = subsets
881 // when a font is selected force the first subset to load
882 value_to_add['selected_subsets'] = {0: subsets[0]}
883 let subsets_options = '',
884 count_subsets = 0
885 $.each(subsets, function (key, el) {
886 let is_selected = ''
887 if (_.isObject(current_val.selected_subsets) && inObject(el, current_val.selected_subsets)) {
888 is_selected = ' selected="selected"'
889 }
890
891 subsets_options += '<option value="' + el + '"' + is_selected + '>' + el + '</option>'
892 count_subsets++
893 })
894
895 if (!_.isUndefined($font_subsets)) {
896 $font_subsets.html(subsets_options)
897
898 // if there is no subset or just 1 we hide the subsets select ... cuz is useless
899 if (count_subsets <= 1) {
900 $font_subsets.parent().css('display', 'none')
901 } else {
902 $font_subsets.parent().css('display', 'inline-block')
903 }
904 }
905 } else if (!_.isUndefined($font_subsets)) {
906 $font_subsets.parent().css('display', 'none')
907 }
908
909 $input.val(encodeURIComponent(JSON.stringify(value_to_add)))
910 }
911 }
912
913 /** Modules **/
914
915 const customifyBackgroundJsControl = (
916 function () {
917 'use strict'
918
919 function init () {
920 // Remove the image button
921 $('.customize-control-custom_background .remove-image, .customize-control-custom_background .remove-file').unbind('click').on('click', function (e) {
922 removeImage($(this).parents('.customize-control-custom_background:first'))
923 preview($(this))
924 return false
925 })
926
927 // Upload media button
928 $('.customize-control-custom_background .background_upload_button').unbind().on('click', function (event) {
929 addImage(event, $(this).parents('.customize-control-custom_background:first'))
930 })
931
932 $('.customify_background_select').on('change', function () {
933 preview($(this))
934 })
935 }
936
937 // Add a file via the wp.media function
938 function addImage (event, selector) {
939
940 event.preventDefault()
941
942 var frame
943 var jQueryel = jQuery(this)
944
945 // If the media frame already exists, reopen it.
946 if (frame) {
947 frame.open()
948 return
949 }
950
951 // Create the media frame.
952 frame = wp.media({
953 multiple: false,
954 library: {
955 //type: 'image' //Only allow images
956 },
957 // Set the title of the modal.
958 title: jQueryel.data('choose'),
959
960 // Customize the submit button.
961 button: {
962 // Set the text of the button.
963 text: jQueryel.data('update')
964 // Tell the button not to close the modal, since we're
965 // going to refresh the page when the image is selected.
966 }
967 })
968
969 // When an image is selected, run a callback.
970 frame.on('select', function () {
971 // Grab the selected attachment.
972 const attachment = frame.state().get('selection').first()
973 frame.close()
974
975 if (attachment.attributes.type !== 'image') {
976 return
977 }
978
979 selector.find('.upload').attr('value', attachment.attributes.url)
980 selector.find('.upload-id').attr('value', attachment.attributes.id)
981 selector.find('.upload-height').attr('value', attachment.attributes.height)
982 selector.find('.upload-width').attr('value', attachment.attributes.width)
983
984 let thumbSrc = attachment.attributes.url
985 if (!_.isUndefined(attachment.attributes.sizes) && !_.isUndefined(attachment.attributes.sizes.thumbnail)) {
986 thumbSrc = attachment.attributes.sizes.thumbnail.url
987 } else if (!_.isUndefined(attachment.attributes.sizes)) {
988 let height = attachment.attributes.height
989 for (let key in attachment.attributes.sizes) {
990 const object = attachment.attributes.sizes[key]
991 if (object.height < height) {
992 height = object.height
993 thumbSrc = object.url
994 }
995 }
996 } else {
997 thumbSrc = attachment.attributes.icon
998 }
999
1000 selector.find('.customify_background_input.background-image').val(attachment.attributes.url)
1001
1002 if (!selector.find('.upload').hasClass('noPreview')) {
1003 selector.find('.preview_screenshot').empty().hide().append('<img class="preview_image" src="' + thumbSrc + '">').slideDown('fast')
1004 }
1005 //selector.find('.media_upload_button').unbind();
1006 selector.find('.remove-image').removeClass('hide')//show "Remove" button
1007 selector.find('.customify_background_select').removeClass('hide')//show "Remove" button
1008
1009 preview(selector)
1010 })
1011
1012 // Finally, open the modal.
1013 frame.open()
1014 }
1015
1016 // Update the background preview
1017 function preview (selector) {
1018
1019 let $parent = selector.parents('.customize-control-custom_background:first')
1020
1021 if (selector.hasClass('customize-control-custom_background')) {
1022 $parent = selector
1023 }
1024
1025 if ($parent.length > 0) {
1026 $parent = $($parent[0])
1027 } else {
1028 return
1029 }
1030
1031 const image_holder = $parent.find('.background-preview')
1032
1033 if (!image_holder) { // No preview present
1034 return
1035 }
1036
1037 const the_id = $parent.find('.button.background_upload_button').data('setting_id'),
1038 this_setting = api.instance(the_id)
1039
1040 let background_data = {}
1041
1042 $parent.find('.customify_background_select, .customify_background_input').each(function () {
1043 var data = $(this).serializeArray()
1044
1045 data = data[0]
1046 if (data && data.name.indexOf('[background-') != -1) {
1047
1048 background_data[$(this).data('select_name')] = data.value
1049
1050 //default_default[data.name] = data.value;
1051 //if (data.name == "background-image") {
1052 // css += data.name + ':url("' + data.value + '");';
1053 //} else {
1054 // css += data.name + ':' + data.value + ';';
1055 //}
1056 }
1057 })
1058
1059 api.instance(the_id).set(background_data)
1060 //// Notify the customizer api about this change
1061 api.trigger('change')
1062 api.previewer.refresh()
1063
1064 //image_holder.attr('style', css).fadeIn();
1065 }
1066
1067 // Update the background preview
1068 function removeImage (parent) {
1069 const selector = parent.find('.upload_button_div')
1070 // This shouldn't have been run...
1071 if (!selector.find('.remove-image').addClass('hide')) {
1072 return
1073 }
1074
1075 selector.find('.remove-image').addClass('hide')//hide "Remove" button
1076 parent.find('.customify_background_select').addClass('hide')
1077
1078 selector.find('.upload').val('')
1079 selector.find('.upload-id').val('')
1080 selector.find('.upload-height').val('')
1081 selector.find('.upload-width').val('')
1082 parent.find('.customify_background_input.background-image').val('')
1083
1084 let customizer_id = selector.find('.background_upload_button').data('setting_id'),
1085 this_setting = api.control(customizer_id + '_control'),
1086 current_vals = this_setting.setting(),
1087 screenshot = parent.find('.preview_screenshot'),
1088 to_array = $.map(current_vals, function (value, index) {
1089 return [value]
1090 })
1091
1092 // Hide the screenshot
1093 screenshot.slideUp()
1094 selector.find('.remove-file').unbind()
1095 to_array['background-image'] = ''
1096 this_setting.setting(to_array)
1097 }
1098
1099 return {
1100 init: init
1101 }
1102 }
1103 )(jQuery)
1104
1105 /** HELPERS **/
1106
1107 /**
1108 * Function to check if a value exists in an object
1109 * @param value
1110 * @param obj
1111 * @returns {boolean}
1112 */
1113 const inObject = function (value, obj) {
1114 for (let k in obj) {
1115 if (!obj.hasOwnProperty(k)) {
1116 continue
1117 }
1118 if (_.isEqual(obj[k], value)) {
1119 return true
1120 }
1121 }
1122 return false
1123 }
1124
1125 const maybeJsonParse = function (value) {
1126 let parsed
1127
1128 //try and parse it, with decodeURIComponent
1129 try {
1130 parsed = JSON.parse(decodeURIComponent(value))
1131 } catch (e) {
1132
1133 // in case of an error, treat is as a string
1134 parsed = value
1135 }
1136
1137 return parsed
1138 }
1139
1140 const getUrlVars = function (name) {
1141 let vars = [], hash
1142 const hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&')
1143 for (let i = 0; i < hashes.length; i++) {
1144 hash = hashes[i].split('=')
1145
1146 vars.push(hash[0])
1147 vars[hash[0]] = hash[1]
1148 }
1149
1150 if (!_.isUndefined(vars[name])) {
1151 return vars[name]
1152 }
1153 return false
1154 }
1155
1156 const isJsonString = function (str) {
1157 try {
1158 JSON.parse(str)
1159 } catch (e) {
1160 return false
1161 }
1162 return true
1163 }
1164 }
1165 )(jQuery, window, wp)
1166