From 20ab01ad102761735e4c467e7aa1c1c50fa3ebe9 Mon Sep 17 00:00:00 2001 From: Tasos Bekos Date: Mon, 19 Aug 2013 20:17:14 +0300 Subject: [PATCH] feat(rating): add support for custom icons per instance --- src/rating/docs/demo.html | 12 +++++--- src/rating/docs/demo.js | 8 +++++ src/rating/docs/readme.md | 36 ++++++++++++++++++++++- src/rating/rating.js | 39 ++++++++++++++++++++----- src/rating/test/rating.spec.js | 53 ++++++++++++++++++++++++++++++++-- template/rating/rating.html | 2 +- 6 files changed, 135 insertions(+), 15 deletions(-) diff --git a/src/rating/docs/demo.html b/src/rating/docs/demo.html index 07ed646505..1d3e79a919 100644 --- a/src/rating/docs/demo.html +++ b/src/rating/docs/demo.html @@ -1,11 +1,15 @@ -
+
+

Default

{{percent}}% -
-
Rate: {{rate}} - Readonly is: {{isReadonly}} - Hovering over: {{overStar || "none"}}
+
Rate: {{rate}} - Readonly is: {{isReadonly}} - Hovering over: {{overStar || "none"}}
-
+
+ +

Custom icons

+
(Rate: {{x}})
+
(Rate: {{y}})
\ No newline at end of file diff --git a/src/rating/docs/demo.js b/src/rating/docs/demo.js index 7365e8e263..9dbbb374e4 100644 --- a/src/rating/docs/demo.js +++ b/src/rating/docs/demo.js @@ -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'} + ]; }; diff --git a/src/rating/docs/readme.md b/src/rating/docs/readme.md index 40a99b5e99..1fe0ac6853 100644 --- a/src/rating/docs/readme.md +++ b/src/rating/docs/readme.md @@ -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. \ No newline at end of file +### Settings ### + +#### `` #### + + * `value` + : + 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. diff --git a/src/rating/rating.js b/src/rating/rating.js index f61695eb75..bbcdfbdf40 100644 --- a/src/rating/rating.js +++ b/src/rating/rating.js @@ -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) { diff --git a/src/rating/test/rating.spec.js b/src/rating/test/rating.spec.js index 64b5e2bb48..ecea8fccc8 100644 --- a/src/rating/test/rating.spec.js +++ b/src/rating/test/rating.spec.js @@ -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; } @@ -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('')($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('')($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('')($rootScope); $rootScope.$digest(); })); @@ -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]); + }); }); }); diff --git a/template/rating/rating.html b/template/rating/rating.html index 2c9dcc746d..822b2cf6ef 100644 --- a/template/rating/rating.html +++ b/template/rating/rating.html @@ -1,3 +1,3 @@ - + \ No newline at end of file