I am trying to write test case for my directive in anguarjs1.x
here is my directive
.directive("weeklyDirective", function($timeout) {
return {
scope: {
data: '=',
},
link: function(scope, element) {
scope.weekDays = [
{ text: 'Sun', id: 1 },
{ text: 'Mon', id: 2 },
{ text: 'Tue', id: 3 },
{ text: 'Wed', id: 4 },
{ text: 'Thu', id: 5 },
{ text: 'Fri', id: 6 },
{ text: 'Sat', id: 7 }
];
},
restrict: 'A',
templateUrl: "/flat-ui/tpls/weekly-scheduler.html",
};
})
where is my directive template
<ul style="padding: 0px;display: inline;margin: 0;list-style: none;">
<li ng-repeat="weekDay in weekDays" style="padding: 10px;display: inline;">
<input type="checkbox" value="{{weekDay.id}}" check-list="data.weeklyDetails" id="{{'chk_'+$index}}" class="ee-check"> <label class="ee-check" for="{{'chk_'+$index}}"><span></span> {{weekDay.text}}</label>
</li>
</ul>
In my weekly directive, I have used another directive which handles my checkbox list
.directive('checkList', function() {
return {
scope: {
list: '=checkList',
value: '#'
},
link: function(scope, elem, attrs) {
var handler = function(setup) {
var checked = elem.prop('checked');
var index = scope.list.indexOf(scope.value);
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push(scope.value);
} else if (!checked && index != -1) {
if (setup) elem.prop('checked', true);
else scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.bind('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
now I am trying to write test cases to test my directive
describe("weeklyDirective directive", function() {
var elm, scope, httpBackend, controller;
beforeEach(module('guideApp.directives'));
beforeEach(module('/flat-ui/tpls/weekly-scheduler.html'));
beforeEach(angular.mock.inject(function($rootScope, $compile) {
compile = $compile;
scope = $rootScope;
elm = angular.element('<div weekly-directive data="data"></div>');
compile(elm)(scope);
scope.data = {
interval: 1,
weeklyDetails: ['1'],
}
scope.$digest();
}));
it("click on check box it should get added in weeklyDetails", function() {
var e = elm.find('input[id="chk_3"]');
console.log(e);
e.trigger('click');
scope.$apply();
var isolateScope = elm.isolateScope();
expect(isolateScope.data.weeklyDetails.indexOf('4') > -1).toBeTruthy();
});
});
where I am trying to test that when user click on check box its value should get added to my array weeklyDetails which in data object (passed to the weeklydirective).
its not working as exptected for me please help me to get this working.
Thanks in Adv.
Related
I have a list of checkboxes - two parents and each parent has 5 childs.
The parents should have 3 states (checked,unchecked,indeterminate).
Right now, my code is working BUT I'm trying to add a 'select all' checkbox ,
which will select the two parents and all their childs.
What I tried to do is adding one more label above:
<label>
<input type="checkbox" data-indeterminate-checkbox data-child-
list="model.people" data-property="eaten" data-ng-
model="model.allEaten"> All eaten
</label>
but it's not working - the checkbox is not acting as expected.
Full code:
http://jsfiddle.net/wnjze03h/210/
HTML:
var app = angular.module('combo', []);
app.controller('MainCtrl', ['$scope', function($scope) {
$scope.model = {
allEaten: false,
people: [
{
name: "Bob",
fruits: [
{ type: 'Apple', eaten: false },
{ type: 'Banana', eaten: false },
{ type: 'Pear', eaten: true },
{ type: 'Tomato', eaten: false },
{ type: 'Grapefruit', eaten: true },
]
},
{
name: "Joe",
fruits: [
{ type: 'Apple', eaten: true },
{ type: 'Banana', eaten: true },
{ type: 'Pear', eaten: true },
{ type: 'Tomato', eaten: true },
{ type: 'Grapefruit', eaten: true },
]
}
]
};
}]);
/**
* Directive for an indeterminate (tri-state) checkbox.
* Based on the examples at http://stackoverflow.com/questions/12648466/how-can-i-get-angular-js-checkboxes-with-select-unselect-all-functionality-and-i
*/
app.directive('indeterminateCheckbox', [function() {
return {
scope: true,
require: '?ngModel',
link: function(scope, element, attrs, modelCtrl) {
var childList = attrs.childList;
var property = attrs.property;
// Bind the onChange event to update children
element.bind('change', function() {
scope.$apply(function () {
var isChecked = element.prop('checked');
// Set each child's selected property to the checkbox's checked property
angular.forEach(scope.$eval(childList), function(child) {
child[property] = isChecked;
});
});
});
// Watch the children for changes
scope.$watch(childList, function(newValue) {
var hasChecked = false;
var hasUnchecked = false;
// Loop through the children
angular.forEach(newValue, function(child) {
if (child[property]) {
hasChecked = true;
} else {
hasUnchecked = true;
}
});
// Determine which state to put the checkbox in
if (hasChecked && hasUnchecked) {
element.prop('checked', false);
element.prop('indeterminate', true);
if (modelCtrl) {
modelCtrl.$setViewValue(false);
}
} else {
element.prop('checked', hasChecked);
element.prop('indeterminate', false);
if (modelCtrl) {
modelCtrl.$setViewValue(hasChecked);
}
}
}, true);
}
};
}]);
.person {
margin-bottom: 20px;
}
.child-list {
margin-left: 20px;
}
<label>
<input type="checkbox" data-indeterminate-checkbox data-child-list="model.people.allEaten" data-property="eaten" data-ng-model="model.allEaten"> All eaten
</label>
<div data-ng-repeat="person in model.people" class="person">
<label>
<input type="checkbox" data-indeterminate-checkbox data-child-list="person.fruits" data-property="eaten" data-ng-model="person.allEaten"> {{person.name}} [All eaten: {{person.allEaten}}]
</label>
<div data-ng-repeat="fruit in person.fruits" class="child-list">
<label>
<input type="checkbox" data-ng-model="fruit.eaten"> {{fruit.type}}
</label>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js"></script>
remove your directive and add following html
<label>
<input style="-webkit-appearance:checkbox" type="checkbox" ng-model="model.allEaten" ng-click="selectAllEaten(model.allEaten)">
allEaten {{model.allEaten}}
</label>
<div ng-repeat="person in model.people">
<label>
<input style="-webkit-appearance:checkbox" ng-checked="person.selected" type="checkbox" ng-model="person.selected" ng-click="selectByPerson(person)">
{{person.name}}
</label>
<div ng-repeat="fruit in person.fruits" style="margin-left:20px;">
<label>
<input style="-webkit-appearance:checkbox" ng-checked="fruit.selected" type="checkbox" ng-model="fruit.eaten">
{{fruit.type}}
</label>
</div>
</div>
and in controller
$scope.selectAllEaten = function (x) {
if (x) {
$scope.model.people.forEach(function (item) {
item.selected = true;
item.fruits.forEach(function (fruit) { fruit.selected = true; });
});
} else {
$scope.model.people.forEach(function (item) {
item.selected = false;
item.fruits.forEach(function (fruit) { fruit.selected = false; });
});
}
}
$scope.selectByPerson = function(x){
if (x.selected) {
x.fruits.forEach(function (fruit) { fruit.selected = true; });
}else{
x.fruits.forEach(function (fruit) { fruit.selected = false; });
}
}
do further validations such as checking parent if all child checked , etc
Budding web developer here struggling with updating the view from my controller.
I'm using highmaps and angular to build a neat selection tool for my web app. I've got a directive nested inside the scope of a controller. I would like this directive to update a value (selectedCountry) stored in the controller. Then, I'd like the controller to display the up to date selectedCountry value on the view.
I've checked that the directive is passing the correct selectedCountry value to the parent controller. However, the controller is not updating the view to match the updated value. I would greatly appreciate if someone could take a look at this.
Demo Here: http://jsfiddle.net/frauLLmr/5/
index.html
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
app.js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function() {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
the issue is that Highcharts.mapChart(element[0], getMapOptions(mapClick)); is not part of the angular scope. So any calls here will not trigger the angular app to refresh. You need to force angular to update using $scope.apply();
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ selfc.selectedCountry);
// force angular update
$scope.$apply();
};
Try this
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
the js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function($scope) {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
$scope.$apply();
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
is it possible with this example from Ben Foster to get all nodes closed by default (at the loading of the page) ? and to open each node with ng-click ?
http://jsfiddle.net/benfosterdev/NP7P5/
I have found a way to get selected node but I don't know how to combine it with ng-click and eventually ng-show or ng-hide:
ng-click='nodeSelected($event, category)'
and in controller
$scope.nodeSelected = function($event, category){
$event.stopPropagation();
console.log('This node is selected' + category);
}
Just found a similar example :
var gyroEditor = angular.module('gyroEditor', []);
gyroEditor.controller('Ctrl', function($scope) {
$scope.nodes = [
{
title: 'Computers',
categories: [
{
title: 'Laptops',
categories: [
{
title: 'Ultrabooks'
},
{
title: 'Macbooks'
}
]
},
{
title: 'Desktops'
},
{
title: 'Tablets',
categories: [
{
title: 'Apple'
},
{
title: 'Android'
}
]
}
]
},
{
title: 'Printers'
}
];
});
gyroEditor.directive('tree', function() {
return {
restrict: 'E',
replace: true,
scope: {nodes: '=nodes'},
templateUrl: 'tree.html',
controller: function($scope) {
console.log('tree ctrl');
}
};
});
gyroEditor.directive('treenode', function() {
return {
restrict: 'E',
replace: true,
scope: {node:'=node'},
templateUrl: 'treenode.html',
controller: function($scope) {
console.log('node ctrl');
}
};
});
gyroEditor.directive("recursive", function($compile) {
return {
restrict: "EACM",
priority: 100000,
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
iElement.append(
compiledContents(scope,
function(clone) {
return clone; }));
};
}
};
});
.panel-left {
float: left;
width: 200px;
margin: 0 20px 20px 0;
}
.panel-editors {
float: right;
height: 100%;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app=gyroEditor ng-controller=Ctrl>
<script type="text/ng-template" id="treenode.html">
<li ng-init="collapsed=true">
<a ng-click="collapsed=!collapsed"><i class="fa fa-{{((collapsed || !node.categories) ? '' : '-open')}}"></i> {{node.title}}</a>
<ol ng-if="!collapsed && node.categories && node.categories.length">
<recursive>
<treenode ng-repeat="c in node.categories" node=c>
</treenode>
</recursive>
</ol>
</li>
</script>
<script type="text/ng-template" id="tree.html">
<ol>
<treenode ng-repeat="n in nodes" node=n></treenode>
</ol>
</script>
<div class=panel-left>
<tree nodes=nodes></tree>
</div>
</div>
{{node.title}}
I have this directive:
import 'jquery';
import 'jquery-ui/ui/widgets/slider';
import 'jquery-ui/themes/base/core.css';
import 'jquery-ui/themes/base/slider.css';
import 'jquery-ui/themes/base/theme.css';
import './range.css';
function range() {
return {
restrict: 'E',
scope: {
min: '#',
max: '#',
startLabel: '&',
endLabel: '&',
showLabels: '#'
},
require: 'ngModel',
template: '<div><div>{{showLabels}}</div><div class="range" ng-class="{labels: showLabels}"></div></div>',
link: function($scope, $element, $attrs, ngModelController) {
var $range = $($element).find('.range');
$scope.min = $scope.min || 0;
$scope.max = $scope.max || 100;
function updateCustomProperties(range) {
$range[0].style.setProperty("--from", (range[0] / $scope.max) * 100);
$range[0].style.setProperty("--to", (range[1] / $scope.max) * 100);
}
if (!ngModelController.$modelValue) {
ngModelController.$modelValue = [$scope.min, $scope.max]
}
$scope.$watch('startLabel', function(value) {
$range.find('.ui-slider-handle').eq(0).attr('data-value', value);
});
$scope.$watch('endLabel', function(value) {
$range.find('.ui-slider-handle').eq(1).attr('data-value', value);
});
$range.slider({
min: $scope.min,
max: $scope.max,
step: 1,
values: ngModelController.$modelValue,
slide: function(event, ui) {
function updateModel() {
ngModelController.$setViewValue(ui.values);
}
if (!$scope.$$phase) {
$scope.$apply(updateModel);
} else {
updateModel();
}
updateCustomProperties(ui.values);
}
});
updateCustomProperties(ngModelController.$modelValue);
$scope.$watch('min', function(value) {
$range.slider('option', 'min', value);
});
$scope.$watch('max', function(value, oldValue) {
$range.slider('option', 'max', value);
updateCustomProperties(ngModelController.$modelValue);
});
ngModelController.$render = function() {
if (ngModelController.$modelValue) {
$range.slider('option', 'values', ngModelController.$modelValue);
updateCustomProperties(ngModelController.$modelValue);
}
};
}
};
}
//range.$inject = [];
module.exports = range;
and this usage:
<range start-label="vm.monthYear(vm.DashboardService.fromDate)"
end-label="vm.monthYear(vm.DashboardService.toDate)"
show-labels="{{vm.MonthDiff(vm.DashboardService.fromDate, vm.DashboardService.toDate) > 2}}"
max="{{vm.range.max}}"
class="slider" ng-model="vm.range.values"></range>
when the value of show-labels change to false I get the value in directive template ({{showLabels}} is false) but the class is not removed, it's always on the DOM element when I inspect it in developer tools.
Why the class is to removed?
The interpolated value is not parsed it's a string to fix it you can call JSON.parse on the interpolated value:
function range() {
return {
restrict: 'E',
scope: {
min: '#',
max: '#',
startLabel: '&',
endLabel: '&',
showLabels: '#'
},
require: 'ngModel',
template: '<div class="range" ng-class="{labels: labels}"></div>',
link: function($scope, $element, $attrs, ngModelController) {
var $range = $($element).find('.range');
$scope.min = $scope.min || 0;
$scope.max = $scope.max || 100;
$scope.$watch('showLabels', (value) => {
$scope.labels = JSON.parse(value);
});
...
}
};
}
I am working on a project where I have one controller and two directives and I need to share the scope between them all, I have created plnkr here.
The code structure is as following:
Main Controller
--Drawable Directive
----Draw-rectangle Directive
In the Main ctrl there is one object rois on scope which I am to Drawable and Draw-rectangle directive. and on click of drawable it updates to scope of Main controller but when I click on the draw-rectangle directive it's not updating the scope.
I want to all (3) scopes to be synced using two way data binding.
It seems conceptually correct but why its not updating the scope from Draw-rectangle Directive?
Thanks in advance!
When you click "draw-rectangle" your are also clicking "drawable" because "draw-rectangle" is inside "drawable". You must stop propagation from "draw-rectangle" to "drawable" using event.preventDefault(); as folow:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
$scope.rois = [{
name: 'Jack',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
$scope.test = "Test";
});
app.directive('drawable', [
function() {
return {
restrict: "EA",
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.rois = [{
name: 'Stark',
city: 'pune'
}, {
name: 'Inc',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
app.directive('drawRectangle', [
function() {
return {
restrict: "EA",
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.stopPropagation(); // STOP PROPAGATION
event.preventDefault();
scope.rois = [{
name: 'Meuk',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller='MainCtrl' style='width: 400px;height: 400px;border: 1px solid red;'>
<div drawable rois="rois" style='width: 300px;height: 300px;border: 1px solid red;'>
<div draw-rectangle rois="rois" style='width: 200px;height: 200px;border: 1px solid red;'>
<button type="button" style='margin: 20px; border: 1px solid red;'>Click me!</button>
</div>
</div>
<br>
<br>{{rois | json}}
</div>
u need to stop bubble up the event, because when rect directive clicked, drawable also trigger click! use event.stopPropagation()
var app = angular.module('myApp');
app.directive('drawable', ['$document',
function($document) {
return {
restrict: "EA",
scope: {
rois: '='
},
link: function(scope, element, attrs) {
console.log(scope.rois);
element.on('click', function(event) {
event.stopPropagation();
scope.rois = [{
name: 'Stark',
city: 'pune'
}, {
name: 'Inc',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
app.directive('drawRectangle', ['$document',
function($document) {
return {
restrict: "EA",
scope: {
rois: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.stopPropagation();
scope.rois = [{
name: 'Meuk',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
You are using isolated scope for two directives. Isolated scope will create a child scope. So, you cannot access "rois" out of the directive's link function.
Try after you remove the isolated scope,
scope: {
rois: '='
},