Absolute position horizontally, stacking vertically

I’m developing a booking calendar and have an interesting requirement. I want to absolutely position div’s (bookings) of a fixed width using left/right offsets, but have those div’s stack one on top of the other if they overlap. If they don’t overlap, then they can just sit next to each other.

I was hoping the app wouldn’t have to support overlapping bookings, but it does. I’m thinking this is probably not possible using CSS, so may need to use server-side calculations or javascript.

Can’t you do it using floats instead of absolute positioning?

How would I position the floats though? How would I produce the following using floats instead of absolute positioning?

<div class="booking" style="left: 10%; width: 15%"></div>
<div class="booking" style="left: 40%; width: 10%"></div>

The following what?

Edited. Used the wrong code syntax. Use to another forum.

I think it would be helpful if you could post an annotated screenshot, or a graphic pseudo-screenshot, showing what you would like the page to look like and why. I have no idea why specific withs might have to be calculated by JS, for example. You have an image in your mind of what a “booking calendar” looks like and how you need it to be rendered. I have neither. Fill in my voids and I’ll bet someone else might find it helpful, too.

No prob. It happens . Best helpful hint I can offer is that if you can’t see what you posted after clicking the [Save/Send] Reply button, we can’t see it either. :tongue:

1 Like

Here’s an example of what I’m currently achieving server-side by calculating top and bottom offsets. Would be really nice if the browser could do some of the work for me.

Thanks for posting the image.

I don’t use either position or width very often.

I was trying to grok how an element 10% from left 15% wide (right side would be at 25%) could possibly overlap an element at 40% left.

What you mean are what I’d call “more than one appointment scheduled for the same block of time” eg.

If there were appts for 8-9 10-11 and 1-3, they would all be in the same row.
If there were appts for 8-10, 11-1, 2-3, and also 9-12, the 9-12 would be in another row.

Any reason why each appt can’t have its own row ordered by start time?
I think it would be massively easier to code up.

Either that or maybe a row for for each possible appt during any given time block. eg.

If the most appts that can be between 11-2 is 3 appts = 3 rows.

It might be easier for users that way. eg. If i was looking for Monday 1-2 and saw all 3 rows for that block were filled, I’m out of luck, else I’m in.

2 Likes

That looks like a perfect use for a grid design pattern. You’ve got a grid that you need to break up into 24 parts, and you position your elements with an appropriate margin and width.

There are hundreds of good examples of css grids out there. Take a look and see how you can apply it in this case?

For example, I usually don’t point people to bootstrap, but you could use their grid as a starting point, but they only break their grids into 12, so you’d need to do some math to double the number of columns, but the basic concept is the same… http://getbootstrap.com/css/#grid

I haven’t a clue why you would need to calculate offsets server-side so I must be missing something important here? :slight_smile:

Your image just looks like a standard table and tabular data. I don’t see any of the overlaps that you mention either. I would not use absolute positioning for this unless you are stacking duplicate entries into the same cell perhaps but that would seem an odd process.

1 Like

The example doesn’t overlap, you’re right, but my point was how would you achieve that using anything but absolute positioning. You’d have to calculate margins and relative distance from neighbouring elements if using floats of anything else that respects document flow, etc, all the things I’m trying to avoid.

@PaulOB @DaveMaxwell In the example, all the bookings conveniently align with the background grid, but they don’t have to. You could in theory book from 9:51am to 10:47am. That’s why I need to use offsets. It can’t be achieved with a grid layout. The problem is, when you have one booking from 9:30am to 10:30am, and another from 10am to 11am (overlapping), how could you make them automatically stack/wrap so they don’t overlap (like in the example, achieved using server-side code). I honestly don’t think it’s possible using CSS, but wanted to ask.

That squashes my idea.

I assumed time blocks would logically be half-hourly or maybe quarter-hourly only.
(I have never made a 13:07 appointment)

Then using table cells like

<td class="b900-930"></td>
<td class="b930-1000"></td>
<td class="b1000-1030"></td>

Then script could use array functions, figure out what needed to go where, if there were any overlapping appointments, sort the rows adding a class of “booked” to the applicable cells.

Assuming an 8 hour work day, that would be 16 to 32 cells per row.
There is no way I’d look forward to working with a table row with 480 cells.

I think before considering how to style with CSS more information regarding any limitations of the script would be useful.
i.e. Where is the information coming from, what information is available to use, and in what format s it?

