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
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.
- A
Button
viewmodel that tracks its ownactive
state and allows to toggle it - A
ButtonList
viewmodel that contains the buttons and the dynamically calculatedbuttonGroups
. - 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>