Search code examples
cssbootstrap-4

Make the dotted lines dynamic while having expand-collapse intact


How to make the dotted lines dynamic while having other functionalities (expand- collapse button on click of set1 or set2) intact. Do not want dotted line to go underneath data. So that it starts and ends like a connector between data. Fiddle showing the same: https://jsfiddle.net/vcjzn5hs/1/

Would appreciate your suggestion.

I am able to make dynamic dotted lines and expand-collapse work independently but not together. For your reference: Sample of how dynamic dotted lines should be http://jsfiddle.net/bwpugnd3/11/. But this example does not work along with expand collapse button.

Code snippet:

.flex {
  display: flex;
  justify-content: space-between;
}

.lev1,
.lev2,
.lev3 {
  position: relative;
  margin-top: 5px;
  margin-left: 15px;
}

.lev1::after,
.lev2::after,
.lev3::after {
  content: '';
  position: absolute;
  width: 80%;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin: 0 6px;
  height: 2px;
  color: lightgrey;
  margin-bottom: 4px;
  background-image: linear-gradient(to right, currentColor 2px, transparent 1px);
  background-size: 4px 2px;
}

.DataRightAlign {
  word-wrap: break-word;
  white-space: break-spaces;
  margin-right: 20px;
  margin-left: 10px;
  color: black;
}
<!--jquery and bootstrap-->

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
     <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />

    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>


<div class="itemDiv">
  <div>
    <a class="accordion-button " data-toggle="collapse" data-target=".lev1"> Set1 </a>
    <div class="collapse multi-collapse lev1 show">
      <p class="flex">
        <span>item1data</span>
        <span class="DataRightAlign"> Set1val here</span>
      </p>
    </div>
    <div class="collapse multi-collapse lev1 show">
      <p class="flex">
        <span>item2</span>
        <span class="DataRightAlign">Set1</span>
      </p>
    </div>
  </div>

  <div>
    <a class="accordion-button " data-toggle="collapse" data-target=".lev3">Set2 </a>
    <div class="collapse multi-collapse lev3 show">
      <p class="flex">
        <span>item3</span>
        <span class="DataRightAlign">Set2val</span>
      </p>
    </div>
  </div>


