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 / font-select-fields.js

font-select-fields.js in Customify 2.6.0, at js/customizer/font-select-fields.js

497 lines 17.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // This is for the Customizer Font control
2 let CustomifyFontSelectFields = (function ($, exports, wp) {
3 const
4 wrapperSelector = '.font-options__wrapper',
5 valueHolderSelector = '.customify_font_values',
6 fontFamilySelector = '.customify_font_family',
7 fontWeightSelector = '.customify_font_weight',
8 fontSubsetsSelector = '.customify_font_subsets',
9 selectPlaceholder = 'Select a font family',
10 weightPlaceholder = 'Select a font weight',
11 subsetPlaceholder = 'Extra Subsets'
12
13 // We will use this to remember that we are self-updating the field from the subfields.
14 // We will save this info for each setting ID.
15 var updatingValue = {},
16 loadingValue = {}
17
18 function init () {
19 let $fontFamilyFields = $(fontFamilySelector)
20
21 // Add the Google Fonts opts to each control.
22 if ( typeof wp.customize.settings['google_fonts_opts'] !== "undefined" ) {
23 $fontFamilyFields.each(function (i, el) {
24 let google_opts_placeholder = $(el).find('.google-fonts-opts-placeholder').first();
25 if ( google_opts_placeholder ) {
26 // Replace the placeholder with the HTML for the Google fonts select options.
27 google_opts_placeholder.replaceWith( wp.customize.settings['google_fonts_opts'] );
28
29 // The active font family might be a Google font so we need to set the current value after we've added the options.
30 let active_font_family = $(el).data('active_font_family');
31 if ( typeof active_font_family !== "undefined" ) {
32 $(el).val(active_font_family);
33 }
34 }
35 })
36 }
37
38 $fontFamilyFields.select2({
39 placeholder: selectPlaceholder
40 }).on('change', function (e) {
41 let new_option = $(e.target).find('option:selected'),
42 wrapper = $(e.target).closest(wrapperSelector)
43
44 // Update the weight subfield with the new options given by the selected font family.
45 updateWeightField(new_option, wrapper)
46
47 // Update the subset subfield with the new options given by the selected font family.
48 updateSubsetField(new_option, wrapper)
49
50 // Serialize subfield values and refresh the fonts in the preview window.
51 selfUpdateValue(wrapper)
52 });
53
54 // Initialize the select2 field for the font family
55 $fontFamilyFields.on( 'change', function( e ) {
56 let new_option = $( e.target ).find( 'option:selected' ),
57 wrapper = $( e.target ).closest( wrapperSelector )
58
59 // Update the weight subfield with the new options given by the selected font family.
60 updateWeightField( new_option, wrapper );
61
62 // Update the subset subfield with the new options given by the selected font family.
63 updateSubsetField( new_option, wrapper );
64
65 // Serialize subfield values and refresh the fonts in the preview window.
66 selfUpdateValue( wrapper );
67 } );
68
69 // Initialize the select2 field for the font weight
70 $(fontWeightSelector).each(function (i, el) {
71
72 let select2_args = {
73 theme: 'classic',
74 placeholder: weightPlaceholder,
75 minimumResultsForSearch: 10,
76 }
77
78 // all this fuss is for the case when the font doesn't come with variants from PHP, like a theme_font
79 if (this.options.length === 0) {
80 let wrapper = $(el).closest(wrapperSelector),
81 font = wrapper.find(fontFamilySelector),
82 option = font[0].options[font[0].selectedIndex],
83 variants = maybeJsonParse($(option).data('variants')),
84 data = [],
85 selected_variants = $(el).data('default') || null
86
87 if (typeof variants === 'undefined') {
88 $(this).hide()
89 return
90 }
91
92 $.each(variants, function (index, weight) {
93 let this_value = {
94 id: weight,
95 text: weight
96 }
97
98 // @todo We actually do not support multiple selected variants. Maybe we should? Right now we don't use multiple selections.
99 if (selected_variants !== null && weight == selected_variants) {
100 this_value.selected = true
101 }
102
103 data.push(this_value)
104 })
105
106 if (data !== []) {
107 select2_args.data = data
108 }
109 }
110
111 $(this).select2(
112 select2_args
113 ).on('change', function (e) {
114 let wrapper = $(e.target).closest(wrapperSelector)
115
116 // Serialize subfield values and refresh the fonts in the preview window.
117 selfUpdateValue(wrapper)
118 })
119 })
120
121 // Initialize the select2 field for the font subsets
122 $(fontSubsetsSelector)
123 .select2({
124 placeholder: subsetPlaceholder,
125 theme: 'classic',
126 minimumResultsForSearch: 10,
127 })
128 .on('change', function (e) {
129 let wrapper = $(e.target).closest(wrapperSelector)
130
131 // Serialize subfield values and refresh the fonts in the preview window.
132 selfUpdateValue(wrapper)
133 })
134
135 let rangers = $fontFamilyFields.parents(wrapperSelector).find('input[type=range]'),
136 selects = $fontFamilyFields.parents(wrapperSelector).find('select').not('select[class*=\' select2\'],select[class^=\'select2\']')
137
138 // Initialize the all the regular selects in the font controls
139 if (selects.length > 0) {
140 selects.on('change', function (e) {
141 let wrapper = $(e.target).closest(wrapperSelector)
142
143 // Serialize subfield values and refresh the fonts in the preview window.
144 selfUpdateValue(wrapper)
145 })
146 }
147
148 // Initialize the all the range fields in the font controls
149 if (rangers.length > 0) {
150 rangers.on('change', function (e) {
151 let wrapper = $(e.target).closest(wrapperSelector)
152
153 // Serialize subfield values and refresh the fonts in the preview window.
154 selfUpdateValue(wrapper)
155
156 wp.customize.previewer.send('font-changed')
157 })
158 }
159
160 // When the previewer window is ready, render the fonts
161 var self = this
162 wp.customize.previewer.bind('ready', function () {
163 self.renderFonts()
164 })
165
166 // Handle the reverse value direction, when the customize setting is updated and the subfields need to update their values.
167 $fontFamilyFields.each(function (i, el) {
168 let wrapper = $(el).closest(wrapperSelector),
169 value_holder = wrapper.children(valueHolderSelector),
170 setting_id = $(value_holder).data('customize-setting-link'),
171 setting = wp.customize(setting_id)
172
173 setting.bind(function (newValue, oldValue) {
174 if (!updatingValue[this.id]) {
175 value_holder.val(newValue)
176
177 loadFontValue(wrapper)
178 }
179 })
180 })
181 }
182
183 /**
184 * This function updates the data in font weight selector from the given <option> element
185 *
186 * @param option
187 * @param wraper
188 */
189 function updateWeightField (option, wraper) {
190 let variants = $(option).data('variants'),
191 font_weights = wraper.find(fontWeightSelector),
192 selected_variant = font_weights.val() ? font_weights.val() : font_weights.data('default'),
193 new_variants = [],
194 id = wraper.find(valueHolderSelector).data('customizeSettingLink')
195
196 variants = maybeJsonParse(variants)
197
198 if (customify_settings.settings[id].load_all_weights || typeof variants === 'undefined' || Object.keys(variants).length < 2 || font_weights.data('disabled') !== undefined) {
199 font_weights.parent().hide()
200 } else {
201 font_weights.parent().show()
202 }
203
204 // we need to turn the data array into a specific form like [{id:"id", text:"Text"}]
205 $.each(variants, function (index, variant) {
206 new_variants[index] = {
207 'id': variant,
208 'text': variant
209 }
210
211 if (selected_variant == variant) {
212 new_variants[index].selected = true
213 }
214 })
215
216 // We need to clear the old select2 field and reinitialize it.
217 $(font_weights).select2().empty()
218 $(font_weights).select2({
219 theme: 'classic',
220 data: new_variants,
221 minimumResultsForSearch: 10,
222 }).on('change', function (e) {
223 let wrapper = $(e.target).closest(wrapperSelector)
224
225 // Serialize subfield values and refresh the fonts in the preview window.
226 selfUpdateValue(wrapper)
227 })
228 }
229
230 /**
231 * This function updates the data in font subset selector from the given <option> element
232 * @param option
233 * @param wraper
234 */
235 function updateSubsetField (option, wraper) {
236 let subsets = $(option).data('subsets'),
237 font_subsets = wraper.find(fontSubsetsSelector),
238 new_subsets = [],
239 type = $(option).data('type')
240
241 if (type !== 'google') {
242 font_subsets.parent().hide()
243 return
244 }
245
246 let current_value = wraper.children(valueHolderSelector).val()
247
248 current_value = maybeJsonParse(current_value)
249 if (_.isUndefined(current_value.selected_subsets)) {
250 return
251 }
252 current_value = current_value.selected_subsets
253
254 subsets = maybeJsonParse(subsets)
255
256 if (typeof subsets !== 'undefined' && Object.keys(subsets).length < 2 || font_subsets.data('disabled') !== undefined) {
257 font_subsets.parent().hide()
258 } else {
259 font_subsets.parent().show()
260 }
261
262 // we need to turn the data array into a specific form like [{id:"id", text:"Text"}]
263 $.each(subsets, function (index, subset) {
264 new_subsets[index] = {
265 'id': subset,
266 'text': subset
267 }
268
269 // current_subsets
270 if ( typeof current_value !== 'undefined' && current_value !== null ) {
271 if ( ! Array.isArray( current_value ) ) {
272 current_value = Object.keys( current_value ).map( function( key ) {
273 return current_value[ key ];
274 } );
275 }
276 if ( current_value.indexOf(subset) !== -1) {
277 new_subsets[index].selected = true
278 }
279 }
280 });
281
282 // We need to clear the old select2 field and reinitialize it.
283 $(font_subsets).select2().empty()
284 $(font_subsets).select2({
285 data: new_subsets
286 }).on('change', function (e) {
287 let wrapper = $(e.target).closest(wrapperSelector)
288
289 // Serialize subfield values and refresh the fonts in the preview window.
290 selfUpdateValue(wrapper)
291 })
292 }
293
294 function getValue (wrapper) {
295 let value_holder = wrapper.children(valueHolderSelector)
296
297 if (value_holder.length) {
298 return maybeJsonParse(value_holder.val())
299 }
300
301 return []
302 }
303
304 function updateValue (wrapper, value) {
305 let value_holder = wrapper.children(valueHolderSelector),
306 setting_id = $(value_holder).data('customize-setting-link'),
307 setting = wp.customize(setting_id)
308
309 if (!value_holder.length) {
310 return
311 }
312
313 if (_.isArrayLikeObject(value)) {
314 value = encodeValues(value)
315 }
316
317 // Set the serialized value in the hidden field.
318 value_holder.val(value)
319 // Update also the Customizer setting value.
320 setting.set(value)
321 }
322
323 /**
324 * This function is a custom value serializer for our entire font field
325 * It collects values and saves them (encoded) into the `.customify_font_values` input's value
326 */
327 function selfUpdateValue (wrapper) {
328 let options_list = $(wrapper).find('.font-options__options-list'),
329 inputs = options_list.find('[data-field]'),
330 value_holder = wrapper.children(valueHolderSelector),
331 setting_id = $(value_holder).data('customize-setting-link'),
332 setting = wp.customize(setting_id),
333 newFontData = {}
334
335 // If we are already self-updating this and we haven't finished, we need to stop here to prevent infinite loops
336 // This call might have come from a subfield detecting the change the triggering a further update_font_value()
337 if (true === updatingValue[setting_id]) {
338 return
339 }
340
341 // If we are loading this setting value and haven't finished, there is no point in updating it as this would cause infinite loops.
342 if (true === loadingValue[setting_id]) {
343 return
344 }
345
346 // Mark the fact that we are self-updating the field value
347 updatingValue[setting_id] = true
348
349 inputs.each(function (key, el) {
350 let field = $(el).data('field'),
351 value = $(el).val()
352
353 if ('font_family' === field) {
354 // the font family also holds the type
355 let selected_opt = $(el.options[el.selectedIndex]),
356 type = selected_opt.data('type'),
357 subsets = selected_opt.data('subsets'),
358 variants = selected_opt.data('variants')
359
360 if (!_.isUndefined(type)) {
361 newFontData['type'] = type
362 if (type === 'theme_font') {
363 newFontData['src'] = selected_opt.data('src')
364 }
365 }
366
367 if (!_.isUndefined(variants)) {
368 newFontData['variants'] = maybeJsonParse(variants)
369 }
370
371 if (!_.isUndefined(subsets)) {
372 newFontData['subsets'] = maybeJsonParse(subsets)
373 }
374 }
375
376 if (!_.isUndefined(field) && !_.isUndefined(value) && !_.isNull(value) && value !== '') {
377 newFontData[field] = value
378 }
379 })
380
381 // Serialize the newly gathered font data
382 let serializedNewFontData = encodeValues(newFontData)
383 // Set the serialized value in the hidden field.
384 value_holder.val(serializedNewFontData)
385 // Update also the Customizer setting value.
386 setting.set(serializedNewFontData)
387
388 // Finished with the field value self-updating.
389 updatingValue[setting_id] = false
390
391 return newFontData
392 }
393
394 /**
395 * This function is a reverse of update_font_value(), initializing the entire font field controls based on the value stored in the hidden input.
396 */
397 function loadFontValue (wrapper) {
398 let options_list = $(wrapper).find('.font-options__options-list'),
399 inputs = options_list.find('[data-field]'),
400 value_holder = wrapper.children(valueHolderSelector),
401 value = maybeJsonParse(value_holder.val()),
402 setting_id = $(value_holder).data('customize-setting-link')
403
404 // If we are already loading this setting value and haven't finished, there is no point in starting again.
405 if (true === loadingValue[setting_id]) {
406 return
407 }
408
409 // Mark the fact that we are loading the field value
410 loadingValue[setting_id] = true
411
412 inputs.each(function (key, el) {
413 let field = $(el).data('field')
414
415 // In the case of select2, only the original selects have the data field, thus excluding select2 created select DOM elements
416 if (typeof field !== 'undefined' && field !== '' && typeof value[field] !== 'undefined') {
417 // If the value contains also the unit (it is not a number) we need to split it and change the subfield accordingly.
418 let cleanValue = value[field],
419 unit = ''
420 // We will do this only for numerical fields.
421 if (_.contains(['letter_spacing', 'line_height', 'font_size'], field) && isNaN(cleanValue)) {
422 // If we have a standardized value field (as array), use that.
423 if (typeof cleanValue.value !== 'undefined') {
424 if (typeof cleanValue.unit !== 'undefined') {
425 unit = cleanValue.unit
426 }
427
428 cleanValue = cleanValue.value
429 } else {
430 // Treat the case when the value is a string.
431 let matches = cleanValue.match(/^([\d.\-+]+)(.+)/i)
432 if (matches !== null && typeof matches[1] !== 'undefined') {
433 cleanValue = matches[1]
434 unit = matches[2]
435 }
436 }
437 }
438
439 if (unit !== '') {
440 $(el).attr('unit', unit)
441 }
442
443 // If this field has a min/max attribute we need to make sure that those attributes allow for the value we are trying to impose.
444 // But only for numerical values.
445 if (!isNaN(cleanValue)) {
446 if ($(el).attr('min') && $(el).attr('min') > cleanValue) {
447 $(el).attr('min', cleanValue)
448 }
449 if ($(el).attr('max') && $(el).attr('max') < cleanValue) {
450 $(el).attr('max', cleanValue)
451 }
452 }
453
454 $(el).val(cleanValue).trigger('change')
455 }
456 })
457
458 // Finished with the field value loading.
459 loadingValue[setting_id] = false
460 }
461
462 const maybeJsonParse = function (value) {
463 let parsed
464
465 //try and parse it, with decodeURIComponent
466 try {
467 parsed = JSON.parse(decodeURIComponent(value))
468 } catch (e) {
469
470 // in case of an error, treat is as a string
471 parsed = value
472 }
473
474 return parsed
475 }
476
477 const encodeValues = function (obj) {
478 return encodeURIComponent(JSON.stringify(obj))
479 }
480
481 const renderFonts = function () {
482 $( '.customify_font_family' ).select2({
483 theme: 'classic',
484 minimumResultsForSearch: 10,
485 }).trigger( 'change' );
486 }
487
488 return {
489 renderFonts: renderFonts,
490 init: init,
491 getValue: getValue,
492 updateValue: updateValue,
493 selfUpdateValue: selfUpdateValue,
494 encodeValues: encodeValues,
495 }
496 })(jQuery, window, wp)
497