ng-click in compiled directive not working - angularjs

The function tied to the ng-click event is never fired. The ng-click is inside a directive bootstrapped with $compile. Does anybody know why the click event never fires? The function is visible on the scope. A working plunker is provided. The code is as follows:
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('MainCtrl', function($scope) {
$scope.datum = new Date('2015-05-01');
});
var link = function($compile) {
return function(scope, element, attributes, form) {
scope.opened = false;
var tag = '<span class="input-group-btn">' +
'<button type="button" class="btn btn-default" ng-click="open($event)">' +
'<i class="glyphicon glyphicon-calendar"></i>' +
'</button>' +
'</span>';
console.log(scope.opened)
element
.removeAttr('szp-date')
.attr('datepicker-popup', "")
.attr('is-open', "opened")
.wrap('<p class="input-group" style="width: 10em"></p>')
.after(tag);
$compile(element)(scope);
};
};
var forminput = function($compile) {
return {
restrict: "A",
require: "^form",
controller: function($scope) {
$scope.open = function($event) {
// This never gets fired
$event.preventDefault();
$event.stopPropagation();
$scope.opened = true;
console.log(scope.opened);
};
},
link: link($compile)
};
};
app.directive("szpDate", forminput);
EDIT:
It is interesting that modifying the input tag as follows:
element
.removeAttr('szp-date')
.attr('datepicker-popup',"")
.attr('is-open', "opened")
// added this line of code
.attr('ng-click', 'open($event)')
.wrap('<p class="input-group" style="width: 10em"></p>')
.after(tag);
produces a clickable input element, but this is not what I want to achieve. I want the button to be clickable.
Edit 2:
More interestingly, I seem to have got it working if i modify this line.
.after(tag)
with
.after($compile(tag)(scope));

Related

AngularJs ui.bootstrap - Modal show error [$injector:unpr]

i am trying to use angular ui.bootstrap to implement some functionality like this but this shows an error
[$injector:unpr]
http://errors.angularjs.org/1.5.2/$injector/unpr?p0=%24modalProvider%20%3C-%20%24modal%20%3C-%20ngReallyClickDirective
my code is
app.js
var app = angular.module('app',['ui.router','oc.lazyLoad','ui.bootstrap','ngReallyClickModule']);
ngReallyClickModule.js
angular.module('ngReallyClickModule',['ui.router'])
.directive('ngReallyClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngReallyClick:"&",
item:"="
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngReallyMessage || "Are you sure ?";
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<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>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngReallyClick({item:scope.item});
}, function() {
//Modal dismissed
});
});
}
}
}
]);
View
<a ng-really-message="Are you sure ?" ng-really-click="test(item)" item="item" ng-repeat="item in [1,2,3,4,5]">Delete</a>
i want modal dialog on ng-really-click click. And call a function of the current controller on click of the modal Ok button.
i am using angular-ui-router and oclazyLoading
The answer is from the error, your are using some provider that you have not defined/injected.
It is hard to say exactly what is the problem considering you have many injections here, but from the error you gave us, there is no provider for $modal. AngularUI recently switched to uib prefixing most of its providers and directives (i.e. $uibModal). Try using the new version.
https://angular-ui.github.io/bootstrap

Angularjs Click event handling within link

