Marionette ItemView event handling conflict with Bootstrap dropdown - javascript

I have a Composite view creating a table, with the childView collection displaying each row. At the end of each row is a Bootstrap based button dropdown.
Clicking on the row will open a modal (working), but the event is still fired when clicking on the button dropdown, so the modal shows AND the dropdown shows underneath.
How do I prevent the clicks on the button from propagating down to the row click event, without preventing the Bootstrap hooks from also getting cut?
The standard method for handling these types of special click events (in my research and book reading), uses e.stopPropagation() on the jquery event that comes back, to prevent the event from going further, but this blocks the Bootstrap dropdown open event from triggering.
If I still call stopPropagation() when the buttongroup is clicked, then use the event object's currentTarget object and toggle the class manually, I can get the dropdown to open without conflict, but since the close button triggers are 4 layers down, I would have brute force & call .parent() 4x times to toggle the class, and that doesn't sound very sustainable or portable.
How can I have a row-click action AND open and close the dropdown using Bootstrap's built-in toggle?
ItemView
Show.Result = Marionette.ItemView.extend({
tagName:"tr",
template: "#row-template",
events:{
"click .js-delete-result": "editRow",
"click .js-edit-result": "deleteRow",
"click div.btn-group button": "openSettings"
"click": "rowClicked",
},
editRow: function(e){e.stopPropagation();alert("you trying to delete bro?!")},
deleteRow: function(e){e.stopPropagation();alert("you trying to edit bro?!")},
openSettings: function(e){
//e.stopPropagation()
//$(e.currentTarget).parent().toggleClass('open')
//e.currentTarget.button('toggle');
},
rowClicked:function(e){
e.preventDefault()
this.trigger("show:result",this)
},
}
Template
<script id="row-template">
<td class="vert-align"><%= value %></td>
<td class="vert-align"><%= notes %></td>
<td class="vert-align">
<div class="btn-group">
<button class="btn btn-xs btn-default dropdown-toggle" aria-haspopup="true" data-toggle="dropdown" aria-expanded="false">
<span class="glyphicon glyphicon-edit"/>
</button>
<ul class="dropdown-menu" role="menu">
<li>Edit</li>
<li> Delete <i class= "glyphicon glyphicon-trash"/></li>
</ul>
</div>
</td>
</script>
Random Idea: Maybe I'm thinking of the problem the wrong way, and should instead find a better CSS / jQuery selector that would cover all but the button-group; Is there a 'not' selector with CSS?
EDIT:
I tried changing the "click": "rowClicked", generic click handler to use the 'not' css selector; "click :not(div.btn-group *)": "rowClicked", but it did not have any effect; it still opens the model when the button is clicked. AFter more research, I realize that my not selector is not formatted / used correctly, but that might be largly because I'm trying to use it in a way it's not made for.
EDIT 2:
I was able to finally prevent the propagation (sort of), but in the process discovered that TWO separate clicks were being generated. I don't know why. It's because the event propagation is never stopped, and bubbles down to the root HTML element, in this case the TR.
Changing the generic click function to check what the target of the click is, and whether it's ancestors contain the .btn-group class, every time you click, but only on the button itself; if you click on just the row then it only fires once.
How can I prevent this propagation / set event listeners on the correct items?
rowClicked:function(e){
e.preventDefault()
console.log("I get called twice?!")
if($(e.target).parents('.btn-group').length > 0){
//do nothing
}
else{
e.stopPropagation()
this.trigger("show:result",this)
}
},

Just an idea, have not tested. What id you stop propagation at the button's parent? e.g. click div.btn-group ? It shouldn't interfere with default bootstrap behaviour yet it stops it from bubbling to the row itself.

try this;
rowClicked:function(e){
if($(e.currentTarget).is('td')){ //or e.target will also do
this.trigger("show:result",this);
}
},

After a ton of googling and testing my code, I think this may be the best approach for now. It does not really address the core of my question, but since a generic click event handler was inserting itself before bootstrap could get to it, changing the focus of the click to something more specific seems to have done the trick.
Thanks to #Pawan and #squall3d for some pointers in this direction, but I am still looking for an answer on how to better propagate / setup / control event listeners to prevent conflicts with other libraries in the future.
However, to get it working for now, I changed the generic click to "click td": "rowClicked". td is almost the base layer, but specific enough so it won't interfere with the button events. I no longer needed the button click handler, (we want bootstrap to do it), so that was removed: "click div.btn-group button": "openSettings"
Show.Result = Marionette.ItemView.extend({
tagName:"tr",
template: "#row-template",
events:{
"click .js-delete-result": "editRow",
"click .js-edit-result": "deleteRow",
"click td": "rowClicked",
},
editRow: function(e){e.stopPropagation();alert("you trying to delete bro?!")},
deleteRow: function(e){e.stopPropagation();alert("you trying to edit bro?!")},
rowClicked:function(e){
e.preventDefault()
this.trigger("show:result",this)
},
}
It is worth noting that I did add additional code to the rowClicked function, to prevent opening the modal if the button dropdown was being shown and you clicked on a row accidently when clicking outside the dropdown to close it. I found this to be a better user experience.
rowClicked:function(e){
if($("#edit-btn").hasClass("open")){
//also do nothing - the dropdown is open
}
else{
e.stopPropagation()
e.preventDefault()
this.trigger("show:result",this)
}
}
and added an id to the btn-group for jquery selection
<div class="btn-group" id="edit-btn">
this works because Bootstrap adds an .open css class to trigger showing the dropdown html.

Related

Why can't we bind functions to :disabled input fields?

When i try to bind JavaScript/jQuery .hover or .click functions to an :disabled input field, none of the functions gets fired.
See the snippet below.
$(document).ready(function() {
var button_1 = $("#button_1");
button_1.hover( function() {
console.log("Hover event button 1");
});
button_1.click( function() {
console.log("Click event button 1");
});
});
function button_2_click() {
console.log("Click even button 3");
}
function button_2_hover() {
console.log("Hover even button 3");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Hover over one of the buttons, and you'll see none of the console messages gets logged. When removing the `disabled` property, all events are fired.</p>
<button id="button_1" disabled>Disabled 1</button>
<button id="button_2" onmouseover="button_2_hover();" onclick="button_2_click();" disabled>Disabled 2</button>
MDN says the following about the disabled property:
Indicates whether the element is disabled or not. If this attribute is
set to true the element is disabled. Disabled elements are usually
drawn with grayed-out text. If the element is disabled, it does not
respond to user actions, it cannot be focused, and the command event
will not fire.
What i tried to archieve is, when a user :hover's over an button:disabled, a message would be shown.
I know i could use a title="text" for that. However, thats not the case. I use an small popup at the right top corner with an message. The message is stored inside an javascript object.
My question:
How can i make an workaroud for this? Or is it just not possible to bind .hover() or onmousemove functions the disabled input fields?
The click part is per specification:
A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.
It would appear that the user agents you're testing in take it further and also prevent the events related to hovering (Chrome and IE seem to, for instance). Other user agents may not (Firefox allows the events related to hover, for instance.)
If you want those events, you could leave the buttons enabled and then style them to look disabled and prevent clicking them doing what they otherwise do, although it may make for a confusing user experience.
Alternately, you could put a span inside the button, and hook the events on that:
$(document).ready(function() {
var button_1 = $("#button_1 span");
button_1.hover( function() {
console.log("Hover event button 1");
});
button_1.click( function() {
console.log("Click event button 1");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Hover over one of the buttons, and you'll see none of the console messages gets logged. When removing the `disabled` property, all events are fired.</p>
<button id="button_1" disabled><span>Disabled 1</span></button>
There are parts of the button that won't trigger the events (bits of the surround), though you could probably fix that with CSS...

FullCalendar eventClick from outside working with problems

Hi I have this current JS for fullCalendar (v 1.6.4):
eventRender: function(event, element, view) {
element.find('.fc-event-inner').attr("id", "event-" + event.id_event);
},
eventClick: function(calEvent, jsEvent, view) {
alert("Event clicked: "+calEvent);
}
This works ok if I click on an event.
I than have this button:
$('#ext-click').click(function(){
$('#event-120.fc-event-inner').trigger('click');
})
(Note: #event-120 is a correct id for the event)
Now, when clicking on #ext-event button nothing happens, BUT, if I click on the event-120 I get the alert, I close the alert and I press #ext-event this time the alert appears.
So #ext-event works only if I first click on event-120, otherwise nothing happens.
Expectancy:
When clicking on #ext-event I should get the event-120 alert every time.
Try this fiddle: https://jsfiddle.net/hs0q6r3v/1/
You will see by clicking the "Click-me" button as first thing, nothing happens. Click then on the event, you'll get an alert, close it and click on the "Click-me" button again, this time it works.
I'd recommend to rethink this user experience approach by triggering a click by button. What's the reason for that? If you want to do a select then use the fullcalendar select method, etc.
Why it's probably not working:
A fullcalendar event is different from a with the id of a fullcalendar id. That might be the reason why the order of clicking is important cause there is a listener generated for this div showing this event. But you can't rely on that the internal Id of an event will always be reflected in the DOM.
e.g. In the latest fullcalendar release the fullcalendar event_id is not used for the id of the related
Approaches for dealing with page change:
I use both approaches in my applications
New Page
calendarview has optional parameter
pass "selected_id" to the new page
when leaving "new_page" pass back "selected_id"
when calendarview receives a "selected_id" deal with it in the "eventRender" method (e.g. special styling for highlighting, ...)
Modal Dialog
if convenient you can also use a modal dialog, so the calendarview will stay the same.

jQuery Click Event Triggering On Multiple Elements (I only want 1)

When my overlay comes up, everything works well, but I added some code to close out the overlay, but this code gets triggered even when I'm just clicking my arrows. The following is the code that's being triggered, which is fine when I'm not clicking the arrows to change the image. But when I click the arrows, the background which is the overlay is also being trigger, so the image is changing but the overlay is also hiding.
$('#overlay').click(function() {
$(this).fadeOut('slow');
});
How can I be able to use the arrows without it also clicking on the background overlay? If you open up the project, you will see what I'm saying.
To open the project:
https://htmlpreview.github.io/?https://github.com/rodriguesandrewb/photo_gallery_v1/blob/master/index.html
To open the repository:
https://github.com/rodriguesandrewb/photo_gallery_v1
You want to use event.stopPropagation(): https://api.jquery.com/event.stoppropagation/
This prevents the event from bubbling (being triggered by other elements)
Your outter most element is #overlay. It means that no matter where you click you'll be always clicking on your #overlay element. That is way your callback is being always triggered and closing your image.
To fix your problem and make your image close only when clicking on it you could use:
$('#changeImage').click(function() {
$(this).closest('#overlay').fadeOut('slow');
});
Ok, there's a ton of code to sort out, so I'm guessing your overlay is
<div id="overlay" style="display: block;"></div>
and your event.target is deep down inside this:
<div class="mainCenter">
<div class="container">
<div id="topFixed">
<input type="text" id="search" placeholder="Search">
</div>
<ul id="gallery">
.......
I'm not 100% sure where your event.target is, (the element you want to click and not everything else). But it's safe to assume that after you click your intended button, the event continues to bubble up the event chain. The event chain is basically your event.target's ancestors which includes#overlay` which is at the very top of the event chain.
To prevent event bubbling (btw bubbling is the default behavior but in instances such as your's it's not desired.) try placing stopPropagation() after or inside at the end of your event handler.
I wish I could be more specific as to where and how to apply this code as it pertains to your source, but you didn't provide the specific areas that concern your eventListeners, eventHandlers, etc...
The #overlay is used in this example but I suggest you use the event.target parent instead. The purpose of this code is to accept an event like 'click' on an element (i.e. button) or multiple elements (i.e. buttons) through their mutually shared parent. That's one place to click for potentially several different buttons. At first you'd think that's non-sense and you'd say, "Sure that button is clicked because the parent was clicked, but now everything the parent is chained to will trigger everything else."
That would be correct except we have stopPropagation(); at the very end of your eventHandler. That will stop propagation of the event bubbling back up the event chain, so there's no more rogue triggers lighting up everywhere. Rogue Triggers® sounds like a great band name. :P
For details and a much better explanation: http://www.kirupa.com/html5/handling_events_for_many_elements.htm
var overlay = document.querySelector("#overlay");
theParent.addEventListener("click", doSomething, false);
function doSomething(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
}
e.stopPropagation();
}

How to close Bootstrap popovers (or any item in general) when a user clicks away from it?

I'm using the popover object from Twitter's Bootstrap library in manual mode and I was wondering how I should go about closing the tooltip when the user clicks away from it.
Here is my HTML:
<a id="stats-bar" rel="popover" data-placement="top" data-trigger="manual" data-title="Title here" data-content="Hello everyone.">Test</a>
and my JavaScript:
$('#stats-bar').click(function(e) {
$(this).popover('show');
});
How can I hide the popover when the user clicks anywhere but the popover itself? I thought of using a fixed transparent div behind the popover and set its click event but I'm not sure that's the best way.
I ended up wiring up to the document click event and hide any tooltips at that point
$(document).click(function (e)
{
// check the parents to see if we are inside of a tool tip. If the click came
// from outside the tooltip, then hide our tooltip
if ($(e.target).parents('.tooltip').length == 0) $('[data-original-title]').tooltip('hide');
});
If you are using a manual trigger option and wiring up to the click event to show the tooltip you will need to call e.stopPropagation() to prevent the document click event from firing when showing the tooltip.
I opted to use a fixed transparent div behind the popover and set its click event as this seems like the most robust and simple solution.
What about an generic click event listener that triggers where ever you click within the body? Check out this post, it looks similar to what you're trying to achieve.
https://stackoverflow.com/a/2125122/1013422
You would use this to close the actual popover
$('#stats-bar').popover('hide')
I would only define the function when the popup event is run and then remove it once you've closed the popup, that way you're not constantly listening for click events.

How do you exclude a page element from triggering a jQuery blur event?

I have an <input> that when focused on it shows a suggest drop down. When anything else is clicked the suggestions disappear. The problem is that I cannot seem to figure out to make it so that when the suggest <div> is clicked the blur event does not run.
Heres some of the HTML:
<label id="testTagsLabel">Tags:</label>
<input type="text" name="tags" id="testTags" placeholder="Ex: Poem, Edgar Allen Poe">
<div id="tagSuggest">
<ul>
<li class="tagSuggestTag">testtag</li>
<li class="tagSuggestTag">testtag2</li>
<li class="tagSuggestTag">testtag3</li>
<li class="tagSuggestTag">testtag4</li>
</ul>
</div>
Heres some of the JavaScript:
$('#testTags').focus(
function(){
$('#tagSuggest').show();
});
$('#testTags').blur(
function(){
$('#tagSuggest').hide();
});
Try something like:
$("#yourinput").blur(function(e) {
if(!$(e.target).is(".suggestDiv")) {
// close the suggest div
}
});
UPDATE: (oops, the code above doesn't work as I thought it would)
This should work:
$(document).click(function(e) {
if (!$(e.target).is("#suggest")) {
$("#suggest").hide();
}
});
Demo: http://jsfiddle.net/PNVCL/
UPDATE2:
I forgot that you still need blur, because you probably want to hide the suggest div when you switch into another input by hitting tab. Here's an updated demo: http://jsfiddle.net/PNVCL/1/
Clicking anywhere still closes the suggest div (except on the suggest div itself or the input) as well as hitting tab to switch to another input. Still needs improvements, but you should be able to pick up from here.
You should not use the blur event because it's impossible to make the difference between a blur caused by a click on the suggest box and another blur (tab, window blur, right click, ...).
A workaround given by #dakis is to use the click event on the document but the suggest box to close the box. I suggest to dynamically add and remove the document click handler to avoid overhead, and to allow the user to click in the field without closing the box.
Demo here: http://jsfiddle.net/fvwPn/
In addition I made the box to close when TAB is pressed. I also added a dirty hack version (commented) which uses the blur event and a big hack using a timeout (since the two events are fired independently, the delay depends on the client browser and speed... yep it's a dirty hack).

Categories

Resources