milo

CssFacet

declaration
 CssFacet 

Css Facet facilitates the binding of model values to the css classes being applied to the element owned by a milo
component.

Facet configuration looks like:

css: {
    classes: {
       '.someModelProp': 'some-css-class', // Apply css class if the value of '.someModelProp' is truthy
       '.someOtherModelProp': {
           'value-1': 'some-css-class', // Apply if the value of '.someOtherModelProp' == 'value-1'
           'value-2: 'some-other-css-class' // etc
       },
       '.anotherModelProp': function getCssClass(modelValue) { return ... } // Apply result of function
       '.oneMoreModelProp': 'my-$-class' // Template value of '.oneMoreModelProp' (By replacing $ character)
    }
}

To bind a data source to the facet, use milo binder:

milo.binder(someDataSource, '->>', myComponent.css);

Or else, set data directly on the facet like so:

```
component.css.set({
'.someModelProp': 'milo',
'.someOtherModelProp': 'is-cool'
});

var CssFacet = module.exports = createFacetClass({
    className: 'Css',
    methods: {
        start: CssFacet$start,
        set: CssFacet$set,
        del: CssFacet$del,
        path: CssFacet$path,
        update: CssFacet$update
    }
});

// Config data type to update function
var updateHandlers = {
    string: updateSimple,
    object: updateByObject,
    function: updateByFunction
};

function CssFacet$start() {
    CssFacet.super.start.apply(this, arguments);
    var getClassList = this.config.getClassList

    this._classList = (getClassList && getClassList.call(this)) || this.owner.el.classList;
    modelUtils.path.wrapMessengerMethods.call(this);

    this.onSync('changedata', modelUtils.changeDataHandler); // Listen for changes to data source
    this.activeModelPaths = {}; // Key-Value object: Css classes (key) set by what model paths (value)
}

function CssFacet$set(data) {
    check(data, Match.OneOf(Object, null, undefined));

    if(data) {
        var self = this;

        _.eachKey(data, function (value, prop) {
            var modelPath = prop.charAt(0) !== '.' ? '.' + prop : prop;

            self.update(modelPath, value);
        });
    } else {
        this.del();
    }
}

function CssFacet$del() {
    var classList = this._classList;
    
    _.eachKey(this.activeModelPaths, function(modelPaths, cssClass) {
        modelPaths.clear();

        classList.remove(cssClass);
    });
}

function CssFacet$path(modelPath) {
    if (!modelPath) return this; // No model path (or '') means the root object

    // Otherwise the modelPath has to exist in the facet configuration
    return this.config.classes && this.config.classes[modelPath] ? new Path(this, modelPath) : null;
}

function CssFacet$update(modelPath, value) {
    var cssConfig = this.config.classes[modelPath];

    if (cssConfig) {
        var handler = updateHandlers[typeof cssConfig];

        handler.call(this, modelPath, cssConfig, value);

        this.postMessageSync('changed', {
            modelPath: modelPath,
            modelValue: value
        });
    }
}

function updateSimple(modelPath, cssClass, data) {
    var classList = this._classList;
    // Remove any css class set via this model path
    _.eachKey(this.activeModelPaths, function(modelPaths, cssClass) {
        if (modelPaths.has(modelPath)) {
            modelPaths.delete(modelPath);

            if (modelPaths.size === 0) // Only remove the class if no other model path is applying it
                classList.remove(cssClass);
        }
    });

    // Apply new css class (cssClass / data can be null if this is a remove only operation)
    if (cssClass && data) {
        cssClass = data ? cssClass.replace(/\$/g, data) : cssClass; // Process any template characters ($) in class name

        var modelPaths = this.activeModelPaths[cssClass] || (this.activeModelPaths[cssClass] = new Set());

        modelPaths.add(modelPath);
        classList.add(cssClass);
    }
}

function updateByObject(modelPath, cssClasses, value) {
    // Apply new css class
    var cssClass = cssClasses[value];

    updateSimple.call(this, modelPath, cssClass, value);
}

function updateByFunction(modelPath, getCssClassFn, data) {
    var cssClass = getCssClassFn.call(this, data);

    updateSimple.call(this, modelPath, cssClass, true);
}

// Path class

function Path(cssFacet, modelPath) {
    this.cssFacet = cssFacet;
    this.modelPath = modelPath;
}

Path.prototype.set = function(value) {
    this.cssFacet.update(this.modelPath, value);
};

Path.prototype.del = function() {
    this.set(null);
};