I'm using the following directive to detect when a click is made outside a div:
app.directive('clickOut', function ($window, $parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var clickOutHandler = $parse(attrs.clickOut);
angular.element($window).on('click', function (event) {
if (element[0].contains(event.target)) return;
clickOutHandler(scope, {$event: event});
scope.$apply();
});
}
};
});
In this div:
<div class="panel-body" click-out="closeMyPopup()">
<div class="row clearfix">
<div class="col-md-12">
<div class="form-inline pull-right">
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement()">Cancel</button>
<button type="submit" class="form-control btn" ng-click="onSaveAnnouncement()">Save</button>
</div>
</div>
</div>
</div>
It works well, when you click outside the div, the function closeMyPopup() is triggered, the issue is that the div has buttons that triggers other functions. By some reason when other function is called, (like when the buttons are clicked) the event click outside is triggered calling the closeMyPopup(), the buttons are inside the div so the event click outside should not be called. There's another directive that I can use, that has the correct behavior and not trigger the click outside when you fire another function? Or how can I workaround this?
I also use this other directive, with the same issue:
app.directive("outsideClick", ['$document', '$parse', function ($document, $parse) {
return {
link: function ($scope, $element, $attributes) {
var scopeExpression = $attributes.outsideClick,
onDocumentClick = function (event) {
var isChild = $element.find(event.target).length > 0;
if (!isChild) {
$scope.$apply(scopeExpression);
}
};
$document.on("click", onDocumentClick);
$element.on('$destroy', function () {
$document.off("click", onDocumentClick);
});
}
}
}]);
Its because the event is being propagated to Window object.
- Window
- document
- dialog
- button
In the above hierarchy, if a click event happens on the last button element, the event will be propagated to the parent element until it reaches Window and then will close your dialog.
Solution 1:
Stop event propagation in each controller function by passing the event as a parameter and calling event.stopPropagation() function:
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement($event)">Cancel</button>
...
$scope.onCancelAnnouncement($event) {
$event.stopPropagation();
}
Solution 2:
Let the event be propagated and check the target element:
angular.element($window).on('click', function (event) {
var target = $(event.target);
if(target.attr("id") === "anyid") { // or target.hasClass("someclass")
// or target.closest(".some-selector")
// just ignore
} else {
// Do whatever you need
}
});
Exactly: events will be presented to every object in the nested DOM-hierarchy unless and until you stop their propagation. This, of course, is by design: JavaScript doesn't assume that "the innermost guy I can find who's listening for this event" is the only guy who might be interested in it. Everyone who says he's listening for it, who is in the position to hear it, is going to hear it, each in their turn ... unless one of them explicitly quashes further propagation, at which JS will stop looking for anyone else to send it to. (No one has to "send the event to the outer container." Instead, they only have to tell JS not to send it on.)
Related
I'm trying to rewrite my functional code to module pattern js, and I have this issue - When I try to delete input field which is dynamically created, I use jQuery $(this) to access dom element and delete its parent 'div'. But this refers to the Modal object, not the component I clicked. How to solve it, without making some field counter and creating fields with unique ID's, then catching ids on click and deleting that input field?
My modal:
var s,
Modal = {
settings: {
addInputBtn: $("#add-input"),
inputContainer: $("#modal-input-form"),
checkBoxesList: $(".check-box"),
inputFieldsList: $(".form-control"),
inputFieldsOptionalList: $(".optional-modal"),
inputHtml: `
<div class="input-group mb-3 optional-modal">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" class="check-box">
</div>
</div>
<input type="text" class="form-control">
<button type="button" class="close">
<span>×</span>
</button>
</div>`
},
init: function () {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function () {
s.addInputBtn.on("click", () => Modal.addInput());
s.inputContainer.on("click", ".close", () => Modal.deleteInput());
},
addInput: function () {
s.inputContainer.append(s.inputHtml);
},
deleteInput: function () {);
$(this).parent('div').remove();
}
}
Modal.init();
deleteInput: function (e) {);
$(e.target).parent('div').remove();
}
Event handlers are passed an event argument. Among its useful properties is target, which is the html element that the event originated on. Note that it could be different from the this of the event handler, which is the element that the event handler is attached to, as events bubble up through the DOM. So if you have this html:
<div id="parent">
<div id="child"></div>
</div>
then for this JS function:
$("#parent").on('click', function(e){
//this will equal <div id="parent"> unless you bind the handler to something else
//e.target will equal the clicked element:
//if the user clicked the child, it will equal the child;
//if the user clicked parent directly, it will equal parent.
$(e.target).closest('#parent').doSomething();
});
Did you try with:
deleteInput: function () {;
$(this.settings).parent('div').remove();
}
Also you have a typo at deleteInput: function () { ); ... the rest of the code, delete that extra ) after function. I suggest you trying $(this.settings).. because, this gets the Modal, and then you need to access the other object inside that object, which is settings. So your modal object, consists of other object, and I'm thinking this way you should be able to get it :)
I have got a button wrapped inside a div.
The problem is that if I click the button, somehow the click function is triggered from the div instead of the button.
Thats the function I have for the click event:
$('#ButtonDiv').on('click', '.Line1', function () {
var myVariable = this.id;
}
Thats my HTML (after is is created dynamically!!):
<div id="ButtonDiv">
<div class="Line1" id="Line1Software">
<button class="Line1" id="Software">Test</button>
</div>
</div>
So now myVariable from the click function is 'Line1Software' because the event is fired from the div instead of the button.
My click function hast to look like this because I am creating buttons dynamically.
Edit:
This is how I create my buttons and wrapp them inside the div
var c = $("<div class='Line1' id='Line1Software'</div>");
$("#ButtonDiv").append(c);
var r = $("<button class='waves-effect waves-light btn-large btnSearch Line1' id='Software' draggable='true'>Software</button>");
$("#Line1Software").append(r);
You code with the example html actually fires twice, once for each element since the event will bubble up and match both elements (since they are .Line1)
If you are trying to add an event listener to the button you should probably be using $('#Software') instead of $('#ButtonDiv')
The real problem is that neither the div nor the button have an id.
You code with the example html actually fires twice, once for each element since the event will bubble up and match both elements (since they are .Line1)
If you only want it to match the innermost element, then use return false to stop the bubbling.
$('#ButtonDiv').on('click', '.Line1', function () {
var myVariable = this.id;
console.log(myVariable);
return false;
});
var c = $("<div class='Line1' id='Line1Software'></div>");
$("#ButtonDiv").append(c);
var r = $("<button class='waves-effect waves-light btn-large btnSearch Line1' id='Software' draggable='true'>Software</button>");
$("#Line1Software").append(r);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="ButtonDiv">
</div>
Your question is a bit odd because you give yourself the answer... Look at your code, you are explicitly using event delegation:
$('#ButtonDiv').on('click', '.Line1', function () {
var myVariable = this.id;
});
This code means that, for each click on a .Line1 element, the event will be delegated to the #ButtonDiv element (thanks to bubbling).
If you do not want this behavior, just do that:
$('.Line1').on('click', function () {
var myVariable = this.id;
});
This is also correct:
$('.Line1').click(function () {
var myVariable = this.id;
});
Given some simple HTML such this, where one element has an onclick function and it's child also has an onclick function:
<div style='background-color:blue;width:500px;height:500px;'
onclick='thing1();'>
<div style='background-color:red;width:100px;height:100px;'
onclick='thing2(57);'></div>
</div>
What would be the correct approach so that when a user clicks the child element, only the child's onclick is executed and not the parent's, but when the parent is clicked, it's onclick is still executed? I see that event.stopPropagation() would be the correct way to go, but since I'm passing an argument to the function thing2(), I can't seem to pass the event as well. For example:
function thing2(a,ev) {
// Do something with a
ev.stopPropagation();
}
Doesn't work, failing with the error TypeError: ev is undefined.
JQuery is fine.
The event is the first param.
function thing2(ev) {
var a = ev.target
ev.stopPropagation()
}
Secondly, it's best not to use onclick=. Instead, give your div classes or ids and do something like this:
<div class="thing-1" data-thingy="57">
<div class="thing-2" data-thingy="65"></div>
</div>
<script>
$('.thing-1').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 57
})
$('.thing-2').click(function (ev) {
ev.stopPropagation()
parseInt($(ev.target).data('thingy'), 10) // 65
})
</script>
When you call a function on click, no event will be passed as argument and it somehow you can do that, that is not a Jquery object and that will not have stopPropagation property. SO you need to define jQuery click event handler for both divs, let's give them ids div1 and div2.
HTML
<div id="div1" style='background-color:blue;width:500px;height:500px;'>
<div id="div2" style='background-color:red;width:100px;height:100px;'></div>
</div>
In Javascript,
function thing2(ev) {
// Do something with a
console.log(ev);
alert('hi2');
ev.stopPropagation();
}
function thing1(ev) {
// Do something with a
alert('hi1');
}
$('#div1').click(thing1);
$('#div2').click(thing2);
I build a directive for time selection with two blocks. Problem is to catch target event on some blocks inside directive template.
Directive template:
<div class='time-picker-container'>
<div class='block1' ng-click='show()'>1</div>
<div class='block2' ng-show='selectVisible'>2</div>
</div>
JS:
scope.selectVisible = false;
scope.show = function() {
scope.selectVisible = scope.selectVisible ? false : true;
}
$rootScope.$on('documentClicked', function(inner, target) {
//try to hide div.block2 if user make click outside block.
});
Basic idea: when user make click on document, outside div.block2, block disappear. When user click somewere inside div.block2 block stay visible.
On run function:
angular.element(document).on('click', function(e) {
$rootScope.$broadcast('documentClicked', angular.element(e.target));
});
In your template, add $event as an argument to the ng-click handler function.
<div class='time-picker-container'>
<div class='block1' ng-click='show($event)'>1</div>
<div class='block2' ng-show='selectVisible'>2</div>
</div>
Then in your ng-click handler use stopPropagation() to prevent the outsideClickHandler from getting called.
angular.module("myApp").controller("myVm", function($scope, $document) {
var vm = $scope;
vm.selectVisible = false;
vm.show = function(event) {
console.log("inside click");
event.stopPropagation();
vm.selectVisible = vm.selectVisible ? false : true;
}
function outsideClickHandler(e) {
console.log("outside click");
$scope.$apply("selectVisible = false");
}
$document.on("click", outsideClickHandler);
$scope.$on("destroy", function() {
$document.off("click", outsideClickHandler)
})
})
To prevent memory leaks, be sure to remove the $document click handler when the scope is destroyed.
The DEMO on JSFiddle.
I'm having trouble binding an event handler on a dynamically-generated element in a Chaplin view, and I can't figure out what's going on. This seems like the most explicit possible implementation:
var MyView = Chaplin.View.extend({
/* using jQuery to bind the event because Chaplin.View.listen
and Chaplin.View.delegate aren't working... */
render: function() {
Chaplin.View.prototype.render.apply(this, arguments);
this.$('#the-button').click(function() { console.log('clicked'); });
console.log('breakpoint here');
}
});
In Chrome Dev Tools:
> this.$('#the-button').attr('unique-attr', 'blah');
< [ <button id="the-button" unique-attr="blah">Text</button>]
> this.$('#the-button').click()
clicked
< [ <button id="the-button" unique-attr="blah">Text</button>]
Unpause the application, make sure we're looking at the same element:
> $('#the-button')
< [ <button id="the-button" unique-attr="blah">Text</button>]
> $('#the-button').click()
[ no output ]
< [ <button id="the-button" unique-attr="blah">Text</button>]
Can anybody please explain why the onclick event handler for the button is being triggered in the scope of the "render" function but not being triggered in the global scope? Thanks.
Solved it.
I was referring to MyView.$el.html() in a parent view. That returns the innerHTML of the element. But the event handler was binding to the root of $el, which was no longer being rendered into the document, thus no event handlers were being called.