Yahoo! UI Library

codecola-degree  1.0.0

Yahoo! UI Library > codecola-degree > codecola-gradient.js (source view)
Search:
 
Filters
/*
Copyright (c) 2011, ZHOUQICF.COM. All rights reserved.
Code licensed under the MIT License:
version: 1.0.0
*/
/**
 * a gradient control for css3 property
 * @module codecola-gradient
 */
YUI().add('codecola-gradient', function(Y) {
    /**
     * a gradient control for css3 property
     * @param config {Object} Object literal specifying codecolaGradient configuration properties.
     * @class codecolaGradient
     * @constructor
     * @namespace Y
     * @extends Widget
     * @requires codecola-color node widget ua codecola-gradient-css
     */
    Y.codecolaGradient = Y.Base.create('codecola-gradient', Y.Widget, [], {
        initializer: function() {
        },

        renderUI: function() {
            var random = (new Date).getTime(),
                ids = {
                    panel: "codecola-gradient-panel-" + random,
                    panelWrap: "codecola-gradient-panel-wrap-" + random,
                    stops: "codecola-gradient-stops-" + random,
                    color: "codecola-gradient-color-" + random,
                    location: "codecola-gradient-location-" + random,
                    button: "codecola-gradient-stop-delete-button-" + random,
                    orientation: "codecola-gradient-orientation-" + random,
                    stopDetail: "codecola-gradient-stop-detail-" + random
                };

            var html = '<div class="codecola-gradient-wrap">'+
                        '   <div class="codecola-gradient-panel-wrap" id="' + ids.panelWrap + '">'+
                        '       <div class="codecola-gradient-panel" id="' + ids.panel + '"></div>'+
                        '   </div>' +
                        '   <div class="codecola-gradient-stops" id="' + ids.stops + '"></div>' +
                        '   <div class="codecola-gradient-orientation-wrap">' +
                        '       <label class="codecola-gradient-label" for="' + ids.orientation + '">Orientation:</label>' +
                        '	    <select class="codecola-gradient-orientation" id="' + ids.orientation + '">' +
                        '		    <option value="horizontal">horizontal</option>' +
                        '		    <option value="vertical">vertical</option>' +
                        '	    </select>' +
                        '   </div>' +
                        '   <div class="codecola-gradient-stop-detail" id="' + ids.stopDetail + '">' +
                        '	    <div class="codecola-gradient-color-wrap">' +
                        '		    <label class="codecola-gradient-label">Color:</label>' +
                        '		    <div class="codecola-gradient-color" id="' + ids.color + '"></div>' +
                        '	    </div>' +
                        '	    <div class="codecola-gradient-location-wrap">' +
                        '		    <label for="' + ids.location + '" class="codecola-gradient-label">Location:</label>' +
                        '		    <input type="number" class="codecola-gradient-location" id="' + ids.location + '" step="1" min="0" max="100"> %' +
                        '	    </div>' +
                        '	    <div class="codecola-gradient-stop-delete">' +
                        '		    <button class="codecola-gradient-stop-delete-button" id="' + ids.button + '">delete</button>' +
                        '	    </div>' +
                        '   </div>'+
                        '</div>';

            //create nodes
            var that = this;
            Y.one(that.get('wrap')).append(Y.Node.create(html));

            that.vars = {
                panel: Y.one('#'+ids.panel),
                panelWrap: Y.one('#'+ids.panelWrap),
                stops: Y.one('#'+ids.stops),
                color: Y.one('#'+ids.color),
                location: Y.one('#'+ids.location),
                button: Y.one('#'+ids.button),
                orientation: Y.one('#'+ids.orientation),
                stopDetail: Y.one('#'+ids.stopDetail),
                id: 0,
                colorControl: null,
                currentStop: null,
                disable: false,
                rule: {
                    type: "",
                    orientation: "",
                    stops: []
                }
            };

            that.vars.panelWrap.setStyle("width", that.get('panelWidth'));
            that.vars.stops.setStyle("width", that.get('panelWidth'));

            that.vars.colorControl = new Y.codecolaColor({
                wrap: '#'+ids.color,
                onChange: function(color) {
                    var cStop = that.vars.currentStop;
                    if (!cStop || that.vars.disable) {
                        return;
                    }
                    that.vars.rule.stops[cStop.getAttribute("index")].color = color;
                    cStop.setStyle("backgroundColor", color);
                    that._initPanel()._fireCallback();
                }
            });
            that.vars.colorControl.render();
            return that;
        },

        bindUI: function() {
            var that = this,
                vars = that.vars,
                rule = vars.rule;
            vars.orientation.on("change", function(e) {
                rule.orientation = this.get('value');
                that._initPanel()._fireCallback();
            });
            vars.stops.on("click", function(e) {
                if (e.target.get('nodeName') == "I" || vars.disable) {
                    return;
                }
                var s = {
                    "color": vars.colorControl.getColor(),
                    "position": that._getFloatLeft(e.clientX - vars.panel.getX() - 5)
                };
                that._addStops([s])._initPanel()._fireCallback();
            });
            vars.location.on("change", function(e) {
                var cStop = vars.currentStop;
                if (!cStop) {
                    return;
                }
                var left = this.get('value');
                left = that._percentToFloat(left + "%");
                rule.stops[cStop.getAttribute("index")].position = left;
                cStop.setStyle("left", that._getPixLeft(left));
                that._initPanel()._fireCallback();
            });
            vars.button.on("click", function(e) {
                var cStop = vars.currentStop;
                //that.vars.stops.getElementsByTagName("i").length <= 2
                if (!cStop) {
                    return;
                }
                delete rule.stops[cStop.getAttribute("index")];
                vars.stops.removeChild(cStop);
                vars.colorControl.disable();
                vars.location.set('disabled', true);
                vars.button.set('disabled', true);
                that._initPanel()._fireCallback();
            });

            return that;
        },

        syncUI: function() {
            this._initRule()._initControls();
            return this;
        },

        renderer: function() {
            this.renderUI().bindUI().syncUI().get('onInit')();
            return this;
        },

        /**
         * update the this.vars.rule object
         * @method _initRule
         * @private
         * @chainable
         */
        _initRule: function(){
            var
            rule = this.vars.rule,
            stops = [],
            gradient = this.get('gradient');

            if (/-webkit-gradient/.test(gradient)) {
                gradient = gradient.replace(/\s*,\s*/g, ",").replace("-webkit-gradient(", "").replace(/\)$/, "").split(/,(?=[fct])/);
                var part1 = gradient[0].split(",");
                rule.type = part1[0];
                rule.orientation = part1[1] + "," + part1[2];
                rule.orientation = rule.orientation == "0% 0%,100% 0%" ? "horizontal" : "vertical";
                for (var i = 1, j = gradient.length; i < j; i++) {
                    var c = gradient[i];
                    if (/color/.test(c)) {
                        c = c.replace("color-stop(", "").replace(/\)$/, "").split(/,(?=r)/);
                    } else if (/from/.test(c)) {
                        c = [0, c.replace(/from\(|/, "").replace(/\)$/, "")];
                    } else {
                        c = [1, c.replace(/to\(/, "").replace(/\)$/, "")];
                    }
                    stops.push({
                        "position": c[0],
                        "color": c[1]
                    });
                }
            } else {
                gradient = gradient.replace(/\s*,\s*/g, ",").replace(/-(moz|o|ms|webkit)-linear-gradient\(/, "").replace(/\)$/, "").split(/,(?=[r#])/);
                rule.type = "linear";
                rule.orientation = gradient[0];
                rule.orientation = rule.orientation == "left" ? "horizontal" : "vertical";
                for (var i = 1, j = gradient.length; i < j; i++) {
                    var c = gradient[i].split(" ");
                    stops.push({
                        "position": this._percentToFloat(c[1]),
                        "color": c[0]
                    });
                }
            }
            rule.stops = stops;
            return this;
            //this._addStops(stops);
        },

        /**
         * @method _initOrientation
         * @private
         * @chainable
         */
        _initOrientation: function() {
            this.vars.orientation.set('value', this.vars.rule.orientation);
            return this;
        },

        /**
         * @method _initPanel
         * @private
         * @chainable
         */
        _initPanel: function() {
            this.vars.panel.setStyle("backgroundImage", this.getGradient(false, true));
            return this;
        },
        /**
         * @method _initStops
         * @private
         * @chainable
         */
        _initStops: function(){
            this.vars.stops.empty();
            this._addStops(this.vars.rule.stops);
            return this;
        },

        /**
         * init all controls
         * @method _initControls
         * @private
         * @chainable
         */
        _initControls: function(){
            this._initStops()._initOrientation()._initPanel();
            return this;
        },

        /**
         * update the attribute 'gradient', init all the controls, fire the onChange event
         * @method setGradient
         * @param {Object} param.gradient for update the attribute 'gradient'
         * @chainable
         */
        setGradient: function(param) {
            this.set('gradient', param.gradient);
            this.syncUI()._fireCallback();
            return this;
        },

        /**
         * add stops
         * @method _addStops
         * @param {Array}
         * @private
         * @chainable
         */
        _addStops: function(stops) {
            var that = this, i;
            Y.each(stops, function(s) {
                var id = that.vars.id,
                    p = that._getPixLeft(s.position);

                i = Y.Node.create('<i class="codecola-gradient-stop" index="'+id+'"></i>');
                i.setStyles({
                    left: p,
                    backgroundColor: s.color
                });
                that.vars.stops.append(i);
                that._initStopEvent(i, id);

                that.vars.rule.stops[id] = s;
                that.vars.id++;
            });
            that._changeCurrentStop(i);
            return that;
        },

        /**
         * bind event to the stop
         * @method _initStopEvent
         * @param {Node} stop
         * @param {Number} id
         * @private
         * @chainable
         */
        _initStopEvent: function(stop, id) {
            var preX, preEventX, drag = false,
                that = this,
                doc = Y.one('html'),
                panelWidth = that.get('panelWidth');
            stop.on("mousedown", function(e) {
                if (that.vars.disable) {
                    return;
                }
                doc.setStyle("webkitUserSelect", "none");
                that._changeCurrentStop(stop);
                drag = true;
                preX = that._getPixLeft(that.vars.rule.stops[id].position, true);
                preEventX = e.pageX;
            });
            doc.on("mouseup", function(e) {
                if (drag || !that.vars.disable) {
                    doc.setStyle("webkitUserSelect", "");
                    drag = false;
                }
            });
            doc.on("mousemove", function(e) {
                if (!drag || that.vars.disable) {
                    return;
                }
                var left = preX + (e.pageX - preEventX);
                if (left < -5 || left > panelWidth - 5) {
                    return;
                }
                stop.setStyle("left", left + "px");
                var floatLeft = that._getFloatLeft(left);
                that.vars.rule.stops[id].position = floatLeft;
                that.vars.location.set('value', that._floatToPercent(floatLeft, true));
                that._initPanel()._fireCallback();
            });
            return that;
        },

        /**
         * activate a stop
         * @method _changeCurrentStop
         * @param {Node}
         * @private
         * @chainable
         */
        _changeCurrentStop: function(stop) {
            var that = this,
                preStop = that.vars.currentStop,
                selectClassName = "codecola-gradient-stop-select",
                cStop = that.vars.rule.stops[stop.getAttribute("index")];
            if (preStop) {
                preStop.removeClass(selectClassName);
            }
            stop.addClass(selectClassName);
            that.vars.currentStop = stop;
            that.vars.colorControl.able();
            that.vars.location.set('disabled', false);
            that.vars.button.set('disabled', false);

            that.vars.colorControl.set('color', cStop.color);
            that.vars.colorControl.syncUI();

            that.vars.location.set('value', that._floatToPercent(cStop.position, true));
            return that;
        },

        /**
         * get the current gradient
         * @method getGradient
         * @param {Boolean} isAll if return all of webkit|moz|o|ms gradient <code>{webkit:xxx, moz:xxx, o:xxx, ms:xxx}</code>
         * @param {Boolean} isPanel if for update panel
         * @return {String|Object}
         */
        getGradient: function(isAll, isPanel) {
            var rule = this.vars.rule,
                tempStops = [].concat(rule.stops),
                stops = {
                    webkit: "",
                    moz: ""
                },
                webkit, moz, o, ms,
                orientation = {};

            if (rule.orientation == "horizontal" || isPanel) {
                orientation = {
                    "webkit": "0% 0%,100% 0%",
                    "moz": "left"
                }
            } else {
                orientation = {
                    "webkit": "0% 0%,0% 100%",
                    "moz": "top"
                }
            }

            tempStops.sort(function(a, b) {
                return a.position - b.position;
            });
            for (var i = 0, j = tempStops.length; i < j; i++) {
                var cStop = tempStops[i];
                if (!cStop) {
                    continue;
                }
                var p = cStop.position,
                        c = cStop.color;
                if (p == 0) {
                    stops.webkit += ",from(" + c + ")";
                } else if (p == 1) {
                    stops.webkit += ",to(" + c + ")";
                } else {
                    stops.webkit += ",color-stop(" + p + "," + c + ")";
                }
                stops.moz += "," + c + " " + this._floatToPercent(p);
            }
            webkit = "-webkit-gradient(" + rule.type + "," + orientation.webkit + stops.webkit + ")";
            moz = "-moz-linear-gradient(" + orientation.moz + stops.moz + ")";
            o = "-o-linear-gradient(" + orientation.moz + stops.moz + ")";
            ms = "-ms-linear-gradient(" + orientation.moz + stops.moz + ")";
            if (isAll){
                return {
                    "webkit": webkit,
                    "moz": moz,
                    "o": o,
                    "ms": ms
                }
            } else if (Y.UA.webkit) {
                return webkit;
            } else if (Y.UA.gecko) {
                return moz;
            } else if (Y.UA.opera) {
                return o;
            } else if (Y.UA.ie) {
                return ms;
            }
        },

        /**
         * @method _getFloatLeft
         * @param {Number}
         * @private
         * @return {Number}
         */
        _getFloatLeft: function(leftPix) {
            var floatLeft = ((leftPix + 5) / this.get('panelWidth')).toFixed(2);
            if (floatLeft > 1) {
                return 1;
            }
            if (floatLeft < 0) {
                return 0;
            }
            return floatLeft;
        },

        /**
         * @method _getPixLeft
         * @param {Number} leftFloat
         * @param {Boolean} isNum if return width 'px'
         * @private
         * @return {Number|String}
         */
        _getPixLeft: function(leftFloat, isNum) {
            var panelWidth = this.get('panelWidth'),
                pixLeft = Math.round(leftFloat * panelWidth) - 5;

            if (pixLeft > panelWidth - 5) {
                pixLeft = panelWidth - 5;
            }
            if (pixLeft < -5) {
                pixLeft = -5;
            }
            if (isNum) {
                return pixLeft;
            } else {
                return pixLeft + "px";
            }
        },

        /**
         * transform percent number to float
         * @method _percentToFloat
         * @param {String}
         * @private
         * @return {Number}
         */
        _percentToFloat: function(percent) {
            return parseInt(percent.replace("%", ""), 10) / 100;
        },

        /**
         * transform float number to percent
         * @method _floatToPercent
         * @param {Number} float
         * @param {Boolean} isNum if return width '%'
         * @private
         * @return {String}
         */
        _floatToPercent: function(float, isNum) {
            var percent = Math.round(float * 100);
            if (isNum) {
                return percent;
            }
            return percent + "%";
        },

        /**
         * fire the onChange event
         * @method _fireCallback
         * @private
         * @chainable
         */
        _fireCallback: function(){
            this.get('onChange')(this.getGradient(this.get('isAll')));
            return this;
        },

        /**
         * disable all controls
         * @method disable
         * @chainable
         */
        disable: function() {
            var vars = this.vars;
            vars.colorControl.disable();
            vars.orientation.set('disabled', true);
            vars.location.set('disabled', true);
            vars.button.set('disabled', true);
            vars.disable = true;
            return this;
        },

        /**
         * able all controls
         * @method able
         * @chainable
         */
        able: function() {
            var vars = this.vars;
            vars.colorControl.able();
            vars.orientation.set('disabled', false);
            vars.location.set('disabled', false);
            vars.button.set('disabled', false);
            vars.disable = false;
            return this;
        }
    }, {
        ATTRS:{
            /**
             * @attribute wrap
             * @type String
             * @default 'body'
             * @description a css selector for <code>Y.one()</code>,controls will insert into the wrap
             */
            wrap: {
                value: 'body',
                validator: Y.Lang.isString
            },
            /**
             * @attribute gradient
             * @type String
             * @default "-webkit-gradient(linear, 0% 0%, 100% 0%, from(#000), to(#fff))" or "-moz-linear-gradient(left , #000 0%, #fff 100%)" or "-o-linear-gradient(left , #000 0%, #fff 100%)" or "-ms-linear-gradient(left , #000 0%, #fff 100%)"
             * @description gradient for init
             */
            gradient: {
                value: "-o-linear-gradient(left , #000 0%, #fff 100%)",
                setter: function(v){
                    if(!v){
                        return "-o-linear-gradient(left , #000 0%, #fff 100%)"
                    }
                }
            },
            /**
             * @attribute panelWidth
             * @type Number
             * @default 200
             * @description the control's width
             */
            panelWidth: {
                value: 200,
                validator: Y.Lang.isNumber
            },
            /**
             * @attribute isAll
             * @type Boolean
             * @default false
             * @description if the param include all private property when run the callback
             */
            isAll: {
                value: false,
                validator: Y.Lang.isBoolean
            },
            /**
             * @attribute onInit
             * @type Function
             * @default function(){}
             * @description callback when widget init
             */
            onInit: {
                value: function() {
                },
                validator: Y.Lang.isFunction
            },
            /**
             * @attribute onChange
             * @type Function
             * @default function(){}
             * @description callback when gradient change
             */
            onChange: {
                value: function() {
                },
                validator: Y.Lang.isFunction
            }
        }
    });
}, '1.0.0', {requires:['codecola-color', 'node', 'widget-base', 'ua', 'codecola-gradient-css']});

Copyright © 2011 Yahoo! Inc. All rights reserved.