I am facing a problem with the pop up modal in Angularjs. I have a button and on clicking I need to show a modal. Here is my code. When I click on Details button for first time, the modal pop up appears but when I click again.. the app.directive doesn't get called. Greatly appreciate the help. Thanks.
JS:
myApp.directive('modal', function () {
return {
template: '<div class="modal fade">' +
'<div class="modal-dialog">' +
'<div class="modal-content">' +
'<div class="modal-header">' +
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' +
'<h4 class="modal-title">{{ title }}</h4>' +
'</div>' +
'<div class="modal-body" ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
restrict: 'E',
transclude: true,
replace:true,
scope:true,
link: function postLink(scope, element, attrs) {
scope.title = attrs.title;
scope.$watch(attrs.visible, function(value){
if(value == true)
$(element).modal('show');
else
$(element).modal('hide');
});
$(element).on('shown.bs.modal', function(){
scope.$apply(function(){
scope.$parent[attrs.visible] = true;
});
});
$(element).on('hidden.bs.modal', function(){
scope.$apply(function(){
scope.$parent[attrs.visible] = false;
});
});
}
};
});
.state("abc"){
controller:function(){
$scope.showModal = false;
$scope.toggleModal = function(){
$scope.showModal = false;
$scope.showModal = !$scope.showModal;
var aclpGuid = $stateParams.aclpGuid;
};
}
}
HTML:
<td>
<button ng-click="toggleModal()">Details</button>
<modal visible="showModal">
<label>Room Details</label>
</modal>
</td>
I guess the issue is the isolate scope which you have here scope:true inside the directive. Using this, the scope inside a directive is seperated from the scope outside. To confirm this I checked the scope variable with and without scope: true. Note the $$watchers here:
Without scope: true
With scope: true
The isolated scope has $$watchers == null. So any changes in the scope variable showDialog doesn't fire the watch event. So you might wanna use the parent scope i.e the controller's scope here.
The event fires once as when compiling the directive for the first time it attaches the $watchers to the element only once and for subsequent calls there is no relation between the outer scope to a directive's inner scope.
With that change and few others I have a working plunk.
controller:
myApp.controller('myCtrl', function($scope) {
$scope.showDialog = false;
$scope.toggleModal = function() {
$scope.showDialog = !$scope.showDialog;
};
});
Setting the showDialog to false, whenever the user closes the modal as #Mistalis suggested.
$(element).bind('hidden.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = false;
scope.showDialog = false;
});
});
directive:
myApp.directive('modal', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
templateUrl: 'modal-partial.html',
//scope: true,
link: function postLink(scope, element, attrs) {
scope.showModal = function(visible, elem) {
if (!elem)
elem = element;
if (visible)
$(elem).modal("show");
else
$(elem).modal("hide");
}
scope.title = attrs.title;
scope.$watch(attrs.visible, function(value) {
if (value === true)
$(element).modal('show');
else
$(element).modal('hide');
});
$(element).bind('shown.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = true;
});
});
$(element).bind('hidden.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = false;
scope.showDialog = false;
});
});
}
};
});
Related
I have written a directive for simple dropdown. On click of one value, I am calling a function and updating the value.
If I log 'scope.$parent.selectedItem' , I am able to see the value. But that is not updated in parent controller.
This is Directive code
app.directive('buttonDropdown', [function() {
var templateString =
'<div class="dropdown-button">'+
'<button ng-click="toggleDropDown()" class="dropbtn">{{title}}</button>'+
'<div id="myDropdown" ng-if="showButonDropDown" class="dropdown-content">'+
'<a ng-repeat="item in dropdownItems" ng-click="selectItem(item)">{{item.name}}</a>'+
'</div>'+
'</div>';
return {
restrict: 'EA',
scope: {
dropdownItems: "=",
selectedOption: '=',
title: '#'
},
template: templateString,
controller: function($scope,$rootScope,$timeout) {
$scope.selectedOption = {};
$scope.showButonDropDown = false;
$scope.toggleDropDown = function() {
$scope.showButonDropDown = !$scope.showButonDropDown;
};
$scope.$watch('dropdownItems', function(newVal,oldval){
if(newVal){
console.log(newVal);
}
});
$scope.selectItem = function(item){
console.log(item);
$scope.selectedOption = item;
}
},
link: function(scope, element) {
scope.dropdownItems = scope.dropdownItems || [];
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
scope.showButonDropDown = false;
}
console.log(scope.$parent);
}
}
}
}]);
This is my HTML
<button-dropdown title="Refer a Friend" dropdown-items='dropDownList' selected-option='selectedItem'></button-dropdown>
This is my controller code
$scope.$watch('selectedItem',function(newVal,oldVal){
if(newVal){
console.log("*** New Val** ");
console.log(newVal);
}
});
I didn't understand one thing.. If I print 'scope.$parent.selectedItem', I could see the value. but it is not updating in the controller. Didn't understand, what am I missing. Can anyone help on this. Thanks in advance.
Try in this way
1. Try to $emit the scope variable in directive.
2. get that in controller by using $on.
Directive:
$scope.$emit('selectedItem',scopeVariable);
Controller:
$scope.$on('selectedItem',function(event,newVal){
if(newVal){
// logic here
}
});
I have form in modal pop up.i am using directives for opening the modal pop up
mymodule.directive('modalDialogSite', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
link: function(scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
if (attrs.overflow)
scope.dialogStyle.overflow = attrs.overflow;
scope.hideModal = function() {
scope.show = false;
};
},
template: "<div class='ng-modal' ng-show='show'><div class='ng-modal-overlay'></div><div class='ng-modal-dialog' ng-style='dialogStyle'><div class='ng-modal-close' ng-click='hideModal()'><i class='fa fa-times-circle'></i></div><div class='ng-modal-dialog-content' ng-transclude></div></div></div>"// See below
};
});
If i click cancel button in modal popup i use $setpristine to reset the form but If have any validation error when i click close button (x) it calls hideModal() function so modal get closed but if i reopen the modal pop the validation error still exists in modal popup.How can i reset that form.Here My working Plnkr https://plnkr.co/edit/embFJFmautH3uRbHOWU7?p=preview
I think you have missed several things here. I have created a plunkr for this and it very much self explanatory. You can go over to it and observe that it is exactly what you need. The form inside the modal gets to its initial state when it is opened and the code is well organized in this working plunkr. Also the form is reset when you open the modal.
app.directive('modalDialogAdd', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true,
transclude: true,
link: function(scope, element, attrs) {
scope.dialogStyle = {};
scope.text ={
first_name : '',
last_name: ''
};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
if (attrs.overflow)
scope.dialogStyle.overflow = attrs.overflow;
scope.hideModal = function() {
scope.show = false;
};
scope.$watch('show', function (newVal, oldVal) {
if(newVal){
var childFormController = element.find('form').eq(0).controller('form');
childFormController.$setPristine();
childFormController.$setUntouched();
}
});
},
template: "<div class='ng-modal' ng-show='show'><div class='ng-modal-overlay' ng-click='hideModal()'></div><div class='ng-modal-dialog' ng-style='dialogStyle'><div class='ng-modal-close' ng-click='hideModal()'><i class='fa fa-times-circle'></i></div><div class='ng-modal-dialog-content' ng-transclude></div></div></div>"
};
});
Here is a working plunkr PLUNKR
i have a delete button, for the confirmation from the user, i am using directive for modal.
this is the directive code
app.directive('modal', function() {
return {
template: '<div class="modal fade">' + '<div class="modal-dialog">' + '<div class="modal-content">' + '<div class="modal-header">' + '<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">×</button>' + '<h4 class="modal-title">{{ title }}</h4>' + '</div>' + '<div class="modal-body" ng-transclude></div>' + '</div>' + '</div>' + '</div>',
restrict: 'E',
transclude: true,
replace: true,
scope: true,
link: function postLink(scope, element, attrs) {
scope.title = attrs.title;
scope.$watch(attrs.visible, function(value) {
if (value == true)
$(element).modal('show');
else
$(element).modal('hide');
});
$(element).on('shown.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = true;
});
});
$(element).on('hidden.bs.modal', function() {
scope.$apply(function() {
scope.$parent[attrs.visible] = false;
});
});
}
};
});
this is the controller code
$scope.deletePlace = function(place) {
if (place._id) {
var url = '/api/places/' + place._id;
$http.delete(url, {})
.then(function(response) {
$scope.showModal = false;
$state.transitionTo('dashboard.places.list', null, { reload: true, inherit: true, notify: true });
}, function(response) { // fail
$scope.errorMessage = true;
});
}
}
after clicking ok button on delete modal, the modal will hide, but the black screen remains same and the buttons on page or not clickable, untill i refresh the page manually. Is there any way to remove the black screen after clicking ok button on confirmation modal. If i click the refresh manually, it will work. i don't want to refresh it manually. i want it to be refreshed automatically or from other way to hide the black screen.
Try $state.reload() It should work in most cases if you're using latest version of ui-router.
If that doesn't work try routing to the same page using $state.go() or $state.transitionTo() with additional parameter as {reload: true}.
If that also doesn't work create a auto executing method to initialize your controller and call that method again.
activate();
function activate(){
}
I'm experimenting with an inline-edit directive form here: http://icelab.com.au/articles/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/
I would like to know how to set the cursor in the text box that gets created when I click some text to be edited.
Here is the directive with a few minor changes to the template:
.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled" ng-click="enableEditor()">' +
'{{value}} ' +
'</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue" ng-blur="save()">' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
you need to add a link function like this :
link : function(scope, ele, attrs) {
scope.$watch('view.editorEnabled', function(n, o) {
if(n) ele.find('input')[0].focus();
})
}
I have the following markup:
<div class="row" loader="loader" address-book>
<!-- Loading complete - this should show result -->
<div loader-success></div>
<!-- Loading failed - this should show some error -->
<div loader-failure>Failure</div>
</div>
Here, address-book is the main app, and the loader is to only act as showing an animation while the page is loading. The content of the address-book would go into loader-success and the failure message (that is, if no content found) would go inside loader-failure.
So my idea was to use the following directives:
app.directive('loader', function($compile, $timeout)
{
return {
restrict: 'AE',
scope: {
loader: '&'
},
transclude: true,
template:
'<div class="row" ng-if="loading">' +
' <div class="small-15 columns text-center">' +
' <i class="fa fa-spin fa-circle-o-notch"></i>' +
' </div>' +
'</div>' +
'<div ng-transclude></div>',
link: function(scope, element, attrs)
{
scope.loading = true;
scope.failure = false;
scope.success = false;
scope.$watch('loader', function(fn) {
if (_.isFunction(fn)) {
scope.loader()
.then(function(){
scope.success = true;
scope.loading = false;
scope.$safeApply();
})
.catch(function() {
scope.failure = true;
scope.loading = false;
scope.$safeApply();
});
}
});
}
};
});
where scope.loader is the promise of the $q set from address-book. The promise is resolved/rejected after the address-book tries to get content.
For the success/failure blocks I have:
app.directive('loaderFailure', function()
{
return {
restrict: 'AE',
link: function(scope, element, attrs)
{
element.hide();
scope.$watch('failure', function(value) {
if (_.isTrue(value)) {
element.show();
}
});
}
};
});
app.directive('loaderSuccess', function()
{
return {
restrict: 'AE',
link: function(scope, element, attrs)
{
element.hide();
scope.$watch('success', function(value) {
if (_.isTrue(value)) {
element.show();
}
});
}
};
});
However, because of the isolate scope on loader, the scopes of the two children loader-success and loader-failure can no longer read the parent's. I cannot use the $compile on loader because that would then take the scopes out of the address-book's app.
What can I do?
You can use events. If you want to send some date from children to parent use $scope.$emit.
From parent to children $scope.$broadcast - can have negative performance impact
It it good approach to use events when you want to communicate between components that are loosely coupled.
See more info about events:
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit
So I ended up using $emit. However, since my loader directive can be used on multiple different components on the same page, it was important to be able to distinguish between the two. Also, the loader is added to an element with another directive running on it.
To fix these issues, this is what I did:
Failure
The channel is then used as a unique identifier. It can even be the $id of the other directive scope to ensure that it is completely unique. Now, the directives are as follows:
app.directive('loader', function($compile, $rootScope)
{
return {
restrict: 'AE',
scope: {
loader: '&',
channel : '#loaderChannel'
},
transclude: true,
template:
'<div class="row" ng-if="loading">' +
' <div class="small-15 columns text-center loader">' +
' <i class="fa fa-spin fa-circle-o-notch"></i>' +
' </div>' +
'</div>' +
'<div ng-transclude></div>',
link: function(scope, element, attrs)
{
scope.loading = true;
scope.$watch('loader', function(fn) {
if (_.isFunction(fn)) {
scope.loader()
.then(function(){
scope.loading = false;
$rootScope.$emit('LOADER_SUCCESS', scope.channel);
scope.$safeApply();
})
.catch(function() {
scope.loading = false;
$rootScope.$emit('LOADER_FAILURE', scope.channel);
scope.$safeApply();
});
}
});
}
};
});
app.directive('loaderFailure', function($rootScope)
{
return {
restrict: 'AE',
link: function(scope, element, attrs)
{
element.hide();
$rootScope.$on('LOADER_FAILURE', function(event, channel) {
if (channel == attrs.loaderFailure) element.show();
});
}
};
});
app.directive('loaderSuccess', function($rootScope)
{
return {
restrict: 'AE',
link: function(scope, element, attrs)
{
element.hide();
$rootScope.$on('LOADER_SUCCESS', function(event, channel) {
if (channel == attrs.loaderSuccess) element.show();
});
}
};
});
Basically, I $emit the success or failure with the channel. And the loader-success and loader-failure listen to these broadcasts, match their channel it and behave accordingly.
In each component then, I simply define scope.loader = $q.defer() and resolve/reject it when I need.