diff --git a/spectrum.js b/spectrum.js index 56673298..b7c21e08 100644 --- a/spectrum.js +++ b/spectrum.js @@ -5,14 +5,14 @@ (function (factory) { "use strict"; - - if (typeof define === 'function' && define.amd) { // AMD + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. define(['jquery'], factory); - } - else if (typeof exports == "object" && typeof module == "object") { // CommonJS - module.exports = factory; - } - else { // Browser + } else if (typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals factory(jQuery); } })(function($, undefined) { @@ -237,6 +237,7 @@ previewElement = replacer.find(".sp-preview-inner"), initialColor = opts.color || (isInput && boundElement.val()), colorOnShow = false, + lastKnownColor = false, preferredFormat = opts.preferredFormat, currentPreferredFormat = preferredFormat, clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, @@ -347,12 +348,8 @@ e.stopPropagation(); e.preventDefault(); isEmpty = true; - move(); - if(flat) { - //for the flat style, this is a change event - updateOriginalInput(true); - } + updateOriginalInput(); }); chooseButton.text(opts.chooseText); @@ -391,7 +388,8 @@ currentAlpha = Math.round(currentAlpha * 10) / 10; } - move(); + updateUI(); + updateOriginalInput(); }, dragStart, dragStop); draggable(slider, function (dragX, dragY) { @@ -400,7 +398,8 @@ if (!opts.showAlpha) { currentAlpha = 1; } - move(); + updateUI(); + updateOriginalInput(); }, dragStart, dragStop); draggable(dragger, function (dragX, dragY, e) { @@ -431,13 +430,13 @@ if (!opts.showAlpha) { currentAlpha = 1; } - - move(); + updateUI(); + updateOriginalInput(); }, dragStart, dragStop); if (!!initialColor) { - set(initialColor); + set(initialColor, false, true); // In case color was black - update the preview UI and set the format // since the set function will not run (default color is black). @@ -457,12 +456,9 @@ function paletteElementClick(e) { if (e.data && e.data.ignore) { set($(e.target).closest(".sp-thumb-el").data("color")); - move(); } else { set($(e.target).closest(".sp-thumb-el").data("color")); - move(); - updateOriginalInput(true); if (opts.hideAfterPaletteSelect) { hide(); } @@ -472,26 +468,12 @@ } var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; - paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); - initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); + paletteContainer.on(paletteEvent, ".sp-thumb-el", paletteElementClick); + initialColorContainer.on(paletteEvent, ".sp-thumb-el:nth-child(1)", { ignore: true }, paletteElementClick); } function updateSelectionPaletteFromStorage() { - if (localStorageKey && window.localStorage) { - - // Migrate old palettes over to new format. May want to remove this eventually. - try { - var oldPalette = window.localStorage[localStorageKey].split(",#"); - if (oldPalette.length > 1) { - delete window.localStorage[localStorageKey]; - $.each(oldPalette, function(i, c) { - addColorToSelectionPalette(c); - }); - } - } - catch(e) { } - try { selectionPalette = window.localStorage[localStorageKey].split(";"); } @@ -578,13 +560,11 @@ if ((value === null || value === "") && allowEmpty) { set(null); - updateOriginalInput(true); } else { var tiny = tinycolor(value); if (tiny.isValid()) { set(tiny); - updateOriginalInput(true); } else { textInput.addClass("sp-validation-error"); @@ -665,7 +645,7 @@ set(colorOnShow, true); } - function set(color, ignoreFormatChange) { + function set(color, ignoreFormatChange, ignoreInputChange) { if (tinycolor.equals(color, get())) { // Update UI just in case a validation error needs // to be cleared. @@ -686,11 +666,15 @@ currentValue = newHsv.v; currentAlpha = newHsv.a; } - updateUI(); if (newColor && newColor.isValid() && !ignoreFormatChange) { currentPreferredFormat = preferredFormat || newColor.getFormat(); } + + updateUI(); + if (!ignoreInputChange) { + updateOriginalInput(); + } } function get(opts) { @@ -712,13 +696,6 @@ return !textInput.hasClass("sp-validation-error"); } - function move() { - updateUI(); - - callbacks.move(get()); - boundElement.trigger('move.spectrum', [ get() ]); - } - function updateUI() { textInput.removeClass("sp-validation-error"); @@ -842,20 +819,32 @@ function updateOriginalInput(fireCallback) { var color = get(), - displayColor = '', - hasChanged = !tinycolor.equals(color, colorOnShow); + displayColor = ''; if (color) { - displayColor = color.toString(currentPreferredFormat); // Update the selection palette with the current color addColorToSelectionPalette(color); + + if (isInput) { + boundElement.val(color.toString(currentPreferredFormat)); + } } - if (isInput) { - boundElement.val(displayColor); + // Fire the "input" event (aka 'move') + if (!tinycolor.equals(color, lastKnownColor)) { + lastKnownColor = color; + callbacks.move(get()); + boundElement.trigger('move.spectrum', [ get() ]); } - if (fireCallback && hasChanged) { + // Flat colorpickers fire "change" event on any input + // since they don't have a way to revert. + // Normal colorpickers wait until the picker is closed + // to fire one (if necessary). + var careAboutChanges = flat || fireCallback; + var hasChanged = !tinycolor.equals(color, colorOnShow); + if (careAboutChanges && hasChanged) { + colorOnShow = color; callbacks.change(color); boundElement.trigger('change', [ color ]); } @@ -890,6 +879,7 @@ } function destroy() { + hide(); boundElement.show(); offsetElement.unbind("click.spectrum touchstart.spectrum"); container.remove(); @@ -938,10 +928,7 @@ enable: enable, disable: disable, offset: setOffset, - set: function (c) { - set(c); - updateOriginalInput(); - }, + set: set, get: get, destroy: destroy, container: container @@ -1047,8 +1034,8 @@ var pageX = touches ? touches[0].pageX : e.pageX; var pageY = touches ? touches[0].pageY : e.pageY; - var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); - var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); + var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)) || 0; + var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)) || 0; if (hasTouch) { // Stop scrolling in iOS @@ -1093,7 +1080,10 @@ $(element).bind("touchstart mousedown", start); } - function throttle(func, wait, debounce) { + /** + * Only call a function at a max of once per N milliseconds + */ + function throttle(func, wait) { var timeout; return function () { var context = this, args = arguments; @@ -1101,8 +1091,7 @@ timeout = null; func.apply(context, args); }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); + if (!timeout) timeout = setTimeout(throttler, wait); }; } diff --git a/test/tests.js b/test/tests.js index ddc93686..f147296c 100644 --- a/test/tests.js +++ b/test/tests.js @@ -23,7 +23,6 @@ test( "jQuery Plugin Can Be Created", function() { el.spectrum("destroy"); equal(el.spectrum("container"), el, "After destroying spectrum, string function returns input."); - }); test( "Per-element Options Are Read From Data Attributes", function() { @@ -50,77 +49,114 @@ test( "Per-element Options Are Read From Data Attributes", function() { noData.spectrum("destroy"); }); -test( "Events Fire", function() { - var el = $("").spectrum(); - var count = 0; - expect(5); - el.on("beforeShow.spectrum", function(e) { +module("Dragging"); - // Cancel the event the first time - if (count === 0) { - ok(true, "Cancel beforeShow"); - count++; - return false; - } +test( "Dragging color area", function() { + var el = $("").appendTo("body").spectrum().spectrum("show"); + var colorDragArea = el.spectrum("container").find(".sp-color"); + var body = $("body"); - ok (count === 1, "Allow beforeShow"); - count++; - }); + equal (colorDragArea.length, 1, "There is a color drag area"); + var offset = colorDragArea.offset(); + var height = colorDragArea.height(); + var width = colorDragArea.width(); - el.on("show.spectrum", function(e) { - ok(count === 2, "Show"); - count++; - }); + colorDragArea.trigger("mousedown"); + ok (body.hasClass("sp-dragging"), "The body has dragging class when mousedown triggered"); - el.on("hide.spectrum", function(e) { - ok(count === 3, "Hide"); - - count++; + // top left + colorDragArea.trigger({ + type:"mousemove", + pageX: offset.left, + pageY: offset.top }); + equal (el.spectrum("get").toHex(), "ffffff", "top left"); - el.on("move.spectrum", function(e) { + // top right + colorDragArea.trigger({ + type:"mousemove", + pageX: offset.left + width, + pageY: offset.top + }); + equal (el.spectrum("get").toHex(), "ff0000", "top right"); + // bottom left + colorDragArea.trigger({ + type:"mousemove", + pageX: offset.left, + pageY: offset.top + width }); + equal (el.spectrum("get").toHex(), "000000", "bottom left"); - el.on("change", function(e, color) { - ok(false, "Change should not fire from `set` call"); + // bottom right + colorDragArea.trigger({ + type:"mousemove", + pageX: offset.left + width, + pageY: offset.top + height }); + equal (el.spectrum("get").toHex(), "000000", "bottom right"); - el.spectrum("show"); - el.spectrum("show"); - el.spectrum("hide"); + $(document).trigger("mouseup"); + ok (!body.hasClass("sp-dragging"), "The body does not have a dragging class when mouseup triggered on document"); - el.spectrum("set", "red"); - el.spectrum("destroy"); + colorDragArea.trigger("mousedown"); + ok (body.hasClass("sp-dragging"), "The body has dragging class when mousedown triggered"); + colorDragArea.trigger("mouseup"); + ok (!body.hasClass("sp-dragging"), "The body does not have a dragging class when mouseup triggered on element"); + ok (el.spectrum("container").is(":visible"), "Container is still visible"); - var el2 = $("").spectrum({ - showInput: true - }); - el2.on("change.spectrum", function(e, color) { - ok(true, "Change should fire input changing"); - }); - el2.spectrum("container").find(".sp-input").val("blue").trigger("change"); - el2.spectrum("destroy"); + el.spectrum("destroy").remove(); }); -test( "Dragging", function() { - var el = $("").spectrum(); +test( "Dragging hue area", function() { + var el = $("").appendTo("body").spectrum().spectrum("show"); var hueSlider = el.spectrum("container").find(".sp-hue"); + var body = $("body"); + + equal (hueSlider.length, 1, "There is a hue slider"); - ok (hueSlider.length, "There is a hue slider"); + var offset = hueSlider.offset(); + var height = hueSlider.height(); hueSlider.trigger("mousedown"); + ok (body.hasClass("sp-dragging"), "The body has dragging class when mousedown triggered"); - ok ($("body").hasClass("sp-dragging"), "The body has dragging class"); + // top + hueSlider.trigger({ + type:"mousemove", + pageX: offset.left, + pageY: offset.top + }); + equal (el.spectrum("get").toHex(), "ff0000", "top"); - hueSlider.trigger("mouseup"); + // middle + hueSlider.trigger({ + type:"mousemove", + pageY: offset.top + (height/2) + }); + equal (el.spectrum("get").toHex(), "00ffff", "middle"); - ok (!$("body").hasClass("sp-dragging"), "The body does not have a dragging class"); + // bottom + hueSlider.trigger({ + type:"mousemove", + pageY: offset.top + height + }); + equal (el.spectrum("get").toHex(), "ff0000", "bottom"); - el.spectrum("destroy"); + $(document).trigger("mouseup"); + ok (!body.hasClass("sp-dragging"), "The body does not have a dragging class when mouseup triggered on document"); + ok (el.spectrum("container").is(":visible"), "Container is still visible"); + + hueSlider.trigger("mousedown"); + ok (body.hasClass("sp-dragging"), "The body has dragging class when mousedown triggered"); + hueSlider.trigger("mouseup"); + ok (!body.hasClass("sp-dragging"), "The body does not have a dragging class when mouseup triggered on element"); + ok (el.spectrum("container").is(":visible"), "Container is still visible"); + + el.spectrum("destroy").remove(); }); module("Defaults"); @@ -133,6 +169,9 @@ test( "Default Color Is Set By Input Value", function() { var noColor = $("").spectrum(); equal ( noColor.spectrum("get").toHex(), "000000", "Defaults to black with an invalid color"); + var blackColor = $("").spectrum(); + equal ( blackColor.spectrum("get").toHex(), "000000", "Defaults to black when set on input"); + var noValue = $("").spectrum(); equal ( noValue.spectrum("get").toHex(), "000000", "Defaults to black with no value set"); @@ -142,25 +181,44 @@ test( "Default Color Is Set By Input Value", function() { equal ( noValueHex3.spectrum("get").toHex(true), "000", "Defaults to 3 char hex with no value set"); equal ( noValueHex3.spectrum("get").toString(), "#000", "Defaults to 3 char hex with no value set"); - red.spectrum("destroy"); noColor.spectrum("destroy"); noValue.spectrum("destroy"); noValueHex3.spectrum("destroy"); + blackColor.spectrum("destroy"); }); module("Palettes"); -test( "Palette Events Fire In Correct Order ", function() { - expect(2); +test( "Palette Events Fire In Correct Order for normal picker", function() { + expect(1); var el = $("").spectrum({ showPalette: true, palette: [ ["red", "green", "blue"] - ], - move: function() { + ] + }); - } + el.on("move.spectrum", function(e) { + ok(true, "Move fires"); + }); + + el.on("change.spectrum", function(e) { + ok(false, "Change shouldn't have fired"); + }); + + el.spectrum("container").find(".sp-thumb-el:last-child").click(); + el.spectrum("destroy"); +}); + +test( "Palette Events Fire In Correct Order for flat picker", function() { + expect(2); + var el = $("").spectrum({ + flat: true, + showPalette: true, + palette: [ + ["red", "green", "blue"] + ] }); var count = 0; @@ -281,6 +339,64 @@ test ("containerClassName", function() { el.spectrum("destroy").remove(); }); +test("clickoutFiresChange=false", function() { + expect(3); + var el = $("").appendTo("body").spectrum({ + color: "red" + }); + + el.on("move.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The move event fired with the proper color"); + }); + + el.on("change.spectrum", function(e, color) { + ok (false, + "The change event should not fire"); + }); + + el.spectrum("show"); + + var expectedColor = "blue"; + el.spectrum("set", "blue"); + + // Clickout to revert + expectedColor = "red"; + $(document).trigger("click"); + + equal(el.spectrum("get").toName(), "red"); + el.spectrum("destroy").remove(); +}); + +test("clickoutFiresChange=true", function() { + expect(3); + var el = $("").appendTo("body").spectrum({ + color: "red", + clickoutFiresChange: true + }); + + el.on("move.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The move event fired with the proper color"); + }); + + el.on("change.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The change event fired with the proper color"); + }); + + el.spectrum("show"); + + var expectedColor = "blue"; + el.spectrum("set", "blue"); + + // Clickout to commit + $(document).trigger("click"); + + equal(el.spectrum("get").toName(), "blue"); + el.spectrum("destroy").remove(); +}); + test( "Options Can Be Set and Gotten Programmatically", function() { var spec = $("").spectrum({ @@ -463,6 +579,114 @@ test ("Tooltip is formatted based on preferred format", function() { el.spectrum("destroy"); }); +module("Events"); + +test( "Events Fire", function() { + var el = $("").spectrum(); + var count = 0; + expect(5); + + el.on("beforeShow.spectrum", function(e) { + + // Cancel the event the first time + if (count === 0) { + ok(true, "Cancel beforeShow"); + count++; + return false; + } + + ok (count === 1, "Allow beforeShow"); + count++; + }); + + el.on("show.spectrum", function(e) { + ok(count === 2, "Show"); + count++; + }); + + el.on("hide.spectrum", function(e) { + ok(count === 3, "Hide"); + + count++; + }); + + el.on("move.spectrum", function(e) { + ok(count === 4, "Move"); + + count++; + }); + + el.on("change", function(e, color) { + ok(false, "Change should not fire from `set` call"); + }); + + el.spectrum("show"); + el.spectrum("show"); + el.spectrum("hide"); + + el.spectrum("set", "red"); + + el.spectrum("destroy"); + +}); + +test( "Changing input", function() { + expect(1); + var el = $(""); + + el.on("move.spectrum", function(e, color) { + ok(true, "Changing textarea should fire input move event"); + }); + + el.spectrum({ + showInput: true + }).spectrum("show"); + + el.spectrum("container").find(".sp-input").val("blue").trigger("change"); + el.spectrum("destroy"); +}); + +test( "Change event (normal)" , function() { + expect(1); + var el = $(""); + var readyForEvent = false; + + el.on("change.spectrum", function(e, color) { + if (readyForEvent) { + equal(color.toName(), "orange", "Change should be fired on set()"); + } else { + ok (false, "Shouldn't have receieved the event yet"); + } + }); + + el.spectrum({ + color: "red" + }).spectrum("show"); + + el.spectrum("set", "orange"); + readyForEvent = true; + el.spectrum("container").find(".sp-choose").trigger("click"); + console.log(el.spectrum("container").find(".sp-choose")); + el.spectrum("destroy"); +}); + +test( "Change event (flat)" , function() { + expect(1); + var el = $(""); + + el.on("change.spectrum", function(e, color) { + equal(color.toName(), "orange", "Change should be fired on set()"); + }); + + el.spectrum({ + color: "red", + flat: "true" + }).spectrum("show"); + + el.spectrum("set", "orange"); + el.spectrum("destroy"); +}); + module("Methods"); test( "Methods work as described", function() { @@ -524,8 +748,28 @@ test( "Methods work as described", function() { el.spectrum("destroy"); }); +asyncTest( "Reflows on window resize", function() { + var container = $("
").appendTo("body"); + var el = $("").appendTo(container).spectrum(); + + // Check to make sure the positioning + el.spectrum("show"); + equal (el.spectrum("container").offset().left, 0); + container.show(); + $(window).trigger("resize"); + + // Check to make sure the throttled reflow happened. + setTimeout(function() { + equal (el.spectrum("container").offset().left, 100); + el.spectrum("destroy"); + container.remove(); + QUnit.start(); + }, 50); + +}); + // https://github.com/bgrins/spectrum/issues/97 -test( "Change events fire as described" , function() { +test( "Change events don't fire on original input" , function() { expect(0); var input = $(""); @@ -542,7 +786,6 @@ test( "Change events fire as described" , function() { }); input.spectrum("set", "orange"); - }); test("The selectedPalette should be updated in each spectrum instance, when storageKeys are identical.", function () { @@ -626,7 +869,8 @@ test( "Choosing updates the color", function() { test( "Custom offset", function() { var el = $("").spectrum(); el.spectrum("show"); - deepEqual (el.spectrum("container").offset(), {top: 0, left: 0}); + // Not checking offset top b/c it varies across browsers + equal (el.spectrum("container").offset().left, 0, "Container has correct offset left"); el.spectrum("hide"); el.spectrum("offset", {top: 10, left: 10}); el.spectrum("show"); @@ -634,7 +878,7 @@ test( "Custom offset", function() { el.spectrum("hide"); el.spectrum("offset", null); el.spectrum("show"); - deepEqual (el.spectrum("container").offset(), {top: 0, left: 0}); + equal (el.spectrum("container").offset().left, 0, "Container has correct offset left"); el.spectrum("hide"); el.spectrum("destroy"); @@ -645,3 +889,84 @@ test( "Custom offset", function() { deepEqual (el2.spectrum("container").offset(), {top: 100, left: 100}); el2.spectrum("hide"); }); + +test("Flat picker reselect initial (Issue #264)", function() { + expect(8); + var el = $("").appendTo("body"); + + el.on("move.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The move event fired with the proper color"); + }); + + el.on("change.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The change event fired with the proper color"); + }); + + el.spectrum({ + flat: true, + showPalette: true, + showPaletteOnly: true, + color: "rgb(255,0,0)", + palette: [ + "rgb(255,0,0)", + "rgb(0,255,0)", + "rgb(0,0,255)" + ] + }).spectrum("show"); + + var expectedColor = "rgb(0,255,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(2)").trigger("click"); + expectedColor = "rgb(0,0,255)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(3)").trigger("click"); + expectedColor = "rgb(255,0,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(1)").trigger("click"); + expectedColor = "rgb(0,255,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(2)").trigger("click"); + + el.spectrum("destroy").remove(); +}); + +test("Normal picker reselect initial (Issue #264)", function() { + expect(6); + var el = $("").appendTo("body"); + + el.on("move.spectrum", function(e, color) { + ok (tinycolor.equals(expectedColor, color), + "The move event fired with the proper color"); + }); + + el.on("change.spectrum", function(e, color) { + ok (false, + "The change event should not fire"); + }); + + el.spectrum({ + showPalette: true, + showPaletteOnly: true, + color: "rgb(255,0,0)", + palette: [ + "rgb(255,0,0)", + "rgb(0,255,0)", + "rgb(0,0,255)" + ] + }).spectrum("show"); + + var expectedColor = "rgb(0,255,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(2)").trigger("click"); + expectedColor = "rgb(0,0,255)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(3)").trigger("click"); + expectedColor = "rgb(255,0,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(1)").trigger("click"); + expectedColor = "rgb(0,255,0)"; + el.spectrum("container").find(".sp-thumb-el:nth-child(2)").trigger("click"); + + // Clickout to revert + expectedColor = "rgb(255,0,0)"; + $(document).trigger("click"); + + equal(el.spectrum("get").toString(), "rgb(255, 0, 0)", + "The color was reverted from a clickout"); + el.spectrum("destroy").remove(); +});