4

I have a parent View model:

var monthVM = function (mData) {
this.Id = mData.Id;
this.Name = mData.Name;
if (mData.Days != null) {
        $.each(mData.Days, function (index, item) {
            var newDay = new dayVM(item, index);
            this.Days.push(newDay );
        });
    }
}

And dayVM:

var dayVM= function (dData, index) {
    this.Id=dData.Id;
    this.Mon = ko.observable(dData.Mon);
    this.Tue = ko.observable(dData.Tue);
    this.Wed = ko.observable(dData.Wed);
    this.Thu = ko.observable(dData.Thu);
    this.Fri = ko.observable(dData.Fri);
    this.Sat = ko.observable(dData.Sat);
    this.Sun = ko.observable(dData.Sun);
    
    this.didOnMon = function (item) {       
        if (item.Mon() == false) item.Mon(true);
        else item.Mon(false);
        //other stuff
    }
    this.didOnTue = function (item) {       
        if (item.Tue() == false) item.Tue(true);
        else item.Tue(false);
        //other stuff
    }
}

The HTML binding

<div class="row">
   <div class="col-9 btn-group">
        <button data-bind="css: Mon() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeMon">Mon</button>
        <button data-bind="css: Tue() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeTue">Tue</button>
        <button data-bind="css: Wed() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeWed">Wed</button>
        <button data-bind="css: Thu() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeThu">Thu</button>
        <button data-bind="css: Fri() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeFri">Fri</button> div
        <button data-bind="css: Sat() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeSat">Sat</button>
        <button data-bind="css: Sun() == false ? 'btn btn-white btn-xs mt-1' : 'btn btn-dark btn-xs mt-1', click:changeSun">Sun</button>
   </div>
</div>

I need to add a border like grouping the buttons that are dark, one next to other, but still keeping the design: the buttons are filling col-9 div like enter image description here

Every time one of the buttons becomes white, it should adjust the outline for the other dark ones.

I've tried using

.btn-dark-custom {
   outline: 1px solid black;
   outline-offset: 2px;
}

<button class="btn btn-dark btn-dark-custom" onclick="changeWed()">Wed</button>

but it draws the border for each dark button, not as a group. I've also tried adding border lines, in JS, but I cannot add the offset to it.

LAffair
  • 1,968
  • 5
  • 31
  • 60
  • I assume these classes come from a library? Any reason you can write in the outline on the btn-dark class or just add your own dark/white classes with the outline rule you want? – dale landry Jan 04 '22 at 01:13
  • Inline events like that are not very modern. Most the time it's best to separate the JS from the markup (like use JS's `.addEventListener()`). Also I'm guessing your functions could benefit from being more general like `changeDay("Mon")` instead of having a different function for each day... – Zach Saucier Jan 04 '22 at 01:50
  • @dalelandry The classes are from a library. I've tried adding outline to the class but it draws the line around a single button and it should somehow group them – LAffair Jan 04 '22 at 07:27

3 Answers3

3

In pure css it's a bit complex, but here is a possibility (Jquery is used just for the click class toggle) :

$('.elem').click(function(){$(this).toggleClass('active')})
/* BASE */
.elem {
  float:left;
  background: #eee;
  padding: 5px 10px;
  cursor: pointer;
  position: relative;
}
.elem.active {
  background: #444;
  color: #eee;
}

/* PSEUDO ELEMENT */
.elem::after {
  position: absolute;
  z-index: 1;
  left: -4px;
  top: -4px;
  right: -4px;
  bottom: -4px;
  border: 2px solid black;
}

/* SHOW PSEUDO ELEMENT IN .ACTIVE */
.elem.active::after {
  content: '';
}

/* SELECT .ACTIVE BEFORE OTHER .ACTIVE */
.elem.active + .elem.active::after {
  border-left: none;
  border-right: none;
}

/* SELECT .ACTIVE FIRST CHILD OR FIRST ELEM AFTER NOT ACTIVE */
.elem.active:first-child::after,
.elem:not(.active) + .elem.active::after {
  border-right: none;
}

/* SELECT LAST CHILD */
.elem.active:last-child::after {
  border-right: 2px solid black !important;
}

/* TRICKS LAST .ACTIVE SUCCESSIVE */
.elem.active + .elem:not(.active)::after {
  content: '';
  border: none;
  border-left: 2px solid black;
  left: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <div class="elem active">Mon</div>
  <div class="elem active">Tue</div>
  <div class="elem">Wed</div>
  <div class="elem active">Thu</div>
  <div class="elem active">Fri</div>
  <div class="elem active">Sat</div>
  <div class="elem">Sun</div>
</div>
Liberateur
  • 1,337
  • 1
  • 14
  • 33
  • Nice trick of using left border of non-active element! If you set `border-right: none` in `.elem::after` itself, then you won't need rule `.elem.active:first-child::after, .elem:not(.active)+.elem.active::after` and you can remove the line from `.elem.active + .elem.active::after` as well. – the Hutt Jan 06 '22 at 14:49
1

The outline is applied to entire element so it will be drawn over each element. It is drawn on top of element so you can't hide it. The outline created with the outline properties is drawn "over" a box, i.e., the outline is always on top and doesn’t influence the position or size of the box, or of any other boxes.ref

Pure CSS solution You can create outline like effect using ::before pseudo element:

body {
  padding: 1rem;
}

 :root {
  --outline-color: black;
  --cover-border: 2px solid white;
}

.btn-light {
  background-color: #ddc !important;
  z-index: -1000;
}

.btn-dark {
  position: relative;
  border-radius: 0px !important;
  border-top: var(--cover-border) !important;
  border-bottom: var(--cover-border) !important;
  border-right: var(--cover-border) !important;
  border-left: none !important;
}

.btn-dark::before {
  content: '\a0';
  display: block;
  position: absolute;
  top: -4px;
  left: -4px;
  right: -4px;
  bottom: -4px;
  background-color: var(--outline-color);
  border-radius: 0.25rem;
  z-index: -100;
}

.btn-dark:first-child,
.btn-light+.btn-dark {
  border-top-left-radius: 0.25rem;
  border-bottom-left-radius: 0.25rem;
  border-left: var(--cover-border) !important;
}

.btn-dark:last-child {
  border-top-right-radius: 0.25rem;
  border-bottom-right-radius: 0.25rem;
  border-right: var(--cover-border);
}

.btn-dark+.btn-dark {
  margin-left: -2px !important;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet" />
<div class="row">
  <div class="col-9 btn-group">
    <button class="btn btn-dark btn-xs mt-1">Mon</button>
    <button class="btn btn-dark btn-xs mt-1">Tue</button>
    <button class="btn btn-dark btn-xs mt-1">Wed</button>
    <button class="btn btn-light btn-xs mt-1">Thu</button>
    <button class="btn btn-light btn-xs mt-1">Fri</button>
    <button class="btn btn-dark btn-xs mt-1">Sat</button>
    <button class="btn btn-dark btn-xs mt-1">Sun</button>
  </div>
  <div class="mt-1">&nbsp;</div>
  <div class="col-9 btn-group">
    <button class="btn btn-dark btn-xs mt-1">Mon</button>
    <button class="btn btn-light btn-xs mt-1">Tue</button>
    <button class="btn btn-dark btn-xs mt-1">Wed</button>
    <button class="btn btn-light btn-xs mt-1">Thu</button>
    <button class="btn btn-dark btn-xs mt-1">Fri</button>
    <button class="btn btn-light btn-xs mt-1">Sat</button>
    <button class="btn btn-dark btn-xs mt-1">Sun</button>
  </div>
  <div class="mt-1">&nbsp;</div>
  <div class="col-9 btn-group">
    <button class="btn btn-light btn-xs mt-1">Mon</button>
    <button class="btn btn-dark btn-xs mt-1">Tue</button>
    <button class="btn btn-dark btn-xs mt-1">Wed</button>
    <button class="btn btn-light btn-xs mt-1">Thu</button>
    <button class="btn btn-dark btn-xs mt-1">Fri</button>
    <button class="btn btn-dark btn-xs mt-1">Sat</button>
    <button class="btn btn-light btn-xs mt-1">Sun</button>
  </div>
  <div class="mt-1">&nbsp;</div>
  <div class="col-9 btn-group">
    <button class="btn btn-light btn-xs mt-1">Mon</button>
    <button class="btn btn-light btn-xs mt-1">Tue</button>
    <button class="btn btn-light btn-xs mt-1">Wed</button>
    <button class="btn btn-light btn-xs mt-1">Thu</button>
    <button class="btn btn-light btn-xs mt-1">Fri</button>
    <button class="btn btn-light btn-xs mt-1">Sat</button>
    <button class="btn btn-light btn-xs mt-1">Sun</button>
  </div>
  <div class="mt-1">&nbsp;</div>
  <div class="col-9 btn-group">
    <button class="btn btn-dark btn-xs mt-1">Mon</button>
    <button class="btn btn-dark btn-xs mt-1">Tue</button>
    <button class="btn btn-dark btn-xs mt-1">Wed</button>
    <button class="btn btn-dark btn-xs mt-1">Thu</button>
    <button class="btn btn-dark btn-xs mt-1">Fri</button>
    <button class="btn btn-dark btn-xs mt-1">Sat</button>
    <button class="btn btn-dark btn-xs mt-1">Sun</button>
  </div>


</div>

Possible JavaScript solution You can set outline to .btn-group in CSS. And every time user toggles a button, group all black buttons in their own .btn-group wrappers.
For example, if user deselects Thursday and Friday you can have DOM like this:

body {
  padding: 1rem;
}

.my-group {
  white-space: nowrap;
}

.btn-group {
  padding: 0px !important;
  outline-offset: 2px;
  outline: 1px solid black;
  border-radius: 0.25rem !important;
}

.btn-group>.btn {
  margin: 0px !important;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet" />
<div class="row">
  <div class="col-9 my-group">
    <div class="btn-group">
      <button class="btn btn-dark btn-xs mt-1">Mon</button>
      <button class="btn btn-dark btn-xs mt-1">Tue</button>
      <button class="btn btn-dark btn-xs mt-1">Wed</button>
    </div>
    <button class="btn btn-light btn-xs mt-1">Thu</button>
    <button class="btn btn-light btn-xs mt-1">Fri</button>
    <div class="btn-group">
      <button class="btn btn-dark btn-xs mt-1">Sat</button>
      <button class="btn btn-dark btn-xs mt-1">Sun</button>
    </div>
  </div>
</div>


You can write javascript to have structure like above every time user selects/deselects a button.

the Hutt
  • 16,980
  • 2
  • 14
  • 44
0

try pseudo-elements like ::before, ::after. Here is an example:

.black-button {position: relative;}
.black-button::before {
    position: absolute;
    left: -2px;
    top: -2px;
    right: -2px;
    bottom: -2px;
    border: 2px solid black;
}
VladykoD
  • 284
  • 2
  • 8
  • I need to add an outline to a group of buttons with the same class. Border doesn't have the offset property – LAffair Jan 04 '22 at 07:59
  • then you need to use js. Add some empty divs and with js calculate offset and width for every group. Place them absolute on top of the main layer. – VladykoD Jan 04 '22 at 22:33