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

Issue when using wpColorPicker and wp-color-picker-alpha for widget setting #22

Closed
rinkuyadav999 opened this issue Jul 21, 2018 · 4 comments

Comments

@rinkuyadav999
Copy link

When use wpColorPicker and this wp-color-picker-alpha for color setting inside widget, it saves data but do not change 'Save' button to 'Saved'. Seems there is js issue in wp-color-picker-alpha.

When remove wp-color-picker-alpha from setting, it display color picker and 'Save' button changes to 'Saved'.

@rinkuyadav999
Copy link
Author

class Color_Picker_Widget_25809 extends WP_Widget {

	/**
	 * Widget constructor.
	 *
	 * @since  1.0
	 *
	 * @access public
	 */
	public function __construct() {
		parent::__construct(
			'color-picker',
			_x( 'Color Picker', 'widget title', 'color-picker-widget' ),
			array(
				'classname'   => 'color-picker-widget',
				'description' => _x( 'Widget with a color picker', 'widget description', 'color-picker-widget' )
			)
		);

		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
		add_action( 'admin_footer-widgets.php', array( $this, 'print_scripts' ), 9999 );
	}

	/**
	 * Enqueue scripts.
	 *
	 * @since 1.0
	 *
	 * @param string $hook_suffix
	 */
	public function enqueue_scripts( $hook_suffix ) {
		if ( 'widgets.php' !== $hook_suffix ) {
			return;
		}

		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_script( 'wp-color-picker' );
		wp_enqueue_script( 'underscore' );
	}

