Capture hide event in $modal of angular strap - angularjs

I am using angular Strap to create a modal like :
$modal({
template : "/templ/alert-with-title.html",
content : content,
title : title,
show : true,
backdrop : true,
placement : 'center'
});
I have the written the following :
$scope.$on("modal.hide.before",function() {
console.log("Closing1");
});
$scope.$on("modal.hide",function() {
console.log("Closin2");
});
My /templ/alert-with-title.html is like this :
<div aria-hidden="true" aria-labelledby="windowTitleLabel" role="dialog"
tabindex="-1" class="modal hide fade in modal" id="">
<div class="modal-header">
<a class="fui-cross pull-right" ng-click="$hide()"></a>
<h3 ng-bind="title"></h3>
</div>
<div class="modal-body">
<div class="divDialogElements" >
<span ng-bind="content"></span>
</div>
</div>
<div class="modal-footer">
<div>
<button type="button" ng-click="$hide()"
class="btn btn-default btn-gray-l gray pull-left mar_t-4">OK</button>
</div>
</div>
</div>
However even after all this, I get no console logs when i click Ok. Why is this?

so the solution is very simple, I had to provide the scope to the $modal.
$modal({
template : "/templ/alert-with-title.html",
content : content,
title : title,
show : true,
backdrop : true,
placement : 'center',
scope : $scope
});
But what I do not understand that why for an event that is "$emit" , $on of the outside scope would not work

$emit and $broadcast are angular event handling mechanisms are distinct from events that are found in pure JavaScript. The latter traverse the DOM of your web page. the $event in angular traverses the scope hierarchy present in your module. With that being said here is an excerpt from the source code of angular-strap modal :
function ModalFactory(config) {
var $modal = {};
// Common vars
var options = $modal.$options = angular.extend({}, defaults, config);
var promise = $modal.$promise = $bsCompiler.compile(options);
var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
the parameters you pass as the argument for your $modal service is the config object. the default object contains the default values for the parameters. The line of interest is the last line .
There it checks wether you have provided a scope object as one of the parameters. If so then a child of that scope is created via scope.$new. Else it creates a scope which is the child of the top most scope in the heirarchy.
Therfore any events which are bubbled up via $emit, from this particular scope can only be caught by the $rootScope.
In the code you posted in the question you did not provide any scope object in the parameters. Hence a child of the $rootScope is created, not of the current $scope you were working in. In the second code you posted , a child scope of your current $scope is created. That is the reason why you are able to handle the 'model.hide' and other events from your current $scope
Hope this helps :)

Related

Trigger bootstrap modal by AngularJs and then get data by $http.post

