Joseph McKenzie Joseph McKenzie - 6 months ago 15
jQuery Question

Javascript / jQuery - validate the minimum number of comments required for a form

I have a table with some questions (chosen via

<select>
elements) and comments (in
<textarea>
elements) for each question. All questions are required, but I want only a certain number of comments filled in order to submit. I'm generating the comment and question inputs from a table in SQLite, so I can't hard code
require
anywhere as it would be on all of them. The solution can either be in the Javascript code or the Ruby ERB.

I have a question and comment counter which I'm sure I could use in a solution somehow. I'm pretty new to Javascript, and not sure how to make it work.

This is my attempt at implementing this functionality:

<table class="sortable">
<tr>
<th></th>
</tr>

<% data.each.with_index do |data,index|%>
<%if data[0] >= 6 && data[0] <= 20%>
<tr>
<td><%=data[1]%></td><td><select name="<%=index%>">
<option value=""></option>
<option value="Yes">Yes</option>
<option value="No">No</option>
<option value="na">N/a</option>
</select>

<div class="accordion">Comment</div>
<div class="panel">
<textarea class="comments" name="comment<%=index%>" rows="4" cols="15"></textarea>
</div>
</td>
</tr>
<% end %>
<% end %>
</table>




$('select').change(function() {
// get all selects
var allSelects = $('select');

// set values count by type
//var yes = 0;
//var no = 0;
// var na = 0;
var total = 0;

// for each select increase count
$.each(allSelects, function(i, s) {
// increase count
if($(s).val() == 'Yes' ) { total++; }
if($(s).val() == 'No') { total++; }
if($(s).val() == 'na') { total++; }
});

// update count values summary
$('.cnt-yes').text(yes);
$('.cnt-no').text(no);
$('.cnt-na').text(na);
$('.cnt-total').text(total);
if (total > 19) {
alert('You have completed this ......')

};

var commentstotal = 0;
$('select').change(function() {

$('.comment-total').text(commentstotal);

});

$(".comments").on("blur", function(){
$(this).val() ? commentstotal++ : commentstotal--;
$('.comment-total').text(commentstotal);
})

Answer

The HTML and Javascript was pretty close to what you wanted, and only minor changes were really required. There were a few missing elements, and some reformatting was done to improve readability and consistency. This jsFiddle named Form comment validation shows the solution in action.

The HTML code (assumes that this exists within a <form> element):

<table class="sortable">
  <th>
    <td></td>
  </th>

<% data.each.with_index do |data,index| %>
  <% if data[0] >= 6 && data[0] <= 20 %>
    <tr>
      <td>
        <%=data[1]%></td><td><select name="<%=index%>">
          <option value=""></option>
          <option value="Yes">Yes</option>
          <option value="No">No</option>
          <option value="na">N/A</option>
        </select>

        <div class="accordion">Comment</div>
          <div class="panel">
            <textarea class="comments" name="comment<%=index%>" rows="4" cols="15"></textarea>
        </div>
      </td>
    </tr>
  <% end %>
<% end %>
</table>

<fieldset>
  <legend>Totals</legend>
  <p>Yes choices: <span id="cnt-yes"></span></p>
  <p>No choices: <span id="cnt-no"></span></p>
  <p>N/A choices: <span id="cnt-na"></span></p>
  <p>Total choices: <span id="cnt-total"></span></p>
  <p>Comments: <span id="comment-total"></span></p>
  <p>Progress: <span id="progress-message"></span></p>
</fieldset>

<input type="submit" value="Submit!">

This includes these changes:

  • Corrected the use of the <th> element for the table header
  • Added a <fieldset> to house the counts and progress message
  • Added the submit button to demonstrate the enable/disable functionality

The Javascript code:

var submit = $("input[type='submit']");  // Submit button (to enable/disable dynamically)
var allSelects = $("select");
var cnt_yes = $("#cnt-yes");
var cnt_no = $("#cnt-no");
var cnt_na = $("#cnt-na");
var cnt_total = $("#cnt-total");
var progress_message = $("#progress-message");
var allComments = $(".comments");
var comment_total = $("#comment-total");    

var comments_required = 1;    // <== Change this to set the number of comments required
var comment_minimum_length = 8;  // <== Change this to set the minimum accepted length of a comment

function process_choice() {
    // get all selects

    // set values count by type
    var count_yes = 0;
    var count_no = 0;
    var count_na = 0;
    var count_total = 0;
    var count_comments = 0;
    var complete = true;

    // for each select increase count
    $.each(allSelects, function(i, s) {
        // increase count
        switch ($(s).val()) {
        case "Yes":
            ++count_yes;
            break;
        case "No":
            ++count_no;
            break;
        case "na":
            ++count_na;
            break;
        default:
            complete = false;
            break;
        }
    });

    count_total = count_yes + count_no + count_na;

    $.each(allComments, function() {
        count_comments += $(this).val().length >= comment_minimum_length ? 1 : 0;
    });

    // update count values summary
    cnt_yes.text(count_yes);
    cnt_no.text(count_no);
    cnt_na.text(count_na);
    cnt_total.text(count_total);
    comment_total.text(count_comments);

    if (count_comments < comments_required) {
        var count_remaining = comments_required-count_comments
        $("#progress-message").text("Only " + count_remaining + " choice" + (count_remaining > 1 ? "s" : "") + " left to complete.");
        complete = false;
    } else {
        $("#progress-message").text("The required number of comments has been satisfied.");
    }

    if (complete) {
        submit.removeProp("disabled");
    } else {
        submit.prop("disabled","disabled");
    }
};

allSelects.change(process_choice);
allComments.blur(process_choice);

process_choice();

The updated Javascript includes these changes:

  • jQuery selectors moved to the top of the script to cache the results (preferred for static HTML like this)
  • made the previously anonymous event callback function into a named function for initialization purposes (see the last item)
  • uncommented and renamed the yes, no, and na variables to demonstrate how to update counts dynamically
  • changed the if..if..if to a switch statement, as the options are mutually exclusive for a given choice
  • added the individual totals to count_total for subsequent use
  • added a minimum accepted length check for comments
  • added tracking of whether the form is complete, based on whether all choices and the minimum number of comments have been made
  • updated the totals to the <span> elements in the new <fieldset> display
  • Updated the progress message to include the number of remaining comments or that the requirements have been satisfied
  • enabled/disabled the submit button when choices are unselected or too few comments have been made
  • registered the new named function for the change event
  • called the new named function on startup to initialize the state of the page, so that the page state is guaranteed to be in-sync from the start