	/**
	 * Print scripts.
	 *
	 * @since 1.0
	 */
	public function print_scripts() {
		?>
		<script>
			( function( $ ){
				function initColorPicker( widget ) {
					widget.find( '.color-picker' ).wpColorPicker( {
						change: _.throttle( function() { // For Customizer
							$(this).trigger( 'change' );
						}, 3000 )
					});
				}

				function onFormUpdate( event, widget ) {
					initColorPicker( widget );
				}

				$( document ).on( 'widget-added widget-updated', onFormUpdate );

				$( document ).ready( function() {
					$( '#widgets-right .widget:has(.color-picker)' ).each( function () {
						initColorPicker( $( this ) );
					} );
				} );
			}( jQuery ) );





/**!
 * wp-color-picker-alpha
 *
 * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker
 * Only run in input and is defined data alpha in true
 *
 * Version: 2.1.3
 * https://github.com/kallookoo/wp-color-picker-alpha
 * Licensed under the GPLv2 license.
 */
( function( $ ) {
	// Prevent double-init.
	if ( $.wp.wpColorPicker.prototype._hasAlpha ) {
		return;
	}

		// Variable for some backgrounds ( grid )
	var image   = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==',
		// html stuff for wpColorPicker copy of the original color-picker.js
		_after = '<div class="wp-picker-holder" />',
		_wrap = '<div class="wp-picker-container" />',
		_button = '<input type="button" class="button button-small" />',
		// Prevent CSS issues in < WordPress 4.9
		_deprecated = ( wpColorPickerL10n.current !== undefined );
		// Declare some global variables when is deprecated or not
		if ( _deprecated ) {
			var _before = '<a tabindex="0" class="wp-color-result" />';
		} else {
			var _before = '<button type="button" class="button wp-color-result" aria-expanded="false"><span class="wp-color-result-text"></span></button>',
				_wrappingLabel = '<label></label>',
				_wrappingLabelText = '<span class="screen-reader-text"></span>';
		}
	/**
	 * Overwrite Color
	 * for enable support rbga
	 */
	Color.fn.toString = function() {
		if ( this._alpha < 1 )
			return this.toCSS( 'rgba', this._alpha ).replace( /\s+/g, '' );

		var hex = parseInt( this._color, 10 ).toString( 16 );

		if ( this.error )
			return '';

		if ( hex.length < 6 )
			hex = ( '00000' + hex ).substr( -6 );

		return '#' + hex;
	};

	/**
	 * Overwrite wpColorPicker
	 */
	$.widget( 'wp.wpColorPicker', $.wp.wpColorPicker, {
		_hasAlpha: true,
		/**
		 * @summary Creates the color picker.
		 *
		 * Creates the color picker, sets default values, css classes and wraps it all in HTML.
		 *
		 * @since 3.5.0
		 *
		 * @access private
		 *
		 * @returns {void}
		 */
		_create: function() {
			// Return early if Iris support is missing.
			if ( ! $.support.iris ) {
				return;
			}

			var self = this,
				el = self.element;

			// Override default options with options bound to the element.
			$.extend( self.options, el.data() );

			// Create a color picker which only allows adjustments to the hue.
			if ( self.options.type === 'hue' ) {
				return self._createHueOnly();
			}

			// Bind the close event.
			self.close = $.proxy( self.close, self );

			self.initialValue = el.val();

			// Add a CSS class to the input field.
			el.addClass( 'wp-color-picker' );

			if ( _deprecated ) {
				el.hide().wrap( _wrap );
				self.wrap            = el.parent();
				self.toggler         = $( _before )
					.insertBefore( el )
					.css( { backgroundColor : self.initialValue } )
					.attr( 'title', wpColorPickerL10n.pick )
					.attr( 'data-current', wpColorPickerL10n.current );
				self.pickerContainer = $( _after ).insertAfter( el );
				self.button          = $( _button ).addClass('hidden');
			} else {
				/*
				 * Check if there's already a wrapping label, e.g. in the Customizer.
				 * If there's no label, add a default one to match the Customizer template.
				 */
				if ( ! el.parent( 'label' ).length ) {
					// Wrap the input field in the default label.
					el.wrap( _wrappingLabel );
					// Insert the default label text.
					self.wrappingLabelText = $( _wrappingLabelText )
						.insertBefore( el )
						.text( wpColorPickerL10n.defaultLabel );
				}

				/*
				 * At this point, either it's the standalone version or the Customizer
				 * one, we have a wrapping label to use as hook in the DOM, let's store it.
				 */
				self.wrappingLabel = el.parent();

				// Wrap the label in the main wrapper.
				self.wrappingLabel.wrap( _wrap );
				// Store a reference to the main wrapper.
				self.wrap = self.wrappingLabel.parent();
				// Set up the toggle button and insert it before the wrapping label.
				self.toggler = $( _before )
					.insertBefore( self.wrappingLabel )
					.css( { backgroundColor: self.initialValue } );
				// Set the toggle button span element text.
				self.toggler.find( '.wp-color-result-text' ).text( wpColorPickerL10n.pick );
				// Set up the Iris container and insert it after the wrapping label.
				self.pickerContainer = $( _after ).insertAfter( self.wrappingLabel );
				// Store a reference to the Clear/Default button.
				self.button = $( _button );
			}

			// Set up the Clear/Default button.
			if ( self.options.defaultColor ) {
				self.button.addClass( 'wp-picker-default' ).val( wpColorPickerL10n.defaultString );
				if ( ! _deprecated ) {
					self.button.attr( 'aria-label', wpColorPickerL10n.defaultAriaLabel );
				}
			} else {
				self.button.addClass( 'wp-picker-clear' ).val( wpColorPickerL10n.clear );
				if ( ! _deprecated ) {
					self.button.attr( 'aria-label', wpColorPickerL10n.clearAriaLabel );
				}
			}

			if ( _deprecated ) {
				el.wrap( '<span class="wp-picker-input-wrap" />' ).after( self.button );
			} else {
				// Wrap the wrapping label in its wrapper and append the Clear/Default button.
				self.wrappingLabel
					.wrap( '<span class="wp-picker-input-wrap hidden" />' )
					.after( self.button );

				/*
				 * The input wrapper now contains the label+input+Clear/Default button.
				 * Store a reference to the input wrapper: we'll use this to toggle
				 * the controls visibility.
				 */
				self.inputWrapper = el.closest( '.wp-picker-input-wrap' );
			}

			el.iris( {
				target: self.pickerContainer,
				hide: self.options.hide,
				width: self.options.width,
				mode: self.options.mode,
				palettes: self.options.palettes,
				/**
				 * @summary Handles the onChange event if one has been defined in the options.
				 *
				 * Handles the onChange event if one has been defined in the options and additionally
				 * sets the background color for the toggler element.
				 *
				 * @since 3.5.0
				 *
				 * @param {Event} event    The event that's being called.
				 * @param {HTMLElement} ui The HTMLElement containing the color picker.
				 *
				 * @returns {void}
				 */
				change: function( event, ui ) {
					if ( self.options.alpha ) {
						self.toggler.css( { 'background-image' : 'url(' + image + ')' } );
						if ( _deprecated ) {
							self.toggler.html( '<span class="color-alpha" />' );
						} else {
							self.toggler.css( {
								'position' : 'relative'
							} );
							if ( self.toggler.find('span.color-alpha').length == 0 ) {
								self.toggler.append('<span class="color-alpha" />');
							}
						}

						self.toggler.find( 'span.color-alpha' ).css( {
							'width'                     : '30px',
							'height'                    : '24px',
							'position'                  : 'absolute',
							'top'                       : 0,
							'left'                      : 0,
							'border-top-left-radius'    : '2px',
							'border-bottom-left-radius' : '2px',
							'background'                : ui.color.toString()
						} );
					} else {
						self.toggler.css( { backgroundColor : ui.color.toString() } );
					}

					if ( $.isFunction( self.options.change ) ) {
						self.options.change.call( this, event, ui );
					}
				}
			} );

			el.val( self.initialValue );
			self._addListeners();

			// Force the color picker to always be closed on initial load.
			if ( ! self.options.hide ) {
				self.toggler.click();
			}
		},
		/**
		 * @summary Binds event listeners to the color picker.
		 *
		 * @since 3.5.0
		 *
		 * @access private
		 *
		 * @returns {void}
		 */
		_addListeners: function() {
			var self = this;

			/**
			 * @summary Prevent any clicks inside this widget from leaking to the top and closing it.
			 *
			 * @since 3.5.0
			 *
			 * @param {Event} event The event that's being called.
			 *
			 * @returs {void}
			 */
			self.wrap.on( 'click.wpcolorpicker', function( event ) {
				event.stopPropagation();
			});

			/**
			 * @summary Open or close the color picker depending on the class.
			 *
			 * @since 3.5
			 */
			self.toggler.click( function(){
				if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
					self.close();
				} else {
					self.open();
				}
			});

			/**
			 * @summary Checks if value is empty when changing the color in the color picker.
			 *
			 * Checks if value is empty when changing the color in the color picker.
			 * If so, the background color is cleared.
			 *
			 * @since 3.5.0
			 *
			 * @param {Event} event The event that's being called.
			 *
			 * @returns {void}
			 */
			self.element.on( 'change', function( event ) {
				// Empty or Error = clear
				if ( $( this ).val() === '' || self.element.hasClass( 'iris-error' ) ) {
					if ( self.options.alpha ) {
						if ( _deprecated ) {
							self.toggler.removeAttr( 'style' );
						}
						self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
					} else {
						self.toggler.css( 'backgroundColor', '' );
					}

					// fire clear callback if we have one
					if ( $.isFunction( self.options.clear ) )
						self.options.clear.call( this, event );
				}
			} );

			/**
			 * @summary Enables the user to clear or revert the color in the color picker.
			 *
			 * Enables the user to either clear the color in the color picker or revert back to the default color.
			 *
			 * @since 3.5.0
			 *
			 * @param {Event} event The event that's being called.
			 *
			 * @returns {void}
			 */
			self.button.on( 'click', function( event ) {
				if ( $( this ).hasClass( 'wp-picker-clear' ) ) {
					self.element.val( '' );
					if ( self.options.alpha ) {
						if ( _deprecated ) {
							self.toggler.removeAttr( 'style' );
						}
						self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
					} else {
						self.toggler.css( 'backgroundColor', '' );
					}

					if ( $.isFunction( self.options.clear ) )
						self.options.clear.call( this, event );

					self.element.trigger( 'change' );
				} else if ( $( this ).hasClass( 'wp-picker-default' ) ) {
					self.element.val( self.options.defaultColor ).change();
				}
			});
		},
	});

	/**
	 * Overwrite iris
	 */
	$.widget( 'a8c.iris', $.a8c.iris, {
		_create: function() {
			this._super();

			// Global option for check is mode rbga is enabled
			this.options.alpha = this.element.data( 'alpha' ) || false;

			// Is not input disabled
			if ( ! this.element.is( ':input' ) )
				this.options.alpha = false;

			if ( typeof this.options.alpha !== 'undefined' && this.options.alpha ) {
				var self       = this,
					el         = self.element,
					_html      = '<div class="iris-strip iris-slider iris-alpha-slider"><div class="iris-slider-offset iris-slider-offset-alpha"></div></div>',
					aContainer = $( _html ).appendTo( self.picker.find( '.iris-picker-inner' ) ),
					aSlider    = aContainer.find( '.iris-slider-offset-alpha' ),
					controls   = {
						aContainer : aContainer,
						aSlider    : aSlider
					};

				if ( typeof el.data( 'custom-width' ) !== 'undefined' ) {
					self.options.customWidth = parseInt( el.data( 'custom-width' ) ) || 0;
				} else {
					self.options.customWidth = 100;
				}

				// Set default width for input reset
				self.options.defaultWidth = el.width();

				// Update width for input
				if ( self._color._alpha < 1 || self._color.toString().indexOf('rgb') != -1 )
					el.width( parseInt( self.options.defaultWidth + self.options.customWidth ) );

				// Push new controls
				$.each( controls, function( k, v ) {
					self.controls[k] = v;
				} );

				// Change size strip and add margin for sliders
				self.controls.square.css( { 'margin-right': '0' } );
				var emptyWidth   = ( self.picker.width() - self.controls.square.width() - 20 ),
					stripsMargin = ( emptyWidth / 6 ),
					stripsWidth  = ( ( emptyWidth / 2 ) - stripsMargin );

				$.each( [ 'aContainer', 'strip' ], function( k, v ) {
					self.controls[v].width( stripsWidth ).css( { 'margin-left' : stripsMargin + 'px' } );
				} );

				// Add new slider
				self._initControls();

				// For updated widget
				self._change();
			}
		},
		_initControls: function() {
			this._super();

			if ( this.options.alpha ) {
				var self     = this,
					controls = self.controls;

				controls.aSlider.slider({
					orientation : 'vertical',
					min         : 0,
					max         : 100,
					step        : 1,
					value       : parseInt( self._color._alpha * 100 ),
					slide       : function( event, ui ) {
						// Update alpha value
						self._color._alpha = parseFloat( ui.value / 100 );
						self._change.apply( self, arguments );
					}
				});
			}
		},
		_change: function() {
			this._super();

			var self = this,
				el   = self.element;

			if ( this.options.alpha ) {
				var	controls     = self.controls,
					alpha        = parseInt( self._color._alpha * 100 ),
					color        = self._color.toRgb(),
					gradient     = [
						'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%',
						'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%'
					],
					defaultWidth = self.options.defaultWidth,
					customWidth  = self.options.customWidth,
					target       = self.picker.closest( '.wp-picker-container' ).find( '.wp-color-result' );

				// Generate background slider alpha, only for CSS3 old browser fuck!! :)
				controls.aContainer.css( { 'background' : 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + image + ')' } );

				if ( target.hasClass( 'wp-picker-open' ) ) {
					// Update alpha value
					controls.aSlider.slider( 'value', alpha );

					/**
					 * Disabled change opacity in default slider Saturation ( only is alpha enabled )
					 * and change input width for view all value
					 */
					if ( self._color._alpha < 1 ) {
						controls.strip.attr( 'style', controls.strip.attr( 'style' ).replace( /rgba\(([0-9]+,)(\s+)?([0-9]+,)(\s+)?([0-9]+)(,(\s+)?[0-9\.]+)\)/g, 'rgb($1$3$5)' ) );
						el.width( parseInt( defaultWidth + customWidth ) );
					} else {
						el.width( defaultWidth );
					}
				}
			}

			var reset = el.data( 'reset-alpha' ) || false;

			if ( reset ) {
				self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() {
					self._color._alpha = 1;
					self.active        = 'external';
					self._change();
				} );
			}
			el.trigger( 'change' );
		},
		_addInputListeners: function( input ) {
			var self            = this,
				debounceTimeout = 100,
				callback        = function( event ) {
					var color = new Color( input.val() ),
						val   = input.val();

					input.removeClass( 'iris-error' );
					// we gave a bad color
					if ( color.error ) {
						// don't error on an empty input
						if ( val !== '' )
							input.addClass( 'iris-error' );
					} else {
						if ( color.toString() !== self._color.toString() ) {
							// let's not do this on keyup for hex shortcodes
							if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) )
								self._setOption( 'color', color.toString() );
						}
					}
				};

			input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) );

