扩展 angular ui-bootstrap timepicker 支持秒功能
ui-bootstrap timepicker 只支持选择时分,但由于公司需要选择到秒, 可是又没有找到angular 相关的插件。索性就在 ui-bootstrap timepicker 的基础上扩展了一个。
- 首先创建我们的 template.js 文件 ,这个文件就是存放我们扩展的模板。 代码如下
1 angular.module('myTemplate', ['template/timepicker/mytimepicker.html']); 2 3 angular.module("template/timepicker/mytimepicker.html", []).run(["$templateCache", function ($templateCache) { 4 $templateCache.put("template/timepicker/mytimepicker.html", 5 "<table>\n" + 6 " <tbody>\n" + 7 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" + 8 " <td><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 9 " <td> </td>\n" + 10 " <td><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 11 " <td> </td>\n" + 12 " <td><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 13 " <td ng-show=\"showMeridian\"></td>\n" + 14 " </tr>\n" + 15 " <tr>\n" + 16 " <td class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" + 17 " <input style=\"width:50px;\" type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\">\n" + 18 " </td>\n" + 19 " <td>:</td>\n" + 20 " <td class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" + 21 " <input style=\"width:50px;\" type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\">\n" + 22 " </td>\n" + 23 " <td>:</td>\n" + 24 " <td class=\"form-group\" ng-class=\"{'has-error': invalidSeconds}\">\n" + 25 " <input style=\"width:50px;\" type=\"text\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\">\n" + 26 " </td>\n" + 27 " <td ng-show=\"showMeridian\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" + 28 " </tr>\n" + 29 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" + 30 " <td><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 31 " <td> </td>\n" + 32 " <td><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 33 " <td> </td>\n" + 34 " <td><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 35 " <td ng-show=\"showMeridian\"></td>\n" + 36 " </tr>\n" + 37 " </tbody>\n" + 38 "</table>\n" + 39 ""); 40 }]);
- 创建我们的 directive.js 文件,这个文件就是存放我们扩展的指令。代码如下
1 angular.module("myDirective", []) 2 .controller('MyTimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', 'uibTimepickerConfig', '$locale', '$parse', function ($scope, $element, $attrs, $controller, $log, timepickerConfig, $locale, $parse) { 3 var selected = new Date(), 4 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl 5 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; 6 7 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; 8 $element.removeAttr('tabindex'); 9 10 this.init = function (ngModelCtrl_, inputs) { 11 ngModelCtrl = ngModelCtrl_; 12 ngModelCtrl.$render = this.render; 13 14 ngModelCtrl.$formatters.unshift(function (modelValue) { 15 return modelValue ? new Date(modelValue) : null; 16 }); 17 18 var hoursInputEl = inputs.eq(0), 19 minutesInputEl = inputs.eq(1), 20 secondsInputEl = inputs.eq(2); 21 22 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; 23 if (mousewheel) { 24 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl); 25 } 26 27 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; 28 if (arrowkeys) { 29 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl); 30 } 31 32 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; 33 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl); 34 }; 35 36 var hourStep = timepickerConfig.hourStep; 37 if ($attrs.hourStep) { 38 $scope.$parent.$watch($parse($attrs.hourStep), function (value) { 39 hourStep = parseInt(value, 10); 40 }); 41 } 42 43 var minuteStep = timepickerConfig.minuteStep; 44 if ($attrs.minuteStep) { 45 $scope.$parent.$watch($parse($attrs.minuteStep), function (value) { 46 minuteStep = parseInt(value, 10); 47 }); 48 } 49 50 var secondsStep = 1; 51 if ($attrs.secondsStep) { 52 $scope.$parent.$watch($parse($attrs.secondsStep), function (value) { 53 secondsStep = parseInt(value, 10); 54 }); 55 } 56 var min; 57 $scope.$parent.$watch($parse($attrs.min), function (value) { 58 var dt = new Date(value); 59 min = isNaN(dt) ? undefined : dt; 60 }); 61 62 var max; 63 $scope.$parent.$watch($parse($attrs.max), function (value) { 64 var dt = new Date(value); 65 max = isNaN(dt) ? undefined : dt; 66 }); 67 68 $scope.noIncrementHours = function () { 69 var incrementedSelected = addMinutes(selected, hourStep * 60); 70 return incrementedSelected > max || 71 (incrementedSelected < selected && incrementedSelected < min); 72 }; 73 74 $scope.noDecrementHours = function () { 75 var decrementedSelected = addMinutes(selected, -hourStep * 60); 76 return decrementedSelected < min || 77 (decrementedSelected > selected && decrementedSelected > max); 78 }; 79 80 $scope.noIncrementMinutes = function () { 81 var incrementedSelected = addMinutes(selected, minuteStep); 82 return incrementedSelected > max || 83 (incrementedSelected < selected && incrementedSelected < min); 84 }; 85 86 $scope.noDecrementMinutes = function () { 87 var decrementedSelected = addMinutes(selected, -minuteStep); 88 return decrementedSelected < min || 89 (decrementedSelected > selected && decrementedSelected > max); 90 }; 91 92 $scope.noIncrementSeconds = function () { 93 var incrementedSelected = addMinutes(selected, secondsStep); 94 return incrementedSelected > max || 95 (incrementedSelected < selected && incrementedSelected < min); 96 }; 97 98 $scope.noDecrementSeconds = function () { 99 var decrementedSelected = addMinutes(selected, -secondsStep); 100 return decrementedSelected < min || 101 (decrementedSelected > selected && decrementedSelected > max); 102 }; 103 104 $scope.noToggleMeridian = function () { 105 if (selected.getHours() < 13) { 106 return addMinutes(selected, 12 * 60) > max; 107 } else { 108 return addMinutes(selected, -12 * 60) < min; 109 } 110 }; 111 112 // 12H / 24H mode 113 $scope.showMeridian = timepickerConfig.showMeridian; 114 if ($attrs.showMeridian) { 115 $scope.$parent.$watch($parse($attrs.showMeridian), function (value) { 116 $scope.showMeridian = !!value; 117 118 if (ngModelCtrl.$error.time) { 119 // Evaluate from template 120 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); 121 if (angular.isDefined(hours) && angular.isDefined(minutes)) { 122 selected.setHours(hours); 123 refresh(); 124 } 125 } else { 126 updateTemplate(); 127 } 128 }); 129 } 130 131 // Get $scope.hours in 24H mode if valid 132 function getHoursFromTemplate() { 133 var hours = parseInt($scope.hours, 10); 134 var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); 135 if (!valid) { 136 return undefined; 137 } 138 139 if ($scope.showMeridian) { 140 if (hours === 12) { 141 hours = 0; 142 } 143 if ($scope.meridian === meridians[1]) { 144 hours = hours + 12; 145 } 146 } 147 return hours; 148 } 149 150 function getMinutesFromTemplate() { 151 var minutes = parseInt($scope.minutes, 10); 152 return (minutes >= 0 && minutes < 60) ? minutes : undefined; 153 } 154 155 function getSecondsFromTemplate() { 156 var seconds = parseInt($scope.seconds, 10); 157 return (seconds >= 0 && seconds < 60) ? seconds : undefined; 158 } 159 160 function pad(value) { 161 return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString(); 162 } 163 164 // Respond on mousewheel spin 165 this.setupMousewheelEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) { 166 var isScrollingUp = function (e) { 167 if (e.originalEvent) { 168 e = e.originalEvent; 169 } 170 //pick correct delta variable depending on event 171 var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; 172 return (e.detail || delta > 0); 173 }; 174 175 hoursInputEl.bind('mousewheel wheel', function (e) { 176 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); 177 e.preventDefault(); 178 }); 179 180 minutesInputEl.bind('mousewheel wheel', function (e) { 181 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); 182 e.preventDefault(); 183 }); 184 185 secondsInputEl.bind('mousewhel wheel', function(e) { 186 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds()); 187 e.preventDefault(); 188 }); 189 190 }; 191 192 // Respond on up/down arrowkeys 193 this.setupArrowkeyEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) { 194 hoursInputEl.bind('keydown', function (e) { 195 if (e.which === 38) { // up 196 e.preventDefault(); 197 $scope.incrementHours(); 198 $scope.$apply(); 199 } else if (e.which === 40) { // down 200 e.preventDefault(); 201 $scope.decrementHours(); 202 $scope.$apply(); 203 } 204 }); 205 206 minutesInputEl.bind('keydown', function (e) { 207 if (e.which === 38) { // up 208 e.preventDefault(); 209 $scope.incrementMinutes(); 210 $scope.$apply(); 211 } else if (e.which === 40) { // down 212 e.preventDefault(); 213 $scope.decrementMinutes(); 214 $scope.$apply(); 215 } 216 }); 217 218 secondsInputEl.bind('keydown', function(e) { 219 if (e.which === 38) { // up 220 e.preventDefault(); 221 $scope.incrementSeconds(); 222 $scope.$apply(); 223 } else if (e.which === 40) { // down 224 e.preventDefault(); 225 $scope.decrementSeconds(); 226 $scope.$apply(); 227 } 228 }); 229 }; 230 231 this.setupInputEvents = function (hoursInputEl, minutesInputEl, secondsInputEl) { 232 if ($scope.readonlyInput) { 233 $scope.updateHours = angular.noop; 234 $scope.updateMinutes = angular.noop; 235 $scope.updateSeconds = angular.noop; 236 return; 237 } 238 239 var invalidate = function (invalidHours, invalidMinutes, invalidSeconds) { 240 ngModelCtrl.$setViewValue(null); 241 ngModelCtrl.$setValidity('time', false); 242 if (angular.isDefined(invalidHours)) { 243 $scope.invalidHours = invalidHours; 244 } 245 if (angular.isDefined(invalidMinutes)) { 246 $scope.invalidMinutes = invalidMinutes; 247 } 248 if (angular.isDefined(invalidSeconds)) { 249 $scope.invalidSeconds = invalidSeconds; 250 } 251 }; 252 253 $scope.updateHours = function () { 254 var hours = getHoursFromTemplate(), 255 minutes = getMinutesFromTemplate(); 256 257 if (angular.isDefined(hours) && angular.isDefined(minutes)) { 258 selected.setHours(hours); 259 if (selected < min || selected > max) { 260 invalidate(true); 261 } else { 262 refresh('h'); 263 } 264 } else { 265 invalidate(true); 266 } 267 }; 268 269 hoursInputEl.bind('blur', function (e) { 270 if (!$scope.invalidHours && $scope.hours < 10) { 271 $scope.$apply(function () { 272 $scope.hours = pad($scope.hours); 273 }); 274 } 275 }); 276 277 $scope.updateMinutes = function () { 278 var minutes = getMinutesFromTemplate(), 279 hours = getHoursFromTemplate(); 280 281 if (angular.isDefined(minutes) && angular.isDefined(hours)) { 282 selected.setMinutes(minutes); 283 if (selected < min || selected > max) { 284 invalidate(undefined, true); 285 } else { 286 refresh('m'); 287 } 288 } else { 289 invalidate(undefined, true); 290 } 291 }; 292 293 minutesInputEl.bind('blur', function (e) { 294 if (!$scope.invalidMinutes && $scope.minutes < 10) { 295 $scope.$apply(function () { 296 $scope.minutes = pad($scope.minutes); 297 }); 298 } 299 }); 300 301 $scope.updateSeconds = function () { 302 var minutes = getMinutesFromTemplate(), 303 hours = getHoursFromTemplate(), 304 seconds = getSecondsFromTemplate(); 305 306 if (angular.isDefined(minutes) && angular.isDefined(hours) && angular.isDefined(seconds)) { 307 selected.setSeconds(seconds); 308 if (selected < min || selected > max) { 309 invalidate(undefined, undefined,true); 310 } else { 311 refresh('s'); 312 } 313 } else { 314 invalidate(undefined, undefined, true); 315 } 316 }; 317 318 secondsInputEl.bind('blur', function (e) { 319 if (!$scope.invalidSeconds && $scope.seconds < 10) { 320 $scope.$apply(function () { 321 $scope.seconds = pad($scope.seconds); 322 }); 323 } 324 }); 325 }; 326 327 this.render = function () { 328 var date = ngModelCtrl.$viewValue; 329 330 if (isNaN(date)) { 331 ngModelCtrl.$setValidity('time', false); 332 $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); 333 } else { 334 if (date) { 335 selected = date; 336 } 337 338 if (selected < min || selected > max) { 339 ngModelCtrl.$setValidity('time', false); 340 $scope.invalidHours = true; 341 $scope.invalidMinutes = true; 342 $scope.invalidSeconds = true; 343 } else { 344 makeValid(); 345 } 346 updateTemplate(); 347 } 348 }; 349 350 // Call internally when we know that model is valid. 351 function refresh(keyboardChange) { 352 makeValid(); 353 ngModelCtrl.$setViewValue(new Date(selected)); 354 updateTemplate(keyboardChange); 355 } 356 357 function makeValid() { 358 ngModelCtrl.$setValidity('time', true); 359 $scope.invalidHours = false; 360 $scope.invalidMinutes = false; 361 $scope.invalidSeconds = false; 362 } 363 364 function updateTemplate(keyboardChange) { 365 var hours = selected.getHours(), minutes = selected.getMinutes(), seconds = selected.getSeconds(); 366 367 if ($scope.showMeridian) { 368 hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system 369 } 370 371 $scope.hours = keyboardChange === 'h' ? hours : pad(hours); 372 if (keyboardChange !== 'm') { 373 $scope.minutes = pad(minutes); 374 } 375 if (keyboardChange !== 's') { 376 $scope.seconds = pad(seconds); 377 } 378 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; 379 } 380 381 function addMinutes(date, minutes) { 382 var dt = new Date(date.getTime() + minutes * 60000); 383 var newDate = new Date(date); 384 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); 385 return newDate; 386 } 387 388 function addSeconds(date, seconds) { 389 var dt = new Date(date.getTime() + seconds*1000); 390 var newDate = new Date(date); 391 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); 392 return newDate; 393 } 394 395 function addMinutesToSelected(minutes) { 396 selected = addMinutes(selected, minutes); 397 refresh(); 398 } 399 400 function addSecondsToSelected(seconds) { 401 selected = addSeconds(selected, seconds); 402 refresh(); 403 } 404 405 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? 406 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; 407 408 $scope.incrementHours = function () { 409 if (!$scope.noIncrementHours()) { 410 addMinutesToSelected(hourStep * 60); 411 } 412 }; 413 414 $scope.decrementHours = function () { 415 if (!$scope.noDecrementHours()) { 416 addMinutesToSelected(-hourStep * 60); 417 } 418 }; 419 420 $scope.incrementMinutes = function () { 421 if (!$scope.noIncrementMinutes()) { 422 addMinutesToSelected(minuteStep); 423 } 424 }; 425 426 $scope.decrementMinutes = function () { 427 if (!$scope.noDecrementMinutes()) { 428 addMinutesToSelected(-minuteStep); 429 } 430 }; 431 432 $scope.incrementSeconds = function () { 433 if (!$scope.noIncrementSeconds()) { 434 addSecondsToSelected(secondsStep); 435 } 436 }; 437 438 $scope.decrementSeconds = function () { 439 if (!$scope.noDecrementSeconds()) { 440 addSecondsToSelected(-secondsStep); 441 } 442 }; 443 444 445 $scope.toggleMeridian = function () { 446 if (!$scope.noToggleMeridian()) { 447 addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1)); 448 } 449 }; 450 451 }]) 452 .directive("myTimepicker", function () { 453 return { 454 restrict: 'EA', 455 require: ['myTimepicker', '?^ngModel'], 456 controller: 'MyTimepickerController', 457 controllerAs: 'myTimepicker', 458 replace: true, 459 scope: {}, 460 templateUrl: function (element, attrs) { 461 return attrs.templateUrl || 'template/timepicker/mytimepicker.html'; 462 }, 463 link: function (scope, element, attrs, ctrls) { 464 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 465 466 if (ngModelCtrl) { 467 timepickerCtrl.init(ngModelCtrl, element.find('input')); 468 } 469 } 470 }; 471 });
- 支持原生的所有配置,使用方法如下
<my-timepicker ng-model="我们的Model"></my-timepicker>
这样活生生的秒就出现了。
你可以上翻下翻,还可以手动输入。