Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Commit

Permalink
feat(rating): add rating directive
Browse files Browse the repository at this point in the history
Closes #337
  • Loading branch information
bekos authored and pkozlowski-opensource committed Apr 22, 2013
1 parent e75cea9 commit 6b5e636
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/rating/docs/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div ng-controller="RatingDemoCtrl">
<rating value="rate" max="10" readonly="isReadonly"></rating>

<hr/>
<pre>Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i></pre>

<hr/>
<button class="btn btn-small btn-danger" ng-click="rate = 0" ng-disabled="isReadonly">Clear</button>
<button class="btn btn-small" ng-click="isReadonly = ! isReadonly">Toggle Readonly</button>
</div>
4 changes: 4 additions & 0 deletions src/rating/docs/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var RatingDemoCtrl = function ($scope) {
$scope.rate = 7;
$scope.isReadonly = false;
};
3 changes: 3 additions & 0 deletions src/rating/docs/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Rating directive that will take care of visualising a star rating bar.

It also provides optional attribute `max` to vary the number of stars and `readonly` attribute to diasble user's interaction.
53 changes: 53 additions & 0 deletions src/rating/rating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
angular.module('ui.bootstrap.rating', [])

.constant('ratingConfig', {
max: 5
})

.directive('rating', ['ratingConfig', '$parse', function(ratingConfig, $parse) {
return {
restrict: 'EA',
scope: {
value: '='
},
templateUrl: 'template/rating/rating.html',
replace: true,
link: function(scope, element, attrs) {

var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max;

scope.range = [];
for (var i = 1; i <= maxRange; i++) {
scope.range.push(i);
}

scope.rate = function(value) {
if ( ! scope.readonly ) {
scope.value = value;
}
};

scope.enter = function(value) {
if ( ! scope.readonly ) {
scope.val = value;
}
};

scope.reset = function() {
scope.val = angular.copy(scope.value);
};
scope.reset();

scope.$watch('value', function(value) {
scope.val = value;
});

scope.readonly = false;
if (attrs.readonly) {
scope.$parent.$watch($parse(attrs.readonly), function(value) {
scope.readonly = !!value;
});
}
}
};
}]);
128 changes: 128 additions & 0 deletions src/rating/test/rating.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
describe('rating directive', function () {
var $rootScope, element;
beforeEach(module('ui.bootstrap.rating'));
beforeEach(module('template/rating/rating.html'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.rate = 3;
element = $compile('<rating value="rate"></rating>')($rootScope);
$rootScope.$digest();
}));

function getState(stars) {
var state = [];
for (var i = 0, n = stars.length; i < n; i++) {
state.push( (stars.eq(i).hasClass('icon-star') && ! stars.eq(i).hasClass('icon-star-empty')) );
}
return state;
}

it('contains the default number of icons', function() {
expect(element.find('i').length).toBe(5);
});

it('initializes the default star icons as selected', function() {
var stars = element.find('i');
expect(getState(stars)).toEqual([true, true, true, false, false]);
});

it('handles correcty the click event', function() {
var stars = element.find('i');

var star2 = stars.eq(1);
star2.click();
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, false, false, false]);
expect($rootScope.rate).toBe(2);

var star5 = stars.eq(4);
star5.click();
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, true, true, true]);
expect($rootScope.rate).toBe(5);
});

it('handles correcty the hover event', function() {
var stars = element.find('i');

var star2 = stars.eq(1);
star2.trigger('mouseover');
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, false, false, false]);
expect($rootScope.rate).toBe(3);

var star5 = stars.eq(4);
star5.trigger('mouseover');
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, true, true, true]);
expect($rootScope.rate).toBe(3);

element.trigger('mouseout');
expect(getState(stars)).toEqual([true, true, true, false, false]);
expect($rootScope.rate).toBe(3);
});

it('changes the number of selected icons when value changes', function() {
$rootScope.rate = 2;
$rootScope.$digest();

var stars = element.find('i');
expect(getState(stars)).toEqual([true, true, false, false, false]);
});

it('shows different number of icons when `max` attribute is set', function() {
element = $compile('<rating value="rate" max="7"></rating>')($rootScope);
$rootScope.$digest();

expect(element.find('i').length).toBe(7);
});

it('handles readonly attribute', function() {
$rootScope.isReadonly = true;
element = $compile('<rating value="rate" readonly="isReadonly"></rating>')($rootScope);
$rootScope.$digest();

var stars = element.find('i');
expect(getState(stars)).toEqual([true, true, true, false, false]);

var star5 = stars.eq(4);
star5.trigger('mouseover');
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, true, false, false]);

$rootScope.isReadonly = false;
$rootScope.$digest();

star5.trigger('mouseover');
$rootScope.$digest();
expect(getState(stars)).toEqual([true, true, true, true, true]);
});

});

describe('setting ratingConfig', function() {
var $rootScope, element;
var originalConfig = {};
beforeEach(module('ui.bootstrap.rating'));
beforeEach(module('template/rating/rating.html'));
beforeEach(inject(function(_$compile_, _$rootScope_, ratingConfig) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.rate = 5;
angular.extend(originalConfig, ratingConfig);
ratingConfig.max = 10;
element = $compile('<rating value="rate"></rating>')($rootScope);
$rootScope.$digest();
}));
afterEach(inject(function(ratingConfig) {
// return it to the original state
angular.extend(ratingConfig, originalConfig);
}));

it('should change number of icon elements', function () {
expect(element.find('i').length).toBe(10);
});

});

3 changes: 3 additions & 0 deletions template/rating/rating.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span ng-mouseleave="reset()">
<i ng-repeat="number in range" ng-mouseenter="enter(number)" ng-click="rate(number)" ng-class="{'icon-star': number <= val, 'icon-star-empty': number > val}"></i>
</span>

0 comments on commit 6b5e636

Please sign in to comment.