It’s coming from a database. Start datetime and end datetime is all the information that should be needed. I’ve already got a working solution where I work out which bookings are overlapping, and which produces the desired results with the least number of additional “rows”. The screenshot I posted earlier shows that solution in action.

I was hoping that flexbox or some other new IE11+ supported CSS would be able to achieve something that can be used to position absolutely horizontally, but which would stack vertically in the event of an overlap. Like how I imagine an absolutely positioned float would work, not that such a thing exists.

I always find working with datetimes to be an adventure.

Because I don’t have a database to get values from I created a multi-dimensional array containing random data. I came up with arrays like this (sorted by start)

    [1] => Array
        (
            [id] => 3
            [start] => 2016-12-21 11:30
            [end] => 2016-12-21 13:00
            [duration] => 1.30
        )

    [2] => Array
        (
            [id] => 1
            [start] => 2016-12-21 12:30
            [end] => 2016-12-21 14:30
            [duration] => 2.00
        )

    [3] => Array
        (
            [id] => 2
            [start] => 2016-12-21 13:00
            [end] => 2016-12-21 15:00
            [duration] => 2.00
        )

I found it relatively easy to do the logical next step and use start to determine the left position and duration to determine the width.

i can sort the array in various ways easily enough (eg. why I formatted the duration hm as a float), the sticking point that I keep going back to is trying to find a way to determine the optimum positions

Perhaps a CSS guru can think of a way to use border collapse or some other CSS to display “filled” into “empty” time blocks when they would fit.

But I can’t help thinking the better approach would be to create the optimum sequence first and then position them.

1 Like

Horizontal positioning is certainly the easier part.

The logic I used for vertically stacking was to sort all the bookings by start_date (i.e. left offset), then loop until all bookings had been assigned to a stacking level. Here’s the ruby code for that:

entries.sort_by! { |entry| entry[:left_offset] }
level = 0
loop do
  remaining_entries = entries.select { |entry| entry[:level].nil? }
  break if remaining_entries.length == 0
  max_right_offset = 0
  level += 1
  remaining_entries.each do |entry|
    if entry[:left_offset] >= max_right_offset
      entry[:level] = level
      max_right_offset = 100.0 - entry[:right_offset]
    end
  end
end

I don’t think this question has much to do with CSS and html as such but is more a logic programming exercise. It obviously makes it easier for your logic if you absolutely place the elements but absolute positioning is not very good for a responsive layout or if indeed a user resizes their text only and the layout will break.

I would have been inclined to use a table structure for the display (because that’s what represents the data best) and the logic would then be needed to put the data in the right cell, group of cells, or partial cells of course. You could have a cell for every hour and then just offset the element inside the cell with a left or right margin to counter for minutes etc. Once the table is complete it is robust and could be made responsive with a bit of work unlike the absolutely positioned table.

I would usually create the table as I wanted it to look in CSS and html first and check that it does what I want and then I would give it to the programmer to code dynamically :slight_smile:

However I think that method will take a lot more programming than your existing method so you have to weigh up the pros and cons :slight_smile:

I wrote the code I used in PHP, but I think it’s close to what your Ruby is doing.

Remember, I am not a CSS guru and I never use “absolute” for layout, only extremely rarely for something like a tooltip thingy.

No matter what CSS I tried using to get blocks in lower rows to “jump up” to where they would have fit in an upper row, I failed.

I agree that scrapping the absolute positioning and displaying in table layout would be much easier to style.

Of course that means the code generating the output needs to be more complex.

I used 8 hours in half hour blocks with “appointmemts” ranging from one half to two hours, ordered by start time. If a subsequent “appointment” started after the previous ended, it went into the same row, else into a new row. eg. with random 10 member data sets.

cell content numbers are:
row, cell, offset%, width%

1 Like

CSS can’t do that as it doesn’t have any complex logic like that. You can of course use flexbox and re-order the html to fit as required but once again the choice of re-ordering has to be done dynamically as css won’t know where stuff can fit unless you tell it.

This is primarily a programming task and depending on on how you wish to program the answer the method you use to display is almost secondary. :slight_smile:

If for example in your programming you collated all the appointments and worked out where they could fit in each row and then you could add width and margin-left (or right) to each and then float everything. Rows would wrap automatically but you would need to ensure that each row always adds up to 100% and is in the correct order. Of course entries would need to be the same height or floats snag and if not then inline-block or flexbox would be better.

