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
             ...