I prepared the following code for a job interview in the last few months. Unfortunately the interviewer would not provide feedback on the code itself. I'd like to get some community feedback to ensure that I'm on the right track, and to help improve my performance as a better candidate in the future.

The challenge itself is about preparing their mockups and calculating permutations of 5 different colors. They wanted me to do use only javascript and no frameworks / libraries. The following solution is done entirely with HTML, CSS, and Javascript.

HTML:

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css?family=Cabin+Condensed" rel="stylesheet">
<script src="scripts.js"></script>
</head>

<body>

<div class="flex-container">
    <div class="side">

        <h2>Marble sequence generator</h2>
        <h3>Generate some marbles</h3>

        <p>Type a number below and click "generate marbles" to create a sequence.</p>

        <div class="form-group">
            Number of colours: <br />
            <input id="numberOfColors" class="form-input" type="number" min="1" max="5" step="1" />
            <input class="btn" type="button" value="Generate marbles" onclick=marbleMachine.generateMarbles()>
        </div>

        <div id="error-pane" class="hide">
            You need to enter a value between 1 and 5.
        </div>

        <div id="results-pane" class="hide">
            <h3> Result </h3>
            <div id="generated-results">Generated 325 sequences.</div>
        </div>

    </div>
    <div class="main">
        <div id="config-bar" class="config-bar">
            <div>Configuration Settings</div>
            <div class="dropdown" onclick="marbleMachine.toggleConfig()">V</div>
        </div>
        <div class="control-bar center-vertical">
            <div class="vertical-group">
                <div class="control-group">
                    <label>Marble style:</label>
                    <select id="marbleStyle" name="marbleStyle">
                        <option value="solid">Fill</option>
                        <option value="outline">Outline</option>                            
                    </select>
                </div>
                <div class="control-group">
                    <label>Display as:</label>
                    <select id="displayAs" name="display">
                        <option value="row">Row</option>
                        <option value="column">Column</option>                            
                    </select>
                </div>
            </div>
            <div class="vertical-group">
                <div class="control-group">
                    <label>Marble Size:</label>
                    <div class="range-slider">
                        <input id="marbleSize" name="size" type="range" min="20" max="60" step="20">
                        <div class="sizes">
                            <div>Small<br>20px</div>
                            <div>Medium<br>40px</div>
                            <div>Large<br>60px</div>
                        </div>
                </div>
            </div>
        </div></div>
        <div id="output" class="output" class="solid medium"></div>
    </div>
</div>

</body>

</html>

CSS:

input,
    button {
        box-shadow: none;
    }

    * {
        box-sizing: border-box;
    }

    html,
    body {
        font-family: Helvetica Neue, Helvatica, Arial, sans-serif;
        margin: 0;
        font-size: 15px;
        height: 100%
    }

    .hide {
        display: none;
    }

    #error-pane {
        color: red;
        margin: 5px;
    }

    .config-bar {
        display: flex;
        padding: 20px;
        background-color: #333;
        color: white;
        align-items: center;
        justify-content: space-between;
        line-height: 1em;
    }

    .config-bar.open .dropdown {
        transform: rotate(-180deg);
    }

    .control-bar {
        display: flex;
        overflow: hidden;
        display: none;
        background-color: #333;
        color: white;
    }

    .config-bar.open+.control-bar {
        display: flex;
    }

    .vertical-group {
        flex-flow: column;
        flex: 1;
        align-items: stretch;
        margin: 0 5px;
    }

    .control-group {
        display: flex;
        flex: 1 1 50%;
        margin-bottom: 10px;
        justify-content: flex-start;
    }

    .control-group label {
        flex: 1 1 0%;
    }

    .control-group select {
        flex: 1 1 auto;
    }

    .flex-container {
        display: flex;
        flex-wrap: wrap;
        height: 100%;
    }

    .range-slider {
        display: flex;
        flex-flow: column;
    }

    .sizes {
        display: flex;
        flex-flow: row;
        justify-content: space-between;
        text-align: center;
    }

    /* marble rows */

    .flex-container-inner {
        display: flex;
        flex-wrap: wrap;
        margin: 10px;
        flex-direction: row;
        height: 100%;
    }

    .marbles {
        display: flex;
    }

    /* marble columns */

    .side {
        flex: 30%;
        background-color: #f8f8f8;
        padding: 15px;
    }

    .main {
        flex: 60%;
        background-color: white;
        overflow-x: auto;
    }

    .center {
        margin: auto;
        width: 50%;
        padding: 10px;
    }

    .center-vertical {
        margin-top: auto;
        margin-bottom: auto;
        width: 100%;
        padding: 10px;
    }

    .form-input {
        width: 60px;
        border-radius: 3px;
        font-family: Arial;
        border: 1px solid #ccc;
        font-size: 15px;
        padding: 7px;
    }

    .form-input.error {
        border-color: red;
    }

    .btn {
        border-radius: 3px;
        font-family: Arial;
        color: #ffffff;
        font-size: 15px;
        background: #276dc2;
        padding: 7px 14px 7px 14px;
        text-decoration: none;
        border: none;
        cursor: pointer;
    }

    .output {
        background: white;
        min-width: 100%;
        overflow-x: auto;
    }

    .circle {
        border-radius: 50%;
        padding: 2px;
        margin-bottom: 10px;
    }

    .small .circle {
        width: 20px;
        height: 20px;
        margin: 3px;
    }

    .medium .circle {
        width: 40px;
        height: 40px;
        margin: 6px;
    }

    .large .circle {
        width: 60px;
        height: 60px;
        margin: 9px;
    }

    .display-column {
        flex-flow: row;
    }

    .display-row {
        flex-flow: column;
    }

    .display-column .marbles {
        flex-flow: column;
    }

    .display-row .marbles {
        flex-flow: row;
    }

    .outline .circle {
        border-width: 3px;
        border-style: solid;
        background: white !important;
    }

    .solid .circle {
        border-width: 0 !important;
    }

    .blue {
        background: #4bc1d4;
        border-color: #4bc1d4;
    }

    .green {
        background: #8bc04e;
        border-color: #8bc04e;
    }

    .red {
        background: #da1620;
        border-color: #da1620;
    }

    .yellow {
        background: #fcb722;
        border-color: #fcb722;
    }

    .purple {
        background: #7e69ae;
        border-color: #7e69ae;
    }

    .marbles {
        display: flex;
    }

    /* Responsive layout - when the screen is less than 700px wide, make the two columns stack on top of each other instead of next to each other */

    @media screen and (max-width: 700px) {
        .row,
        .navbar {
            flex-direction: column;
        }
    }

Javascript:

