OL Learn

Show/Hide a DIV on checkbox change

I have a simple COTG form designed for COVID track and trace.


In this form, we want to show the the email address input if the user checks the “Do you have an email?” case. The input field is hidden by default


I also need to click on the Add button and display the initial state of the row with the checkbox unchecked and the email input field hidden. But the hidden DIv is shown by default and the click event has no effect on the added rows.

I know it’s simple but I am disgning it for the first time given we have just purchased 50 COTG licences as we were given assurances COTG can achieve this. Can anyone helps?

This is the source code of the template which is pretty basic and which you will need to use with the cotg-2.0.0.js api. Basically just use any starter COTG template and copy the below source html code and javascript script

<div class="row">
<div class="columns">
    <table id="users_table" class="table-no-style" role="cotg-table" cellpadding="0" cellspacing="0" style="width: 100%;">
        <thead>
            <tr>
                <td style="width: auto%;"></td>
                <td style="width: 10%;">
                    <br>
                </td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    <div class="small-12 columns">
                        <label>Name 
                            <input name="customer_name" placeholder=" enter your name" type="text">
                        </label>
                    </div>
                    <legend>
                        <input id="user_email" value="" name="check_user_email" type="checkbox">
                        <label for="user_email">Do you have an email?</label>
                    </legend>
                    <div id="check_user_email" class="check_user_email" style="display:none;">
                        <div class="row">
                            <div class="small-12 columns">
                                <label>email address 
                                    <input "="" name="user_email" placeholder="email address" type="email">
                                </label>
                            </div>
                        </div>
                    </div>
                </td>
                <td class="text-right">
                    <button class="round tiny secondary del-row" role="cotg-delete-row-button" type="button">Del</button>
                </td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td class="text-right" colspan="6">
                    <button class="round tiny add-row" role="cotg-add-row-button" type="button">Add</button>
                </td>
            </tr>
        </tfoot>
    </table>
</div>

AND THE SCRIPT

$( document ).ready(function() {
		$('input[type="checkbox"]').click(function() { 
    		var inputName = $(this).attr("name"); 
    		$("." + inputName).toggle();
    	
    });

The following script should do just that:

$('.row').on('change',':checkbox', function() {
	if(this.checked){
		$(this).parent().next().show();
	} else {
		$(this).parent().next().hide();
	}
});

function onElementInserted(containerSelector, elementSelector, callback) {

    var onMutationsObserved = function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length) {
                var elements = $(mutation.addedNodes).find(elementSelector);
                for (var i = 0, len = elements.length; i < len; i++) {
                    callback(elements[i]);
                }
            }
        });
    };

    var target = $(containerSelector)[0];
    var config = { childList: true, subtree: true };
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    var observer = new MutationObserver(onMutationsObserved);    
    observer.observe(target, config);

}

onElementInserted('div', '.check_user_email', function(element) {
    element.style.display = "none";
});

Note that the bulk of the script (the onElementInserted() method) is only implemented to properly handle the display status of dynamically added elements, to make sure that newly added rows don’t display the email address field until the user has actually clicked the corresponding checkbox.

1 Like

That’s just Wonderful Phil!!!

Not in a million years I would have been able to come up with that:smile:

As beautiful as your code works, is there any chance I can find the explanation for the code anywhere so I can tailor it to my own requirements?

In addition, I think this should be handled by the COTG API. At the moment, the API doesn’t seem to handle or detect any nested element and event whithin the dynamically added rows.

This code works great here BUT in addition to that I would like the user to use “exclusive” checkboxes (checkbosex which behave like radio buttons - this or the other) for their prefrerred method of communications (email, sms or phone), but once I add these input checkboxes inside my hidden DIV and use the below script to convert the checkboses to radio buttons, this all works fine inside the first original row, but doesn’t work on dynamically added rows:

<div class="row">
<div class="columns">
    <table id="users_table" class="table-no-style" role="cotg-table" cellpadding="0" cellspacing="0" style="width: 100%;">
        <thead>
            <tr>
                <td style="width: auto%;"></td>
                <td style="width: 10%;">
                    <br>
                </td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    <div class="small-12 columns">
                        <label>Name 
                            <input name="customer_name" placeholder=" enter your name" type="text">
                        </label>
                    </div>
                    <legend>
                        <input id="user_email" value="" name="check_user_email" type="checkbox">
                        <label for="user_email">Do you have an email?</label>
                    </legend>
                    <div id="check_user_email" class="check_user_email" style="display:none;">
                        <div class="row">
                            <div class="small-12 columns">
                                <label>email address 
                                    <input name="user_email" placeholder="email address" type="email">
                                </label>
                            </div>
                        </div>
                        <div>
                            <ul>
                                <div class="checkbox-wrapper">
                                    <input id="emailpref" class="communication-preference" value="email" name="communication-preference" checked="true" type="checkbox">
                                    <label for="emailpref">Email</label>
                                    <input id="smspref" class="communication-preference" value="sms" name="communication-preference" type="checkbox">
                                    <label for="smspref">SMS</label>
                                </div>
                            </ul>
                        </div>
                    </div>
                </td>
                <td class="text-right">
                    <button class="round tiny secondary del-row" role="cotg-delete-row-button" type="button">Del</button>
                </td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td class="text-right" colspan="6">
                    <button class="round tiny add-row" role="cotg-add-row-button" type="button">Add</button>
                </td>
            </tr>
        </tfoot>
    </table>
</div>

And the javascript which inculdes yours:

       CheckBoxToRadio(".checkbox-wrapper .communication-preference");
	function CheckBoxToRadio(selectorOfCheckBox, isUncheckable) {
	$(selectorOfCheckBox).each(function(){
    	$(this).change(function(){
        	var isCheckedThis = $(this).prop('checked');
        	$(selectorOfCheckBox).prop('checked',false);
        
        	if (isCheckedThis === true || isUncheckable === true) {
            $(this).prop('checked', true);
        	}
    	});
	});
}
   

$('.row').on('change',':checkbox', function() {

if(this.checked){

	$(this).parent().next().show();

} else {

	$(this).parent().next().hide();

}

});

function onElementInserted(containerSelector, elementSelector, callback) {

var onMutationsObserved = function(mutations) {

    mutations.forEach(function(mutation) {

        if (mutation.addedNodes.length) {

            var elements = $(mutation.addedNodes).find(elementSelector);

            for (var i = 0, len = elements.length; i < len; i++) {

                callback(elements[i]);

            }

        }

    });

};

var target = $(containerSelector)[0];

var config = { childList: true, subtree: true };

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(onMutationsObserved);    

observer.observe(target, config);

}

onElementInserted(‘div’, ‘.check_user_email’, function(element) {

element.style.display = "none";

});

How do I squeeze my code into yours so the checkboxes on danymacally added rows behave lik ethe ones in the first original row.

Can you add support for this behavior in the COTG API?
Your help is very much appreciated.

Well that’s a multi-part question if I ever saw one. Let me answer in stages.

