JavaScript Articles - Blog - Andy Palmer Personal website of Andy Palmer - A Web Developer based in Exeter, Devon Sat, 15 Dec 2018 14:49:14 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.2 Event Delegation with jQuery /blog/code/event-delegation-with-jquery/ /blog/code/event-delegation-with-jquery/#respond Tue, 11 Oct 2016 14:39:57 +0000 https://andypalmer.me/?p=142 It surprises me how many developers are unaware of – or ignorant of – event delegation within JavaScript and jQuery events. Sometimes when developing a site or application we need to attach an event handler to multiple elements, and those elements may or may not be in the DOM when it’s initially loaded (AJAX loaded content […]

The post Event Delegation with jQuery appeared first on Andy Palmer.

]]>
It surprises me how many developers are unaware of – or ignorant of – event delegation within JavaScript and jQuery events.

Sometimes when developing a site or application we need to attach an event handler to multiple elements, and those elements may or may not be in the DOM when it’s initially loaded (AJAX loaded content for example). Imagine we have a shop type page, showing a list of products which can be filtered down using AJAX calls. When the user selects a filter, we generate the new product list to be shown in JavaScript and then append it to the relevant parent DOM element. Now imagine we have a mouseenter event attached to these product elements, which could do anything from cycling through product images, to displaying an overlay with pricing options:

jQuery(function ($) {
    $('.product-list .product').on('mouseenter', function () {
        cycleImages($(this));
    });

    function cycleImages($product) {
        // code to cycle images here
    }
});

All looks good on the initial page load, we hover the cursor over a product element and the mouseenter handler we attached gets called. The problem comes about after the user has selected a filter and our new product elements are inserted into the page. Because we attached the mouseenter handler when the DOM was ready, it didn’t get attached to the new product elements that were just created – hovering over the newly inserted products does nothing.

The Wrong Way

Some developers try to work around this by re-attaching the listeners after the new product elements are inserted:

jQuery(function ($) {
    function attachHandlers() {
        $('.product-list .product')
            .off('mouseenter')
            .on('mouseenter', function () {
                cycleImages($(this));
            });
    }

    // Attach handlers when the DOM is ready
    attachHandlers();

    function filterProducts() {
        $.getJSON('/', $('.filter-form').serialize(), function (json) {
            // Code to insert new DOM elements goes here then we call the attachHandlers function again
            attachHandlers();
        });
    }
});

This is bad. We’re repeatedly detaching/attaching the same event listener every time the user selects a filter. Also by using .off() we’re removing every single mouseenter listener from the matched elements, whether it was added by us or a third party library (this can be solved by using event namespaces, but it’s still the wrong way to do it)

Better, But Still Wrong

How about if we add a class to elements which already have the event listener attached, then we can use jQuery’s .not() function or :not selector to filter down elements to those which do not have the listener attached:

function attachHandlers() {
    $('.product-list .product:not(.attached)')
        .addClass('attached')
        .on('mouseenter', function () {
            cycleImages($(this));
        });
}

This is a bit better than what we had above, we’re only adding the handler once per element but we’ve added extra complexity by using the :not selector and the use of an arbitrary class. We also need to consider the amount of product elements there are likely to be on the page. What if the user wants to view all products on a single page rather than use pagination? You now have potentially hundreds of unique copies of the same function body, all consuming memory and being monitored by the browser individually.

Event Delegation – The Right Way

When an event is fired on an element in JavaScript it ‘bubbles’ all the way up the DOM tree. This means when we hover over one of our product elements, the mouseenter event is fired for the product element itself, the .product-list parent element, its parent and so on, all the way up to the body element and finally the document element. The target property is passed in to each descendent element’s event and references the original element from which the event started.

We can attach the handler to a single element that’s always in the DOM by passing in a selector string as the second argument to jQuery’s .on() method:

jQuery(function ($) {
    $('.product-list').on('mouseenter', '.product', function () {
        // 'this' references the hovered .product element
        cycleImages($(this));
    });
});

Now whenever a product element is hovered our handler will be called because we’ve attached it to the .product-list parent element. This has allowed us to get rid of the function to re-assign the handler to new elements, get rid of the arbitrary .attached class and .not filter, and best of all we’ve attached the handler to one element rather than potentially hundreds so our overhead and memory usage is lower.

Our code is now cleaner, leaner and faster than before.

The post Event Delegation with jQuery appeared first on Andy Palmer.

]]>
/blog/code/event-delegation-with-jquery/feed/ 0
grunt-sass vs grunt-contrib-sass /blog/code/grunt-sass-vs-grunt-contrib-sass/ /blog/code/grunt-sass-vs-grunt-contrib-sass/#respond Mon, 10 Oct 2016 15:20:05 +0000 https://andypalmer.me/?p=193 If you use grunt-contrib-sass to compile your SCSS code into CSS I recommend taking a look at grunt-sass. It can be used as an drop in replacement for grunt-contrib-sass but with one major difference: speed. grunt-contrib-sassuses a Sass compiler written in Ruby. It works just fine for projects where you don’t have a lot of […]

The post grunt-sass vs grunt-contrib-sass appeared first on Andy Palmer.

]]>
If you use grunt-contrib-sass to compile your SCSS code into CSS I recommend taking a look at grunt-sass. It can be used as an drop in replacement for grunt-contrib-sass but with one major difference: speed.

grunt-contrib-sassuses a Sass compiler written in Ruby. It works just fine for projects where you don’t have a lot of SCSS to process but once you start getting into the hundreds of kilobytes there’s a noticeable slow down in the time it takes to generate your CSS files. grunt-sass gets around this by using libsass which is a compiler written in C++. It’s blazingly fast with large files and data.

To install grunt-sass to your project, simply run the following command:

yarn add grunt-sass

There’s no need to worry about installing the compiler itself as the package will download a pre-compiled binary as one of it’s dependencies.

The post grunt-sass vs grunt-contrib-sass appeared first on Andy Palmer.

]]>
/blog/code/grunt-sass-vs-grunt-contrib-sass/feed/ 0
Using the HTML5 required Attribute for Multiple Elements /blog/code/using-html5-required-attribute-for-multiple-elements/ /blog/code/using-html5-required-attribute-for-multiple-elements/#respond Tue, 04 Oct 2016 17:32:39 +0000 https://andypalmer.me/?p=55 While developing an eCommerce site I was asked by the client if it was possible to add the HTML5 required attribute to the telephone number and mobile number fields. Only one field would actually be required so if a customer entered a telephone number the browser wouldn’t throw an error about the mobile number field […]

The post Using the HTML5 required Attribute for Multiple Elements appeared first on Andy Palmer.

]]>
While developing an eCommerce site I was asked by the client if it was possible to add the HTML5 required attribute to the telephone number and mobile number fields. Only one field would actually be required so if a customer entered a telephone number the browser wouldn’t throw an error about the mobile number field being empty and vice versa.

I had a look around online in the usual places; Google, Stack Overflow, MDN et al. to no avail – this isn’t functionality that’s built into HTML5’s form validation so I had to go down the jQuery/JavaScript route instead:

jQuery(function ($) {
    var $inputs = $('input[name=telephone],input[name=mobile]');
    $inputs.on('input', function () {
        // Sets the required property of the other input to false if this input is not empty.
        $inputs.not(this).prop('required', !$(this).val().length);
    });
});

The above code translated into pseudo-English is: WHEN either input fields named ‘telephone’ or ‘mobile’ have their text content edited, IF that field is not empty, make the other field NOT required.

jQuery Plugin

The snippet worked perfectly for my use case but if it’s going to be used more than once it’d make sense to be DRY and make this into a jQuery plugin:

(function ($) {
    $.fn.groupRequired = function () {
        var $inputs = this;

        $inputs.on('input', function () {
            $inputs.not(this).prop('required', !$(this).val().length);
        });
    }
})(jQuery);

Now we can call the jQuery plugin on as many groups of elements as we want:

jQuery(function ($) {
    $('.my-required-group').groupRequired();
    $('input[name=telephone],input[name=mobile]').groupRequired();
    $('form input[type=tel]').groupRequired();
});

I have released a plugin encapsulating the above functionality and included a few more features, you can get it from the project’s Github page or install it with yarn:

yarn add jquery-grouprequired

The post Using the HTML5 required Attribute for Multiple Elements appeared first on Andy Palmer.

]]>
/blog/code/using-html5-required-attribute-for-multiple-elements/feed/ 0
Most Visible JavaScript Module and jQuery Plugin Released /blog/code/most-visible-javascript-module-and-jquery-plugin-released/ /blog/code/most-visible-javascript-module-and-jquery-plugin-released/#respond Mon, 26 Sep 2016 13:40:10 +0000 https://andypalmer.me/?p=95 Today I released a JavaScript module/jQuery plugin I’ve been working on over the last few days. Most Visible is used to reduce a set of elements to the one which is most visible within the viewport.

The post Most Visible JavaScript Module and jQuery Plugin Released appeared first on Andy Palmer.

]]>
Today I released a JavaScript module/jQuery plugin I’ve been working on over the last few days. Most Visible is used to reduce a set of elements to the one which is most visible within the viewport. The simplest way to use it is as a jQuery plugin like so:

$(function() {
    $('.elements').mostVisible().addClass('active');
});

Package Managers

It’s available for installation via yarn or NPM:

yarn add most-visible
npm install most-visible --save

CDN

If you’re not a package manager type of person (you should be) you can include the file directly into your page courtesy of unpkg.com:

<script src="https://unpkg.com/most-visible@latest/dist/most-visible.js"></script>
<script src="https://unpkg.com/most-visible@latest/dist/most-visible.min.js"></script>

Direct Download

If you’re really old fashioned and don’t want to use a package manager or a CDN there are also direct download links:

most-visible.js (3.8KB, 1.4KB gzipped)

most-visible.min.js (1.4KB, 663B gzipped)

For docs, demos and information on the different ways you can use the module check out the project’s GitHub page.

The post Most Visible JavaScript Module and jQuery Plugin Released appeared first on Andy Palmer.

]]>
/blog/code/most-visible-javascript-module-and-jquery-plugin-released/feed/ 0