TimeSelector / TimePicker Directive – AngularJS

Hey everyone,

I’ve been mucking around with directives for that last few days and one of my requirements has been a simple timepicker. Nothing too fancy, but if anyone would like to use it – feel free.

See the following fiddle for the demo: http://jsfiddle.net/LFB3F/2/

Timepicker Directive

Timepicker Directive

The directive is initialised with both the hour and minute defaults being set like this:

<ng-time-selector hours="hours" minutes="minutes"></ng-time-selector>

Note that hours and minutes should be set in the controller that wraps around the Timepicker:

app.controller("MyCtrl", function MyCtrl($scope) {
    $scope.hours = 11;
    $scope.minutes = 45;       
});

The hours and minutes will then be updated whenever the user changes the value. If you need any other hooks it’s fairly easy to modify and there are a heap of comments.

You may also want to put the HTML in an external template. Just make the following changes to the directive (note templateURL):

app.directive("ngTimeSelector", function () {
    return {
        restrict: 'EA',
        templateUrl: '/Directives/TimeSelector',
        scope: {
            hours: "=",
            minutes: "="
        },
        replace: true,
        link: function (scope, elem, attr) {
...
...

The JSFiddle is probably the easiest way to see how it all works. You can simply copy it from there to your app. Alternatively, I’ve dumped the code below:

Uncondensed HTML (for those who prefer to use a template)

<div class="timeSelectorDirective"> 
    <div class="section hours">
        <div class="increase" ng-click="increaseHours()">
            <i class="icon fa fa-caret-up"></i>
        </div>
        <div class="display">
            {{displayHours()}}
        </div>        
         <div class="decrease" ng-click="decreaseHours()">
            <i class="icon fa fa-caret-down"></i>
        </div>
    </div>
    <div class="section minutes">
        <div class="increase" ng-click="increaseMinutes()">
            <i class="icon fa fa-caret-up"></i>
        </div>
        <div class="display">
            {{displayMinutes()}}
        </div>        
         <div class="decrease" ng-click="decreaseMinutes()">
            <i class="icon fa fa-caret-down"></i>
        </div>
    </div>    
    <div class="section hours">
        <div class="increase" ng-click="switchPeriod()">
            <i class="icon fa fa-caret-up"></i>
        </div>
        <div ng-if="hours >= 12" class="display">
            PM
        </div>
        <div ng-if="hours < 12" class="display">
            AM
        </div>   
         <div class="decrease" ng-click="switchPeriod()">
            <i class="icon fa fa-caret-down"></i>
        </div>
    </div>
</div>

Timepicker Directive

/* 
    http://www.whatibroke.com/?p=899
*/
 
var app = angular.module('myApp', []);
 
app.controller("MyCtrl", function MyCtrl($scope) {
    $scope.hours = 11;
    $scope.minutes = 45;       
});
 
 
app.directive("ngTimeSelector", function () {
    return {
        restrict: 'EA',
        template: '<div class="timeSelectorDirective"> <div class="section hours"> <div class="increase" ng-click="increaseHours()"> <i class="icon fa fa-caret-up"></i> </div> <div class="display"> {{displayHours()}} </div> <div class="decrease" ng-click="decreaseHours()"> <i class="icon fa fa-caret-down"></i> </div> </div> <div class="section minutes"> <div class="increase" ng-click="increaseMinutes()"> <i class="icon fa fa-caret-up"></i> </div> <div class="display"> {{displayMinutes()}} </div> <div class="decrease" ng-click="decreaseMinutes()"> <i class="icon fa fa-caret-down"></i> </div> </div> <div class="section hours"> <div class="increase" ng-click="switchPeriod()"> <i class="icon fa fa-caret-up"></i> </div> <div ng-if="hours >= 12" class="display"> PM </div> <div ng-if="hours < 12" class="display"> AM </div> <div class="decrease" ng-click="switchPeriod()"> <i class="icon fa fa-caret-down"></i> </div> </div> </div>',
        scope: {
            hours: "=",
            minutes: "="
        },
        replace: true,
        link: function (scope, elem, attr) {
 
            //Create vars
            scope.period = "AM";
 
            /* Increases hours by one */
            scope.increaseHours = function () {
 
                //Check whether hours have reached max
                if (scope.hours < 23) {
                    scope.hours = ++scope.hours;
                }
                else {
                    scope.hours = 0;
                }
            }
 
            /* Decreases hours by one */
            scope.decreaseHours = function () {
 
                //Check whether hours have reached min
                scope.hours = scope.hours <= 0 ? 23 : --scope.hours;
            }
 
            /* Increases minutes by one */
            scope.increaseMinutes = function () {
 
                //Check whether to reset
                if (scope.minutes >= 59) {
                    scope.minutes = 0;
                }
                else {
                    scope.minutes++;
                }
            }
 
            /* Decreases minutes by one */
            scope.decreaseMinutes = function () {
 
                //Check whether to reset
                if (scope.minutes <= 0) {
                    scope.minutes = 59;
                }
                else {
                    scope.minutes = --scope.minutes;
                }
            }
 
 
            /* Displays hours - what the user sees */
            scope.displayHours = function () {
 
                //Create vars
                var hoursToDisplay = scope.hours;
 
                //Check whether to reset etc
                if (scope.hours > 12) {
                    hoursToDisplay = scope.hours - 12;
                }                
 
                //Check for 12 AM etc
                if (hoursToDisplay == 0) {
 
                    //Set to am and display 12
                    hoursToDisplay = 12;
                }
                else {
 
                    //Check whether to prepend 0
                    if (hoursToDisplay <= 9) {
                        hoursToDisplay = "0" + hoursToDisplay;
                    }
                }
 
                return hoursToDisplay;
            }
 
            /* Displays minutes */
            scope.displayMinutes = function () {
                return scope.minutes <= 9 ? "0" + scope.minutes : scope.minutes;
            }
 
            /* Switches the current period by ammending hours */
            scope.switchPeriod = function () {
                scope.hours = scope.hours >= 12 ? scope.hours - 12 : scope.hours + 12;
            }
        }
    }
});

Styles

body{
	background-color: #F0F0F0;
	font-family: "Lato", sans-serif;
	font-weight: 300;
	color: #363636;
}
 
.timeSelectorDirective {
    background: none;
    -webkit-user-select: none;      
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
}
.timeSelectorDirective .increase, .timeSelectorDirective .decrease{
    text-align: center;
    vertical-align: middle;    
    color: rgb(112, 112, 112);    
    text-shadow: 0px 1px #FFF;
    cursor: pointer;
    -webkit-transition: 500ms ease-out all;
    -moz-transition: 500ms ease-out all;
    -ms-transition: 500ms ease-out all;
    -o-transition: 500ms ease-out all;
    transition: 500ms ease-out all;
    font-size: 100%;
    border: 1px solid #CCC;
    padding: 3px;
    margin: 3px;
    border: 1px solid #EDE;
}
 
.timeSelectorDirective .increase:hover, .timeSelectorDirective .decrease:hover{
    color: rgba(112, 112, 112, 0.5);   
    border-color: #CCC;
    background-color: #FFF;
}
 
.timeSelectorDirective .increase:active, .timeSelectorDirective .decrease:active{
    color: rgb(112, 112, 112);
    box-shadow: inset 1px 1px 1px #DDD;
}
 
.timeSelectorDirective .section{
    display: inline-block;
}
 
.timeSelectorDirective .display{
    background-color: rgb(247, 247, 247);
    color: #555555;
    padding: 5px;
    margin: 0px 3px;
    min-width: 30px;
    text-align: center;
    border: 1px solid #DDD;
    box-shadow: 1px 1px 1px #FFFFFF;
}

Sample Usage

<div ng-app="myApp">
    <h1><a href='http://www.whatibroke.com/?p=899'>www.whatibroke.com</a></h1>
 
    <div ng-controller="MyCtrl" style="margin-top:40px;">
        <b>Hours: </b>{{hours}} &nbsp;
        <b>Minutes: </b>{{minutes}}
        <ng-time-selector hours="hours" minutes="minutes"></ng-time-selector>
    </div>
</div>

Misc Includes
AngularJS: http://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js
FontAwesome: http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css
Font: http://fonts.googleapis.com/css?family=Lato:300,300italic,400,700,700italic|Rokkitt:400,700.css

Leave a Reply