Skip to content

Instantly share code, notes, and snippets.

@lionelB
Forked from t8g/index.html
Last active December 29, 2015 12:48
Show Gist options
  • Save lionelB/7672633 to your computer and use it in GitHub Desktop.
Save lionelB/7672633 to your computer and use it in GitHub Desktop.
numeric stepper attribute directive.

Numeric stepper

an angular directive for building a numeric stepper

##features

  • Define minimun, maximun values and a stepSize,
  • choose cycling through value when reach minimum or maximun
  • integrate well whitin a form (update valid / invalid state)
  • update value with keyboard arrow

demo

http://plnkr.co/edit/40Bdf61wwg9VR4aACX5i?p=preview

.ui-stepper{
position:relative;
}
.stepper-bt{
position:absolute;
right:0;
height:15px;
border:#ccc 1px solid;
font-size:.8em;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fff), to(#e6e6e6)); // Safari 4+, Chrome 2+
background-image: -webkit-linear-gradient(top, #fff, 0%, #e6e6e6, 100%); // Safari 5.1+, Chrome 10+
background-image: -moz-linear-gradient(top, #fff 0%, #e6e6e6 100%); // FF 3.6+
background-image: linear-gradient(to bottom, #fff 0%, #e6e6e6 100%); // Standard, IE10
background-repeat: repeat-x;
display: inline-block;
padding: 0 .5em;
margin-right:-3px;
}
.stepper-bt:hover{
background: #dedede;
background-image: none;
}
.stepper-bt:active{
background: #efefef;
background-image: none;
}
.stepper-bt__up{
top:0;
border-top-right-radius: 3px;
.caret{
position:relative;
top:-2px;
}
}
.stepper-bt__down{
border-bottom-right-radius: 3px;
bottom:0;
}
.stepper-bt[disabled]{
background: #eee;
background-image: none;
}
.stepper-bt[disabled] .caret{
opacity:.3;
}
<!doctype html>
<html ng-app="ui">
<head>
<meta charset="UTF-8">
<title>Angular numeric Stepper</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, maximum-scale=1" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.2/css/bootstrap-theme.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.2/css/bootstrap.css" />
</head>
<body>
<div ng-controller="AppController">
<input type="text" ui-stepper min="0" max="60" ng-model="hours" step-size="1" />
<span class="help-block text-left">{{hours}} hours</span>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js"></script>
<script src="ui.stepper.js"></script>
<script src="script.js"></script>
</body>
</html>
var app = angular.module('ui', ['ui.stepper']);
app.controller('AppController', ['$scope',
function($scope){
$scope.hours="0";
}
]);
<div class="ui-stepper input-group">
<input type="text" class="form-control input-sm" ng-model='value' ng-change="onChange()"/>
<button class="stepper-bt stepper-bt__up dropup" ng-disabled="isOverMax()" ng-click="increment()">
<i class="caret"></i>
<span class="sr-only">Increment</span>
</button>
<button class="stepper-bt stepper-bt__down" ng-disabled="isOverMin()" ng-click="decrement()">
<span class="caret"></span>
<span class="sr-only">Decrement</span>
</button>
</div>
/* global angular, console */
angular.module('ui.stepper', [])
.constant('stepperConfig',{
stepSize: 10,
min: 0,
max: 100
})
.directive('uiStepper', ['stepperConfig',
function(stepperConfig){
'use strict';
return {
restrict: 'A',
require: 'ngModel',
scope: {
'value': "=ngModel"
},
replace: true,
templateUrl: 'stepper.html',
link: function(scope, element, attrs, ngModelController ){
// Set default values
attrs.min = parseInt(attrs.min || stepperConfig.min, 10);
attrs.max = parseInt(attrs.max || stepperConfig.max, 10);
attrs.value = parseInt(attrs.value || stepperConfig.value, 10);
attrs.stepSize = parseInt(attrs.stepSize || stepperConfig.stepSize, 10);
attrs.allowCycle = attrs.allowCycle || false;
var input = element.find('input');
// when model change, cast to integer
ngModelController.$formatters.push(function(value) {
return parseInt(value, 10)||0;
});
// when view change, cast to integer
ngModelController.$parsers.push(function(value) {
return parseInt(value, 10)||0;
});
ngModelController.$render = function() {
console.warn("RENDER", ngModelController.$viewValue, input[0].value );
input[0].value = ngModelController.$viewValue;
};
scope.$watch('value', function(newValue, oldValue, scope) {
checkValidity();
});
scope.onChange = function(){
ngModelController.$setViewValue( +input[0].value || 0 );
ngModelController.$render();
checkValidity();
};
scope.isOverMin = function() {
var disabled = !ngModelController.$valid ||
attrs.allowCycle || (+ngModelController.$viewValue === attrs.min);
return disabled;
};
scope.isOverMax = function() {
var disabled = !ngModelController.$valid ||
attrs.allowCycle || (+ngModelController.$viewValue === attrs.max);
return disabled;
};
scope.increment = function(){
var val = +ngModelController.$viewValue
, updateFn = attrs.allowCycle ? loop : clamp;
input[0].focus();
ngModelController.$setViewValue(updateFn( val + attrs.stepSize, attrs.min, attrs.max) );
ngModelController.$render();
};
scope.decrement = function(){
var val = +ngModelController.$viewValue
, updateFn = attrs.allowCycle ? loop : clamp;
ngModelController.$setViewValue(updateFn( val - attrs.stepSize, attrs.min, attrs.max) );
ngModelController.$render();
checkValidity();
};
function loop(val, min, max){
return val < min? max : (val > max? min : val);
}
function clamp(val, min, max){
return val < min? min : (val > max? max : val);
}
function checkValidity() {
var isValid;
isValid = ngModelController.$viewValue>=attrs.min && +ngModelController.$viewValue <= attrs.max;
ngModelController.$setValidity('outOfBounds', isValid);
}
function onKeyDown(evt){
if (!ngModelController.$valid) {
return;
}
var key = evt.charCode || evt.keyCode || 0;
if (key === 38) {
scope.increment(); }
if (key === 40){
scope.decrement();
}
scope.$apply();
console.log(key);
}
input.bind('keydown', onKeyDown);
checkValidity();
}
};
}
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment