combine dynamic and static classes through css bin

2019-01-08 22:18发布

In knockout.js we can use css binding for static classes

<div data-bind="css: {'translucent ': number() < 10}">static dynamic css classes</div>

and dynamic

<div data-bind="css: color">static dynamic css classes</div>

I've tried http://jsfiddle.net/tT9PK/1/ to combine it in something like

css: {color, translucent: number() < 10}

to get dynamic class color and static translucent at the same time, but I get an error. Is there a way to do that?

9条回答
家丑人穷心不美
2楼-- · 2019-01-08 22:30

I'd create the css binding value in your viewmodel. You can define a computed that returns either an object or string.

Some examples, using ES2015:

const App = function() {
  this.number = ko.observable(12);
  this.color = ko.observable("red");
  
  this.cssConfigObj = ko.pureComputed(() => ({
    "italic": this.number() > 10,
    [this.color()]: true
  }));
  
  this.cssConfigStr = ko.pureComputed(() => 
    `${this.color()} ${this.number() > 10 ? "italic" : ""}`
  );
};

ko.applyBindings(new App());
.monospaced {
  font-family: monospace;
}

.italic {
  font-style: italic;
}

.red {
  color: red; 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div
  class="monospaced"
  data-bind="css: cssConfigObj"
>Hello world</div>

<div
  class="monospaced"
  data-bind="css: cssConfigStr"
>Hello world</div>

查看更多
仙女界的扛把子
3楼-- · 2019-01-08 22:32

Correct...and to launch you even further, check out this modification.

http://jsfiddle.net/Fv27b/2/

Here, you'll see that not only are we combining the options, but we're creating our own binding entirely...which results in a much more portable extension of not just this view model, but any view model you may have in your project...so you'll only need to write this one once!

ko.bindingHandlers.colorAndTrans = {
    update: function(element, valAccessor) {
        var valdata = valAccessor();
        var cssString = valdata.color();
        if (valdata.transValue() < 10) cssString += " translucent";
        element.className = cssString;
    }
}

To invoke this, you just use it as a new data-bind property and can include as many (or as few) options as possible. Under this specific condition, I might have just provided $data, however if you're wanting a reusable option you need to be more specific as to what data types you need as parameters and not all view models may have the same properties.

data-bind="colorAndTrans: { color: color, transValue: number }"

Hope this does more than answer your question!

查看更多
别忘想泡老子
4楼-- · 2019-01-08 22:42

If you really get into complicated styling case, just accumulate all in the computed property. You can do it as Alex mentioned or a bit more readable:

vm.divStyle = ko.computed(function() {
        var styles = [];

        if (vm.isNested()) styles.push('nested');
        if (vm.isTabular()) styles.push('tabular');
        else styles.push('non-tabular');
        if (vm.color()) styles.push(vm.color());

        return styles.join(' ');
});

the main drawback is that you're moving a part of view definition into the viewmodel, that should be more independent. The alternative is to provide all the logic above as a plain js function call, and let knockout evaluate it.

查看更多
啃猪蹄的小仙女
5楼-- · 2019-01-08 22:42

There is a more elegant solution to this problem via computed property names (for FF>34, Chrome, Safari>7.1):

<div data-bind="css: { [color]: true,'translucent': number() < 10 }">
    static dynamic css classes
</div>

Whereas color is a property with a string value.

If the value of color is an observable then we need to clear the classname before that observable updates. If we do not do this then each change will add another class and not remove the previous one. This can easily be accomplished manually but I wrote an extender for those who are interested.

ko.extenders.css = function(target, value) {
  var beforeChange;
  var onChange;

  //add sub-observables to our observable
  target.show = ko.observable(true);

  beforeChange = function(oldValue){
    target.show(false);
  }
  onChange = function(newValue){
    target.show(true);
  }
  target.subscribe(beforeChange, null, "beforeChange");
  target.subscribe(onChange);
  return target;
};

With this extender, your JavaScript code would look like this:

function MyViewModel() {
    this.color = ko.observable("red").extend({ css: true });
    this.number = ko.observable(9)
};

And your markup would be this simple:

<div data-bind="css: { [color()]: color.show(),'translucent': number() < 10 }">
    static dynamic css classes
</div>

I have a code pen demonstrating this technique: http://codepen.io/USIUX/pen/WryGZQ

I have also submitted an issue with knockout in hopes that one day the custom extender will not be necessary: https://github.com/knockout/knockout/issues/1990

查看更多
\"骚年 ilove
6楼-- · 2019-01-08 22:44

I solved this problem a while back by just cloning the css binding as css2.

 ko.bindingHandlers['css2'] = ko.bindingHandlers.css;

Normally you can't use the same binding handler twice in a data-bind attribute, so this allowed me to do the following:

<div data-bind="css: color, css2: { 'translucent': number() < 10 }">static dynamic css classes</div>

I can't quite decide whether I still prefer this, or @Aleksey's answer, but this may be the only choice if you have multiple dynamic classes to add.

查看更多
手持菜刀,她持情操
7楼-- · 2019-01-08 22:50

Nice question, the problem seems to be the binding css isn't thought to mix the two kinds, color(): color() != '' doesn't work (would be nice).

I like @Simon_waver's answer approach, simple and practical.

Maybe at the time of the question wasn't supported (Idk) but with current knockout also combining the classes works: data-bind="css: computed"

viewModel.computed = ko.pureComputed(function() {
   return viewModel.color() + (viewModel.number() < 10 ? ' translucent' : '');
});
查看更多
登录 后发表回答