1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates a color picker displaying "Web safe" colors.
 27  * @constructor
 28  * @class
 29  * Instances of this class may be
 30  * used with {@link DwtMenu} to create a {@link DwtColorPicker} menu. Clicking on a color cell generates a
 31  * DwtSelectionEvent the detail attribute of which contains the color string associated
 32  * the cell on which the user clicked
 33  *
 34  *
 35  * @author Ross Dargahi
 36  * 
 37  * @param {hash}	params		a hash of parameters
 38  * @param {DwtComposite} params.parent		the parent widget
 39  * @param {string}       params.className	a CSS class
 40  * @param {constant}     params.posStyle	the positioning style
 41  * @param {boolean}      params.hideNoFill  True to hide the no-fill/use-default option
 42  * @param {string}       params.noFillLabel			the no-fill label
 43  * @param {boolean}      params.allowColorInput		if <code>true</code>, allow a text field to allow user to input their customized RGB value
 44  * @param {string}       params.defaultColor Default color.
 45  * 
 46  * @extends		DwtControl
 47  */
 48 DwtColorPicker = function(params) {
 49 	if (arguments.length == 0) return;
 50     params = Dwt.getParams(arguments, DwtColorPicker.PARAMS);
 51 
 52 	params.className = params.className || "DwtColorPicker";
 53 	DwtComposite.call(this, params);
 54 
 55     this._hideNoFill = params.hideNoFill;
 56 	this._noFillLabel = params.noFillLabel;
 57     this._allowColorInput = params.allowColorInput;
 58     this._defaultColor = params.defaultColor || "#000000";
 59     this._createHtml();
 60 };
 61 
 62 DwtColorPicker.prototype = new DwtComposite;
 63 DwtColorPicker.prototype.constructor = DwtColorPicker;
 64 
 65 DwtColorPicker.prototype.toString = function() {
 66 	return "DwtColorPicker";
 67 };
 68 
 69 DwtColorPicker.PARAMS = ["parent", "className", "posStyle", "noFillLabel", "allowColorInput", "defaultColor"];
 70 
 71 //
 72 // Constants
 73 //
 74 
 75 // RE to parse out components out of a "rgb(r, g, b);" string
 76 DwtColorPicker._RGB_RE = /^rgb\(([0-9]{1,3}),\s*([0-9]{1,3}),\s*([0-9]{1,3})\)$/;
 77 DwtColorPicker._HEX_RE = /^\#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
 78 
 79 //
 80 // Data
 81 //
 82 
 83 DwtColorPicker.prototype.TEMPLATE = "dwt.Widgets#DwtColorPicker";
 84 
 85 //
 86 // Public methods
 87 //
 88 
 89 /**
 90  * Adds a listener to be notified when the button is pressed.
 91  *
 92  * @param {AjxListener}	listener	a listener
 93  */
 94 DwtColorPicker.prototype.addSelectionListener = 
 95 function(listener) {
 96 	this.addListener(DwtEvent.SELECTION, listener);
 97 };
 98 
 99 /**
100  * Removes a selection listener.
101  *
102  * @param {AjxListener}	listener	a listener
103  */
104 DwtColorPicker.prototype.removeSelectionListener = 
105 function(listener) { 
106 	this.removeListener(DwtEvent.SELECTION, listener);
107 };
108 
109 DwtColorPicker.prototype.dispose = 
110 function () {
111 	if (this._disposed) { return; }
112 	DwtControl.prototype.dispose.call(this);
113 };
114 
115 DwtColorPicker.prototype._createHtml = function(templateId) {
116     this._createHtmlFromTemplate(templateId||this.TEMPLATE, {id:this.getHtmlElement().id});
117 };
118 
119 DwtColorPicker.prototype._createHtmlFromTemplate = function(templateId, data) {
120     data.allowColorInput = this._allowColorInput;
121     data.hideNoFill = this._hideNoFill;
122     data.noFillLabel = this._noFillLabel;
123     DwtComposite.prototype._createHtmlFromTemplate.apply(this, arguments);
124 
125     // create controls
126     if (data.allowColorInput) {
127         var inputEl = document.getElementById(data.id+"_input");
128 	var inputParams = {
129 		parent: this,
130 		validationStyle: DwtInputField.CONTINUAL_VALIDATION, //update the preview for each key up 
131 		errorIconStyle: DwtInputField.ERROR_ICON_RIGHT, 
132 		validator: DwtColorPicker.__isValidInputValue
133 	};
134         var input = this._colorInput = new DwtInputField(inputParams);
135         input.replaceElement(inputEl);
136 	// Add  callback for update the preview when the input value is validated.
137 	var updateCallback = new AjxCallback(this, this._updatePreview);
138 	input.setValidationCallback(updateCallback);
139 	
140 	var error = this._error = new DwtLabel({parent:this});
141 	var errorEl = document.getElementById(data.id+"_error");
142         error.replaceElement(errorEl);
143         error.setVisible(false);
144         
145 	this._preview = document.getElementById(data.id+"_preview");
146         
147 	var buttonEl = document.getElementById(data.id+"_button");
148         var button = new DwtButton({parent:this});
149         button.setText(AjxMsg.setColor);
150         button.replaceElement(buttonEl);
151         button.addSelectionListener(new AjxListener(this, this._handleSetColor));
152     }
153 
154     var buttonEl = document.getElementById(data.id+"_default");
155     if (buttonEl) {
156         if (!DwtColorPicker.Button) {
157             DwtColorPicker.__defineClasses();
158         }
159         var button = this._defaultColorButton = new DwtColorPicker.Button({parent:this});
160         button.setText(data.noFillLabel || AjxMsg.colorsUseDefault);
161         button.replaceElement(buttonEl);
162         button.addSelectionListener(new AjxListener(this, this._handleColorSelect, [0]));
163     }
164 
165     // set color handlers
166     var colorsEl = document.getElementById(data.id+"_colors");
167     var mouseOver = AjxEnv.isIE ? DwtEvent.ONMOUSEENTER : DwtEvent.ONMOUSEOVER;
168     var mouseOut  = AjxEnv.isIE ? DwtEvent.ONMOUSELEAVE : DwtEvent.ONMOUSEOUT;
169 
170     Dwt.setHandler(colorsEl, DwtEvent.ONMOUSEDOWN, AjxCallback.simpleClosure(this._handleMouseDown, this));
171     Dwt.setHandler(colorsEl, DwtEvent.ONMOUSEUP, AjxCallback.simpleClosure(this._handleMouseUp, this));
172     Dwt.setHandler(colorsEl, mouseOver, AjxCallback.simpleClosure(this._handleMouseOver, this));
173     Dwt.setHandler(colorsEl, mouseOut, AjxCallback.simpleClosure(this._handleMouseOut, this));
174 };
175 
176 DwtColorPicker.prototype._handleMouseOver = function(htmlEvent) {
177     var event = DwtUiEvent.getEvent(htmlEvent);
178     var target = DwtUiEvent.getTarget(event);
179     if (!Dwt.hasClass(target, "Color")) return;
180 
181     this._handleMouseOut(htmlEvent);
182     Dwt.addClass(target, DwtControl.HOVER);
183     this._mouseOverEl = target;
184 };
185 
186 DwtColorPicker.prototype._handleMouseOut = function(htmlEvent) {
187     if (this._mouseOverEl) {
188         Dwt.delClass(this._mouseOverEl, DwtControl.HOVER);
189     }
190     this._mouseOverEl = null;
191 };
192 
193 DwtColorPicker.prototype._handleMouseDown = function(htmlEvent) {
194     var event = DwtUiEvent.getEvent(htmlEvent);
195     var target = DwtUiEvent.getTarget(event);
196     this._mouseDownEl = Dwt.hasClass(target, "Color") ? target : null;
197 };
198 DwtColorPicker.prototype._handleMouseUp = function(htmlEvent) {
199     var event = DwtUiEvent.getEvent(htmlEvent);
200     var target = DwtUiEvent.getTarget(event);
201     if (this._mouseDownEl != target) return;
202 
203     var cssColor = DwtCssStyle.getProperty(target, "background-color");
204     this._handleColorSelect(DwtColorPicker.__color2hex(cssColor));
205 };
206 
207 DwtColorPicker.prototype._handleSetColor = function(evt) {
208     var color = this._colorInput.getValue();
209     if (color) {
210 	color = DwtColorPicker.__color2hex(color);
211 	if(!color) 
212 		return; 
213     	this._handleColorSelect(color);
214     }
215 };
216 
217 DwtColorPicker.prototype._handleColorSelect = function(color) {
218     this._inputColor = color;
219 
220     // If our parent is a menu then we need to have it close
221     if (this.parent instanceof DwtMenu) {
222         DwtMenu.closeActiveMenu();
223     }
224 
225     // Call Listeners on mouseEv.target.id
226     if (this.isListenerRegistered(DwtEvent.SELECTION)) {
227         var selEvent = DwtShell.selectionEvent;
228 //        DwtUiEvent.copy(selEvent, htmlEvent);
229         selEvent.item = this;
230         selEvent.detail = this._inputColor;
231         this.notifyListeners(DwtEvent.SELECTION, selEvent);
232     }
233 };
234 
235 /**
236  * Gets the input color.
237  * 
238  * @return	{string}	the color (in hex) from the input color field
239  */
240 DwtColorPicker.prototype.getInputColor = function () {
241     return this._inputColor;
242 };
243 
244 DwtColorPicker.prototype.setDefaultColor = function (color) {
245     if(this._defaultColorButton) {
246         this._defaultColorButton.setDefaultColor(color);
247     }
248 };
249 
250 DwtColorPicker.__color2hex = function(s) {
251 	//in IE we can't get the calculated value so for white/black we get white/black (of course it could be set the the hex value in the markup but this is more bulletproof to make sure here)
252 	if (s == "white") {
253 		return "#FFFFFF";
254 	}
255 	if (s == "black") {
256 		return "#000000";
257 	}
258 
259     var m = s && s.match(DwtColorPicker._RGB_RE);
260     if (m) {
261 	// each component should be in range of (0 - 255)
262 	for( var i = 1; i <= 3; i++ ) {
263 		if(parseInt(m[i]) > 255)
264 			return "";
265 	}
266         return AjxColor.color(m[1], m[2], m[3]);
267     }
268     m = s && s.match(DwtColorPicker._HEX_RE);
269     if (m) {
270         return s;
271     }
272     return "";
273 };
274 
275 DwtColorPicker.__isValidInputValue = function(s) {
276    // null is valid for we consider the condition
277    // the user delete all the word it has been input
278    if (!s)
279 	return s;
280    var r = DwtColorPicker.__color2hex(s);
281    if (!r) { 
282 	throw AjxMsg.colorFormatError;	
283    }
284    return s;	
285 };
286 
287 DwtColorPicker.prototype._updatePreview = function(inputelement, isValid, value){
288    if (isValid) {
289 	value = DwtColorPicker.__color2hex(value);
290 	Dwt.setVisible(this._preview, true);
291 	this._preview.style.backgroundColor = value;
292 	this._error.setVisible(false);
293    }
294    else {
295 	Dwt.setVisible(this._preview, false);
296 	this._error.setVisible(true);
297 	this._error.setText(AjxMsg.colorFormatError);
298    }
299 };
300 //
301 // Classes
302 //
303 
304 DwtColorPicker.__defineClasses = function() {
305     // HACK: This defines the custom button after the color picker has
306     // HACK: been initialized and instantiated so that we dont' get
307     // HACK: weird dependency issues. (I noticed this in particular
308     // HACK: in the admin client.)
309     DwtColorPicker.Button = function(params) {
310         params.className = params.className || "DwtColorPickerButton";
311         DwtButton.call(this, params);
312         this._colorDiv = document.getElementById(this.getHtmlElement().id+"_color");
313     };
314     DwtColorPicker.Button.prototype = new DwtButton;
315     DwtColorPicker.Button.prototype.constructor = DwtColorPicker.Button;
316 
317     DwtColorPicker.Button.prototype.setDefaultColor = function(color) {
318         this._colorDiv.style.backgroundColor = (color === null) ? "" : color;
319     };
320 
321     DwtColorPicker.Button.prototype.toString = function() {
322         return "DwtColorPickerButton";
323     };
324 
325     DwtColorPicker.Button.prototype.TEMPLATE = "dwt.Widgets#DwtColorPickerButton";
326 };
327