Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Customizer context #399

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
25f95bf
Enable autocomplete when focusing an autocomplete-ready field
dlh01 Oct 17, 2015
370d191
Allow init_sortable() and init_label_macros() to be triggered externally
dlh01 Nov 15, 2015
490f7a8
Add the basics of a Customizer context
dlh01 Nov 15, 2015
c5fc06e
Include jQuery Datepicker as Datepicker dependency
dlh01 Nov 22, 2015
be02e65
Replace per-field JS handlers with Customizer event
dlh01 Nov 22, 2015
be00ddf
Fix Datepicker initialization in the Customizer
dlh01 Nov 22, 2015
a79fe3e
Initialize display-if triggers in the Customizer
dlh01 Nov 22, 2015
c1c66d8
Remove unnecessary variable
dlh01 Nov 22, 2015
8f39b3f
Fix unnecessarily debounced autocomplete events
dlh01 Nov 22, 2015
980e2b5
Remove extra space in string
dlh01 Dec 6, 2015
0795c83
Alert users to failed validation during saving
dlh01 Dec 6, 2015
e017a20
Bind FM event to Sections as they're added
dlh01 Dec 7, 2015
bd39fe8
Offer suggestion after saving in Customizer fails
dlh01 Dec 7, 2015
fbd2dbd
Merge branch 'master' into customizer-context
dlh01 Dec 7, 2015
077968b
Merge branch 'master' into customizer-context
dlh01 Jan 2, 2016
d6330b4
Don't reserialize all controls on changes in value
dlh01 Jan 2, 2016
466ce6e
Fix some saving callbacks after d6330b4
dlh01 Jan 26, 2016
01893e7
Serialize FM Control values into JS objects
dlh01 Jan 26, 2016
2080bc1
Merge branch 'master' into customizer-context
dlh01 Jan 26, 2016
96a9041
stripslashes_deep(), use context API when saving
dlh01 Jan 30, 2016
1155bf5
Pass context, not field, to Customize control
dlh01 Jan 30, 2016
176460c
Ensure Customize settings accept null default value
dlh01 Jan 30, 2016
43b320a
Trigger an event after an FM RTE loads TinyMCE
dlh01 Jan 30, 2016
3c13c56
Check for getUserSetting() before calling it
dlh01 Jan 30, 2016
8066242
Add support for RichTextAreas in the Customizer
dlh01 Jan 30, 2016
591ae7b
Don't reserialize each control on 'ready'
dlh01 Jan 30, 2016
1d6148c
Expose setting functions in the global fm object
dlh01 Jan 31, 2016
5d51f6e
Include fm-customize in script tests
dlh01 Feb 4, 2016
7c991b8
Add support for Colorpickers in the Customizer
dlh01 Feb 7, 2016
9e98884
Introduce Fieldmanager_Customize_Setting
dlh01 Feb 11, 2016
9b2a851
Document more of Fieldmanager_Context_Customizer tests
dlh01 Feb 11, 2016
ac92831
Expose the target selector in fm.customize
dlh01 Feb 19, 2016
738d1ed
Fix reference to 'this' in setEachControl()
dlh01 Feb 19, 2016
fa1c350
Check for serializeJSON() on found element itself
dlh01 Feb 19, 2016
4005cf3
Return the control, not nothing, after setControl()
dlh01 Feb 19, 2016
affc2ce
Update to QUnit 1.21.0
dlh01 Feb 19, 2016
c9cb7e5
Add Customizer JS unit tests, save one event
dlh01 Feb 19, 2016
d71c9aa
Fix default RichTextArea 'teeny' setting
dlh01 Feb 19, 2016
75e5544
Update WordPress versions QUnit tests againsts
dlh01 Feb 19, 2016
d4e0093
Provide 'settings' in test control params
dlh01 Feb 19, 2016
5484a07
Merge branch 'master' into customizer-context
dlh01 Feb 20, 2016
8bb2f5d
Add explanatory comment around creating field Setting
dlh01 Feb 20, 2016
24c04e4
Merge branch 'master' into customizer-context
dlh01 Feb 20, 2016
31c7740
Merge branch 'master' into customizer-context
dlh01 Feb 20, 2016
0b13e64
Remove autocomplete shim
dlh01 Feb 20, 2016
cdf92e0
Merge branch 'master' into customizer-context
dlh01 Feb 28, 2016
d72e3dc
Update test control constructors after r36689
dlh01 Mar 3, 2016
7065196
Update autocomplete event test after 0b13e64
dlh01 Mar 3, 2016
c24c240
Check control has setting before using it
dlh01 Mar 5, 2016
45bff86
Remove debouncing from changes after 'keyup'
dlh01 Mar 5, 2016
25c8fb0
Remove the other half of RichTextArea debouncing
dlh01 Mar 5, 2016
82aaf30
Merge redundant keyup events
dlh01 Mar 5, 2016
163d44b
Hook into 'customize_register' at priority 100
dlh01 Mar 18, 2016
2396297
Don't require each Customizer context to create a section
dlh01 Mar 18, 2016
2571c9d
Revert "Update test control constructors after r36689"
dlh01 Mar 18, 2016
85f90e7
Correctly instantiate controls in JS tests
dlh01 Mar 18, 2016
af2ed0f
Remove extra line
dlh01 Mar 18, 2016
42b1b5a
Merge branch 'master' into customizer-context
dlh01 May 15, 2016
9d94adb
Render Fieldmanager_Customize_Control::label and ::description
dlh01 May 15, 2016
85f1efb
Support core's Customizer setting validation
dlh01 Jun 3, 2016
c8b87f7
Clean up comments
dlh01 Jun 3, 2016
e27ddda
Revert "Support core's Customizer setting validation"
dlh01 Jul 2, 2016
a855da6
Merge branch 'master' into customizer-context
dlh01 Aug 19, 2016
c6ec5b3
Restore Customizer calculated context
dlh01 Aug 19, 2016
7cacfaa
Restore 'customize_controls_enqueue_scripts' hook
dlh01 Aug 19, 2016
59ad758
Update `init_display_if()` on control section expansion
dlh01 Aug 19, 2016
1ed2d45
Test JS against 4.6 and 4.5
dlh01 Aug 19, 2016
2637ef2
Remove outdated script-loading preparation
dlh01 Aug 19, 2016
e44f903
Improve 'X' placement after FM 1.0 CSS refresh
dlh01 Aug 19, 2016
fb490c0
Revert custom `alert()` on validation failure
dlh01 Sep 10, 2016
a34aee2
Add default method for a Setting's $validate_callback
dlh01 Sep 10, 2016
669eec6
Rearrange validate_callback() tests to match class
dlh01 Sep 10, 2016
f0be9ff
Use context's validation callback with FM settings
dlh01 Sep 18, 2016
93b5061
Parse query-string values before validating
dlh01 Sep 18, 2016
de1ab07
Reject invalid values from sanitize callback
dlh01 Sep 18, 2016
451935b
Test catching invalid values while sanitizing
dlh01 Sep 18, 2016
9a94d30
Test validating value submitted as a query string
dlh01 Sep 18, 2016
804f8e1
Handle calls to `wp_die()` on validation error
dlh01 Sep 25, 2016
38e6884
Remove outdated test
dlh01 Sep 25, 2016
f154b9f
Update `Fieldmanager_RichTextArea::customize_controls_print_footer_sc…
dlh01 Sep 25, 2016
dfcefd7
Merge branch 'master' into customizer-context
dlh01 Sep 25, 2016
1de94b3
Fix docs standards violations
dlh01 Sep 25, 2016
afca523
Handle all exceptions in `validate_callback()`
dlh01 Sep 25, 2016
32c61b6
Standardize on 'Customize' for objects
dlh01 Oct 10, 2016
11037a4
Fix `Fieldmanager_Datepicker` styles
dlh01 Oct 12, 2016
0053c3e
Fix inconsistent check for Customize context
dlh01 Oct 14, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion css/fieldmanager.css
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,20 @@ a.fm-delete:hover {
.form-field .fm-option label,
.form-field .fm-checkbox label {
display: inline;
}
}

.wp-customizer .ui-autocomplete,
.wp-customizer .ui-datepicker {
/* Hoist jQuery UI popups over who-knows-what in the Customizer. */
z-index: 500000 !important;
}

.wp-customizer .fmjs-removable .fmjs-drag-icon {
/* Nudge for the Customizer. */
margin-top: 7px;
}

.wp-customizer .fmjs-removable .fmjs-drag-icon + .fmjs-removable-element {
/* Nudge for the Customizer. */
max-width: 90%;
}
20 changes: 20 additions & 0 deletions fieldmanager.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ function fieldmanager_load_class( $class ) {
}
return fieldmanager_load_file( 'datasource/class-fieldmanager-datasource-' . $class_id . '.php' );
}

if ( 'Fieldmanager_Customize_Control' === $class ) {
return fieldmanager_load_file( 'class-fieldmanager-customize-control.php' );
}

return fieldmanager_load_file( 'class-fieldmanager-' . $class_id . '.php', $class );
}

Expand Down Expand Up @@ -181,6 +186,12 @@ function fm_add_script( $handle, $path, $deps = array(), $ver = false, $in_foote

add_action( 'admin_enqueue_scripts', $add_script );
add_action( 'wp_enqueue_scripts', $add_script );
/*
* Use a later priority because 'customize_controls_enqueue_scripts' will be
* in the middle of firing at the default priority when
* Fieldmanager_Customize_Control::enqueue() calls this.
*/
add_action( 'customize_controls_enqueue_scripts', $add_script, 20 );
}

/**
Expand Down Expand Up @@ -278,6 +289,11 @@ function fm_get_context() {
* }
*/
function fm_calculate_context() {
// Consider the Customizer preview authoritative.
if ( is_customize_preview() ) {
return array( 'customizer', null );
}

// Safe to use at any point in the load process, and better than URL matching.
if ( is_admin() ) {
$script = substr( $_SERVER['PHP_SELF'], strrpos( $_SERVER['PHP_SELF'], '/' ) + 1 );
Expand Down Expand Up @@ -312,6 +328,10 @@ function fm_calculate_context() {
}
}

if ( 'customize.php' === $script || is_customize_preview() ) {
return array( 'customizer', null );
}

switch ( $script ) {
// Context = "post".
case 'post.php':
Expand Down
10 changes: 7 additions & 3 deletions js/fieldmanager-autocomplete.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
( function( $ ) {
(function( $ ) {

fm.autocomplete = {

Expand Down Expand Up @@ -34,7 +34,7 @@ fm.autocomplete = {
custom_data = custom_result;
}
}

$.post( ajaxurl, {
action: $el.data( 'action' ),
fm_context: $el.data( 'context' ),
Expand Down Expand Up @@ -77,5 +77,9 @@ fm.autocomplete = {

$( document ).ready( fm.autocomplete.enable_autocomplete );
$( document ).on( 'fm_collapsible_toggle fm_added_element fm_displayif_toggle fm_activate_tab', fm.autocomplete.enable_autocomplete );
$( document ).on( 'focus',
'input[class*="fm-autocomplete"]:not(.fm-autocomplete-enabled)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the prior to implementation approaches in addition to this approach?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. But the existing JS predates my involvement with FM, so I'd appreciate a review from you or someone else to confirm.

fm.autocomplete.enable_autocomplete
);

} ) ( jQuery );
})( jQuery );
181 changes: 181 additions & 0 deletions js/fieldmanager-customize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* global document, jQuery, wp, _ */
/**
* Integrate Fieldmanager and the Customizer.
*
* @param {function} $ jQuery
* @param {function} api wp.customize API.
* @param {function} _ Underscore
*/
(function( $, api, _ ) {
'use strict';

/**
* Fires when an .fm-element input triggers a 'change' event.
*
* @param {Event} e Event object.
*/
var onFmElementChange = function( e ) {
reserializeEachControl();
};

/**
* Fires when an .fm-element input triggers a 'keyup' event.
*
* @param {Event} e Event object.
*/
var onFmElementKeyup = function( e ) {
var $target = $( e.target );

// Ignore [Escape] and [Enter].
if ( 27 === e.keyCode || 13 === e.keyCode ) {
return;
}

if ( $target.hasClass( 'fm-autocomplete' ) ) {
// Update an autocomplete setting object when the input's text is deleted.
if ( '' === $target.val() ) {
// See fm.autocomplete.enable_autocomplete() for this tree.
// @todo Risky? Autocomplete hidden fields don't typically get set to value="".
$target.siblings( 'input[type=hidden]' ).first().val( '' );

/*
* Don't update when typing into the autocomplete input. The hidden
* field actually contains the value and is handled onFmElementChange().
*/
} else {
return;
}
}

reserializeEachControl();
};

/**
* Fires when a Fieldmanager object is dropped while sorting.
*
* @param {Event} e Event object.
* @param {Element} el The sorted element.
*/
var onFmSortableDrop = function( e, el ) {
reserializeEachControl();
};

/**
* Fires when Fieldmanager adds a new element in a repeatable field.
*
* @param {Event} e Event object.
*/
var onFmAddedElement = function( e ) {
reserializeEachControl();
};

/**
* Fires when an item is selected and previewed in a Fieldmanager media field.
*
* @param {Event} e Event object.
* @param {jQuery} $wrapper .media-wrapper jQuery object.
* @param {object} attachment Attachment attributes.
* @param {object} wp Global WordPress JS API.
*/
var onFieldmanagerMediaPreview = function( e, $wrapper, attachment, wp ) {
reserializeEachControl();
};

/**
* Fires after clicking the "Remove" link of a Fieldmanager media field.
*
* @param {Event} e Event object.
*/
var onFmMediaRemoveClick = function( e ) {
reserializeEachControl();
};

/**
* Fires after clicking the "Remove" link of a Fieldmanager repeatable field.
*
* @param {Event} e Event object.
*/
var onFmjsRemoveClick = function( e ) {
reserializeEachControl();
};

/**
* Set the values of all Fieldmanager controls.
*/
var reserializeEachControl = function() {
api.control.each( reserializeControl );
};

/**
* Set a Fieldmanager control to its form values.
*
* @param {Object} control Customizer Control object.
*/
var reserializeControl = function( control ) {
if ( 'fieldmanager' !== control.params.type ) {
return;
}

control.setting.set( control.container.find( '.fm-element' ).serialize() );
};

/**
* Trigger an event when a Customizer section with a Fieldmanager control expands.
*/
var bindToSectionsWithFm = function() {
// @todo Also do this on section add
api.section.each(function( section ) {
$.each( section.controls(), function( i, control ) {
if ( 'fieldmanager' !== control.params.type ) {
return;
}

section.container.bind( 'expanded', function() {
$( document ).trigger( 'fm_customizer_control_section_expanded' );
});

return false;
});
});
};

/**
* Fires when the Customizer is loaded.
*/
var ready = function() {
var $document = $( document );

/*
* We debounce() most keyup events to avoid refreshing the Customizer
* preview every single time the user types a letter. But typing into
* the autocomplete input does not itself trigger a refresh -- the only
* time it should affect the preview is when removing an autocomplete
* selection. We allow that to occur normally.
*/
$document.on( 'keyup', '.fm-element:not(.fm-autocomplete)', _.debounce( onFmElementKeyup, 500 ) );
$document.on( 'keyup', '.fm-autocomplete', onFmElementKeyup );

$document.on( 'change', '.fm-element', onFmElementChange );
$document.on( 'click', '.fm-media-remove', onFmMediaRemoveClick );
$document.on( 'click', '.fmjs-remove', onFmjsRemoveClick );
$document.on( 'fm_sortable_drop', onFmSortableDrop );
$document.on( 'fieldmanager_media_preview', onFieldmanagerMediaPreview );

bindToSectionsWithFm();

/*
* Hacky, because it always prompts the user to save. Unlike when we
* create individual settings, the field "value" is always going to
* change when it's reserialized. It also ensures defaults are applied
* when the Customizer loads. But if the user saved those changes, the
* defaults would be applied, as opposed to a submenu page, where there
* isn't an AYS prompt. Creating a query string on the PHP side might
* work, but that's even weirder.
*/
reserializeEachControl();
};

if ( typeof api !== 'undefined' ) {
api.bind( 'ready', ready );
}
})( jQuery, wp.customize, _ );
5 changes: 4 additions & 1 deletion js/fieldmanager-datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
}

$( document ).ready( fm.datepicker.add_datepicker );
$( document ).on( 'fm_collapsible_toggle fm_added_element fm_displayif_toggle fm_activate_tab', fm.datepicker.add_datepicker );
$( document ).on(
'fm_collapsible_toggle fm_added_element fm_displayif_toggle fm_activate_tab fm_customizer_control_section_expanded',
fm.datepicker.add_datepicker
);
} ) ( jQuery );
39 changes: 23 additions & 16 deletions js/fieldmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ var match_value = function( values, match_string ) {
return false;
}

/**
* Initializes triggers to conditionally hide or show fields.
*/
var init_display_if = function() {
$( '.display-if' ).each(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this could collide with another class in global scope. Can we narrow scope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, although because this is existing FM code just relocated, I'd be inclined to determine a better scope in a separate PR. If that sounds OK to you, I'll file an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that sounds OK to you, I'll file an issue.

Sounds fine

var src = $( this ).data( 'display-src' );
var values = $( this ).data( 'display-value' ).split( ',' );
var trigger = $( this ).siblings( '.fm-' + src + '-wrapper' ).find( '.fm-element' );
var val = trigger.val();
if ( trigger.is( ':radio' ) && trigger.filter( ':checked' ).length ) {
val = trigger.filter( ':checked' ).val();
}
trigger.addClass( 'display-trigger' );
if ( ! match_value( values, val ) ) {
$( this ).hide();
}
});
};

fm_add_another = function( $element ) {
var el_name = $element.data( 'related-element' )
, limit = $element.data( 'limit' ) - 0
Expand Down Expand Up @@ -163,21 +182,6 @@ $( document ).ready( function () {

$( '.fm-collapsed > .fm-group:not(.fmjs-proto) > .fm-group-inner' ).hide();

// Initializes triggers to conditionally hide or show fields
$( '.display-if' ).each( function() {
var src = $( this ).data( 'display-src' );
var values = $( this ).data( 'display-value' ).split( ',' );
var trigger = $( this ).siblings( '.fm-' + src + '-wrapper' ).find( '.fm-element' );
var val = trigger.val();
if ( trigger.is( ':radio' ) && trigger.filter( ':checked' ).length ) {
val = trigger.filter( ':checked' ).val();
}
trigger.addClass( 'display-trigger' );
if ( !match_value( values, val ) ) {
$( this ).hide();
}
} );

// Controls the trigger to show or hide fields
$( document ).on( 'change', '.display-trigger', function() {
var val = $( this ).val().split(',');
Expand All @@ -198,8 +202,11 @@ $( document ).ready( function () {

init_label_macros();
init_sortable();
init_display_if();

$( document ).on( 'fm_activate_tab', init_sortable );
$( document ).on( 'fm_activate_tab fm_customizer_control_section_expanded', init_sortable );
$( document ).on( 'fm_customizer_control_section_expanded', init_label_macros );
$( document ).on( 'fm_customizer_control_section_expanded', init_display_if );
} );

} )( jQuery );
47 changes: 47 additions & 0 deletions php/class-fieldmanager-customize-control.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Class file for Fieldmanager_Customize_Control.
*/

if ( class_exists( 'WP_Customize_Control' ) ) :
/**
* Render a Fieldmanager field as a Customizer control.
*/
class Fieldmanager_Customize_Control extends WP_Customize_Control {
/**
* The field object to use. Supply via `$args['fm']`.
*
* @var Fieldmanager_Field
*/
protected $fm;

/**
* The control 'type', used in scripts to identify FM controls.
*
* @var string
*/
public $type = 'fieldmanager';

/**
* Enqueue control-related scripts and styles.
*/
public function enqueue() {
fm_add_script(
'fm_customizer',
'js/fieldmanager-customize.js',
array( 'jquery', 'underscore', 'fieldmanager_script', 'customize-controls' ),
'1.0.0',
true
);
}

/**
* Render the control's content.
*
* @see Fieldmanager_Field::element_markup().
*/
public function render_content() {
echo $this->fm->element_markup( $this->value() );
}
}
endif;
Loading