2
\$\begingroup\$

I needed to put together a small survey / test application which consisted of a bunch of radio inputs. I decided to give the standard radio button a visual overhaul and also rethink the visual feedback when the user submits an answer.

I choose to swoop through the different questions with a slider. That part left me in doubt. The slides array grew a little complex / counterintutive in order to create the entire application from this array. The alternative was to create seperate intro and result pages / views.

I then decided that the total results should be divided into topics / dimensions so that I could group the results.

When user has submitted x number of questions the results are displayed with standard progressbars. The values from the radio inputs are translated into percentage, which controls the fill of the progressbar.

It's built on top of bootstrap. This provided me with a simple slider which was easy to implement. In production I don't plan on loading the entire bootstrap js. Only carousel and transition js files.

Any suggestions on how to improve the code will be applauded loudly :)

$(document).ready(function() {
  var slides = [
    {
      text: "This is a fairly simple survey with a sample of questions asking for your opinion on different topics. Each answer is related to a certain topic (dimension) and the final scores are applied to the individual section accordingly.",
      radios: false,
      button: true,
      buttonText: "Start survey",
      result: false,
      intro: true,
      color: "green"
    },
    {
      text: "How would you rate the room service?",
      radios: true,
      dimension: 1,
      dimensionName: "Customer service",
      button: false,
      result: false,
      intro: false,
      color: "green"
    },
    {
      text: "How would you rate the restaurant service?",
      radios: true,
      dimension: 1,
      dimensionName: "Customer service",
      button: false,
      result: false,
      intro: false,
      color: "green"
    },
    {
      text: "How would you rate the housekeeping?",
      radios: true,
      dimension: 1,
      dimensionName: "Customer service",
      button: false,
      result: false,
      intro: false,
      color: "green"
    },
    {
      text: "How would you rate the hotel experience?",
      radios: true,
      dimension: 2,
      dimensionName: "Customer experience",
      button: false,
      result: false,
      intro: false,
      color: "orange"
    },
        {
      text: "How would you rate the park experience?",
      radios: true,
      dimension: 2,
      dimensionName: "Customer experience",
      button: false,
      result: false,
      intro: false,
      color: "orange"
    },
        {
      text: "How would you rate the food quality in the park?",
      radios: true,
      dimension: 3,
      dimensionName: "Food quality",
      button: false,
      result: false,
      intro: false,
      color: "lightblue"
    },
        {
      text: "How would you rate the food quality in the cafe?",
      radios: true,
      dimension: 3,
      dimensionName: "Food quality",
      button: false,
      result: false,
      intro: false,
      color: "lightblue"
    },
    {
      text: "This is the place to show the results of the quick survey.<br/>Scores are divided into dimensions accordingly.",
      radios: false,
      button: false,
      result: true,
      intro: false,
      color: "green"
    }
  ];

  var countSlides;
  countSlides = slides.length;

  function createSlides() {
    // I wanna take the slider ID in as argument in order to make it more versatile. Dont know if it could work
    var wrapper = $("#feedbackTest");
    var questionNumber = 1;
    var questionsTotal = 0;
    var output;

    for(var i = 0; i < countSlides; i++) {
      if (slides[i].radios === true) {
        questionsTotal += 1;
      }
    }

    output = "<div class='carousel-inner'>";
    // Loop through all elements and insert html block of radio buttons
    for(var i = 0; i < countSlides; i++) {
      if (i === 0) {
        output += "<div class='item active'>";
      } else {
        output += "<div class='item'>";
      }
      output += "<div class='section " + slides[i].color + "'>";
      output += "<div class='section-header'>";
      output += "<h2>This is the header</h2>";
      output += "</div>"; // .section-header
      output += "<div class='question'>";
      output += "<div class='question-number clearfix'>";
      
      if (slides[i].intro === true) {
        output += "<p>Introduction</p>";
        output += "<hr>";
      } else if (slides[i].radios === true) {
        output += "<p>Question " + questionNumber + " of " + questionsTotal + "</p>";
        output += "<hr>";
        questionNumber += 1;
      } else if (slides[i].result === true) {
        output += "<p>Result</p>";
        output += "<hr>";
      }
      output += "</div>";
        output += "<p>" + slides[i].text; + "</p>";
      if (slides[i].radios === true) {
        output += "<div class='radio-button-group'>";
        output += "<label class='radio'>";
        output += "<input id='radios" + i + "' name='radios" + i + "' type='radio' value='1' data-dimension-name='" + slides[i].dimensionName + "' data-dimension='" + slides[i].dimension + "'> Very dissatisfied";
        output += "</label>";
        output += "<label class='radio'>";
        output += "<input id='radios" + i + "' name='radios" + i + "' type='radio' value='2' data-dimension-name='" + slides[i].dimensionName + "' data-dimension='" + slides[i].dimension + "'> Dissatisfied";
        output += "</label>";
        output += "<label class='radio'>";
        output += "<input id='radios" + i + "' name='radios" + i + "' type='radio' value='3' data-dimension-name='" + slides[i].dimensionName + "' data-dimension='" + slides[i].dimension + "'> Neither satisfied nor dissatisfied";
        output += "</label>";
        output += "<label class='radio'>";
        output += "<input id='radios" + i + "' name='radios" + i + "' type='radio' value='4' data-dimension-name='" + slides[i].dimensionName + "' data-dimension='" + slides[i].dimension + "'> Satisfied";
        output += "</label>";
        output += "<label class='radio'>";
        output += "<input id='radios" + i + "' name='radios" + i + "' type='radio' value='5' data-dimension-name='" + slides[i].dimensionName + "' data-dimension='" + slides[i].dimension + "'> Very satisfied";
        output += "</label>";
        output += "<div class='test-submit clearfix'>";
        output += "<a data-slide='prev' href='#feedbackTest'>&lt; Previous question</a>";
        output += "</div>"; // .test-submit
        output += "</div>"; // .radio-button-group
      }
      if (slides[i].result === true) {
        output += "<div class='results'></div>";
      }
      if (slides[i].button === true) {
        output += "<div class='clearfix'><button href='#feedbackTest' class='btn center-block' data-slide='next'>" + slides[i].buttonText + "</button></div>";
      }
      output += "</div>";
      output += "</div>";
      output += "</div>"; // .section
    }
    output += "</div>"; // .carousel-inner
    $(wrapper).append(output);
  }

  createSlides();

  function showScore() {
    var values;
    var dimension;
    var dimensionName;
    var counter = 1;
    var scores = {};
    var names = {};
    var dimensionCounter = {}
    var output = "";
    values = $("input:radio:checked");

    for (var i = 0; i < values.length; i++)
    {
      dimension = $(values[i]).data("dimension-name");
      if(typeof scores[dimension] == "undefined") {
        scores[dimension] = parseInt($(values[i]).val());
        names[dimension] = 1;
      } else {
        scores[dimension] += parseInt($(values[i]).val());
        names[dimension] += 1;
      }
    }

    for(dimension in scores) {
      var highestScore = names[dimension] * 5;
      var scorePercent = scores[dimension] * 100 / highestScore;
      output += "<div>Dimension " + counter + ": " + dimension + "</div>";
      output += "<div class='progress'><div aria-valuemax='100' aria-valuemin='0' aria-valuenow='" + scorePercent + "' class='progress-bar' role='progressbar' style='width:" + scorePercent + "%;'></div></div>";
      counter += 1;
    }
    $(".results").html(output);
  }

  // Toggles the animated background color fill on the label for each radio button
  // And fires the next slide in the carousel
  $("input").click(function() {
    $("input:not(:checked)").parent().removeClass("checked");
    $("input:checked").parent().addClass("checked");
    $(".carousel").delay(600).queue(function() {
      $(this).carousel("next");
      $(this).dequeue();
    });
    showScore();
  });
});
body {
	color: rgba(57,59,65,1);
	font-family: Corbel, "Open Sans", "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
}

