1
\$\begingroup\$

I'm using PHP HTML and JavaScript to write questions that need to be shown/hidden based on inputs from the user. I have several rows in a table, each row contains a <td> with a question and a <td> with an input (for the answer to the question) and I am using JavaScript to hide/show rows that contain questions that sometimes aren't applicable to the user if they've answered questions with a certain answer.

For example, Question 1 should always be displayed, Question 2 only displays if the answer to question one is not yes (I have to allow the possibility that the answer is blank). Question 3 should display if Question 2's answer is not yes.

Also, if question 1's answer is changed to something other than yes. Question 2 and 3 should be hidden.

The code works correctly, but is there a more efficient method to write the code rather than repeating the cumbersome boolean logic shown below? To make more questions I need to copy and paste the code and change the boolean conditions and the Element IDs for each question.

// question asks them if they served in a second branch of the military
// if hideAllQuestions is true, then all questions that rely on this
// question and questions relying on those questions should disappear. 
// if showMainQuestion is true, then all immediate follow up questions 
var secondBranchLogic = function(hideAllQuestions = false,     showMainQuestion = true) 
{   
    var mainQuestionTagID = [ "#secondbranch_row" ];
    if (showMainQuestion) {
        // functions that cycle through each question in mainQuestionTagID and show them, set them to required, or hide them.
        enableQuestions(mainQuestionTagID);
        requireQuestions(mainQuestionTagID);
    } else {
        disableQuestions(mainQuestionTagID);
    }

    // boolean logic that determines if the main question's answer should show/hide the sub questions.
    var answer = $("#secondbranch").val();
    var hasSecondBranch = ( answer === 'Yes');
    var showSubQuestions = (showMainQuestion && hasSecondBranch && !hideAllQuestions);
    // all immediate sub questions that should be shown/hidden, but not the sub questions below said sub questions.
    var questionTagIDs = [
        "#militarybranch2_row",
        "#militarybranch2length_row", 
        "#militarybranch2rank_row",
        "#militarybranch2job_row" ];

    if (showSubQuestions) {
        enableQuestions(questionTagIDs);
        requireQuestions(questionTagIDs);
    } else {
        disableQuestions(questionTagIDs);
    }

    // boolean logic determining if dischargeLogic should hide/show some questions.
    var hideNextQuestions = ( answer === '' || hideAllQuestions );
    var showNextMainQuestion = ( !hideNextQuestions);
    dischargeLogic(hideNextQuestions, showNextMainQuestion); 
};

// question asking if they were discharged honorably
// this function behaves much like the previous one.
var dischargeLogic = function(hideAllQuestions = false, showMainQuestion = true) 
{
    var mainQuestionTagID = [ "#honorabledischarge_row" ];
    if (showMainQuestion) {

        enableQuestions(mainQuestionTagID);
        requireQuestions(mainQuestionTagID);
    } else {
        disableQuestions(mainQuestionTagID);
    }

    var answer = $("#honorabledischarge").val();
    var answered = (answer !== '');
    var questionTagIDs = [ "#dischargedescription_row" ];
    if (answered) 
    {
        honorablyDischarged = (answer === 'Yes');
        showSubQuestion = (!honorablyDischarged && !hideAllQuestions);
        if (showSubQuestion) {
            enableQuestions(questionTagIDs);
            requireQuestions(questionTagIDs);
        } else {
            disableQuestions(questionTagIDs);
        }
    } else {
        disableQuestions(questionTagIDs);
    }

    var hideNextQuestions = ( answer === '' || hideAllQuestions);
    var showNextMainQuestion = (!hideNextQuestions);
    deploymentsLogic(hideNextQuestions, showNextMainQuestion);
};

Now, here's the PHP/HTML code for it:

<tr style="display:none;" id="secondbranch_row">
    <td  class="Question">
        Have you served in another military branch?
    </td>
    <td style="width:100px;">
        <select type="text" id="secondbranch" name="secondbranch" style="width:100%;" class="needs_saved_military" required>
            <option value=''></option>
            <option value='Yes'>Yes</option>
            <option value='No'>No</option>
        </select>
    </td>
</tr>
<tr style="display:none;" id="militarybranch2_row">
    <td>
        Which branch did you serve in?
    </td>
    <td>
        <select type="text" id="militarybranch2" name="militarybranch2" style="width:100%;" class="needs_saved_military" required>
            <option value=''></option>
            <!-- all caps means it's a global constant array -->
            <?php foreach (MILITARY_BRANCH as $iBranch)
            {?>
                <option value = "<?php echo $iBranch?>"><?php echo $iBranch?> </option>
            <?php }?>
        </select>
    </td>
</tr>
<tr style="display:none;" id="militarybranch2length_row">
    <td>
        How long?
    </td>
    <td>
        <select type="text" id="militarybranch2length" name="militarybranch2length" style="width:100%;" class="needs_saved_military" required>
            <option value=''></option>
            <?php foreach (MILITARY_YEARS_SERVED as $iYears)
            {?>
                <option value = "<?php echo $iYears?>"><?php echo $iYears?> </option>
            <?php }?>
        </select>
    </td>