However semantically the data is primarily tabular and I would have a hard time convincing myself to use any element other than a table for this task. The fact that it’s not easy to program should not be the deciding factor.

If using absolute positioning to lay out the items then the flow of the document would need to be preserved in some way (possibly by finding the height of the rows and then setting a non positioned parents height in ems to match).

It’s an interesting problem but the programming task is way beyond me so I can offer styling suggestions.:wink:

1 Like

I think if I had gone with a table it would have been more repetitive in some ways, but since I’m more familiar with table layout than I am with either position or float it would have taken less time.

I gave up on trying to get position working, sometimes something would be too far to the left while another was too far to the right.

I finally went with float and rows separated by clearfix divs.

After multi-sorting the data set by left-most, narrowest, it was easy to put them in the same row if they fit else start a new row. The tricky part was moving lower blocks up when they would fit.

I needed to write a “memory” variable for each row to hold its current right-most value, and temporary row arrays. Then loop through the rows, determine if they would fit, and if so move them and update the right-most value for that row array. .

With a 10 member random data set pre back-end there were typically 6 to 8 rows.
I was able to get the average down to about half that, but as said, done by back-end control logic rather than CSS magic.

3 Likes

I don’t want to post a “show me how to do this the wrong way” post.
i.e. how to display tabular data without using a tabular layout.
But maybe you can see some glaring issues I’m blind to.

You can see where I reduced font-size to help with “float snag”. Which I guess wouldn’t be an issue if the “cells” had no content.
And there still is mis-alignment of the pseudo-cells.
IMHO it seems to be extremely fragile.

<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Wardrop</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- 
<meta http-equiv="refresh" content="4;URL='http://localhost/test/wardrop.php'" />   
-->
<style type="text/css">
* {
 outline: 1px solid #F33;
 padding: 0;
 border: 0;
 margin: 0;
 box-sizing: border-box;
}
body > div:nth-of-type(1) {
 position: absolute;
 background-color: #EFE;
 min-width: 100%;
}
.row {
 display: block;
 position: relative;
 width: 100%;
}

.thead { 
 display: inline-block;
 position: relative;
 margin: 0 -0.2%;
 background-color: #FEE;
 font-size: 0.8em;
 text-align: center;
 min-width: 12.5%;
}

.data {
/* display: inline-block;
 margin: 0 -0.2%;
 position: relative; */
 font-size: 0.9em;
 float: left;
 background-color: #DDF;
}
.clearfix {
 min-width: 100%;
 clear: both;
}
</style>
</head>
<body>
<div>
<div class="row">
	<span class="thead">0-12.5</span>
	<span class="thead">12.5-25</span>
	<span class="thead">25-37.5</span>
	<span class="thead">37.5-50</span>
	<span class="thead">50-62.5</span>
	<span class="thead">62.5-75</span>
	<span class="thead">75-87.5</span>
	<span class="thead">87.5-100</span>
</div>
<div class="row">
	<span class="data" style="margin-left: 0%; min-width: 25%;" >0.0|0|25</span>
	<span class="data" style="margin-left: 0%; min-width: 25%;" >4.0|25|25</span>
	<span class="data" style="margin-left: 0%; min-width: 18.75%;" >7.0|50|18.75</span>
	<span class="data" style="margin-left: 0%; min-width: 6.25%;" >7.1|68.75|6.25</span>
</div>
<div class="clearfix"></div>
<div class="row">
	<span class="data" style="margin-left: 6.25%; min-width: 12.5%;" >1.0|6.25|12.5</span>
	<span class="data" style="margin-left: 12.5%; min-width: 6.25%;" >5.0|31.25|6.25</span>
	<span class="data" style="margin-left: 31.25%; min-width: 6.25%;" >8.0|68.75|6.25</span>
</div>
<div class="clearfix"></div>
<div class="row">
	<span class="data" style="margin-left: 12.5%; min-width: 6.25%;" >2.0|12.5|6.25</span>
	<span class="data" style="margin-left: 12.5%; min-width: 25%;" >6.0|31.25|25</span>
</div>
<div class="clearfix"></div>
<div class="row">
	<span class="data" style="margin-left: 12.5%; min-width: 25%;" >3.0|12.5|25</span>
</div>
<div class="clearfix"></div>
</div>
</body>
</html>

NOTE
If I were to further develop this staying with the same approach, one step would be taking the inline style out and putting them into class or data attributes.

1 Like