How work with $(target) inside directive? - javascript

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.

Related

Click outside div - when div has buttons that fires events - angularjs

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.)

jQuery how to bind event to element which is deleted and added again

I am using jQuery and a modal library named jQuery.Modal. I have a customize function to show this modal in order to fit the UI design:
show: function (option) {
var view = new Modal.View();
sdhapp.modalRegion.show(view);
$(".sdh-ui.modal-header").html(option.title);
$(".sdh-ui.modal-body").empty();
if (option.image) {
if (option.image === 'error') {
$(".sdh-ui.modal-body").append(
'<div class="image-box" style="background-image: url(images/error-icon.png)"></div>');
}
}
if (option.confirmButton === true) {
$(".sdh-ui.modal-footer").html(
'<button type="button" id="modal-confirm-button">Confirm</button><button type="button" id="modal-cancel-button">Cancel</button>');
}
$(".sdh-ui.modal-body").append(option.body);
$('#error-modal-form').modal({
showClose: false
});
bindModalEvents(option);
}
Where view is Marionette view that renders the modal body.
I am binding the click events:
$('#modal-cancel-button').on('click', function () {
unbindModalEvents();
$.modal.close();
if (option.cancel)
option.cancel.call();
});
$('#modal-confirm-button').on('click', function () {
unbindModalEvents();
$.modal.close();
if (option.confirm)
option.confirm.call();
});
$('#modal-ok-button').on('click', function () {
unbindModalEvents();
$.modal.close();
if (option.ok)
option.ok.call();
});
and unbind it:
var unbindModalEvents = function(){
console.log('called');
$('#modal-cancel-button').unbind('click');
$('#modal-confirm-button').unbind('click');
$('#modal-ok-button').unbind('click');
};
the problem is that the click event ( the close function ) is only triggered once. If I the modal is shown for a second time, the click events is not triggered
I tried to remove the element, using "bind" instead of "on" but nothing works
Any advice?
When you close the modal, it's deleted from the DOM. That means that any event handlers bound to will also disappear. Try this:
$('body').on('click', '#modal-cancel-button', function () {
unbindModalEvents();
$.modal.close();
if (option.cancel)
option.cancel.call();
});
$('body').on('click', '#modal-confirm-button', function () {
unbindModalEvents();
$.modal.close();
if (option.confirm)
option.confirm.call();
});
$('body').on('click', '#modal-ok-button', function () {
unbindModalEvents();
$.modal.close();
if (option.ok)
option.ok.call();
});
This binds the handler to the body, which then watches for events that originate from modals. You can add and create as many as you'd like and the handler won't get deleted.

React component onClick handler not working

I have a basic pop-up (an image) that appears when you click on a button. When you click outside of that pop-up, it should close but when you click inside of it, it should stay open and call the test function. Its initial state, isPopupVisible, is set to false. When you click on the button, I set the state to true which renders the pop-up window.
The problem is the test function isn't being called when you click on the image. I think it is because the state is set to false and the image element with the onClick function isn't initially rendered. Does anyone know how to solve this?
(written in ecmascript 6)
getInitialState () {
return {isPopupVisible: false}
},
onClick () {
if(!this.state.isPopupVisible){
this.setState({isPopupVisible: true});
document.body.addEventListener('click', this.onClickBody)
}
},
onClickBody () {
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', this.onClickBody)
},
test () {
console.log('the onClick from the image works!');
},
showPopup () {
if(this.state.isPopupVisible){
return <div>
<img src='img.png' onClick={this.test} />
</div>
}else{
return null;
}
},
render () {
return (
<span>
<button onClick={this.onClick}>
See Popup
</button>
{this.showPopup()}
</span>
);
}
This is a bit of a hack, but...
onClickBody () {
setTimeout(function(){
if (!this.clickedInBounds && this.isMounted()) {
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', this.onClickBody)
}
this.clickedInBounds = false;
}.bind(this), 0);
},
test () {
console.log('the onClick from the image works!');
this.clickedInBounds = true;
},
The setTimeout is just to make it run that code after test has a chance to run. isMounted is just for the small chance that the component was unmounted between the click on body, and the setTimeout coming around.
jsbin
Rather than adding a click listener on body, you can add an underlying transparent div before rendering the popup that'll close the popup when clicked.
For example, this is how this is implemented in react-bootstrap Modal component: https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Modal.jsx#L73
You can check out the demo here: http://react-bootstrap.github.io/components.html#modals
Your onClickBody handler is getting called before test is ever invoked, changing state and removing the image from the virtual DOM before the synthetic event ever fires.
There may be an elegant way around this, but because you are mixing native and React event APIs, my guess is that you'll have to change the signature of onClickBody to have the event as its first parameter and then wrap the body of the method in something like if (e.target.nodeName !== "IMG") {}.
JSFiddle showing this in action here: http://jsfiddle.net/reactjs/69z2wepo/
I know it has been a year for this Post but I know the right answer.
you have a lot of referencing dangers.
onClick () {
var self = this;
if(!this.state.isPopupVisible){
self .setState({isPopupVisible: true});
document.body.addEventListener('click', self .onClickBody)
}
},
onClickBody () {
var self = this;
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', self .onClickBody)
},
test () {
console.log('the onClick from the image works!');
},
showPopup () {
var self = this;
if(this.state.isPopupVisible){
return <div>
<img src='img.png' onClick={self.test} />
</div>
}else{
return null;
}
},
this should for sure solve your issue

Close div when click outside the div with Angular

Here is the thing with jquery when i want to close div when i click outside the div .
$(document).on("click", function (e) {
if (e.target.id != "user-login-top" && !$(e.target).closest("#user-login-wrapper").length) {
$("#user-login-wrapper").removeClass("wide");
}
});
what's the equal thing in angular ?
the Fiddle
many thanks
Through implementing a customized directive, clicking outside the top menu will hide the right sidebar.
Check out this: http://jsfiddle.net/zqdmny41/20/
app.controller('mainCtrl', function ($scope) {
// Add a function to hide the right sidebar.
$scope.hideSideMenu = function() {
$scope.bodyCon = false;
$scope.noneStyle = false;
}
...
})
.directive("outsideClick", ['$document', function( $document){
return {
link: function( $scope, $element, $attributes ){
var scopeExpression = $attributes.outsideClick,
onDocumentClick = function(event){
if(event.target.id != 'rightMenu' && event.target.id != 'toggle-menu') {
$scope.$apply(scopeExpression);
}
};
$document.on("click", onDocumentClick);
$element.on('$destroy', function() {
$document.off("click", onDocumentClick);
});
}
}
}]);
In your HTML:
<aside class="rightbar" id="rightMenu" ng-class="{'noneStyle' : noneStyle}" outside-click="hideSideMenu()">
The effect is:
clicking on the top icon will toggle the right sidebar,
clicking on the right sidebar itself will trigger nothing,
and clicking on somewhere else will hide the right sidebar.
The basic logic is:
Create a customized directive named "outsideClick" and apply it to the right sidebar menu by adding an attribute outside-click="hideSideMenu()"
The function name hideSideMenu is passed in to the directive, which adds an click event to the document.
When user clicks to the document, if the target id is not rightMenu and not toggle-menu, the right sidebar will be hidden.
Reference: http://vadimpopa.com/onblur-like-for-a-div-in-angularjs-to-close-a-popup/

Prevent click after focus event

