E
E
Eugene2019-01-12 22:05:11
JavaScript
Eugene, 2019-01-12 22:05:11

How to competently make 3 states of a checkbox in an Angular filter?

Good day.
Faced with rather difficult task for the level of knowledge.
I am working with a project on Angular, more in terms of layout. I'm not familiar with TS, but I can figure it out with an example.
The site has a filter with 3 levels of nesting:
- Level 1
- Level 2
- Level 2
- Level 3
- Level 3
- Level 3
- Level 2
If one of the items on the 3rd level is checked, the checkboxes of the 2nd and 1st levels were marked as indeterminate (3rd state), respectively, at the 2nd level it is similar. If all checkboxes are checked at this level (state 2), the checkbox of the parent checkbox on the level above is also checked and removed if it is cleared at the level below.
In layout on jq, there were no problems with the implementation, well, maybe a little crooked.
JQ

$(document).ready(function () {
    $('.b-filterItem_bvar').on('click', '.cbx-label', function () {
        // Вложенность 3-го уровня
        if ($(this).closest('.b-filterItem').hasClass('fi_b-dropdown')) {
            var f_filterItem = $(this).closest('.b-filterItem'),
                  f_filterItemHead = f_filterItem.find('.b-filterItem_head.checkGroup'),
                  f_filterItemCount = f_filterItem.find('.bvar_dropdown').find('.cbx-input').length,
                  f_filterItem_cheked = f_filterItem.find('.bvar_dropdown').find('.cbx-input:checked').length;

            if (f_filterItemCount === f_filterItem_cheked) {
            // Отмечены все чекбоксы
                f_filterItemHead.find('.cbx-label').addClass('checked');
                f_filterItemHead.find('.cbx-input').attr("checked", "checked");
            if (f_filterItemHead.find('.cbx__checkMark').hasClass('noAllChecked')) {
                f_filterItemHead.find('.cbx__checkMark').removeClass('noAllChecked');
            }
            } else if (f_filterItem_cheked === 0) {
                //  Не отмечены чекбоксы
                f_filterItemHead.find('.cbx-label').removeClass('checked');
                f_filterItemHead.find('.cbx-input').removeAttr("checked", "checked");
            if (f_filterItemHead.find('.cbx__checkMark').hasClass('noAllChecked')) {
                f_filterItemHead.find('.cbx__checkMark').removeClass('noAllChecked');
            }
            } else if (f_filterItemCount !== f_filterItem_cheked & f_filterItem_cheked !== 0) {
                // Отмечены некоторые чекбоксы
                f_filterItemHead.find('.cbx-label').removeClass('checked');
                f_filterItemHead.find('.cbx-input').removeAttr("checked", "checked");
                f_filterItemHead.find('.cbx__checkMark').addClass('noAllChecked');
            }
        }

        // Замена чекбокса родительской группы
        var f_cbxBvar = $(this).closest('.b-filterItem_bvar'),
              f_itemBodyHead = $(this).closest('.b-filterItem_body').prev('.b-filterItem_head '),
              f_cbxBvarCount = f_cbxBvar.find('.cbx-input').length,
              f_cbxBvarCount_cheked = f_cbxBvar.find('.cbx-input:checked').length;

        if (f_cbxBvarCount === f_cbxBvarCount_cheked) {
            f_itemBodyHead.find('.cbx__checkMark').removeClass('noAllChecked');
            f_itemBodyHead.find('.cbx-label').addClass('checked');
            f_itemBodyHead.find('.cbx-input').attr("checked", "checked");
        } else if (f_cbxBvarCount_cheked === 0) {
            f_itemBodyHead.find('.cbx__checkMark').removeClass('noAllChecked');
            f_itemBodyHead.find('.cbx-label').removeClass('checked');
            f_itemBodyHead.find('.cbx-input').removeAttr("checked", "checked");
        } else if (f_cbxBvarCount !== f_cbxBvarCount_cheked) {
            f_itemBodyHead.find('.cbx__checkMark').addClass('noAllChecked');
            f_itemBodyHead.find('.cbx-label').removeClass('checked');
            f_itemBodyHead.find('.cbx-input').removeAttr("checked", "checked");
        }
    });
});

