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 support for custom icons per instance
Browse files Browse the repository at this point in the history
  • Loading branch information
bekos authored and pkozlowski-opensource committed Aug 24, 2013
1 parent e55d906 commit 20ab01a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 15 deletions.
12 changes: 8 additions & 4 deletions src/rating/docs/demo.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<div ng-controller="RatingDemoCtrl">
<div ng-controller="RatingDemoCtrl" class="well well-small">
<h4>Default</h4>
<rating value="rate" max="max" readonly="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null"></rating>
<span class="badge" ng-class="{'badge-warning': percent<30, 'badge-info': percent>=30 && percent<70, 'badge-success': percent>=70}" ng-show="overStar && !isReadonly">{{percent}}%</span>

<hr/>
<pre>Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || "none"}}</b></pre>
<pre style="margin:15px 0;">Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || "none"}}</b></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>
<hr />

<h4>Custom icons</h4>
<div ng-init="x = 5"><rating value="x" max="15" state-on="'icon-ok-sign'" state-off="'icon-ok-circle'"></rating> <b>(<i>Rate:</i> {{x}})</b></div>
<div ng-init="y = 2"><rating value="y" rating-states="ratingStates"></rating> <b>(<i>Rate:</i> {{y}})</b></div>
</div>
8 changes: 8 additions & 0 deletions src/rating/docs/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ var RatingDemoCtrl = function ($scope) {
$scope.overStar = value;
$scope.percent = 100 * (value / $scope.max);
};

$scope.ratingStates = [
{stateOn: 'icon-ok-sign', stateOff: 'icon-ok-circle'},
{stateOn: 'icon-star', stateOff: 'icon-star-empty'},
{stateOn: 'icon-heart', stateOff: 'icon-ban-circle'},
{stateOn: 'icon-heart'},
{stateOff: 'icon-off'}
];
};
36 changes: 35 additions & 1 deletion src/rating/docs/readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
Rating directive that will take care of visualising a star rating bar.

It also provides optional attributes: `max` to vary the number of stars, `readonly` to disable user's interaction, `on-hover` to signal when the user's mouse is over a particular star, and `on-leave` to signal when the mouse leaves the control altogether.
### Settings ###

#### `<rating>` ####

* `value` <i class="icon-eye-open"></i>
:
The current rate.

* `max`
_(Defaults: 5)_ :
Changes the number of icons.

* `readonly`
_(Defaults: false)_ :
Prevent user's interaction.

* `on-hover(value)`
:
An optional expression called when user's mouse is over a particular icon.

* `on-leave()`
:
An optional expression called when user's mouse leaves the control altogether.

* `state-on`
_(Defaults: 'icon-star')_ :
A variable used in default template to specify the class for selected icons.

* `state-off`
_(Defaults: 'icon-star-empty')_ :
A variable used in default template to specify the class for unselected icons.

* `rating-states`
_(Defaults: null)_ :
An array of objects defining properties for all icons. In default template, `stateOn` & `stateOff` property is used to specify the icon's class.
39 changes: 32 additions & 7 deletions src/rating/rating.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
angular.module('ui.bootstrap.rating', [])

.constant('ratingConfig', {
max: 5
max: 5,
stateOn: 'icon-star',
stateOff: 'icon-star-empty'
})

.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {

this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;

$scope.range = [];
for (var i = 1; i <= this.maxRange; i++) {
$scope.range.push(i);
}
this.createDefaultRange = function(len) {
var defaultStateObject = {
stateOn: this.stateOn,
stateOff: this.stateOff
};

var states = new Array(len);
for (var i = 0; i < len; i++) {
states[i] = defaultStateObject;
}
return states;
};

this.normalizeRange = function(states) {
for (var i = 0, n = states.length; i < n; i++) {
states[i].stateOn = states[i].stateOn || this.stateOn;
states[i].stateOff = states[i].stateOff || this.stateOff;
}
return states;
};

// Get objects used in template
$scope.range = angular.isDefined($attrs.ratingStates) ? this.normalizeRange(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createDefaultRange(this.maxRange);

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

$scope.value = value;
};

$scope.enter = function(value) {
Expand Down
53 changes: 51 additions & 2 deletions src/rating/test/rating.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ describe('rating directive', function () {
return getStars().eq( number - 1 );
}

function getState() {
function getState(classOn, classOff) {
var stars = getStars();
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')) );
state.push( (stars.eq(i).hasClass(classOn || 'icon-star') && ! stars.eq(i).hasClass(classOff || 'icon-star-empty')) );
}
return state;
}
Expand Down Expand Up @@ -124,12 +124,57 @@ describe('rating directive', function () {
expect($rootScope.leaving).toHaveBeenCalled();
});

describe('custom states', function() {
beforeEach(inject(function() {
$rootScope.classOn = 'icon-ok-sign';
$rootScope.classOff = 'icon-ok-circle';
element = $compile('<rating value="rate" state-on="classOn" state-off="classOff"></rating>')($rootScope);
$rootScope.$digest();
}));

it('changes the default icons', function() {
expect(getState($rootScope.classOn, $rootScope.classOff)).toEqual([true, true, true, false, false]);
});
});

describe('`rating-states`', function() {
beforeEach(inject(function() {
$rootScope.states = [
{stateOn: 'sign', stateOff: 'circle'},
{stateOn: 'heart', stateOff: 'ban'},
{stateOn: 'heart'},
{stateOff: 'off'}
];
element = $compile('<rating value="rate" rating-states="states"></rating>')($rootScope);
$rootScope.$digest();
}));

it('should define number of icon elements', function () {
expect(getStars().length).toBe($rootScope.states.length);
});

it('handles each icon', function() {
var stars = getStars();

for (var i = 0; i < stars.length; i++) {
var star = stars.eq(i);
var state = $rootScope.states[i];
var isOn = i < $rootScope.rate;

expect(star.hasClass(state.stateOn)).toBe(isOn);
expect(star.hasClass(state.stateOff)).toBe(!isOn);
}
});
});

describe('setting ratingConfig', function() {
var originalConfig = {};
beforeEach(inject(function(ratingConfig) {
$rootScope.rate = 5;
angular.extend(originalConfig, ratingConfig);
ratingConfig.max = 10;
ratingConfig.stateOn = 'on';
ratingConfig.stateOff = 'off';
element = $compile('<rating value="rate"></rating>')($rootScope);
$rootScope.$digest();
}));
Expand All @@ -141,5 +186,9 @@ describe('rating directive', function () {
it('should change number of icon elements', function () {
expect(getStars().length).toBe(10);
});

it('should change icon states', function () {
expect(getState('on', 'off')).toEqual([true, true, true, true, true, false, false, false, false, false]);
});
});
});
2 changes: 1 addition & 1 deletion template/rating/rating.html
Original file line number Diff line number Diff line change
@@ -1,3 +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>
<i ng-repeat="r in range" ng-mouseenter="enter($index + 1)" ng-click="rate($index + 1)" ng-class="$index < val && r.stateOn || r.stateOff"></i>
</span>

0 comments on commit 20ab01a

Please sign in to comment.