2
\$\begingroup\$

I have created a reusable code for jQuery tab functionality for my client site.
Every thing works fine but the only thing I am worried is that the lines code I have written for this small feature is very high. Can any one help me to shrink lines of code or any suggestions.

Here is my script

/**
 * @param link Class name of the tab header
 * @param content Class name of the element
 * @param current should be class name of the element which you want to show in the begin
 * @example _initTab('.tab-link','.tab-content','.first-tab-content-id')
 */
function _initTab(link, content, current) {
    var tabLink = link || '.tab-link';
    var tabContent = content || '.tab-content';
    var elementId = current || null; //Clicked element

    //In the begin hide all the tabs
    $(tabContent).addClass('hide');

    function animateIcon() {
        var iconElement = $(tabLink).find('.icon');
        var addClassName = iconElement.hasClass('sz-icon-arrow-right') ? 'sz-icon-arrow-down' : 'sz-icon-arrow-right';
        var removeClassName = iconElement.hasClass('sz-icon-arrow-right') ? 'sz-icon-arrow-right' : 'sz-icon-arrow-down';
        iconElement.removeClass(removeClassName).addClass(addClassName);
    }

    function showElement(elId) {
        animateIcon();
        $(tabContent).addClass('hide');
        $(elId).removeClass('hide');
    }

    //If any tab-content needs to be opened in the begin
    if (elementId) {
        showElement(elementId);
    }

    //Bind click events for the tab
    $(document).on('click', tabLink, function () {
        elementId = $(this).data('element');
        showElement(elementId);
    });
}

My Html will look something like this

<div class="tab-wrapper">
    <h3 class="tab-link" data-element=".credit-card-sec">
        Credit Card
        <span class="icon sz-icon-arrow-right"></span>
    </h3>
    <div class="tab-content credit-card-sec">
        <div class="credit-card-form">
            Credit card form elements
        </div>
    </div>
    <h3 class="tab-link" data-element=".debit-card-sec">
        Credit Card
        <span class="icon sz-icon-arrow-right"></span>
    </h3>
    <div class="tab-content debit-card-sec">
        <div class="debit-card-form">
            Debit card form elements
        </div>
    </div> 
</div>
\$\endgroup\$
2
\$\begingroup\$

Here a are a few suggestions:

Create a Private Scope & Use Strict

You can use an IIFE to create a private scope for your code. This will create a closure that will help avoid collisions between your code and any other code that might be on the page. Also, you should add the 'use strict'; at the top of the code. This helps prevent some common issues.

(function($){
  'use strict';
  // code here
})(jQuery);

Cache Your Selections

For example, you refer to $(tabLink) and $(tabContent) through out the code. Go ahead and cache these when you declare them so that jQuery doesn't have to query the DOM each time. It's one of its slowest operations.

var $tabLink= $(link || '.tab-link');

Simplify animateIcon

There is really no reason to check to see which class you want to remove. You can remove both of them at the same time. If the object doesn't have the class then it's no big deal. So that function could become:

function animateIcon() {
  var addClassName = ( $iconElement.hasClass('sz-icon-arrow-right') ) ? 'sz-icon-arrow-down' : 'sz-icon-arrow-right';
  $iconElement.removeClass('sz-icon-arrow-right sz-icon-arrow-down').addClass(addClassName);
}

DRY Your Code

In your tabInit code, you do the same thing as your showElement function. You hide the tabs and show an element. So go ahead and just call the function directly. The only difference is in the tabInit code, you check for the element to exist first. That should probably be in the showElement code anyway so just add it.

Prepare For More

Your tabInit function takes three parameters now. Later on, you might want to add some functionality so you add another parameter. This can become a viscous cycle. Go ahead and let the function take a JavaScript object as a single parameter. That way you can add additional functionality by extending the object without changing the function signature.

You can declare a default tabOptions object with all of your defaults and use jQuerys extend(API) method to combine the given options with the defaults.

var tabOptions = {
  current : null,
  content : '.tab-content',
  link : '.tab-link'
}; 

function tabInit( opts ) {
  var options = $.extend( tabOptions, opts);
  var $tabLink = $(options.link);
}   

Namespacing

You might also want to consider adding a namespace to your code. For example, you could call it myFancyTab. Since all of your code is private, you can add your namespace to the window object and pass in only the public methods. In this case, I believe the only public method would need to be tabInit which you could then simply to just init.

window.myFancyTab = {
  init : tabInit
};

Note:The left-hand side is the "public" method name and the right side is the private method name.

Putting It All Together

So with all those changes in hand, your code might end up looking something like this:

(function($){
'use strict';

var $tabContent, $tabLink, $iconElement;

function tabInit( opts ) {
  var options = $.extend(tabOptions, opts);
  $tabLink = $( options.link );
  $tabContent = $( options.content );
  $iconElement = $tabLink.find('.icon');
  $tabLink.on('click', clickHandler );
  showElement( options.current );
}

function animateIcon() {
  var addClassName = ( $iconElement.hasClass('sz-icon-arrow-right') ) ? 'sz-icon-arrow-down' : 'sz-icon-arrow-right';
  $iconElement.removeClass('sz-icon-arrow-right sz-icon-arrow-down').addClass(addClassName);
}

function showElement(elId) {
  animateIcon();
  $tabContent.addClass('hide');
  if (elId) { $(elId).removeClass('hide'); }
}

function clickHandler() {
  showElement( $(this).data('element') );
}

var tabOptions = {
  current : null,
  content : '.tab-content',
  link : '.tab-link'
}; 

window.myFancyTab = {
  init : tabInit
};

})( jQuery );

You would then call it like this:

myFancyTab.init();  //accept all default options
myFancyTab.init({ current : '#myEl' });  //accept defaults with a current tab
myFancyTab.init({ link : '.other-tab-content' });  // use a differnt class for tab content

Hope that helps. Feel free to ask any questions.

\$\endgroup\$
1
\$\begingroup\$

For animateIcon you can use $.toggleClass(). So instead of this:

function animateIcon() {
  var iconElement = $(tabLink).find('.icon');
  var addClassName = iconElement.hasClass('sz-icon-arrow-right') ? 'sz-icon-arrow-down' : 'sz-icon-arrow-right';
  var removeClassName = iconElement.hasClass('sz-icon-arrow-right') ? 'sz-icon-arrow-right' : 'sz-icon-arrow-down';
  iconElement.removeClass(removeClassName).addClass(addClassName);
}

You will have:

function animateIcon() {
  $(tabLink).find('.icon').toggleClass('sz-icon-arrow-down, sz-icon-arrow-right');
}

Example

$("#content").on("click", function(){
  $(this).toggleClass("one two");
})
.one{
  background:red;
}

.two{
  background:blue;
}

div{
  height:60px;
  width:60px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id="content" class="one"></div>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.