From 27b4e0ffd59199b463082072b3587fc3ff90d22d Mon Sep 17 00:00:00 2001 From: Steve Sutton Date: Wed, 10 Oct 2018 10:43:32 -0400 Subject: [PATCH] Updating the agenda view for event plugin. Update the agenda view to use the one created for visitalpena. Adding new management options for setting the colors for the agenda view. This allows the colors to be set without updating the theme. The old agenda view is saved as agenda_old.html so it can be setup as the template attribute in the shortcode to keep that template. --- classes/data/dataManagement.php | 64 + css/wheelcolorpicker.css | 160 + index.php | 2 +- js/jquery.wheelcolorpicker.js | 2625 +++++++++++++++++ models/admin/management/events.php | 15 + ..._V0.1.7.sql => create_database_V0.1.8.sql} | 8 + setup/databaseScripts/dbVersions.php | 1 + setup/databaseScripts/steve-V0.1.8.sql | 0 .../update_database_V0.1.8.sql | 41 + views/admin/management/events.html | 56 + views/front/events/agenda.html | 686 +++-- views/front/events/agenda_old.html | 376 +++ 12 files changed, 3710 insertions(+), 324 deletions(-) create mode 100644 css/wheelcolorpicker.css create mode 100644 js/jquery.wheelcolorpicker.js rename setup/databaseScripts/{create_database_V0.1.7.sql => create_database_V0.1.8.sql} (95%) delete mode 100644 setup/databaseScripts/steve-V0.1.8.sql create mode 100644 setup/databaseScripts/update_database_V0.1.8.sql create mode 100644 views/front/events/agenda_old.html diff --git a/classes/data/dataManagement.php b/classes/data/dataManagement.php index bf2e23a..ad2f7b2 100644 --- a/classes/data/dataManagement.php +++ b/classes/data/dataManagement.php @@ -247,6 +247,70 @@ class GlmDataEventsManagement extends GlmDataAbstract 'use' => 'a', ), + // Color of the search button + 'event_add_button_color' => array( + 'field' => 'event_add_button_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Option to hide the add event button + 'event_add_button_hidden' => array( + 'field' => 'event_add_button_hidden', + 'type' => 'checkbox', + 'default' => false, + 'use' => 'a' + ), + + // Color of the search button + 'event_back_to_search_color' => array( + 'field' => 'event_back_to_search_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Color of the search button + 'agenda_date_background_color' => array( + 'field' => 'agenda_date_background_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Color of the search button + 'agenda_date_text_color' => array( + 'field' => 'agenda_date_text_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Color of the search button + 'agenda_title_color' => array( + 'field' => 'agenda_title_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Color of the search button + 'agenda_container_background_color' => array( + 'field' => 'agenda_container_background_color', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + + // Max Width of Agenda View + 'agenda_view_max_width' => array( + 'field' => 'agenda_view_max_width', + 'type' => 'text', + 'required' => false, + 'use' => 'a', + ), + ); } diff --git a/css/wheelcolorpicker.css b/css/wheelcolorpicker.css new file mode 100644 index 0000000..1272619 --- /dev/null +++ b/css/wheelcolorpicker.css @@ -0,0 +1,160 @@ +/** + * jQuery Wheel Color Picker + * Base Stylesheet + * + * http://www.jar2.net/projects/jquery-wheelcolorpicker + * + * Copyright © 2011-2016 Fajar Chandra. All rights reserved. + * Released under MIT License. + * http://www.opensource.org/licenses/mit-license.php + * + * Note: Width, height, left, and top properties are handled by the + * plugin. These values might change on the fly. + */ + +.jQWCP-wWidget { + position: absolute; + width: 250px; + height: 180px; + background: #eee; + box-shadow: 1px 1px 4px rgba(0,0,0,.5); + border-radius: 4px; + border: solid 1px #aaa; + padding: 10px; + z-index: 1001; +} + +.jQWCP-wWidget.jQWCP-block { + position: relative; + border-color: #aaa; + box-shadow: inset 1px 1px 1px #ccc; +} + +.jQWCP-wWheel { + background-repeat: no-repeat; + background-position: center; + background-size: contain; + position: relative; + float: left; + width: 180px; + height: 180px; + -webkit-border-radius: 90px; + -moz-border-radius: 50%; + border-radius: 50%; + border: solid 1px #aaa; + margin: -1px; + margin-right: 10px; + transition: border .15s; + cursor: crosshair; +} + +.jQWCP-wWheel:hover { + border-color: #666; +} + +.jQWCP-wWheelOverlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0; + -webkit-border-radius: 90px; + -moz-border-radius: 50%; + border-radius: 50%; +} + +.jQWCP-wWheelCursor { + width: 8px; + height: 8px; + position: absolute; + top: 50%; + left: 50%; + margin: -6px -6px; + cursor: crosshair; + border: solid 2px #fff; + box-shadow: 1px 1px 2px #000; + border-radius: 50%; +} + +.jQWCP-slider-wrapper, +.jQWCP-wPreview { + position: relative; + width: 20px; + height: 180px; + float: left; + margin-right: 10px; +} + +.jQWCP-wWheel:last-child, +.jQWCP-slider-wrapper:last-child, +.jQWCP-wPreview:last-child { + margin-right: 0; +} + +.jQWCP-slider, +.jQWCP-wPreviewBox { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + box-sizing: border-box; + border: solid 1px #aaa; + margin: -1px; + -moz-border-radius: 4px; + border-radius: 4px; + transition: border .15s; +} + +.jQWCP-slider { + cursor: crosshair; +} + +.jQWCP-slider-wrapper:hover .jQWCP-slider { + border-color: #666; +} + +.jQWCP-scursor { + position: absolute; + left: 0; + top: 0; + right: 0; + height: 6px; + margin: -5px -1px -5px -3px; + cursor: crosshair; + border: solid 2px #fff; + box-shadow: 1px 1px 2px #000; + border-radius: 4px; +} + +.jQWCP-wAlphaSlider, +.jQWCP-wPreviewBox { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEVAQEB/f39eaJUuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYRBDgK9dKdMgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVAjXY/jPwIAVYRf9DwB+vw/x6vMT1wAAAABJRU5ErkJggg==') center center; +} + +.jQWCP-overlay { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1000; +} + +/*********************/ + +/* Mobile layout */ + +.jQWCP-mobile.jQWCP-wWidget { + position: fixed; + bottom: 0; + left: 0 !important; + top: auto !important; + width: 100%; + height: 75%; + max-height: 240px; + box-sizing: border-box; + border-radius: 0; +} diff --git a/index.php b/index.php index f069dd5..4ddb209 100644 --- a/index.php +++ b/index.php @@ -44,7 +44,7 @@ if (!defined('ABSPATH')) { * version from this plugin. */ define('GLM_MEMBERS_EVENTS_PLUGIN_VERSION', '1.7.0'); -define('GLM_MEMBERS_EVENTS_PLUGIN_DB_VERSION', '0.1.7'); +define('GLM_MEMBERS_EVENTS_PLUGIN_DB_VERSION', '0.1.8'); // This is the minimum version of the GLM Members DB plugin require for this plugin. define('GLM_MEMBERS_EVENTS_PLUGIN_MIN_MEMBERS_REQUIRED_VERSION', '2.9.15'); diff --git a/js/jquery.wheelcolorpicker.js b/js/jquery.wheelcolorpicker.js new file mode 100644 index 0000000..9cdeca8 --- /dev/null +++ b/js/jquery.wheelcolorpicker.js @@ -0,0 +1,2625 @@ +/** + * jQuery Wheel Color Picker + * + * https://raffer.one/projects/jquery-wheelcolorpicker + * + * Author : Fajar Chandra + * Date : 2018.02.09 + * + * Copyright © 2011-2018 Fajar Chandra. All rights reserved. + * Released under MIT License. + * http://www.opensource.org/licenses/mit-license.php + */ + +(function ($) { + + /** + * Function: wheelColorPicker + * + * The wheelColorPicker plugin wrapper. Firing all functions and + * setting/getting all options in this plugin should be called via + * this function. + * + * Before that, if wheelColorPicker instance is not yet initialized, + * this will initialize ColorPicker widget. + * + * This function will look for the options parameter passed in, and + * try to do something as specified in this order: + * 1. If no argument is passed, then initialize the plugin or do nothing + * 2. If object is passed, then call setOptions() + * 3. If string is passed, then try to fire a method with that name + * 4. If string is passed and no method matches the name, then try + * to set/get an option with that name. If a setter/getter method + * available (i.e. setSomething), it will set/get that option via that method. + */ + $.fn.wheelColorPicker = function() { + var returnValue = this; // Allows method chaining + + // Separate first argument and the rest.. + // First argument is used to determine function/option name + // e.g. wheelColorPicker('setColor', { r: 0, g: 0, b: 1 }) + if(arguments.length > 0) { + var shift = [].shift; + var firstArg = shift.apply(arguments); + var firstArgUc = (typeof firstArg === "string") ? firstArg.charAt(0).toUpperCase() + firstArg.slice(1) : firstArg; + } + else { + var firstArg = undefined; + var firstArgUc = undefined; + } + var args = arguments; + + + this.each(function() { + + // Grab ColorPicker object instance + var instance = $(this).data('jQWCP.instance'); + + // Initialize if not yet created + if(instance == undefined || instance == null) { + // Get init options + var options = {}; + if(typeof firstArg === "object") { + options = firstArg; + } + + instance = new WCP.ColorPicker(this, options); + $(this).data('jQWCP.instance', instance); + } + + /// What to do? /// + + // No arguments provided, do nothing + // wheelColorPicker() + if(firstArg === undefined || typeof firstArg === "object") { + } + + // Call a method + // wheelColorPicker('show') + else if(typeof instance[firstArg] === "function") { + //console.log('method'); + var ret = instance[firstArg].apply(instance, args); + + // If instance is not returned, no method chaining + if(ret !== instance) { + returnValue = ret; + return false; + } + } + + // Try option setter + // wheelColorPicker('color', '#ff00aa') + else if(typeof instance['set'+firstArgUc] === "function" && args.length > 0) { + //console.log('setter'); + var ret = instance['set'+firstArgUc].apply(instance, args); + + // If instance is not returned, no method chaining + if(ret !== instance) { + returnValue = ret; + return false; + } + } + + // Try option getter + // wheelColorPicker('color') + else if(typeof instance['get'+firstArgUc] === "function") { + //console.log('getter'); + var ret = instance['get'+firstArgUc].apply(instance, args); + + // If instance is not returned, no method chaining + if(ret !== instance) { + returnValue = ret; + return false; + } + } + + // Set option value + // wheelColorPicker('format', 'hex') + else if(instance.options[firstArg] !== undefined && args.length > 0) { + //console.log('set option'); + instance.options[firstArg] = args[0]; + } + + // Get option value + // wheelColorPicker('format') + else if(instance.options[firstArg] !== undefined) { + //console.log('get option'); + returnValue = instance.options[firstArg]; + return false; + } + + // Nothing matches, throw error + else { + $.error( 'Method/option named ' + firstArg + ' does not exist on jQuery.wheelColorPicker' ); + } + + }); + + return returnValue; + }; + + + + /******************************************************************/ + + ///////////////////////////////////////// + // Shorthand for $.fn.wheelColorPicker // + ///////////////////////////////////////// + var WCP = $.fn.wheelColorPicker; + ///////////////////////////////////////// + + /** + * Object: defaults + * + * Contains default options for the wheelColorPicker plugin. + * + * Member properties: + * + * format - Color naming style. See colorToRgb for all + * available formats. + * live - Enable dynamic slider gradients. + * preview - Enable live color preview on input field + * userinput - (Deprecated) Enable picking color by typing directly + * validate - When userinput is enabled, force the value to be + * a valid format. If user input an invalid color, the value + * will be reverted to last valid color. + * autoResize - Automatically resize container width. + * If set to false, you could manually adjust size with CSS. + * autoFormat - Automatically convert input value to + * specified format. For example, if format is "rgb", + * "#ff0000" will automatically converted into "rgb(255,0,0)". + * color - Initial value in any of supported color + * value format or as an object. Setting this value will + * override the current input value. + * alpha - (Deprecated) Force the color picker to use alpha value + * despite its selected color format. This option is + * deprecated. Use sliders = "a" instead. + * inverseLabel - (deprecated) Boolean use inverse color for + * input label instead of black/white color. + * preserveWheel - Boolean preserve color wheel shade when slider + * position changes. If set to true, changing + * color wheel from black will reset selectedColor.val + * (shade) to 1. + * interactive - Boolean enable interactive sliders where slider bar + * gradients change dynamically as user drag a slider + * handle. Set to false if this affect performance. + * See also 'quality' option if you wish to keep + * interactive slider but with reduced quality. + * cssClass - Object CSS Classes to be added to the color picker. + * layout - String [block|popup] Layout mode. + * animDuration - Number Duration for transitions such as fade-in + * and fade-out. + * quality - Rendering details quality. The normal quality is 1. + * Setting less than 0.1 may make the sliders ugly, + * while setting the value too high might affect performance. + * sliders - String combination of sliders. If null then the color + * picker will show default values, which is "wvp" for + * normal color or "wvap" for color with alpha value. + * Possible combinations are "whsvrgbap". + * Order of letters will affect slider positions. + * sliderLabel - Boolean Show labels for each slider. + * sliderValue - Boolean Show numeric value of each slider. + * hideKeyboard - Boolean Keep input blurred to avoid on screen keyboard appearing. + * If this is set to true, avoid assigning handler to blur event. + * rounding - Round the alpha value to N decimal digits. Default is 2. + * Set -1 to disable rounding. + * mobile - Display mobile-friendly layout when opened in mobile device. + * mobileWidth - Max screen width to use mobile layout instead of default one. + * mobileAutoScroll - Automatically scroll the page if focused input element + * gets obstructed by color picker dialog. + * htmlOptions - Load options from HTML attributes. + * To set options using HTML attributes, + * prefix these options with 'data-wcp-' as attribute names. + * snap - Snap color wheel and slider on 0, 0.5, and 1.0 + * snapTolerance - Snap if slider position falls within defined + * tolerance value. + */ + WCP.defaults = { + format: 'hex', /* 1.x */ + preview: false, /* 1.x */ + live: true, /* 2.0 */ + userinput: true, /* DEPRECATED 1.x */ + validate: true, /* 1.x */ + autoResize: true, /* 3.0 */ + autoFormat: true, /* 3.0 */ + //color: null, /* DEPRECATED 1.x */ /* OBSOLETE 3.0 */ /* Init-time only */ + //alpha: null, /* DEPRECATED 1.x */ /* OBSOLETE 3.0 */ /* See methods.alpha */ + preserveWheel: null, /* DEPRECATED 1.x */ /* Use live */ + cssClass: '', /* 2.0 */ + layout: 'popup', /* 2.0 */ /* Init-time only */ + animDuration: 200, /* 2.0 */ + quality: 1, /* 2.0 */ + sliders: null, /* 2.0 */ + //sliderLabel: true, /* 2.0 */ /* NOT IMPLEMENTED */ + //sliderValue: false, /* 2.0 */ /* NOT IMPLEMENTED */ + rounding: 2, /* 2.3 */ + mobile: true, /* 3.0 */ + mobileWidth: 480, /* 3.0 */ + hideKeyboard: false, /* 2.4 */ + htmlOptions: true, /* 2.3 */ + snap: false, /* 2.5 */ + snapTolerance: 0.05 /* 2.5 */ + }; + + + + /******************************************************************/ + + ////////////////////////////// + // STATIC OBJECTS AND FLAGS // + ////////////////////////////// + + /* + * Note: To determine input position (top and left), use the following: + * WCP.ORIGIN.top + this.input.getBoundingClientRect().top + * instead of using $(this.input).offset().top because on mobile browsers + * (chrome) jQuery's offset() function returns wrong value. + */ + + /// Top left of the page is not on (0,0), making window.scrollX/Y and offset() useless + /// See WCP.ORIGIN + WCP.BUG_RELATIVE_PAGE_ORIGIN = false; + + /// Coordinate of the top left page (mobile chrome workaround) + WCP.ORIGIN = { left: 0, top: 0 }; + + + /******************************************************************/ + + ////////////////////// + // HELPER FUNCTIONS // + ////////////////////// + + /** + * Function: colorToStr + * + * Since 2.0 + * + * Convert color object to string in specified format + * + * Available formats: + * - hex + * - hexa + * - css + * - cssa + * - rgb + * - rgb% + * - rgba + * - rgba% + * - hsv + * - hsv% + * - hsva + * - hsva% + * - hsb + * - hsb% + * - hsba + * - hsba% + */ + WCP.colorToStr = function( color, format ) { + var result = ""; + switch( format ) { + case 'css': + result = "#"; + case 'hex': + var r = Math.round(color.r * 255).toString(16); + if( r.length == 1) { + r = "0" + r; + } + var g = Math.round(color.g * 255).toString(16); + if( g.length == 1) { + g = "0" + g; + } + var b = Math.round(color.b * 255).toString(16); + if( b.length == 1) { + b = "0" + b; + } + result += r + g + b; + break; + + case 'cssa': + result = "#"; + case 'hexa': + var r = Math.round(color.r * 255).toString(16); + if( r.length == 1) { + r = "0" + r; + } + var g = Math.round(color.g * 255).toString(16); + if( g.length == 1) { + g = "0" + g; + } + var b = Math.round(color.b * 255).toString(16); + if( b.length == 1) { + b = "0" + b; + } + var a = Math.round(color.a * 255).toString(16); + if( a.length == 1) { + a = "0" + a; + } + result += r + g + b + a; + break; + + case 'rgb': + result = "rgb(" + + Math.round(color.r * 255) + "," + + Math.round(color.g * 255) + "," + + Math.round(color.b * 255) + ")"; + break; + + case 'rgb%': + result = "rgb(" + + (color.r * 100) + "%," + + (color.g * 100) + "%," + + (color.b * 100) + "%)"; + break; + + case 'rgba': + result = "rgba(" + + Math.round(color.r * 255) + "," + + Math.round(color.g * 255) + "," + + Math.round(color.b * 255) + "," + + color.a + ")"; + break; + + case 'rgba%': + result = "rgba(" + + (color.r * 100) + "%," + + (color.g * 100) + "%," + + (color.b * 100) + "%," + + (color.a * 100) + "%)"; + break; + + case 'hsv': + result = "hsv(" + + (color.h * 360) + "," + + color.s + "," + + color.v + ")"; + break; + + case 'hsv%': + result = "hsv(" + + (color.h * 100) + "%," + + (color.s * 100) + "%," + + (color.v * 100) + "%)"; + break; + + case 'hsva': + result = "hsva(" + + (color.h * 360) + "," + + color.s + "," + + color.v + "," + + color.a + ")"; + break; + + case 'hsva%': + result = "hsva(" + + (color.h * 100) + "%," + + (color.s * 100) + "%," + + (color.v * 100) + "%," + + (color.a * 100) + "%)"; + break; + + + case 'hsb': + result = "hsb(" + + color.h + "," + + color.s + "," + + color.v + ")"; + break; + + case 'hsb%': + result = "hsb(" + + (color.h * 100) + "%," + + (color.s * 100) + "%," + + (color.v * 100) + "%)"; + break; + + case 'hsba': + result = "hsba(" + + color.h + "," + + color.s + "," + + color.v + "," + + color.a + ")"; + break; + + case 'hsba%': + result = "hsba(" + + (color.h * 100) + "%," + + (color.s * 100) + "%," + + (color.v * 100) + "%," + + (color.a * 100) + "%)"; + break; + + } + return result; + }; + + + /** + * Function: strToColor + * + * Since 2.0 + * + * Convert string to color object. + * Please note that if RGB color is supplied, the returned value + * will only contain RGB. + * + * If invalid string is supplied, FALSE will be returned. + */ + WCP.strToColor = function( val ) { + var color = { a: 1 }; + var tmp; + var hasAlpha; + + // #fff + // #ffff + if( + val.match(/^#[0-9a-f]{3}$/i) != null || + val.match(/^#[0-9a-f]{4}$/i) + ) { + if( isNaN( color.r = parseInt(val.substr(1, 1), 16) * 17 / 255 ) ) { + return false; + } + if( isNaN( color.g = parseInt(val.substr(2, 1), 16) * 17 / 255 ) ) { + return false; + } + if( isNaN( color.b = parseInt(val.substr(3, 1), 16) * 17 / 255 ) ) { + return false; + } + + // Alpha + if(val.length == 5) { + if( isNaN( color.a = parseInt(val.substr(4, 1), 16) * 17 / 255 ) ) { + return false; + } + } + } + + // fff + // ffff + else if( + val.match(/^[0-9a-f]{3}$/i) != null || + val.match(/^[0-9a-f]{4}$/i) != null + ) { + if( isNaN( color.r = parseInt(val.substr(0, 1), 16) * 17 / 255 ) ) { + return false; + } + if( isNaN( color.g = parseInt(val.substr(1, 1), 16) * 17 / 255 ) ) { + return false; + } + if( isNaN( color.b = parseInt(val.substr(2, 1), 16) * 17 / 255 ) ) { + return false; + } + + // Alpha + if(val.length == 4) { + if( isNaN( color.a = parseInt(val.substr(3, 1), 16) * 17 / 255 ) ) { + return false; + } + } + } + + // #ffffff + // #ffffffff + else if( + val.match(/^#[0-9a-f]{6}$/i) != null || + val.match(/^#[0-9a-f]{8}$/i) != null + ) { + if( isNaN( color.r = parseInt(val.substr(1, 2), 16) / 255 ) ) { + return false; + } + if( isNaN( color.g = parseInt(val.substr(3, 2), 16) / 255 ) ) { + return false; + } + if( isNaN( color.b = parseInt(val.substr(5, 2), 16) / 255 ) ) { + return false; + } + + // Alpha + if(val.length == 9) { + if( isNaN( color.a = parseInt(val.substr(7, 2), 16) / 255 ) ) { + return false; + } + } + } + + // ffffff + // ffffffff + else if( + val.match(/^[0-9a-f]{6}$/i) != null || + val.match(/^[0-9a-f]{8}$/i) != null + ) { + if( isNaN( color.r = parseInt(val.substr(0, 2), 16) / 255 ) ) { + return false; + } + if( isNaN( color.g = parseInt(val.substr(2, 2), 16) / 255 ) ) { + return false; + } + if( isNaN( color.b = parseInt(val.substr(4, 2), 16) / 255 ) ) { + return false; + } + + // Alpha + if(val.length == 8) { + if( isNaN( color.a = parseInt(val.substr(6, 2), 16) / 255 ) ) { + return false; + } + } + } + + // rgb(100%,100%,100%) + // rgba(100%,100%,100%,100%) + // rgba(255,255,255,1) + // rgba(100%,1, 0.5,.2) + else if( + val.match(/^rgba\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null || + val.match(/^rgb\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null + ) { + if(val.match(/a/i) != null) { + hasAlpha = true; + } + else { + hasAlpha = false; + } + + tmp = val.substring(val.indexOf('(')+1, val.indexOf(',')); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.r = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.r = parseInt(tmp) / 255 ) ) { + return false; + } + } + + tmp = val.substring(val.indexOf(',')+1, val.indexOf(',', val.indexOf(',')+1)); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.g = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.g = parseInt(tmp) / 255 ) ) { + return false; + } + } + + if(hasAlpha) { + tmp = val.substring(val.indexOf(',', val.indexOf(',')+1)+1, val.lastIndexOf(',')); + } + else { + tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')')); + } + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.b = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.b = parseInt(tmp) / 255 ) ) { + return false; + } + } + + if(hasAlpha) { + tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')')); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.a = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.a = parseFloat(tmp) ) ) { + return false; + } + } + } + } + + // hsv(100%,100%,100%) + // hsva(100%,100%,100%,100%) + // hsv(360,1,1,1) + // hsva(360,1, 0.5,.2) + // hsb(100%,100%,100%) + // hsba(100%,100%,100%,100%) + // hsb(360,1,1,1) + // hsba(360,1, 0.5,.2) + else if( + val.match(/^hsva\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null || + val.match(/^hsv\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null || + val.match(/^hsba\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null || + val.match(/^hsb\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null + ) { + if(val.match(/a/i) != null) { + hasAlpha = true; + } + else { + hasAlpha = false; + } + + tmp = val.substring(val.indexOf('(')+1, val.indexOf(',')); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.h = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.h = parseFloat(tmp) / 360 ) ) { + return false; + } + } + + tmp = val.substring(val.indexOf(',')+1, val.indexOf(',', val.indexOf(',')+1)); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.s = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.s = parseFloat(tmp) ) ) { + return false; + } + } + + if(hasAlpha) { + tmp = val.substring(val.indexOf(',', val.indexOf(',')+1)+1, val.lastIndexOf(',')); + } + else { + tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')')); + } + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.v = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.v = parseFloat(tmp) ) ) { + return false; + } + } + + if(hasAlpha) { + tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')')); + if( tmp.charAt( tmp.length-1 ) == '%') { + if( isNaN( color.a = parseFloat(tmp) / 100 ) ) { + return false; + } + } + else { + if( isNaN( color.a = parseFloat(tmp) ) ) { + return false; + } + } + } + } + + else { + return false; + } + + return color; + }; + + + /** + * Function: hsvToRgb + * + * Since 2.0 + * + * Perform HSV to RGB conversion + */ + WCP.hsvToRgb = function( h, s, v ) { + + // Calculate RGB from hue (1st phase) + var cr = (h <= (1/6) || h >= (5/6)) ? 1 + : (h < (1/3) ? 1 - ((h - (1/6)) * 6) + : (h > (4/6) ? (h - (4/6)) * 6 + : 0)); + var cg = (h >= (1/6) && h <= (3/6)) ? 1 + : (h < (1/6) ? h * 6 + : (h < (4/6) ? 1 - ((h - (3/6)) * 6) + : 0)); + var cb = (h >= (3/6) && h <= (5/6)) ? 1 + : (h > (2/6) && h < (3/6) ? (h - (2/6)) * 6 + : (h > (5/6) ? 1 - ((h - (5/6)) * 6) + : 0)); + + //~ console.log(cr + ' ' + cg + ' ' + cb); + + // Calculate RGB with saturation & value applied + var r = (cr + (1-cr)*(1-s)) * v; + var g = (cg + (1-cg)*(1-s)) * v; + var b = (cb + (1-cb)*(1-s)) * v; + + //~ console.log(r + ' ' + g + ' ' + b + ' ' + v); + + return { r: r, g: g, b: b }; + }; + + + /** + * Function: rgbToHsv + * + * Since 2.0 + * + * Perform RGB to HSV conversion + */ + WCP.rgbToHsv = function( r, g, b ) { + + var h; + var s; + var v; + + var maxColor = Math.max(r, g, b); + var minColor = Math.min(r, g, b); + var delta = maxColor - minColor; + + // Calculate saturation + if(maxColor != 0) { + s = delta / maxColor; + } + else { + s = 0; + } + + // Calculate hue + // To simplify the formula, we use 0-6 range. + if(delta == 0) { + h = 0; + } + else if(r == maxColor) { + h = (6 + (g - b) / delta) % 6; + } + else if(g == maxColor) { + h = 2 + (b - r) / delta; + } + else if(b == maxColor) { + h = 4 + (r - g) / delta; + } + else { + h = 0; + } + // Then adjust the range to be 0-1 + h = h/6; + + // Calculate value + v = maxColor; + + //~ console.log(h + ' ' + s + ' ' + v); + + return { h: h, s: s, v: v }; + }; + + + + /******************************************************************/ + + //////////////////////// + // COLOR PICKER CLASS // + //////////////////////// + + /** + * Class: ColorPicker + * + * Since 3.0 + */ + WCP.ColorPicker = function ( elm, options ) { + + // Assign reference to input DOM element + this.input = elm; + + // Setup selected color in the following priority: + // 1. options.color + // 2. input.value + // 3. default + this.color = { h: 0, s: 0, v: 1, r: 1, g: 1, b: 1, a: 1 }; + this.setValue(this.input.value); + + // Set options + this.options = $.extend(true, {}, WCP.defaults); + this.setOptions(options); + + // Check sliders option, if not defined, set default sliders + if(this.options.sliders == null) + this.options.sliders = 'wvp' + (this.options.format.indexOf('a') >= 0 ? 'a' : ''); + + this.init(); + }; + + + //////////////////// + // Static members // + //////////////////// + + + /** + * Static Property: ColorPicker.widget + * + * Reference to global color picker widget (popup) + */ + WCP.ColorPicker.widget = null; + + + /** + * Property: ColorPicker.overlay + * + * Reference to overlay DOM element (overlay for global popup) + */ + WCP.ColorPicker.overlay = null; + + + /** + * Function: init + * + * Since 3.0 + * 2.0 was methods.staticInit + * + * Initialize wheel color picker globally. + */ + WCP.ColorPicker.init = function() { + // Only perform initialization once + if(WCP.ColorPicker.init.hasInit == true) + return; + WCP.ColorPicker.init.hasInit = true; + + // Insert overlay element to handle popup closing + // when hideKeyboard is true, hence input is always blurred + var $overlay = $(''); + $overlay.on('click', WCP.Handler.overlay_click); + WCP.ColorPicker.overlay = $overlay.get(0); + $('body').append($overlay); + + // Insert CSS for color wheel + var wheelImage = WCP.ColorPicker.getWheelDataUrl(200); + $('head').append( + '' + ); + + // Attach events + $('html').on('mouseup.wheelColorPicker', WCP.Handler.html_mouseup); + $('html').on('touchend.wheelColorPicker', WCP.Handler.html_mouseup); + $('html').on('mousemove.wheelColorPicker', WCP.Handler.html_mousemove); + $('html').on('touchmove.wheelColorPicker', WCP.Handler.html_mousemove); + $(window).on('resize.wheelColorPicker', WCP.Handler.window_resize); + }; + + + /** + * Function: createWidget + * + * Since 3.0 + * 2.5 was private.initWidget + * + * Create color picker widget. + */ + WCP.ColorPicker.createWidget = function() { + /// WIDGET /// + // Notice: We won't use canvas to draw the color wheel since + // it may takes time and cause performance issue. + var $widget = $( + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "
" + + "
" + ); + + // Small UI fix to disable highlighting the widget + // Also UI fix to disable touch context menu + $widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-scursor, .jQWCP-slider') + .attr('unselectable', 'on') + .css('-moz-user-select', 'none') + .css('-webkit-user-select', 'none') + .css('user-select', 'none') + .css('-webkit-touch-callout', 'none'); + + // Disable context menu on sliders + // Workaround for touch browsers + $widget.on('contextmenu.wheelColorPicker', function() { return false; }); + + // Bind widget events + $widget.on('mousedown.wheelColorPicker', '.jQWCP-wWheel', WCP.Handler.wheel_mousedown); + $widget.on('touchstart.wheelColorPicker', '.jQWCP-wWheel', WCP.Handler.wheel_mousedown); + $widget.on('mousedown.wheelColorPicker', '.jQWCP-wWheelCursor', WCP.Handler.wheelCursor_mousedown); + $widget.on('touchstart.wheelColorPicker', '.jQWCP-wWheelCursor', WCP.Handler.wheelCursor_mousedown); + $widget.on('mousedown.wheelColorPicker', '.jQWCP-slider', WCP.Handler.slider_mousedown); + $widget.on('touchstart.wheelColorPicker', '.jQWCP-slider', WCP.Handler.slider_mousedown); + $widget.on('mousedown.wheelColorPicker', '.jQWCP-scursor', WCP.Handler.sliderCursor_mousedown); + $widget.on('touchstart.wheelColorPicker', '.jQWCP-scursor', WCP.Handler.sliderCursor_mousedown); + + return $widget.get(0); + }; + + + /** + * Function: getWheelDataUrl + * + * Create color wheel image and return as base64 encoded data url. + */ + WCP.ColorPicker.getWheelDataUrl = function( size ) { + var r = size / 2; // radius + var center = r; + var canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + var context = canvas.getContext('2d'); + + // Fill the wheel with colors + for(var y = 0; y < size; y++) { + for(var x = 0; x < size; x++) { + // Get the offset from central position + var offset = Math.sqrt(Math.pow(x - center, 2) + Math.pow(y - center, 2)); + + // Skip pixels outside picture area (plus 2 pixels) + if(offset > r + 2) { + continue; + } + + // Get the position in degree (hue) + var deg = ( + (x - center == 0 + ? (y < center ? 90 : 270) + : (Math.atan((center - y) / (x - center)) / Math.PI * 180) + ) + + (x < center ? 180 : 0) + + 360 + ) % 360; + + // Relative offset (sat) + var sat = offset / r; + + // Value is always 1 + var val = 1; + + // Calculate color + var cr = (Math.abs(deg + 360) + 60) % 360 < 120 ? 1 + : (deg > 240 ? (120 - Math.abs(deg - 360)) / 60 + : (deg < 120 ? (120 - deg) / 60 + : 0)); + var cg = Math.abs(deg - 120) < 60 ? 1 + : (Math.abs(deg - 120) < 120 ? (120 - Math.abs(deg - 120)) / 60 + : 0); + + var cb = Math.abs(deg - 240) < 60 ? 1 + : (Math.abs(deg - 240) < 120 ? (120 - Math.abs(deg - 240)) / 60 + : 0); + var pr = Math.round((cr + (1 - cr) * (1 - sat)) * 255); + var pg = Math.round((cg + (1 - cg) * (1 - sat)) * 255); + var pb = Math.round((cb + (1 - cb) * (1 - sat)) * 255); + + context.fillStyle = 'rgb(' + pr + ',' + pg + ',' + pb + ')'; + context.fillRect(x, y, 1, 1); + } + } + + return canvas.toDataURL(); + }; + + + ///////////// + // Members // + ///////////// + + + /** + * Property: ColorPicker.options + * + * Plugin options for the color picker instance, extended from WCP.defaults. + */ + WCP.ColorPicker.prototype.options = null; + + + /** + * Property: ColorPicker.input + * + * Reference to input DOM element + */ + WCP.ColorPicker.prototype.input = null; + + + /** + * Property: ColorPicker.widget + * + * Reference to widget DOM element (global popup or private inline widget) + */ + WCP.ColorPicker.prototype.widget = null; + + + /** + * Property: ColorPicker.color + * + * Selected color object. + */ + WCP.ColorPicker.prototype.color = null; + + + /** + * Property: ColorPicker.lastValue + * + * Store last input value + */ + WCP.ColorPicker.prototype.lastValue = null; + + + /** + * Function: ColorPicker.setOptions + * + * Since 3.0 + * + * Set options to the color picker. If htmlOptions is set to true, + * options set via html attributes are also reloaded. If both html + * attribute and argument exists, option set via options argument + * gets priority. + */ + WCP.ColorPicker.prototype.setOptions = function( options ) { + + // options should be a separate object (passed by value) + // Make a copy of options + options = $.extend(true, {}, options); + + // Load options from HTML attributes + if(this.options.htmlOptions) { + for(var key in WCP.defaults) { + // Only if option key is valid and not set via function argument + if(this.input.hasAttribute('data-wcp-'+key) && options[key] === undefined) { + options[key] = this.input.getAttribute('data-wcp-'+key); + // Change true/false string to boolean + if(options[key] == 'true') { + options[key] = true; + } + else if(options[key] == 'false') { + options[key] = false; + } + } + } + } + + // Set options + for(var key in options) { + // Skip undefined option key + if(this.options[key] === undefined) + continue; + + var keyUc = key.charAt(0).toUpperCase() + key.slice(1); + + // If setter is available, try setting it via setter + if(typeof this['set'+keyUc] === "function") { + this['set'+keyUc](options[key]); + } + // Otherwise directly update options + else { + this.options[key] = options[key]; + } + } + + return this; // Allow chaining + }; + + + /** + * Function: ColorPicker.init + * + * Initialize wheel color picker widget + */ + WCP.ColorPicker.prototype.init = function() { + WCP.ColorPicker.init(); + + // Initialization must only occur once + if(this.hasInit == true) + return; + this.hasInit = true; + + var instance = this; + var $input = $(this.input); + var $widget = null; + + /// LAYOUT & BINDINGS /// + // Setup block mode layout + if( this.options.layout == 'block' ) { + // Create widget + this.widget = WCP.ColorPicker.createWidget(); + $widget = $(this.widget); + + // Store object instance reference + $widget.data('jQWCP.instance', this); + + // Wrap widget around the input elm and put the input + // elm inside widget + $widget.insertAfter(this.input); + // Retain display CSS property + if($input.css('display') == "inline") { + $widget.css('display', "inline-block"); + } + else { + $widget.css('display', $input.css('display')); + } + $widget.append(this.input); + $input.hide(); + + // Add tabindex attribute to make the widget focusable + if($input.attr('tabindex') != undefined) { + $widget.attr('tabindex', $input.attr('tabindex')); + } + else { + $widget.attr('tabindex', 0); + } + + // Further widget adjustments based on options + this.refreshWidget(); + + // Draw shading + this.redrawSliders(true); + this.updateSliders(); + + // Bind widget element events + $widget.on('focus.wheelColorPicker', WCP.Handler.widget_focus_block); + $widget.on('blur.wheelColorPicker', WCP.Handler.widget_blur_block); + } + + // Setup popup mode layout + else { + // Only need to create one widget, used globally + if(WCP.ColorPicker.widget == null) { + WCP.ColorPicker.widget = WCP.ColorPicker.createWidget(); + $widget = $(WCP.ColorPicker.widget); + + // Assign widget to global + $widget.hide(); + $('body').append($widget); + + // Bind popup events + $widget.on('mousedown.wheelColorPicker', WCP.Handler.widget_mousedown_popup); + //$widget.on('mouseup.wheelColorPicker', WCP.Handler.widget_mouseup_popup); + + } + this.widget = WCP.ColorPicker.widget; + + // Bind input element events + $input.on('focus.wheelColorPicker', WCP.Handler.input_focus_popup); + $input.on('blur.wheelColorPicker', WCP.Handler.input_blur_popup); + } + + // Bind input events + $input.on('keyup.wheelColorPicker', WCP.Handler.input_keyup); + $input.on('change.wheelColorPicker', WCP.Handler.input_change); + + // Set color value + // DEPRECATED by 3.0 + if(typeof this.options.color == "object") { + this.setColor(this.options.color); + this.options.color = undefined; + } + else if(typeof this.options.color == "string") { + this.setValue(this.options.color); + this.options.color = undefined; + } + + // Set readonly mode + /* DEPRECATED */ + if(this.options.userinput) { + $input.removeAttr('readonly'); + } + else { + $input.attr('readonly', true); + } + }; + + + /** + * Function: destroy + * + * Destroy the color picker and return it to normal element. + */ + WCP.ColorPicker.prototype.destroy = function() { + var $widget = $(this.widget); + var $input = $(this.input); + + // Reset layout + // No need to delete global popup + if(this.options.layout == 'block') { + // Check if active control is the same widget as destroyed widget, remove the reference if it's true + var $control = $( $('body').data('jQWCP.activeControl') ); // Refers to slider wrapper or wheel + if ($control.length) { + var controlWidget = $control.closest('.jQWCP-wWidget'); + if ($widget.is(controlWidget)) { + $('body').data('jQWCP.activeControl', null); + } + } + + $widget.before(this.input); + $widget.remove(); + $input.show(); + } + + // Unbind events + $input.off('focus.wheelColorPicker'); + $input.off('blur.wheelColorPicker'); + $input.off('keyup.wheelColorPicker'); + $input.off('change.wheelColorPicker'); + + // Remove data + $input.data('jQWCP.instance', null); + + // remove self + delete this; + }; + + + /** + * Function: refreshWidget + * + * Since 3.0 + * 2.5 was private.adjustWidget + * + * Update widget to match current option values. + */ + WCP.ColorPicker.prototype.refreshWidget = function() { + var $widget = $(this.widget); + var options = this.options; + var mobileLayout = false; + + // Set CSS classes + $widget.attr('class', 'jQWCP-wWidget'); + if(options.layout == 'block') { + $widget.addClass('jQWCP-block'); + } + $widget.addClass(options.cssClass); + //$widget.addClass(this.input.getAttribute('class')); + + // Check whether to use mobile layout + if(window.innerWidth <= options.mobileWidth && options.layout != 'block' && options.mobile) { + mobileLayout = true; + $widget.addClass('jQWCP-mobile'); + } + + // Rearrange sliders + $widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-wPreview') + .hide() + .addClass('hidden'); + + for(var i in options.sliders) { + var $slider = null; + switch(this.options.sliders[i]) { + case 'w': + $slider = $widget.find('.jQWCP-wWheel'); break; + case 'h': + $slider = $widget.find('.jQWCP-wHue'); break; + case 's': + $slider = $widget.find('.jQWCP-wSat'); break; + case 'v': + $slider = $widget.find('.jQWCP-wVal'); break; + case 'r': + $slider = $widget.find('.jQWCP-wRed'); break; + case 'g': + $slider = $widget.find('.jQWCP-wGreen'); break; + case 'b': + $slider = $widget.find('.jQWCP-wBlue'); break; + case 'a': + $slider = $widget.find('.jQWCP-wAlpha'); break; + case 'p': + $slider = $widget.find('.jQWCP-wPreview'); break; + } + + if($slider != null) { + $slider.appendTo(this.widget); + $slider.show().removeClass('hidden'); + } + } + + // If widget is hidden, show it first so we can calculate dimensions correctly + //var widgetIsHidden = false; + //if($widget.is(':hidden')) { + //widgetIsHidden = true; + //$widget.css({ opacity: '0' }).show(); + //} + + // Adjust sliders height based on quality + var sliderHeight = options.quality * 50; + $widget.find('.jQWCP-slider').attr('height', sliderHeight); + + var $visElms = $widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-wPreview').not('.hidden'); + + // Adjust container and sliders width + // Only if not on mobile layout (force fixed on mobile) + if(options.autoResize && !mobileLayout) { + // Auto resize + var width = 0 + + // Set slider size first, then adjust container + $visElms.css({ width: '', height: '' }); + + $visElms.each(function(index, item) { + var $item = $(item); + width += parseFloat($item.css('margin-left').replace('px', '')) + parseFloat($item.css('margin-right').replace('px', '')) + $item.outerWidth(); + }); + $widget.css({ width: width + 'px' }); + } + else { + // Fixed size + + // Set container size first, then adjust sliders + $widget.css({ width: '' }); + + var $visWheel = $widget.find('.jQWCP-wWheel').not('.hidden'); + var $visSliders = $widget.find('.jQWCP-slider-wrapper, .jQWCP-wPreview').not('.hidden'); + $visWheel.css({ height: $widget.height() + 'px', width: $widget.height() }); + if($visWheel.length > 0) { + var horzSpace = $widget.width() - $visWheel.outerWidth() - parseFloat($visWheel.css('margin-left').replace('px', '')) - parseFloat($visWheel.css('margin-right').replace('px', '')); + } + else { + var horzSpace = $widget.width(); + } + if($visSliders.length > 0) { + var sliderMargins = parseFloat($visSliders.css('margin-left').replace('px', '')) + parseFloat($visSliders.css('margin-right').replace('px', '')); + $visSliders.css({ height: $widget.height() + 'px', width: (horzSpace - ($visSliders.length - 1) * sliderMargins) / $visSliders.length + 'px' }); + } + } + + // Reset visibility + //if(widgetIsHidden) { + //$widget.css({ opacity: '' }).hide(); + //} + + return this; // Allows method chaining + }; + + + /** + * Function: redrawSliders + * + * Introduced in 2.0 + * + * Redraw slider gradients. Hidden sliders are not redrawn as to + * improve performance. If options.live is FALSE, sliders are not redrawn. + * + * Parameter: + * force - Boolean force redraw all sliders. + */ + WCP.ColorPicker.prototype.redrawSliders = function( force ) { + + // Skip if widget not yet initialized + if(this.widget == null) + return this; + + var $widget = $(this.widget); + + // DEPRECATED 3.0 + // In 2.0, parameters are ( sliders, force ) + if(typeof arguments[0] === "string") { + force = arguments[1]; + } + + // No need to redraw sliders on global popup widget if not + // attached to the input elm in current iteration + if(this != $widget.data('jQWCP.instance')) + return this; + + var options = this.options; + var color = this.color; + + var w = 1; + var h = options.quality * 50; + + var A = 1; + var R = 0; + var G = 0; + var B = 0; + var H = 0; + var S = 0; + var V = 1; + + // Dynamic colors + if(options.live) { + A = color.a; + R = Math.round(color.r * 255); + G = Math.round(color.g * 255); + B = Math.round(color.b * 255); + H = color.h; + S = color.s; + V = color.v; + } + + /// PREVIEW /// + // Preview box must always be redrawn, if not hidden + var $previewBox = $widget.find('.jQWCP-wPreviewBox'); + if(!$previewBox.hasClass('hidden')) { + var previewBoxCtx = $previewBox.get(0).getContext('2d'); + previewBoxCtx.fillStyle = "rgba(" + R + "," + G + "," + B + "," + A + ")"; + previewBoxCtx.clearRect(0, 0, 1, 1); + previewBoxCtx.fillRect(0, 0, 1, 1); + } + + /// SLIDERS /// + if(!this.options.live && !force) + return this; + + /// ALPHA /// + // The top color is (R, G, B, 1) + // The bottom color is (R, G, B, 0) + var $alphaSlider = $widget.find('.jQWCP-wAlphaSlider'); + if(!$alphaSlider.hasClass('hidden') || force) { + var alphaSliderCtx = $alphaSlider.get(0).getContext('2d'); + var alphaGradient = alphaSliderCtx.createLinearGradient(0, 0, 0, h); + alphaGradient.addColorStop(0, "rgba("+R+","+G+","+B+",1)"); + alphaGradient.addColorStop(1, "rgba("+R+","+G+","+B+",0)"); + alphaSliderCtx.fillStyle = alphaGradient; + alphaSliderCtx.clearRect(0, 0, w, h); + alphaSliderCtx.fillRect(0, 0, w, h); + } + + /// RED /// + // The top color is (255, G, B) + // The bottom color is (0, G, B) + var $redSlider = $widget.find('.jQWCP-wRedSlider'); + if(!$redSlider.hasClass('hidden') || force) { + var redSliderCtx = $redSlider.get(0).getContext('2d'); + var redGradient = redSliderCtx.createLinearGradient(0, 0, 0, h); + redGradient.addColorStop(0, "rgb(255,"+G+","+B+")"); + redGradient.addColorStop(1, "rgb(0,"+G+","+B+")"); + redSliderCtx.fillStyle = redGradient; + redSliderCtx.fillRect(0, 0, w, h); + } + + /// GREEN /// + // The top color is (R, 255, B) + // The bottom color is (R, 0, B) + var $greenSlider = $widget.find('.jQWCP-wGreenSlider'); + if(!$greenSlider.hasClass('hidden') || force) { + var greenSliderCtx = $greenSlider.get(0).getContext('2d'); + var greenGradient = greenSliderCtx.createLinearGradient(0, 0, 0, h); + greenGradient.addColorStop(0, "rgb("+R+",255,"+B+")"); + greenGradient.addColorStop(1, "rgb("+R+",0,"+B+")"); + greenSliderCtx.fillStyle = greenGradient; + greenSliderCtx.fillRect(0, 0, w, h); + } + + /// BLUE /// + // The top color is (R, G, 255) + // The bottom color is (R, G, 0) + var $blueSlider = $widget.find('.jQWCP-wBlueSlider'); + if(!$blueSlider.hasClass('hidden') || force) { + var blueSliderCtx = $blueSlider.get(0).getContext('2d'); + var blueGradient = blueSliderCtx.createLinearGradient(0, 0, 0, h); + blueGradient.addColorStop(0, "rgb("+R+","+G+",255)"); + blueGradient.addColorStop(1, "rgb("+R+","+G+",0)"); + blueSliderCtx.fillStyle = blueGradient; + blueSliderCtx.fillRect(0, 0, w, h); + } + + /// HUE /// + // The hue slider is static. + var $hueSlider = $widget.find('.jQWCP-wHueSlider'); + if(!$hueSlider.hasClass('hidden') || force) { + var hueSliderCtx = $hueSlider.get(0).getContext('2d'); + var hueGradient = hueSliderCtx.createLinearGradient(0, 0, 0, h); + hueGradient.addColorStop(0, "#f00"); + hueGradient.addColorStop(0.166666667, "#ff0"); + hueGradient.addColorStop(0.333333333, "#0f0"); + hueGradient.addColorStop(0.5, "#0ff"); + hueGradient.addColorStop(0.666666667, "#00f"); + hueGradient.addColorStop(0.833333333, "#f0f"); + hueGradient.addColorStop(1, "#f00"); + hueSliderCtx.fillStyle = hueGradient; + hueSliderCtx.fillRect(0, 0, w, h); + } + + /// SAT /// + // The top color is hsv(h, 1, v) + // The bottom color is hsv(0, 0, v) + var $satSlider = $widget.find('.jQWCP-wSatSlider'); + if(!$satSlider.hasClass('hidden') || force) { + var satTopRgb = $.fn.wheelColorPicker.hsvToRgb(H, 1, V); + satTopRgb.r = Math.round(satTopRgb.r * 255); + satTopRgb.g = Math.round(satTopRgb.g * 255); + satTopRgb.b = Math.round(satTopRgb.b * 255); + var satSliderCtx = $satSlider.get(0).getContext('2d'); + var satGradient = satSliderCtx.createLinearGradient(0, 0, 0, h); + satGradient.addColorStop(0, "rgb("+satTopRgb.r+","+satTopRgb.g+","+satTopRgb.b+")"); + satGradient.addColorStop(1, "rgb("+Math.round(V*255)+","+Math.round(V*255)+","+Math.round(V*255)+")"); + satSliderCtx.fillStyle = satGradient; + satSliderCtx.fillRect(0, 0, w, h); + } + + /// VAL /// + // The top color is hsv(h, s, 1) + // The bottom color is always black. + var $valSlider = $widget.find('.jQWCP-wValSlider'); + if(!$valSlider.hasClass('hidden') || force) { + var valTopRgb = $.fn.wheelColorPicker.hsvToRgb(H, S, 1); + valTopRgb.r = Math.round(valTopRgb.r * 255); + valTopRgb.g = Math.round(valTopRgb.g * 255); + valTopRgb.b = Math.round(valTopRgb.b * 255); + var valSliderCtx = $valSlider.get(0).getContext('2d'); + var valGradient = valSliderCtx.createLinearGradient(0, 0, 0, h); + valGradient.addColorStop(0, "rgb("+valTopRgb.r+","+valTopRgb.g+","+valTopRgb.b+")"); + valGradient.addColorStop(1, "#000"); + valSliderCtx.fillStyle = valGradient; + valSliderCtx.fillRect(0, 0, w, h); + } + + return this; // Allows method chaining + }; + + + /** + * Function: updateSliders + * + * Introduced in 2.0 + * + * Update slider cursor positions based on this.color value. + * Only displayed sliders are updated. + */ + WCP.ColorPicker.prototype.updateSliders = function() { + + // Skip if not yet initialized + if(this.widget == null) + return this; + + var $widget = $(this.widget); + var color = this.color; + + // No need to redraw sliders on global popup widget if not + // attached to the input elm in current iteration + if(this != $widget.data('jQWCP.instance')) + return this; + + // Wheel + var $wheel = $widget.find('.jQWCP-wWheel'); + if(!$wheel.hasClass('hidden')) { + var $wheelCursor = $widget.find('.jQWCP-wWheelCursor'); + var $wheelOverlay = $widget.find('.jQWCP-wWheelOverlay'); + var wheelX = Math.cos(2 * Math.PI * color.h) * color.s; + var wheelY = Math.sin(2 * Math.PI * color.h) * color.s; + var wheelOffsetX = $wheel.width() / 2; + var wheelOffsetY = $wheel.height() / 2; + $wheelCursor.css('left', (wheelOffsetX + (wheelX * $wheel.width() / 2)) + 'px'); + $wheelCursor.css('top', (wheelOffsetY - (wheelY * $wheel.height() / 2)) + 'px'); + // Keep shading to 1 if preserveWheel is true (DEPRECATED) or live is true + if(this.options.preserveWheel == true || (this.options.preserveWheel == null && this.options.live == false)) { + $wheelOverlay.css('opacity', 0); + } + else { + $wheelOverlay.css('opacity', 1 - (color.v < 0.2 ? 0.2 : color.v)); + } + } + + // Hue + var $hueSlider = $widget.find('.jQWCP-wHueSlider'); + if(!$hueSlider.hasClass('hidden')) { + var $hueCursor = $widget.find('.jQWCP-wHueCursor'); + $hueCursor.css('top', (color.h * $hueSlider.height()) + 'px'); + } + + // Saturation + var $satSlider = $widget.find('.jQWCP-wSatSlider'); + if(!$satSlider.hasClass('hidden')) { + var $satCursor = $widget.find('.jQWCP-wSatCursor'); + $satCursor.css('top', ((1 - color.s) * $satSlider.height()) + 'px'); + } + + // Value + var $valSlider = $widget.find('.jQWCP-wValSlider'); + if(!$valSlider.hasClass('hidden')) { + var $valCursor = $widget.find('.jQWCP-wValCursor'); + $valCursor.css('top', ((1 - color.v) * $valSlider.height()) + 'px'); + } + + // Red + var $redSlider = $widget.find('.jQWCP-wRedSlider'); + if(!$redSlider.hasClass('hidden')) { + var $redCursor = $widget.find('.jQWCP-wRedCursor'); + $redCursor.css('top', ((1 - color.r) * $redSlider.height()) + 'px'); + } + + // Green + var $greenSlider = $widget.find('.jQWCP-wGreenSlider'); + if(!$greenSlider.hasClass('hidden')) { + var $greenCursor = $widget.find('.jQWCP-wGreenCursor'); + $greenCursor.css('top', ((1 - color.g) * $greenSlider.height()) + 'px'); + } + + // Blue + var $blueSlider = $widget.find('.jQWCP-wBlueSlider'); + if(!$blueSlider.hasClass('hidden')) { + var $blueCursor = $widget.find('.jQWCP-wBlueCursor'); + $blueCursor.css('top', ((1 - color.b) * $blueSlider.height()) + 'px'); + } + + // Alpha + var $alphaSlider = $widget.find('.jQWCP-wAlphaSlider'); + if(!$alphaSlider.hasClass('hidden')) { + var $alphaCursor = $widget.find('.jQWCP-wAlphaCursor'); + $alphaCursor.css('top', ((1 - color.a) * $alphaSlider.height()) + 'px'); + } + + return this; // Allows method chaining + }; + + + /** + * Function: updateSelection + * + * DEPRECATED by 2.0 + * + * Update color dialog selection to match current selectedColor value. + */ + WCP.ColorPicker.prototype.updateSelection = function() { + this.redrawSliders(); + this.updateSliders(); + return this; + }; + + + /** + * Function: updateInput + * + * Since 3.0 + * + * Update input value and background color (if preview is on) + */ + WCP.ColorPicker.prototype.updateInput = function() { + // Skip if not yet initialized + if(this.widget == null) + return this; + + var $input = $(this.input); + + // #13 only update if value is different to avoid cursor repositioned to the end of text on some browsers + if(this.input.value != this.getValue()) { + this.input.value = this.getValue(); + } + + if( this.options.preview ) { + $input.css('background', WCP.colorToStr( this.color, 'rgba' )); + if( this.color.v > .5 ) { + $input.css('color', 'black'); + } + else { + $input.css('color', 'white'); + } + } + }; + + + /** + * Function: updateActiveControl + * + * Move the active control. + */ + WCP.ColorPicker.prototype.updateActiveControl = function( e ) { + var $control = $( $('body').data('jQWCP.activeControl') ); // Refers to slider wrapper + + if($control.length == 0) + return; + + var $input = $(this.input); + var options = this.options; + var color = this.color; + + // pageX and pageY wrapper for touches + if(e.pageX == undefined && e.originalEvent.touches.length > 0) { + e.pageX = e.originalEvent.touches[0].pageX; + e.pageY = e.originalEvent.touches[0].pageY; + } + //$('#log').html(e.pageX + '/' + e.pageY); + + /// WHEEL CONTROL /// + if($control.hasClass('jQWCP-wWheel')) { + var $cursor = $control.find('.jQWCP-wWheelCursor'); + var $overlay = $control.find('.jQWCP-wWheelOverlay'); + + var relX = (e.pageX - $control.offset().left - ($control.width() / 2)) / ($control.width() / 2); + var relY = - (e.pageY - $control.offset().top - ($control.height() / 2)) / ($control.height() / 2); + + // BUG_RELATIVE_PAGE_ORIGIN workaround + if(WCP.BUG_RELATIVE_PAGE_ORIGIN) { + var relX = (e.pageX - ($control.get(0).getBoundingClientRect().left - WCP.ORIGIN.left) - ($control.width() / 2)) / ($control.width() / 2); + var relY = - (e.pageY - ($control.get(0).getBoundingClientRect().top - WCP.ORIGIN.top) - ($control.height() / 2)) / ($control.height() / 2); + } + + //console.log(relX + ' ' + relY); + + // Sat value is calculated from the distance of the cursor from the central point + var sat = Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2)); + // If distance is out of bound, reset to the upper bound + if(sat > 1) { + sat = 1; + } + + // Snap to 0,0 + if(options.snap && sat < options.snapTolerance) { + sat = 0; + } + + // Hue is calculated from the angle of the cursor. 0deg is set to the right, and increase counter-clockwise. + var hue = (relX == 0 && relY == 0) ? 0 : Math.atan( relY / relX ) / ( 2 * Math.PI ); + // If hue is negative, then fix the angle value (meaning angle is in either Q2 or Q4) + if( hue < 0 ) { + hue += 0.5; + } + // If y is negative, then fix the angle value (meaning angle is in either Q3 or Q4) + if( relY < 0 ) { + hue += 0.5; + } + + this.setHsv(hue, sat, color.v); + } + + /// SLIDER CONTROL /// + else if($control.hasClass('jQWCP-slider-wrapper')) { + var $cursor = $control.find('.jQWCP-scursor'); + + var relY = (e.pageY - $control.offset().top) / $control.height(); + + // BUG_RELATIVE_PAGE_ORIGIN workaround + if(WCP.BUG_RELATIVE_PAGE_ORIGIN) { + var relY = (e.pageY - ($control.get(0).getBoundingClientRect().top - WCP.ORIGIN.top)) / $control.height(); + } + + var value = relY < 0 ? 0 : relY > 1 ? 1 : relY; + + // Snap to 0.0, 0.5, and 1.0 + //console.log(value); + if(options.snap && value < options.snapTolerance) { + value = 0; + } + else if(options.snap && value > 1-options.snapTolerance) { + value = 1; + } + if(options.snap && value > 0.5-options.snapTolerance && value < 0.5+options.snapTolerance) { + value = 0.5; + } + + $cursor.css('top', (value * $control.height()) + 'px'); + + /// Update color value /// + // Red + if($control.hasClass('jQWCP-wRed')) { + this.setRgb(1-value, color.g, color.b); + } + // Green + if($control.hasClass('jQWCP-wGreen')) { + this.setRgb(color.r, 1-value, color.b); + } + // Blue + if($control.hasClass('jQWCP-wBlue')) { + this.setRgb(color.r, color.g, 1-value); + } + // Hue + if($control.hasClass('jQWCP-wHue')) { + this.setHsv(value, color.s, color.v); + } + // Saturation + if($control.hasClass('jQWCP-wSat')) { + this.setHsv(color.h, 1-value, color.v); + } + // Value + if($control.hasClass('jQWCP-wVal')) { + this.setHsv(color.h, color.s, 1-value); + } + // Alpha + if($control.hasClass('jQWCP-wAlpha')) { + this.setAlpha(1-value); + } + } + }; + + + /** + * Function: getColor + * + * Since 2.0 + * + * Return color components as an object. The object consists of: + * { + * r: red + * g: green + * b: blue + * h: hue + * s: saturation + * v: value + * a: alpha + * } + */ + WCP.ColorPicker.prototype.getColor = function() { + return this.color; + }; + + + /** + * Function: getValue + * + * Get the color value as string. + */ + WCP.ColorPicker.prototype.getValue = function( format ) { + var options = this.options; + + if( format == null ) { + format = options.format; + } + + // If settings.rounding is TRUE, round alpha value to N decimal digits + if(options.rounding >= 0) { + this.color.a = Math.round(this.color.a * Math.pow(10, options.rounding)) / Math.pow(10, options.rounding); + } + return WCP.colorToStr( this.color, format ); + }; + + + /** + * Function: setValue + * + * Set the color value as string. + * + * Parameters: + * value - String representation of color. + * updateInput - Whether to update input text. Default is true. + */ + WCP.ColorPicker.prototype.setValue = function( value, updateInput ) { + var color = WCP.strToColor(value); + if(!color) + return this; + + return this.setColor(color, updateInput); + } + + + /** + * Function: setColor + * + * Introduced in 2.0 + * + * Set color by passing an object consisting of: + * { r, g, b, a } or + * { h, s, v, a } + * + * For consistency with options.color value, this function also + * accepts string value. + * + * Parameters: + * color - Color object or string representation of color. + * updateInput - Whether to update input text. Default is true. + */ + WCP.ColorPicker.prototype.setColor = function( color, updateInput ) { + if(typeof color === "string") { + return this.setValue(color, updateInput); + } + else if(color.r != null) { + return this.setRgba(color.r, color.g, color.b, color.a, updateInput); + } + else if(color.h != null) { + return this.setHsva(color.h, color.s, color.v, color.a, updateInput); + } + else if(color.a != null) { + return this.setAlpha(color.a, updateInput); + } + return this; + }; + + + /** + * Function: setRgba + * + * Introduced in 2.0 + * + * Set color using RGBA combination. + * + * Parameters: + * r - Red component [0..1] + * g - Green component [0..1] + * b - Blue component [0..1] + * a - Alpha value [0..1] + * updateInput - Whether to update input text. Default is true. + */ + WCP.ColorPicker.prototype.setRgba = function( r, g, b, a, updateInput ) { + // Default value + if(updateInput === undefined) updateInput = true; + + var color = this.color; + color.r = r; + color.g = g; + color.b = b; + + if(a != null) { + color.a = a; + } + + var hsv = WCP.rgbToHsv(r, g, b); + color.h = hsv.h; + color.s = hsv.s; + color.v = hsv.v; + + this.updateSliders(); + this.redrawSliders(); + if(updateInput) { + this.updateInput(); + } + return this; + }; + + + /** + * Function: setRgb + * + * Introduced in 2.0 + * + * Set color using RGB combination. + * + * Parameters: + * r - Red component [0..1] + * g - Green component [0..1] + * b - Blue component [0..1] + * updateInput - Whether to update input text. + */ + WCP.ColorPicker.prototype.setRgb = function( r, g, b, updateInput ) { + return this.setRgba(r, g, b, null, updateInput); + }; + + + /** + * Function: setHsva + * + * Introduced in 2.0 + * + * Set color using HSVA combination. + * + * Parameters: + * h - Hue component [0..1] + * s - Saturation component [0..1] + * v - Value component [0..1] + * a - Alpha value [0..1] + * updateInput - Whether to update input text. Default is true. + */ + WCP.ColorPicker.prototype.setHsva = function( h, s, v, a, updateInput ) { + // Default value + if(updateInput === undefined) updateInput = true; + + var color = this.color; + color.h = h; + color.s = s; + color.v = v; + + if(a != null) { + color.a = a; + } + + var rgb = WCP.hsvToRgb(h, s, v); + color.r = rgb.r; + color.g = rgb.g; + color.b = rgb.b; + + this.updateSliders(); + this.redrawSliders(); + if(updateInput) { + this.updateInput(); + } + return this; + }; + + + /** + * Function: setHsv + * + * Introduced in 2.0 + * + * Set color using HSV combination. + * + * Parameters: + * h - Hue component [0..1] + * s - Saturation component [0..1] + * v - Value component [0..1] + * updateInput - Whether to update input text. + */ + WCP.ColorPicker.prototype.setHsv = function( h, s, v, updateInput ) { + return this.setHsva(h, s, v, null, updateInput); + }; + + + /** + * Function: setAlpha + * + * Introduced in 2.0 + * + * Set alpha value. + * + * Parameters: + * value - Alpha value [0..1] + * updateInput - Whether to update input text. Default is true. + */ + WCP.ColorPicker.prototype.setAlpha = function( value, updateInput ) { + // Default value + if(updateInput === undefined) updateInput = true; + + this.color.a = value; + + this.updateSliders(); + this.redrawSliders(); + if(updateInput) { + this.updateInput(); + } + return this; + }; + + + /** + * Function: show + * + * Show the color picker dialog. This function is only applicable to + * popup mode color picker layout. + */ + WCP.ColorPicker.prototype.show = function() { + var input = this.input; + var $input = $(input); // Refers to input elm + var $widget = $(this.widget); + var options = this.options; + + // Don't do anything if not using popup layout + if( options.layout != "popup" ) + return; + + // Don't do anything if the popup is already shown and attached + // to the correct input elm + //if( this == $widget.data('jQWCP.instance') ) + //return; + + // Attach instance to widget (because popup widget is global) + $widget.data('jQWCP.instance', this); + + // Terminate ongoing transitions + $widget.stop( true, true ); + + // Reposition the popup window + $widget.css({ + top: (input.getBoundingClientRect().top - WCP.ORIGIN.top + $input.outerHeight()) + 'px', + left: (input.getBoundingClientRect().left - WCP.ORIGIN.left) + 'px' + }); + + // Refresh widget with this instance's options + this.refreshWidget(); + + // Redraw sliders + this.redrawSliders(); + this.updateSliders(); + + // Store last textfield value (to determine whether to trigger onchange event later) + this.lastValue = input.value; + + $widget.fadeIn( options.animDuration ); + + // If hideKeyboard is true, force to hide soft keyboard + if(options.hideKeyboard) { + $input.blur(); + $(WCP.ColorPicker.overlay).show(); + } + + // On mobile layout, autoscroll page to keep input visible + if($widget.hasClass('jQWCP-mobile')) { + var scrollTop = $('html').scrollTop(); + var inputTop = input.getBoundingClientRect().top - WCP.ORIGIN.top; + + // If input is way too top + if(inputTop < scrollTop) { + $('html').animate({ scrollTop: inputTop}); + } + + // If input is way too bottom + else if(inputTop + $input.outerHeight() > scrollTop + window.innerHeight - $widget.outerHeight()) { + $('html').animate({ scrollTop: inputTop + $input.outerHeight() - window.innerHeight + $widget.outerHeight()}); + } + } + }; + + + + /** + * Function: hide + * + * Hide the color picker dialog. This function is only applicable to + * popup mode color picker layout. + */ + WCP.ColorPicker.prototype.hide = function() { + var $widget = $(this.widget); + + // Only hide if popup belongs to this instance + if(this != $widget.data('jQWCP.instance')) + return; + + $widget.fadeOut( this.options.animDuration ); + $(WCP.ColorPicker.overlay).hide(); + }; + + + + //////////////////// + // Event Handlers // + //////////////////// + + WCP.Handler = {}; + + /** + * input.onFocus.popup + */ + WCP.Handler.input_focus_popup = function( e ) { + var instance = $(this).data('jQWCP.instance'); + instance.show(); + + // Workaround to prevent on screen keyboard from appearing + if($(this).attr('readonly') == null) { + $(this).attr('readonly', true); + setTimeout(function() { + $(instance.input).removeAttr('readonly'); + }); + + // Firefox on Android + if(navigator.userAgent.match(/Android .* Firefox/) != null) { + setTimeout(function() { + $(instance.input).attr('readonly', true); + $(instance.input).one('blur', function() { + $(instance.input).removeAttr('readonly'); + }); + }); + } + } + }; + + + /** + * input.onBlur.popup + * + * onBlur event handler for popup layout. + */ + WCP.Handler.input_blur_popup = function( e ) { + var instance = $(this).data('jQWCP.instance'); + + // If keyboard is hidden, input is always blurred so + // no point in hiding + if(instance.options.hideKeyboard) + return; + + instance.hide(); + + // Trigger 'change' event only when it was modified by widget + // because user typing on the textfield will automatically + // trigger 'change' event on blur. + if(instance.lastValue != this.value) { + $(this).trigger('change'); + } + }; + + + /** + * input.onKeyUp + * + * Update the color picker while user type in color value. + */ + WCP.Handler.input_keyup = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var color = WCP.strToColor(this.value); + if(color) { + instance.setColor(color, false); + } + }; + + + /** + * input.onChange + * + * Reformat the input value based on selected color and configurations. + */ + WCP.Handler.input_change = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var color = WCP.strToColor(this.value); + + // If autoFormat option is enabled, try to reformat the value if it is a valid color + if(instance.options.autoFormat && color) { + instance.setColor(color, true); + } + + // If validate option is enabled, revert the value if it is not a valid color + else if(instance.options.validate && !color && this.value != '') { + this.value = instance.getValue(); + } + }; + + + /** + * widget.onFocus.block + * + * Prepare runtime widget data + */ + WCP.Handler.widget_focus_block = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var $input = $(instance.input); + + // Store last textfield value + instance.lastValue = instance.input.value; + + // Trigger focus event + $input.triggerHandler('focus'); + }; + + + /** + * widget.onMouseDown.popup + * + * Prevent loss focus of the input causing the dialog to be hidden + * because of input blur event. + */ + WCP.Handler.widget_mousedown_popup = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var $input = $(instance.input); + + // Temporarily unbind blur and focus event until mouse is released + $input.off('focus.wheelColorPicker'); + $input.off('blur.wheelColorPicker'); + + // Temporarily unbind all blur events until mouse is released + // data('events') is deprecated since jquery 1.8 + if($input.data('events') != undefined) { + var blurEvents = $input.data('events').blur; + } + else { + var blurEvents = undefined; + } + var suspendedEvents = { blur: [] }; + //suspendedEvents.blur = blurEvents; + //$input.off('blur'); + if(blurEvents != undefined) { + for(var i = 0; i < blurEvents.length; i++) { + suspendedEvents.blur.push(blurEvents[i]); + //suspendedEvents.blur['blur' + (blurEvents[i].namespace != '' ? blurEvents[i].namespace : '')] = blurEvents[i].handler; + } + } + $input.data('jQWCP.suspendedEvents', suspendedEvents); + //console.log(blurEvents); + //console.log($input.data('jQWCP.suspendedEvents')); + }; + + /** + * widget.onMouseUp + * + * Re-bind events that was unbound by widget_mousedown_popup. + */ + /*WCP.Handler.widget_mouseup_popup = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var $input = $(instance.input); + + //Input elm must always be focused, unless hideKeyboard is set to true + if(!instance.options.hideKeyboard) { + $input.trigger('focus.jQWCP_DONT_TRIGGER_EVENTS'); // This allow input to be focused without triggering events + } + + //Rebind blur & focusevent + $input.on('focus.wheelColorPicker', WCP.Handler.input_focus_popup); + $input.on('blur.wheelColorPicker', WCP.Handler.input_blur_popup); + + };*/ + + + + /** + * widget.onBlur + * + * Try to trigger onChange event if value has been changed. + */ + WCP.Handler.widget_blur_block = function( e ) { + var instance = $(this).data('jQWCP.instance'); + var $input = $(instance.input); + + // Trigger 'change' event only when it was modified by widget + // because user typing on the textfield will automatically + // trigger 'change' event on blur. + if(instance.lastValue != instance.input.value) { + $input.trigger('change'); + } + + // Trigger blur event + $input.triggerHandler('blur'); + }; + + + /** + * wheelCursor.onMouseDown + * + * Begin clicking the wheel down. This will allow user to move + * the crosshair although the mouse is outside the wheel. + */ + WCP.Handler.wheelCursor_mousedown = function( e ) { + var $this = $(this); // Refers to cursor + var $widget = $this.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + $('body').data('jQWCP.activeControl', $this.parent().get(0)); + + // Trigger sliderdown event + $input.trigger('sliderdown'); + }; + + + /** + * wheel.onMouseDown + * + * Begin clicking the wheel down. This will allow user to move + * the crosshair although the mouse is outside the wheel. + * + * Basically this is the same as wheelCursor_mousedown handler + */ + WCP.Handler.wheel_mousedown = function( e ) { + var $this = $(this); // Refers to wheel + var $widget = $this.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + $('body').data('jQWCP.activeControl', $this.get(0)); + + // Trigger sliderdown event + $input.trigger('sliderdown'); + }; + + + /** + * slider.onMouseDown + * + * Begin clicking the slider down. This will allow user to move + * the slider although the mouse is outside the slider. + */ + WCP.Handler.slider_mousedown = function( e ) { + var $this = $(this); // Refers to slider + var $widget = $this.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + $('body').data('jQWCP.activeControl', $this.parent().get(0)); + + // Trigger sliderdown event + $input.trigger('sliderdown'); + }; + + /** + * sliderCursor.onMouseDown + * + * Begin clicking the slider down. This will allow user to move + * the slider although the mouse is outside the slider. + */ + WCP.Handler.sliderCursor_mousedown = function( e ) { + var $this = $(this); // Refers to slider cursor + var $widget = $this.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + $('body').data('jQWCP.activeControl', $this.parent().get(0)); + + // Trigger sliderdown event + $input.trigger('sliderdown'); + }; + + + + /** + * html.onMouseUp + * + * Clear active control reference. + * Also do cleanups after widget.onMouseDown.popup + * + * Note: This event handler is also applied to touchend + */ + WCP.Handler.html_mouseup = function( e ) { + var $control = $( $('body').data('jQWCP.activeControl') ); // Refers to slider wrapper or wheel + + // Do stuffs when there's active control + if($control.length == 0) + return; + + var $widget = $control.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + + // Rebind blur and focus event to input elm which was + // temporarily released when popup dialog is shown + if(instance.options.layout == 'popup') { + // Focus first before binding event so it wont get fired + // Input elm must always be focused, unless hideKeyboard is set to true + if(!instance.options.hideKeyboard) { + $input.trigger('focus.jQWCP_DONT_TRIGGER_EVENTS'); // This allow input to be focused without triggering events + } + + // Rebind blur & focusevent + $input.on('focus.wheelColorPicker', WCP.Handler.input_focus_popup); + $input.on('blur.wheelColorPicker', WCP.Handler.input_blur_popup); + + // Rebind suspended events + var suspendedEvents = $input.data('jQWCP.suspendedEvents'); + if(suspendedEvents != undefined) { + var blurEvents = suspendedEvents.blur; + for(var i = 0; i < blurEvents.length; i++) { + $input.on('blur' + (blurEvents[i].namespace == '' ? '' : '.' + blurEvents[i].namespace), blurEvents[i].handler); + } + } + } + + + // Update active control + if($control.length != 0) { + // Last time update active control before clearing + // Only call this function if mouse position is known + // On touch action, touch point is not available + if(e.pageX != undefined) { + instance.updateActiveControl( e ); + } + + // Clear active control reference + $('body').data('jQWCP.activeControl', null); + + // Trigger sliderup event + $input.trigger('sliderup'); + } + }; + + + /** + * html.onMouseMove + * + * Move the active slider (when mouse click is down). + * + * Note: This event handler is also applied to touchmove + */ + WCP.Handler.html_mousemove = function( e ) { + var $control = $( $('body').data('jQWCP.activeControl') ); // Refers to slider wrapper or wheel + + // Do stuffs when there's active control + if($control.length == 0) + return; + + // If active, prevent default + e.preventDefault(); + + var $widget = $control.closest('.jQWCP-wWidget'); + var instance = $widget.data('jQWCP.instance'); + var $input = $(instance.input); + + instance.updateActiveControl( e ); + + // Trigger slidermove event + $input.trigger('slidermove'); + + return false; + }; + + + /** + * window.onResize + * + * Adjust block widgets + */ + WCP.Handler.window_resize = function( e ) { + var $widgets = $('body .jQWCP-wWidget.jQWCP-block'); + + $widgets.each(function() { + var instance = $(this).data('jQWCP.instance'); + instance.refreshWidget(); + instance.redrawSliders(); + }); + }; + + + /** + * overlay.onClick + * + * Hide colorpicker popup dialog if overlay is clicked. + * This has the same effect as blurring input element if hideKeyboard = false. + */ + WCP.Handler.overlay_click = function( e ) { + if(WCP.ColorPicker.widget == null) + return; + + var $widget = $(WCP.ColorPicker.widget); + var instance = $widget.data('jQWCP.instance'); + + // If no instance set, do nothing + if(instance != null) { + var $input = $(instance.input); + + // Trigger 'change' event only when it was modified by widget + // because user typing on the textfield will automatically + // trigger 'change' event on blur. + if(instance.lastValue != instance.input.value) { + $input.trigger('change'); + } + + instance.hide(); + } + }; + + /******************************************************************/ + + //////////////////////////////////////////////////////// + // Automatically initialize color picker on page load // + // for elements with data-wheelcolorpicker attribute. // + //////////////////////////////////////////////////////// + + $(document).ready(function() { + $('[data-wheelcolorpicker]').wheelColorPicker({ htmlOptions: true }); + }); + + + + /******************************************************************/ + + ////////////////////////////////// + // Browser specific workarounds // + ////////////////////////////////// + + (function() { + // MOZILLA // + + // Force low resolution slider canvases to improve performance + // Note: Do not rely on $.browser since it's obsolete by jQuery 2.x + if($.browser != undefined && $.browser.mozilla) { + $.fn.wheelColorPicker.defaults.quality = 0.2; + } + + // MOBILE CHROME // + + // BUG_RELATIVE_PAGE_ORIGIN + // Calibrate the coordinate of top left point of the page + // On mobile chrome, the top left of the page is not always set at (0,0) + // making window.scrollX/Y and $.offset() useless + $(document).ready(function() { + $('body').append( + '
' + ); + + var origin = document.getElementById('jQWCP-PageOrigin').getBoundingClientRect(); + WCP.ORIGIN = origin; + + $(window).on('scroll.jQWCP_RelativePageOriginBugFix', function() { + var origin = document.getElementById('jQWCP-PageOrigin').getBoundingClientRect(); + WCP.ORIGIN = origin; + if(origin.left != 0 || origin.top != 0) { + WCP.BUG_RELATIVE_PAGE_ORIGIN = true; + } + }); + }); + + })(); + +}) (jQuery); diff --git a/models/admin/management/events.php b/models/admin/management/events.php index 3a487e4..a573709 100644 --- a/models/admin/management/events.php +++ b/models/admin/management/events.php @@ -334,6 +334,21 @@ class GlmMembersAdmin_management_events extends GlmDataEventsManagement default: + wp_enqueue_script( + 'wheel-color-picker', + GLM_MEMBERS_EVENTS_PLUGIN_URL . '/js/jquery.wheelcolorpicker.js', + array( 'jquery' ), + '1.0.0', + false + ); + wp_enqueue_style( + 'wheel-color-picker-style', + GLM_MEMBERS_EVENTS_PLUGIN_URL . '/css/wheelcolorpicker.css', + array(), + '1.0.0', + false + ); + // Make sure option is set if default $option = 'settings'; diff --git a/setup/databaseScripts/create_database_V0.1.7.sql b/setup/databaseScripts/create_database_V0.1.8.sql similarity index 95% rename from setup/databaseScripts/create_database_V0.1.7.sql rename to setup/databaseScripts/create_database_V0.1.8.sql index 4e25ecb..ec852b8 100644 --- a/setup/databaseScripts/create_database_V0.1.7.sql +++ b/setup/databaseScripts/create_database_V0.1.8.sql @@ -232,6 +232,14 @@ CREATE TABLE {prefix}management ( ical_feed_image_size TINYTEXT NULL, -- Image size to use in iCal Feed event_display_member_message BOOLEAN DEFAULT '0', -- Boolean to show member message or not event_member_message TEXT NULL, -- Member Message + event_add_button_color TINYTEXT NULL, -- Color of the search button + event_add_button_hidden BOOLEAN NULL, -- Option to hide the add event button + event_back_to_search_color TINYTEXT NULL, -- Background Color of the search + agenda_date_background_color TINYTEXT NULL, -- Background Color of the date + agenda_date_text_color TINYTEXT NULL, -- Text Color of the date + agenda_title_color TINYTEXT NULL, -- Color of the Event Title + agenda_container_background_color TINYTEXT NULL, -- Event Container Background Color + agenda_view_max_width TINYTEXT NULL, -- Max Width of agenda view PRIMARY KEY (id) ); diff --git a/setup/databaseScripts/dbVersions.php b/setup/databaseScripts/dbVersions.php index 5be8b57..fea850f 100644 --- a/setup/databaseScripts/dbVersions.php +++ b/setup/databaseScripts/dbVersions.php @@ -43,5 +43,6 @@ $glmMembersEventsDbVersions = array( '0.1.5' => array('version' => '0.1.5', 'tables' => 13, 'date' => '02/21/2018'), '0.1.6' => array('version' => '0.1.6', 'tables' => 13, 'date' => '08/06/2018'), '0.1.7' => array('version' => '0.1.7', 'tables' => 13, 'date' => '09/14/2018'), + '0.1.8' => array('version' => '0.1.8', 'tables' => 13, 'date' => '10/09/2018'), ); diff --git a/setup/databaseScripts/steve-V0.1.8.sql b/setup/databaseScripts/steve-V0.1.8.sql deleted file mode 100644 index e69de29..0000000 diff --git a/setup/databaseScripts/update_database_V0.1.8.sql b/setup/databaseScripts/update_database_V0.1.8.sql new file mode 100644 index 0000000..ecb3ccc --- /dev/null +++ b/setup/databaseScripts/update_database_V0.1.8.sql @@ -0,0 +1,41 @@ +-- Gaslight Media Members Database - Events Add-On +-- File Created: 09/14/18 +-- Database Version: 0.1.7 +-- Database Update From Previous Version Script +-- +-- To permit each query below to be executed separately, +-- all queries must be separated by a line with four dashes + +ALTER TABLE {prefix}management ADD COLUMN event_add_button_color TINYTEXT NULL; -- Color of the search button + +---- + +ALTER TABLE {prefix}management ADD COLUMN event_add_button_hidden BOOLEAN NULL; -- Option to hide the add event button + +---- + +ALTER TABLE {prefix}management ADD COLUMN event_back_to_search_color TINYTEXT NULL; -- Background Color of the search + +---- + +ALTER TABLE {prefix}management ADD COLUMN agenda_date_background_color TINYTEXT NULL; -- Background Color of the date + +---- + +ALTER TABLE {prefix}management ADD COLUMN agenda_date_text_color TINYTEXT NULL; -- Text Color of the date + +---- + +ALTER TABLE {prefix}management ADD COLUMN agenda_title_color TINYTEXT NULL; -- Color of the Event Title + +---- + +ALTER TABLE {prefix}management ADD COLUMN agenda_container_background_color TINYTEXT NULL; -- Event Container Background Color + +---- + +ALTER TABLE {prefix}management ADD COLUMN agenda_view_max_width TINYTEXT NULL; -- Max Width of agenda view + +---- + +UPDATE {prefix}management SET event_add_button_hidden = false; diff --git a/views/admin/management/events.html b/views/admin/management/events.html index a446ed8..614faaa 100644 --- a/views/admin/management/events.html +++ b/views/admin/management/events.html @@ -72,6 +72,57 @@ + + Front-End Event Options + +

+ + + + Front-End View Styles + + + + + + + + + + + + + + + + + + + + + + +
Max Width for Agenda View: + + + +
Event Date Background Color: + +
Event Date Text Color: + +
Event Title Color: + +
Event Container Background Color: + +
+ + +

{$terms.term_member_cap} Message

This will display a message to your members when they add or edit one of their events.

@@ -449,6 +500,11 @@ }); + /** + * Setup the color pickers. + */ + $('.colorpicker').wheelColorPicker(); + // Flash certain elements for a short time after display $(".glm-flash-updated").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500); diff --git a/views/front/events/agenda.html b/views/front/events/agenda.html index f239435..d11a605 100644 --- a/views/front/events/agenda.html +++ b/views/front/events/agenda.html @@ -1,376 +1,416 @@ + +
-
- {include file='front/events/searchForm.html'} -
+
+ {include file='front/events/searchForm.html'} +
+ +
+
+ {foreach $eventsByDate as $date => $key} +
+
+
+
+ {$date|date_format:"%A %B %e"} +
-
-
- {foreach $eventsByDate as $date => $key} -
-
-
-
{$date|date_format:"%b"}
-
- {$date|date_format:"%e"} - {$date|date_format:"%a"}
-
-
- {foreach $key as $events} - {foreach $events as $event} - {$showTime = true} -
-
-

