how to test Bootstrap Modal in AngularJS using jasmine? - angularjs

I am using bootstrap modal for displaying pop up's in angular app. It is working perfectly from front end but DOM elements inside pop up are not getting appended to the body
In the output, when I am displaying content of document, content of my html template is not getting appended to body tag. So I am unable to find/test any DOM element. It is not displaying any errors in console also.
Please any one can help me in resolving this issue by specifying how to test bootstrap modal in angular application using karma/jasmine.
angular.module("myApp").directive('popUp', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'A',
replace: true,
scope: {
course: '=',
},
compile: function(element, cAtts){
var template,
$element,
loader;
var windowOpen = false;
loader = $http.get('components/popUp.view.html').success(function(data) {
template = data;
});
//return the Link function
return function(scope, element, lAtts) {
element.on('click', function(e) {
e.preventDefault();
$element = $( $compile(template)(scope) );
$element.modal({backdrop: 'static'});
windowOpen = true;
});
// if the template changes, we need to compile the current
// template again. just in case there are funky sticky things
scope.$watch('template', function(newValue, oldValue) {
if(newValue == undefined && oldValue == undefined) return;
if(windowOpen) return;
if(newValue == undefined){
//$(".modal").remove();
$element.remove();
$element = undefined;
return;
}
$element = $( $compile(template)(scope) );
});
};
}
}
}]);
My HTML Template:
<div id="{{handler}}" class="modal fade">
<div style="margin:-1px auto; width:90%;float:right;" class="modal-dialog">
<div style="padding-left: 25px; height:100vh;" class="modal-content">
<div class="modal-header navbar-static-top"><img src="content/assets/img/close.png" data-dismiss="modal" aria-hidden="true" ng-click="close()" title="Close" alt="Close" class="popUpClose close pull-left"/>
<div class="popUpTitle">
<div style="font-size:22px;">{{course.CourseName}}</div>
<div style="font-size:15px; margin-top:-5px;">{{course.CourseId}}</div><br/>
</div>
</div>
<div style="display:inline" class="modal-body">
<p>This is test Modal</p>
</div>
<div style="border:none;" class="modal-footer"> </div>
</div>
</div>
</div>
My Test File:
(function(){
"use strict";
describe("Testing Pop Up Directive Functionality", function(){
var $httpBackend, $scope, fakeData, $compile, $document;
var compileDirective, course, element, template;
beforeEach(module('myApp.testing'));
beforeEach(module('myApp'));
beforeEach(inject(function ($injector, _$templateCache_, _$httpBackend_, _fakeData_, _$document_) {
$httpBackend = _$httpBackend_;
fakeData = _fakeData_;
$document = _$document_;
angular.module('components/popUp.view.html')._runBlocks[0](_$templateCache_);
template = _$templateCache_.get('components/courses/course.attendance.popUp.view.html');
course = fakeData.fakeCourses.Courses[0];
compileDirective = function() {
inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
$scope.course=course;
element = angular.element('<div manageattendance="" course="course"></div>');
element = $compile(element)($scope);
});
//$scope.$digest();
};
}));
it("Should display attendance of course if user is authorized", function(){
$httpBackend.expectGET('components/courses/course.attendance.popUp.view.html').respond(template);
compileDirective();
$httpBackend.flush();
element.trigger('click');
console.log($document.find('html').html());
});
});
})();
AngularJS : 1.3.11
Bootstrap : 3.3.2
Karma : 0.12.31
Jasmine-core : 2.3.4
karma-jasmine : 0.3.5

I am not sure what is the cause but by removing css class "fade" from modal div, html content from modal is getting appended to the body tag.

Related

Object not assignable in directive

