admin管理员组

文章数量:1323524

I'm new to authoring jQuery plugins, but I've got a good bit of experience with using them. Some plugins I've worked with can only be initialized programmatically, i.e.

$("input[name='blah']").fancyPants();

I find this annoying, but I've managed to figure out how to write a basic plugin like this from tutorials and looking through other plugins' code.

Others, like Bootstrap's modal ponent, can be initialized purely through markup, i.e.

<input type='text' name='blah' class='fancy-pants'>

And then just by including the plugin .js file, any input with class fancy-pants is automatically initialized as a fancyPants plugin. I like this behavior, even though I know it hogs a particular class name.

Question 1: how do they do this? I looked through bootstrap.js, but there seems to be a lot in there I don't understand.

Question 2: Lets say I have a plugin that enpasses multiple elements in the DOM. For example,

<button class="bootstrapradio" name="primary_group" value="epicurist"><i class='fa fa-cutlery'></i></button>
<button class="bootstrapradio" name="primary_group" value="futurist"><i class='fa fa-space-shuttle'></i></button>
<button class="bootstrapradio" name="primary_group" value="stoic"><i class='fa fa-tree'></i></button>        

<button class="bootstrapradio" name="beverage" value="beer"><i class='fa fa-beer'></i></button>
<button class="bootstrapradio" name="beverage" value="martini"><i class='fa fa-glass'></i></button>

Without changing the markup structure (e.g. adding a wrapper container), how can I automatically initialize two instances of my plugin, one which covers all buttons with a class of bootstrapradio and name primary_group, and another which covers all buttons with a class of bootstrapradio and name beverage?

EDIT: Here's what I'm working with right now.

(function ( $ ) {

    var methods = {
        init : function(options) {
            // Initialization method, adds classes to elements and binds events
            ...
            return this;
        },
        value : function( new_val ) {
            // Method to get/set value
            ...
        }
    };

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

An example of how I'd like this plugin to transform the DOM:

BEFORE document.ready:

    <button class="bootstrapradio" name="primary_group" value="epicurist" title='Epicurist' disabled><i class='fa fa-cutlery'></i></button>
    <button class="bootstrapradio" name="primary_group" value="futurist" title='Futurist'><i class='fa fa-space-shuttle'></i></button>
    <button class="bootstrapradio" name="primary_group" value="stoic" title='Stoic'><i class='fa fa-tree'></i></button>        

    <button class="bootstrapradio" name="beverage" value="beer" title='Beer'><i class='fa fa-beer'></i></button>
    <button class="bootstrapradio" name="beverage" value="martini" title='Martini'><i class='fa fa-glass'></i></button>

AFTER document.ready:

    <button class="btn btn-xs bootstrapradio" name="primary_group" value="epicurist" title='Epicurist' disabled><i class='fa fa-cutlery'></i></button>
    <button class="btn btn-xs bootstrapradio" name="primary_group" value="futurist" title='Futurist'><i class='fa fa-space-shuttle'></i></button>
    <button class="btn btn-xs bootstrapradio" name="primary_group" value="stoic" title='Stoic'><i class='fa fa-tree'></i></button>
    <input type="hidden" name="primary_group">

    <button class="btn btn-xs bootstrapradio" name="beverage" value="beer" title='Beer'><i class='fa fa-beer'></i></button>
    <button class="btn btn-xs bootstrapradio" name="beverage" value="martini" title='Martini'><i class='fa fa-glass'></i></button>
    <input type="hidden" name="beverage">

I'm new to authoring jQuery plugins, but I've got a good bit of experience with using them. Some plugins I've worked with can only be initialized programmatically, i.e.

$("input[name='blah']").fancyPants();

I find this annoying, but I've managed to figure out how to write a basic plugin like this from tutorials and looking through other plugins' code.

Others, like Bootstrap's modal ponent, can be initialized purely through markup, i.e.

<input type='text' name='blah' class='fancy-pants'>

And then just by including the plugin .js file, any input with class fancy-pants is automatically initialized as a fancyPants plugin. I like this behavior, even though I know it hogs a particular class name.

Question 1: how do they do this? I looked through bootstrap.js, but there seems to be a lot in there I don't understand.

Question 2: Lets say I have a plugin that enpasses multiple elements in the DOM. For example,

<button class="bootstrapradio" name="primary_group" value="epicurist"><i class='fa fa-cutlery'></i></button>
<button class="bootstrapradio" name="primary_group" value="futurist"><i class='fa fa-space-shuttle'></i></button>
<button class="bootstrapradio" name="primary_group" value="stoic"><i class='fa fa-tree'></i></button>        

<button class="bootstrapradio" name="beverage" value="beer"><i class='fa fa-beer'></i></button>
<button class="bootstrapradio" name="beverage" value="martini"><i class='fa fa-glass'></i></button>

Without changing the markup structure (e.g. adding a wrapper container), how can I automatically initialize two instances of my plugin, one which covers all buttons with a class of bootstrapradio and name primary_group, and another which covers all buttons with a class of bootstrapradio and name beverage?

EDIT: Here's what I'm working with right now.

(function ( $ ) {

    var methods = {
        init : function(options) {
            // Initialization method, adds classes to elements and binds events
            ...
            return this;
        },
        value : function( new_val ) {
            // Method to get/set value
            ...
        }
    };

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

An example of how I'd like this plugin to transform the DOM:

BEFORE document.ready:

    <button class="bootstrapradio" name="primary_group" value="epicurist" title='Epicurist' disabled><i class='fa fa-cutlery'></i></button>
    <button class="bootstrapradio" name="primary_group" value="futurist" title='Futurist'><i class='fa fa-space-shuttle'></i></button>
    <button class="bootstrapradio" name="primary_group" value="stoic" title='Stoic'><i class='fa fa-tree'></i></button>        

    <button class="bootstrapradio" name="beverage" value="beer" title='Beer'><i class='fa fa-beer'></i></button>
    <button class="bootstrapradio" name="beverage" value="martini" title='Martini'><i class='fa fa-glass'></i></button>

AFTER document.ready:

    <button class="btn btn-xs bootstrapradio" name="primary_group" value="epicurist" title='Epicurist' disabled><i class='fa fa-cutlery'></i></button>
    <button class="btn btn-xs bootstrapradio" name="primary_group" value="futurist" title='Futurist'><i class='fa fa-space-shuttle'></i></button>
    <button class="btn btn-xs bootstrapradio" name="primary_group" value="stoic" title='Stoic'><i class='fa fa-tree'></i></button>
    <input type="hidden" name="primary_group">

    <button class="btn btn-xs bootstrapradio" name="beverage" value="beer" title='Beer'><i class='fa fa-beer'></i></button>
    <button class="btn btn-xs bootstrapradio" name="beverage" value="martini" title='Martini'><i class='fa fa-glass'></i></button>
    <input type="hidden" name="beverage">
Share Improve this question edited Aug 31, 2014 at 20:18 alexw asked Aug 31, 2014 at 19:25 alexwalexw 8,6886 gold badges58 silver badges88 bronze badges 6
  • 2 What exactly do you mean by initialise? – ArtOfCode Commented Aug 31, 2014 at 19:33
  • I think OP wants to know how to make use of say data-role=modal and similar attributes to trigger specific JS events. @ArtOfCode – Newtt Commented Aug 31, 2014 at 19:35
  • Yeah, I get that, I just don't know what he wants to do in this initialisation. – ArtOfCode Commented Aug 31, 2014 at 19:37
  • Yes, I'd like the plugin to automatically call its init method on an element with a particular attribute (class or data-*, for instance). – alexw Commented Aug 31, 2014 at 19:37
  • If you're going this route you should also consider setting some defaults so the use can override those by passing in their own options. This is also very mon pattern: stackoverflow./questions/8905507/… – jammykam Commented Aug 31, 2014 at 20:43
 |  Show 1 more ment

4 Answers 4

Reset to default 3

You can select attributes with jQuery this way

$("[data-role='modal']");

So, in your example you'd have something like

$(".bootstrapradio[name='primary_group']").myPlugin();
$(".bootstrapradio[name='beverage']").myPlugin();

Lots of plugins do this without the class, by having a plugin specific attribute name.

One example is Lightbox2, which finds all of its possible elements using $("[data-lightbox]"), and then separates them into individual lightbox groups based on the value of those attributes.

If you want it to do it automatically, you would just wrap this same functionality in a document ready inside your plugin file.

As an example, for this HTML:

<div data-bootstrapradio='type1'></div>
<div data-bootstrapradio='type1'></div>
<div data-bootstrapradio='type2'></div>

And this Javascript:

$(function(){

    $.fn.bootstrapradio = function(params) {
        return $(this).each(function() {
            // Your code
        });
    }

    // Get all the radios on the page currently
    var $radios = $("[data-bootstrapradio]");

    // Get the groupings of them
    var radioTypes = [];
    $radios.each(function(i,el){
        var type = $(this).attr("data-bootstrapradio");
        if (radioTypes.indexOf(type) === -1){
            radioTypes.push(type);
        }
    });

    // Initialize the plugin using all radios of each type
    for (var i=0; i<radioTypes.length; i++){

        var type = radioTypes[i];

        var radioGroup = $radios.filter(function(i,el){
            return $(el).is("[data-bootstrapradio="+type+"]");
        }).bootstrapradio();

        // Do whatever else with radioGroup you want for initialization
    }

});

You end up with 2 bootstrapradios, the first which was initialized with two elements, and the second which was initialized with one.

Well it does depend what you want to do in your initialisation. In general, I guess you're going to have to use a document-ready clause with some attribute checking logic and custom initialisation code.

Here's the standard construct for jQuery plugins:

(function($) {
    $.fn.myPlugin = function(params) {
         return $(this).each(function() {
             // do your plugin stuff
         });
    }
})(jQuery);

I think you'll have to modify that so that on document-ready, your plugin initialises, and then perhaps if you still want to be able to call it, you add the function too:

(function($) {
    $(document).ready(function() {
        $(".bootstrapradio").each(function() {
            if($(this).attr("name") == "primary-group") {
                // do something specific for name 'primary-group'
            }
            else if($(this).attr("name") == "something-else") {
                // do something else
            }
            else {
                // either catch-all or just drop the element
            }
        });
    });

    $.fn.myPlugin = function(params) {
        // do your plugin stuff
    }
})(jQuery);

If you want to use your existing init method and just give it the name, it's even easier:

(function($) {
    var self = this;

    $(document).ready(function() {
        $(".bootstrapradio").each(function() {
            self.methods.init($(this).attr("name"));
        });
    });

    $.fn.myPlugin = function(params) {
        // do your plugin stuff
    }
})(jQuery);

The self is important to give you access to the correct scope here.

Additionally, your current code has a syntax error. Your last line:

}( jQuery ));

should be:

})(jQuery);

EDIT: Worth noting that that last point isn't actually a syntax error after all, according to the article linked in the ment below. Either works.

Try (this pattern)

// hold jquery `ready` event until `plugin` "ready"
$.holdReady(true);
$(function() {
  // `deferred` until _after_ `plugin` defined ,
  // `ready` `hold` `resolved` within `plugin`
  $.bootstrapradio().css("font-size", "24px")
});

// `plugin` 
(function($, window, undefined) {
    // define `plugin` as `function` and `object`
    // check document for `bootstrapradio` `class` elements ,
    // if true , proceed , if not ,
    // create empty jquery object , proceed
    // define logic checks for elements , objects , other
    // in window , document , other
    $.fn.bootstrapradio = $.bootstrapradio = function(options) {
        $elems = $(".bootstrapradio").is("*") ? $(".bootstrapradio") : $({});
        // settings
        $.bootstrapradio.settings = {
            "primary_group" : "green"
            , "beverage" : "blue"
        };
        // extend settings to options
        var options = $.extend($.bootstrapradio.settings, options);
        // do stuff
        // e.g., apply `settings`/`options` to `$elem`
        // based on `name` , `value` attributes in `html`
        $elems.each(function(k, v) {
            $.each(options, function(_name, _value) {
                if (_name === v.name) {
                  $(v).html(v.value).css("color", _value)
                }
            })
        });
        // return `$elems`
        return $elems;
    } || {};
    // call `plugin`
    $.when($.bootstrapradio())
    .done(function(_$elem) {
        // check if `plugin` type cast
        // to both `function` and `object` ,
        // redundant check if `plugin` `in` window
        if (typeof $.fn.bootstrapradio === "function" 
           && typeof $.bootstrapradio === "object"
           && $.bootstrapradio in window) {
               // resolve jquery `ready` (`document.ready`) `hold` ,
               // do other stuff ,
               // e.g., `$.bootstrapradio().css("font-size", "24px")`
               $.holdReady(false);
        };
    })

}(jQuery, window));

jsfiddle http://jsfiddle/guest271314/f2asu701/

Well, @ArtOfCode's answer put me on the right track. Basically, I ended up using the .each pattern inside $(document).ready, and then check to see if the hidden input field has been added for the named group to which the element belongs. So that way, I can add the btn btn-xs classes to all elements, but only add the hidden input once.

Then, I kept my original code with the init method to allow initializing the plugin programmatically.

Here's what I came up with, I'm releasing it under the MIT license :)

https://github./alexweissman/bootstrapradio

本文标签: javascriptCreating a jQuery plugin that initializes itself based on HTML class and nameStack Overflow