When user clicks on input field, two consecutive events are being executed: focus and click.
focus always gets executed first and shows the notice. But click which runs immediately after focus hides the notice. I only have this problem when input field is not focused and both events get executed consecutively.
I'm looking for the clean solution which can help me to implement such functionality (without any timeouts or weird hacks).
HTML:
<label for="example">Example input: </label>
<input type="text" id="example" name="example" />
<p id="notice" class="hide">This text could show when focus, hide when blur and toggle show/hide when click.</p>
JavaScript:
$('#example').on('focus', _onFocus)
.on('blur', _onBlur)
.on('click', _onClick);
function _onFocus(e) {
console.log('focus');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
$('#notice').removeClass('hide');
}
function _onClick(e) {
console.log('click');
$('#notice').toggleClass('hide');
}
function _onBlur(e) {
console.log('blur');
$('#notice').addClass('hide');
}
UPDATED Fiddle is here:
I think you jumbled up the toggles. No need to prevent propagation and all that. Just check if the notice is already visible when click fires.
Demo: http://jsfiddle.net/3Bev4/13/
Code:
var $notice = $('#notice'); // cache the notice
function _onFocus(e) {
console.log('focus');
$notice.removeClass('hide'); // on focus show it
}
function _onClick(e) {
console.log('click');
if ($notice.is('hidden')) { // on click check if already visible
$notice.removeClass('hide'); // if not then show it
}
}
function _onBlur(e) {
console.log('blur');
$notice.addClass('hide'); // on blur hide it
}
Hope that helps.
Update: based on OP's clarification on click toggling:
Just cache the focus event in a state variable and then based on the state either show the notice or toggle the class.
Demo 2: http://jsfiddle.net/3Bev4/19/
Updated code:
var $notice = $('#notice'), isfocus = false;
function _onFocus(e) {
isFocus = true; // cache the state of focus
$notice.removeClass('hide');
}
function _onClick(e) {
if (isFocus) { // if focus was fired, show/hide based on visibility
if ($notice.is('hidden')) { $notice.removeClass('hide'); }
isFocus = false; // reset the cached state for future
} else {
$notice.toggleClass('hide'); // toggle if there is only click while focussed
}
}
Update 2: based on OP's observation on first click after tab focus:
On second thought, can you just bind the mousedown or mouseup instead of click? That will not fire the focus.
Demo 3: http://jsfiddle.net/3Bev4/24/
Updated code:
$('#example').on('focus', _onFocus)
.on('blur', _onBlur)
.on('mousedown', _onClick);
var $notice = $('#notice');
function _onFocus(e) { $notice.removeClass('hide'); }
function _onClick(e) { $notice.toggleClass('hide'); }
function _onBlur(e) { $notice.addClass('hide'); }
Does that work for you?
Setting a variable for "focus" seems to do the trick : http://jsfiddle.net/3Bev4/9/
Javascript:
$('#example').on('focus', _onFocus)
.on('click', _onClick)
.on('blur', _onBlur);
focus = false;
function _onFocus(e) {
console.log('focus');
$('#notice').removeClass('hide');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
focus = true;
}
function _onClick(e) {
console.log('click');
if (!focus) {
$('#notice').toggleClass('hide');
} else {
focus = false;
}
}
function _onBlur(e) {
console.log('blur');
$('#notice').addClass('hide');
}
If you want to hide the notice onBlur, surely it needs to be:
function _onBlur(e) {
console.log('blur');
$('#notice').addClass('hide'); // Add the hidden class, not remove it
}
When doing this in the fiddle, it seemed to fix it.
The code you have written is correct, except that you have to replae $('#notice').removeClass('hide'); with $('#notice').addClass('hide');
Because onBlur you want to hide so add hide class, instead you are removing the "hide" calss.
I hope this is what the mistake you have done.
Correct if I am wrong, Because I don't know JQuery much, I just know JavaScript.
you can use many jQuery methods rather than add or move class:
Update: add a params to deal with the click function
http://jsfiddle.net/3Bev4/23/
var showNotice = false;
$('#example').focus(function(){
$('#notice').show();
showNotice = true;
}).click(function(){
if(showNotice){
$('#notice').show();
showNotice = false;
}else{
showNotice = true;
$('#notice').hide();
}
}).blur(function(){
$('#notice').hide();
});

Categories