a,
a:hover {
  color: rgba(232,62,51,1);
}

a {
  text-decoration: none;
}

a.green-link {
  color: rgba(76,129,121,1);
}

a, 
label {
	cursor: pointer;
}

/* Container */
.vh100 {
  min-height: 100vh;
}

.container {
  	padding-left: 15px;
    padding-right: 15px;
  	margin-left: auto;
    margin-right: auto;
}

@media (min-width: 440px) {
  .container {
    width: 440px;
  }
}

.form-wrap {
	margin: 20px 0px;
  min-height: 71vh;
}

h2 {
	margin-bottom: 10px;
	padding: 5px 20px 5px;
	text-transform: uppercase;
}

/* List group items */
.list-group-item:after {
  content: '';
  bottom: 0;
  border-bottom: 18px solid rgba(191,192,194,1);
  border-left: 18px solid #fff;
  position: absolute;
  right: 0;
  width: 0;
}

.list-group-item {
  border: none;
  margin-bottom: 10px;
  padding-right: 30px;
}

/* Questions */
.question {
	background-color: rgba(191,192,194,1);
	margin-bottom: 10px;
  min-height: 530px;
	padding: 20px;
  position: relative;
}

.question:before {
  content: '';
  position: absolute;
  top: 0; right: 0;
  border-top: 18px solid #fff;
  border-left: 18px solid rgba(191,192,194,1);
  width: 0;
}


