Search code examples
javascriptvue.jscomponentsvuejs2bootstrap-modal

Wrong focus after closing a 2nd modal


I'm using vue.js 2 and bootsrap 3 to open a modal that opens a 2nd modal.

Few days ago, I asked a question on how to set a focus on a control contained in a 2nd modal. I got a great answer that solved the issue.

Problem When opening the 1st modal, the user is able to scroll through it to see its bottom. But after opening and closing the 2nd modal, the focus moves to the page that contains the 1st modal. and when the user scrolls to see the rest of the 1st modal, he scrolls the page behind that 1st modal.

It is very uncomfortable to use especially when the modal is bigger than the screen height. Is there a way to prevent this?

To reproduce this issue, open the answer and click on "Expand snippet"


Solution

  • Here is a modified version of the previous answer that sets the focus back to the original modal after the sub modal is closed.

    The change is here:

    $(this.$refs.submodal.$el).on("hidden.bs.modal", this.onShown)
    

    This is added in the mounted handler. It adds a handler to the hidden.bs.modal event of the sub modal. It also removes the handler when the component is destroyed.

    Additionally, because closing a modal removes the modal-open class that is assigned to the body when a modal opens, the code below adds that class to the body whenever onShown is called so that the scroll is not affected for the parent modal.

    $("body").addClass("modal-open")
    

    Here is a working example.

    console.clear()
    
    Vue.component("sub-modal", {
      template: "#submodal",
      methods: {
        show() {
          $(this.$el).modal("show")
        },
        onShown(event) {
          console.log("submodal onshown")
          this.$refs.input.focus()
        }
      },
      mounted() {
       $(this.$el).on("shown.bs.modal", this.onShown)
      },
      beforeDestroy() {
        $(this.$el).off("shown.bs.modal", this.onShown)
      }
    })
    
    Vue.component("modal", {
      template: "#modal",
      methods: {
        show() {
          $(this.$refs.modal).modal("show")
        },
        showSubModal() {
          this.$refs.submodal.show()
        },
        onShown(event) {
          console.log("parent")
          this.$refs.input.focus()
          // Add the "modal-open" class back to the body in case
          // it was removed by the sub modal
          $("body").addClass("modal-open")
        }
      },
      mounted() {
        $(this.$refs.modal).on("shown.bs.modal", this.onShown)
        $(this.$refs.submodal.$el).on("hidden.bs.modal", this.onShown)
      },
      beforeDestroy() {
        $(this.$refs.modal).off("shown.bs.modal", this.onShown)
        $(this.$refs.submodal.$el).on("hidden.bs.modal", this.onShown)
      }
    })
    
    new Vue({
      el: "#app",
    })
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
    
    
    <script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <div id="app">
      <modal ref="modal"></modal>
      <button @click="$refs.modal.show()" class="btn">Show Modal</button>
    </div>
    
    <template id="submodal">
      <div class="modal fade" tabindex="-1" role="dialog">
        <div class="modal-dialog" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
              <h4 class="modal-title">Modal title</h4>
            </div>
            <div class="modal-body">
              <input ref="input" type="text" class="form-control">
            </div>
            <div class="modal-footer">
              <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
              <button type="button" class="btn btn-primary">Save changes</button>
            </div>
          </div><!-- /.modal-content -->
        </div><!-- /.modal-dialog -->
      </div><!-- /.modal -->
    
    </template>
    
    <template id="modal">
      <div>
        <div ref="modal" class="modal fade" tabindex="-1" role="dialog">
    
          <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title">Modal title</h4>
              </div>
              <div class="modal-body" style="height: 80vh">
                Stuff
                <input ref="input" type="text" class="form-control">
              </div>
              <div class="modal-footer">
                <button @click="showSubModal" type="button" class="btn btn-primary">Show Sub Modal</button>
              </div>
            </div><!-- /.modal-content -->
          </div><!-- /.modal-dialog -->
        </div><!-- /.modal -->
        <sub-modal ref="submodal"></sub-modal>
      </div>
    </template>