Solution

  • One approach, and frankly the easiest, is below with explanatory comments in the code; please note that I removed all CSS from your own demo because it made it slightly easier to create the demo, but – of course – there's nothing to stop you adding your own CSS back:

    /* a simple CSS reset to ensure the browser uses the same sizing
       algorithm for element sizes; including the width and padding
       within the declared sizes, also removing default margins
       and padding: */
    body {
      block-size: 100vh;
      padding-block: 1rem;
      padding-inline: 1.5rem;
    }
    
    .itemDiv {
      /* setting the inline-size of the element; inline-size is the axis
         on which inline-content (such as <span> elements) is laid out,
         and is perpendicular to the block-axis. In English the inline-axis
         is the horizontal axis running left-to-right. The clamp() function
         sets the preferred size of the element to 90%, with a 30rem
         minimum-size and 1100px maximum size: */
      inline-size: clamp(30rem, 90%, 1100px);
      /* centering the element on the inline-axis: */
      margin-inline: auto;
    }
    
    .flex {
      display: flex;
      gap: 0.5rem;
      /* spacing the elements out: */
      justify-content: space-between;
    }
    
    /* using the second span (noted in the HTML) to contain the line leading: */
    .flex > span:nth-child(2) {
      background-image:
        /* using a repeating-linear-gradient() as the background-image: */
        repeating-linear-gradient(
          /* 90degrees causes the gradient to run horizontally, from an
             origin on the left side: */
          90deg,
          /* currentColor is the currently-assigned color of the element, this
             runs from 0 to 2px: */
          currentColor 0 2px,
          /* and we switch to a transparent "color" which starts at 2px and
             runs for 3px until a point at 5px: */
          transparent 2px 5px
        );
      /* positioning the repeating-linear-gradient() at 0 on the inline axis,
         and at 100% minus 5px on the block-axis; 5px is something of a magic
         number to line the dots of the background image to the approximate
         baseline of the text in the adjacent elements: */
      background-position: 0 calc(100% - 5px);
      /* preventing the background from repeating: */
      background-repeat: no-repeat;
      /* setting the size of the background-image to be 100% of the width/inline-axis,
         and 2px on the vertical/block-axis: */
      background-size: 100% 2px;
      /* this causes the element to grow to fill all available space: */
      flex-grow: 1;
    }
    
    .accordion-button {
      /* if an element can be clicked, and is interactive, I tend to assign the
         following property and property-value to indicate that interactivity: */
      cursor: pointer;
    }
    <!--jquery and bootstrap-->
    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    
    
    <!--html -->
    <div class="itemDiv">
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev1"> Set1 </a>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item1data</span>
            <!-- I feel bad about it, but the easiest way is to add an element to hold
                 the leading: -->
            <span></span>
            <span class="DataRightAlign"> Set1val here</span>
          </p>
        </div>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item2</span>
            <span></span>
            <span class="DataRightAlign">Set1</span>
          </p>
        </div>
      </div>
    
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev3">Set2 </a>
        <div class="collapse multi-collapse lev3 show">
          <p class="flex">
            <span>item3</span>
            <span></span>
            <span class="DataRightAlign">Set2val</span>
          </p>
        </div>
      </div>
      
    </div>

    JS Fiddle demo.

    An alternative is to use a CSS generated content to hold the leading (the dots that lie between the item-data and set-value), this has problems though; again, explanatory comments are in the code:

    /* a simple CSS reset to ensure the browser uses the same sizing
       algorithm for element sizes; including the width and padding
       within the declared sizes, also removing default margins
       and padding: */
    body {
      block-size: 100vh;
      padding-block: 1rem;
      padding-inline: 1.5rem;
    }
    
    .itemDiv {
      /* setting the inline-size of the element; inline-size is the axis
         on which inline-content (such as <span> elements) is laid out,
         and is perpendicular to the block-axis. In English the inline-axis
         is the horizontal axis running left-to-right. The clamp() function
         sets the preferred size of the element to 90%, with a 30rem
         minimum-size and 1100px maximum size: */
      inline-size: clamp(30rem, 90%, 1100px);
      /* centering the element on the inline-axis: */
      margin-inline: auto;
    }
    
    .flex {
      display: flex;
      gap: 0.5rem;
      /* spacing the elements out: */
      justify-content: space-between;
    }
    
    .flex > span:first-child {
      /* here we use display: contents, which emulates "unwrapping" of the content
         from the matched element, and placing it directly into its parent-element;
         this means that both the <span> content and the ::after pseudo-element can
         take part in the flex layout, acting as siblings: */
      display: contents;
    }
    
    /* using the second span (noted in the HTML) to contain the line leading: */
    .flex > span:first-child::after {
      background-image:
        /* using a repeating-linear-gradient() as the background-image: */
        repeating-linear-gradient(
          /* 90degrees causes the gradient to run horizontally, from an
             origin on the left side: */
          90deg,
          /* currentColor is the currently-assigned color of the element, this
             runs from 0 to 2px: */
          currentColor 0 2px,
          /* and we switch to a transparent "color" which starts at 2px and
             runs for 3px until a point at 5px: */
          transparent 2px 5px
        );
      /* positioning the repeating-linear-gradient() at 0 on the inline axis,
         and at 100% minus 5px on the block-axis; 5px is something of a magic
         number to line the dots of the background image to the approximate
         baseline of the text in the adjacent elements: */
      background-position: 0 calc(100% - 5px);
      /* preventing the background from repeating: */
      background-repeat: no-repeat;
      /* setting the size of the background-image to be 100% of the width/inline-axis,
         and 2px on the vertical/block-axis: */
      background-size: 100% 2px;
      content: '';
      /* this causes the element to grow to fill all available space: */
      flex-grow: 1;
    }
    
    .accordion-button {
      /* if an element can be clicked, and is interactive, I tend to assign the
         following property and property-value to indicate that interactivity: */
      cursor: pointer;
    }
    <!--jquery and bootstrap-->
    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    
    
    <!--html -->
    <div class="itemDiv">
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev1"> Set1 </a>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item1data</span>
            <span class="DataRightAlign"> Set1val here</span>
          </p>
        </div>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item2</span>
            <span class="DataRightAlign">Set1</span>
          </p>
        </div>
      </div>
    
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev3">Set2 </a>
        <div class="collapse multi-collapse lev3 show">
          <p class="flex">
            <span>item3</span>
            <span class="DataRightAlign">Set2val</span>
          </p>
        </div>
      </div>
      
    </div>

    JS Fiddle demo.

    Unfortunately, the use of:

    .flex > span:first-child {
      display: contents;
    }
    

    means that we can no longer style the element with background-color, or border and so on; this is an unavoidable consequence of this approach.

    Finally, though, and thought of rather later than I'd like, we have the option of nesting flex-layouts as follows:

    /* a simple CSS reset to ensure the browser uses the same sizing
       algorithm for element sizes; including the width and padding
       within the declared sizes, also removing default margins
       and padding: */
    body {
      block-size: 100vh;
      padding-block: 1rem;
      padding-inline: 1.5rem;
    }
    
    .itemDiv {
      /* setting the inline-size of the element; inline-size is the axis
         on which inline-content (such as <span> elements) is laid out,
         and is perpendicular to the block-axis. In English the inline-axis
         is the horizontal axis running left-to-right. The clamp() function
         sets the preferred size of the element to 90%, with a 30rem
         minimum-size and 1100px maximum size: */
      inline-size: clamp(30rem, 90%, 1100px);
      /* centering the element on the inline-axis: */
      margin-inline: auto;
    }
    
    .flex {
      display: flex;
      gap: 0.5rem;
      /* spacing the elements out: */
      justify-content: space-between;
    }
    
    .flex > span:first-child {
      /* we assign display: flex to the element, this causes it to lay its
         own children out according to the flex layout properties: */
      display: flex;
      /* this causes the element to grow to fill all available space within
         its parent: */
      flex-grow: 1;
      /* we inherit the gap property from the parent, so that the gaps here
         are the same as the gaps between this element and its sibling(s): */
      gap: inherit;
    }
    
    .flex > span:first-child::after {
      /* again, using the background image to create the leading: */
      background-image:
        /* using a repeating-linear-gradient() as the background-image: */
        repeating-linear-gradient(
          /* 90degrees causes the gradient to run horizontally, from an
             origin on the left side: */
          90deg,
          /* currentColor is the currently-assigned color of the element, this
             runs from 0 to 2px: */
          currentColor 0 2px,
          /* and we switch to a transparent "color" which starts at 2px and
             runs for 3px until a point at 5px: */
          transparent 2px 5px
        );
      /* positioning the repeating-linear-gradient() at 0 on the inline axis,
         and at 100% minus 5px on the block-axis; 5px is something of a magic
         number to line the dots of the background image to the approximate
         baseline of the text in the adjacent elements: */
      background-position: 0 calc(100% - 5px);
      /* preventing the background from repeating: */
      background-repeat: no-repeat;
      /* setting the size of the background-image to be 100% of the width/inline-axis,
         and 2px on the vertical/block-axis: */
      background-size: 100% 2px;
      content: '';
      /* as with the parent element, we use flex-grow to allow the pseudo-element
         to grow to fill the available space: */
      flex-grow: 1;
    }
    
    .accordion-button {
      /* if an element can be clicked, and is interactive, I tend to assign the
         following property and property-value to indicate that interactivity: */
      cursor: pointer;
    }
    <!--jquery and bootstrap-->
    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    
    
    <!--html -->
    <div class="itemDiv">
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev1"> Set1 </a>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item1data</span>
            <span class="DataRightAlign"> Set1val here</span>
          </p>
        </div>
        <div class="collapse multi-collapse lev1 show">
          <p class="flex">
            <span>item2</span>
            <span class="DataRightAlign">Set1</span>
          </p>
        </div>
      </div>
    
    
      <div>
        <a class="accordion-button " data-toggle="collapse" data-target=".lev3">Set2 </a>
        <div class="collapse multi-collapse lev3 show">
          <p class="flex">
            <span>item3</span>
            <span class="DataRightAlign">Set2val</span>
          </p>
        </div>
      </div>
      
    </div>

    JS Fiddle demo.

    References: