Custom directive to detect out side the button click - angularjs

I need to write a custom directive to close (or hide) the button when click out side the button (empty area of the DOM). In other words anywhere except within the button ? This behaviour should be applied only for this button when I apply the custom directive.Any guide would be highly appreciated.
<button class="btn btn-primary" ng-click="CreateUpdate()">Submit</button>
app.directive('MyDirective', function() {
//content
});

Within the button directive you can inject $document. Then wire up on click on the document.
<button class="myButton btn btn-primary" ng--lick="CreateUpdate()">Submit</button>
mainModule.directive("myButton", ['$document',
function ($document) {
return {
restrict: "E",
link: function (scope, element, attrs) {
$document.bind('click', function(event){
//Get the element clicked
var clickedElement = angular.element(event.target);
// If the clickedElement is not same as button (say, the clicked element class is different from button class) then close the button.
}
}
})

Related

Why does my Angular directive behave like this?

I want to implement a directive that changes some HTML colors in a view when a certain {{valueFromServer}} changes. Currently, I've tried the following:
View:
<span class="glyphicon glyphicon-thumbs-up voteColorer" ng-click="upvote(post)" votes="{{valueFromServer}}"></span>
<span class="glyphicon glyphicon-thumbs-down voteColorer" ng-click="downvote(post)" votes="{{valueFromServer}}"></span>
Directive:
app.directive('voteColorer', function () {
return {
restrict : 'C',
link: function(scope, elem, attrs) {
elem.bind("click" , function(e){
attrs.$observe('votes', function() {
console.log(elem); // for testing purposes
// would like to do color change operations here
});
});
}
}
});
When I click either of the glyphicon spans and valueFromServer changes I'd expect the directive to log into the console the span element I just clicked.
What it actually does is it first logs the span I click. On my second click it logs the span I clicked AND the span I clicked before that. On my third click it logs the span I just clicked, the span I clicked before that and the span I clicked before that etc. So on e.g. my fifth click the directive prints five console.log elements.
Why does it do this and how do I get rid of this behavior?

How to use a different combination of triggers for uib-popover?

The official documentation at :https://angular-ui.github.io/bootstrap/#/popover says that the following trigger combos can be passed as param to the popover-trigger attribute :
mouseenter: mouseleave
click: click
outsideClick: outsideClick
focus: blur
none
I want to use a combination of
mouseenter: outsideClick
How to achieve this without using the popover-is-open attribute?
You can't, the docs state
The outsideClick trigger will cause the popover to toggle on click, and hide when anything else is clicked.
"anything else" includes the element itself, so toggeling the element using outsideClick on or off and will interfere with the natural behavior of other triggers.
for example if state your triggers like so popover-trigger="mouseleave outsideClick"
, the trigger mouseleave will hide the popover instead of showing it if you have already clicked the element, otherwise it will just show it on leave. (plunk).
If you can hack it using popover-is-open then continue doing so, if it bothers you too much you can always request a feature.
popover-trigger="mouseenter outsideClick" for the uib-popover directive does not seem to work as one would think.
Initially, I thought it meant the following:
On mouse enter show the popover
On mouse leave hide the popover
On click keep popover open in an active state
On outside click close popover if it is in an active state
Since it does not I needed a manual approach, the following is stated in the documentation.
For any non-supported value, the trigger will be used to both show and hide the popover. Using the 'none' trigger will disable the internal trigger(s), one can then use the popover-is-open attribute exclusively to show and hide the popover.
So I created some HTML like:
<span class="glyphicon glyphicon-info-sign"
ng-class="{'text-primary' : isInfoPopoverClicked}"
ng-click="toggleInfoPopoverClicked()"
ng-mouseenter="enterInfoPopover()"
ng-mouseleave="leaveInfoPopover()"
custom-click-outside="closeInfoPopover()"
uib-popover-template="'info.html'"
popover-trigger="'none'"
popover-is-open="isInfoPopoverOpen()"
popover-placement="auto top"
popover-append-to-body="true" >
</span>
The JS in the controller:
// Toggle popover's clicked active state
$scope.toggleInfoPopoverClicked = function() {
$scope.isInfoPopoverClicked = !$scope.isInfoPopoverClicked;
};
// Close the popover, used for outside click and close action inside the template
$scope.closeInfoPopover = function() {
delete $scope.isInfoPopoverClicked;
};
// On mouse enter, show the popover
$scope.enterInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = true;
};
// On mouse leave, close the popover.
// If clicked active state is false set to undefined.
// This supports when the user clicks the icon to close,
// that mouse enter does not immediately display the popover again.
$scope.leaveInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = false;
if(false === $scope.isInfoPopoverClicked) {
delete $scope.isInfoPopoverClicked;
}
};
// Expression used in the popover-is-open attribute
$scope.isInfoPopoverOpen = function() {
if($scope.isInfoPopoverClicked) {
return true;
} else if(false === $scope.isInfoPopoverClicked){
return false;
}
return $scope.isInfoPopoverMouseEnter;
};
The template for the uib-popover-template I used:
<div custom-stop-event="click" class="pull-right">
<span ng-click="closeInfoPopover()" class="glyphicon glyphicon-remove"></span>
<section>{{info}}</section>
</div>
Now the trickier part was that this solution required me to create two more directives.
One to close the popover when clicking outside the element.
Another to stop the click event fired inside the pop-up. Preventing it from closing the popover.
The custom-click-outside directive:
angular.module('LSPApp').directive('customClickOutside', ['$document', function ($document) {
return {
restrict: 'A',
scope: {
clickOutside: '&customClickOutside'
},
link: function (scope, element) {
var handler = function (event) {
if (element !== event.target && !element[0].contains(event.target)) {
scope.$applyAsync(function () {
scope.clickOutside();
});
}
};
// Might not work on elements that stop events from bubbling up
$document.on('click', handler);
// Clean up event so it does not keep firing after leaving scope
scope.$on('$destroy', function() {
$document.off('click', handler);
});
}
};
}]);
The custom-stop-event directive called from the template's HTML:
angular.module('LSPApp').directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.on(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
Hopefully, this helps someone, my final solution had all this encapsulated in it's own directive to promote reuse.

How to prevent doubleclicking on a button with angularjs?

I want to select an element in my case a button on the page in my angularcontroller and then disable it. The button looks like this:
myBtn= $element.by.buttonText('submit')
I don't want the user to click the button twice in order to avoid to post requests in the backend. When I get the code above I get an angular reference order. What is an easy way to select a button and then set the disabled property to true so the user cannot click the button twice?
You can use ng-disabled to disable your button according to a flag set in your submit function. For example:
<form ng-submit="submit()">
...
<button type="submit" ng-disabled="isSubmitting">Submit</button>
</form>
and in your controller:
$scope.submit = function() {
$scope.isSubmitting = true;
$http.post('...').finally(function() {
$scope.isSubmitting = false;
});
};
if you have many buttons on page, then its better to create a directive so that on any button which is clickable, it doesnt get pressed twice
app.directive('ngClickDisable', function() {
return {
scope: {
clickAndDisable: '&'
},
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
iElement.prop('disabled',true);
scope.clickAndDisable().finally(function() {
iElement.prop('disabled',false);
})
});
}
};
});
This can be used on a button as follows:
<button ng-click-disable="functionThatReturnsPromise()">Click me</button>