I am trying to make a custom directive which would show different buttons based on attribute present. One of the button requires click event handler and I want to handle it within the directive as there will be multiple instances of this directive in same page. I tried the code below but to no avail.
'use strict';
angular
.module('test-template', [])
.directive('testTemplateBricks', [
'$compile',
'$timeout',
function($compile,$timeout) {
return {
restrict: 'A',
link: function($scope, iElm, iAttrs, controller) {
var el = "";
if(iAttrs.needImg=="true")
{
el += '<input type="file" style="display:none;" class="browse-file"/><button class="btn btn-info" ><span class="glyphicon glyphicon-picture" ng-click="browse()"></span></button>';
}
if(iAttrs.needTxt=="true")
{
el += ' <button class="btn btn-info"><span class="glyphicon glyphicon-pencil"></span></button>';
}
$compile(el)($scope);
iElm.append(el);
$scope.browse = function() { console.log("browsing");};
$timeout(function(){
iElm.on("click",function(e){
console.log("Browsing");
iElm.find("input[type=file]").click();
});
});
}
};
}
]);
EDIT: http://plnkr.co/edit/bNRLvWjEE7LLvhwRFIae?p=preview
In this sample I want to display the hidden file browser when the image button is clicked.
So I wouldn't recommend this approach to toggling the visibility of elements, it's something that's better handled in the template logic.
But to get you started I've taken your code and amended it somewhat (https://jsbin.com/negawu)
angular
.module('test-template', [])
.directive('testTemplateBricks', [
'$compile',
'$timeout',
function($compile,$timeout) {
return {
restrict: 'A',
template: '<input type="file" class="browse-file"/>' +
'<button class="btn btn-info" ng-show="showImage">' +
'<span class="glyphicon glyphicon-picture" ng-click="browse()"></span>' +
'</button>' +
'<button class="btn btn-info" ng-show="showText">' +
'<span class="glyphicon glyphicon-pencil"></span>' +
'</button>',
link: function($scope, iElm, iAttrs, controller) {
$scope.showImage = false;
$scope.showText = false;
if (iAttrs.needImg == "true") {
$scope.showImage = true;
}
if (iAttrs.needTxt == "true") {
$scope.showText = true;
}
$scope.browse = function() {
console.log("browsing");
};
}
};
}
]);

Angular directive for bootstrap popover