// I prefer to keep all of my javascript within one main object.
    // this ensures that I don't have to worry about clobbering 
    // 3rd party declarations that may or may not be in the global
    // window space. Ever use jquery with another library that used 
    // $.?
    var marbleMachine = {};
    marbleMachine.marbleSequences = [];

    // always use constants, it's much better to change static values
    //in one place, rather than all over the code base.
    marbleMachine.inputs = {
      1: ["blue"],
      2: ["blue", "green"],
      3: ["blue", "green", "red"],
      4: ["blue", "green", "red", "yellow"],
      5: ["blue", "green", "red", "yellow", "purple"]
    };

    // Hide / Show the configuration settings by adding / removing
    // the "open" class.
    marbleMachine.toggleConfig = function () {
      let configElement = document.getElementById("config-bar");
      let configElementClasses = configElement.getAttribute("class");

      if (configElementClasses.indexOf("open") === -1) {
        configElement.setAttribute("class", "config-bar open");
      } else {
        configElement.setAttribute("class", "config-bar");
      }
    };

    // I"m using a simple class on the error-pane div called hide
    // if hide is present then we hide away the pane, otherwise 
    // we display it. 

    // inputElement (inputObject) - the user input element to toggle the error class.
    // showErrorPane (boolean) - controls whether we are displaying an error to the user or hiding the entire pane away.
    marbleMachine.showErrorPane = function (inputElement, showErrorPane) {
      let errorPane = document.getElementById("error-pane");

      if (showErrorPane) {
        errorPane.setAttribute("class", ""); // remove the hide class.
        inputElement.setAttribute("class", "form-input error");
      } else {
        errorPane.setAttribute("class", "hide");
        inputElement.setAttribute("class", "form-input");
      }

    };

    // Once again we're using a simple boolean value to determine whether or not
    // we show / hide the results pane.
    marbleMachine.showResultsPane = function (showResultsPane) {
      if (showResultsPane) {
        document.getElementById("results-pane").setAttribute("class", ""); // remove the hide class.  
      } else {
        document.getElementById("results-pane").setAttribute("class", "hide");
      }
    };

    // returns false if user provided invalid input.
    // returns their selection between 1 and 5 if valid.
    marbleMachine.getUserInput = function () {
      let inputElement = document.getElementById("numberOfColors");
      let numberOfColors = inputElement.value;

      if (numberOfColors <= 0 || numberOfColors > 5) {
        marbleMachine.showErrorPane(inputElement, true);
        return false;
      }

      marbleMachine.showErrorPane(inputElement, false);

      return numberOfColors;
    };

    // Generates the html for the output panel.
    // Accepts the final computed marbleSequences as input to display.
    marbleMachine.generateOutputHtml = function (marbleSequences) {
      let marbleStyle = document.getElementById("marbleStyle").value;
      let displayAs = document.getElementById("displayAs").value;
      let marbleSize = document.getElementById("marbleSize").value;
      let marbleDisplayClass = displayAs === "column" ? "display-column" : "display-row";
      let marbleSizeClass = null;
      let marbleSequencesHtml = "";

      switch (marbleSize) {
        case "20":
          marbleSizeClass = "small";
          break;

        case "40":
          marbleSizeClass = "medium";
          break;

        default:
          marbleSizeClass = "large";
          break;
      }

      marbleSequences.forEach(function (marbleSequence, index) {
        marbleSequencesHtml += `<div class="marbles">`;

        marbleSequence.forEach(function (marble, index) {
          marbleSequencesHtml += `<div class="circle ${marble}"></div>`;
        });

        marbleSequencesHtml += `</div>`;
      });

      let output = `<div class="flex-container-inner ${marbleStyle} ${marbleSizeClass} ${marbleDisplayClass}">
            ${marbleSequencesHtml}
        </div>`;

      document.getElementById("output").innerHTML = output;
      document.getElementById("generated-results").innerHTML = `Generated ${marbleSequences.length} sequences.`;
      marbleMachine.showResultsPane(true);
    };

    // Computes the final marble sequences.
    // this is essentially our "main" function that drives the javascript program.
    marbleMachine.generateMarbles = function () {
      let selectedInput = marbleMachine.getUserInput();

      if (selectedInput) {
        let marbleSequences = marbleMachine.getCombinations(
          marbleMachine.inputs[selectedInput]
        );
        marbleMachine.generateOutputHtml(marbleSequences);
      } else {
        document.getElementById("output").innerHTML = ""; // clear out the output container.
        marbleMachine.showResultsPane(false);
      }
    };

    // computes the marble sequences and gets all the possible combinations.
    marbleMachine.getCombinations = function (list) {
      let result = [];
      // Loop through the "1 to n" combinations
      for (let i = 1; i <= list.length; i++) {
        i_combinations = marbleMachine.nCombinations(list, i);
        for (let j = 0; j < i_combinations.length; j++) {
          result.push(i_combinations[j]);
        }
      }
      return result;
    };

    // recursive function to calculate the remaining permutations based on the subset
    // provided. ** dragons rule these lands. **
    marbleMachine.nCombinations = function (list, numItems) {
      if (list.length < numItems) {
        return [];
      }

      // if only single marbles, return the list quickly.
      if (numItems == 1) {
        let result = [];
        for (let i = 0; i < list.length; i++) {
          result.push([list[i]]);
        }

        return result;
      }

      // loop through the list, combining each element with all the
      // possible permutations of the remaining elements.
      let result = [];
      for (let i = 0; i < list.length; i++) {
        let currentElement = list.slice(i, i + 1);
        let restList = list.slice(0, i).concat(list.slice(i + 1));
        let restCombinations = marbleMachine.nCombinations(restList, numItems - 1);

        for (let j = 0; j < restCombinations.length; j++) {
          result.push(currentElement.concat(restCombinations[j]));
        }
      }

      return result;
    };

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.