{$event.name}

- {$eventDateTime = $event.starting_date|date_format:"%I:%M %P"} - {foreach $event.recurrences as $rec} - {$start_time = $rec.start_time.time} - {if $start_time == $eventDateTime} - {if ($rec.from_date.timestamp == $rec.to_date.timestamp && $rec.from_date.timestamp == $date) - or ($date <= $rec.to_date.timestamp && $date >= $rec.from_date.timestamp )} -
- {if !$event.all_day} - {$showTime = false} - {$event_start_time = $event.starting_date|date_format:"%m/%d/%Y"} - {$event_start_time = $event_start_time|cat:' '} - {$event_start_time = $event_start_time|cat:$rec.start_time.time} - {$event_end_time = $event.starting_date|date_format:"%m/%d/%Y"} - {$event_end_time = $event_end_time|cat:' '} - {$event_end_time = $event_end_time|cat:$rec.end_time.time} - {if $event_start_time|date_format:"%l:%M %P" == $event_end_time|date_format:"%l:%M %P" or $rec.start_time_only.value == '1'} - {$event_start_time|date_format:"%l:%M %P"} - {else} - {$event_start_time|date_format:"%l:%M %P"} - {$event_end_time|date_format:"%l:%M %P"} +
+ {foreach $key as $events} + {foreach $events as $event} + {$showTime = true} +
+
+ + +