I write my custom directive for bootstrap popover, but face some trouble.
This is the code:
angular.module('CommandCenterApp')
.directive('bzPopover', function($compile,$http, $commandHelper) {
return{
restrict: "A",
replace: false,
scope: {
currencies:"=data",
selected:"=selected"
},
link: function (scope, element, attrs) {
var html = '<div class="currency-popup">' +
'<span class="select-label">Select currency:</span>'+
'<select class="custom-select" ng-model="selected" ng-options="currency.CurrencyName for currency in currencies track by currency.CurrencyId">' +
'</select>' +
'<button class="btn btn-green" ng-click="saveCurrency()">Save</button>'+
'</div>';
var compiled = $compile(html)(scope);
$(element).popover({
content:compiled,
html: true,
placement:'bottom'
});
scope.saveCurrency = function () {
var obj = {
Currency:scope.selected,
venueId: $commandHelper.getVenueId()
}
$http.post("/api/currencyapi/changecurrency", obj).success(function() {
scope.$emit('currencySaved', scope.selected);
});
//$(element).popover('hide');
}
scope.$watch('selected', function() {
console.log(scope.selected);
});
}
}
});
When I first time invoke popover all works fine, I click on button and it trigger scope.saveChanges function. Then I close popover and invoke it again, and directive doesnt work anymore.
In markup popover present as:
<a bz-popover data="controller.currencies" selected="controller.selectedCurrency" class="change-currency hidden-xs hidden-sm" href>Change currency</a>
Can anyone help me with this?
UPDATE: it looks like all bindings(scope.saveCurrency,watched on selected property) stop working after popover hidding.
Not really sure if this is the problem you're describing because in my fiddle I had to click twice on the button to show the popover after closing the popover.
I don't know what's the problem but with trigger: 'manual' and binding to click event it is working as expected.
Please have a look at the demo below or in this jsfiddle.
I've commented some of your code because it's not needed to show the popover behaviour and also the ajax call is not working in the demo.
angular.module('CommandCenterApp', [])
.controller('MainController', function() {
this.currencies = [{
CurrencyId: 1,
CurrencyName: 'Dollar'},{
CurrencyId: 2,
CurrencyName: 'Euro'}];
})
.directive('bzPopover', function($compile,$http) { //, $commandHelper) {
return{
restrict: "A",
replace: false,
scope: {
currencies:"=data",
selected:"=selected"
},
link: function (scope, element, attrs) {
var html = '<div class="currency-popup">' +
'<span class="select-label">Select currency:</span>'+
'<select class="custom-select" ng-model="selected" ng-options="currency.CurrencyName for currency in currencies track by currency.CurrencyId">' +
'</select>' +
'<button class="btn btn-green" ng-click="saveCurrency()">Save</button>'+
'</div>';
var compiled = $compile(html)(scope);
$(element).popover({
content:compiled,
html: true,
placement:'bottom',
trigger: 'manual'
});
$(element).bind('click', function() {
$(element).popover('toggle');
});
scope.saveCurrency = function () {
var obj = {
Currency:scope.selected,
venueId: 1//$commandHelper.getVenueId()
}
$http.post("/api/currencyapi/changecurrency", obj).success(function() {
scope.$emit('currencySaved', scope.selected);
});
$(element).popover('hide');
}
scope.$watch('selected', function() {
console.log(scope.selected);
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css" rel="stylesheet"/>
<div ng-app="CommandCenterApp" ng-controller="MainController as controller">
<button bz-popover data="controller.currencies" selected="controller.selectedCurrency" class="change-currency hidden-xs hidden-sm">Change currency</button>
</div>
Shameless self-promotion here, but you may want to take a look at the Angualr UI Bootstrap library as we've already done this for you. And even if you don't want to use it, you can just grab the code you need...

how to change a directive variable value in controller

List of directive are listed like:
HTML:
<div ng-app="dashboard" ng-controller="MyCtrl">
<button class="btn btn-white" ng-click="minim()"><i class="fa fa-minus-square"></i></button>
<widget-item widget-id="1" widget-type ="chart"></widget-item>
<widget-item widget-id="2" widget-type ="table"></widget-item>
</div>
JS:
var dashboard = angular.module("dashboard",['ui.bootstrap','ngAnimate']);
dashboard.controller( 'MyCtrl', function ( $scope,$modal) {
$scope.on = true;
$scope.minim = function(){
$scope.on = !$scope.on;
};
});
dashboard.directive('widgetItem', ['$compile', function($compile){
restrict: 'E',
scope:true,
template: '<div class="panel"><div class="panel-heading"><div class="mypanel-btn" > <i class="fa fa-minus"></i></div></div><div class="panel-body" ng-show="on"></div></div>',
link:function(scope, element, attrs) {
scope.on = true;
scope.toggle = function () {
scope.on = !scope.on;
};
}
}]);
Here I have minimize button for individual directive as well as all directive. Individual minimize button working fine(It's define in directive link). Over all minimize button doesn't work. How do I change directive isolate value from controller?
In your case, you can use $broadcast which will emit event and dispatches the event downwards to all child scopes.
Then, you can use $on to listen some event into your directive.
If i follow your code :
Controller
(function(){
function Controller($scope, $http) {
$scope.on = true;
$scope.minim = function(){
$scope.on = !$scope.on;
//send minim event downwards to all child scope
$scope.$broadcast('minim', $scope.on);
};
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Directive
(function(){
function widgetItem($compile) {
return{
restrict: 'E',
scope: true,
templateUrl: 'template.html',
link:function(scope, element, attrs) {
//Listen event minim
scope.$on('minim', function(e, data){
//Change our scope.on data
scope.on = data;
});
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
}
angular
.module('app')
.directive('widgetItem', widgetItem);
})();
And then, call your directive :
HTML
<body ng-app="app" ng-controller="ctrl">
<button class="btn btn-white" ng-click="minim()"><i class="fa fa-minus-square"></i></button>
<widget-item widget-id="1" widget-type ="chart"></widget-item>
<widget-item widget-id="2" widget-type ="table"></widget-item>
</body>
If you use directive with scope: true, it means that you new scope is a child of parent scope and you can change whatever you want,because it's not an isolated scope.
if you need more with examples, here is a cool article
http://www.w3docs.com/snippets/angularjs/change-variable-from-outside-of-directive.html

angularjs custom confirm box

So I'm trying to implement a custom confirm box using Angular. Ideally, I would simply like to add an attribute to enable the functionality. Example:
<button type="button" ng-click="delete(foo)">Delete</button> -> <button type="button" ng-click="delete(foo)" ng-confirm="Are you sure you want to delete this foo?">Delete</button>
(foo is inside an ng-repeat... foo in fooList..)
So all of the problems I am having revolve around tying the click event that would normally happen to a different button. I have a seperate directive "confirmBox" that will create my modal (not using bootstrap) and handle all of the showing/hiding/etc.
What I am currently using requires me to alter my ng-click functionality, which I really want to get away from:
Current Implementation:
<button ... ng-click="confirm('Are you sure you want to delete this foo?, 'delete', foo)">Delete</button>
var confirmModule = angular.module('confirm', []);
confirmModule.run(function($rootScope) {
$rootScope.confirm = function(text, func, obj) {
$rootScope.$broadcast('confirm', func, obj, text);
};
});
confirmModule.directive('confirmBox', function($parse) {
return {
restrict: 'A',
template: myModalTemplate,
link: function(scope, element, attrs){
element.hide();
var noBtn = element.find("[name='no']");
noBtn.bind("click", function() {
element.hide();
});
scope.$on("confirm", function(event, func, obj, text) {
var yesBtn = element.find("[name='yes']");
element.show();
yesBtn.unbind("click").bind("click", function() {
scope[func](obj);
});
});
}
}
});
Anyone have any ideas? I started by adding the directive for the button and then unbinding the click event so ng-click doesn't fire. Then I am left with the string 'delete(foo)' from the ng-click attribute that I can execute with $parse(attrs.ngClick)(scope), but I don't know how to tie that to the separate directives button click.
Edit: Here is a fiddle with my current attempt at implementation. The problem is the variable being passed in to the function is always undefined.
http://jsfiddle.net/UCtbj/2/
Edit2: Updated implementation, however I don't particularly like how it links the two directives together by targetting the other directives elements.
http://jsfiddle.net/UCtbj/3/
It seems to me that you're trying to do things the jQuery way from within the directive. However, what you want is as simple as pulling in the UI-Bootstrap directive for confirming actions. http://plnkr.co/edit/JhfAF1?p=preview
First simple service for modal windows:
app.service('ConfirmService', function($modal) {
var service = {};
service.open = function (text, onOk) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalConfirmCtrl',
resolve: {
text: function () {
return text;
}
}
});
modalInstance.result.then(function (selectedItem) {
onOk();
}, function () {
});
};
return service;
})
app.controller('ModalConfirmCtrl', function ($scope, $modalInstance, text) {
$scope.text = text;
$scope.ok = function () {
$modalInstance.close(true);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
Then simple directive that uses it:
app.directive('confirm', function(ConfirmService) {
return {
restrict: 'A',
scope: {
eventHandler: '&ngClick'
},
link: function(scope, element, attrs){
element.unbind("click");
element.bind("click", function(e) {
ConfirmService.open(attrs.confirm, scope.eventHandler);
});
}
}
});
And here u go:
<button ng-click="test(12)" confirm='Are you sure?'>Button</button>
http://plnkr.co/edit/LOZOnsVyx3JU5XoKYn74?p=preview
To allow a button to be marked up like
<button type="button" ng-click="deleteItem(drink)" ng-confirm="Are you sure you want to delete '{{drink.name}}'">Delete</button>
You can write a directive that
Intercepts the click event before ngClick's click handler can run
Opens a dialog (using $modal and not the removed $dialog)
On close of the dialog (which is treated as a success) run the function specified by the ngClick attribute on the element.
Basing the code on the previous answer, you can do this as follows:
app.directive('ngConfirm', function($modal, $parse) {
return {
// So the link function is run before ngClick's, which has priority 0
priority: -1,
link: function(scope, element, attrs) {
element.on('click', function(e) {
// Don't run ngClick's handler
e.stopImmediatePropagation();
$modal.open({
templateUrl: 'ng-confirm-template',
controller: 'ngConfirmController',
resolve: {
message: function() {
return attrs.ngConfirm;
}
}
}).result.then(function() {
// Pass original click as '$event', just like ngClick
$parse(attrs.ngClick)(scope, {$event: e});
});
});
}
};
});
which needs a simple controller:
app.controller('ngConfirmController', function($scope, $modalInstance, message) {
$scope.message = message;
$scope.yes = function() {
$modalInstance.close();
};
$scope.no = function() {
$modalInstance.dismiss();
};
});
and template for the dialog:
<script type="text/ng-template" id="ng-confirm-template">
<div class="modal-body">
<p>{{message}}</p>
</div>
<div class="modal-footer">
<button class="btn btn-link pull-left" ng-click="no()">No</button>
<button class="btn btn-primary pull-right" ng-click="yes()">Yes</button>
</div>
</script>
You can see this running at http://plnkr.co/edit/Gm9lFsGb099w6kCMQoVY?p=preview
Edit: changed plunker link to example without scrollbar appearing/disappearing on display of the dialog
Here is a nice directive for that.That is ngBootbox. Check it out.
<button class="btn btn-lg btn-primary"
ng-bootbox-title="A cool title!"
ng-bootbox-custom-dialog="Some custom text"
ng-bootbox-buttons="customDialogButtons"
ng-bootbox-class-name="some-class">
Custom dialog
</button>
<script>
$scope.customDialogButtons = {
warning: {
label: "Warning!",
className: "btn-warning",
callback: function() { $scope.addAction('Warning', false); }
},
success: {
label: "Success!",
className: "btn-success",
callback: function() { $scope.addAction('Success!', true) }
},
danger: {
label: "Danger!",
className: "btn-danger",
callback: function() { $scope.addAction('Danger!', false) }
},
main: {
label: "Click ME!",
className: "btn-primary",
callback: function() { $scope.addAction('Main...!', true) }
}
};
</script>
Demo
ngBootbox
I created a repo for this functionality. It wraps the ui-bootstrap modal to produce a confirmation box. It is customizable and easily integrated into any application.
Here is the link to the GitHub: https://github.com/Schlogen/angular-confirm
Example Usages:
As a directive:
<button type="button" ng-click="delete()" confirm-if="checked" confirm="Are you sure, {{name}}?">Delete</button>
As a service:
$confirm({text: 'Are you sure you want to delete?'})
.then(function() {
$scope.deletedConfirm = 'Deleted';
});
Ok, here is the one I ended up going with
1) Create a service for the dialog
app.service('dialogModal', [
'$modal', function($modal) {
return function(message, title, okButton, cancelButton) {
okButton = okButton === false ? false : (okButton || 'Yes');
cancelButton = cancelButton === false ? false : (cancelButton || 'No');
var modalInstance = $modal.open({
templateUrl: '/templates/deletePrompt.html',
controller: ModalInstanceCtrl,
resolve: {
settings: function() {
return {
modalTitle: title,
modalBody: message,
okButton: okButton,
cancelButton: cancelButton
};
}
}
});
// return the modal instance
return modalInstance;
}
}
]);
2) Create a controller and pass the model instance in it
var ModalInstanceCtrl = function ($scope, $modalInstance, settings) {
angular.extend($scope, settings);
$scope.ok = function () {
$modalInstance.close(true);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
3) included the link into the header to take on default styling
<link data-require="bootstrap-css#3.x" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
4) overwrote the styling in my own css
5) Here is my delete prompt template
<div id="overlayClearMainDiv" class="dialog-modal">
<div id="overlayClearText">
<span>{{modalBody}}</span>
</div>
<div id="overlayClearButton">
<button id="overlayClearYesButton" class="confirmButton" type="button" ng-click="ok()" ng-show="okButton">{{okButton}}</button>
<button class="confirmButton-white" ng-click="cancel()" ng-show="cancelButton">{{cancelButton}}</button>
</div>
</div>
Here's a quick one for you - http://plnkr.co/edit/YklthDZcknmvMjU5A6pe?p=preview
So basically if you are interested in showing a modal dialog once a user clicks on let's say, a button there's no need to make it difficult.
All you need is a simple directive that encapsulate $modal service found in ui-bootstrap.
In my simple example I just pass in a string representing a message and then defining a on-confirm attribute that my directive invokes once the user confirms. Invoking the function itself leverages the awesomeness of $parse to resolve the expression and once resolved, invoke it with the scope.
Nice and clear and here's how it looks like.
View
<body ng-controller="AppController">
<input type="button" value="Delete"
confirm="'Are you sure you want to delete me?'" on-confirm="delete()" />
<script type="text/ng-template" id="modal.html">
<div class="modal-header">
<h3 class="modal-title">Confirm</h3>
</div>
<div class="modal-body">
<p>The world won't be a better place if you delete me.</p>
</div>
<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>
</script>
</body>
Controller / Directive
angular
.module('App', ['ui.bootstrap'])
.controller('AppController', ['$scope', function($scope){
$scope.delete = function(){
alert('Woho, Im deleted!');
};
}])
.directive('confirm', ['$modal', '$parse', function($modal, $parse){
return {
link: function(scope, el, attr){
el.bind('click', function(){
var instance = $modal.open({
templateUrl: 'modal.html',
controller: ['$scope', '$modalInstance', modalController]
});
instance.result.then(function(){
// close - action!
$parse(attr.onConfirm)(scope);
},function(){
// dimisss - do nothing
});
});
}
};
function modalController(modalScope, $modalInstance){
modalScope.ok = function(){
modalInstance.close();
};
modalScope.cancel = function(){
modalInstance.dismiss();
};
}
}]);

Resources