Detect if button in accordion has been left open


#1

I am using bootstrap 4 and a set of questions ordered using an accordion.

On one of the levels is the question, and what I want to try and do is detect if they go down the levels to the question open it but then move to another section without closing the question button down.

How can I detect if they do this, and on click of another button close the question button, I have this below, which I think is close as I get the alert when clicking on another button, but it doesnt close the question that was left open.

$(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                alert("the question left open will close and the answer saved to the db");
                $(".question").attr('aria-expanded' == 'false');
            } else {

            }
        });
    });

This is the question level

<div id="accordionQuestion@(Model.QuestionID)">
    <div class="card">
        <div class="card-header" id="headingQuestion@(Model.QuestionID)">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion@(Model.QuestionID)" aria-expanded="false" aria-controls="clpQuestion@(Model.QuestionID)">
                    @(Model.DisplayOrder). @Model.Question
                </button>
            </h5>
        </div>

        <div id="clpQuestion@(Model.QuestionID)" class="collapse" aria-labelledby="headingQuestion@(Model.QuestionID)" data-parent="#accordionSection@(ViewData["categoryID"])">
            <div class="card-body">
               @Html.Partial("~/Views/Questionnaire/_QuestionnaireAnswer.cshtml", Model.AnswerModel, new ViewDataDictionary { { "recordID", @ViewData["recordID"] } })
            </div>
        </div>
    </div>
</div>

and this for example is the category level right at the top

<div id="accordionCategory@(Model.ID)">
    <div class="card category-card">
        <div class="card-header">
            <h5 class="mb-0">
                <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#clpCategory@(Model.ID)" aria-expanded="false" aria-controls="clpCategory@(Model.ID)">
                    @Model.CategoryName 
                </button>
            </h5>
        </div>

        <div id="clpCategory@(Model.ID)" class="collapse" aria-labelledby="headingCategory@(Model.ID)" data-parent="#accordion">
            <div class="card-body">
                @foreach (var section in Model.Sections)
                {
                    @Html.Partial("~/Views/Questionnaire/_QuestionnaireSection.cshtml", section, new ViewDataDictionary { { "recordID", @ViewData["recordID"] }, { "questionID", Model.ID } })
                }
            </div>
        </div>
    </div>
</div>

#2

So the general response i would give is 'forget about detection'.
What i mean by that is...When a section opens, the state of all questions should be 'closed'.
So it doesnt matter if you detect one open, just fire off a close to all of them. Based on your pseudocode, you believe that setting the aria-expanded property to false will collapse them. (I assume it will; I dont know if you're using anything fancy for your accordions/hiding).

So, on button click:
Target all questions, and set the aria-expanded property to false.

$("category-card card-header btn").click(function() {
    $(".question").prop("aria-expanded",false);
}

(From your code, you may need to move the 'question' class to the actual question elements, so that they can be uniquely targetted. Or add another class like 'questionbody' or something to avoid colliding with other scripts.)


#3

I changed attr to prop as below, and again I can get the alert but the question button stays open after I click away and then return to it

$(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                alert("2. save answer to db of question left open and close it");
                $('.question').prop('aria-expanded',false);
            } else {

            }
        });
    });

I currently have 2 functions, the first one is an action when the user closes the question which works fine, and the second deals with if they open the question but dont close it before they move to a new section.

$(function () {
        $('.question').on('click', function (e) {
            var menuItem = $(e.currentTarget);
            if (menuItem.attr('aria-expanded') === 'true') {
                alert("1. save answer to db and close question button");
            } else {
                
            }
        });
    });

$(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                //alert("2. save answer to db of question left open and close it");
                $('.question').attr('aria-expanded','false');
            } else {

            }
        });
    });

#4

Its odd I cant work it out, as all seems to be very clear and working except for the fact that it doesn't actually physically close the class question when its open.

In the function below x equals false until I open the question, then when i click away it firstly equals to true, so all good, then when i go back to check if its physically closed x equals false again, so it sets it to false but doesn't physically close the question up, so I'm wondering if this is possible.

$(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                //alert("2. save answer to db of question left open and close it");
                var x = $('.question').attr('aria-expanded'); 
                alert(x + "1");
                if (x == 'true') {
                    $('.question').attr('aria-expanded', 'false');
                }
            } else {
                var x = $('.question').attr('aria-expanded');
                alert(x + "2");
            }
        });
    });

#5

Well, since you're using jquery anyway, you could try letting jquery handle the showing/hiding by using the toggle or show and hide functions...

but you're targetting .question.
question is the button.
If you're expecting it to collapse the card-body div, you'd need to target THAT, not the button.

What code are you using to OPEN the question?


#6

I see, I tried that by adding a new class to the wrapper div for the question and called it .questionWrapper, and then adapted to the code also, and couldn't work out what needs closing lol, my heads hurting now.

<div id="accordionQuestion@(Model.QuestionID)">
    <div class="card questionWrapper">
        <div class="card-header" id="headingQuestion@(Model.QuestionID)">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion@(Model.QuestionID)" aria-expanded="false" aria-controls="clpQuestion@(Model.QuestionID)">
                    @(Model.DisplayOrder). @Model.Question
                </button>
            </h5>
        </div>

        <div id="clpQuestion@(Model.QuestionID)" class="collapse" aria-labelledby="headingQuestion@(Model.QuestionID)" data-parent="#accordionSection@(ViewData["categoryID"])">
            <div class="card-body">
               @Html.Partial("~/Views/Questionnaire/_QuestionnaireAnswer.cshtml", Model.AnswerModel, new ViewDataDictionary { { "recordID", @ViewData["recordID"] } })
            </div>
        </div>
    </div>
</div>

    $(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                //alert("2. save answer to db of question left open and close it");
                var x = $('.question').attr('aria-expanded'); 
                alert(x + "1");
                if (x == 'true') {
                    $('.questionWrapper').attr('aria-expanded', 'false');
                }
            } else {
                var x = $('.question').attr('aria-expanded');
                alert(x + "2");
            }
        });
    });

#7

For S&G's, here's how i'd do it with your HTML.

$(function() {
  $('.question').click(function() {
       //Show or hide the question.
       $(this).parent().parent().next().toggle();
  });
  $("btn:not(.question)").click(function() {
      //A non-question button (any of the categories) has been clicked. Close All The Questions!
      $(".question").each(function() { $(this).parent().parent().next().hide(); });
  });
});

(Though i'd probably change the HTML to make it work a bit easier... )


#8

AHHHHHH, I missed this reply....

Think its nailed it, cant believe it.

Sorry but thank you


#9

Nearly...
There are issues where with yours, but have got it with below by the looks, just maybe the code is a bit amateur, im not sure.

function setQuestionFalse() {
        $('.question').attr('aria-expanded', 'false');
        $('.question2').collapse('hide');
    }

    function setQuestionFalseSaveDB() {
        // set save to db code here
        setQuestionFalse()
    }

    $(function () {
        $('.question').on('click', function (e) {
            var menuItem = $(e.currentTarget);
            if (menuItem.attr('aria-expanded') === 'true') {
                setQuestionFalseSaveDB()
            } else {
                setQuestionFalse()
            }
        });
    });

    $(function () {
        $('button').on('click', function (e) {
            if ($('.question').attr('aria-expanded') === 'true') {
                var x = $('.question').attr('aria-expanded');
                if (x == 'true') {
                    setQuestionFalseSaveDB()
                } else {
                    setQuestionFalse()
                }
            } else {
                setQuestionFalse()
            }
        });
    });

#10

$(function() {
  $('.question').click(function() {
       //Close all other questions.
      $('.question').not($(this)).each(function() { $(this).parent().parent().next().hide(); });
       //Show or hide the question.
       $(this).parent().parent().next().toggle();
  });
  $("btn:not(.question)").click(function() {
      //A non-question button (any of the categories) has been clicked. Close All The Questions!
      $(".question").each(function() { $(this).parent().parent().next().hide(); });
  });
});

#11

Thanks for sticking with me on this m_hutley, I'm not as good as you obviously so reading the above will this allow me to save to the db using c# and razor when either the user closes the question they open, or if they move to another area leaving the question open, it will close the question and then save it to the db because it has been closed.

Hope that makes sense, Ive added in my code some comments where Im trying to do this.

its -

 // save to db here

in some if statements

I also just tried yours, and it doesnt close an open question if I move to a new section, it only closes the previous question when I then open another question, yes I see.

But I have been told that if there a question open, and if i click any other button it closes that question whilst allowing the user to continue browsing. There a bigger picture that Im not fully aware of I think, this is just the first bit.


#12

It gets a bit more complex when trying to find specifically questions that you're closing (because presumably you dont want to write EVERY question's answer to the database, because they haven't opened every question). It can be done, but because of the html structure it gets a bit messy.

Here's my version of what you (appear) to be trying to do with opening and closing things. Let me know if it isn't the behavior you've described.


#13

Morning,

that looks so complex, and trying to work it in but i can open the first button up to reveal the sections, but as soon as i click the section everything closes back up, so you go in that loop of start stop.

If it helps, below is a complete category, and there can be any number of these and at the moment I have 5.

<div class="moduleQuestionnairePanel" id="pnlFullQuestionnaire" style="display: none">
            <div id="accordion">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

<div id="accordionCategory2">
    <div class="card category-card">
        <div class="card-header">
            <h5 class="mb-0">
                <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#clpCategory2" aria-expanded="false" aria-controls="clpCategory2">
                    First Category 
                </button>
            </h5>
        </div>

        <div id="clpCategory2" class="collapse" aria-labelledby="headingCategory2" data-parent="#accordion">
            <div class="card-body">
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>-->

<div id="accordionSection2">
    <div class="card">
        <div class="card-header" >
            <h5 class="mb-0">
                <button class="btn btn-link" data-toggle="collapse" data-target="#clpSection2" aria-expanded="false" aria-controls="clpSection2">
                    First Section
                </button>
            </h5>
        </div>

        <div id="clpSection2" class="collapse" aria-labelledby="headingSection2" data-parent="#accordionCategory2">
            <div class="card-body">
<div id="accordionQuestion4">
    <div class="card">
        <div class="card-header" id="headingQuestion4">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion4" aria-expanded="false" aria-controls="clpQuestion4">
                    1. One
                </button>
            </h5>
        </div>

        <div id="clpQuestion4" class="collapse question2" aria-labelledby="headingQuestion4" data-parent="#accordionSection2">
            <div class="card-body">
               <form action="/en/Questionnaire/SubmitAnswer" class="questionForm" data-ajax="true" data-ajax-method="POST" data-ajax-success="ProcessAjaxFormResponse" id="frmQuestion4" method="post"><input data-val="true" data-val-number="The field QuestionID must be a number." data-val-required="The QuestionID field is required." id="QuestionID" name="QuestionID" type="hidden" value="4" /><input data-val="true" data-val-number="The field Int64 must be a number." data-val-required="The Int64 field is required." id="recordID" name="recordID" type="hidden" value="1" />    <div class="questionAnswerWrapper">
        <input data-val="true" data-val-number="The field ChoiceID must be a number." id="hdnChoiceID4" name="ChoiceID" type="hidden" value="7" />
            <button type="button" id="btnQ4C7" class="choice-btn btn btn-info active" data-choice-id="7" data-question-id="4" data-model-field=#hdnChoiceID4>Yes</button>
            <button type="button" id="btnQ4C8" class="choice-btn btn btn-info " data-choice-id="8" data-question-id="4" data-model-field=#hdnChoiceID4>Maybe</button>
            <button type="button" id="btnQ4C9" class="choice-btn btn btn-info " data-choice-id="9" data-question-id="4" data-model-field=#hdnChoiceID4>No</button>
    </div>
</form>
            </div>
        </div>
    </div>
</div>
<div id="accordionQuestion5">
    <div class="card">
        <div class="card-header" id="headingQuestion5">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion5" aria-expanded="false" aria-controls="clpQuestion5">
                    2. Two
                </button>
            </h5>
        </div>

        <div id="clpQuestion5" class="collapse question2" aria-labelledby="headingQuestion5" data-parent="#accordionSection2">
            <div class="card-body">
               <form action="/en/Questionnaire/SubmitAnswer" class="questionForm" data-ajax="true" data-ajax-method="POST" data-ajax-success="ProcessAjaxFormResponse" id="frmQuestion5" method="post"><input data-val="true" data-val-number="The field QuestionID must be a number." data-val-required="The QuestionID field is required." id="QuestionID" name="QuestionID" type="hidden" value="5" /><input data-val="true" data-val-number="The field Int64 must be a number." data-val-required="The Int64 field is required." id="recordID" name="recordID" type="hidden" value="1" />    <div class="questionAnswerWrapper">
        <input data-val="true" data-val-number="The field ChoiceID must be a number." id="hdnChoiceID5" name="ChoiceID" type="hidden" value="10" />
            <button type="button" id="btnQ5C10" class="choice-btn btn btn-info active" data-choice-id="10" data-question-id="5" data-model-field=#hdnChoiceID5>Yes</button>
            <button type="button" id="btnQ5C11" class="choice-btn btn btn-info " data-choice-id="11" data-question-id="5" data-model-field=#hdnChoiceID5>No</button>
    </div>
</form>
            </div>
        </div>
    </div>
</div>
            </div>
        </div>
    </div>
</div>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>-->

<div id="accordionSection3">
    <div class="card">
        <div class="card-header" >
            <h5 class="mb-0">
                <button class="btn btn-link" data-toggle="collapse" data-target="#clpSection3" aria-expanded="false" aria-controls="clpSection3">
                    Second Section
                </button>
            </h5>
        </div>

        <div id="clpSection3" class="collapse" aria-labelledby="headingSection3" data-parent="#accordionCategory2">
            <div class="card-body">
<div id="accordionQuestion6">
    <div class="card">
        <div class="card-header" id="headingQuestion6">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion6" aria-expanded="false" aria-controls="clpQuestion6">
                    1. Three
                </button>
            </h5>
        </div>

        <div id="clpQuestion6" class="collapse question2" aria-labelledby="headingQuestion6" data-parent="#accordionSection3">
            <div class="card-body">
               <form action="/en/Questionnaire/SubmitAnswer" class="questionForm" data-ajax="true" data-ajax-method="POST" data-ajax-success="ProcessAjaxFormResponse" id="frmQuestion6" method="post"><input data-val="true" data-val-number="The field QuestionID must be a number." data-val-required="The QuestionID field is required." id="QuestionID" name="QuestionID" type="hidden" value="6" /><input data-val="true" data-val-number="The field Int64 must be a number." data-val-required="The Int64 field is required." id="recordID" name="recordID" type="hidden" value="1" />    <div class="questionAnswerWrapper">
        <input data-val="true" data-val-number="The field ChoiceID must be a number." id="hdnChoiceID6" name="ChoiceID" type="hidden" value="13" />
            <button type="button" id="btnQ6C12" class="choice-btn btn btn-info " data-choice-id="12" data-question-id="6" data-model-field=#hdnChoiceID6>Yes</button>
            <button type="button" id="btnQ6C13" class="choice-btn btn btn-info active" data-choice-id="13" data-question-id="6" data-model-field=#hdnChoiceID6>No</button>
    </div>
</form>
            </div>
        </div>
    </div>
</div>
<div id="accordionQuestion7">
    <div class="card">
        <div class="card-header" id="headingQuestion7">
            <h5 class="mb-0">
                <button class="btn btn-link question" data-toggle="collapse" data-target="#clpQuestion7" aria-expanded="false" aria-controls="clpQuestion7">
                    2. Four
                </button>
            </h5>
        </div>

        <div id="clpQuestion7" class="collapse question2" aria-labelledby="headingQuestion7" data-parent="#accordionSection3">
            <div class="card-body">
               <form action="/en/Questionnaire/SubmitAnswer" class="questionForm" data-ajax="true" data-ajax-method="POST" data-ajax-success="ProcessAjaxFormResponse" id="frmQuestion7" method="post"><input data-val="true" data-val-number="The field QuestionID must be a number." data-val-required="The QuestionID field is required." id="QuestionID" name="QuestionID" type="hidden" value="7" /><input data-val="true" data-val-number="The field Int64 must be a number." data-val-required="The Int64 field is required." id="recordID" name="recordID" type="hidden" value="1" />    <div class="questionAnswerWrapper">
        <input data-val="true" data-val-number="The field ChoiceID must be a number." id="hdnChoiceID7" name="ChoiceID" type="hidden" value="14" />
            <button type="button" id="btnQ7C14" class="choice-btn btn btn-info active" data-choice-id="14" data-question-id="7" data-model-field=#hdnChoiceID7>Yes</button>
            <button type="button" id="btnQ7C15" class="choice-btn btn btn-info " data-choice-id="15" data-question-id="7" data-model-field=#hdnChoiceID7>No</button>
    </div>
</form>
            </div>
        </div>
    </div>
</div>
            </div>
        </div>
    </div>
</div>
            </div>
        </div>
    </div>
</div>

#14

I might be wrong but it seemed to sort itself out by me adding an extra .parent on the line below.

$(".btn").not(".question").not($(this)).parent().parent().parent().next().hide();

One other thing, it does save great if i have a question open and then click elsewhere, but it also needs to save if they open a question and close the same question up.

There a slight 'do I don't I' moment too when you attempt to open a question up after clicking around a bit. It starts to toggle open, then its as if it thinks about very briefly, then carry's on opening.

But think its nearly there, thank you

This seems to be closer

$(function () {
// controls the save answer action when navigating the accordion menu in details

/* Lee - Attempt 3 */
    function hideopen(cur) {

            //Find only the open questions, and close them.
        $('.question').not(cur).parent().parent().next().not(':hidden').each(function () {
            alert("save1");
                //Do the DB stuff here.
//also save to db when they close the question up they open without moving to a new category
                console.log("Closed " + $(this).find('div').html().trim());
                $(this).hide();
        });

    }

    $(function () {

        $('.question').click(function () {
            //Close all OPEN questions.
            hideopen($(this));
            //Show or hide the question.
            $(this).parent().parent().next().toggle();
        });

        $(".btn").not(".question").click(function () {
            //As above.
            //Close all OPEN questions.
            hideopen($(this));
            //Category functions.
            //Hide every other category.
            $(".btn").not(".question").not($(this)).parent().parent().parent().next().hide();
            //Toggle this one.
            $(this).parent().next().toggle();
        });

    });

#15

Ah okay see, you snuck the Sections level in on me. Important information!

At this point, we need a way of separating Categories, Sections, and Questions. So we need more classes, because "btn" isn't sufficient anymore.

I'm going to add the 'sectionbtn' class to all buttons related to sections. I'm going to add the 'categorybtn' class to all buttons related to categories.


#16

Brill your back lol, Im so grateful for your help.

I have added the extra classes, and I feel bad saying it but there two things.

When the user opens the question and then closes that question it needs to save there too.

Another thing, when your drilling down in say the first category, and then you move to a new category it leaves open the levels in the first category you opened, as in yes it opens up the new category to reveal the sections, but it doesnt close the first category up like the accordion supposed to do.

Thanks again for everything


#17

i think I resolved the one problem with not closing the previous category up with one less parent as below

$(".categorybtn").not($(this)).parent().parent().next().hide();

Its just making sure now if they open a question and then close it that saves to the db too


#18

Yeah, I fixed that in the pen, you must have clicked on it before i noticed (Dont have a second category to test with).

As far as saving when they close the existing question, it's a check inside the question.click function. I have updated the pen above with the change.


#19

EDIT: Except i'm a goofball first thing in the morning and cant remember that display is a style, not an attribute. Derp.

.attr() should be .css() in that pen.


#20

superb, your awesome, thank you very much...

That's fixed it, and I am very grateful again thank you!!!!