How can I submit an angular-ui modal with keyboard

angular-ui modals (see http://angular-ui.github.io/bootstrap/ under Modal) can be default be cancelled by pressing escape. This option is configurable through the keyboard option to $modal.open().
I would also like to be able to submit such a modal by using the keyboard, for example by pressing Ctrl-Enter. (I have several different dialogs of this kind in my app. Each modal has a different controller due to different input / output requirements.)
The button specification at the end of the angular template of the example in the documentation looks like this:
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
The rest of the example can be found here: http://plnkr.co/edit/uNeRrPI8CdZFNslcQzFy?p=preview
Here's a gist for adding an extra angular directive for keyboard shortcuts to elements with ng-click directives: https://gist.github.com/mkhatib/5802718 -- is this the best solution?
You could listen for a key event in the ModalInstanceCtrl like this:
function onKeydown(evt) {
if (evt.ctrlKey && evt.which === 13) { // enter key
evt.preventDefault();
$scope.$apply(function () {
$scope.ok();
});
}
}
$document.on('keydown', onKeydown);
$scope.$on('$destroy', function () {
$document.off('keydown', onKeydown);
});
Example Plunker: http://plnkr.co/edit/CR0HxGzCVK2V2dAxzjX4?p=preview
Hope this helps.
Here is a directive working with Angular 1.3. It can be added as attribute of a form or anywhere inside a modal. Only one modal should be opened at at once.
/**
* Submit form in modal on Enter key pressed.
* Usage: <form org-submit-onenter="myModalCtrl.ok()"></form>
*
* Only one modal should be opened at at once.
*/
function orgSubmitOnenter($document) {
return {
restrict: 'A',
priority: 0,
link: function($scope, element, attr) {
if (!attr.orgSubmitOnenter) return;
function onKeydown(event) {
if (event.which === 13) { // Enter key
event.preventDefault();
$scope.$eval(attr.orgSubmitOnenter);
}
}
$document.on('keydown', onKeydown);
$scope.$on('modal.closing', function () {
$document.off('keydown', onKeydown);
});
}
}
}
angular
.module('directives')
.directive('orgSubmitOnenter', orgSubmitOnenter);

How can I remove :active from bootstrap button

I have a website, where I have multiple buttons. Once a button is pressed I populate a list, though my problem is that the last pressed button keeps to be looking pressed (has the :active class). I thought about using angular's $timeout to reset the button, though the removeClass function doesn't do the trick.
My view looks like this:
div(ng-controller='productButtonController')
div(ng-repeat='product in products')
div.col-md-4
button.btn.btn-block.sell-button(id='{{product._id}}' ng-click='sell()'){{product.name}}
and my controller:
app.controller('productButtonController', ['$scope', '$timeout', 'productServices', 'flash',
function($scope, $timeout, productServices, flash) {
productServices.getProducts()
.success(function(result) {
$scope.products = result.data
})
.error(showErrorMessage(flash))
$scope.sell = function() {
console.log(this.product)
that = this
$('#' + that.product._id).removeClass('active')
}
}
])
Add the angular $window service to your dependencies for the
controller
Call the blur method on the document's active element, which will be
your button.
$window.document.activeElement.blur();
See How do you clear the focus in javascript?.
This code from Justin Poehnelt's handy GIST solves this elegantly.
app.directive('blur', [function () {
return {
restrict: 'A',
link: function (scope, element) {
element.on('click', function () {
element.blur();
});
}
};
}]);
Add the blur attribute to a button/element you need blurred after click. Eg.
<button type="button" blur>Click me</button>
If you simply want to override the focus status of the bootstrap buttons you could do it with:
.btn:focus{
outline: none;
}
Then your buttons should look like:
<button class="btn btn-default">My button 1</button>
It's also important that the stylesheet which overrides the button status is loaded after the bootstrap stylesheet.
EDIT:
Sorry, but the previous step only removes the outline. The background-color of the button still remains the same.
Since bootstrap doesn't append any active classes to the clicked element as far as i know you need to change the :focus status of the button:
$('#' + that.product._id).blur();
Let me know if this works for you.

Resources