I have a list of customers each customer have button more info.
I want , when i click on it then showing bootstrap modal by AngularJs controller and then request data by $http.post and getting some more info about this customer and showing info inside modal.
How can i do this purpose ?
this button :
<button type='button' class='btn btn-primary btn-sm'
data-ng-click='moreinfo(customer.id)' >more info</button>
You can first pass each customer info variable to each more info.
Button like this :
<button type='button' class='btn btn-primary btn-sm btnmargin'
data-toggle='modal' data-target='#cInfo' data-ng-click='moreinfo(customer)'
>more info</button>
then you should write this code inside controller :
$scope.customerinfo=[];
$scope.moreinfo= function(customer){
$scope.customerinfo= customer;
};
Html bootstrap modal :
<!-- Modal start -->
<div class='modal fade' id='cinfo' tabindex='-1' role='dialog'
aria-labelledby='myModalLabel' aria-hidden='true'>
<div class='modal-dialog modal-lg' role='document'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'>
<span aria-hidden='true'>×</span>
<span class='sr-only'>Close</span></button>
<h4 class='modal-title text-danger'
id='myModalLabel'>customer info</h4>
</div>
<div class='modal-body'>
{{customerinfo.firstName}}
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-default'
data-dismiss='modal'>close</button>
</div>
</div>
</div>
</div>
<!-- Modal end -->
Now you can click on each row button more info and see info in inside modal body.
Use ngDialog instead of bootstrap modal.
It is easy to implement in angularjs and you can have different controller for it as well and you can definitely transfer data from main page to this ngDialog.
https://github.com/likeastore/ngDialog
I will suggest you to go with ui-bootstrap but looking at other answers and considering you do not want to add any more JS library/plugin
Hope this helps you
Add a directive called bootstrap-modal as following
app.directive('bootstrapModal', ['$rootScope', '$http', function ($rootScope, $http) {
"use strict";
return {
restrict: "A",
//add isolated scope if you want
//scope: {
//},
link: function (scope, element) {
scope.$on('showModal', function (event, object) {
//fire your ajax here
$http.get('url').then(function(response){
//process your response alter DOM and show modal
element.modal('toggle');
});
});
}
};
}]);
and in your moreInfo function in controller
$scope.moreInfo = function(){
$rootScope.$broadCast('showModal', dataToPassToListener)
}
You should use the directive with the div which you want to show as modal. As in the same div where you would have given role="dialog" if you would have used simple bootstrap.js
I know that you don't want more JS plugin but I suggest you to use the UI Bootstrap for Angularjs:
https://angular-ui.github.io/bootstrap/
It's basically a set of pre-defined directives you can use to load Bootstrap component.
In your case, the thing can end like that:
<button type="button" class="btn btn-primary" ng-click = "moreinfo(customer.id)"> More Info </button>
In your controller :
angular.module('myApp').controller('CustomerInfoCtrl',['$uibModalInstance','$scope', function($uibModalInstance,$scope){
$scope.moreinfo = function(id){
var InfoModal = $uibModalInstance.open({
templateUrl : 'route/to/my/template.html,
controller: 'MoreInfoCtrl',
scope: $scope,
resolve: {
customerId : function(){
return id;
}
}
});
InfoModal.result.then(function(){
//callback when modal closed
},function(){
//callback when clicked on cancel to dismiss the modal
});
}]);
Then you create another controller, MoreInfoCtrl:
angular.module('myApp').controller('MoreInfoCtrl',['$http','$scope','id', function($http, $scope, id){
//Do your http call with the variable id (i.e the customer.id )
}]);
You have plenty of options. You can easily pass variables, scope or do callback process.
I'm using it a lot in a project and it really helps a lot.
I suggest you to try it. And it's not really heavy (from above link):
Whichever method you choose the good news that the overall size of a
download is fairly small: 122K minified for all directives with
templates and 98K without (~31kB with gzip compression, with
templates, and 28K gzipped without)

Communications between nested controllers

I am new to angular, searched for a good solution for the below but couldnt find a good option.
I have an extremelly sipmple modal dialog controlled by ModalDialogCtrl that contains an edited object, such as Rabbit or Dog or Cat or anything else. I want same functionality for any object allowing Save when user presses "Save" button.
Dialog's viewmodel has a nested view for the object being edited whose template name is substituted depending on the type of edited object. This specific view contains object-specific controller.
Modal controller:
function ModalDialogCtrl($scope) {
// $scope.objectSpecificViewModelTemplate = "rabbit.html";
// or
// $scope.objectSpecificViewModelTemplate = "dog.html";
// etc
ctrl.save = function () {
// need to call inner object controller's save() method here
};
ctrl.cancel = function () {
// cancel editing
};
};
Modal dialog view:
<div class="modal-header">
<!-- Modal header -->
</div>
<div class="modal-body" id="modal-body">
<!-- Modal body containing object-specific view model -->
<div ng-include src="objectSpecificViewModelTemplate"></div>
</div>
<div class="modal-footer">
<!-- Modal buttons -->
<button class="btn btn-primary" type="button">OK</button>
<button class="btn btn-warning" type="button">Cancel</button>
</div>
Object-specific view templates:
<div ng-controller="RabbitCtrl">
<p>Weight: <input type="text" ng-model="rabbit.weight" /></p>
</div>
or
<div ng-controller="DogCtrl">
<p>Color: <input type="text" ng-model="dog.color" /></p>
</div>
Object-specific controllers:
function RabbitCtrl($scope) {
$scope.rabbit = { weight: 5}
$scope.save = function() { /* save to server */ };
}
function DogCtrl($scope) {
$scope.dog = { dog: "black"}
$scope.save = function() { /* save to server */ };
}
What I need is to call inner object's save() method when user presses Save button. And I want the modal controller and object-specific controllers be decoupled as I might want to reuse them in different spots of the application. So I think gennerally my question looks like: how to make parent controller to call specific nested controller method (there can be many nested controllers) or how to make inner controller to call specific parent's controller method?
I see too ways here:
Use events.
Use require. In child controller you require parent and call i.e.:
parentCtrl.register(childCtrl)
Now in parentCtrl you store link to child:
vm.register = function(child) {
vm.containedComponent = child;
}
and can call any method of it. (i.e. onSave)
(This is not that bad if you sure that your parent will always have exactly one child, however if child may change using ng-if you will need to manually unregister it)

Why does adding a show/hide feature break my AngularJS code?

Starting with the following working fiddle:
http://jsfiddle.net/77vXu/14/
I added a few changes to add a show/hide button
http://jsfiddle.net/77vXu/27/
var myApp = angular.module('myApp', []);
myApp.controller('test', function($scope) {
$scope.show = false;
$scope.cancelMessage = '';
$scope.clickTest = function(){
alert($scope.cancelMessage);
};
$scope.toggleShow = function(){
$scope.show = !$scope.show;
}
});
But this completely breaks the character counter. What have I done wrong?
From angularjs :Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
Solution 1.
Please remove ng-if from textarea see here : http://jsfiddle.net/Tex3P/
<div ng-app="myApp">
<div ng-controller="test">
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<div ng-if="show">
<textarea ng-model="cancelMessage" ></textarea>
<span > {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()" ng-if="show">clickTest</button>
</div>
</div>
</div>
Solution 2.
Define cancelMessage as a object. http://jsfiddle.net/cnre6/
<div ng-app="myApp">
<div ng-controller="test">
<p>f{{cancelMessage}}</p>
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<textarea ng-model="cancelMessage" ng-if="show"></textarea>
<span ng-if="show"> {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()" ng-if="show">clickTest</button>
</div>
</div>
var myApp = angular.module('myApp', []);
myApp.controller('test', function ($scope) {
$scope.show = false;
$scope.cancelMessage = {};
$scope.clickTest = function () {
alert($scope.cancelMessage);
};
$scope.toggleShow = function () {
$scope.show = !$scope.show;
}
});
The reason it does not work is because of the way scope variables behave when they're assigned within a child scope and your model does not have a '.' in it. ng-if creates a child scope and since your ng-model does not have a '.' in it it will assign a scope variable named 'cancelMessage' in the child scope that shadows the scope variable in the 'test' controller's scope with the same name - effectively breaking two-way model binding as soon as text is entered in the textarea.
To fix this, you should have a '.' in your ng-model:
<textarea ng-model="cancelMessage.test" ng-if="show"></textarea>
By having a '.', angular will resolve what's left of the dot first, and will find the reference defined in the 'test' controller. It then binds the 'test' property of the 'cancelMessage' model.
The important point is, binding is resolving to the same model (the model which is defined on the 'test' controller's scope.
Infamous Dot in ng-Model (by Design)
Demo Plunker
If you refer to AngularJS documentation on ng-if, it says
"The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM, otherwise a clone of the element is reinserted into the DOM." (https://docs.angularjs.org/api/ng/directive/ngIf)
One thing you can do is hide/show it instead of deleting it from DOM using ng-show or ng-hide
I demonstrate this in this fiddle : http://jsfiddle.net/lookman/0rfz6d1v/
<div ng-app="myApp">
<div ng-controller="test">
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<div ng-show="show">
<textarea ng-model="cancelMessage" ></textarea>
<span > {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()">clickTest</button>
</div>
</div>
</div>

Using AngularJS, how do I set $scope properties in the controller of the parent page

Thanks for looking.
I have the following markup for a modal which shares the same angular controller as it's parent page:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<div class="row" ng-controller="EventCreateController">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
controller: 'EventCreateController',
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
Please note the Event.Item.VideoUrl model reference.
The modal allows a user to set the URL of a video, and the goal is to have that set $scope.Event.Item.VideoUrl in the controller and then close the modal. The parent page and the modal both share the same controller, so I had hoped that this would work.
The modal behavior is fine (opens and closes as it should), but the $scope.Event.Item.VideoUrl property is not getting set.
Any advice is appreciated.
Problem Solved!
Thanks to Bogdan Savluk, I realized that I had a scope inheritance problem. So, removing both the explicit reference to the controller in the modal HTML as well as in the JavaScript constructor, resolved my problem:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<!-- <div class="row" ng-controller="EventCreateController"> <--REMOVE THIS! -->
<div class="row">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
//controller: 'EventCreateController', <--REMOVE THIS!!
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
If you are passing scope to $modal.open() than scope for modal would be created as child scope from passed scope... - so you will have access to all properties from it.
But in case when you are passing the same controller to it - that controller would be applied to new scope and will override all properties from parent.
So in general, as I see the only thing you need to do to achieve desired result is to remove controller from configuration passed to $modal.open() or replace it with something that is specific only for that modal.

Why this doesnt fire ng-repeat?

My HTML
ng-app and ng-controller are specified in markup earlier
<div class="statusEntry" ng-repeat="statusInput in statusInputs">
<span class="userName"> a </span>
<span class="statusMsg"> b </span>
</div>
Controller
app.controller('globalCtrl', ['$scope', function($scope) {
//someWork
pubnub.subscribe({
channel: "statuses",
callback:
function (data) {
splitData = data.split(';');
prepData = '{'+splitData[0]+','+splitData[1]+'}';
statusInputs.push(prepData);
}
});
When I push the data no new object appears.
Your Controller has no name.
You haven't declare an ng-app or ng-controller in your markup anywhere.
data should be named $scope so Angular can appropriately inject the dependency.
It doesn't look like either statusInputs or your function are part of the $scope therefore there's no way for your view to access them.
Replace
statusInputs.push(prepData);
with
$scope.statusInputs.push(prepData);
This is how you enable your views to access them.

Resources