11

I'm developing a control aiming to display activity blocks on a calendar grid. It is a plain JavaScript/CSS, relying on jQuery for DOM manipulation and such. Here is a picture:

Calendar control in need of proper layout

There are bands A and B, each containing a couple of activity blocks ([1,2], [3,4]). Activities can either overlap or follow each other sequentially. My goal is to place the activity blocks accordingly: if activities overlap like [1,2], I want them wrap and sit on top of one another like pictured; if they are sequential like [3,4], I want them side by side, NOT like pictured.

Additionally, I would like bands' (A,B) height to adjust automatically. Thus, a band with overlapped activity blocks would have twice the height of the band with sequential ones.

At this point I can get either one or the other.

If activity blocks have display: block;, the activities wrap regardless whether they actually overlap (3,4). Band's height does get adjusted accordingly.

If activity blocks have display: inline-block;, activities share the same height so one gets hidden by the other (1,2). The band stays one activity block in height.

Everything is a div and here is the relevant HTML/CSS:

<div class="band">
  <div class="activity-block" style="left: 331.429px; width: 11.4286px;"></div>
  <div class="activity-block" style="left: 160px; width: 297.143px;"></div>
</div>
<div class="band">
  <div class="activity-block" style="left: 205.714px; width: 22.8571px;"></div>
  <div class="activity-block" style="left: 365.714px; width: 3417.14px;"></div>
</div>
<div class="band"></div>

Bands (A, B):

.band {
  min-height: 20px;
}

Activity blocks (1,2,3,4):

.activity-block {
  background-color: #66C6C2;
  border-radius: 3px;
  display: block;
  height: 20px;
  margin-bottom: 5px;
  margin-top: 5px;
  position: relative;
}

left and width of an activity block are set from JavaScript.

I would like to get there using only two CSS classes, one for the band and one for the activity blocks. I realize the goal can be achieved using JavaScript, but I wonder if this is something possible via CSS only.

Nic Nilov
  • 5,056
  • 2
  • 22
  • 37
  • It's probably not possible. – bjb568 Dec 16 '15 at 15:40
  • In that case a definitive answer explaining why exactly it is not possible, would be most helpful. – Nic Nilov Dec 16 '15 at 16:38
  • *Why?* Because CSS just doesn't do everything. We have JS for a reason. Maybe they'll add that capability to CSS4 or something. – bjb568 Dec 16 '15 at 19:44
  • Just because `probably` is not good for an answer. To this degree of approximation I was able to arrive myself. But, since there are people far more knowledgeable in CSS than me, an exact answer remains something to hope for. – Nic Nilov Dec 16 '15 at 19:54
  • It's hard to prove a negative. Maybe you can do it with floats and flexbox and absolute inside 5 extra wrapper divs and then just add some special casing for a few major browsers using CSS browser targeting… But who cares? You can also implement [Rule 110](https://en.wikipedia.org/wiki/Rule_110) with CSS. But there is no CSS feature made for that and no simple solution. – bjb568 Dec 16 '15 at 20:00
  • I should agree with bjb568 CSS cannot decide alone if there is an overlap or not. You have to decide outside of CSS ("jQuery to the Rescue") if there is an overlap and then accordingly set two different classes for overlap or newline. This should not be a drawback, as you need to do the sorting of the activity-blocks outside of CSS as well. – yunzen Dec 22 '15 at 08:45
  • Actually CSS can decide on overlaps if floats are used. It can't decide on overlaps while keeping desired horizontal coordinate simultaneously, that's true. – Nic Nilov Dec 22 '15 at 18:53

4 Answers4

2

See if it suits your demands: you want to set positions in javascript and let css break lines for you if there is an overlap, right?

The idea is to put each block to float to one side and set the margins with your javascript code. But the band div must be float, or else it won't grow to accomodate the line break that happens in case of overlap (more on this in Floating elements within a div, floats outside of div. Why?).

<html>
<head>
    <style>
        .activity-block {
            background-color: #66C6C2;
            border-radius: 3px;
            height: 20px;
            margin-bottom: 5px;
            margin-top: 5px;
        }
        .band {
            min-height: 30px;
            border: solid 1px;
            width: 1200px;
            float: left;
        }
        .fl {
            float: left;
        }
        .fr {
            float: right;
        }
    </style>
</head>
<body>
    <div class="band">
        <div class="activity-block fl" style="margin-left: 331px; width: 81px;">1</div>
        <div class="activity-block fr" style="margin-right: 160px; width: 497px;" id="x2">2</div>
    </div>
    <div class="band">
        <div class="activity-block fl" style="margin-left: 205px; width: 22px;">3</div>
        <div class="activity-block fr" style="margin-right: 365px; width: 417px;" id="x4">4</div>
    </div>
</body>

I made a jsFiddle for testing:

https://jsfiddle.net/vdusch4y/6/

Some restrictions you need to check:

  1. No more than 2 blocks per band.
  2. The second block in the div floats to the right and represents an activity that starts after the first.
  3. Instead of setting left in your javascript, you set the margin-right of the second block inside each band, according to: (margin-right) = (band width) - (calculated block left) - (calculated block width). For symmetry, I am using margin (left in the case) for the first block too.

In the jsFiddle example, try changing the band width, or block margins/widths, to see how the break occurs automatically, leaving the band with a bigger height.

Hope it helps!

Community
  • 1
  • 1
Eduardo Poço
  • 2,819
  • 1
  • 19
  • 27
  • This seems to be a promising approach, thanks! The restriction to have no more than two blocks is not feasible, but I will try and see what can be done using floats. – Nic Nilov Dec 18 '15 at 11:00
  • I could not figure out the rule for placing more than 3 blocks. If the third one does not overlap any of the other two, does it go on the first line? – Eduardo Poço Dec 18 '15 at 12:11
  • Float elements that are in the same side interact with each other: if the first one goes down, the other goes too. That's why I only got two blocks, one to the left and one to the right. Something like a 'float: up' would solve, if existed. – Eduardo Poço Dec 18 '15 at 12:30
  • If there is no overlapping, a single band line can contain any number of blocks. Let's say optimisation of blocks placement within the line is not a concern and suppose the blocks would be sorted by least size. It will still require calculation of each blocks' left margin which would not be possible without knowing the line the block would end up on. It seems that within boundaries of the task, CSS-only solution is indeed not possible. – Nic Nilov Dec 18 '15 at 15:13
  • I've run the same problem building blocks to display several events with different durations in a calendar, while trying to get the minimal stack height. This seems quite similar if not exactly the same. I solved it just sorting the events in the appropiate order, all floated to the left and setting an attribute with the number of days. Probably same results could be get using javascript, but: Is possible in your case use the appropiate "ordering logic" at server side? In that case there is a solution with quite simple css and no javascript if you want it. – miguel-svq Dec 19 '15 at 18:36
  • I can order elements on backend. How do you position elements within bands in relation to the scale though? Just floating all to the left would work to get minimal stack height but their horizontal position would not be right without some kind of workaround. If you have a solution, please post as an answer. – Nic Nilov Dec 21 '15 at 13:43
1

As commenting users suggested there is no way to do this in CSS only. CSS works in let's call it "rows". What you are trying to achieve is to based on some condition place content in one or two rows, that is something that CSS cannot handle. You can only specify how content behaves in its own row. The closest you can get is to define elements as inline-block with proper left padding when not overlapping and block when they are, but you cannot do that without using JavaScript for handling the logic.

preator
  • 984
  • 5
  • 6
0

if you want them overlapping/on the same line besides each other the simplest way would be to add position absolute to the activity-block and position relative to the band. let the left & width be set through javascript. I hope this would resolve your issue

.band {
 position: relative;
}
.activity-block {
 position: absolute;
}
Rovi
  • 259
  • 1
  • 3
0

If each block will either consist one type of activities either follow or queued , then just by changing the class applied to the block it can be achieved.Please check the following code.Hope it will work for you.

HTML CODE:

<div class="band column">
  <div >one</div>
  <div  style="margin-left:130px">two</div>
  <div  style="margin-left:30px">three</div>
</div>
<div class="band row">
  <div>one</div>
  <div>two</div>
  <div>three</div>
</div>

CSS :

.band {
      display: -webkit-flex;
    display: flex;
    background-color: white;
      align-items: flex-start;
    border:1px solid black;
}
.band.row{-webkit-flex-direction: row; flex-direction: row;}
.band.column{-webkit-flex-direction: column; flex-direction: column;}
.band div{background-color: cornflowerblue;
    max-width: 100%;
    height: 100px;
    margin: 10px;}
.band div:nth-child(1){background-color: #009688;width:130px;}
.band div:nth-child(2){background-color: #0000FF;width;50px}
.band div:nth-child(3){background-color: #990000;width:200px}

Do distinguish the activities i have used different background color.

You can check the demo Fiddle

nitish koundade
  • 801
  • 5
  • 12
  • Thanks for the demo! In order to assign a proper class to a block I would have to analyze whether there is an overlap beforehands, either on backend or in browser's JavaScript. This is possible but it is precisely what I would like to avoid. – Nic Nilov Dec 21 '15 at 16:02