#1
  1. No Profile Picture
    Contributing User
    Devshed Regular (2000 - 2499 posts)

    Join Date
    Sep 2006
    Posts
    2,015
    Rep Power
    535

    Best practices to create a jQuery plugin (and other help!)


    I am creating my own jQuery plugin, and have several questions. The purpose of the plugin is to take a HTML table, and make the header fixed, and make it sortable. A working copy is located at http://jsfiddle.net/6cqzN/1/. The purpose of the exercise is just as much to teach me a good and repeatable approach to creating jQuery plugins.

    1. I am basing the design pattern on http://docs.jquery.com/Plugins/Authoring. Do you recommend another design pattern? Am I correctly implementing it?

    2. I created a method "update" which just alternating stripes the rows. I can access this method externally by doing $('#myTab').sortfixedtable('update');. How would I access this method internally so I do not need to duplicate the code (for instance, after I init or sort the table).

    3. The "sort" method is a little different than the rest. Instead of "this" referring to the original DOM table object, "this" refers to the header element TH that was clicked. As such, I didn't make it chainable by using return this.each(function(){}. Was this appropriate? One problem is sorting the row manually after for instance adding a row. I want to grab the original element (i.e. $('#myTable').sortfixedtable('sort'); ), but that would mean that "this" refers to the table and not the header TH. Do I need to make two methods; one that works with the header TH and one that works with the main table?

    4. Is it considered preferred to not use jQuery within the plugin, but native JavaScript in order to increase speed?

    5. Is it best to keep the original HTML DOM intact but hidden, or is it okay to mix it around as I did (note that destroy puts it back).

    6. I ended up wrapping the header table, body div/table, and footer table in another div. Think this is necessary?

    7. I couldn't figure out how to allow CSS to target the original table ID, and move that style to the new header and footer which now reside outside of the table ID DOM. As such, style can only be applied by targeting a class. This is okay, but it would have been nice to allow targeting a class. Any easy way to do this?

    8. It looks perfect with FF15, IE6, IE7, and IE8, however, IE9 will sometimes wrap the header and/or footer. The intention is to take a table (with or without style), and keep it identical except for the scroll bar and style associated with added classes (for instance, header sorting and alternating rows). I've created a hack solution of detecting if the browser is IE9, however, that is obviously not a good solution. Any recommendation?

    9. Lastly, are there any other changes which you would recommend making.

    If you have good advice for one of my many questions but not all of them, please do not hesitate to just replying to one of them. Thank you!
  2. #2
  3. CSS & JS/DOM Adept
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jul 2004
    Location
    USA (verifiably)
    Posts
    20,127
    Rep Power
    4304
    1) It looks correct. You might find these helpful:

    Style in jQuery Plugins and Why it Matters
    http://remysharp.com/2010/06/03/sign...jquery-plugin/
    http://stefangabos.ro/jquery/jquery-...ate-revisited/
    https://github.com/cowboy/talks/blob...n-authoring.js
    http://coding.smashingmagazine.com/2...ugin-patterns/

    2) Try using "methods.update()". However, for that to work it looks like you would need to change this line of that function from
    Code:
    return this.each(function(){
    to
    Code:
    return $(this).each(function(){
    3) You could branch it based on the nodeName of the element.

    4) If you suspect that something is significantly faster in native JS, than try creating a speed test. Otherwise using jQuery is fine.

    7) Have you considered changing the ID on the original table and giving the new table the original ID?

    8) Would you please show us a screenshot of the "wrapping"? I suggest you set a width on ".myTableParent" <div>. So to answer point 6 as well, yes, a wrapper can be useful.

    9) One good general principle for programming is "DRY", Don't Repeat Yourself, which means you should avoid unnecessarily having what is basically the same statement or code block multiple times in your code. In the sort function, I would suggest moving the first pair of lines that set x_td and y_td before the if condition they are in and in the else block do this:
    Code:
                        x_td = parseFloat(x_td);
                        y_td = parseFloat(y_td);
    Spreading knowledge, one newbie at a time.

    Check out my blog. | Learn CSS. | PHP includes | X/HTML Validator | CSS validator | Common CSS Mistakes | Common JS Mistakes

    Remember people spend most of their time on other people's sites (so don't violate web design conventions).
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Regular (2000 - 2499 posts)

    Join Date
    Sep 2006
    Posts
    2,015
    Rep Power
    535
    Thank you Kravvitz!

    1) It looks correct. You might find these helpful:
    Thanks. Will do.

    2) Tried using "methods.update()". However, for that to work it looks like you would need to change this line of that function from
    I try using "methods.update()", but it didn't work. I then put a console.log(this) within the update method, and it replied that "this" referred to my plugin opposed to the targeted element. I believe I am missing some critical aspect about how things really work, and would appreciate as much elaboration as you are willing to give.

    3) You could branch it based on the nodeName of the element.
    I do not understand. Please elaborate.

    4) If you suspect that something is significantly faster in native JS, than try creating a speed test. Otherwise using jQuery is fine.
    Thanks. Guess it typically doesn't matter. The "finding if all elements in columns are numbers" script can take a noticeable amount of time with a big table, so I guess the right approach is don't worry about it unless it makes a difference.

    7) Have you considered changing the ID on the original table and giving the new table the original ID?
    Yes, but wasn't sure if this was the best idea. My original element was a table. My new element is a div which includes a table (header), a div with a table (body) in it, and another table (footer). Maybe still okay, but got me worried. Still think it best to move the ID to the new element?

    8) Would you please show us a screenshot of the "wrapping"? I suggest you set a width on ".myTableParent" <div>. So to answer point 6 as well, yes, a wrapper can be useful.
    Attached. Note that the "Footer Four" is on two lines. My desire that the table should look identical as before the plugin except for the scroll bar and any style added due to the added classes (i.e. alternate rows, etc). I can't believe it works on all browsers including IE6!, and then doesn't work with IE9. Step forwards or backwards?

    Thanks again for your help!
    Attached Images
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Regular (2000 - 2499 posts)

    Join Date
    Sep 2006
    Posts
    2,015
    Rep Power
    535
    Hello again Kravvitz,

    I read each of your recommended articles. Essential jQuery Plugin Patterns was a little over my head. jQuery Plugin Boilerplate described an interesting approach. It did, however, specifically state that "It does not adhere to the suggestions made by the jQuery documentation regarding Plugins/Authoring." Do you typically (or for my plugin if best not to generalize), would you recommend one approach over another?

    I am still confused regarding my questions 2 and 3 which I think are related.

    if I put a console.log(this) in either destroy(), init(), or update(), and then do something like $('#myTable').sortfixedtable('update'), I receive the jQuery object of the target (the table). Makes sense.

    If I do the same within sort(), and then click a header, I receive the header object (not a jQuery object). I guess this makes sense, but why isn't it a jQuery object? If I do $('#myTable').sortfixedtable('sort'), it breaks because this no longer refers to the header, but the table. Okay, but how do I make this work? If I then destroy() and re-create the plugin, then click the header, I receive multiple copies of the header (the number of copies increments every time I destroyed it and re-created it). I guess my destroy() method is inadvertently leaving some baggage, but can't find it.

    If next within a given method (say init), I use "methods.update()", the console.log within update() shows "this" as the following:
    Code:
     destroy     function()
     init        function()
    -sort        function()
      guid       11
      prototype  object()
     update      function()
    No longer does "this" reference the table element, so the function obviously breaks. Okay, I get the (4) function() parts, but why does sort also include guid and prototype? How do I get back to "this"?

    There is something fundamentally different about my sort() method than the others. Within it, "this" is not a jQuery object, and it points to the header. Also, I don't do the typical iterating over each element using "return $(this).each(function(){}". Lastly, e for all other methods is undefined, but is the click even for this sort(). Should I not be adding a sort() method like I did for the other three?

    My head hurts. I look forward to your or anyone's insight.
    Last edited by NotionCommotion; November 21st, 2012 at 08:24 AM.
  8. #5
  9. CSS & JS/DOM Adept
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jul 2004
    Location
    USA (verifiably)
    Posts
    20,127
    Rep Power
    4304
    Attached. Note that the "Footer Four" is on two lines. My desire that the table should look identical as before the plugin except for the scroll bar and any style added due to the added classes (i.e. alternate rows, etc). I can't believe it works on all browsers including IE6!, and then doesn't work with IE9.
    Different browsers and platforms render text differently. Some variation is to be expected. One solution would be to make the 3 tables a little wider than necessary to allow for that. Another would be to disable (or limit) word wrapping in the header and footer tables via the "white-space" property.

    Do you typically (or for my plugin if best not to generalize), would you recommend one approach over another?
    I haven't used the jQuery Plugin Boilerplate approach. Use whichever one you feel more comfortable with.

    No longer does "this" reference the table element, so the function obviously breaks. Okay, I get the (4) function() parts, but why does sort also include guid and prototype? How do I get back to "this"?
    You could call the function via the apply() or call() methods in order to change what "this" points to within the function. Otherwise, you'll need to pass a reference when you call the update function or somehow obtain it within the update function. In JavaScript functions automatically get a prototype property/object and I suspect that the browser automatically adds the guid property too.
    Spreading knowledge, one newbie at a time.

    Check out my blog. | Learn CSS. | PHP includes | X/HTML Validator | CSS validator | Common CSS Mistakes | Common JS Mistakes

    Remember people spend most of their time on other people's sites (so don't violate web design conventions).
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Regular (2000 - 2499 posts)

    Join Date
    Sep 2006
    Posts
    2,015
    Rep Power
    535
    Thanks Kravvitz,

    Different browsers and platforms render text differently. Some variation is to be expected.
    I get that, but didn't expect to see any for this case. I wrapped a <div> with zero margin and padding, and made the width what I thought was exactly the width of the scroll bar plus the table width. Turns out exactly 1 px more makes it work for IE9, but that seems like a weak solution. Maybe I am calculating the scroll width incorrectly?

    Code:
    this.style.width = "100%";
    this.parentNode.style.overflow = "scroll";
    scrollWidth = this.parentNode.offsetWidth-this.offsetWidth;
    In regards to what "this" means for various contexts, can you recommend a good article or book which might help me better understand?

    Thanks
  12. #7
  13. CSS & JS/DOM Adept
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jul 2004
    Location
    USA (verifiably)
    Posts
    20,127
    Rep Power
    4304
    You're welcome.

    I don't know if browsers are consistent about whether they include the scrollbar width in the offsetWidth or not.

    Here are some articles that cover the "this" keyword and scope:
    http://www.digital-web.com/articles/...in_javascript/
    http://net.tutsplus.com/tutorials/ja...-this-keyword/
    http://www.moreofless.co.uk/javascri...s-scope-usage/
    http://oranlooney.com/function-scope-javascript/
    https://developer.mozilla.org/en-US/...Operators/this
    Spreading knowledge, one newbie at a time.

    Check out my blog. | Learn CSS. | PHP includes | X/HTML Validator | CSS validator | Common CSS Mistakes | Common JS Mistakes

    Remember people spend most of their time on other people's sites (so don't violate web design conventions).

IMN logo majestic logo threadwatch logo seochat tools logo