Skip to content

Instantly share code, notes, and snippets.

@lionelB
Last active December 29, 2015 04:08
Show Gist options
  • Select an option

  • Save lionelB/7612233 to your computer and use it in GitHub Desktop.

Select an option

Save lionelB/7612233 to your computer and use it in GitHub Desktop.
An attempt to do a Numeric Stepper the Angular Way http://plnkr.co/edit/R4FYQKCGyB1GYl2hL4LO
<!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" />
<style>
.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;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff', endColorstr='#e6e6e6', GradientType=0)",argb(#fff),argb(#e6e6e6))); // IE9 and down
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;
}
.stepper-bt__up .caret{
position:relative;
top:-2px;
}
.stepper-bt__down{
border-bottom-right-radius: 3px;
bottom:0;
}
</style>
</head>
<body>
<div ng-controller="AppController">
<ui-stepper min="0" max="12" value="hours" step-size="1"></ui-stepper>
<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="/js/ui.stepper.js"></script>
<script src="/js/app.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" />
<a class="stepper-bt stepper-bt__up dropup" ng-click="increment()">
<i class="caret"></i>
<span class="sr-only">Increment</span>
</a>
<a class="stepper-bt stepper-bt__down" ng-click="decrement()">
<span class="caret"></span>
<span class="sr-only">Decrement</span>
</a>
</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: 'view/stepper.html',
link: function(scope, element, attrs, ngModel ){
// 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;
console.log(attrs.allowCycle);
var input = element.find('input');
// Allow user to increment / decrement using arrow key
input.bind('keydown', function(evt){
var key = evt.charCode || evt.keyCode || 0;
if (key === 38) {
scope.increment();
scope.$apply();
}
if (key === 40){
scope.decrement();
scope.$apply();
}
});
// Prevent user to enter non numeric char
input.bind('keypress', function(evt){
var key = evt.charCode || evt.keyCode || 0;
if ( !/[0-9]/.test( String.fromCharCode(key) )) {
evt.preventDefault();
return false;
}
});
scope.increment = function(){
var val = parseInt(ngModel.$modelValue, 10)
, updateFn = attrs.allowCycle ? loop : clamp;
input[0].focus();
ngModel.$setViewValue(updateFn( val + attrs.stepSize, attrs.min, attrs.max) );
};
scope.decrement = function(){
var val = parseInt(ngModel.$modelValue, 10)
, updateFn = attrs.allowCycle ? loop : clamp;
ngModel.$setViewValue(updateFn( val - attrs.stepSize, attrs.min, attrs.max) );
};
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);
}
}
};
}
]);
@t8g
Copy link
Copy Markdown

t8g commented Nov 23, 2013

Got error on line 52 when clicking.

var input looks wrong to me as find does not work with class name : http://docs.angularjs.org/api/angular.element

Correction :

  • line 28 : var input = element.find('input');
  • lines 52, 58 : input[0].focus();

And maybe some optimizations :

  • why scoping max, min and stepSize ? You can remove lines 15,16,17 and replace scope.min by attrs.min (and ...)
  • lines 53+54 : use Math.min (idem for line 59+60 with Math.max)
  • Maybe you can use ng-model as a required attribute and make use of the fourth parameter of your link function

@lionelB
Copy link
Copy Markdown
Author

lionelB commented Nov 23, 2013

Thx a lot... It seems that I made some mistake when isolating my code... Also on plunker, I saw some error when I dropped jquery which I need on my project)... Now I understand!

Not sure about scoping max, min, and stepSize... My goal was I to mabe them updatable/bindable from code or other binding (ex:switching AM/PM, max going from 12 to 24)

About requiring a ng-model, I'm not sure to see the benefit cause it's a bit new to me, but I see some example using that scheme.

Nice to have insightfull comment !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment