New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trigger event when bootstrap-buttons are pressed #2380

Closed
philfreo opened this Issue Mar 2, 2012 · 31 comments

Comments

Projects
None yet
@philfreo
Contributor

philfreo commented Mar 2, 2012

Because bootstrap-button.js uses event delegation on the body tag to detect and handle clicks on its buttons, it makes it hard for other code to "do something" when the button is pressed and also properly detect whether the button is (after the click) in an activated state or not.

For example, someone might try to use a btn.click() event handler and then check for hasClass('active') -- but that won't work since the active class hasn't been toggled on or off until the event bubbles up all the way to <body>.

A simple solution would be to trigger an event upon button click.
At the end of the Button.prototype.toggle function, just add this.$element.trigger('button-change');

@quasipickle

This comment has been minimized.

Show comment
Hide comment
@quasipickle

quasipickle Mar 3, 2012

Why not just add your own listener via on() (or delegate()) to the <body>?

delegate() is likely used because of the large performance gains over adding a listener to each button.

quasipickle commented Mar 3, 2012

Why not just add your own listener via on() (or delegate()) to the <body>?

delegate() is likely used because of the large performance gains over adding a listener to each button.

@philfreo

This comment has been minimized.

Show comment
Hide comment
@philfreo

philfreo Mar 5, 2012

Contributor

Ya I understand the performance implications if you have a lot of buttons, but if you have different buttons that should do different things it's much cleaning coding wise to say fooBtnGroup.on('button-change', '.btn') than to have to keep adding additional handlers to the body delegate that check for only one button group before continuing their action. Am I missing something?

Contributor

philfreo commented Mar 5, 2012

Ya I understand the performance implications if you have a lot of buttons, but if you have different buttons that should do different things it's much cleaning coding wise to say fooBtnGroup.on('button-change', '.btn') than to have to keep adding additional handlers to the body delegate that check for only one button group before continuing their action. Am I missing something?

@Merg1255

This comment has been minimized.

Show comment
Hide comment
@Merg1255

Merg1255 Mar 7, 2012

I agree. There should be a clear button onclick trigger, and also an easy wayto check if it's active.

Merg1255 commented Mar 7, 2012

I agree. There should be a clear button onclick trigger, and also an easy wayto check if it's active.

@quasipickle

This comment has been minimized.

Show comment
Hide comment
@quasipickle

quasipickle Mar 8, 2012

What about:

$("body").on("click", ".footBtnGroup .btn")

Seems pretty clean.

quasipickle commented Mar 8, 2012

What about:

$("body").on("click", ".footBtnGroup .btn")

Seems pretty clean.

@Merg1255

This comment has been minimized.

Show comment
Hide comment
@Merg1255

Merg1255 Mar 8, 2012

yes, there might be a few things that work, but why not have a easier way to do this? It should make developing better.

Merg1255 commented Mar 8, 2012

yes, there might be a few things that work, but why not have a easier way to do this? It should make developing better.

@philfreo

This comment has been minimized.

Show comment
Hide comment
@philfreo

philfreo Mar 8, 2012

Contributor

and what if all you have is a reference to the button group (no simple path would suffice)?

Contributor

philfreo commented Mar 8, 2012

and what if all you have is a reference to the button group (no simple path would suffice)?

@quasipickle

This comment has been minimized.

Show comment
Hide comment
@quasipickle

quasipickle Mar 8, 2012

@philfreo Admittedly if you don't have a string selector it does get uglier - but still possible: http://jsfiddle.net/GFsQM/1/

@Merg1255 From what I've read on here, @fat and @markdotto are reluctant to add functionality if said functionality is already possible. In my opinion adding another click event to fire whenever a button is clicked is extra bloat. I'm nothing more than a bystander of Bootstrap though, so my opinion doesn't really count for much.

quasipickle commented Mar 8, 2012

@philfreo Admittedly if you don't have a string selector it does get uglier - but still possible: http://jsfiddle.net/GFsQM/1/

@Merg1255 From what I've read on here, @fat and @markdotto are reluctant to add functionality if said functionality is already possible. In my opinion adding another click event to fire whenever a button is clicked is extra bloat. I'm nothing more than a bystander of Bootstrap though, so my opinion doesn't really count for much.

@philfreo

This comment has been minimized.

Show comment
Hide comment
@philfreo

philfreo Mar 8, 2012

Contributor

I guess the overall point is: from a purely UI/CSS perspective, the existing code is perfect.
However to actually integrate any real functionality with the buttons (when they do different things) there's no good way to reliably make sure your handler gets put in the right event delegation/bubbling order.

Bootstrap doesn't have to have it bind an onclick per button (because there could be tons on a page). You just need it to trigger ONE event each time a button (or even button group) is pressed.

Contributor

philfreo commented Mar 8, 2012

I guess the overall point is: from a purely UI/CSS perspective, the existing code is perfect.
However to actually integrate any real functionality with the buttons (when they do different things) there's no good way to reliably make sure your handler gets put in the right event delegation/bubbling order.

Bootstrap doesn't have to have it bind an onclick per button (because there could be tons on a page). You just need it to trigger ONE event each time a button (or even button group) is pressed.

@Merg1255

This comment has been minimized.

Show comment
Hide comment
@Merg1255

Merg1255 commented Mar 8, 2012

agree

@alekseyev

This comment has been minimized.

Show comment
Hide comment
@alekseyev

alekseyev Mar 24, 2012

I agree, currently these buttons are a pain to use

alekseyev commented Mar 24, 2012

I agree, currently these buttons are a pain to use

@alekseyev

This comment has been minimized.

Show comment
Hide comment
@alekseyev

alekseyev Mar 24, 2012

I tried

$('body').on('click', '.btn', handler_func)

But handler_func is still invoked BEFORE the state is changed

alekseyev commented Mar 24, 2012

I tried

$('body').on('click', '.btn', handler_func)

But handler_func is still invoked BEFORE the state is changed

@fat

This comment has been minimized.

Show comment
Hide comment
@fat

fat Mar 25, 2012

Member

If you're writing js already - i highly suggest you don't include the button plugin just for toggle functionality.

If you look at the code - it's literally just: this.$element.toggleClass('active')

It only exists really as a helper for people who are trying to stay out of the js and get that functionality for free.

I'm hesitant to trigger a fake 'click' event handler for this because i think it's making things a little too "abstract" and roundabout. Instead, just toggle your active class. word?

Member

fat commented Mar 25, 2012

If you're writing js already - i highly suggest you don't include the button plugin just for toggle functionality.

If you look at the code - it's literally just: this.$element.toggleClass('active')

It only exists really as a helper for people who are trying to stay out of the js and get that functionality for free.

I'm hesitant to trigger a fake 'click' event handler for this because i think it's making things a little too "abstract" and roundabout. Instead, just toggle your active class. word?

@fat fat closed this Mar 25, 2012

@philfreo

This comment has been minimized.

Show comment
Hide comment
@philfreo

philfreo May 28, 2012

Contributor

Sure, the JS isn't complicated and can be replicated, but same thing with everything in Bootstrap. I thought the point was to have a set of standard components that could easily be put in. It's not completely trivial to have button toggling JS (especially with button groups) so having one good standard solution is much better than coming up with one each time it's needed.

Contributor

philfreo commented May 28, 2012

Sure, the JS isn't complicated and can be replicated, but same thing with everything in Bootstrap. I thought the point was to have a set of standard components that could easily be put in. It's not completely trivial to have button toggling JS (especially with button groups) so having one good standard solution is much better than coming up with one each time it's needed.

@raziel057

This comment has been minimized.

Show comment
Hide comment
@raziel057

raziel057 Jun 18, 2012

  • 1 to add the trigger. It seems to be a must have feature and it doesn't need a lot of code, just a call to this.$element.trigger('button-change');

raziel057 commented Jun 18, 2012

  • 1 to add the trigger. It seems to be a must have feature and it doesn't need a lot of code, just a call to this.$element.trigger('button-change');
@raziel057

This comment has been minimized.

Show comment
Hide comment
@raziel057

raziel057 Jun 18, 2012

Moreover, for buttons type radio, it's a little more complicated that just use this.element.toogleClass('active'). We need to removeClass('active') on the others buttons. For now, the developper must rewrite the logic and I think it would be abstract when he just want to add complementary behaviors.

raziel057 commented Jun 18, 2012

Moreover, for buttons type radio, it's a little more complicated that just use this.element.toogleClass('active'). We need to removeClass('active') on the others buttons. For now, the developper must rewrite the logic and I think it would be abstract when he just want to add complementary behaviors.

@mfansler

This comment has been minimized.

Show comment
Hide comment
@mfansler

mfansler Aug 25, 2012

Contributor

@raziel057 You don't need to rewrite any logic. See Docs on Buttons Plugin. It includes radio button behavior.

Contributor

mfansler commented Aug 25, 2012

@raziel057 You don't need to rewrite any logic. See Docs on Buttons Plugin. It includes radio button behavior.

@clawfire

This comment has been minimized.

Show comment
Hide comment
@clawfire

clawfire Sep 25, 2012

@mfansler yep it already includes it, but here @raziel057 talk about rewriting the logic just as @fat suggested it. (wich is pretty stupid IMO ... )

clawfire commented Sep 25, 2012

@mfansler yep it already includes it, but here @raziel057 talk about rewriting the logic just as @fat suggested it. (wich is pretty stupid IMO ... )

@jonaizen

This comment has been minimized.

Show comment
Hide comment
@jonaizen

jonaizen Dec 11, 2012

I find this obnoxious too. I handled it using window.setTimeout and calling my code a little bit after the click happens - in my case it works, but I am concerned about it being inconsistent.

jonaizen commented Dec 11, 2012

I find this obnoxious too. I handled it using window.setTimeout and calling my code a little bit after the click happens - in my case it works, but I am concerned about it being inconsistent.

@evereq

This comment has been minimized.

Show comment
Hide comment
@evereq

evereq Dec 26, 2012

Well, I think it should be fixed in Bootstrap code... basically many developers would expect that inside click event handler it is possible to read state of the button ('active' or not). It's just trap for many errors....

My +1 to add trigger to "change" event and add this to documentation of Buttons Plugin with samples how and why it's important to use "change" event, instead of "click" to detect button state!

evereq commented Dec 26, 2012

Well, I think it should be fixed in Bootstrap code... basically many developers would expect that inside click event handler it is possible to read state of the button ('active' or not). It's just trap for many errors....

My +1 to add trigger to "change" event and add this to documentation of Buttons Plugin with samples how and why it's important to use "change" event, instead of "click" to detect button state!

@felipap

This comment has been minimized.

Show comment
Hide comment
@felipap

felipap Jan 10, 2013

I agree with the OP, this is very subtle behavior! Where is it documented that we must use $('body').on('click', '.btn', func); insted of $('.btn').click(func);?
I had to spend a couple of hours of debugging yesterday before I stumbled across this issue here.

felipap commented Jan 10, 2013

I agree with the OP, this is very subtle behavior! Where is it documented that we must use $('body').on('click', '.btn', func); insted of $('.btn').click(func);?
I had to spend a couple of hours of debugging yesterday before I stumbled across this issue here.

@phpwns

This comment has been minimized.

Show comment
Hide comment
@phpwns

phpwns Feb 23, 2013

Here's a solution that works with vanilla bootstrap code (including the ability to "lock" the class of an individual button in the group) AND has a functional listener:

<!-- toggles -->
<div class="btn-group" data-toggle="buttons-checkbox" id="fooBtnGroup">
    <button type="button" class="btn active" data-toggle="button">Widgets</button>
    <button type="button" class="btn active">Frammis Rods</button>              
    <button type="button" class="btn">Comex Gears</button>
</div>
$( 'body' ).on( 'click', '#fooBtnGroup .btn', function(event) {
    event.stopPropagation(); // prevent default bootstrap behavior
    if( $(this).attr('data-toggle') != 'button' ) { // don't toggle if data-toggle="button"
        $(this).toggleClass('active');
    }
    alert( $(this).hasClass( 'active' ) ); // button state AFTER the click
});

phpwns commented Feb 23, 2013

Here's a solution that works with vanilla bootstrap code (including the ability to "lock" the class of an individual button in the group) AND has a functional listener:

<!-- toggles -->
<div class="btn-group" data-toggle="buttons-checkbox" id="fooBtnGroup">
    <button type="button" class="btn active" data-toggle="button">Widgets</button>
    <button type="button" class="btn active">Frammis Rods</button>              
    <button type="button" class="btn">Comex Gears</button>
</div>
$( 'body' ).on( 'click', '#fooBtnGroup .btn', function(event) {
    event.stopPropagation(); // prevent default bootstrap behavior
    if( $(this).attr('data-toggle') != 'button' ) { // don't toggle if data-toggle="button"
        $(this).toggleClass('active');
    }
    alert( $(this).hasClass( 'active' ) ); // button state AFTER the click
});
@nzifnab

This comment has been minimized.

Show comment
Hide comment
@nzifnab

nzifnab Feb 23, 2013

I'm...confused. Over here: http://twitter.github.com/bootstrap/javascript.html#overview there is an Events subheading that indicates I should be able to handle events from my buttons. Can someone tell me what the point of a <div class='btn-group' data-toggle='buttons-radio'>...</div> is for if you literally can't do anything with selected option after the fact? There's no notion of data-value on those grouped buttons, I can't find any documentation as to the selected value of the button group, and switching buttons fires exactly zero javascript events.

Seems to me like we need to take a look at the "Principal of least surprise" and trigger a toggle event directly on those button elements or the button group when they're toggled.

Because right now I honestly don't know what the point of the radio group is even for o.O

nzifnab commented Feb 23, 2013

I'm...confused. Over here: http://twitter.github.com/bootstrap/javascript.html#overview there is an Events subheading that indicates I should be able to handle events from my buttons. Can someone tell me what the point of a <div class='btn-group' data-toggle='buttons-radio'>...</div> is for if you literally can't do anything with selected option after the fact? There's no notion of data-value on those grouped buttons, I can't find any documentation as to the selected value of the button group, and switching buttons fires exactly zero javascript events.

Seems to me like we need to take a look at the "Principal of least surprise" and trigger a toggle event directly on those button elements or the button group when they're toggled.

Because right now I honestly don't know what the point of the radio group is even for o.O

@kevinknelson

This comment has been minimized.

Show comment
Hide comment
@kevinknelson

kevinknelson Jun 25, 2013

@nzifnab - I know this is old, but thought I'd answer since this came up in one of my own searches for a similar problem...

The btn-group is just styling. The data-toggle is just a simple javascript that makes them act like radio or checkboxes and automatically does the activating for you. This will not make code for a submittable form...so it's most useful when you have JavaScript doing an AJAX post, etc., to utilize the data and build your values. To get a list of the selected values, you can find the buttons with the active status like so:

var values = [];
$('#btn-group-instance').find('.active').each( function() {
    values.push( $(this).data('value') );
} );

In one case recently, I was able to just use the default functionality. However, like in this thread, I now have an instance where I must know the state of the button when it's clicked and act accordingly and had the trouble that my method was getting called before the toggle took place. So, I turned off the data-toggle to stop bootstrap from handling it and made my own method to do the same thing:

$('body').on('click','#btn-group-instance button', function() {
    $(this).toggleClass('active');
    // if active/else not active logic
} );
}

kevinknelson commented Jun 25, 2013

@nzifnab - I know this is old, but thought I'd answer since this came up in one of my own searches for a similar problem...

The btn-group is just styling. The data-toggle is just a simple javascript that makes them act like radio or checkboxes and automatically does the activating for you. This will not make code for a submittable form...so it's most useful when you have JavaScript doing an AJAX post, etc., to utilize the data and build your values. To get a list of the selected values, you can find the buttons with the active status like so:

var values = [];
$('#btn-group-instance').find('.active').each( function() {
    values.push( $(this).data('value') );
} );

In one case recently, I was able to just use the default functionality. However, like in this thread, I now have an instance where I must know the state of the button when it's clicked and act accordingly and had the trouble that my method was getting called before the toggle took place. So, I turned off the data-toggle to stop bootstrap from handling it and made my own method to do the same thing:

$('body').on('click','#btn-group-instance button', function() {
    $(this).toggleClass('active');
    // if active/else not active logic
} );
}
@ryepup

This comment has been minimized.

Show comment
Hide comment
@ryepup

ryepup Jun 26, 2013

Here's my use case: I have a survey with a bunch of btn-group data-toggle="buttons-radio" in Backbone views (Marionette ItemViews actually, but the point is I've got an abstraction layer). So I have QuestionView objects, which render out the bootstrap radio buttons.

I have a QuestionView.answer function that returns the selected value (essentially $('.active').val()), which is called by other code elsewhere to assemble a list of answers when the user hits a "submit" button at the bottom of the page.

I have other QuestionView subclasses for different types of survey questions, with different implementations of answer, which is a nice common interface to get the answer off any kind of question when the user hits "submit".

Now I want to also fire a custom event when the user changes the answer. For other QuestionView implementations, I can find up a DOM event, and then re-use my answer function to emit the event.

On button group radio buttons, if I bind the "click" event, my handler is firing before Bootstrap sets the active class, so my answer function isn't finding the right button.

I can work around this pretty easily (maybe a setTimeout something), but it'd be trivial for me if there was some kind of change event fired when the active class was ready to go. It's a little silly, but in my use case would make it easier.

ryepup commented Jun 26, 2013

Here's my use case: I have a survey with a bunch of btn-group data-toggle="buttons-radio" in Backbone views (Marionette ItemViews actually, but the point is I've got an abstraction layer). So I have QuestionView objects, which render out the bootstrap radio buttons.

I have a QuestionView.answer function that returns the selected value (essentially $('.active').val()), which is called by other code elsewhere to assemble a list of answers when the user hits a "submit" button at the bottom of the page.

I have other QuestionView subclasses for different types of survey questions, with different implementations of answer, which is a nice common interface to get the answer off any kind of question when the user hits "submit".

Now I want to also fire a custom event when the user changes the answer. For other QuestionView implementations, I can find up a DOM event, and then re-use my answer function to emit the event.

On button group radio buttons, if I bind the "click" event, my handler is firing before Bootstrap sets the active class, so my answer function isn't finding the right button.

I can work around this pretty easily (maybe a setTimeout something), but it'd be trivial for me if there was some kind of change event fired when the active class was ready to go. It's a little silly, but in my use case would make it easier.

@nzifnab

This comment has been minimized.

Show comment
Hide comment
@nzifnab

nzifnab Jun 26, 2013

@ryepup you pretty much need to re-write the buttons-radio group javascript yourself to toggle between options so that you have access to the value that was changed, because the bootstrap team appear to have a very different idea of how these should be used or something (and by that, I mean, they prefer the javascript functionality for them to be worthless).

This is the implementation I've been using (erm it's in coffeescript):

$('body').on "click", ".js-btn-group[data-toggle='buttons-radio'] .btn", (e) ->
  activateRadioButton($(this))

activateRadioButton = ($element) ->
  $element.parent('.js-btn-group').data('value', $element.data('value')).children('.btn').removeClass('active')
  $element.addClass('active')
  $element.trigger("myapp.selected")

Basically, when you toggle one of the elements, it sets a data-attribute on the group element data-value so that javascript elsewhere can know the exact value that was selected (this requires each button to also have a data-value attribute set). It then also fires a specific custom event on the clicked button so you can get a handle on both that and the selected value if you need to when those are pressed.

Here's that coffeescript converted to JS:

var activateRadioButton;
$('body').on("click", ".js-btn-group[data-toggle='buttons-radio'] .btn", function(e) {
  return activateRadioButton($(this));
});
activateRadioButton = function($element) {
  $element.parent('.js-btn-group').data('value', $element.data('value')).children('.btn').removeClass('active');
  $element.addClass('active');
  return $element.trigger("myapp.selected");
};

nzifnab commented Jun 26, 2013

@ryepup you pretty much need to re-write the buttons-radio group javascript yourself to toggle between options so that you have access to the value that was changed, because the bootstrap team appear to have a very different idea of how these should be used or something (and by that, I mean, they prefer the javascript functionality for them to be worthless).

This is the implementation I've been using (erm it's in coffeescript):

