1
\$\begingroup\$

Looking for a review of my first published jQuery Plugin. It's for TreeViews, very basic example demo can be seen at: Demo

Everything works fine, just looking to see if I can get some feedback on things that I may be doing wrong best practices wise and/or things I could be doing better.

(function ( $ ) {

  var methods = {
    init : function(options) {

      // Default Settings
      var settings = $.extend({
          'expandIconClass' : 'closed',
          'contractIconClass' : 'open',
          'setFocus' : undefined,
          'classPrefix' : 'goodtree_',
        }, options);

      return this.each(function() {

        // Hide all of the children Elements
        $(this).find('ul').hide();

        // Add the plus minus buttons
        $(this).find('li').each(function() {
          if($(this).children('ul').length > 0)
            $(this).prepend($('<div />', {'class': settings.classPrefix + "toggle " + settings.expandIconClass}));
        });

        // Events
        $('.' + settings.classPrefix + 'toggle').click(function() {
          $(this).parent().children('ul').toggle();
          $(this).hasClass('open') 
            ? $(this).removeClass(settings.contractIconClass).addClass(settings.expandIconClass) 
            : $(this).removeClass(settings.expandIconClass).addClass(settings.contractIconClass);
        });

        if(undefined !== settings.setFocus)
        {
          $(this).goodtree('setFocus', settings.setFocus);
        }

      });
    },

    setFocus : function(obj) {
      return this.each(function() {
        var tree_parent = this;
        $(obj).parents('ul').each(function() {
          if($(this) === this)
            return;
          else
            $(this).show();
        });
      });
    }
  }

  $.fn.goodtree = function( method ) {
    if ( methods[method] ) {
        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
      } else if ( typeof method === 'object' || ! method ) {
        return methods.init.apply( this, arguments );
      } else {
        $.error( 'Method ' +  method + ' does not exist on jQuery.goodtree' );
      }
  };
}) ( jQuery );
\$\endgroup\$
2
  • \$\begingroup\$ Just a question why are you doing ` var tree_parent = this;` if you do not use the var tree parent after! \$\endgroup\$ Commented Apr 5, 2013 at 21:30
  • \$\begingroup\$ because it was a bug...I need to be comparing to tree_parent in the comparison \$\endgroup\$ Commented Apr 8, 2013 at 13:10

1 Answer 1

1
\$\begingroup\$

First off it'd be good to run the code through jslint or jshint. It identifies a few issues, such as an extra comma in the default options list; that'll break the code in older IE versions. Also, it flags some things like the block-less if, which - while allowed - it's best to avoid for the sake of consistency and maintainability.

As for the code itself, here are the things I noticed:

  • jQuery objects should be cached whenever possible
  • The code only handles unordered lists (ULs), not ordered lists (OL) although it might as well
  • The event handling is added document-wide every time the code is run. If you use the plugin twice in a document, the first tree gets double event handlers. That spells trouble.
  • The setFocus function might misbehave if the tree list is itself inside a list element, as it loops through all list-type parents up to the document root.
  • The classPrefix option is confusing: It's not used for the expand/contract classes, only for the toggle button's "main" class. But since that's the only one, why not just let the user define that as well? Basically, if a prefix is used only once, it's not necessary.
  • Comparing against undefined is a no-no. undefined is not a reserved word, and can (in some runtimes) be defined. In this case, a simple boolean works just as well.

Here's what I arrived at

(function ($) {
  var methods = {
    init: function(options) {
      // Default Settings
      var settings = $.extend({
        expandIconClass:   'closed',
        contractIconClass: 'open',
        toggleButtonClass: 'toggle',
        setFocus:          false      // just use a boolean here
      }, options);

      return this.each(function() {
        var target = $(this);

        // walk the tree
        target.find('li').each(function() {
          var node = $(this),
              branches = node.children('ul, ol'),
              button;

          if(branches.length > 0) {
            branches.hide();
            button = $('<div />', {
              'class': settings.toggleButtonClass + " " + settings.expandIconClass,
              on: {
                click: function (event) {
                  // we already have the correct elements here
                  branches.toggle();
                  button.toggleClass(settings.expandIconClass + " " + settings.contractIconClass);
                }
              }
            });

            node.prepend(button);
          }
        });

        if(settings.setFocus === true) {
          target.goodtree('setFocus');
        }
      });
    },

    setFocus: function(element) {
      return this.each(function() {
        $(element).parents('ul, ol').each(function() {
          var ancestor = $(this);
          if( this.is(ancestor) ) { // better check
            return false; // stop the each-loop
          }
          ancestor.show();
        });
      });
    }
  };

  $.fn.goodtree = function(method) {
    if(typeof methods[method] === 'function') { // stronger conditional
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if(typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error('Method ' +  method + ' does not exist on jQuery.goodtree');
    }
  };
}(jQuery));

Here's a demo (I reused your demo markup and styling, minus the open/close icons)

\$\endgroup\$
4
  • \$\begingroup\$ this makes a lot of sense, thanks tons for the advice! \$\endgroup\$ Commented Apr 8, 2013 at 13:15
  • \$\begingroup\$ @goodcodeguy No prob; glad it's useful. By the way, I might rename setFocus to reveal or expand or similar. Seems more descriptive, and "focus" is usually used to refer to selected input elements. Just a thought. \$\endgroup\$
    – Flambino
    Commented Apr 8, 2013 at 19:30
  • \$\begingroup\$ actually that's what it's used for. It's used to focus on a single element no matter where it is in the trees and opens up all the parents so it can be exposed (can also be used to set focus on multiple elements). Very useful for search filters which is the primary reason I generally use this type of layout. \$\endgroup\$ Commented Apr 9, 2013 at 0:31
  • \$\begingroup\$ @goodcodeguy Exactly my point. It's used to expose or reveal an item, whereas "focus" typically refers to the form element that's receiving keyboard input. I.e. there is a focus() method in the native DOM already, but its purpose is different and is only used in connection with form elements. Hence why I'd suggest not using the word "focus" unless you're dealing with input elements. \$\endgroup\$
    – Flambino
    Commented Apr 9, 2013 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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