.question-number {
	font-size: 12px;
	font-weight: bold;
	text-transform: uppercase;
}

.question-number hr {
	border: 2px solid rgba(57,59,65,1);
	float: left;
	margin: -9px 0 10px;
	width: 10%;
}

.question-text {
	padding: 0 0 8px; 
}


/*  Radio buttons 
-----------------------------------------------------------------------------------------------*/

input[type="radio"] {
	display: none;
}

.checkbox label {
  background-color: rgba(0,0,0,0);
  border-left: none;
  padding: 5px 45px;
  text-transform: none;
}

label {
	background-color: #fff;
	border-left: 40px solid rgba(76,129,121,1);
	padding: 20px 20px;
  text-transform: uppercase;
}

label {
  vertical-align: middle;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  box-shadow: 0 0 1px rgba(0, 0, 0, 0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -moz-osx-font-smoothing: grayscale;
  position: relative;
  -webkit-transition-property: color;
  transition-property: color;
  -webkit-transition-duration: 0.5s;
  transition-duration: 0.5s;
  width: 100%;
}

label:before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(76,129,121,1);
  -webkit-transform: scaleX(0);
  transform: scaleX(0);
  -webkit-transform-origin: 0 50%;
  transform-origin: 0 50%;
  -webkit-transition-property: transform;
  transition-property: transform;
  -webkit-transition-duration: 0.5s;
  transition-duration: 0.5s;
  -webkit-transition-timing-function: ease-in-out;
  transition-timing-function: ease-in-out;
}

label.checked {
  color: white!important;
}

label.checked:before {
  -webkit-transform: scaleX(1);
  transform: scaleX(1);
}

/* Progress bars */
.progress {
  height: 20px;
  margin: 10px 0 25px;
  border-radius: 0;
  box-shadow: 0 0 0;
  -webkit-box-shadow: 0 0 0;
}

.progress-bar {
  background-color: #578780;
}

/* Button */
.btn {
	background-color: rgba(76,129,121,.9);
	border-color: rgb(76,129,121);
	border-radius: 0;
	color: #fff;
	padding: 20px 25px;
  text-transform: uppercase;
}

.btn:hover,
.btn:active,
.btn:focus {
	background-color: rgba(76,129,121,1);
	border-color: rgba(76,129,121,1);
	color: #fff;
}

.btn .ion-ios-arrow-thin-right {
	margin-left: 5px;
}

.btn:after {

}

.someText {
  font-weight: bold;
  text-align: center;
  text-transform: uppercase;
}

.otherText {
  text-align: center;
}

.input-group-btn {
  font-size: 14px;
}

.email-field {
  padding: 30px 25px;
}


/* Footer section */
.footer-upper-section {
  background-color: #393b41;
  color: #fff;
  font-family: Palatino, Georgia, "Times New Roman", Times, serif;
  padding: 30px 0;
  text-align: center;
}