			// If we initialized hidden, show on first focus. The rest is up to you.
			if ( self.options.hide ) {
				input.on( 'focus', function() {
					self.show();
				} );
			}
		}
	} );
}( jQuery ) );






		</script>
		<?php
	}

	/**
	 * Widget output.
	 *
	 * @since  1.0
	 *
	 * @access public
	 * @param  array $args
	 * @param  array $instance
	 */
	public function widget( $args, $instance ) {
		extract( $args );

		// Title
		$title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : _x( 'Color Picker', 'widget title', 'color-picker-widget' );
		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );

		// Colors
		$color1 = ( ! empty( $instance['color1'] ) ) ? $instance['color1'] : '#fff';
		$color2 = ( ! empty( $instance['color2'] ) ) ? $instance['color2'] : '#f00';

		echo $before_widget;
		if ( $title )
			echo $before_title . $title . $after_title;
		?>
			<div style="height: 100px; width: 100%; background-color:<?php echo $color1; ?>"></div>
			<div style="height: 100px; width: 100%; background-color:<?php echo $color2; ?>"></div>
		<?php
		echo $after_widget;
	}

	/**
	 * Saves widget settings.
	 *
	 * @since  1.0
	 *
	 * @access public
	 * @param  array $new_instance
	 * @param  array $old_instance
	 * @return array
	 */
	public function update( $new_instance, $old_instance ) {
		$instance = $old_instance;

		$instance[ 'title' ]  = strip_tags( $new_instance['title'] );
		$instance[ 'color1' ] = strip_tags( $new_instance['color1'] );
		$instance[ 'color2' ] = strip_tags( $new_instance['color2'] );

		return $instance;
	}

	/**
	 * Prints the settings form.
	 *
	 * @since  1.0
	 *
	 * @access public
	 * @param  array $instance
	 */
	public function form( $instance ) {
		// Defaults
		$instance = wp_parse_args(
			$instance,
			array(
				'title' => _x( 'Color Picker', 'widget title', 'color-picker-widget' ),
				'color1' => '',
				'color2' => ''
			)
		);

		$title = esc_attr( $instance[ 'title' ] );
		$color1 = esc_attr( $instance[ 'color1' ] );
		$color2 = esc_attr( $instance[ 'color2' ] );
		?>
		<p>
			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
			<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>
		</p>
		<p>
			<label for="<?php echo $this->get_field_id( 'color1' ); ?>"><?php _e( 'Color 1:' ); ?></label><br>
			<input type="text" name="<?php echo $this->get_field_name( 'color1' ); ?>" class="color-picker" id="<?php echo $this->get_field_id( 'color1' ); ?>" value="<?php echo $color1; ?>" data-default-color="#fff" data-alpha="true" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id( 'color2' ); ?>"><?php _e( 'Color 2:' ); ?></label><br>
			<input type="text" name="<?php echo $this->get_field_name( 'color2' ); ?>" class="color-picker" id="<?php echo $this->get_field_id( 'color2' ); ?>" value="<?php echo $color2; ?>" data-default-color="#f00" data-alpha="true" />
		</p>
		<?php
	}
}

add_action( 'widgets_init', function() {
	register_widget( 'Color_Picker_Widget_25809' );
} );


@urosmil
Copy link

urosmil commented Sep 17, 2018

Hi. Any news about this issue? Problem is that on _init, at the end self._change() called. Inside self._change() trigger('change') is called that calls default widget change function and that's why save isn't changed to saved, because widget thinks that some property has value changed.

@kallookoo
Copy link
Owner

Seeing the code I think it's not a bug, basically for how the code is written.

@urosmil
Copy link

urosmil commented Sep 17, 2018

HI kallookoo,

thanks on quick answer. Here is the video showing the issue https://www.screencast.com/t/77bakoIH As you can see, problem is that button for save is never in disabled state with saved label. Also, remove label is not shown. Maybe the biggest problem is that when you use colorpicker with alpha on widget page, when you want to navigate off, confirmation dialog is displayed on top of the window and users get confused, everything is saved, but confirmation dialog is displayed. Could you please consider some improvement regarding this?

Thanks in advance,
Uros

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants