Étendre les fenêtres modales de Bootstrap
Par Darkaurora

Le , par Darkaurora, Membre confirmé
Cela fait déjà longtemps que j'utilise ce composant proposé par le framework Bootstrap mais malheureusement il lui manquait une fonctionnalité intéressante: stackable.

Dans ces dernières versions la modal de BS permet justement de s'empiler mais malheureusement il y a un effet, que je trouve désagréable, c'est qu'a chaque ouverture d'une fenêtre un élément "backdrop" viens s'ajouter au DOM, rendant l'arrière plan de plus en plus opaque au fur et à mesure de l'ouverture de fenêtre.

Je me suis donc penché sur les solutions permettant de minimiser l'effet et deux solutions s'offrent à nous:
  • Soit nous définissons un code "rustine" qui permettra de corriger le problème.
  • Soit nous agissons directement au niveau du plugin.


La première solution est normalement celle que tout le monde devrait choisir, en effet les modals BS offrent une API permettant d'agir sur les éléments à 4 étapes de l'initialisation et de la destruction des fenêtres. De plus agir de cette manière nous assure l'intégrité du framework BS notamment lorsque décision est prise de le mettre à jour.

Malheureusement cette solution souffre d'un problème de "timing" dans notre cas d'étude, en agissant de cette manière et avec le même code que je propose ci dessous, vous constaterez des effets visuels indésirables.

C'est pourquoi j'ai opté pour la deuxième solution, un peu plus contraignante mais très fonctionnelle:

Après avoir lut le code du plugin deux fonctions nous intéressent:
Modal.prototype.show et Modal.prototype.backdrop.

Fonction Show:
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Modal.prototype.show = function (_relatedTarget) { 
    var that = this 
    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 
  
    this.$element.trigger(e) 
  
    if (this.isShown || e.isDefaultPrevented()) return 
  
    this.isShown = true 
  
    this.checkScrollbar() 
    this.setScrollbar() 
    this.$body 
      .addClass('modal-open') 
// Nous intervenons ici afin d'enregistrer un nombre de modals ouvertes 
      .data('modalIndice', this.$body.data('modalIndice') ? 
        this.$body.data('modalIndice') + 1 : 
        1 
      ) 
  
    this.escape() 
    this.resize() 
  
    this.$element 
      .on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 
// Grace à la donnée modalIndice nous pouvons attribuer un numéro ordonné à la modal actuelle. 
      .data('modalOrder', this.$body.data('modalIndice')) 
  
    this.$dialog.on('mousedown.dismiss.bs.modal', function () { 
      that.$element.one('mouseup.dismiss.bs.modal', function (e) { 
        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true 
      }) 
    }) 
  
    this.backdrop(function () { 
      var transition = $.support.transition && that.$element.hasClass('fade') 
  
      if (!that.$element.parent().length) { 
        that.$element.appendTo(that.$body) // don't move modals dom position 
      } 
  
      that.$element 
        .show() 
        .scrollTop(0) 
  
      that.adjustDialog() 
  
      if (transition) { 
        that.$element[0].offsetWidth // force reflow 
      } 
  
      that.$element 
        .addClass('in') 
        .attr('aria-hidden', false) 
  
      that.enforceFocus() 
  
      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 
  
      transition ? 
        that.$dialog // wait for modal to slide in 
          .one('bsTransitionEnd', function () { 
            that.$element.trigger('focus').trigger(e) 
          }) 
          .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 
        that.$element.trigger('focus').trigger(e) 
    }) 
  }

Fonction Backdrop:
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Modal.prototype.backdrop = function (callback) { 
    var that = this 
    var animate = this.$element.hasClass('fade') ? 'fade' : '' 
    var lastVisibleBackdrop = this.$body.find('.modal-backdrop:last:visible') // Nous récupérons le dernier arrière plan visible 
    var lastHiddenBackdrop = this.$body.find('.modal-backdrop:hidden:last') // Nous récupérons le dernier arrière plan caché 
  
    if (this.isShown && this.options.backdrop) { 
      var doAnimate = $.support.transition && animate 
// On cache tous les autres backdrop à part celui actif puis on réduis le z-index des autres modals pour les faire passer sous le backdrop actif 
      if (lastVisibleBackdrop.length) { 
        lastVisibleBackdrop.hide(); 
        this.$body.find('.modal:visible').each(function() { 
          if ($(this)[0] !== that.$element[0]) { 
            $(this).css('z-index', 1030) 
          } 
        }) 
      } 
  
      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') 
          .appendTo(this.$body) 
  
      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 
        if (this.ignoreBackdropClick) { 
          this.ignoreBackdropClick = false 
          return 
        } 
        if (e.target !== e.currentTarget) return 
        this.options.backdrop == 'static' 
          ? this.$element[0].focus() 
          : this.hide() 
      }, this)) 
  
      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 
  
      this.$backdrop.addClass('in') 
  
      if (!callback) return 
  
      doAnimate ? 
        this.$backdrop 
          .one('bsTransitionEnd', callback) 
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 
        callback() 
  
    } else if (!this.isShown && this.$backdrop) { 
      this.$backdrop.removeClass('in') 
// On affiche le dernier backdrop caché puis on augmente le z-index de la modal ayant l'indice juste au dessous de celle qui est active et en cours de destruction 
      if (lastHiddenBackdrop.length) { 
        lastHiddenBackdrop.show(); 
        this.$body.find('.modal:visible').each(function() { 
          if ($(this).data('modalOrder') === that.$element.data('modalOrder') - 1) { 
            $(this).css('z-index', 1050) 
          } 
        }) 
      } 
  
      var callbackRemove = function () { 
        that.removeBackdrop() 
        callback && callback() 
      } 
      $.support.transition && this.$element.hasClass('fade') ? 
        this.$backdrop 
          .one('bsTransitionEnd', callbackRemove) 
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 
        callbackRemove() 
  
    } else if (callback) { 
      callback() 
    } 
  }

Et le tour est joué . Certes il reste encore pas mal de travail afin de fournir un plugin plus fournis que l'original et ainsi s'en détaché, mais l'idée est là et elle n'est pas bien compliqué.

Avis, remarques et optimisations sont les bienvenus

Le code est disponible ici


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :
Responsable bénévole de la rubrique JavaScript : Xavier Lecomte -