5
\$\begingroup\$

I am making a website that takes a bunch of dining hall information and puts it all together in a neater (and hopefully more mobile friendly) form.

Here is the link: https://googledrive.com/host/0By7Z_HHj2VhnWmlhbWl4aF8xdFU/nuEats.html

It's a little incomplete at the moment, but what it does is the bar up top shows you how much a "meal" is worth at a university convenience store (there is an arrow that is usually there, but it is current gone because it is 2:21 AM, which is after closing time).

Also, there are links to dining halls and their menus. My first question is: I don't know a lot about HTML and CSS best practices.

I have tried to keep everything nice and neat, but I was wondering if someone could just browse through my code and see

  • Is the code efficient
  • Is the code clear and concise?

The second point is especially important to me because I don't want anything to be ambiguous or hard to understand in my code, since I inevitably will have to pass it down.

HTML

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>nuEats</title>

        <!-- Bootstrap core CSS -->
        <link rel="stylesheet" href="css/bootstrap.min.css" type="text/css"/>

        <!-- User-fed CSS -->
        <link rel="stylesheet" href="nuEats.css" type="text/css">

        <!-- Bootstrap responsive CSS -->
        <link rel="stylesheet" href="css/bootstrap-theme.min.css" type="text/css"/>
    </head>

    <body onload="onCreate()">

        <!-- Navigation Bar -->
        <div class="navbar navbar-inverse navbar-static-top">
            <div class="navbar-inner">
                <div class="container">
                    <a class="navbar-brand" href="#">nuEats</a>
                </div>
            </div>
        </div>

        <!-- Clock -->
        <div class="container">
            <div id="clock"></div>
        </div>

        <!-- Equivalency Meal Exchange Rate Timeline -->
        <div class="container">
            <div id="rate-bars-label">Equivalency Meal Exchange Rate:</div>
            <div class="rate-bars-container" id="rate-bars-container"></div>
            <div id="arrow"><img src="arrow.png"></div>
        </div>

        <!-- Info Panels for Each Dining Hall -->
        <div class="container">
            <div class="panel-group" id="accordion">

                <!-- 1835 Hinman -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            1835 Hinman
                            <a href="https://m-nucuisine.sodexomyway.com/images/Hinman3_tcm238-12915.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in">
                        <div id="hinman" class="panel-body">
                            <!-- This is where body text goes -->
                        </div>
                    </div>
                </div>

                <!-- Allison -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Allison
                            <a href="https://m-nucuisine.sodexomyway.com/images/Allison3_tcm238-9944.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>

                <!-- Elder -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Elder
                            <a href="https://m-nucuisine.sodexomyway.com/images/Elder3_tcm238-12911.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>

                <!-- Plex East -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Plex East
                            <a href="https://m-nucuisine.sodexomyway.com/images/Foster_East3_tcm238-12913.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>

                <!-- Plex West -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Plex West
                            <a href="https://m-nucuisine.sodexomyway.com/images/Foster_West3_tcm238-12914.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>

                <!-- Sargent -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Sargent
                            <a href="https://m-nucuisine.sodexomyway.com/images/Sargent3_tcm238-12910.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>

                <!-- Willard -->
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h4 class="panel-title">
                            Willard
                            <a href="https://m-nucuisine.sodexomyway.com/images/Willard3_tcm238-12912.htm">
                                <div class="menu">Menu</div>
                            </a>
                        </h4>
                    </div>
                </div>
            </div>
        </div>
    </body>

    <!-- Bootstrap Core Javascript -->
    <script src="jquery-1.10.1.min.js"></script> <!-- Change this back to the link when you publish!!! -->
    <script src="js/bootstrap.js"></script>

    <!-- User Javascript -->
    <script src="nuEats.js" type="text/javascript"></script>
<html>

CSS

#clock {
    font-size: 1em;
}

/* Control the label FONT above the timeline bars */
#rate-bars-label {
    margin-top: 5px;
    margin-bottom: 20px;

    font-size: 1em;
    font-weight: bold;
}

/* Format each equivalency-rate bar as a group */
.rate-bars {
    height: 1em;
    float: left;
}

/* Set each bar's color individually; add more CSS if you add more time blocks */
/* LIGHT GREEN */
#block0 {
    background-color: #40d351;
}

/* MEDIUM GREEN */
#block1 {
    background-color: #2e973a;
}

/* DARK GREEN */
#block2 {
    background-color: #216C2A;
}

/* MEDIUM GREEN */
#block3 {
    background-color: #2e973a;
}

/* Control the FONT surrounding the timeline bars */
.timeText {
    margin-top: -1.5em;
    margin-left: 5px;   /* Some space makes it look better */

    font-size: 0.75em;
    font-weight: bold;
}

