CSSStyleDeclaration.js 6.8 KB
/*********************************************************************
 * This is a fork from the CSS Style Declaration part of
 * https://github.com/NV/CSSOM
 ********************************************************************/
"use strict";
var CSSOM = require('cssom');
var fs = require('fs');
var path = require('path');

var camelToDashed = require('./parsers').camelToDashed;
var dashedToCamelCase = require('./parsers').dashedToCamelCase;

/**
 * @constructor
 * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
 */
var CSSStyleDeclaration = function CSSStyleDeclaration(onChangeCallback) {
    this._values = {};
    this._importants = {};
    this._length = 0;
    this._onChange = onChangeCallback || function () { return; };
};
CSSStyleDeclaration.prototype = {
    constructor: CSSStyleDeclaration,

    /**
     *
     * @param {string} name
     * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue
     * @return {string} the value of the property if it has been explicitly set for this declaration block.
     * Returns the empty string if the property has not been set.
     */
    getPropertyValue: function (name) {
        if (!this._values.hasOwnProperty(name)) {
            return "";
        }
        return this._values[name].toString();
    },

    /**
     *
     * @param {string} name
     * @param {string} value
     * @param {string} [priority=null] "important" or null
     * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
     */
    setProperty: function (name, value, priority) {
        if (value === undefined) {
            return;
        }
        if (value === null || value === '') {
            this.removeProperty(name);
            return;
        }
        var camel_case = dashedToCamelCase(name);
        this[camel_case] = value;
        this._importants[name] = priority;
    },
    _setProperty: function (name, value, priority) {
        if (value === undefined) {
            return;
        }
        if (value === null || value === '') {
            this.removeProperty(name);
            return;
        }
        if (this._values[name]) {
            // Property already exist. Overwrite it.
            var index = Array.prototype.indexOf.call(this, name);
            if (index < 0) {
                this[this._length] = name;
                this._length++;
            }
        } else {
            // New property.
            this[this._length] = name;
            this._length++;
        }
        this._values[name] = value;
        this._importants[name] = priority;
        this._onChange(this.cssText);
    },

    /**
     *
     * @param {string} name
     * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty
     * @return {string} the value of the property if it has been explicitly set for this declaration block.
     * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
     */
    removeProperty: function (name) {
        if (!this._values.hasOwnProperty(name)) {
            return "";
        }

        var prevValue = this._values[name];
        delete this._values[name];
        delete this._importants[name];

        var index = Array.prototype.indexOf.call(this, name);
        if (index < 0) {
            return prevValue;
        }

        // That's what WebKit and Opera do
        Array.prototype.splice.call(this, index, 1);

        // That's what Firefox does
        //this[index] = ""

        this._onChange(this.cssText);
        return prevValue;
    },


    /**
     *
     * @param {String} name
     */
    getPropertyPriority: function (name) {
        return this._importants[name] || "";
    },


    getPropertyCSSValue: function () {
        //FIXME
        return;
    },

    /**
     *   element.style.overflow = "auto"
     *   element.style.getPropertyShorthand("overflow-x")
     *   -> "overflow"
     */
    getPropertyShorthand: function () {
        //FIXME
        return;
    },

    isPropertyImplicit: function () {
        //FIXME
        return;
    },

    /**
     *   http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-item
     */
    item: function (index) {
        index = parseInt(index, 10);
        if (index < 0 || index >= this._length) {
            return '';
        }
        return this[index];
    }
};

Object.defineProperties(CSSStyleDeclaration.prototype, {
    cssText: {
        get: function () {
            var properties = [];
            var i;
            var name;
            var value;
            var priority;
            for (i = 0; i < this._length; i++) {
                name = this[i];
                value = this.getPropertyValue(name);
                priority = this.getPropertyPriority(name);
                if (priority !== '') {
                    priority = " !" + priority;
                }
                properties.push([name, ': ', value, priority, ';'].join(''));
            }
            return properties.join(' ');
        },
        set: function (value) {
            var i;
            this._values = {};
            Array.prototype.splice.call(this, 0, this._length);
            this._importants = {};
            var dummyRule;
            try {
                dummyRule = CSSOM.parse('#bogus{' + value + '}').cssRules[0].style;
            } catch (err) {
                // malformed css, just return
                return;
            }
            var rule_length = dummyRule.length;
            var name;
            for (i = 0; i < rule_length; ++i) {
                name = dummyRule[i];
                this.setProperty(dummyRule[i], dummyRule.getPropertyValue(name), dummyRule.getPropertyPriority(name));
            }
            this._onChange(this.cssText);
        },
        enumerable: true,
        configurable: true
    },
    parentRule: {
        get: function () { return null; },
        enumerable: true,
        configurable: true
    },
    length: {
        get: function () { return this._length; },
        /**
         * This deletes indices if the new length is less then the current
         * length. If the new length is more, it does nothing, the new indices
         * will be undefined until set.
         **/
        set: function (value) {
            var i;
            for (i = value; i < this._length; i++) {
                delete this[i];
            }
            this._length = value;
        },
        enumerable: true,
        configurable: true
    },
    'float': {
        get: function () { return this.cssFloat; },
        set: function (value) {
            this.cssFloat = value;
        },
        enumerable: true,
        configurable: true
    }
});

require('./properties')(CSSStyleDeclaration.prototype);

exports.CSSStyleDeclaration = CSSStyleDeclaration;