/** @author Matt Crinklaw-Vogt (tantaman) */ (function( $ ) { if (!$.event.special.destroyed) { $.event.special.destroyed = { remove: function(o) { if (o.handler) { o.handler(); } } } } function ctrlPtComparator(l,r) { return l.position - r.position; } function bind(fn, ctx) { if (typeof fn.bind === "function") { return fn.bind(ctx); } else { return function() { fn.apply(ctx, arguments); } } } var browserPrefix = ""; function GradientSelection($el, opts) { this.$el = $el; this.$el.css("position", "relative"); this.opts = opts; var $preview = $(""); this.$el.append($preview); var canvas = $preview[0]; canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; this.g2d = canvas.getContext("2d"); var $ctrlPtContainer = $("
"); this.$el.append($ctrlPtContainer) this.$ctrlPtContainer = $ctrlPtContainer; this.updatePreview = bind(this.updatePreview, this); this.controlPoints = []; this.ctrlPtConfig = new ControlPtConfig(this.$el, opts); for (var i = 0; i < opts.controlPoints.length; ++i) { var ctrlPt = this.createCtrlPt(opts.controlPoints[i]); this.controlPoints.push(ctrlPt); } this.docClicked = bind(this.docClicked, this); this.destroyed = bind(this.destroyed, this); $(document).bind("click", this.docClicked); this.$el.bind("destroyed", this.destroyed); this.previewClicked = bind(this.previewClicked, this); $preview.click(this.previewClicked); this.updatePreview(); } GradientSelection.prototype = { docClicked: function() { this.ctrlPtConfig.hide(); }, createCtrlPt: function(ctrlPtSetup) { return new ControlPoint(this.$ctrlPtContainer, ctrlPtSetup, this.opts.orientation, this, this.ctrlPtConfig) }, destroyed: function() { $(document).unbind("click", this.docClicked); }, updateOptions: function(opts) { $.extend(this.opts, opts); this.updatePreview(); }, updatePreview: function() { var result = []; this.controlPoints.sort(ctrlPtComparator); if (this.opts.orientation == "horizontal") { var grad = this.g2d.createLinearGradient(0, 0, this.g2d.canvas.width, 0); for (var i = 0; i < this.controlPoints.length; ++i) { var pt = this.controlPoints[i]; grad.addColorStop(pt.position, pt.color); result.push({ position: pt.position, color: pt.color }); } } else { } this.g2d.fillStyle = grad; this.g2d.fillRect(0, 0, this.g2d.canvas.width, this.g2d.canvas.height); if (this.opts.generateStyles) var styles = this._generatePreviewStyles(); this.opts.change(result, styles); }, removeControlPoint: function(ctrlPt) { var cpidx = this.controlPoints.indexOf(ctrlPt); if (cpidx != -1) { this.controlPoints.splice(cpidx, 1); ctrlPt.$el.remove(); } }, previewClicked: function(e) { var offset = $(e.target).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; var imgData = this.g2d.getImageData(x,y,1,1); var colorStr = "rgb(" + imgData.data[0] + "," + imgData.data[1] + "," + imgData.data[2] + ")"; var cp = this.createCtrlPt({ position: x / this.g2d.canvas.width, color: colorStr }); this.controlPoints.push(cp); this.controlPoints.sort(ctrlPtComparator); }, _generatePreviewStyles: function() { //linear-gradient(top, rgb(217,230,163) 86%, rgb(227,249,159) 9%) var str = this.opts.type + "-gradient(" + ((this.opts.type == "linear") ? (this.opts.fillDirection + ", ") : ""); var first = true; for (var i = 0; i < this.controlPoints.length; ++i) { var pt = this.controlPoints[i]; if (!first) { str += ", "; } else { first = false; } str += pt.color + " " + ((pt.position*100)|0) + "%"; } str = str + ")" var styles = [str, browserPrefix + str]; return styles; } }; function ControlPoint($parentEl, initialState, orientation, listener, ctrlPtConfig) { this.$el = $(""); $parentEl.append(this.$el); this.$parentEl = $parentEl; this.configView = ctrlPtConfig; if (typeof initialState === "string") { initialState = initialState.split(" "); this.position = parseFloat(initialState[1])/100; this.color = initialState[0]; } else { this.position = initialState.position; this.color = initialState.color; } this.listener = listener; this.outerWidth = this.$el.outerWidth(); this.$el.css("background-color", this.color); if (orientation == "horizontal") { var pxLeft = ($parentEl.width() - this.$el.outerWidth()) * (this.position); this.$el.css("left", pxLeft); } else { var pxTop = ($parentEl.height() - this.$el.outerHeight()) * (this.position); this.$el.css("top", pxTop); } this.drag = bind(this.drag, this); this.stop = bind(this.stop, this); this.clicked = bind(this.clicked, this); this.colorChanged = bind(this.colorChanged, this); this.$el.draggable({ axis: (orientation == "horizontal") ? "x" : "y", drag: this.drag, stop: this.stop, containment: $parentEl }); this.$el.click(this.clicked); } ControlPoint.prototype = { drag: function(e, ui) { // convert position to a % var left = ui.position.left; this.position = (left / (this.$parentEl.width() - this.outerWidth)); this.listener.updatePreview(); }, stop: function(e, ui) { this.listener.updatePreview(); this.configView.show(this.$el.position(), this.color, this); }, clicked: function(e) { this.configView.show(this.$el.position(), this.color, this); e.stopPropagation(); return false; }, colorChanged: function(c) { this.color = c; this.$el.css("background-color", this.color); this.listener.updatePreview(); }, removeClicked: function() { this.listener.removeControlPoint(this); this.listener.updatePreview(); } }; function ControlPtConfig($parent, opts) { //color-chooser this.$el = $(''); $parent.append(this.$el); var $cpicker = $(''); this.$el.append($cpicker); var $rmEl = $(""); this.$el.append($rmEl); this.colorChanged = bind(this.colorChanged, this); this.removeClicked = bind(this.removeClicked, this); $cpicker.ColorPicker({ onChange: this.colorChanged }); this.$cpicker = $cpicker; this.opts = opts; this.visible = false; $rmEl.click(this.removeClicked); } ControlPtConfig.prototype = { show: function(position, color, listener) { this.visible = true; this.listener = listener; this.$el.css("visibility", "visible"); this.$cpicker.ColorPickerSetColor(color); this.$cpicker.css("background-color", color); if (this.opts.orientation === "horizontal") { this.$el.css("left", position.left); } else { this.$el.css("top", position.top); } //else { // this.visible = false; //this.$el.css("visibility", "hidden"); //} }, hide: function() { if (this.visible) { this.$el.css("visibility", "hidden"); this.visible = false; } }, colorChanged: function(hsb, hex, rgb) { hex = "#" + hex; this.listener.colorChanged(hex); this.$cpicker.css("background-color", hex) }, removeClicked: function() { this.listener.removeClicked(); this.hide(); } }; var methods = { init: function(opts) { opts = $.extend({ controlPoints: ["#FFF 0%", "#000 100%"], orientation: "horizontal", type: "linear", fillDirection: "left", generateStyles: true, change: function() {} }, opts); this.each(function() { var $this = $(this); var gradSel = new GradientSelection($this, opts); $this.data("gradientPicker-sel", gradSel); }); }, update: function(opts) { this.each(function() { var $this = $(this); var gradSel = $this.data("gradientPicker-sel"); if (gradSel != null) { gradSel.updateOptions(opts); } }); } }; $.fn.gradientPicker = function(method, opts) { if (typeof method === "string" && method !== "init") { methods[method].call(this, opts); } else { opts = method; methods.init.call(this, opts); } }; })( jQuery );