.timeText-right {
    margin-top: -3.0em;

    font-size: 0.75em;
    font-weight: bold;
    float: right;
}

.priceText {
    margin-left: 5px;

    color: white;
    font-size: 0.75em;
}

/* Formatting concerning the "Menu" button */ 
.menu {
    font-weight: bold;
    float: right;
}

.menu:hover {
    text-decoration: underline;
}

JavaScript

function onCreate() {

    // Consider this to be the "main" function of sorts
    document.getElementById("clock").innerHTML = createClock();                                     // Create a clock
    setInterval(function() { document.getElementById("clock").innerHTML = createClock(); }, 500);   // Update it every half-second

    // Dynamically create equivalency-rate bars from array data
    //              Time     Equivalency Rate
    var EqRates = [ [7,  30, "5.00"],
                    [10, 45, "7.00"],
                    [16, 45, "9.00"],
                    [19, 30, "7.00"],
                    [26, 00, "N/A" ] ]; // 2:00 AM, the NEXT day!
    createEqRateBars(EqRates);

    // Set the position of an arrow pointing to the correct time block
    setArrowPosition(EqRates);
    setInterval(function() { setArrowPosition(EqRates); }, 1000);   // Update it every second
    $(window).resize(function() { setArrowPosition(EqRates); });    // Update it upon window resize


}


/*
 * Create a clock, with special formatting
 */
function createClock() {
    // Date format is "Sunday, January 1st, 12:00:00 AM"
    var today = new Date();

    var day = returnDay(today.getDay());    // Convert day integer to string (i.e. Monday, Tuesday, Wednesday...)
    var o = returnMonth(today.getMonth());  // Convert month integer to string (i.e. January, February, March...)
    var d = formatDate(today.getDate());    // Add suffix to date (i.e. 1st, 2nd, 4th...)

    var h = today.getHours();
    var m = formatTime(today.getMinutes()); // If minutes or seconds is single digit, add zero before
    var s = formatTime(today.getSeconds());

    var AMPM = getAMPM(h);
    h = h % 12;

    // Controls the final output of the clock's text
    return (day).bold() + ", " + o + " " + d + ", " + h + ":" + m + ":" + s + " " + AMPM;
}

// A series of helper functions
function returnDay(day) { return ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][day]; }
function returnMonth(month) { return ["January","February","March","April","May","June","July","August","September","October","November","December"][month]; }
function formatDate(date) {
    digit = date % 10;
    if (digit == 1) {
        return date + "st";
    } else if (digit == 2) {
        return date + "nd";
    } else if (digit == 3) {
        return date + "rd";
    } else {
        return date + "th";
    }
}

// Add zeros before numbers where necessary
function formatTime(i) {
    if (i < 10) {
        i = "0" + i;
    }
    return i;
}

// Given an hour, determine if it is AM or PM
function getAMPM(hour) {
    return ["AM","PM"][(((hour % 24) >= 12) & 1)];
}


/*
 * Procedurally create equivalency rate exchange bars
 */
function createEqRateBars(EqRates) {

    // EqRates is a "3-dimensional" array of time slots and corresponding equivalency rates
    var rows = EqRates.length;
    var columns = EqRates[0].length;

    // Use the total length of all blocks to figure out what proportion each individual block takes up
    var startMin = timeInMin(EqRates[0][0], EqRates[0][1]); // Converts hours and minutes to just minutes for easy math
    var endMin = timeInMin(EqRates[rows - 1][0], EqRates[rows - 1][1]);   
    var totalMin = endMin - startMin;

    // For each time block, create a div that represents that block graphically as a horizontal bar
    for (var i = 0; i < rows - 1; i++) {

        // Set up variables that specify the horizontal bar's name and width, then add it
        var name = "block" + i;
        var blockMin = timeInMin(EqRates[i + 1][0], EqRates[i + 1][1]) - timeInMin(EqRates[i][0], EqRates[i][1]);
        var blockWidth = (blockMin / totalMin) * 100 + "%";

        jQuery('<div/>', {
            id: name,
            class: "rate-bars",
            css: {
                width: blockWidth,
            }
        }).appendTo('#rate-bars-container');


        // Format time information, then display it ABOVE the bar
        var hour = EqRates[i][0] % 12;              // 12-hour time
        var minute = formatTime(EqRates[i][1]);     // Add '0' before minute if minute < 10
        var AMPM = getAMPM(EqRates[i][0]);          // Is it AM or PM?

        jQuery('<div/>', {
            class: 'timeText',
            text: hour + ":" + minute + " " + AMPM
        }).appendTo("#" + name);


        // Pull price information, then display it ON the bar
        var price = EqRates[i][2];

        jQuery('<div/>', {
            class: 'priceText',
            text: '$' + price
        }).appendTo("#" + name);
    }

    // Do something special to pull closing time and put it ABOVE the bar and flush right
    var hour = EqRates[rows-1][0] % 12;             // 12-hour time
    var minute = formatTime(EqRates[rows-1][1]);    // Add '0' before minute if minute < 10
    var AMPM = getAMPM(EqRates[rows - 1][0]);       // Is it AM or PM?

    jQuery('<div/>', {
        class: 'timeText-right',
        text: hour + ":" + minute + " " + AMPM
    }).appendTo("#" + name); // Just to keep thing simple, we'll make it a subclass of the last horizontal bar
}


/*
 * Set arrow location depending on equivalency meal value
 */
function setArrowPosition(EqRates) {

    // EqRates is a "3-dimensional" array of time slots and corresponding equivalency rates
    var rows = EqRates.length;

    // Get some time information
    var today = new Date();

    var currentMin = dateInMin(today);                                      // Current time in minutes from midnight
    var openMin = timeInMin(EqRates[0][0], EqRates[0][1]);                  // 7:30 AM - same format
    var closeMin = timeInMin(EqRates[rows - 1][0], EqRates[rows - 1][1]);   // 2:00 AM the NEXT DAY - same format
    var DAY_IN_MINUTES = 24*60;                                             // Will be useful later on for subtracting

    // If it is after 7:30 AM OR before 2:00 AM on the SAME DAY, show the arrow, otherwise hide it
    if (currentMin >= openMin || currentMin < closeMin - DAY_IN_MINUTES) {

        /* Place the "current time" arrow as such:
         * 1) Subtract currentMin by openMin so the arrow is at 0(%) displacement at 7:30 AM
         * 2) Divide by (closeMin - openMin), the minutes from open to close time, so that at close time, the arrow is at 100(%) displacement
         * 3) There is 5px of offset from the width of the arrow, convert that to a (%) and subtract it
         * 4) Multiply by 100 so we have an actual percent
         * 5) Set this absolute number to the left-margin shift
         */
        var timelinePercent = [(currentMin - openMin)/(closeMin - openMin) - (5/$("#rate-bars-container").width())] * 100;
        $("#arrow").css("margin-left", timelinePercent + "%");
    } else {
        $("#arrow").hide();
    }
}

// Easy ways to return times as minutes
function dateInMin(dateObject) {
    return dateObject.getHours() * 60 + dateObject.getMinutes();
}

function timeInMin(hours, minutes) {
    return hours * 60 + minutes;
}

Furthermore, I want to have the bar for each dining hall glow green or red depending on whether or not the dining hall is open and show what kind of meal (breakfast, lunch, dinner, etc.) it is currently serving. I plan to pull this information from a tab-delimited table in a text file. Can anyone recommend a best/better way to do this? I was thinking either Javascript or PHP, but I don't really like PHP, both because I don't know it yet and because I believe it will spaghettify my code.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

From a once over,

DRY (Dont' Repeat Yourself)

  • You have a number of these blocks that seem all copy pasted:

    <!-- Sargent -->
    <div class="panel panel-default">
        <div class="panel-heading">
            <h4 class="panel-title">
                 Sargent
                <a href="https://m-nucuisine.sodexomyway.com/images/Sargent3_tcm238-12910.htm">
                    <div class="menu">Menu</div>
                </a>
            </h4>
        </div>
    </div>
    

You could consider having a JavaScript array holding just the restaurant names and links and then building the HTML with templating. Furthermore I am not sure why you need an h4 in a div in a div, you should be able to style the top div in such a manner that the HTML would look like

    <!-- Sargent -->
    <div class="panel panel-default awesomerestaurantclass">
        Sargent
        <a href="https://m-nucuisine.sodexomyway.com/images/Sargent3_tcm238-12910.htm">
            <div class="menu">Menu</div>
        </a>
    </div>

Style

  • Don't skip the newlines in returnDay and returnMonth
  • formatTime could use a ternary, return (i < 10) ? ("0" + i) : i;

  • createEqRateBars is mixing logic/calculation and creation of UI elements, you should split that out in 2 functions. It looks messy and too tightly coupled now.

Functionality

  • It seems like your code in formatDate will return 11st and 13rd, that seems wrong

All in all

Time/date handling is a pain in any language, and I think you pulled it off pretty well. I probably would have encapsulated EqRates into an object with helper functions to make the code a little more readable, but other than that I would not maintaining code like this.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for taking the time to do this. One question... Is it bad to use a javascript array to hold data like that? Would it be netter to store it in another way, and if so, what would that be? \$\endgroup\$ Commented Mar 30, 2014 at 1:27
  • \$\begingroup\$ Since the concept of times and names of months/days is unlikely to change, I think you are taking the right approach. \$\endgroup\$
    – konijn
    Commented Mar 30, 2014 at 21:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.