$('body').on "click", ".js-btn-group[data-toggle='buttons-radio'] .btn", (e) ->
  activateRadioButton($(this))

activateRadioButton = ($element) ->
  $element.parent('.js-btn-group').data('value', $element.data('value')).children('.btn').removeClass('active')
  $element.addClass('active')
  $element.trigger("myapp.selected")

Basically, when you toggle one of the elements, it sets a data-attribute on the group element data-value so that javascript elsewhere can know the exact value that was selected (this requires each button to also have a data-value attribute set). It then also fires a specific custom event on the clicked button so you can get a handle on both that and the selected value if you need to when those are pressed.

Here's that coffeescript converted to JS:

var activateRadioButton;
$('body').on("click", ".js-btn-group[data-toggle='buttons-radio'] .btn", function(e) {
  return activateRadioButton($(this));
});
activateRadioButton = function($element) {
  $element.parent('.js-btn-group').data('value', $element.data('value')).children('.btn').removeClass('active');
  $element.addClass('active');
  return $element.trigger("myapp.selected");
};
@jorgelbg

This comment has been minimized.

Show comment
Hide comment
@jorgelbg

jorgelbg Oct 31, 2013

Is this yet unresolved? or it will not be implemented? I think it would be nice to have an event that gets triggered after the button has been pressed. Right now I've solved this with:

        $('.calc').on('click', function(e) {
            e.stopImmediatePropagation()
            $(this).button('toggle');

            // do some stuff
            alert('click after toggle');
        })

jorgelbg commented Oct 31, 2013

Is this yet unresolved? or it will not be implemented? I think it would be nice to have an event that gets triggered after the button has been pressed. Right now I've solved this with:

        $('.calc').on('click', function(e) {
            e.stopImmediatePropagation()
            $(this).button('toggle');

            // do some stuff
            alert('click after toggle');
        })
@adamserafini

This comment has been minimized.

Show comment
Hide comment
@adamserafini

adamserafini Mar 27, 2014

+1. All these solutions feel like fragile 'workarounds' that might stop working depending on bootstrap implementation changing. An button event hook would be nice - in the same way that you can currently hook into modal events etc.

adamserafini commented Mar 27, 2014

+1. All these solutions feel like fragile 'workarounds' that might stop working depending on bootstrap implementation changing. An button event hook would be nice - in the same way that you can currently hook into modal events etc.

@frankydp

This comment has been minimized.

Show comment
Hide comment
@frankydp

frankydp Apr 8, 2014

+1 for hooks in the same vein as the modal. stopPropagation always has implementation issues.

frankydp commented Apr 8, 2014

+1 for hooks in the same vein as the modal. stopPropagation always has implementation issues.

@gschueler

This comment has been minimized.

Show comment
Hide comment
@gschueler

gschueler May 16, 2014

+1
For an active UI that needs to do something when you toggle a button, it would b really useful to have an event triggered by data-toggle="button".

gschueler commented May 16, 2014

+1
For an active UI that needs to do something when you toggle a button, it would b really useful to have an event triggered by data-toggle="button".

@puzumaki

This comment has been minimized.

Show comment
Hide comment
@puzumaki

puzumaki Aug 15, 2014

Bootstrap 3.x does not seem to have added a "hook" to the button clicked event (please point me to the documentation if that is incorrect). This is a feature that should be included. My use for the button group plugin is to perform an action on change, and I cannot use the script for that purpose as it is now. On change, I have a dialog box that is triggered to ask if the feature should be enabled/disabled. No submit action. Please consider adding this functionality, the working around this makes the button plugin useless for any of my purposes.

puzumaki commented Aug 15, 2014

Bootstrap 3.x does not seem to have added a "hook" to the button clicked event (please point me to the documentation if that is incorrect). This is a feature that should be included. My use for the button group plugin is to perform an action on change, and I cannot use the script for that purpose as it is now. On change, I have a dialog box that is triggered to ask if the feature should be enabled/disabled. No submit action. Please consider adding this functionality, the working around this makes the button plugin useless for any of my purposes.

@twbs twbs locked and limited conversation to collaborators Aug 15, 2014

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.