In this example, I start from the click event on the .cbx-label class and look for parents from it to the DOM and change their state.
HTML
<div class="b-filterItem dropdown open">
  
  <!-- 1 уровень -->
  <div class="b-filterItem_head checkGroup">
    <div class="cbx">
      <div class="cbx-label">
        <input class="cbx-input" id="letters_cbx_1" type="checkbox" name="" value="">
        <div class="cbx__box"></div>
        <div class="cbx__checkMark"></div>
      </div>
    </div>
    <div class="b-filterItem_title">
      <span class="b-filterItem_arrow f_chO"></span>
      <span class="b-filterItem_name text-overflow">Типы</span>
    </div>
  </div>

  <div class="b-filterItem_body f_chB">
    <div class="b-filterItem_bvar">
      
      <!-- 2 уровень -->
      <div class="b-filterItem">
        <div class="b-filterItem_head">
          <div class="cbx">
            <div class="cbx-label">
              <input class="cbx-input" type="checkbox" name="">
              <div class="cbx__box"></div>
              <div class="cbx__checkMark"></div>
            </div>
          </div>
          <div class="b-filterItem_bvar-title text-overflow">
              <span class="text-overflow">Аккаунт</span>
          </div>
        </div>
        <div class="bvar_dropdown f_chB" style="display: none;">

          <!-- 3 уровень -->
          <div class="bvar_dropdown-item">
            <div class="app-spacer"></div>
            <div class="cbx-label">
              <div class="cbx">
                <input class="cbx-input" type="checkbox" name="">
                <div class="cbx__box"></div>
                <div class="cbx__checkMark"></div>
              </div>
              <div class="bvar_dropdown-label">
                  <span class="bvar_dropdown-title text-overflow">
                    <span class="text-overflow">Facetime аудио</span>
                  </span>
              </div>
            </div>
          </div>
          <!-- 3 уровень -->
          <!-- 3 уровень -->

        </div>
      </div>
      <!-- 2 уровень -->
      <!-- 2 уровень -->

    </div>
  </div>
  <!-- 1 уровень -->
  <!-- 1 уровень -->
</div>

SCSS
&__box:before,
  &__box:after {
    content: "";
    position: absolute;
    width: 16px;
    height: 16px;
    border-radius: $borderRadius;
    top: 3px;
    left: 0;
  }
  &__box:before {
    border: 2px solid $greyCbxColor;
    background: transparent;
  }
  &__box:after,
  &__box.indeterminate:after {
    background: $blueColor url('../assets/icons/svg/icon-checked.svg') no-repeat center;
    background-size: 80%;
    color: #fff;
    opacity: 0;
  }

  input[type="checkbox"]:checked ~ &__box:after {
    opacity: 1;
  }
  input[type="checkbox"]:indeterminate ~ &__box:after {
    opacity: 1;
    background: $blueColor url('../assets/icons/svg/icon--indeterminate-wite.svg') no-repeat center;
  }

  input[type="checkbox"],
  &-input {
    position: absolute;
    width: 16px;
    height: 16px;
    margin: 0;
    top: 3px;
    opacity: 0;
    z-index: 1;
  }

I know that jq is not optimal and there are a lot of unnecessary movements, but it works well and without errors.
Simply copying the structure and attaching a js file with jq does not work because the filter is loaded by the angular component.
I want to understand how without jq you can implement a similar functionality with 3 states.
In jq, by and large, it runs up and down and hangs classes, a checkmark (2 state) and an indeterminate checkbox state (indeterminate) are implemented by simply showing the cbx__checkMark class.
Ps
In the scss example, the states are not implemented using the cbx__checkMark class, but on pseudo-elements. I don't think it should be confusing.
I want to understand how to do it in JS without jQuery or in TypeScrypt. I will be grateful for hints.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
G
grinat, 2019-01-12
@grinat

You create a structure like this:

{
  "value": 1,
  "isSelected": true,
  "allChildsSelected": true,
  "childs": [
    {
      "isSelected": true,
      "value": 2,
      "allChildsSelected": true,
      "childs": [
        {
          "value": 3,
          "allChildsSelected": true,
          "isSelected": true,
          "childs": null
        }
      ]
    },
    {
      "value": 5,
      "allChildsSelected": true,
      "childs": null,
      "isSelected": true
    }
  ]
}

You render it in a template. You hang a handler on the checkbox, and walk along this tree and check the boxes as you need.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question