Explanation of previous code
The $('.row').on('change',':checkbox', function() line creates an event monitor for any checkbox that’s nested under any element that has the class row, which is what gets duplicated because your Add button has been assigned the cotg-add-row-button role. The code inside the event monitor simply toggles the visibility of the email field based on its checkbox status. Note that for this to work, you have to retain the same order of elements that you provided: the check_user_email DIV must immediately follow the LEGEND element, and both elements must belong to the same parent.

The onElementInserted method creates a MutationObserver object, which monitors changes to the DOM. When a row gets added, its child controls are also being duplicated but they retain the same display status that the first row in the table contains. So if you had already “expanded” that row (i.e. the email address field is displayed for that row), then any duplicated rows will also display their that field, even though the user hasn’t clicked on the specific checkbox for that row yet. So by observing the changes to the DOM, more specifically when the <div class="check_user_email"> elements gets created, we are able to immediately set its display mode to none.

Hope that answers that part of your reply. I will address the other points in separate posts.

As for using Radio buttons, I’m not sure why you are converting Checkboxes to Radios, but it’s must simpler with native Radio buttons. I would recommend you change your HTML to something like:

<tbody>
  <tr>
    <td>
      <div class="small-12 columns">
        <label>Name 
          <input name="customer_name" placeholder=" enter your name" type="text">
        </label>
      </div>
      <legend>
        <input id="user_contact" value="" name="check_user_contact" type="checkbox">
        <label for="user_contact">Yes, you may contact me</label>
      </legend>
      <div id="check_user_email" class="check_user_email" style="display: none; margin: 24px">
        <div>Preferred communication method</div>
        <input id="emailpref" class="communication-preference" value="email" name="communication-preference" checked="true" type="radio">
        <label for="emailpref">Email</label>
        <input id="smspref" class="communication-preference" value="sms" name="communication-preference" type="radio">
        <label for="smspref">SMS</label>
        <label id="field_label" class="field_label" for="user_field">Email address</label>
        <input name="user_field" placeholder="myname@myserver.com" type="email">
      </div>
    </td>
    <td class="text-right">
      <button class="round tiny secondary del-row" role="cotg-delete-row-button" type="button">Del</button>
    </td>
  </tr>
</tbody>

This is just the TBODY part, by the way.
Now in the script you add a handler to the Radio buttons’ change event:

$('.row').on('change','[type=radio]', function() {
	var elem = $(this).siblings().closest('.field_label');
	if(this.value=="email"){
		elem.text("Email address");
		elem.next().attr("type", "email");
		elem.next().attr("placeholder","myname@myserver.com");
	} else {
		elem.text("Phone number");
		elem.next().attr("type", "phone");
		elem.next().attr("placeholder","1(555) 555-5555");
	}
});

And you then change the onElementInserted() method to properly reset the child elements in each new row to a proper default value:

onElementInserted('div', '.check_user_email', function(element) {
	var elem = $(element);
    elem.hide();
    var fld = elem.children().closest('.field_label');
    fld.text("Email address");
	fld.next().attr("type", "email");
	fld.next().attr("placeholder","myname@myserver.com");
});

I think this makes for a more user-friendly form over all.
This could all be optimized, but I will leave that exercise to the reader. :slight_smile:

And finally, as far as adding native support in COTG for this, I would say that given the number of different options and methods that can be included in each row, there is still a fair amount of customization that would be required for each individual form. So it would be difficult, if not impossible, to generically account for all situations.

Once you have understood the inner workings of the code above, however, implementing those customizations won’t take you nearly as long the next time around.

This is clear. Thank you.

The actual form is much more complex than that. Unfortunateley, for this specific case, the end user needs the checkboxes to behave like radio buttons for semantic. Radio buttons are also nested in the row for other purposes and your additional script above defintely helps. So thank you so much for that

I might probably endup presebting the form differently to get around the checkbox to radio button behavior.

As a minimum, I think native support for mostly used nested click, change, keyup, keydown…etc event should be added so that these events are automatically added when a row is cloned.
So when a custom script uses these events on nested elements, one should expect them to be available.

For instance a delegation applied with a click event $(“table[role=‘cotg.FieldsTable’]”).on(’'change", nested_selector, function(){…do something…}) should automatically work on nested elements matching the nested_selector, whether they are added dynamically or not.

Well that’s the crux of the problem right there: the onChange event as you describe it here would apply unilaterally to all elements matching the selector. But you certainly don’t want to apply the same action to checkboxes, radio buttons and edit fields. Same goes for mouse or keyboard events. So you would probably have to have as many selectors and event monitors as the number of different control types found in each row… which in the end would probably require as much - if not more - customization than if you have to selectively pick and chose which ones you actually want to apply.

However, your comments do highlight the fact that cloning the table rows could be improved to make it easier for Form designers to achieve their goals with as little fiddling as possible. I will discuss this with my colleagues and we’ll see what we can come up with in future versions of the software.

Good discussion overall… Thanks!

Hello Phil.

Thank you for your valuable help. We came across several other issues whilst designing our forms with COTG.

Most of our codes didn’t have any effect on nested elements inside dynamically added rows.

Just to give you an idea, we have used the SurveyJS API to design forms similar to these examples:

https://www.surveyjs.io/Examples/LibrarySinglePage?id=real-patient-history&platform=jQuery&theme=modern

And

https://www.surveyjs.io/Examples/LibrarySinglePage?id=real-covid-19&platform=jQuery&theme=modern

Our COVID-19 track and trace forms include combinations of components in these two examples.

We were able to produce the forms with virtually no scripts with this API.

In the second page of the first example form above, one can add dynamic rows which contain other nested form elements which respond to standard DOM events. In addition, I was able to convert the radio buttons to checkboxes as previously discussed.

We are contracting for OL resellers and customers in the Middle East and Asia and are facing though competitions and deadlines to produce these forms as soon and with minimal costs as possible. Forms building form about 60% of our business and actual customers will often follow our recommendations as far as the form building software to purchase is concerned. We did recommend COTG for this customer because of the other automation capabilities delivered through the Workflow plugins only to later discover the limitations of building forms in the Designer.

I hope form design within Connect will be improved in the coming releases.

Why not integrate the surveyjs api in Connect as it is free under the MIT license?

I will be sitting down with my colleagues to discuss this topic further. We’ve already had some preliminary discussions on the subject and we agreed that you make several good points in your posts.

Not making any promises at this stage, but we will do our best to make things much easier in future versions.