.footer-bottom-section {
  background-color: #000;
  color: #fff;
  padding: 30px 0;
  text-align: center;
}

.footer-upper-section a {
  color: #fff;
}
.footer-bottom-section a {
  color: #fff;
  text-decoration: underline;
}

@media screen and ( min-height: 1300px ) {
  .footer {
    position: fixed;
    bottom: 0;
    width: 100%
  }
}



/**************************************
  Section headers shifting colors
  *************************************/

.section-header:after {
  content: '';
  bottom: 0;
  border-bottom: 18px solid #fff;
  border-left: 18px solid rgba(76,129,121,1);
  position: absolute;
  right: 0;
  width: 0;
}


.section-header {
  background-color: rgba(76,129,121,1);
  position: relative;
}

.section-header h2 {
  color: #fff;
  padding:20px 20px 20px;
}

/* Yellow */
.section.yellow label {
  border-left: 40px solid rgba(212,198,10,1);
}

.section.yellow label:before {
  background: rgba(212,198,10,1);
}

.section.yellow .btn {
  background-color: rgba(212,198,10,.9);
  border-color: rgba(212,198,10,1);
}


.section.yellow .btn:hover,
.section.yellow .btn:active,
.section.yellow .btn:focus {
  background-color: rgba(212,198,10,1);
  border-color: rgba(212,198,10,1);
  color: #fff;
}

.section.yellow .section-header{
  background: rgba(212,198,10,1);
}

.section.yellow .section-header:after {
  border-left: 18px solid rgba(212,198,10,1);
}
/* End yellow */


/* Sand */
.section.sand label {
  border-left: 40px solid rgba(252,248,191,1);
}

.section.sand label:before {
  background: rgba(252,248,191,1);
}

.section.sand label.checked {
	color: #333!important;
}

.section.sand .btn {
  background-color: rgba(252,248,191,.9);
  border-color: rgba(252,248,191,1);
}


.section.sand .btn:hover,
.section.sand .btn:active,
.section.sand .btn:focus {
  background-color: rgba(252,248,191,1);
  border-color: rgba(252,248,191,1);
  color: #333;
}

.section.sand .section-header{
  background: rgba(252,248,191,1);
}

.section.sand .section-header h2 {
	color: #333!important;
}

.section.sand .section-header:after {
  border-left: 18px solid rgba(252,248,191,1);
}
/* End of Sand */

/* Orange */
.section.orange label {
  border-left: 40px solid rgba(227,108,10,1);
}

.section.orange label:before {
  background: rgba(227,108,10,1);
}

.section.orange .btn {
  background-color: rgba(227,108,10,.9);
  border-color: rgba(227,108,10,1);
}


.section.orange .btn:hover,
.section.orange .btn:active,
.section.orange .btn:focus {
  background-color: rgba(227,108,10,1);
  border-color: rgba(227,108,10,1);
  color: #333;
}

.section.orange .section-header{
  background: rgba(227,108,10,1);
}

.section.orange .section-header:after {
  border-left: 18px solid rgba(227,108,10,1);
}
/* End of Orange */

/* Lightorange */
.section.lightorange label {
  border-left: 40px solid rgba(251,212,180,1);
}

.section.lightorange label:before {
  background: rgba(251,212,180,1);
}

.section.lightorange .btn {
  background-color: rgba(251,212,180,.9);
  border-color: rgba(251,212,180,1);
}


.section.lightorange .btn:hover,
.section.lightorange .btn:active,
.section.lightorange .btn:focus {
  background-color: rgba(251,212,180,1);
  border-color: rgba(251,212,180,1);
  color: #333;
}

.section.lightorange .section-header{
  background: rgba(251,212,180,1);
}

.section.lightorange .section-header:after {
  border-left: 18px solid rgba(251,212,180,1);
}
/* End of Lightorange */

/* Lightblue */
.section.lightblue label {
  border-left: 40px solid rgba(216,232,230,1);
}

.section.lightblue label:before {
  background: rgba(216,232,230,1);
}

.section.lightblue .btn {
  background-color: rgba(216,232,230,.9);
  border-color: rgba(216,232,230,1);
}


.section.lightblue .btn:hover,
.section.lightblue .btn:active,
.section.lightblue .btn:focus {
  background-color: rgba(216,232,230,1);
  border-color: rgba(216,232,230,1);
  color: #333;
}

.section.lightblue .section-header{
  background: rgba(216,232,230,1);
}

.section.lightblue .section-header:after {
  border-left: 18px solid rgba(216,232,230,1);
}
/* End of lightblue */

/* Lightgreen */
.section.lightgreen label {
  border-left: 40px solid rgba(217,222,196,1);
}

.section.lightgreen label:before {
  background: rgba(217,222,196,1);
}

.section.lightgreen .btn {
  background-color: rgba(217,222,196,.9);
  border-color: rgba(217,222,196,1);
}


.section.lightgreen .btn:hover,
.section.lightgreen .btn:active,
.section.lightgreen .btn:focus {
  background-color: rgba(217,222,196,1);
  border-color: rgba(217,222,196,1);
  color: #333;
}

.section.lightgreen .section-header{
  background: rgba(217,222,196,1);
}

.section.lightgreen .section-header:after {
  border-left: 18px solid rgba(217,222,196,1);
}
/* End of Lightgreen */

/* grass */
.section.grass label {
  border-left: 40px solid rgba(122,134,75,1);
}

.section.grass label:before {
  background: rgba(122,134,75,1);
}

.section.grass .btn {
  background-color: rgba(122,134,75,.9);
  border-color: rgba(122,134,75,1);
}


.section.grass .btn:hover,
.section.grass .btn:active,
.section.grass .btn:focus {
  background-color: rgba(122,134,75,1);
  border-color: rgba(122,134,75,1);
  color: #333;
}

.section.grass .section-header{
  background: rgba(122,134,75,1);
}

.section.grass .section-header:after {
  border-left: 18px solid rgba(122,134,75,1);
}
/* End of grass */

/* gray */
.section.gray label {
  border-left: 40px solid rgba(191,192,194,1);
}

.section.gray label:before {
  background: rgba(191,192,194,1);
}

.section.gray .btn {
  background-color: rgba(191,192,194,.9);
  border-color: rgba(191,192,194,1);
}


.section.gray .btn:hover,
.section.gray .btn:active,
.section.gray .btn:focus {
  background-color: rgba(191,192,194,1);
  border-color: rgba(191,192,194,1);
  color: #333;
}

.section.gray .section-header{
  background: rgba(191,192,194,1);
}

.section.gray .section-header:after {
  border-left: 18px solid rgba(191,192,194,1);
}
/* End of gray */
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div class="container vh100">
  <div class="carousel slide" data-interval="1000000" data-ride="carousel" id="feedbackTest">
 	</div>
</div>

\$\endgroup\$
1
\$\begingroup\$

Here are a few thoughts, in no particular order:

  1. Separation of concerns: Better separate display/styling/HTML issues from the high-level application code (the questions posed and the responses provided).
  2. Object-oriented approach: Consider for example constructors; Survey(), Section(), Slide(), Radio_group(), Radio_button().
  3. Question types: The survey is currently limited to a hard-coded "VERY DISSATISFIED" ... "VERY SATISFIED" scale. Other semantic scales could be offered, as could "Yes/No" booleans.
  4. Branching: It's not atypical for surveys to branch - ie to be responsive to the answers provided. Branching would probably require a change of approach, by which slides are built on-the-fly rather than building everything at the start.
  5. Colors: As it stands colors are limited to those specified in the CSS style sheet. You might consider a more flexible, data-driven approach, implemented wholly in javascript.
  6. Summarizing results: There are issues around the summarization of Likert scale questionnaire results that you need to be aware of. I'm sure the web includes many other good articles/discussions on the subject.
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot for the great comments. Your first two bullets are definitely areas I need to look into. I guess constructors also would make bullet 3 and 4 more approachable. 5. Yeah. That would be a nice addition. Right now this is being built for a specific design manual. But for learning this seems like a great task to implement :D Again. Thanks. Your help i very much appreciated. \$\endgroup\$ – Mark Buskbjerg Sep 1 '17 at 12:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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