Skip to content
Gary Storey
TwitterGithub

Improving an Accordion script - Part 3

JavaScript, jQuery2 min read

In this final post on improving a basic accordion script, we will be taking our final code from part two, and updating it to a be a jquery plugin. You can check out part one if you want to start at the beginning to see how we got here.

So, I tend to start with the same base jQuery template when I create a new plugin. That way my coding is more consistent across plugins. If you would like a copy of the boilerplate code, you can get it from this gist. Here it is:

1( function( $, document, window, undefined ) {
2 'use strict';
3
4 var Constructor = function( elm, options, id ) {
5 this.el = elm;
6 this.$el = $(elm);
7 this.init( options );
8 };
9
10 Constructor.prototype = {
11 init: function( options) {
12 this.options = $.extend( true, {}, $.fn.pluginName.defaults, options );
13 },
14 destroy : function ( ) {}
15 };
16
17 jQuery.fn.pluginName = function( options ) {
18 switch ( typeof options ) {
19 case 'number': //if it takes a number if not remove
20 Constructor = $(this).data( 'Constructor' );
21 if ( Constructor !== null ) {
22 //do something here
23 }
24 break;
25 case 'string':
26 Constructor = $(this).data( 'Constructor' );
27 if ( Constructor !== null ) {
28 switch( options ) {
29 case 'init':
30 Constructor.init();
31 break;
32 case 'destroy':
33 Constructor.destroy();
34 break;
35 }
36 }
37 break;
38 default:
39 return this.each( function () {
40 new Constructor( $( this ), options );
41 });
42 }
43 };
44 jQuery.fn.pluginName.defaults = {
45 option1 : '',
46 option2 : ''
47 };
48
49 }(jQuery, document, window));

This sets up a basic jquery plugin. I simply do a Find/Replace on the word "Constructor" and replace that with the constructor function name and replace "pluginName" with the name of the plugin. And we are ready to go.

Now, let's rip apart our final code from part two and put it into the boilerplate code. I will call the constructor function "Accordion" and the plugin "accordion" and replace those values in the code above. We can take our settings object and place that in our plugin code defaults so that they look like this:

1jQuery.fn.accordion.defaults = {
2 classes : { // CSS class names used by the script
3 item : 'accordion-item',
4 header : 'accordion-header',
5 content : 'accordion-content',
6 selected : 'accordion-expanded',
7 expandAll : 'accordion-expand-all',
8 collapseAll : 'accordion-collapse-all'
9 },
10 events : 'click.accordion dblclick.accordion touchend.accordion'
11};

Note: I removed the "selector" option and the "root" class option. These will be handled by the selector that the plugin is called against.

Now lets copy over our other functions into the plugin and we are pretty close to done. We can remove the setSelector() function since, again, that is handled by the plugin. The next function is the addEvents() function.

1addEvents : function () {
2 var acc = accordion.settings;
3 acc.selector.on( acc.events, checkEvents( evt ) );
4}

We will need to point to the plugin element for the selector and use our new options object instead of the settings object we had before.

1addEvents : function () {
2 this.$el.on( this.options.events, this._.checkEvents( evt ) );
3}

Next up the checkEvents() function:

1checkEvents : function ( evt ) {
2 var sel = accordion.settings.selector,
3 cls = accordion.settings.classes,
4 $this = $(evt.target), $p = $this.parent();
5
6 if ($this.hasClass('.' + cls.expandAll)) {
7 sel.find('.' + cls.expanded).removeClass('.' + cls.expanded);
8 }
9 if ($this.hasClass('.' + cls.collapseAll)) {
10 sel.find('.' + cls.collapsed).removeClass('.' + cls.collapsed);
11 }
12 if ($this.hasClass('.' + cls.header)) {
13 $p.toggleClass('.' + cls.expanded);
14 }
15}

OK. Again we have to change up the code to point to our new options object and selector. Otherwise the code stays the same.

1checkEvents : function ( evt ) {
2 var sel = this.$el,
3 cls = this.options.classes,
4 $this = $(evt.target), $p = $this.parent();
5
6 if ($this.hasClass('.' + cls.expandAll)) {
7 sel.find('.' + cls.expanded).removeClass('.' + cls.expanded);
8 }
9 if ($this.hasClass('.' + cls.collapseAll)) {
10 sel.find('.' + cls.collapsed).removeClass('.' + cls.collapsed);
11 }
12 if ($this.hasClass('.' + cls.header)) {
13 $p.toggleClass('.' + cls.expanded);
14 }
15}

And here is the updated removeEvents() function:

1removeEvents : function() {
2 this.$el.off( this.options.events, this._.checkEvents( evt ) );
3}

We now have a working jQuery plugin just by changing a few variables and copy/pasting our previous code. This is why I said it would be easier if we made it an object first.

Also, I will keep the plugin constructor call to accept only a standard options object or a string. The string can be either "init" or "destroy" to invoke those functions and that's it.

Here is a look at our final code:

1(function($, document, window, undefined) {
2 'use strict';
3
4 var Accordion = function( elm, options, id ) {
5 this.el = elm;
6 this.$el = $(elm);
7 this.init( options );
8 };
9
10 Accordion.prototype = {
11 _ : { // private functions
12 addEvents : function () {
13 this.$el.on( this.options.events, this._.checkEvents( evt ) );
14 },
15
16 checkEvents : function ( evt ) {
17 var sel = this.$el,
18 cls = this.options.classes,
19 $this = $(evt.target), $p = $this.parent();
20
21 if ($this.hasClass('.' + cls.expandAll)) {
22 sel.find('.' + cls.expanded).removeClass('.' + cls.expanded);
23 }
24 if ($this.hasClass('.' + cls.collapseAll)) {
25 sel.find('.' + cls.collapsed).removeClass('.' + cls.collapsed);
26 }
27 if ($this.hasClass('.' + cls.header)) {
28 $p.toggleClass('.' + cls.expanded);
29 }
30 },
31
32 removeEvents : function() {
33 this.$el.off( this.options.events, this._.checkEvents( evt ) );
34 }
35 }, // end private methods
36
37 init : function() {
38 this.options = $.extend( true, {}, $.fn.accordion.defaults, options );
39 this._.addEvents();
40 },
41
42 destroy : function() {
43 this._.removeEvents();
44 }
45};
46
47jQuery.fn.pluginName = function( options ) {
48 switch ( typeof options ) {
49 case 'string':
50 Accordion = $(this).data( 'Accordion' );
51 if ( Accordion !== null ) {
52 switch( options ) {
53 case 'init':
54 Accordion.init();
55 break;
56 case 'destroy':
57 Accordion.destroy();
58 break;
59 }
60 }
61 break;
62 default:
63 return this.each( function () {
64 new Accordion( $( this ), options );
65 });
66 }
67};
68
69jQuery.fn.pluginName.defaults = {
70 classes : { // CSS class names used by the script
71 item : 'accordion-item',
72 header : 'accordion-header',
73 content : 'accordion-content',
74 selected : 'accordion-expanded',
75 expandAll : 'accordion-expand-all',
76 collapseAll : 'accordion-collapse-all'
77 },
78 events : 'click.accordion dblclick.accordion touchend.accordion'
79};
80
81}(jQuery, document, window));

And that's it! We have a working jQuery accordion plugin. We have come a long way from the starting code but we now have a very flexible plugin.

You can fork it or download it from my GitHub repository.

'Til next time!

-G

© 2023 by Gary Storey. All rights reserved.