How to detect clicks outside of scope in angularjs - angularjs

I have a angular controller which needs to reset whenever the user clicks outside the scope of the controller. How do I do this?
Sample html:
<div id='parent'>
<div id='1' ng-controller="ctrl1">
<!--other things-->
</div>
<div id='2' ng-controller="ctrl2">
<!--other things-->
</div>
</div>
<div id="parent2">
<!--other things-->
</div>
I want to be able to reset ctrl2 if a click occurs outside of ctr2 when the click occurs outside of div2
ctr2 has a reset function defined inside

If you are wanting a reset when the click happens within a sibling div, then the fact that the controllers share $scope should make that fairly straightforward. If you want to reset the div when a click happens anywhere else on the page, then you should set the "resetting" div as a directive, binding the $window object to a click handler:
app.directive('reset', function($window){
return {
template: '<div ng-class="{red: directiveToggler}">I\'m in 2' +
' - click me to turn red, click anywhere else to turn me normal'+
'</div>',
controller: function($scope){
$scope.directiveToggler = true;
},
link: function(scope, element){
var w = angular.element($window);
w.bind('click', function(e){
if (e.target != element[0].children[0]){
scope.directiveToggler = false;
scope.$apply();
} else {
scope.directiveToggler = true;
scope.$apply()
}
})
}
}
})
Note that there are probably waaay better ways of handling the class/style changes than what I have set up, but the question was about click events ;).
Here's a plunk to demo

You can use the $rootScope to broadcast events to other controllers. See this JSFiddle: http://jsfiddle.net/simpulton/XqDxG/

Related

trigger angular.ui popover on event but on click

is it possible to trigger the popover directive on an event? I'm trying to look for a string character and trigger a custom template, however, I cannot find a way to get my way around it, I only see custom attributes attached to a button.
You can use popover-is-open to display the popover on a given event.
Here is an example, where a timeout is used to simulate an event that shows the popover:
Markup:
<div ng-controller="PopoverDemoCtrl as vm">
Wait for 3 seconds for the event to happen...
<div uib-popover="Read the message!"
popover-title="Hello World!"
popover-placement="bottom"
id="popover"
class="btn btn-default spaced"
popover-is-open="vm.showPopover">
Popover
</div>
</div>
JavaScript:
function PopoverDemoCtrl($timeout) {
var popoverDemoCtrl = this;
popoverDemoCtrl.showPopover = false;
$timeout(function () {
popoverDemoCtrl.showPopover = true;
}, 3000);
}
PopoverDemoCtrl.$inject = ['$timeout'];
angular
.module('myApp', ['ui.bootstrap'])
.controller('PopoverDemoCtrl', PopoverDemoCtrl);
The full Fiddle: http://jsfiddle.net/masa671/gtgqof2k/

Angular refresh current page - problems

home.html is a template with two fieldsets.
I use ng-show to make the first fieldset visisble when the page loads and when a user clicks a button the other fieldset is made visible and the first one hidden.
This is accomplished by using a variable 'preview' like so (this is a jsfiddle) :
<div ng-app="app">
<div ng-controller="MainController">
<fieldset ng-show="!preview">
<p>This is fieldset 1</p>
<button ng-click="toggleIt()">Toggle</button>
</fieldset>
<fieldset ng-show="preview">
<p>This is fieldset 2</p>
</fieldset>
Home
</div>
</div>
and the controller is like:
angular.module('app', []).
controller('MainController', ['$scope', function ($scope) {
$scope.preview = false;
$scope.toggleIt = function(){
$scope.preview = true;
}
$scope.returnIt = function(){
location.reload();
}
}]);
In JsFiddle it works fine but when I use the 'Home' link in my angular app which is:
Home
and this is picked up by the app.routes.js file
.when('/', {
templateUrl : 'app/views/pages/home.html',
controller: 'MainController',
controllerAs: 'main'
})
Nothing happens - fieldset 2 is still visible rather than fieldset 1.
I was expecting that $scope.preview would be set to false as this is the first line in the controller and that would make the first fieldset visible again.
When I do a browser refresh it works but not using the routing.
I have tried location.reload(); (and this works in the fiddle), but when I apply it inside the app controller I get TypeError: $location.reload is not a function.
I have tried $route.reload() which sends it into an infinite loop, and window.location.reload() which keeps reloading.
Is there a simple way I can achieve a browser-like refresh of the page via the routing?

Passing data to ionic popover in ng-repeat

I want to add ionic Popover to my application and place in under ng-repeat however I am struggling a bit with this.
How can I pass a parameter to it?
<p ng-repeat="query in ctrl.timesheet">query.Name<button ng-click="openPopover($event)">Open Popover</button></p>
<script id="my-popover.html" type="text/ng-template">
<ion-popover-view>
<ion-header-bar>
<h1 class="title">My Popover Title</h1>
</ion-header-bar>
<ion-content>
<button ng-click="ctrl.delete(index)">Delete</button>
</ion-content>
</ion-popover-view>
</script>
So in short I want a list of buttons and whenever i click the popover for the button than there is a option to delete the element.
See this demo: http://play.ionic.io/app/eb32466d892c
You can pass scope of your controller to pop over, so whatever you have in parent controller will be available in pop over. Pass a parameter index in openpopover funtion : ng-click = "openPopover($event, pass-index-here)"> and bind it to $scope.index before you open popup, See this:
$ionicPopover.fromTemplateUrl('my-popover.html', {
scope: $scope
}).then(function(popover) {
$scope.popover = popover;
});
$scope.openPopover = function($event,index) {
$scope.index = {'value' : index}; //i am using object, because simple variable shows binding problem some time
$scope.popover.show($event);
};
Now this $scope.index will be available in popover, and will contain latest index of item, which you have clicked because we update its value before opening popover :
<button ng-click="ctrl.delete(index.value)">Delete</button>

angularjs $modal avoid multiple instances

I want to know it there is a way to avoid opening multiple modal instances, or by some id or some function?
i want to avoid boolean variables to set true or false when i open, because i have a page with a lot of modals and that is not cool.
In my example if you click "Modal 3" buttonand the "Do stuff button" it opens a lot of instances, i have a plunker working example here:
modalInstance = $modal.open({
templateUrl: 'myModalContent2.html',
controller: ModalInstanceCtrl,
scope: $scope
});
http://plnkr.co/edit/fkx9faTdDsDjVpgFVKlo?p=preview
If you just want to refresh the content that's inside the modal, you can simply give your modal-body an id, and set the innerHTML in your otherfunction()
<div id="myModal" class="modal-body">
<h3>MODAL TITLE</h3>
<button ng-click="otherFunction()" class="btn btn-default">Do stuff</button>
</div>
in app.js
$scope.otherFunction = function () {
document.getElementById("myModal").innerHTML = "New content";
}

How can I set a form contained inside a ng-include to be prestine?

I have the following code:
<div modal="modal.shouldBeOpen" close="close()" options="opts">
<div class="modal-body">
<form novalidate name="itemForm" style="margin-bottom: 0px;">
Which is contained inside the included file modal.html
<div data-ng-controller="AdminController">
<ng-include src="'/Content/app/admin/partials/grid-subject.html'"></ng-include >
<ng-include src="'/Content/app/admin/partials/modal.html'"></ng-include>
</div>
In my AdminController controller I am trying to use the following code to reset the form to pristine:
$scope.itemForm.$setPristine();
When I do this it tells me that "itemForm" is undefined.
Is there a way I can set the contents of the form to pristine. I assume this is a scope problem but I am not sure how to fix it. I
tried the one solution of removing the second include and pasting the code in directly. This solution works.
However we want to be able to reuse code
so I would like to be able to do this with an include for modal.html
Note that the reason we would like to do this is because we have something like the following on our modal.html:
<button
class="btn float-right"
data-ng-disabled="itemForm.$pristine"
data-ng-click="modalReset()"
data-ng-show="modal.resetButton">
Reset</button>
</form>
So we are actually inside of the itemForm and would like to set it to $pristine from the button inside.
This answer will break all the rules (i.e., DOM traversal inside a controller), but here it is anyway...
.controller('AdminController', ['$scope','$element',
function($scope, $element) {
$scope.$on('$includeContentLoaded', function() {
var childFormController = $element.find('form').eq(0).controller('form');
console.log(childFormController);
childFormController.$setPristine();
});
}]);
We wait for the ng-included content to load, then from the $element where AdminController is defined, we look for form elements, pick the first one, then get its FormController.
Plunker
If you are only calling $setPristine() as a result of some user interaction, you won't need to look for the $includedContentLoaded event – I only had to do that because I didn't want to create any UI component to trigger the operation, and when the controller first runs, the form doesn't exist yet.
See also AngularJS: Access formController of a form placed inside transcluded directive from parent controller which deals with the similar problem of trying to access a child from a parent.
A cleaner solution: define a directive (use it on the ng-include element) and pass it an AdminController function as an attribute. In the directive's link function, call that method and pass the FormController as a parameter. Then the AdminController will have a reference to the desired FormController. (I did not bother coding this up, as I'm not sure you want a solution where you have to use a directive along with ng-include.)
Well, one way to do it is to broadcast an event, like so:
angular.module('myApp',[])
.controller('AdminCtrl',function($scope){
$scope.modalReset = function(){
$scope.$broadcast('modal-reset');
};
})
.controller('ModalCtrl', function($scope){
$scope.$on('modal-reset', function(){
$scope.itemForm.$setPristine();
});
});
This way you don't have to traverse the dom.
Do not break the rules :) Just define the variable (empty object) in the controller and use it while defining your form. Since angular JS uses scope prototypes under the hood, when form will try to access the inner scope (to bootstrap the variable), it will first go via scope chain and try to find the same variable in the parent's scope.
<!—- The vars should live in the controller. I placed them here for the example. -—>
<div ng-controller=“controllerName” ng-init="form={}; model={}" >
<div ng-include=“ ‘path-to-the-template’ ”></div>
</div>
<!—- Inside path-to-the-template -—>
<form name="form.createUser">
<input name="name" ng-model="model.name" />
<input name="email" ng-model="model.email" />
</form>
Link for reference http://blog.152.org/2014/07/angular-form-element-not-attaching-to.html
If you want to achieve this as the result of some user interaction, in my opinion a much more cleaner and 'angular' way of doing it would be to use a custom directive which will set the form to pristine (i.e. when the user wants to clear the form by pressing esc or clicking a button or whatever).
app.directive("formCleaner",
function () {
return {
restrict: 'E',
require: '^form',
scope: {
callback: '&',
defaultText:'#'
},
template: '<button type="button" ng-click="setFormToPristine()" class="btn btn-warning" >{{defaultText}}</button>',
link: function (scope, element, attrs, formCtrl) {
scope.setFormToPristine = function () {
formCtrl.$setPristine();
scope.callback();
};
}
};
});
and simply hook it up to some button in your form:
<form name="testForm">
<input type="text" ng-model="someModel" />
<hr/>
<input type="button" value="submit form" class="btn btn-primary" ng-disabled="testForm.$pristine"
ng-click=submitForm(testForm) />
<form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>
</form>
And if you're looking to set the form to pristine directly from the controller, (not as a result of some user interaction) such as success response from a POST, then one way would be to assign a callback to the directive which will be responsible for clearing the form and then invoking that callback from the controller. In your view:
<form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>
and the controller:
$scope.resetFormOnSubmitCallback=function(cb){
$log.warn("simulating $http POST call.....");
$timeout(function() {
cb();
$scope.someModel=null;
}, 3000)
}
and the directive:
return {
restrict: 'E',
require: '^form',
scope: {
callback: '&',
defaultText:'#',
ngDisabled:'='
},
template: '<button type="button" ng-disabled="ngDisabled" ng-click="submitForm()" class="btn btn-primary" >{{defaultText}}</button>',
link: function (scope, element, attrs, formCtrl) {
var setFormToPristine=function(){
$log.log("setting form to prsitine....");
formCtrl.$setPristine();
};
scope.submitForm = function () {
scope.callback({
onFormSubmittedCallback:setFormToPristine
});
};
}
};
See plunk

Resources