In this plunk I have a directive that wraps a div. The div is shown when an ng-if condition is true (set with the click of a button).
The directive has a scope element css that is an object, where the object has an attribute width. Problem is that Angular complains when the directive is shown; see in the console the following error message when the button is clicked:
Expression '{ width: width}' in attribute 'css' used with directive
'modal' is non-assignable!
Note that this problem goes away when the $timeout in the directive is removed, but I cannot discard it.
Why does this happen and how to fix it (keeping the $timeout)?
HTML
<button ng-click="open()">Open modal</button>
<div modal ng-if="showModal" css="{ width: width}">
<p>some text in modal</p>
</div>
Javascript
angular.module("app", [])
.controller('ctl', function($scope) {
$scope.width = '200px';
$scope.open = function(){
$scope.showModal = true;
};
})
.directive("modal", function($timeout) {
var directive = {};
directive.restrict = 'EA';
directive.scope = { css: '=' };
directive.templateUrl = "modal.html";
directive.link = function (scope, element, attrs) {
$timeout(function(){
scope.css.height = '100%';
},100);
};
return directive;
});
Template
<style>
#modaldiv{
border:2px solid red;
}
</style>
<div id="modaldiv" ng-style="{'width': css.width,'height': css.height}">
Some content
</div>
The error appears since you are not passing a scope variable to your css attribute.
You can fix this by creating a variable that holds your css in ctrl and pass this variable to the css attribute.
Controller
$scope.css = {width: $scope.width};
HTML
<div modal ng-if="showModal" css="css">
<p>some text in modal</p>
</div>
Or alternatively create a local deep copy of css in the directive and manipulate the copy in your $timeout.
Directive
directive.link = function (scope, element, attrs) {
scope.cssCopy = angular.copy(scope.css);
$timeout(function(){
scope.cssCopy.width = '100%';
}, 100);
};
Template
<div id="modaldiv" ng-style="{'width': cssCopy.width,'height': cssCopy.height}">
Some content
</div>

Image is not changing even i changed image

