Home > Mobile >  KnockoutJS foreach binding add outline around selected items
KnockoutJS foreach binding add outline around selected items

Time:01-04

I have a knockout observable array of items displayed as buttons:

<div  data-bind="foreach: myList">
     <button data-bind="css: Sel == false ? 'btn btn-white' : 'btn btn-dark', click:$root.makeChange, text:$data.Id, attr:{'id':'h_' $data.Id}"></button>
</div>

for which I need to add a outline as enter image description here for items that have Sel set to true.

I've tried using outline: 2px solid black; outline-offset: 1px; but it adds outline around each button, not for the group and the right line of the outline is drawn behind the next button.

I've also tried using borders, and adding them with JS, but I don't have the space between the button and border.

CodePudding user response:

The outline property feels like the right approach to this issue, but it only allows outlining a single element, and it only allows complete outlines.

That means the active elements must be wrapped in a container, and that container must receive the outline.

This can be done with a computed observable that turns a flat list:

[
    {text: 'Mon', active: true},
    {text: 'Tue', active: true},
    {text: 'Wed', active: true},
    {text: 'Tue', active: false},
    {text: 'Fri', active: false},
    {text: 'Sat', active: true},
    {text: 'Sun', active: true}
]

into a nested one:

[
  [
    {text: 'Mon', active: true},
    {text: 'Tue', active: true},
    {text: 'Wed', active: true}
  ],
  [
    {text: 'Tue', active: false},
    {text: 'Fri', active: false},
  ],
  [
    {text: 'Sat', active: true},
    {text: 'Sun', active: true}
  ]
]

depending on which item is active.

Here is how I would implement that in knockout.

  1. A Button viewmodel that tracks its own active state and allows to toggle it
  2. A ButtonList viewmodel that contains the buttons and the dynamically calculated buttonGroups.
  3. A view that creates .btn-group and .btn-group.active divs based on that.

Interactive sample below:

function Button(params) {
  this.text = params.text;
  this.active = ko.observable(params.active);
  this.toggle = () => this.active(!this.active());
}

function ButtonList(params) {
  this.buttons = ko.observableArray(params.buttons.map(b => new Button(b)));
  this.buttonGroups = ko.pureComputed(() => {
    const groups = [];
    let prev = null, group = null;
    this.buttons().forEach(b => {
      if (b.active() !== prev) {
        group = [];
        groups.push(group);
        prev = b.active();
      }
      group.push(b);
    });
    return groups;
  });
}

const vm = new ButtonList({
  buttons: [
    {text: 'Mon', active: true},
    {text: 'Tue', active: true},
    {text: 'Wed', active: true},
    {text: 'Tue', active: false},
    {text: 'Fri', active: false},
    {text: 'Sat', active: true},
    {text: 'Sun', active: true}
  ]
});
ko.applyBindings(vm);
.btns {
  display: flex;
}
.btn-group {
  display: flex;
  outline-offset: 2px;
}
.btn-group.active {
  outline: 2px solid black;
}
.btn-group > * {
  width: 50px;
  padding: 5px;
  margin: 0;
  text-align: center;
}
.btn-group > .active {
  background-color: black;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div  data-bind="foreach: buttonGroups">
  <div  data-bind="foreach: $data, css: {active: $data[0].active}">
    <div data-bind="click: toggle, text: text, css: {active: active}"></div>
  </div>
</div>

  •  Tags:  
  • Related