</tr>
<tr style="display:none;" id="militarybranch2rank_row">
    <td>
        What was your rank?
    </td>
    <td>
        <input type="text" id="militarybranch2rank" name="militarybranch2rank" style="width:100%;" class="needs_saved_military" required></input>
    </td>
</tr>
<tr style="display:none;" id="militarybranch2job_row">
    <td>
        What was your job?
    </td>
    <td>
        <input type="text" id="militarybranch2job" name="militarybranch2job" style="width:100%;" class="needs_saved_military" required></input>
    </td>
</tr>

<!-- HONORABLE DISCHARGE -->
<tr style="display:none;" id="honorabledischarge_row">
    <td  class="Question">
        Were you honorably discharged?
    </td>
    <td style="width:100px;">
        <select type="text" id="honorabledischarge" name="honorabledischarge" style="width:100%;" class="needs_saved_military" required>
            <option value=''></option>
            <option value='Yes'>Yes</option>
            <option value='No'>No</option>
        </select>
    </td>
</tr>
<tr style="display:none;" id="dischargedescription_row">
    <td>
        What was the discharge?
    </td>
    <td>
        <input type="text" id="dischargedescription" name="dischargedescription" style="width:100%;" class="needs_saved_military" required></input>
    </td>
</tr>
\$\endgroup\$
  • \$\begingroup\$ Is the first question “Have you served in another military branch?”? If so, how does it’s containing row have display: none removed from the style? Also, you stated “Question 3 should display if Question 2's answer is not yes.” but question 2 does not look like a yes/no question... \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Mar 20 at 12:26
  • 3
    \$\begingroup\$ Could you please edit your post to add the calls to secondBranchLogic() and dischargeLogic() so we can see what parameters are passed? \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ May 3 at 11:58
1
\$\begingroup\$

Your question is about avoiding the javascript being so cumbersome, but I wonder whether once you've worked out an elegant script to write the table, whether elegant javascript code will be easier to work out.

One trick for tables is to create them in entirely in javascript. The following example isn't for the entirety of your table, but it includes some of the sorts of logic I think you would use.

var branches = ["military_branch1","military_branch2"];

military_table_html = '<table>';

for(var i=0; i<branches.length; i++){

  military_table_html += "<tr><td>" +branches[i]+"</td></tr>";
 
 if(branches[i] == "military_branch1"){
    military_table_html += "<tr><td>that was the first branch</td></tr>";
  } else {
   military_table_html += "<tr><td>that was the second branch</td></tr>";
  }
}
military_table_html += '</table>';

document.getElementById("military_table").innerHTML = military_table_html;
<span id="military_table"></span>

\$\endgroup\$
0
\$\begingroup\$

is there a more efficient method to write the code rather than repeating the cumbersome boolean logic shown below?

Yes. Yes there is. Think about the logic at any given question/answer and make it work for every question/answer. The result will be a single function.

We are at question #2:

  • Evaluate the previous question's answer:
    • NO: display this question
    • YES: hide this question

Make that a function. Call it for every question, in order. Every time any question is answered always call it for all questions (in order) as this will handle arbitrarily changed answers.


To make more questions I need to copy and paste the code and change the boolean conditions and the Element IDs for each question.

No. No you don't. Reuse the function; passing in 1) prev. question's answer 2) this question's ID. Perhaps the questions are numbered and that should suffice as an identifier.


Organize your code

  • Build the question table separately as @AnthonyHaffey says
  • Add an event handler that calls that displayHide function. Every question uses this one handler.
  • A javascript array holding all the original questions. Pass that into the table building function.
  • Each question has a unique identifier. In the HTML table make an appropriate element property to hold it.
\$\endgroup\$
  • 2
    \$\begingroup\$ Downvote elaboration please! If we can't give the OP the resolution (s)he's looking for at least avoid wrong answers. Down votes should not be relative to other answers nor for a non-preferred answer. If my answer is wrong the OP needs to know why/how - and so do I. As of this writing there are only 2 answers, collectively with zero up votes. If code will clarify please say so. If I'm answering a question that was not asked please say so. \$\endgroup\$ – radarbob May 14 '18 at 17:52
0
\$\begingroup\$

Reviewing your code

Javascript

Some variables like honorablyDischarged and showSubQuestion are not declared with a keyword var, let or const, and thus are treated as global variables. This is typically something to avoid. This may be difficult to explain but there are many reasons to avoid global variables, many of which are explained in this article. The reasons that stand out the most to me are Implicit coupling and Testing and Confinement (testing becomes a lot more difficult when global variables are used).

Variable showNextMainQuestion is only used one time immediately after it is assigned, just before each function is completed. It is a waste of a variable - just pass the negated value to the subsequent function call.

HTML

I see input elements in the markup like:

<input type="text" id="militarybranch2rank" name="militarybranch2rank" style="width:100%;" class="needs_saved_military" required>
</input>

But input elements have no permitted content1 2 and thus are empty elements.

In HTML, using a closing tag on an empty element is usually invalid. For example, <input type="text"></input> is invalid HTML.3

There is no reason to add the closing tag on input elements - instead make them self-close.

<input type="text" id="militarybranch2rank" name="militarybranch2rank" style="width:100%;" class="needs_saved_military" required />

Response to your question

The code works correctly, but is there a more efficient method to write the code rather than repeating the cumbersome boolean logic shown below?

Yes you could use an approach similar to Event delegation to handle change events on any form element and conditionally display subsequent rows.

You stated:

Question 1 should always be displayed, Question 2 only displays if the answer to question one is not yes (I have to allow the possibility that the answer is blank). Question 3 should display if Question 2's answer is not yes.

but in the example you gave, the second question has values coming from the PHP constant MILITARY_BRANCH - does that contain elements with yes and no? I presume not but maybe I am wrong... Anyway, you could do something like below, where each change event conditionally shows subsequent rows.

You could also add data attributes to the markup to notate which rows/inputs should have yes/no or other values and then have conditional logic (using jQuery's .data() method) -or use that same method to mark a row as being "completed" or some other sentinel value to signify that certain subsequent rows should be displayed.

const toggleNextRowOnChange = changeEvent => {
    const element = $(changeEvent.target);
    if (!element.is('input, select')) {
        return;
    }
    const row = element.closest('tr');
    if (!row.length) {
        return;
    }
    const nextRows = row.nextAll('tr');
    if (!nextRows.length) {
        return;
    }
    //check if 'yes' is an option - if not then only do this if answer is not yes?
    if (element.val()) { 
        nextRows.first().show();
        //optionally show subsequent rows if the next row has a value that should entail subsequent rows are displayed
    }
    else {
        nextRows.hide();
    }
  };
$(_ => { //DOM ready callback
    //register change handler on body
    $(document.body).on('keyup mouseup change', toggleNextRowOnChange);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
    <tr id="secondbranch_row">
        <td  class="Question">
            Have you served in another military branch?
        </td>
        <td style="width:100px;">
            <select type="text" id="secondbranch" name="secondbranch" style="width:100%;" class="needs_saved_military" required>
                <option value=''></option>
                <option value='Yes'>Yes</option>
                <option value='No'>No</option>
            </select>
        </td>
    </tr>
    <tr style="display:none;" id="militarybranch2_row">
        <td>
            Which branch did you serve in?
        </td>
        <td>
            <select type="text" id="militarybranch2" name="militarybranch2" style="width:100%;" class="needs_saved_military" required>
                <option value=''></option>
                <!-- all caps means it's a global constant array -->
                    <option value = "Army">Army </option>
                    <option value = "CoastGuard">CoastGuard </option>
                    <option value = "Marines">Marines </option>
            </select>
        </td>
    </tr>
    <tr style="display:none;" id="militarybranch2length_row">
        <td>
            How long?
        </td>
        <td>
            <select type="text" id="militarybranch2length" name="militarybranch2length" style="width:100%;" class="needs_saved_military" required>
                <option value=''></option>
                <option value = "1">1</option>
                <option value = "2">2</option>
                <option value = "3">3</option>
                <option value = "4">4</option>
                <option value = "5">5</option>
                <option value = "6">6</option>
            </select>
        </td>
    </tr>
    <tr style="display:none;" id="militarybranch2rank_row">
        <td>
            What was your rank?
        </td>
        <td>
            <input type="text" id="militarybranch2rank" name="militarybranch2rank" style="width:100%;" class="needs_saved_military" required />
        </td>
    </tr>
    <tr style="display:none;" id="militarybranch2job_row">
        <td>
            What was your job?
        </td>
        <td>
            <input type="text" id="militarybranch2job" name="militarybranch2job" style="width:100%;" class="needs_saved_military" required />
        </td>
    </tr>
    <!-- HONORABLE DISCHARGE -->
    <tr style="display:none;" id="honorabledischarge_row">
        <td  class="Question">
            Were you honorably discharged?
        </td>
        <td style="width:100px;">
            <select type="text" id="honorabledischarge" name="honorabledischarge" style="width:100%;" class="needs_saved_military" required>
                <option value=''></option>
                <option value='Yes'>Yes</option>
                <option value='No'>No</option>
            </select>
        </td>
    </tr>
    <tr style="display:none;" id="dischargedescription_row">
        <td>
            What was the discharge?
        </td>
        <td>
            <input type="text" id="dischargedescription" name="dischargedescription" style="width:100%;" class="needs_saved_military" required />
        </td>
    </tr>
  </table>

1https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input

2https://html.spec.whatwg.org/multipage/input.html#the-input-element

3https://developer.mozilla.org/en-US/docs/Glossary/Empty_element

\$\endgroup\$

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.