In angularjs if i change or remove image it does not make any changes. After refreshing the page it show the changed image.I am using following line to remove image
jQuery
$('#profile_image').val('')
This Sample is basic if you want to know more about AngularJs click on link
var app = angular.module("app", []);
app.controller("ctrl", [
"$scope",
function($scope) {
$scope.imgShow = true;
$scope.imgSrc = "https://www.gravatar.com/avatar/75025ad9a8cfdaa5772545e6e8f41133?s=32&d=identicon&r=PG&f=1";
$scope.display = function() {
$scope.imgShow = true;
$scope.imgSrc = "https://www.gravatar.com/avatar/75025ad9a8cfdaa5772545e6e8f41133?s=32&d=identicon&r=PG&f=1";
}
$scope.change = function() {
$scope.imgSrc = "https://www.gravatar.com/avatar/fccd71b79b3571b459cdfe40e7bf5dd8?s=32&d=identicon&r=PG&f=1";
}
$scope.remove = function() {
$scope.imgShow = false;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<img ng-src="{{imgSrc}}" ng-show="imgShow">
<hr/>
<button ng-click="display()" ng-hide="imgShow">display image</button>
<button ng-click="change()">change image</button>
<button ng-click="remove()">remove image</button>
</div>
If you want to manipulate the DOM in AngularJS you should use directives for this purpose.
You should avoid using jquery inside of your controllers and simply work with your model. Below is fileUpload directive that could help you to work with <input type="file"> in a more angularjs-way:
angular.module('myApp', [])
.controller('TestController', ['$scope', function ($scope) {
var ctrl = this;
ctrl.imageFile = null;
ctrl.clearFile = clearFile;
function clearFile(){
ctrl.imageFile = null;
}
$scope.$ctrl = ctrl;
}])
.directive('fileUpload', [function () {
return {
require: "ngModel",
restrict: 'A',
link: function ($scope, el, attrs, ngModel) {
function onChange (event) {
//update bindings with $applyAsync
$scope.$applyAsync(function(){
ngModel.$setViewValue(event.target.files[0]);
});
}
//change event handler
el.on('change', onChange);
//set up a $watch for the ngModel.$viewValue
$scope.$watch(function () {
return ngModel.$viewValue;
}, function (value) {
//clear input value if model was cleared
if (!value) {
el.val("");
}
});
//remove change event handler on $destroy
$scope.$on('$destroy', function(){
el.off('change', onChange);
});
}
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//code.angularjs.org/1.3.20/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="TestController">
<input type="file" file-upload ng-model="$ctrl.imageFile" />
<input type="button" ng-click="$ctrl.clearFile()" value="Reset" />
<div ng-if="$ctrl.imageFile">
{{$ctrl.imageFile.name}}<br />
{{$ctrl.imageFile.size}} byte(s)<br/>
{{$ctrl.imageFile.type}}
</div>
</div>
</div>
UPDATE: Also take a look at this useful reading.

$templateCache to fancybox, how to bind variables to controller scope

I have been struggling for a coupe of hours trying to get fancybox to display html template from $templateCache. It all works fine except for the annoying fact the the data binding does not work and I'm not sure how to solve it.
<div ng-controller="MyCtrl">
Hello, {{ templateVariable }}!
<script type="text/ng-template" id="testTemplate.html">
<h1>{{ templateVariable }}</h1>
<p>Bla bla bla</p>
</script>
<br /><br />
<a href="#" show-template>Show template</a>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('showTemplate', function($templateCache, $compile, $parse) {
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
element.bind('click', function() {
var template = $templateCache.get('testTemplate.html');
var compiled = $compile(template)(scope);
$.fancybox.open(template);
});
}
};
});
myApp.controller('MyCtrl', function($scope) {
$scope.templateVariable = 'My template variable';
});
JSFiddle:
http://jsfiddle.net/oligustafsson/p4f7mh19/
Anyone have any insights to how to accomplish this feat?
To answer my own question, this is what I came up with:
<div ng-controller="MyCtrl">
Hello, {{ templateVariable }}!
<script type="text/ng-template" id="testTemplate.html">
<div>
<h1>{{ templateVariable }}</h1>
<p>Bla bla bla</p>
<div>Mooo</div>
</div>
</script>
<br /><br />
Show template
</div>
I wrapped the template html in a div.
var myApp = angular.module('myApp',[]);
myApp.directive('showTemplate', function($templateCache, $compile, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
element.bind('click', function() {
$timeout( function(){
var template = $templateCache.get('testTemplate.html');
var linkFn = $compile(template);
var linkedContent = linkFn(scope);
$.fancybox.open(linkedContent);
}, 0)
});
}
};
});
myApp.controller('MyCtrl', function($scope) {
$scope.templateVariable = 'My template variable';
});
Finding some other suggestions like using $timeout and $compile, this seems to work just fine.
JSFiddle: http://jsfiddle.net/oligustafsson/vpbutty0/
Thanx!

Angular Directive function call on ng-change

I have an angular-bootstrap modal popup box that contains a custom directive in the template:
Modal.html
<div class="modal-header">
<h3 class="modal-title">Modal Header</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<my-directive ng-model="test" ng-change="doSomething()" />
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="upload()">Upload</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
ModalCtrl.js
app.controller('ModalCtrl', function ModalCtrl( $scope, $modalInstance ) {
$scope.test = 'empty';
$scope.upload = function() {
$scope.test = 'change!';
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
});
I open the modal popup from my main controller like this:
MainCtrl.js
var app = angular.module( 'myApp', [ 'ui.bootstrap' ] );
app.controller('MainCtrl', function MainCtrl( $scope, $modal ) {
$scope.openModal = function() {
var modalInstance = $modal.open({
templateUrl: '/assets/modals/Modal.html',
controller: 'ModalCtrl',
size: 'md'
});
};
});
As you can see from the above code, the modal template contains my custom directive <my-directive> which takes $scope.test - defined in the ModalCtrl - as it's model and should call $scope.doSomething() when that model changes.
The custom directive looks like this:
MyDirective.js
angular.module( 'myApp' ).directive( 'myDirective', function() {
return {
controller: function( $scope ) {
$scope.doSomething = function() {
console.log( 'doSomething() called! $scope.test now = ' + $scope.test );
};
},
link: function( $scope, el, att ) {
console.log( '$scope.test = ' + $scope.test );
},
template: '<div class="thumbnail"></div>'
};
} );
When the modal popup opens the console prints out $scope.test = empty which is exactly what I expect to see. But when $scope.test is changed by calling the $scope.upload() function in ModalCtrl nothing happens! What I think should happen is $scope.doSomething() is called which then should print out in the console doSomething() called! $scope.test now = change!.
Any help would be much appreciated, this is driving me mad!
I figured it out. Instead of using ng-model and ng-change I added:
scope: {
test: '#'
}
to my directive and added this into my link function:
$scope.$watch( 'test', function() {
console.log( 'test changed' );
} );
Then I add test as a parameter in my directives tag like so:
<my-directive test="test" />
and finally whenever I change $scope.test I call $scope.$apply() and voila!

Angular placing functions

I'm using the angular-google-maps library in my project. I have used a directive to load a custom google maps menu. The goal is to obviously reuse the directive. In the menu are a couple of buttons which when clicked should all carry out a function. I'm still trying to get my head around on how to do that, so here is my problem:
I would like to pan the map to its original position when the button "Home" is clicked. Normally that is just done with ng-click and the function is placed within the scope of the controller. With the directive I'm confused. Where should I place the "home()" function? Directive? Directive controller? Controller? I hope this makes any sense?!?!
HTML:
<div class="map_canvas">
<google-map center="map.center" zoom="map.zoom" draggable="true">
<marker ng-repeat="m in map.markers" coords="m" icon="m.icon" click="onMarkerClicked(m)">
<marker-label content="m.name" anchor="50 0" class="marker-labels"/>
<window ng-cloak coords="map.center" isIconVisibleOnClick="false" options="map.infowindows.options">
<p>This is an info window at {{ m.latitude | number:4 }}, {{ m.longitude | number:4 }}!</p>
<p class="muted">My marker will stay open when the window is popped up!</p>
</window>
</marker>
<map-custom-control position="google.maps.ControlPosition.TOP_CENTER" control-template="../templates/gmaps/main_menu.html" control-click=""></map-custom-control>
</google-map>
</div>
Template:
<div class="gmaps-menu">
<div class="gmaps-row">
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_020_home.png" ng-click="home()"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_349_fullscreen.png"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_096_vector_path_polygon.png"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_030_pencil.png"></button>
</div>
</div>
Directive:
AppDirectives.directive('mapCustomControl', ['$log', '$timeout', '$http', '$templateCache', 'google', 'GMapsLib' ,function ($log, $timeout, $http, $templateCache, google,GMapsLib) {
return {
restrict: 'E',
replace: true,
require: '^googleMap',
link: function(scope,element,attr,mapCtrl){
if (!angular.isDefined(attr.controlTemplate)) {
$log.error('map-custom-control: could not find a valid control-template property!');
return;
}
var templateUrl = attr.controlTemplate;
var position = google.maps.ControlPosition.TOP_CENTER;
if (angular.isDefined(attr.position)) {
var EVAL_IS_OK_WE_CONTROL_THE_INPUT = eval;
position = EVAL_IS_OK_WE_CONTROL_THE_INPUT(attr.position);
}
$timeout(function() {
var map = mapCtrl.getMap();
var controlDiv = document.createElement('div');
controlDiv.style.padding = '5px';
controlDiv.style.width = 'auto';
controlDiv.marginLeft = 'auto';
controlDiv.marginRight = 'auto';
$http.get(templateUrl, {cache: $templateCache})
.success(function(html) {
controlDiv.innerHTML = html;
})
.then(function (/*response*/) {
map.controls[position].push(controlDiv);
if (angular.isDefined(attr.controlClick)) {
google.maps.event.addDomListener(controlDiv, 'click', function() {
scope.$apply(attr.controlClick);
});
}
}
);
});
}
};
}]);
You can pass the scope function that has to be executed on the controller:
HTML
<div ng-app="app" ng-controller="sampleCtrl">
<maps-custom-control click-handler="alertMe()"></maps-custom-control>
</div>
JS
var app = angular.module('app', []);
app.directive('mapsCustomControl', function() {
return {
restrict: 'EA',
replace: true,
scope: {
clickHandler: '&'
},
template: '<div style="width: 100px; height:100px; background-color: red;" ng-click="clickHandler()"></div>'
};
});
app.controller('sampleCtrl', function ($scope) {
$scope.alertMe = function () {
window.alert('Refresh gMaps control');
};
});
Since we pass the alertMe function, this is the function that will get executed, I hope this makes sense?
Fiddle
A small remark on your code, it would be better if you get the template as follows:
app.directive('..', function() {
return {
template: '<div ng-include="getTemplate()"></div>',
link: function(scope, element, attr) {
scope.getTemplate = function() {
return this.attr.controlTemplate;
}
}
};
});
This way you don't need to do any strange ajax calls. Just add all the mark-up in your template and include it. don't make it necessary hard :-)

Resources