JSFiddle - React, Tailwind, and code Playground
by johnoscott
HTML
<script src="http://code.angularjs.org/1.2.2/angular.js"></script>
<script src="http://fonts.googleapis.com/css?family=Roboto"></script>
<body ng-app="demo" ng-controller="DemoController">
<h3>rn-stepper demo (5/5)</h3>
<form name="form" >
Model value : {{ rating }}<br>
Min value: <input type="text" size="3" ng-model="minRating"><br>
Max value: <input type="text" size="3" ng-model="maxRating"><br>
Form has been modified : {{ form.$dirty }}<br>
Form is valid : {{ form.$valid }}
<hr>
<div min="minRating" max="maxRating" ng-model="rating" rn-stepper></div>
</form>
</body>
SCSS
body {
font-family: 'Roboto', sans-serif;
}
$stepper-height: 40px;
$stepper-value-width: 40px;
$stepper-button-width: 40px;
$stepper-border-width: 1px;
$stepper-button-bg: #4D4DFF;
$stepper-value-bg: #eee;
div[rn-stepper] {
&.ng-invalid-out-of-bounds {
div {
color: red;
}
}
border:1px solid #bbb;
display:inline-block;
height:$stepper-height + ($stepper-border-width * 2);
box-sizing:border-box;
button {
appearance:none;
-webkit-appearance:none;
margin:0;
border:0;
width: $stepper-button-width;
height:$stepper-height;
box-sizing:border-box;
background: $stepper-button-bg;
color: white;
font-weight:bold;
font-size:20px;
outline: none;
&:active {
box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.25);
background: darken($stepper-button-bg, 5%);
}
}
div {
vertical-align:top;
width:$stepper-value-width;
background:$stepper-value-bg;
text-align:center;
display:inline-block;
height:$stepper-height;
line-height:$stepper-height;
box-sizing:border-box;
}
}
JavaScript
angular.module('demo', [])
.controller('DemoController', function($scope) {
$scope.rating = 42;
$scope.minRating = 45;
$scope.maxRating = 50;
})
.directive('rnStepper', function() {
return {
restrict: 'AE',
require: 'ngModel',
scope: {
min: '=',
max: '='
},
template: '<button ng-click="decrement()">-</button>' +
'<div>{{ value }}</div>' +
'<button ng-click="increment()">+</button>',
link: function(scope, iElement, iAttrs, ngModelController) {
// we can now use our ngModelController builtin methods
// that do the heavy-lifting for us
// when model change, update our view (just update the div content)
ngModelController.$render = function() {
iElement.find('div').text(ngModelController.$viewValue);
};
function checkValidity() {
// check if min/max defined to check validity
var isOverMin = (angular.isDefined(scope.min) && ngModelController.$viewValue < parseInt(scope.min, 10)),
isOverMax = (angular.isDefined(scope.max) && ngModelController.$viewValue > parseInt(scope.max, 10)),
valid = !(isOverMin || isOverMax);
// set our model validity
// the outOfBounds is an arbitrary key for the error.
// will be used to generate the CSS class names for the errors
ngModelController.$setValidity('outOfBounds', valid);
}
// update the model then the view
function updateModel(offset) {
// call $parsers pipeline then update $modelValue
ngModelController.$setViewValue(ngModelController.$viewValue + offset);
// update the local view
ngModelController.$render();
// check validity
...