JavaScript Sort Ordering and Choice Limiting

maaknplan
maaknplan used Ask the Experts™
on
Hi

I have a simple form where the user can make up to three selections. The user can select the checkboxes in any order, but as they click on each one, the order in which they were checked is remembered, and the position is set to either 1, 2 or 3 when the checkbox is checked, or 0 if the checkbox is unchecked.

The catch is that if a user selects 3 checkboxes, but then unselects the checkbox in position 2, position 3 needs to become 2, so the order is 1,2 and not 1,3.

Example in w3schools

<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script>
        function IsChecked(chk) {

            if (TotalChecked() > 3) {
                alert('Only 3 boxes can be checked!');
                $(chk).prop("checked", false);
                return;
            }

            SetPosition();
        }

        function TotalChecked() {
            var total = 0;
            $(".chk").each(function () {
                if ($(this).prop("checked")) {
                    total++;
                }
            });

            return total;
        }

        function SetPosition() {
            var items = [];

            //Get the items and their values first
            $(".position").each(function () {
                var id = $(this).attr('id');
                var val = $(this).val();
                var item = { id: id, value: val };
                var chk = $("#" + id.replace("p", "c"));
                if ($(chk).prop("checked")) {
                    items.push(item);
                }
            });

            //Sort items

            items.sort(function (a, b) {
                return a.value - b.value;
            });

            $(".position").each(function () {
                var id = $(this).attr('id');
                var chk = $("#" + id.replace("p", "c"));
                var len = items.length;



                if ($(chk).prop("checked")) {
                    var val = $(this).val();

                    if (len == 0) {
                        if (val == 0) { val++ };
                    }
                    else if (len == 1) {
                        items[0].value = 1;
                    }
                    else if (len == 2) {
                        items[0].value = 2;
                        items[1].value = 1;
                    }
                    else {
                        items[0].value = 3;
                        items[1].value = 2;
                        items[2].value = 1;
                    }
                }
                else {
                    $(this).val(0);
                }
            });

            var i;
            for (i = 0; i < items.length; i++) {
                $("#" + items[i].id).val(items[i].value);
            }

        }
    </script>
</head>
<body>

    <input id="c1" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p1" class="form-control position" type="text" value="0" /><br />
    <input id="c2" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p2" class="form-control position" value="0" /><br />
    <input id="c3" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p3" class="form-control position" value="0" /><br />
    <input id="c4" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p4" class="form-control position" value="0" /><br />
    <hr />
    <p id="message"></p>
</body>
</html>

Open in new window


I just can't seem to get this to work, any help is much appreciated.

Thanks.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Most Valuable Expert 2017
Distinguished Expert 2018
Commented:
The solution is quite simple.

Start with a value set to 1 and increment it each time a checkbox is clicked - setting the checkbox's value to this number if the checbox is checked on or 0 if it checked off.

Your HTML might look like this
<input type="checkbox" name="cb1" />
<input type="checkbox" name="cb2" />
<input type="checkbox" name="cb3" />

Open in new window

jQuery would look like this
var start = 1;
$(function() {
  $(':checkbox').change(function() {
    this.value = this.checked ? start++ : 0;
  });
});
</script>

Open in new window


Plain JavaScript would look like this
<script>
var start = 1;
var cb = document.querySelectorAll('input[type=checkbox]');
for(var i = 0; i < cb.length; i++) {
  cb[i].addEventListener('change', function () {
    this.value = this.checked ? start++ : 0;
  });
}
</script>

Open in new window

Working sample here
maaknplanSenior Developer

Author

Commented:
Thanks for the reply. Your solution is pretty elegant. The only issue is it doesn't reverse if you uncheck the checkboxes.

Here is what I ended up with - w3schools example.

<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script>
        function IsChecked(chk) {
            var total = TotalChecked();
            if (total > 3) {
                alert('Only 3 boxes can be checked!');
                $(chk).prop("checked", false);
                return;
            }
            var id = $(chk).attr('id');
            var pos = $("#" + id.replace("c", "p")); //Find the corresponding input
            var initVal = pos.val(); //Get the initial value of the corresponding input

            if ($(chk).prop("checked") == false) {
                pos.val(0); //Reset the corresponding input to 0 if the checkbox is unchecked
            }

            //If the corresponding input value of the function checkbox is 3, no changes need to be made as the other
            //inputs will be correctly set.
            if (initVal == 3) { return };

            switch (total) {
                case 1: //Only one checkbox is selected, so the corresponding input must be 1
                    if ($(chk).prop("checked") == true) {
                        pos.val(1); //If the function checkbox is checked, set the corresponding input to 1
                    }
                    else { //If the function checkbox is not checked, find the checked checkbox, and set its corresponding input to 1
                        $(".chk").each(function () {
                            if ($(this).prop("checked")) {
                                var id = $(this).attr('id');
                                var pos2 = $("#" + id.replace("c", "p"));
                                pos2.val(1);
                            }
                        });
                    }
                    break;
                case 2: //Two checkboxes are selected, so the corresponding inputs must be 1 and 2
                    if ($(chk).prop("checked") == true) {
                        pos.val(2); //If the function checkbox is checked, set the corresponding input to 2
                    }
                    else {//If the function checkbox is not checked, find the checked checkboxes, and set the corresponding inputs
                        $(".chk").each(function () {
                            if ($(this).prop("checked")) {
                                var id = $(this).attr('id');
                                var pos2 = $("#" + id.replace("c", "p")); //Find the corresponding input for the checked checkbox
                                //It doesn't matter if pos2.val() == 1
                                if (pos2.val() == 2) { 
                                    pos2.val(1);
                                }
                                else if (pos2.val() == 3) {
                                    pos2.val(2);
                                }
                            }
                        });
                    }
                    break;
                case 3:
                    if ($(chk).prop("checked") == true) {
                        pos.val(3);
                    }
                    break;
            }
        }

        function TotalChecked() {
            var total = 0;
            $(".chk").each(function () {
                if ($(this).prop("checked")) {
                    total++;
                }
            });
            return total;
        }

    </script>
</head>
<body>

    <input id="c1" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p1" class="form-control position" disabled="disabled" type="text" value="0" /><br />
    <input id="c2" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p2" class="form-control position" disabled="disabled" value="0" /><br />
    <input id="c3" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p3" class="form-control position" disabled="disabled" value="0" /><br />
    <input id="c4" onchange="IsChecked(this)" class="chk" type="checkbox" />
    <input id="p4" class="form-control position" disabled="disabled" value="0" /><br />
    <hr />
    <p id="message"></p>
</body>
</html>

Open in new window

maaknplanSenior Developer

Author

Commented:
The solution offered is simple and elegant, however it does not allow the order selected to be reversed.
Most Valuable Expert 2017
Distinguished Expert 2018

Commented:
however it does not allow the order selected to be reversed.

Please explain by reversed?
Most Valuable Expert 2017
Distinguished Expert 2018

Commented:
Just to explain the solution a bit more.

The paradigm of this solution is to use a progressive counter - like the queue tickets you sometimes get in a store. Each click represents a new arrival and so gets the next number in the queue. A click off represents the leaving of the queue so the number goes down to 0.

The checkbox with the lowest (non-zero) value is the one that was clicked first out of all the remaining clicked items.

The actual value in this case is not relevant - only its magnitude relative to the values of the other clicked items - which provides an ordering on the selected checkboxes.

Trying to see how this solution does not meet the requirements of the questions asked?

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial