loading...

Bootstrap 4 – Customizing plugins

While there are many plugins to customize, we will choose two that we have already come across in the previous chapters:

  • Bootstrap’s jQuery alert plugin
  • Bootstrap’s jQuery carousel plugin

Customizing Bootstrap’s jQuery alert plugin

The alert plugin, as we have seen, is exceedingly simple. The alert is rendered on the page, displaying a message, and the only functionality it has is the ability to close and disappear when a user clicks on a certain element.

To demonstrate how to customize or extend a plugin, in this case, alert we will keep it very simple. We will add an extra bit of functionality—when a user clicks on a certain element, the alert will minimize. Similarly, we also want to give the user the ability to expand the alert when it is in its minimized state. To do this, we need to extend both the JavaScript and the styling of alert.

Before we get to the coding of the plugin functionality and styling, let’s put together the markup for an alert on the MyPhoto page.

The markup

As an example use case, let’s display an alert informing the user of a special offer. We will add our alert above the unsupported browser alert:

    <div class="alert alert-info" style="position: fixed; margin-top:
    4em; width: 90%;margin-left: 4em;">
        <a href="#" class="close" data-dismiss="alert" 
        aria-label="close">&times;</a>
        <strong><i ></i> Special Offer -  
        </strong>
        2 FOR 1 PRINTS TODAY ONLY WITH PROMO CODE <span style="font-
        style: italic">BOOTSTRAP</span>
    </div>

We’re using Bootstrap’s contextual information class, alert-info, to style the alert box, and we’re following the same pattern as the unsupported browser alert. The special offer alert has inline styles applied, and the unsupported browser alert has styles linked to its alert-myphoto class. Before we go any further, let’s extract that out into a single class in myphoto.css for reusability and maintainability. Remove the .alert-myphoto rules and add the following:

    .alert-position {
        position: fixed;
        margin-top: 4em;
        width: 50%;
        margin-left: 25%;
        z-index: 10;
    }
    #unsupported-browser-alert {
        display:none;
    }

We’ve made some slight changes here. The alert will now have a hard-set width of 50% of the viewport and will be rendered 25% from the left. To ensure that the alert is always rendered above any other content on the page, we set z-index to 10. As long as no other elements have a higher z-index, the alert will always be visible. Now, we remove the inline styles on the alert elements and add the alert-position class. We extend the class slightly for elements with the unsupported-browser-alert ID to ensure that it isn’t displayed. Update the alert elements with the alert-position class, and add to the unsupported browser alert root:

    <div class="alert alert-info alert-position">
        ...
    </div>
    <div                  
     >
        ...
    </div>

Take a look at the following screenshot for our new alert:

Figure 6.1: A modified alert used to display special promotions; unlike the default alert, this alert has its width hard-set to 50% of the viewport and will be rendered 25% from the left (example01.html)

Now that we have our special offers alert, we can add our minimize and expand elements. We want these elements to function and display similar to the close element, so we can use the close element as a template. Observe the following code:

    <a href="#" class="close" data-dismiss="alert" 
    aria-label="close">&times;</a>
    <a href="#" class="close minimize" data-minimize="alert" 
    aria-label="minimize">_</a>
    <a href="#"  data-expand="alert"
    aria-label="expand">+</a>

We have replicated the close element twice. We have added a minimize and an expand class, while retaining the close element, as we want to inherit everything the close class includes. We have added new data attributes—instead of data-dismiss, we have data-minimize and data-expand. These are the data attributes that the plugin will listen to. We then updated the aria-label with the appropriate names, and applied appropriate content inside the element—an underscore ( ) to indicate minimization and a plus ( +) to indicate expansion (refer to Figure 6.2). Take a look at the following screenshot:

Figure 6.2: Our custom promotion alert with the expand and minimize functionalities (example01.html)

Besides the close button, we now have the expand and minimize buttons. We don’t want to show the expand button when the alert is already expanded, and we don’t want to show the minimize button when the alert is already minimized. As the alert is expanded by default, we’ll add a d-none class to the expand element. Recall that d-none hides a given element for all viewports; d-none is the equivalent of hide in Bootstrap 3. Observe the following code:

    <a href="#" class="close" data-dismiss="alert" 
    aria-label="close">&times;</a>
    <a href="#" class="close minimize" data-minimize="alert" 
    aria-label="minimize">_</a>
    <a href="#"  data-expand="alert" 
    aria-label="expand">+</a>

Take a look at figure 6.3—the expand control will now be hidden:

Figure 6.3: Our custom promotion alert with the expand element hidden (example02.html)

Now that the alert has the desired appearance, we are ready to customize the styling of the alert plugin.

Extending alert’s style sheets

As we mentioned earlier, it is bad practice to modify Bootstrap’s Sass files directly, due to maintenance issues. Instead, we will create our own style sheet—styles/alerts.css.

Before we create any new classes, we should extract any alert-related CSS from myphoto.css into this new style sheet in order to improve code maintainability. The only classes we have so far are alert-danger and alert-position. Place them into our new alert-specific style sheet, and include the stylesheet in our HTML. Ensure that you include it after bootstrap.min.css and myphoto.css to ensure that the style rules in alert.css take priority. Observe the following code:

    <link rel="stylesheet" href="node_modules/bootstrap/dist/css    
    /bootstrap.min.css" />
    <link rel="stylesheet" href="styles/myphoto.css" />
    <link rel="stylesheet" href="styles/alert.css" />

To create the ability to minimize and expand an alert, we actually do not need many style rules at all. In fact, we will use just one new class—alert-minimize. When a user clicks on the minimize button, the alert-minimize class will be applied to the root alert element. To expand it, the alert-minimize class will simply be removed.

Update alert.css with the following rules:

    .alert-minimize {
        width: 100px;
    }
    .alert-minimize * {
        display: none;
    }
    .alert-minimize.close {
        display: block;
    }

The alert-minimize class will force a 100px element width. All descendants of the alert-minimize class will be given the display value of none so that they do not appear on screen. To ensure that the functional buttons are still visible, any element with the close class (remember that we retained the close class for all our functional buttons in the alert) will be given the display value of block. Let’s manually apply alert-minimize to our alert to see how it renders. Take a look at this screenshot:

Figure 6.4: Our minimized custom promotion alert

Next up, let’s remove the alert-minimize class and extend the alert plugin JavaScript to apply and remove the class dynamically.

Extending alert’s functionality with JavaScript

As with extending the styles, to extend the JavaScript, we can modify Bootstrap’s alert.js directly, but again, that is a bad idea in terms of maintainability. Instead, we will create a js directory in our project, and a file called alert.js. Include this file in your HTML, after bootstrap.min.js:

    <script 
    src="node_modules/bootstrap/dist/js/bootstrap.min.js">          
    </script>
    <script src="js/alert.js"></script>

The first thing we will do is to create an immediately invoked function and add the function to the jQuery object:

    +function ($) {
        'use strict';
        var Alert = $.fn.alert.Constructor;
    }(jQuery);

The function assigns an Alert variable to the alert plugins prototype—which as we saw earlier, is made available through the Constructor property.

With this reference to the Alert prototype, we can add our own functions to the prototype to handle minimizing and expanding an alert. Taking the close function we studied earlier, and with a few changes, let’s create a function to minimize the alert:

    Alert.prototype.minimize = function (e) {
        var $this = $(this)
        var selector = $this.attr('data-minimize')
        if (!selector) {
            selector = $this.attr('href')
            selector = selector && selector.replace(/.*(?=#[^\s]*$)/, 
            '')
            // strip for ie7
        }
        $this.addClass('d-none')
        $this.siblings('.expand').removeClass('d-none')
        var $parent = $(selector)
        if (e) e.preventDefault()
        if (!$parent.length) {
            $parent = $this.closest('.alert')
        }
        $parent.trigger(e = $.Event('minimize.bs.alert'))
        if (e.isDefaultPrevented()) return
        $parent.addClass('alert-minimize')
    }

The function is quite similar to the close function, so we will highlight the important differences. Line 15 and 16 handle hiding the minimize button and showing the expand button, adding the d-none class to the element that triggered the event, and removing the d-none class from any sibling element with the expand class. Line 30 adds the alert-minimize class, which handles the shrinking of the Alert element, to the parent of the element that triggered the event. Essentially, the minimize function will shrink the alert, hide the minimize button, and show the expand button. Let’s hook up a listener to this function.

We do this in the same way as the Bootstrap alert plugin links the data—dismiss the click event to the close function, adding the following to alert.js below the minimize function definition:

    $(document).on('click.bs.alert.data-api', '[data-                     
    minimize="alert"]', Alert.prototype.minimize)

Now, an element with the data-minimize attribute with an "alert" value will call the Alert.prototype.minimize function on a click event. The minimize element in our special offers alert has this attribute. Open up MyPhoto and click on the minimize button. Take a look at the screenshot in figure 6.5:

Figure 6.5: Our minimized custom promotion alert—note the expand and close buttons (example02.html)

That’s excellent. Our minimize button and functionality are wired up correctly to shrink our special offers alert and replace the minimize button with an expand button when clicked on.

The last thing we need to do now is ensure that the user can expand the alert when they click on the expand button. To do this, we follow the same steps as we did for the minimize functionality. Let’s add an expand function to the Alert prototype:

    Alert.prototype.expand = function (e) {
        var $this = $(this)
        var selector = $this.attr('data-expand')
        if (!selector) {
          selector = $this.attr('href')
          selector = selector && selector.replace(/.*(?=#[^\s]*$)/, 
            '')
            // strip for ie7
        }
        $this.addClass('d-none')
        $this.siblings('.minimize').removeClass('d-none')
        var $parent = $(selector)
        if (e) e.preventDefault()
        if (!$parent.length) {
          $parent = $this.closest('.alert')
        }
        $parent.trigger(e = $.Event('expand.bs.alert'))
        if (e.isDefaultPrevented()) return
        $parent.removeClass('alert-minimize')
    }

The differences between the expand and minimize functions are very small, so small that it probably makes sense for them to be encapsulated into one function. However, for the sake of simplicity, we will keep the two functions separate. Essentially, the actions of minimize are reversed. The d-none class is again applied to the element triggering the event, the d-none class is removed from any sibling with the minimize class, and the alert-minimize class is removed from the parent element. This is simple and effective. Now, we just need to hook up a click event on an element with the data-expand attribute set to alert to the expand method. Observe the following code:

    $(document).on('click.bs.alert.data-api', '[data-expand="alert"]', 
    Alert.prototype.expand)

That’s it. With our extension to the alert plugin, when a user clicks on expand in the minimized state, the alert reverts to its initial expanded state, and the expand button is replaced by the minimize button. Our users now have the ability to reduce the screen real estate our alert covers but are still able to retrieve the information from the alert at a later stage if needed.

While these alert customizations are relatively simple, they do provide a strong example of how to extend a plugin’s functionality and teach principles that can be applied to more complex extensions.

In summary, our complete code is as follows:

+ function($) {
    'use strict';
    var Alert = $.fn.alert.Constructor;
    Alert.prototype.minimize = function(e) {
        var $this = $(this)
        var selector = $this.attr('data-minimize')
        if (!selector) {
            selector = $this.attr('href')
            selector = selector && selector.replace(/.*(?=#[^\s]*$)/, 
            '') // strip for ie7
        }
        $this.addClass('d-none')
        $this.siblings('.expand').removeClass('d-none')
        var $parent = $(selector)
        if (e) e.preventDefault()
        if (!$parent.length) {
            $parent = $this.closest('.alert')
        }
        $parent.trigger(e = $.Event('minimize.bs.alert'))
        if (e.isDefaultPrevented()) return $parent.addClass('alert-
        minimize')
    }
    Alert.prototype.expand = function(e) {
        var $this = $(this)
        var selector = $this.attr('data-expand')
        if (!selector) {
            selector = $this.attr('href')
            selector = selector && selector.replace(/.*(?=#[^\s]*$)/, 
            '') // strip for ie7
        }
        $this.addClass('d-none')
        $this.siblings('.minimize').removeClass('d-none')
        var $parent = $(selector)
        if (e) e.preventDefault()
        if (!$parent.length) {
            $parent = $this.closest('.alert')
        }
        $parent.trigger(e = $.Event('expand.bs.alert'))
        if (e.isDefaultPrevented()) return $parent.removeClass('alert-
        minimize')
    }
    $(document).on('click.bs.alert.data-api', '[data-
    minimize="alert"]', Alert.prototype.minimize)
    $(document).on('click.bs.alert.data-api', '[data-expand="alert"]', 
    Alert.prototype.expand)
}(jQuery);

The accompanying markup is this:

<div class="alert alert-info alert-position">
     <a href="#" class="close" data-dismiss="alert" aria-        
      label="close">&times;</a>
     <strong><i ></i> Special Offer -</strong>
     2 FOR 1 PRINTS TODAY ONLY WITH PROMO CODE 
     <span style="font-style: italic">BOOTSTRAP</span>
</div>

Customizing Bootstrap’s jQuery carousel plugin

MyPhoto uses Bootstrap’s carousel as a gallery to display sample images. The carousel is a very neat component, allowing the user to cycle through images. We will add some new functionality to the carousel plugin. Specifically, we will implement the ability to surface an image in a modal window when there is a click event on a carousel slide. We will be using Bootstrap’s modal plugin to surface the modal, and we will dynamically pass the image source and carousel caption from the markup of the slide to the modal. A practical application of this might be the need to surface a larger version of the image, or to display additional metadata or controls that would not normally fit on the carousel slide.

We will begin customizing the carousel plugin by first writing the markup for it.

The markup

The only thing we really need to do in the markup is create a modal element and reference that modal in the carousel’s slide elements so as to link them together. First, let’s create the modal. We only want a bare-bones modal here—an image, a close button, and a title. We have already seen how to create modals before, so let’s just add the markup we need to our HTML. We will add it just above the carousel element:

 <div class="modal fade carousel-modal"  
  tabindex="-1" role="dialog">
    <div class="modal-dialog">
       <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"></h4>
           </div>
           <div >
               <img>
           </div>
       </div>
    </div>
 </div>

We have created a very simple modal here. We added a carousel-modal class to the parent element for any styles we may need to apply, and we attributed carousel-modal as the id for the modal. We have an empty modal-title element, which we will populate dynamically. Most interestingly, we have an empty img tag in the modal-body element. It isn’t often that you see an img tag with no src attribute, but our extension will create this attribute dynamically. We could, of course, have created a different modal for each image, but that wouldn’t scale, and it just wouldn’t be interesting!

That’s our simple modal window declared. Great. Now we just need to reference the modal in our slides. On each img element within the carousel, simply add a data-modal-picture attribute with the #carousel-modal value. Observe the following code:

    <div class="carousel-inner" role="listbox">
   <div style="height: 400px" class="carousel-item active">
      <img class="d-block img-fluid" src="images/brazil.png" 
         data-modal-picture="#carousel-modal">
      <div class="carousel-caption">
         Brazil
      </div>
   </div>
   <div style="height: 400px" class="carousel-item">
      <img class="d-block img-fluid" src="images/datsun.png" 
         data-modal-picture="#carousel-modal">
      <div class="carousel-caption">
         Datsun 260Z
      </div>
   </div>
   <div style="height: 400px" class="carousel-item">
      <img class="d-block img-fluid" src="images/skydive.png" 
         data-modal-picture="#carousel-modal">
      <div >
         Skydive
      </div>
   </div>
</div>

The data-modal-picture attribute is the data-attribute we will hook our on-click listener to in the very same way that alert hooked into data-dismiss. Let’s set up our carousel plugin extension and wire all this together.

Extending carousel’s functionality with JavaScript

Just like with our alert extension, we will create a new JS file for the carousel extension. Create a js/carousel.js file and include the file on the MyPhoto page:

    <script src="js/carousel.js"></script>

Again, we want to create an IIFE and assign the carousel constructor to a variable we can work with. Observe the following code:

    +function ($) {
        'use strict';
        var Carousel = $.fn.carousel.Constructor;
    }(jQuery);

From our markup, we know what data-attribute we want to listen to—data-modal-picture. Observe the following code:

    +function ($) {
        'use strict';
        var Carousel = $.fn.carousel.Constructor;
        $(document).on('click.bs.carousel.data-api', '[data-modal-
        picture]', Carousel.prototype.zoom)
    }(jQuery);

Note that, unlike with alert.js, we are not referencing any particular value for the data-modal-picture attribute. We will be using the attribute value to identify which modal to use, so of course we want the plugin to be flexible enough to handle more than one modal ID. We have also defined which function we want to call when the event is triggered—Carousel.protoype.zoom. Let’s create that function:

    Carousel.prototype.zoom = function () {
        var $this = $(this)
        var $src = $this.attr('src')
        var $title = $this.next('.carousel-caption').text()
        var $modal = $this.attr('data-modal-picture')
        var $modalElement = $.find($modal)
        $($modalElement).find('.modal-body').find('img').attr('src', 
        $src)
        $($modalElement).find('.modal-title').text($title)
        $($modal).modal('show')
    }

First, as we did earlier, we create a jQuery wrapper of the element that triggers the event. Next, we use the attr method to find the value of the element’s src attribute. We then use the next method to find the next carousel-caption element and assign the inner text of that element to $title. We need these to dynamically update the blank modal.

Next, we grab the value of the data-modal-picture element, which we then use as a reference to find the modal we want to use to render our picture. We use the find method to first find the modal-body of this element and then the nested image element. We then create an src attribute on this element, passing in a reference to the source of the slide’s image element. Similarly, we inject the caption of the slide into the modal’s title element.

Finally, we use the modal API to show the modal. Take a look at the screenshot in figure 6.6:

Figure 6.6: Our modal showing a clicked slide image (example03.html); note the CSS bug due to which the image is overflowing the modal

The modal is now surfacing. The dynamic title is working well, too. The dynamic image is getting applied and loaded. That’s perfect, except that the entire thing looks terrible. However, it’s nothing a bit of CSS can’t fix.

Extending carousel’s style sheets

Thanks to our forward-thinking, we already have the carousel-modal class applied to the modal parent element. We just need to set some rules.

As this modal is directly related to our carousel plugin extension, we will create a CSS file explicitly for handling styling born out of our extension. Create styles/carousel.css and include the file in our page:

    <link rel="stylesheet" href="node_modules/font-
    awesome/css/font-awesome.min.css" />
    <link rel="stylesheet" href="styles/alert.css" />
    <link rel="stylesheet" href="styles/carousel.css" />

There are two things wrong that we need to address. First, the modal is too narrow. We want it to be somewhat larger. To achieve this, we need to apply the modal-lg class to the modal dialog:

    <div >
        ...
    </div>
Figure 6.7: Our modal showing an enlarged version of a slide image with applied changes (example03.html); note how the image is still overflowing the modal

Now, we just need to ensure that the image doesn’t breach the borders of the modal. The fix here is simple. We will just give any img element that is a descendant of a carousel-modal element a width of 100% so that it will only take up the width explicitly available to it. Observe the following code:

    .carousel-modal img {
        width: 100%;
    }

Take a look at the screenshot in figure 6.8:

Figure 6.8: Our modal showing an enlarged version of a slide image, with the image fitting the modal (example03.html)

This is much better. Our customization is complete. We have surfaced a modal on a click event from the carousel, passed data from the carousel component into the modal component, and rendered the image successfully. Very neat.

Now that we have successfully customized two of Bootstrap’s jQuery plugins, let’s build a plugin from scratch.

Comments are closed.

loading...