{$event.name}

+
+ + {$eventDateTime = $event.starting_date|date_format:"%I:%M %P"} + {foreach $event.recurrences as $rec} + {$start_time = $rec.start_time.time} + {if $start_time == $eventDateTime} + {if ($rec.from_date.timestamp == $rec.to_date.timestamp && $rec.from_date.timestamp == $date) + or ($date <= $rec.to_date.timestamp && $date >= $rec.from_date.timestamp )} +
+ {if !$event.all_day} + {$showTime = false} + {if $event.starting_date|date_format:"%l:%M %P" == $event.ending_date|date_format:"%l:%M %P"} + {$event.starting_date|date_format:"%l:%M %P"} + {else} + {$event.starting_date|date_format:"%l:%M %P"} - {$event.ending_date|date_format:"%l:%M %P"} + {/if} + + {/if} + {if $rec.name && $rec.name != 'Imported' && $rec.name != 'Imported Event Schedule' } + {$rec.name} {/if} + ( {if $event.times|@count > 1} Occurring {/if} + {$rec.from_date.date} + {if $rec.from_date.date != $rec.to_date.date} + - {$rec.to_date.date} + {else if $rec.specific_dates} + - {$rec.specific_dates|@end|date_format:"%m/%d/%Y"} + {/if} + + + {if $rec.day_of_week.names|@count < 7 && $rec.day_of_week.names|@count > 0 } + + on + {foreach $rec.day_of_week.names as $day} + {if $day == "Thursday"} + {$day|substr:0:4} + {else} + {$day|substr:0:3} + {/if} + {/foreach} + {/if} - {if $rec.name && $rec.name != 'Imported' && $rec.name != 'Imported Event Schedule' } - {$rec.name} + ) +
+ {if $event.locations.city.name} +
City: {$event.locations.city.name}
{/if} - ( {if $event.times|@count > 1} Occurring {/if} - {$rec.from_date.date} - {if $rec.from_date.date != $rec.to_date.date} - - {$rec.to_date.date} - {else if $rec.specific_dates} - - {$rec.specific_dates|@end|date_format:"%m/%d/%Y"} - {/if} - - - {if $rec.day_of_week.names|@count < 7 && $rec.day_of_week.names|@count > 0 } - - on - {foreach $rec.day_of_week.names as $day} - {if $day == "Thursday"} - {$day|substr:0:4} - {else} - {$day|substr:0:3} - {/if} - {/foreach} - - {/if} - ) -
+ {/if} + {/foreach} +
+ {if !$event.all_day && $showTime} + {if $event.starting_date|date_format:"%l:%M %P" == $event.ending_date|date_format:"%l:%M %P"} +
{$event.starting_date|date_format:"%l:%M %P"}
+ {else} +
{$event.starting_date|date_format:"%l:%M %P"} - {$event.ending_date|date_format:"%l:%M %P"}
{/if} {/if} - {/foreach} -
- {if !$event.all_day && $showTime} - {if $event.starting_date|date_format:"%l:%M %P" == $event.ending_date|date_format:"%l:%M %P"} -
{$event.starting_date|date_format:"%l:%M %P"}
- {else} -
{$event.starting_date|date_format:"%l:%M %P"} - {$event.ending_date|date_format:"%l:%M %P"}
+
+
+ {if $event.locations} +
Location: {$event.locations.name}
{/if} - {/if} -
-
- {if $event.image}{/if} - {$event.intro} -
- Read More + + {$event.intro} +
+
+ {if $event.image}{/if} +
-
+ {/foreach} {/foreach} - {/foreach} +
-
- {/foreach} + {/foreach} +
-
- {assign var="current_year" value=$smarty.now|date_format:"%Y"} - {assign var="current_month" value=$smarty.now|date_format:"%m"} -
-
- -
-
-
+ +
+ +
+
+
+
-
- - - - - }); - diff --git a/views/front/events/agenda_old.html b/views/front/events/agenda_old.html new file mode 100644 index 0000000..f239435 --- /dev/null +++ b/views/front/events/agenda_old.html @@ -0,0 +1,376 @@ +
+
+ {include file='front/events/searchForm.html'} +
+ +
+
+ {foreach $eventsByDate as $date => $key} +
+
+
+
{$date|date_format:"%b"}
+
+ {$date|date_format:"%e"} + {$date|date_format:"%a"} +
+
+
+
+ {foreach $key as $events} + {foreach $events as $event} + {$showTime = true} +
+
+

{$event.name}

+ {$eventDateTime = $event.starting_date|date_format:"%I:%M %P"} + {foreach $event.recurrences as $rec} + {$start_time = $rec.start_time.time} + {if $start_time == $eventDateTime} + {if ($rec.from_date.timestamp == $rec.to_date.timestamp && $rec.from_date.timestamp == $date) + or ($date <= $rec.to_date.timestamp && $date >= $rec.from_date.timestamp )} +
+ {if !$event.all_day} + {$showTime = false} + {$event_start_time = $event.starting_date|date_format:"%m/%d/%Y"} + {$event_start_time = $event_start_time|cat:' '} + {$event_start_time = $event_start_time|cat:$rec.start_time.time} + {$event_end_time = $event.starting_date|date_format:"%m/%d/%Y"} + {$event_end_time = $event_end_time|cat:' '} + {$event_end_time = $event_end_time|cat:$rec.end_time.time} + {if $event_start_time|date_format:"%l:%M %P" == $event_end_time|date_format:"%l:%M %P" or $rec.start_time_only.value == '1'} + {$event_start_time|date_format:"%l:%M %P"} + {else} + {$event_start_time|date_format:"%l:%M %P"} - {$event_end_time|date_format:"%l:%M %P"} + {/if} + + {/if} + {if $rec.name && $rec.name != 'Imported' && $rec.name != 'Imported Event Schedule' } + {$rec.name} + {/if} + ( {if $event.times|@count > 1} Occurring {/if} + {$rec.from_date.date} + {if $rec.from_date.date != $rec.to_date.date} + - {$rec.to_date.date} + {else if $rec.specific_dates} + - {$rec.specific_dates|@end|date_format:"%m/%d/%Y"} + {/if} + + + {if $rec.day_of_week.names|@count < 7 && $rec.day_of_week.names|@count > 0 } + + on + {foreach $rec.day_of_week.names as $day} + {if $day == "Thursday"} + {$day|substr:0:4} + {else} + {$day|substr:0:3} + {/if} + {/foreach} + + + {/if} + ) +
+ {/if} + {/if} + {/foreach} +
+ {if !$event.all_day && $showTime} + {if $event.starting_date|date_format:"%l:%M %P" == $event.ending_date|date_format:"%l:%M %P"} +
{$event.starting_date|date_format:"%l:%M %P"}
+ {else} +
{$event.starting_date|date_format:"%l:%M %P"} - {$event.ending_date|date_format:"%l:%M %P"}
+ {/if} + {/if} +
+
+ {if $event.image}{/if} + {$event.intro} +
+ Read More +
+
+
+
+ {/foreach} + {/foreach} +
+
+ {/foreach} + +
+
+ {assign var="current_year" value=$smarty.now|date_format:"%Y"} + {assign var="current_month" value=$smarty.now|date_format:"%m"} +
+
+ +
+
+
+
+
+
+ + + + -- 2.17.1