mirror of
https://ghproxy.net/https://github.com/AlxMedia/magaziner.git
synced 2025-08-28 09:43:30 +08:00
Update to Kirki 4.0.22
This commit is contained in:
parent
2b6ac38550
commit
78edeb1b25
492 changed files with 29668 additions and 39884 deletions
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 kirki-framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,98 @@
|
|||
# control-repeater
|
||||
|
||||
## Installation
|
||||
|
||||
First, install the package using composer:
|
||||
|
||||
```bash
|
||||
composer require kirki-framework/control-repeater
|
||||
```
|
||||
|
||||
Make sure you include the autoloader:
|
||||
```php
|
||||
require_once get_parent_theme_file_path( 'vendor/autoload.php' );
|
||||
```
|
||||
|
||||
To add a control using the customizer API:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Registers the control and whitelists it for JS templating.
|
||||
*
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $wp_customize The WP_Customize_Manager object.
|
||||
* @return void
|
||||
*/
|
||||
add_action( 'customize_register', function( $wp_customize ) {
|
||||
$wp_customize->register_control_type( '\Kirki\Control\Repeater' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Add Customizer settings & controls.
|
||||
*
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $wp_customize The WP_Customize_Manager object.
|
||||
* @return void
|
||||
*/
|
||||
add_action( 'customize_register', function( $wp_customize ) {
|
||||
|
||||
// Add settings.
|
||||
$wp_customize->add_setting( new \Kirki\Settings\Repeater( $wp_customize, 'my_repeater_setting', [
|
||||
'default' => [
|
||||
[
|
||||
'link_text' => esc_html__( 'Kirki Site', 'theme_textdomain' ),
|
||||
'link_url' => 'https://aristath.github.io/kirki/',
|
||||
'link_target' => '_self',
|
||||
],
|
||||
[
|
||||
'link_text' => esc_html__( 'Kirki Repository', 'theme_textdomain' ),
|
||||
'link_url' => 'https://github.com/aristath/kirki',
|
||||
'link_target' => '_self',
|
||||
],
|
||||
],
|
||||
'type' => 'theme_mod',
|
||||
'capability' => 'edit_theme_options',
|
||||
'transport' => 'refresh',
|
||||
'sanitize_callback' => function( $value ) { // Custom sanitization callback.
|
||||
$value = ( is_array( $value ) ) ? $value : json_decode( urldecode( $value ), true );
|
||||
$value = ( empty( $value ) || ! is_array( $value ) ) ? [] : $value;
|
||||
|
||||
foreach ( $value as $row_index => $row_data ) {
|
||||
$value[ $row_index ]['link_text'] = isset( $row_data['link_text'] ) ? sanitize_text_field( $row_data['link_text'] ) : '';
|
||||
$value[ $row_index ]['link_url'] = isset( $row_data['link_url'] ) ? esc_url( $row_data['link_url'] ) : '';
|
||||
$value[ $row_index ]['link_target'] = isset( $row_data['link_target'] ) && in_array( $row_data['link_target'], [ '_self', '_blank', '_parent', '_top' ], true ) ? $row_data['link_target'] : '_self';
|
||||
}
|
||||
},
|
||||
] ) );
|
||||
|
||||
// Add controls.
|
||||
$wp_customize->add_control( new \Kirki\Control\Repeater( $wp_customize, 'my_repeater_setting', [
|
||||
'label' => esc_html__( 'My Control', 'theme_textdomain' ),
|
||||
'section' => 'colors',
|
||||
'fields' => [
|
||||
'link_text' => [
|
||||
'type' => 'text',
|
||||
'label' => esc_html__( 'Link Text', 'theme_textdomain' ),
|
||||
'description' => esc_html__( 'This will be the label for your link', 'theme_textdomain' ),
|
||||
'default' => '',
|
||||
],
|
||||
'link_url' => [
|
||||
'type' => 'text',
|
||||
'label' => esc_html__( 'Link URL', 'theme_textdomain' ),
|
||||
'description' => esc_html__( 'This will be the link URL', 'theme_textdomain' ),
|
||||
'default' => '',
|
||||
],
|
||||
'link_target' => [
|
||||
'type' => 'radio',
|
||||
'label' => esc_html__( 'Link Target', 'theme_textdomain' ),
|
||||
'description' => esc_html__( 'This will be the link target', 'theme_textdomain' ),
|
||||
'default' => '_self',
|
||||
'choices' => [
|
||||
'_blank' => esc_html__( 'New Window', 'theme_textdomain' ),
|
||||
'_self' => esc_html__( 'Same Frame', 'theme_textdomain' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
] ) );
|
||||
} );
|
||||
```
|
2
functions/kirki/packages/kirki-framework/control-repeater/dist/control.css
vendored
Normal file
2
functions/kirki/packages/kirki-framework/control-repeater/dist/control.css
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.customize-control-repeater{position:relative}.customize-control-repeater .repeater-fields .repeater-row{background:#eee;border:1px solid #e5e5e5;margin-top:.5rem;position:relative}.customize-control-repeater .repeater-fields .repeater-row.minimized{border:1px solid #dfdfdf;padding:0}.customize-control-repeater .repeater-fields .repeater-row.minimized:hover{border:1px solid #e5e5e5}.customize-control-repeater .repeater-fields .repeater-row.minimized .repeater-row-content{display:none}.customize-control-repeater .repeater-fields .repeater-row label{clear:both;margin-bottom:12px}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-,.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input{display:none}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input img{border:1px solid transparent}.customize-control-repeater .repeater-fields .repeater-row .repeater-field.repeater-field-radio-image input:checked+label img{border:1px solid #3498db;-webkit-box-shadow:0 0 5px 2px rgba(0,0,0,.25);box-shadow:0 0 5px 2px rgba(0,0,0,.25)}.customize-control-repeater .repeater-fields .repeater-row .repeater-field:last-child{border-bottom:none;padding-bottom:0}.customize-control-repeater button.repeater-add{margin-top:1rem}.customize-control-repeater .repeater-row-content{background:#fff;padding:10px 15px}.customize-control-repeater .repeater-field{border-bottom:1px dotted #ccc;clear:both;margin-bottom:12px;padding-bottom:12px;width:100%}.customize-control-repeater .repeater-field .customize-control-description,.customize-control-repeater .repeater-field .customize-control-title{font-size:13px;line-height:normal}.customize-control-repeater .repeater-field.repeater-field-hidden{border:0;margin:0;padding:0}.customize-control-repeater .repeater-field-select select{margin-left:0}.customize-control-repeater .repeater-field-checkbox label{line-height:28px}.customize-control-repeater .repeater-field-checkbox input{line-height:28px;margin-right:5px}.customize-control-repeater .repeater-field-textarea textarea{resize:vertical;width:100%}.customize-control-repeater .repeater-row-header{word-wrap:break-word;background:#fff;border-bottom:1px solid #dfdfdf;height:auto;line-height:30px;min-height:20px;overflow:hidden;padding:10px 15px;position:relative}.customize-control-repeater .repeater-row-header:hover{cursor:move}.customize-control-repeater .repeater-row-header .dashicons{color:#a0a5aa;font-size:18px;position:absolute;right:12px;top:2px}.customize-control-repeater .repeater-row-label{display:block;font-size:13px;font-weight:600;height:18px;line-height:20px;overflow:hidden;width:90%}.customize-control-repeater .repeater-row-remove{color:#a00}.customize-control-repeater .repeater-row-remove:hover{color:red}.customize-control-repeater .repeater-minimize{line-height:36px}.customize-control-repeater .remove-button,.customize-control-repeater .upload-button{width:48%}.kirki-image-attachment{margin:0 0 10px;text-align:center}.kirki-image-attachment img{display:inline-block}.kirki-file-attachment{margin:0 0 10px;text-align:center}.kirki-file-attachment .file{background:#f9f9f9;border:1px dotted #c3c3c3;display:block;padding:10px 5px}.limit{border-radius:3px;padding:3px}.limit.highlight{background:#d32f2f;color:#fff}
|
||||
/*# sourceMappingURL=control.css.map */
|
2
functions/kirki/packages/kirki-framework/control-repeater/dist/control.js
vendored
Normal file
2
functions/kirki/packages/kirki-framework/control-repeater/dist/control.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,534 @@
|
|||
<?php
|
||||
/**
|
||||
* Customizer Control: repeater.
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Control;
|
||||
|
||||
use Kirki\Control\Base;
|
||||
use Kirki\URL;
|
||||
/**
|
||||
* Repeater control
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
class Repeater extends Base {
|
||||
|
||||
/**
|
||||
* The control type.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'repeater';
|
||||
|
||||
/**
|
||||
* The fields that each container row will contain.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
public $fields = [];
|
||||
|
||||
/**
|
||||
* Will store a filtered version of value for advenced fields (like images).
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
protected $filtered_value = [];
|
||||
|
||||
/**
|
||||
* The row label
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
public $row_label = [];
|
||||
|
||||
/**
|
||||
* The button label
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $button_label = '';
|
||||
|
||||
/**
|
||||
* The version. Used in scripts & styles for cache-busting.
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public static $control_ver = '1.0';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Supplied `$args` override class property defaults.
|
||||
* If `$args['settings']` is not defined, use the $id as the setting ID.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $manager Customizer bootstrap instance.
|
||||
* @param string $id Control ID.
|
||||
* @param array $args {@see WP_Customize_Control::__construct}.
|
||||
*/
|
||||
public function __construct( $manager, $id, $args = [] ) {
|
||||
|
||||
parent::__construct( $manager, $id, $args );
|
||||
|
||||
// Set up defaults for row labels.
|
||||
$this->row_label = [
|
||||
'type' => 'text',
|
||||
'value' => esc_attr__( 'row', 'kirki' ),
|
||||
'field' => false,
|
||||
];
|
||||
|
||||
// Validate row-labels.
|
||||
$this->row_label( $args );
|
||||
|
||||
if ( empty( $this->button_label ) ) {
|
||||
/* translators: %s represents the label of the row. */
|
||||
$this->button_label = sprintf( esc_html__( 'Add new %s', 'kirki' ), $this->row_label['value'] );
|
||||
}
|
||||
|
||||
if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
|
||||
$args['fields'] = [];
|
||||
}
|
||||
|
||||
// An array to store keys of fields that need to be filtered.
|
||||
$media_fields_to_filter = [];
|
||||
|
||||
foreach ( $args['fields'] as $key => $value ) {
|
||||
if ( ! isset( $value['default'] ) ) {
|
||||
$args['fields'][ $key ]['default'] = '';
|
||||
}
|
||||
if ( ! isset( $value['label'] ) ) {
|
||||
$args['fields'][ $key ]['label'] = '';
|
||||
}
|
||||
$args['fields'][ $key ]['id'] = $key;
|
||||
|
||||
// We check if the filed is an uploaded media ( image , file, video, etc.. ).
|
||||
if ( isset( $value['type'] ) ) {
|
||||
switch ( $value['type'] ) {
|
||||
case 'image':
|
||||
case 'cropped_image':
|
||||
case 'upload':
|
||||
// We add it to the list of fields that need some extra filtering/processing.
|
||||
$media_fields_to_filter[ $key ] = true;
|
||||
break;
|
||||
|
||||
case 'dropdown-pages':
|
||||
// If the field is a dropdown-pages field then add it to args.
|
||||
$dropdown = wp_dropdown_pages(
|
||||
[
|
||||
'name' => '',
|
||||
'echo' => 0,
|
||||
'show_option_none' => esc_html__( 'Select a Page', 'kirki' ),
|
||||
'option_none_value' => '0',
|
||||
'selected' => '',
|
||||
]
|
||||
);
|
||||
|
||||
// Hackily add in the data link parameter.
|
||||
$dropdown = str_replace( '<select', '<select data-field="' . esc_attr( $args['fields'][ $key ]['id'] ) . '"' . $this->get_link(), $dropdown ); // phpcs:ignore Generic.Formatting.MultipleStatementAlignment
|
||||
$args['fields'][ $key ]['dropdown'] = $dropdown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fields = $args['fields'];
|
||||
|
||||
// Now we are going to filter the fields.
|
||||
// First we create a copy of the value that would be used otherwise.
|
||||
$this->filtered_value = $this->value();
|
||||
|
||||
if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
|
||||
|
||||
// We iterate over the list of fields.
|
||||
foreach ( $this->filtered_value as &$filtered_value_field ) {
|
||||
|
||||
if ( is_array( $filtered_value_field ) && ! empty( $filtered_value_field ) ) {
|
||||
|
||||
// We iterate over the list of properties for this field.
|
||||
foreach ( $filtered_value_field as $key => &$value ) {
|
||||
|
||||
// We check if this field was marked as requiring extra filtering (in this case image, cropped_images, upload).
|
||||
if ( array_key_exists( $key, $media_fields_to_filter ) ) {
|
||||
|
||||
// What follows was made this way to preserve backward compatibility.
|
||||
// The repeater control use to store the URL for images instead of the attachment ID.
|
||||
// We check if the value look like an ID (otherwise it's probably a URL so don't filter it).
|
||||
if ( is_numeric( $value ) ) {
|
||||
|
||||
// "sanitize" the value.
|
||||
$attachment_id = (int) $value;
|
||||
|
||||
// Try to get the attachment_url.
|
||||
$url = wp_get_attachment_url( $attachment_id );
|
||||
|
||||
$filename = basename( get_attached_file( $attachment_id ) );
|
||||
|
||||
// If we got a URL.
|
||||
if ( $url ) {
|
||||
|
||||
// 'id' is needed for form hidden value, URL is needed to display the image.
|
||||
$value = [
|
||||
'id' => $attachment_id,
|
||||
'url' => $url,
|
||||
'filename' => $filename,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue control related scripts/styles.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
|
||||
parent::enqueue();
|
||||
|
||||
// Enqueue the script.
|
||||
wp_enqueue_script( 'kirki-control-repeater', URL::get_from_path( dirname( dirname( __DIR__ ) ) . '/dist/control.js' ), [ 'jquery', 'customize-base', 'wp-color-picker' ], self::$control_ver, false );
|
||||
|
||||
// Enqueue the style.
|
||||
wp_enqueue_style( 'wp-color-picker' );
|
||||
wp_enqueue_style( 'kirki-control-repeater-style', URL::get_from_path( dirname( dirname( __DIR__ ) ) . '/dist/control.css' ), [], self::$control_ver );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the parameters passed to the JavaScript via JSON.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function to_json() {
|
||||
|
||||
parent::to_json();
|
||||
|
||||
$fields = $this->fields;
|
||||
|
||||
$this->json['fields'] = $fields;
|
||||
$this->json['row_label'] = $this->row_label;
|
||||
|
||||
// If filtered_value has been set and is not empty we use it instead of the actual value.
|
||||
if ( is_array( $this->filtered_value ) && ! empty( $this->filtered_value ) ) {
|
||||
$this->json['value'] = $this->filtered_value;
|
||||
}
|
||||
|
||||
$this->json['value'] = apply_filters( "kirki_controls_repeater_value_{$this->id}", $this->json['value'] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the control's content.
|
||||
* Allows the content to be overriden without having to rewrite the wrapper in $this->render().
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function render_content() {
|
||||
|
||||
?>
|
||||
<label>
|
||||
<?php if ( ! empty( $this->label ) ) : ?>
|
||||
<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $this->description ) ) : ?>
|
||||
<span class="description customize-control-description"><?php echo wp_kses_post( $this->description ); ?></span>
|
||||
<?php endif; ?>
|
||||
<input type="hidden" {{{ data.inputAttrs }}} value="" <?php echo wp_kses_post( $this->get_link() ); ?> />
|
||||
</label>
|
||||
|
||||
<ul class="repeater-fields"></ul>
|
||||
|
||||
<?php if ( isset( $this->choices['limit'] ) ) : ?>
|
||||
<?php /* translators: %s represents the number of rows we're limiting the repeater to allow. */ ?>
|
||||
<p class="limit"><?php printf( esc_html__( 'Limit: %s rows', 'kirki' ), esc_html( $this->choices['limit'] ) ); ?></p>
|
||||
<?php endif; ?>
|
||||
<button class="button-secondary repeater-add"><?php echo esc_html( $this->button_label ); ?></button>
|
||||
|
||||
<?php
|
||||
$this->repeater_js_template();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An Underscore (JS) template for this control's content (but not its container).
|
||||
* Class variables for this control class are available in the `data` JS object.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
public function repeater_js_template() {
|
||||
?>
|
||||
|
||||
<script type="text/html" class="customize-control-repeater-content">
|
||||
<# var field; var index = data.index; #>
|
||||
|
||||
<li class="repeater-row minimized" data-row="{{{ index }}}">
|
||||
|
||||
<div class="repeater-row-header">
|
||||
<span class="repeater-row-label"></span>
|
||||
<i class="dashicons dashicons-arrow-down repeater-minimize"></i>
|
||||
</div>
|
||||
<div class="repeater-row-content">
|
||||
<# _.each( data, function( field, i ) { #>
|
||||
|
||||
<div class="repeater-field repeater-field-{{{ field.type }}} repeater-field-{{ field.id }}">
|
||||
|
||||
<# if ( 'text' === field.type || 'url' === field.type || 'link' === field.type || 'email' === field.type || 'tel' === field.type || 'date' === field.type || 'number' === field.type ) { #>
|
||||
<# var fieldExtras = ''; #>
|
||||
<# if ( 'link' === field.type ) { #>
|
||||
<# field.type = 'url' #>
|
||||
<# } #>
|
||||
|
||||
<# if ( 'number' === field.type ) { #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.min ) ) { #>
|
||||
<# fieldExtras += ' min="' + field.choices.min + '"'; #>
|
||||
<# } #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.max ) ) { #>
|
||||
<# fieldExtras += ' max="' + field.choices.max + '"'; #>
|
||||
<# } #>
|
||||
<# if ( ! _.isUndefined( field.choices ) && ! _.isUndefined( field.choices.step ) ) { #>
|
||||
<# fieldExtras += ' step="' + field.choices.step + '"'; #>
|
||||
<# } #>
|
||||
<# } #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<input type="{{field.type}}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}"{{ fieldExtras }}>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'number' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<input type="{{ field.type }}" name="" value="{{{ field.default }}}" data-field="{{{ field.id }}}"{{ numberFieldExtras }}>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'hidden' === field.type ) { #>
|
||||
|
||||
<input type="hidden" data-field="{{{ field.id }}}" <# if ( field.default ) { #> value="{{{ field.default }}}" <# } #> />
|
||||
|
||||
<# } else if ( 'checkbox' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" value="{{{ field.default }}}" data-field="{{{ field.id }}}" <# if ( field.default ) { #> checked="checked" <# } #> /> {{{ field.label }}}
|
||||
<# if ( field.description ) { #>{{{ field.description }}}<# } #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'select' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<select data-field="{{{ field.id }}}"<# if ( ! _.isUndefined( field.multiple ) && false !== field.multiple ) { #> multiple="multiple" data-multiple="{{ field.multiple }}"<# } #>>
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<option value="{{{ i }}}" <# if ( -1 !== jQuery.inArray( i, field.default ) || field.default == i ) { #> selected="selected" <# } #>>{{ choice }}</option>
|
||||
<# }); #>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'dropdown-pages' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ data.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<div class="customize-control-content repeater-dropdown-pages">{{{ field.dropdown }}}</div>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'radio' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<label><input type="radio" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>> {{ choice }} <br/></label>
|
||||
<# }); #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'radio-image' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
|
||||
<# _.each( field.choices, function( choice, i ) { #>
|
||||
<input type="radio" id="{{{ field.id }}}_{{ index }}_{{{ i }}}" name="{{{ field.id }}}{{ index }}" data-field="{{{ field.id }}}" value="{{{ i }}}" <# if ( field.default == i ) { #> checked="checked" <# } #>>
|
||||
<label for="{{{ field.id }}}_{{ index }}_{{{ i }}}"><img src="{{ choice }}"></label>
|
||||
</input>
|
||||
<# }); #>
|
||||
</label>
|
||||
|
||||
<# } else if ( 'color' === field.type ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
<# var defaultValue = '';
|
||||
if ( field.default ) {
|
||||
if ( -1 === field.default.indexOf( 'rgba' ) ) {
|
||||
defaultValue = ( '#' !== field.default.substring( 0, 1 ) ) ? '#' + field.default : field.default;
|
||||
defaultValue = ' data-default-color=' + defaultValue; // Quotes added automatically.
|
||||
} else {
|
||||
defaultValue = ' data-default-color="' + defaultValue + '" data-alpha="true"';
|
||||
}
|
||||
} #>
|
||||
<input class="color-picker-hex" type="text" maxlength="7" value="{{{ field.default }}}" data-field="{{{ field.id }}}" {{ defaultValue }} />
|
||||
|
||||
<# } else if ( 'textarea' === field.type ) { #>
|
||||
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<textarea rows="5" data-field="{{{ field.id }}}">{{ field.default }}</textarea>
|
||||
|
||||
<# } else if ( field.type === 'image' || field.type === 'cropped_image' ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
|
||||
<figure class="kirki-image-attachment" data-placeholder="<?php esc_attr_e( 'No Image Selected', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<# var defaultImageURL = ( field.default.url ) ? field.default.url : field.default; #>
|
||||
<img src="{{{ defaultImageURL }}}">
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'No Image Selected', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</figure>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
<button type="button" class="button upload-button" data-label=" <?php esc_attr_e( 'Add Image', 'kirki' ); ?>" data-alt-label="<?php echo esc_attr_e( 'Change Image', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<?php esc_html_e( 'Change Image', 'kirki' ); ?>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'Add Image', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</button>
|
||||
<# if ( field.default.id ) { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
|
||||
<# } else { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
|
||||
<# } #>
|
||||
</div>
|
||||
|
||||
<# } else if ( field.type === 'upload' ) { #>
|
||||
|
||||
<label>
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
</label>
|
||||
|
||||
<figure class="kirki-file-attachment" data-placeholder="<?php esc_attr_e( 'No File Selected', 'kirki' ); ?>" >
|
||||
<# if ( field.default ) { #>
|
||||
<# var defaultFilename = ( field.default.filename ) ? field.default.filename : field.default; #>
|
||||
<span class="file"><span class="dashicons dashicons-media-default"></span> {{ defaultFilename }}</span>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'No File Selected', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</figure>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="button remove-button<# if ( ! field.default ) { #> hidden<# } #>"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
<button type="button" class="button upload-button" data-label="<?php esc_attr_e( 'Add File', 'kirki' ); ?>" data-alt-label="<?php esc_attr_e( 'Change File', 'kirki' ); ?>">
|
||||
<# if ( field.default ) { #>
|
||||
<?php esc_html_e( 'Change File', 'kirki' ); ?>
|
||||
<# } else { #>
|
||||
<?php esc_html_e( 'Add File', 'kirki' ); ?>
|
||||
<# } #>
|
||||
</button>
|
||||
<# if ( field.default.id ) { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default.id }}}" data-field="{{{ field.id }}}" >
|
||||
<# } else { #>
|
||||
<input type="hidden" class="hidden-field" value="{{{ field.default }}}" data-field="{{{ field.id }}}" >
|
||||
<# } #>
|
||||
</div>
|
||||
|
||||
<# } else if ( 'custom' === field.type ) { #>
|
||||
|
||||
<# if ( field.label ) { #><span class="customize-control-title">{{{ field.label }}}</span><# } #>
|
||||
<# if ( field.description ) { #><span class="description customize-control-description">{{{ field.description }}}</span><# } #>
|
||||
<div data-field="{{{ field.id }}}">{{{ field.default }}}</div>
|
||||
|
||||
<# } #>
|
||||
|
||||
</div>
|
||||
<# }); #>
|
||||
<button type="button" class="button-link repeater-row-remove"><?php esc_html_e( 'Remove', 'kirki' ); ?></button>
|
||||
</div>
|
||||
</li>
|
||||
</script>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate row-labels.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @param array $args {@see WP_Customize_Control::__construct}.
|
||||
* @return void
|
||||
*/
|
||||
protected function row_label( $args ) {
|
||||
|
||||
// Validating args for row labels.
|
||||
if ( isset( $args['row_label'] ) && is_array( $args['row_label'] ) && ! empty( $args['row_label'] ) ) {
|
||||
|
||||
// Validating row label type.
|
||||
if ( isset( $args['row_label']['type'] ) && ( 'text' === $args['row_label']['type'] || 'field' === $args['row_label']['type'] ) ) {
|
||||
$this->row_label['type'] = $args['row_label']['type'];
|
||||
}
|
||||
|
||||
// Validating row label type.
|
||||
if ( isset( $args['row_label']['value'] ) && ! empty( $args['row_label']['value'] ) ) {
|
||||
$this->row_label['value'] = esc_html( $args['row_label']['value'] );
|
||||
}
|
||||
|
||||
// Validating row label field.
|
||||
if ( isset( $args['row_label']['field'] ) && ! empty( $args['row_label']['field'] ) && isset( $args['fields'][ sanitize_key( $args['row_label']['field'] ) ] ) ) {
|
||||
$this->row_label['field'] = esc_html( $args['row_label']['field'] );
|
||||
} else {
|
||||
// If from field is not set correctly, making sure standard is set as the type.
|
||||
$this->row_label['type'] = 'text';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
/**
|
||||
* Override field methods
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Field;
|
||||
|
||||
use Kirki\Compatibility\Field;
|
||||
use Kirki\Field\Upload;
|
||||
|
||||
/**
|
||||
* Field overrides.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
class Repeater extends Field {
|
||||
|
||||
/**
|
||||
* The field type.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'kirki-repeater';
|
||||
|
||||
/**
|
||||
* Used only on repeaters.
|
||||
* Contains an array of the fields.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* Sets the control type.
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_type() {
|
||||
|
||||
$this->type = 'repeater';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the $transport
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_transport() {
|
||||
|
||||
// Force using refresh mode.
|
||||
// Currently the repeater control does not support postMessage.
|
||||
$this->transport = 'refresh';
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the $sanitize_callback
|
||||
*
|
||||
* @access protected
|
||||
* @since 1.0
|
||||
* @return void
|
||||
*/
|
||||
protected function set_sanitize_callback() {
|
||||
|
||||
if ( empty( $this->sanitize_callback ) ) {
|
||||
$this->sanitize_callback = [ $this, 'sanitize' ];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The sanitize method that will be used as a falback
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param string|array $value The control's value.
|
||||
*/
|
||||
public function sanitize( $value ) {
|
||||
|
||||
// is the value formatted as a string?
|
||||
if ( is_string( $value ) ) {
|
||||
$value = rawurldecode( $value );
|
||||
$value = json_decode( $value, true );
|
||||
}
|
||||
|
||||
// Nothing to sanitize if we don't have fields.
|
||||
if ( empty( $this->fields ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
foreach ( $value as $row_id => $row_value ) {
|
||||
|
||||
// Make sure the row is formatted as an array.
|
||||
if ( ! is_array( $row_value ) ) {
|
||||
$value[ $row_id ] = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start parsing sub-fields in rows.
|
||||
foreach ( $row_value as $subfield_id => $subfield_value ) {
|
||||
|
||||
// Make sure this is a valid subfield.
|
||||
// If it's not, then unset it.
|
||||
if ( ! isset( $this->fields[ $subfield_id ] ) ) {
|
||||
unset( $value[ $row_id ][ $subfield_id ] );
|
||||
}
|
||||
|
||||
// Get the subfield-type.
|
||||
if ( ! isset( $this->fields[ $subfield_id ]['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subfield = $this->fields[ $subfield_id ];
|
||||
$subfield_type = $subfield['type'];
|
||||
|
||||
// Allow using a sanitize-callback on a per-field basis.
|
||||
if ( isset( $this->fields[ $subfield_id ]['sanitize_callback'] ) ) {
|
||||
$subfield_value = call_user_func( $this->fields[ $subfield_id ]['sanitize_callback'], $subfield_value );
|
||||
} else {
|
||||
|
||||
switch ( $subfield_type ) {
|
||||
case 'image':
|
||||
case 'cropped_image':
|
||||
case 'upload':
|
||||
$save_as = isset( $subfield['choices'] ) && isset( $subfield['choices']['save_as'] ) ? $subfield['choices']['save_as'] : 'url';
|
||||
$subfield_value = Upload::sanitize( $subfield_value, $save_as );
|
||||
|
||||
break;
|
||||
case 'dropdown-pages':
|
||||
$subfield_value = (int) $subfield_value;
|
||||
break;
|
||||
case 'color':
|
||||
if ( $subfield_value ) {
|
||||
$subfield_value = \Kirki\Field\ReactColorful::sanitize( $subfield_value );
|
||||
}
|
||||
break;
|
||||
case 'text':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'url':
|
||||
case 'link':
|
||||
$subfield_value = esc_url_raw( $subfield_value );
|
||||
break;
|
||||
case 'email':
|
||||
$subfield_value = filter_var( $subfield_value, FILTER_SANITIZE_EMAIL );
|
||||
break;
|
||||
case 'tel':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'checkbox':
|
||||
$subfield_value = (bool) $subfield_value;
|
||||
break;
|
||||
case 'select':
|
||||
if ( isset( $this->fields[ $subfield_id ]['multiple'] ) ) {
|
||||
if ( true === $this->fields[ $subfield_id ]['multiple'] ) {
|
||||
$multiple = 2;
|
||||
}
|
||||
$multiple = (int) $this->fields[ $subfield_id ]['multiple'];
|
||||
if ( 1 < $multiple ) {
|
||||
$subfield_value = (array) $subfield_value;
|
||||
foreach ( $subfield_value as $sub_subfield_key => $sub_subfield_value ) {
|
||||
$subfield_value[ $sub_subfield_key ] = sanitize_text_field( $sub_subfield_value );
|
||||
}
|
||||
} else {
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'radio':
|
||||
case 'radio-image':
|
||||
$subfield_value = sanitize_text_field( $subfield_value );
|
||||
break;
|
||||
case 'textarea':
|
||||
$subfield_value = html_entity_decode( wp_kses_post( $subfield_value ) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$value[ $row_id ][ $subfield_id ] = $subfield_value;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
/**
|
||||
* Repeater Customizer Setting.
|
||||
*
|
||||
* @package kirki-framework/control-repeater
|
||||
* @copyright Copyright (c) 2019, Ari Stathopoulos (@aristath)
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
namespace Kirki\Settings;
|
||||
|
||||
/**
|
||||
* Repeater Settings.
|
||||
*/
|
||||
class Repeater extends \WP_Customize_Setting {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Any supplied $args override class property defaults.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param WP_Customize_Manager $manager The WordPress WP_Customize_Manager object.
|
||||
* @param string $id A specific ID of the setting. Can be a theme mod or option name.
|
||||
* @param array $args Setting arguments.
|
||||
*/
|
||||
public function __construct( $manager, $id, $args = [] ) {
|
||||
parent::__construct( $manager, $id, $args );
|
||||
|
||||
// Will convert the setting from JSON to array. Must be triggered very soon.
|
||||
add_filter( "customize_sanitize_{$this->id}", [ $this, 'sanitize_repeater_setting' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the value of the setting.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @return mixed The value.
|
||||
*/
|
||||
public function value() {
|
||||
return (array) parent::value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the JSON encoded setting coming from Customizer to an Array.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.0
|
||||
* @param string $value URL Encoded JSON Value.
|
||||
* @return array
|
||||
*/
|
||||
public function sanitize_repeater_setting( $value ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
$value = json_decode( urldecode( $value ) );
|
||||
}
|
||||
|
||||
if ( empty( $value ) || ! is_array( $value ) ) {
|
||||
$value = [];
|
||||
}
|
||||
|
||||
// Make sure that every row is an array, not an object.
|
||||
foreach ( $value as $key => $val ) {
|
||||
$value[ $key ] = (array) $val;
|
||||
if ( empty( $val ) ) {
|
||||
unset( $value[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Reindex array.
|
||||
if ( is_array( $value ) ) {
|
||||
$value = array_values( $value );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,824 @@
|
|||
import "./control.scss";
|
||||
|
||||
/* global kirkiControlLoader */
|
||||
/* eslint max-depth: 0 */
|
||||
/* eslint no-useless-escape: 0 */
|
||||
var RepeaterRow = function( rowIndex, container, label, control ) {
|
||||
var self = this;
|
||||
this.rowIndex = rowIndex;
|
||||
this.container = container;
|
||||
this.label = label;
|
||||
this.header = this.container.find( '.repeater-row-header' );
|
||||
|
||||
this.header.on( 'click', function() {
|
||||
self.toggleMinimize();
|
||||
} );
|
||||
|
||||
this.container.on( 'click', '.repeater-row-remove', function() {
|
||||
self.remove();
|
||||
} );
|
||||
|
||||
this.header.on( 'mousedown', function() {
|
||||
self.container.trigger( 'row:start-dragging' );
|
||||
} );
|
||||
|
||||
this.container.on( 'keyup change', 'input, select, textarea', function( e ) {
|
||||
self.container.trigger( 'row:update', [ self.rowIndex, jQuery( e.target ).data( 'field' ), e.target ] );
|
||||
} );
|
||||
|
||||
this.setRowIndex = function( rowNum ) {
|
||||
this.rowIndex = rowNum;
|
||||
this.container.attr( 'data-row', rowNum );
|
||||
this.container.data( 'row', rowNum );
|
||||
this.updateLabel();
|
||||
};
|
||||
|
||||
this.toggleMinimize = function() {
|
||||
|
||||
// Store the previous state.
|
||||
this.container.toggleClass( 'minimized' );
|
||||
this.header.find( '.dashicons' ).toggleClass( 'dashicons-arrow-up' ).toggleClass( 'dashicons-arrow-down' );
|
||||
};
|
||||
|
||||
this.remove = function() {
|
||||
this.container.slideUp( 300, function() {
|
||||
jQuery( this ).detach();
|
||||
} );
|
||||
this.container.trigger( 'row:remove', [ this.rowIndex ] );
|
||||
};
|
||||
|
||||
this.updateLabel = function() {
|
||||
var rowLabelField,
|
||||
rowLabel,
|
||||
rowLabelSelector;
|
||||
|
||||
if ( 'field' === this.label.type ) {
|
||||
rowLabelField = this.container.find( '.repeater-field [data-field="' + this.label.field + '"]' );
|
||||
if ( _.isFunction( rowLabelField.val ) ) {
|
||||
rowLabel = rowLabelField.val();
|
||||
if ( '' !== rowLabel ) {
|
||||
if ( ! _.isUndefined( control.params.fields[ this.label.field ] ) ) {
|
||||
if ( ! _.isUndefined( control.params.fields[ this.label.field ].type ) ) {
|
||||
if ( 'select' === control.params.fields[ this.label.field ].type ) {
|
||||
if ( ! _.isUndefined( control.params.fields[ this.label.field ].choices ) && ! _.isUndefined( control.params.fields[ this.label.field ].choices[ rowLabelField.val() ] ) ) {
|
||||
rowLabel = control.params.fields[ this.label.field ].choices[ rowLabelField.val() ];
|
||||
}
|
||||
} else if ( 'radio' === control.params.fields[ this.label.field ].type || 'radio-image' === control.params.fields[ this.label.field ].type ) {
|
||||
rowLabelSelector = control.selector + ' [data-row="' + this.rowIndex + '"] .repeater-field [data-field="' + this.label.field + '"]:checked';
|
||||
rowLabel = jQuery( rowLabelSelector ).val();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.header.find( '.repeater-row-label' ).text( rowLabel );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.header.find( '.repeater-row-label' ).text( this.label.value + ' ' + ( this.rowIndex + 1 ) );
|
||||
};
|
||||
this.updateLabel();
|
||||
};
|
||||
|
||||
wp.customize.controlConstructor.repeater = wp.customize.Control.extend( {
|
||||
|
||||
// When we're finished loading continue processing
|
||||
ready: function() {
|
||||
var control = this;
|
||||
|
||||
// Init the control.
|
||||
if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) {
|
||||
kirkiControlLoader( control );
|
||||
} else {
|
||||
control.initKirkiControl();
|
||||
}
|
||||
},
|
||||
|
||||
initKirkiControl: function( control ) {
|
||||
var limit, theNewRow, settingValue;
|
||||
control = control || this;
|
||||
|
||||
// The current value set in Control Class (set in Kirki_Customize_Repeater_Control::to_json() function)
|
||||
settingValue = control.params.value;
|
||||
|
||||
// The hidden field that keeps the data saved (though we never update it)
|
||||
control.settingField = control.container.find( '[data-customize-setting-link]' ).first();
|
||||
|
||||
// Set the field value for the first time, we'll fill it up later
|
||||
control.setValue( [], false );
|
||||
|
||||
// The DIV that holds all the rows
|
||||
control.repeaterFieldsContainer = control.container.find( '.repeater-fields' ).first();
|
||||
|
||||
// Set number of rows to 0
|
||||
control.currentIndex = 0;
|
||||
|
||||
// Save the rows objects
|
||||
control.rows = [];
|
||||
|
||||
// Default limit choice
|
||||
limit = false;
|
||||
if ( ! _.isUndefined( control.params.choices.limit ) ) {
|
||||
limit = ( 0 >= control.params.choices.limit ) ? false : parseInt( control.params.choices.limit, 10 );
|
||||
}
|
||||
|
||||
control.container.on( 'click', 'button.repeater-add', function( e ) {
|
||||
e.preventDefault();
|
||||
if ( ! limit || control.currentIndex < limit ) {
|
||||
theNewRow = control.addRow();
|
||||
theNewRow.toggleMinimize();
|
||||
control.initColorPicker();
|
||||
control.initSelect( theNewRow );
|
||||
} else {
|
||||
jQuery( control.selector + ' .limit' ).addClass( 'highlight' );
|
||||
}
|
||||
} );
|
||||
|
||||
control.container.on( 'click', '.repeater-row-remove', function() {
|
||||
control.currentIndex--;
|
||||
if ( ! limit || control.currentIndex < limit ) {
|
||||
jQuery( control.selector + ' .limit' ).removeClass( 'highlight' );
|
||||
}
|
||||
} );
|
||||
|
||||
control.container.on( 'click keypress', '.repeater-field-image .upload-button,.repeater-field-cropped_image .upload-button,.repeater-field-upload .upload-button', function( e ) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery( this );
|
||||
control.openFrame( e );
|
||||
} );
|
||||
|
||||
control.container.on( 'click keypress', '.repeater-field-image .remove-button,.repeater-field-cropped_image .remove-button', function( e ) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery( this );
|
||||
control.removeImage( e );
|
||||
} );
|
||||
|
||||
control.container.on( 'click keypress', '.repeater-field-upload .remove-button', function( e ) {
|
||||
e.preventDefault();
|
||||
control.$thisButton = jQuery( this );
|
||||
control.removeFile( e );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Function that loads the Mustache template
|
||||
*/
|
||||
control.repeaterTemplate = _.memoize( function() {
|
||||
var compiled,
|
||||
|
||||
/*
|
||||
* Underscore's default ERB-style templates are incompatible with PHP
|
||||
* when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax.
|
||||
*
|
||||
* @see trac ticket #22344.
|
||||
*/
|
||||
options = {
|
||||
evaluate: /<#([\s\S]+?)#>/g,
|
||||
interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
|
||||
escape: /\{\{([^\}]+?)\}\}(?!\})/g,
|
||||
variable: 'data'
|
||||
};
|
||||
|
||||
return function( data ) {
|
||||
compiled = _.template( control.container.find( '.customize-control-repeater-content' ).first().html(), null, options );
|
||||
return compiled( data );
|
||||
};
|
||||
} );
|
||||
|
||||
// When we load the control, the fields have not been filled up
|
||||
// This is the first time that we create all the rows
|
||||
if ( settingValue.length ) {
|
||||
_.each( settingValue, function( subValue ) {
|
||||
theNewRow = control.addRow( subValue );
|
||||
control.initColorPicker();
|
||||
control.initSelect( theNewRow, subValue );
|
||||
} );
|
||||
}
|
||||
|
||||
control.repeaterFieldsContainer.sortable( {
|
||||
handle: '.repeater-row-header',
|
||||
update: function() {
|
||||
control.sort();
|
||||
}
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the media modal.
|
||||
*
|
||||
* @param {Object} event - The JS event.
|
||||
* @returns {void}
|
||||
*/
|
||||
openFrame: function( event ) {
|
||||
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-cropped_image' ) ) {
|
||||
this.initCropperFrame();
|
||||
} else {
|
||||
this.initFrame();
|
||||
}
|
||||
|
||||
this.frame.open();
|
||||
},
|
||||
|
||||
initFrame: function() {
|
||||
var libMediaType = this.getMimeType();
|
||||
|
||||
this.frame = wp.media( {
|
||||
states: [
|
||||
new wp.media.controller.Library( {
|
||||
library: wp.media.query( { type: libMediaType } ),
|
||||
multiple: false,
|
||||
date: false
|
||||
} )
|
||||
]
|
||||
} );
|
||||
|
||||
// When a file is selected, run a callback.
|
||||
this.frame.on( 'select', this.onSelect, this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a media modal select frame, and store it so the instance can be reused when needed.
|
||||
* This is mostly a copy/paste of Core api.CroppedImageControl in /wp-admin/js/customize-control.js
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initCropperFrame: function() {
|
||||
|
||||
// We get the field id from which this was called
|
||||
var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' ),
|
||||
attrs = [ 'width', 'height', 'flex_width', 'flex_height' ], // A list of attributes to look for
|
||||
libMediaType = this.getMimeType();
|
||||
|
||||
// Make sure we got it
|
||||
if ( _.isString( currentFieldId ) && '' !== currentFieldId ) {
|
||||
|
||||
// Make fields is defined and only do the hack for cropped_image
|
||||
if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'cropped_image' === this.params.fields[ currentFieldId ].type ) {
|
||||
|
||||
//Iterate over the list of attributes
|
||||
attrs.forEach( function( el ) {
|
||||
|
||||
// If the attribute exists in the field
|
||||
if ( ! _.isUndefined( this.params.fields[ currentFieldId ][ el ] ) ) {
|
||||
|
||||
// Set the attribute in the main object
|
||||
this.params[ el ] = this.params.fields[ currentFieldId ][ el ];
|
||||
}
|
||||
}.bind( this ) );
|
||||
}
|
||||
}
|
||||
|
||||
this.frame = wp.media( {
|
||||
button: {
|
||||
text: 'Select and Crop',
|
||||
close: false
|
||||
},
|
||||
states: [
|
||||
new wp.media.controller.Library( {
|
||||
library: wp.media.query( { type: libMediaType } ),
|
||||
multiple: false,
|
||||
date: false,
|
||||
suggestedWidth: this.params.width,
|
||||
suggestedHeight: this.params.height
|
||||
} ),
|
||||
new wp.media.controller.CustomizeImageCropper( {
|
||||
imgSelectOptions: this.calculateImageSelectOptions,
|
||||
control: this
|
||||
} )
|
||||
]
|
||||
} );
|
||||
|
||||
this.frame.on( 'select', this.onSelectForCrop, this );
|
||||
this.frame.on( 'cropped', this.onCropped, this );
|
||||
this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
|
||||
|
||||
},
|
||||
|
||||
onSelect: function() {
|
||||
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
|
||||
|
||||
if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-upload' ) ) {
|
||||
this.setFileInRepeaterField( attachment );
|
||||
} else {
|
||||
this.setImageInRepeaterField( attachment );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* After an image is selected in the media modal, switch to the cropper
|
||||
* state if the image isn't the right size.
|
||||
*/
|
||||
|
||||
onSelectForCrop: function() {
|
||||
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
|
||||
|
||||
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
|
||||
this.setImageInRepeaterField( attachment );
|
||||
} else {
|
||||
this.frame.setState( 'cropper' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* After the image has been cropped, apply the cropped image data to the setting.
|
||||
*
|
||||
* @param {object} croppedImage Cropped attachment data.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCropped: function( croppedImage ) {
|
||||
this.setImageInRepeaterField( croppedImage );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a set of options, computed from the attached image data and
|
||||
* control-specific data, to be fed to the imgAreaSelect plugin in
|
||||
* wp.media.view.Cropper.
|
||||
*
|
||||
* @param {wp.media.model.Attachment} attachment - The attachment from the WP API.
|
||||
* @param {wp.media.controller.Cropper} controller - Media controller.
|
||||
* @returns {Object} - Options.
|
||||
*/
|
||||
calculateImageSelectOptions: function( attachment, controller ) {
|
||||
var control = controller.get( 'control' ),
|
||||
flexWidth = !! parseInt( control.params.flex_width, 10 ),
|
||||
flexHeight = !! parseInt( control.params.flex_height, 10 ),
|
||||
realWidth = attachment.get( 'width' ),
|
||||
realHeight = attachment.get( 'height' ),
|
||||
xInit = parseInt( control.params.width, 10 ),
|
||||
yInit = parseInt( control.params.height, 10 ),
|
||||
ratio = xInit / yInit,
|
||||
xImg = realWidth,
|
||||
yImg = realHeight,
|
||||
x1,
|
||||
y1,
|
||||
imgSelectOptions;
|
||||
|
||||
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
|
||||
|
||||
if ( xImg / yImg > ratio ) {
|
||||
yInit = yImg;
|
||||
xInit = yInit * ratio;
|
||||
} else {
|
||||
xInit = xImg;
|
||||
yInit = xInit / ratio;
|
||||
}
|
||||
|
||||
x1 = ( xImg - xInit ) / 2;
|
||||
y1 = ( yImg - yInit ) / 2;
|
||||
|
||||
imgSelectOptions = {
|
||||
handles: true,
|
||||
keys: true,
|
||||
instance: true,
|
||||
persistent: true,
|
||||
imageWidth: realWidth,
|
||||
imageHeight: realHeight,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
x2: xInit + x1,
|
||||
y2: yInit + y1
|
||||
};
|
||||
|
||||
if ( false === flexHeight && false === flexWidth ) {
|
||||
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
|
||||
}
|
||||
if ( false === flexHeight ) {
|
||||
imgSelectOptions.maxHeight = yInit;
|
||||
}
|
||||
if ( false === flexWidth ) {
|
||||
imgSelectOptions.maxWidth = xInit;
|
||||
}
|
||||
|
||||
return imgSelectOptions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return whether the image must be cropped, based on required dimensions.
|
||||
*
|
||||
* @param {bool} flexW - The flex-width.
|
||||
* @param {bool} flexH - The flex-height.
|
||||
* @param {int} dstW - Initial point distance in the X axis.
|
||||
* @param {int} dstH - Initial point distance in the Y axis.
|
||||
* @param {int} imgW - Width.
|
||||
* @param {int} imgH - Height.
|
||||
* @returns {bool} - Whether the image must be cropped or not based on required dimensions.
|
||||
*/
|
||||
mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
|
||||
return ! ( ( true === flexW && true === flexH ) || ( true === flexW && dstH === imgH ) || ( true === flexH && dstW === imgW ) || ( dstW === imgW && dstH === imgH ) || ( imgW <= dstW ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* If cropping was skipped, apply the image data directly to the setting.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onSkippedCrop: function() {
|
||||
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
|
||||
this.setImageInRepeaterField( attachment );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the setting and re-renders the control UI.
|
||||
*
|
||||
* @param {object} attachment - The attachment object.
|
||||
* @returns {void}
|
||||
*/
|
||||
setImageInRepeaterField: function( attachment ) {
|
||||
var $targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image' );
|
||||
|
||||
$targetDiv.find( '.kirki-image-attachment' ).html( '<img src="' + attachment.url + '">' ).hide().slideDown( 'slow' );
|
||||
|
||||
$targetDiv.find( '.hidden-field' ).val( attachment.id );
|
||||
this.$thisButton.text( this.$thisButton.data( 'alt-label' ) );
|
||||
$targetDiv.find( '.remove-button' ).show();
|
||||
|
||||
//This will activate the save button
|
||||
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
|
||||
this.frame.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the setting and re-renders the control UI.
|
||||
*
|
||||
* @param {object} attachment - The attachment object.
|
||||
* @returns {void}
|
||||
*/
|
||||
setFileInRepeaterField: function( attachment ) {
|
||||
var $targetDiv = this.$thisButton.closest( '.repeater-field-upload' );
|
||||
|
||||
$targetDiv.find( '.kirki-file-attachment' ).html( '<span class="file"><span class="dashicons dashicons-media-default"></span> ' + attachment.filename + '</span>' ).hide().slideDown( 'slow' );
|
||||
|
||||
$targetDiv.find( '.hidden-field' ).val( attachment.id );
|
||||
this.$thisButton.text( this.$thisButton.data( 'alt-label' ) );
|
||||
$targetDiv.find( '.upload-button' ).show();
|
||||
$targetDiv.find( '.remove-button' ).show();
|
||||
|
||||
//This will activate the save button
|
||||
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
|
||||
this.frame.close();
|
||||
},
|
||||
|
||||
getMimeType: function() {
|
||||
|
||||
// We get the field id from which this was called
|
||||
var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' );
|
||||
|
||||
// Make sure we got it
|
||||
if ( _.isString( currentFieldId ) && '' !== currentFieldId ) {
|
||||
|
||||
// Make fields is defined and only do the hack for cropped_image
|
||||
if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'upload' === this.params.fields[ currentFieldId ].type ) {
|
||||
|
||||
// If the attribute exists in the field
|
||||
if ( ! _.isUndefined( this.params.fields[ currentFieldId ].mime_type ) ) {
|
||||
|
||||
// Set the attribute in the main object
|
||||
return this.params.fields[ currentFieldId ].mime_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'image';
|
||||
},
|
||||
|
||||
removeImage: function( event ) {
|
||||
var $targetDiv,
|
||||
$uploadButton;
|
||||
|
||||
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image,.repeater-field-upload' );
|
||||
$uploadButton = $targetDiv.find( '.upload-button' );
|
||||
|
||||
$targetDiv.find( '.kirki-image-attachment' ).slideUp( 'fast', function() {
|
||||
jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) );
|
||||
} );
|
||||
$targetDiv.find( '.hidden-field' ).val( '' );
|
||||
$uploadButton.text( $uploadButton.data( 'label' ) );
|
||||
this.$thisButton.hide();
|
||||
|
||||
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
|
||||
},
|
||||
|
||||
removeFile: function( event ) {
|
||||
var $targetDiv,
|
||||
$uploadButton;
|
||||
|
||||
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$targetDiv = this.$thisButton.closest( '.repeater-field-upload' );
|
||||
$uploadButton = $targetDiv.find( '.upload-button' );
|
||||
|
||||
$targetDiv.find( '.kirki-file-attachment' ).slideUp( 'fast', function() {
|
||||
jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) );
|
||||
} );
|
||||
$targetDiv.find( '.hidden-field' ).val( '' );
|
||||
$uploadButton.text( $uploadButton.data( 'label' ) );
|
||||
this.$thisButton.hide();
|
||||
|
||||
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current value of the setting
|
||||
*
|
||||
* @returns {Object} - Returns the value.
|
||||
*/
|
||||
getValue: function() {
|
||||
|
||||
// The setting is saved in JSON
|
||||
return JSON.parse( decodeURI( this.setting.get() ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a new value for the setting
|
||||
*
|
||||
* @param {Object} newValue - The new value.
|
||||
* @param {bool} refresh - If we want to refresh the previewer or not
|
||||
* @param {bool} filtering - If we want to filter or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
setValue: function( newValue, refresh, filtering ) {
|
||||
|
||||
// We need to filter the values after the first load to remove data requrired for diplay but that we don't want to save in DB
|
||||
var filteredValue = newValue,
|
||||
filter = [];
|
||||
|
||||
if ( filtering ) {
|
||||
jQuery.each( this.params.fields, function( index, value ) {
|
||||
if ( 'image' === value.type || 'cropped_image' === value.type || 'upload' === value.type ) {
|
||||
filter.push( index );
|
||||
}
|
||||
} );
|
||||
jQuery.each( newValue, function( index, value ) {
|
||||
jQuery.each( filter, function( ind, field ) {
|
||||
if ( ! _.isUndefined( value[ field ] ) && ! _.isUndefined( value[ field ].id ) ) {
|
||||
filteredValue[index][ field ] = value[ field ].id;
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
this.setting.set( encodeURI( JSON.stringify( filteredValue ) ) );
|
||||
|
||||
if ( refresh ) {
|
||||
|
||||
// Trigger the change event on the hidden field so
|
||||
// previewer refresh the website on Customizer
|
||||
this.settingField.trigger( 'change' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new row to repeater settings based on the structure.
|
||||
*
|
||||
* @param {Object} data - (Optional) Object of field => value pairs (undefined if you want to get the default values)
|
||||
* @returns {Object} - Returns the new row.
|
||||
*/
|
||||
addRow: function( data ) {
|
||||
var control = this,
|
||||
template = control.repeaterTemplate(), // The template for the new row (defined on Kirki_Customize_Repeater_Control::render_content() ).
|
||||
settingValue = this.getValue(), // Get the current setting value.
|
||||
newRowSetting = {}, // Saves the new setting data.
|
||||
templateData, // Data to pass to the template
|
||||
newRow,
|
||||
i;
|
||||
|
||||
if ( template ) {
|
||||
|
||||
// The control structure is going to define the new fields
|
||||
// We need to clone control.params.fields. Assigning it
|
||||
// ould result in a reference assignment.
|
||||
templateData = jQuery.extend( true, {}, control.params.fields );
|
||||
|
||||
// But if we have passed data, we'll use the data values instead
|
||||
if ( data ) {
|
||||
for ( i in data ) {
|
||||
if ( data.hasOwnProperty( i ) && templateData.hasOwnProperty( i ) ) {
|
||||
templateData[ i ].default = data[ i ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templateData.index = this.currentIndex;
|
||||
|
||||
// Append the template content
|
||||
template = template( templateData );
|
||||
|
||||
// Create a new row object and append the element
|
||||
newRow = new RepeaterRow(
|
||||
control.currentIndex,
|
||||
jQuery( template ).appendTo( control.repeaterFieldsContainer ),
|
||||
control.params.row_label,
|
||||
control
|
||||
);
|
||||
|
||||
newRow.container.on( 'row:remove', function( e, rowIndex ) {
|
||||
control.deleteRow( rowIndex );
|
||||
} );
|
||||
|
||||
newRow.container.on( 'row:update', function( e, rowIndex, fieldName, element ) {
|
||||
control.updateField.call( control, e, rowIndex, fieldName, element ); // eslint-disable-line no-useless-call
|
||||
newRow.updateLabel();
|
||||
} );
|
||||
|
||||
// Add the row to rows collection
|
||||
this.rows[ this.currentIndex ] = newRow;
|
||||
|
||||
for ( i in templateData ) {
|
||||
if ( templateData.hasOwnProperty( i ) ) {
|
||||
newRowSetting[ i ] = templateData[ i ].default;
|
||||
}
|
||||
}
|
||||
|
||||
settingValue[ this.currentIndex ] = newRowSetting;
|
||||
this.setValue( settingValue, true );
|
||||
|
||||
this.currentIndex++;
|
||||
|
||||
return newRow;
|
||||
}
|
||||
},
|
||||
|
||||
sort: function() {
|
||||
var control = this,
|
||||
$rows = this.repeaterFieldsContainer.find( '.repeater-row' ),
|
||||
newOrder = [],
|
||||
settings = control.getValue(),
|
||||
newRows = [],
|
||||
newSettings = [];
|
||||
|
||||
$rows.each( function( i, element ) {
|
||||
newOrder.push( jQuery( element ).data( 'row' ) );
|
||||
} );
|
||||
|
||||
jQuery.each( newOrder, function( newPosition, oldPosition ) {
|
||||
newRows[ newPosition ] = control.rows[ oldPosition ];
|
||||
newRows[ newPosition ].setRowIndex( newPosition );
|
||||
|
||||
newSettings[ newPosition ] = settings[ oldPosition ];
|
||||
} );
|
||||
|
||||
control.rows = newRows;
|
||||
control.setValue( newSettings );
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a row in the repeater setting
|
||||
*
|
||||
* @param {int} index - Position of the row in the complete Setting Array
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteRow: function( index ) {
|
||||
var currentSettings = this.getValue(),
|
||||
row,
|
||||
prop;
|
||||
|
||||
if ( currentSettings[ index ] ) {
|
||||
|
||||
// Find the row
|
||||
row = this.rows[ index ];
|
||||
if ( row ) {
|
||||
|
||||
// Remove the row settings
|
||||
delete currentSettings[ index ];
|
||||
|
||||
// Remove the row from the rows collection
|
||||
delete this.rows[ index ];
|
||||
|
||||
// Update the new setting values
|
||||
this.setValue( currentSettings, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Remap the row numbers
|
||||
for ( prop in this.rows ) {
|
||||
if ( this.rows.hasOwnProperty( prop ) && this.rows[ prop ] ) {
|
||||
this.rows[ prop ].updateLabel();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a single field inside a row.
|
||||
* Triggered when a field has changed
|
||||
*
|
||||
* @param {Object} e - Event Object
|
||||
* @param {int} rowIndex - The row's index as an integer.
|
||||
* @param {string} fieldId - The field ID.
|
||||
* @param {string|Object} element - The element's identifier, or jQuery Object of the element.
|
||||
* @returns {void}
|
||||
*/
|
||||
updateField: function( e, rowIndex, fieldId, element ) {
|
||||
var type,
|
||||
row,
|
||||
currentSettings;
|
||||
|
||||
if ( ! this.rows[ rowIndex ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.params.fields[ fieldId ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = this.params.fields[ fieldId].type;
|
||||
row = this.rows[ rowIndex ];
|
||||
currentSettings = this.getValue();
|
||||
|
||||
element = jQuery( element );
|
||||
|
||||
if ( _.isUndefined( currentSettings[ row.rowIndex ][ fieldId ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'checkbox' === type ) {
|
||||
currentSettings[ row.rowIndex ][ fieldId ] = element.is( ':checked' );
|
||||
} else {
|
||||
|
||||
// Update the settings
|
||||
currentSettings[ row.rowIndex ][ fieldId ] = element.val();
|
||||
}
|
||||
this.setValue( currentSettings, true );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the color picker on color fields
|
||||
* Called after AddRow
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initColorPicker: function() {
|
||||
var control = this,
|
||||
colorPicker = control.container.find( '.color-picker-hex' ),
|
||||
options = {},
|
||||
fieldId = colorPicker.data( 'field' );
|
||||
|
||||
// We check if the color palette parameter is defined.
|
||||
if ( ! _.isUndefined( fieldId ) && ! _.isUndefined( control.params.fields[ fieldId ] ) && ! _.isUndefined( control.params.fields[ fieldId ].palettes ) && _.isObject( control.params.fields[ fieldId ].palettes ) ) {
|
||||
options.palettes = control.params.fields[ fieldId ].palettes;
|
||||
}
|
||||
|
||||
// When the color picker value is changed we update the value of the field
|
||||
options.change = function( event, ui ) {
|
||||
|
||||
var currentPicker = jQuery( event.target ),
|
||||
row = currentPicker.closest( '.repeater-row' ),
|
||||
rowIndex = row.data( 'row' ),
|
||||
currentSettings = control.getValue();
|
||||
|
||||
currentSettings[ rowIndex ][ currentPicker.data( 'field' ) ] = ui.color.toString();
|
||||
control.setValue( currentSettings, true );
|
||||
|
||||
};
|
||||
|
||||
// Init the color picker
|
||||
if ( colorPicker.length && 0 !== colorPicker.length ) {
|
||||
colorPicker.wpColorPicker( options );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the dropdown-pages field.
|
||||
* Called after AddRow
|
||||
*
|
||||
* @param {object} theNewRow the row that was added to the repeater
|
||||
* @param {object} data the data for the row if we're initializing a pre-existing row
|
||||
* @returns {void}
|
||||
*/
|
||||
initSelect: function( theNewRow, data ) {
|
||||
var control = this,
|
||||
dropdown = theNewRow.container.find( '.repeater-field select' ),
|
||||
dataField;
|
||||
|
||||
if ( 0 === dropdown.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataField = dropdown.data( 'field' );
|
||||
multiple = jQuery( dropdown ).data( 'multiple' );
|
||||
|
||||
data = data || {};
|
||||
data[ dataField ] = data[ dataField ] || '';
|
||||
|
||||
jQuery( dropdown ).val( data[ dataField ] || jQuery( dropdown ).val() );
|
||||
|
||||
this.container.on( 'change', '.repeater-field select', function( event ) {
|
||||
|
||||
var currentDropdown = jQuery( event.target ),
|
||||
row = currentDropdown.closest( '.repeater-row' ),
|
||||
rowIndex = row.data( 'row' ),
|
||||
currentSettings = control.getValue();
|
||||
|
||||
currentSettings[ rowIndex ][ currentDropdown.data( 'field' ) ] = jQuery( this ).val();
|
||||
control.setValue( currentSettings );
|
||||
} );
|
||||
}
|
||||
} );
|
Loading…
Add table
Add a link
Reference in a new issue