New to Grid in CSS and, of course, starting with a more convoluted need.
Think an airport Arrivals/Departures display. No keyboard, no mouse, no human interaction. This callout app used to be just one big long scrolling list. I am turning it into a little more organized layout. Depending on what screen it finally shows up on it may have room for 2 columns, 3 columns maybe even 5 columns - where each column is the same width and there is a minimum width.
1st row: header / full width / 3 lines of text
2nd row: should be equal to the remaining height, full width and is a container
In the container:
@model wsGT4.Models.DictionaryResultSet<string, List<wsGT4.Models.Callout>>
@{
ViewBag.Title = "Callout";
Layout = "~/Views/Shared/_EmptyLayout.cshtml";
}
<section class="header">
<H1>TOMORROW'S CALLOUTS WILL DISPLAY STARTING AT 5PM</H1>
<H2>BRING DOCS TO CALLOUTS</H2>
@if (Model.Success == false)
{
foreach (var msg in Model.Messages)
{
<h2>@msg</h2>
}
}
else
{
if (DateTime.Now.AddHours(Model.TimezoneOffset).Hour >= 17)
{
<h2>CALLOUTS FOR <span style="color:red; background-color: yellow;">TOMORROW</span> - @DateTime.Now.AddDays(1).ToString("MMM dd") <span style="font-size:.6em">(@DateTime.Now.AddHours(Model.TimezoneOffset).ToString("HH:mm"))</span></h2>
}
else
{
<h2>CALLOUTS FOR TODAY - @DateTime.Now.ToString("MMM dd") <span style="font-size:.6em">(@DateTime.Now.AddHours(Model.TimezoneOffset).ToString("HH:mm"))</span></h2>
}
}
</section>
<section class="container">
@foreach (var kvp in Model.ResultList.OrderBy(a => a.Key))
{
<div class="timeDisplay">
>>> @kvp.Key
</div>
<div class="viewPort">
<div class="textList">
@foreach (var subject in kvp.Value.OrderBy(a => a.LastName).ThenBy(a => a.SubjectId))
{
<span>@subject.LastName</span>
<span>#@subject.SubjectId</span>
<span>@subject.EventTitle</span>
}
</div>
</div>
}
</section>
CSS (which I am royally screwing up)
.header {
padding: 5px;
border: groove;
border-bottom-color: black;
border-width: 2px;
/* Grid styles */
display: grid;
align-items: center;
/*grid-template-columns: 1fr;*/
grid-template-rows: repeat(3, 1fr);
}
.container {
max-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 30px auto-fill;
grid-gap: 5px;
}
.timeDisplay{
max-height: 30px;
}
.viewPort {
height: 100%;
overflow: hidden;
}
.textList {
height: 100%;
font-size: 125%;
display: grid;
grid-template-columns: 3fr 2fr 5fr;
}
Since the model collection coming back can have an unknown number of time/list sets I am trying to let automation do some of the work. The idea being that if I have more columns than what can fit then they are either (A)
not displayed or (B)
wrap below the bottom of the screen, effectively being hidden.
So I changed my approach a little bit after diving into flex vs grid in CSS. The container
got rewritten this way:
<section class="wrapper">
<ul class="colList">
@foreach (var kvp in Model.ResultList.OrderBy(a => a.Key))
{
<li class="columnItem">
<div class="columnTitle--wrapper">
<h2>>>> @kvp.Key</h2>
</div>
<div class="cardViewer">
<div class="cardList">
<ul class="cardList--ul">
@foreach (var subject in kvp.Value.OrderBy(a => a.EventTitle).ThenBy(a => a.LastName).ThenBy(a => a.SubjectId))
{
<li class="cardItem">
<h3>@subject.EventTitle.ToUpper()</h3>
<h3>@subject.LastName.ToUpper()</h3>
<h3>#@subject.SubjectId.ToUpper()</h3>
</li>
}
</ul>
</div>
</div>
</li>
}
</ul>
</section>
and the CSS turned into:
/* column styles */
.wrapper {
}
.colList {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
grid-gap: .5rem;
align-items: start;
top: 500vh;
}
.columnItem {
border-radius: .2rem;
padding: .5rem;
}
.columnTitle--wrapper {
}
.columnTitle--wrapper h2 {
font-weight: 700;
}
/* card styles */
.cardViewer {
overflow: hidden;
height: 80vh;
max-height: 80vh;
}
.cardList {
position: relative;
}
.cardList--ul {
display: grid;
grid-template-rows: auto;
grid-gap: .5rem;
margin: .5rem 0;
}
.cardItem {
background-color: white;
border: 1px solid #BBB;
border-radius: .25rem;
box-shadow: 0 1px 0 rgba(9,45,66,.25);
padding: .5rem;
display: grid;
grid-template-columns: 5fr 7fr 2fr;
}
And I added JS to handle multiple marquees when needed (list was too long for the viewport):
class marqueeInfo {
constructor(viewer, cardList) {
this.viewer = viewer;
this.cardList = cardList;
if (this.isScrollable) {
this.cardList.style.top = parseInt(this.viewerHeight()) + "px"
}
else {
this.cardList.style.top = "0px"
}
}
viewer() { return this.viewer; }
cardList() { return this.cardList; }
viewerHeight() { return this.viewer.offsetHeight; }
cardListHeight() { return this.cardList.offsetHeight; }
isScrollable() { return this.cardList.offsetHeight > this.viewer.offsetHeight; }
}
var marqueeArray = [];
var marqueeSpeed = 4 //Specify marquee scroll speed (larger is faster 1-10)
var delayb4scroll = 100 //Specify initial delay before marquee starts to scroll on page (2000=2 seconds)
var lastRefresh;
var isRefreshing = false;
var refreshMinutes = 1;
// may be overkill here...
if (window.addEventListener)
window.addEventListener("load", initializeMarqueeHandler, false)
else if (window.attachEvent)
window.attachEvent("onload", initializeMarqueeHandler)
else if (document.getElementById)
window.onload = initializeMarqueeHandler
function initializeMarqueeHandler() {
lastRefresh = new Date();
isRefreshing = false;
var viewers = document.getElementsByClassName("cardViewer");
var cards;
var i;
for (i = 0; i < viewers.length; i++) {
cards = viewers[i].getElementsByClassName("cardList");
if (cards.length != 1)
return;
marqueeArray.push(new marqueeInfo(viewers[i], cards[0]));
}
setTimeout('lefttime=setInterval("scrollMarquees()",30)', delayb4scroll)
}
function scrollMarquees() {
marqueeArray.forEach(function (marquee, index, array) {
if (marquee.isScrollable()) {
var cardHeight = marquee.cardListHeight();
var viewerHeight = marquee.viewerHeight();
var cardTop = parseInt(marquee.cardList.style.top);
var targetTop = 0 - cardHeight;
if (cardTop > targetTop) // are we thru the list?
marquee.cardList.style.top = cardTop - marqueeSpeed + "px" //scroll
else
marquee.cardList.style.top = viewerHeight + 25 + "px";
}
else {
marquee.cardList.style.top = "0px";
}
})
var milliseconds = (new Date()).getTime() - lastRefresh.getTime();
if (milliseconds > (refreshMinutes * 60 * 1000) && !isRefreshing) {
lastRefresh = new Date();
isRefreshing = true;
window.location.reload(true);
}
}
Everything is working as expected - finally.