1

I'm using Vue.js 2.1.10 and Bootstrap 3.3.7 to show a modal that opens another modal dialog. Each modal dialog is contained in a distinct component. Inside the 1st component, there is a reference to the 2nd component (select-travler).

According to the Bootsrap documentation, I have to set the focus by listening to the event shown.bs.modal. This works great to set the focus on an input control contained in the 1st modal. Problem: this way doesn't work when the modal is above another modal.

The 1st modal component looks like this:

<template>
    <div ref="tripEdit" class="modal fade" role="dialog">
        <!-- Inbeded component -->
        <select-travler ref="selectTravler"></select-travler>
        <!-- /Inbeded component -->

        <div class="modal-lg modal-dialog">
            <div class="modal-content">
                <div class="modal-body container form-horizontal">

                    <div class="form-group">
                        <label for="travler_name" class="control-label">
                            Travler's name
                        </label>
                        <input id="travler_name" ref="travler_name"
                            v-model="travler_name"/>
                    </div>
                </div>
            </div>
        </div>

    </div>
</template>

<script>
    export default {
        data () {
            return {
                travler_name: null,
            }
        },

        methods: {
            show (operationType) {
                $(this.$refs.tripEdit).modal('show');

                let that = this;
                $(this.$refs.tripEdit).on('shown.bs.modal', function () {
                    $(that.$refs.travler_name).focus();
                });

                if (operationType === 'newTravel') {
                    this.$refs.selectTravler.show();
                }
            },            
        },
    }
</script>

The 2nd component contains a similar layout with the following show method:

show () {
    $(this.$refs.selectTravler).modal('show');

    let that = this;
    $(this.$refs.selectTravler).on('shown.bs.modal', function () {
        $(that.$refs.people_names).focus();
    });
},

When the 2nd modal opens, the focus is still on the 1st modal behind the 2nd modal dialog (I can see the caret blinking in travler_name). How can I set the focus on people_names when the 2nd modal is shown?

Warrio
  • 1,853
  • 4
  • 28
  • 45
  • It looks to me like you are adding new `shown.bs.modal` handlers every time a modal is shown, and more than that, you're adding them *after* the modal is triggered to be shown. You probably want to move the `.on(...)` code into the mounted event. Also, don't forget to *remove* the handler when the modal is destroyed. – Bert Aug 10 '17 at 15:26

3 Answers3

5

I think there are really several issues at play here. First, as I mentioned in the comment above, you are not properly adding and removing the shown.bs.modal event handlers.

Second, because your second modal is nested inside the first modal, the shown.bs.modal event will bubble up to the parent modal and it's handler will fire. Initially I thought stopPropagation would be a good way to handle this, but in the end, I simply de-nested the submodal component in the template.

Here is an example of this behavior actually working.

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()
    }
  },
  mounted() {
    $(this.$refs.modal).on("shown.bs.modal", this.onShown)
  },
  beforeDestroy() {
    $(this.$refs.modal).off("shown.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/vue@2.2.6/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">
            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>

Also, for future readers, I got some useful information about how to construct the template for the modal component used above from this answer. Specifically, unless you manually specify a z-index for the modal, the modal that appears last in HTML will have a higher z-index. The implication being the submodal component needs to come second in the template.

Bert
  • 80,741
  • 17
  • 199
  • 164
  • Great post. Somehow I still didn't make it on my side for the 2nd focus. I implemented your code in mine and added `console.log("parent.")` in the modal `onShown` method and `console.log("submodal.")` in the submodal `onShown` method. After pressing the 1st button, the councole displays: `parent.`. After pressing the 2nd button (Show Sub Modal) the councole displays: `parent. submodal. parent. parent.`. Is this normal? I did write `event.stopPropagation();` exactly as you did. – Warrio Aug 10 '17 at 17:29
  • @Warrio I'm not sure what's going on with your's specifically, but I did notice that when one modal is *nested* inside the other, then `shown.bs.modal` is triggered twice for the parent. I updated the above code to remove the nested submodal, and now the event fires just the once. It also likely removes the need for `stopPropagation`. – Bert Aug 10 '17 at 17:47
  • I can't believe it!! it works :D I definitely owe you a beer or two :D If you one day, you visit Belgrade, send a msg!! Awesome :D – Warrio Aug 10 '17 at 18:10
  • @Warrio Glad to hear it :) – Bert Aug 10 '17 at 18:11
1

I ran into a similar issue. A b-modal forces focus to stay in the modal. You can disable it by adding a no-enforce-focus attribute.

no-enforce-focus Boolean false Disables the enforce focus routine which maintains focus inside the modal

https://bootstrap-vue.org/docs/components/modal

Jose Solorzano
  • 393
  • 4
  • 6
0

This means that the element you're trying to focus is not properly referenced. Trying to console.log(element); the line before focussing people_names. To see if you're getting the right element.

show () {
    $(this.$refs.selectTravler).modal('show');

    let element = this.$refs.people_names;
    $(this.$refs.selectTravler).on('shown.bs.modal', function () {
        $(element).focus();
    });
},

Have you considered v-show to open and close your modals ?