├── README.md ├── demo ├── css │ ├── demo.css │ └── keypad-numeric.css ├── index.html ├── js │ ├── DemoController.js │ └── app.js ├── lib │ ├── angular.js │ └── jquery.2.0.2.js └── partials │ └── keypad │ └── numeric.html └── src ├── ngDraggable └── ngDraggable.js └── ngKeypad ├── ngKey.js ├── ngKeypad.js └── ngKeypadInput.js /README.md: -------------------------------------------------------------------------------- 1 | # ng-keypad 2 | 3 | 4 | ng-keypad is a simple set of directive that allow you to create a custom keypad to provide on screen user input functionnality. Mobile and desktop compatible. You can view a demo here : [View Demo](http://dev.tommyrochette.com/ng-keypad/demo/). 5 | 6 | Keypad design by [Courtny Cotten](http://dribbble.com/court) 7 | 8 | 9 | ## Usage 10 | 11 | ``` 12 | //Basic usage 13 |
14 | // Insert key pad template here. 15 |
16 | 17 | //Combined with ng-include 18 |
19 |
20 |
21 | ``` 22 | 23 | ## Options 24 | 25 | - `data-ng-keypad` *REQUIRED* Specify ID for the keypad instance. You can have multiple keypads in the same application. 26 | - `data-auto-close` Set that to true and the keypad will automatically close when user click outside of it. 27 | - `data-ng-draggable` Use for the ng-draggable directive. Allowing the keypad to be moved on screen. 28 | 29 | ## ng-key 30 | 31 | ### Usage 32 | 33 | ``` 34 | //Basic usage - This dispatch Keypad.KEY_PRESSED event 35 | 36 | 37 | //Custom or modifier key - This dispatch Keypad.MODIFIER_KEY_PRESSED event. Those events need to be handled by your application. 38 | 39 | ``` 40 | 41 | 42 | ## ng-keypad-input 43 | 44 | ### Usage 45 | 46 | 47 | ``` 48 | //Basic usage 49 | 50 | 51 | //It can also be used on a tag to prevent keyboard to show up on mobile devices 52 | 53 | ``` 54 | 55 | ### Options 56 | 57 | - `data-ng-model` *REQUIRED* Instance of ng-model that will key updated by the keypad. 58 | - `data-ng-keypad-input` *REQUIRED* Specify ID of the ng-keypad instance you want to open when user focus or click on the field. 59 | - `data-ng-keypad-restrict` Restrict character or input value using regular expression. 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /demo/css/demo.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width:100%; 3 | height:100%; 4 | font-family: Helvetica, Arial, sans-serif; 5 | padding:10px; 6 | } 7 | 8 | .numpad{ 9 | left:350px; 10 | top:200px; 11 | } 12 | 13 | hr{ 14 | margin-top:40px; 15 | } 16 | 17 | 18 | .clear{ 19 | display: block; 20 | clear: both; 21 | height: 1px; 22 | } 23 | 24 | .helpers button{ 25 | width:auto; 26 | padding:10px; 27 | 28 | } 29 | 30 | .values{ 31 | margin-top:40px; 32 | line-height:20px; 33 | } 34 | 35 | 36 | 37 | .closed{ 38 | display:none; 39 | } 40 | 41 | .pressed{ 42 | background:#666; 43 | } 44 | 45 | .input{ 46 | display: block; 47 | border:2px solid #666; 48 | max-width: 100px; 49 | padding:5px 10px; 50 | height:18px; 51 | text-decoration: none; 52 | color:#000; 53 | } 54 | 55 | .focus{ 56 | border:2px solid #FF0000; 57 | } 58 | -------------------------------------------------------------------------------- /demo/css/keypad-numeric.css: -------------------------------------------------------------------------------- 1 | .numpad { 2 | position: relative; 3 | height: 300px; 4 | width: 215px; 5 | padding: 13px; 6 | z-index: 10000; 7 | background: #666; 8 | -webkit-border-radius: 10px; 9 | -moz-border-radius: 10px; 10 | -ms-border-radius: 10px; 11 | -o-border-radius: 10px; 12 | border-radius: 10px; 13 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #999999), color-stop(100%, #666666)); 14 | background-image: -webkit-linear-gradient(#999999, #666666); 15 | background-image: -moz-linear-gradient(#999999, #666666); 16 | background-image: -o-linear-gradient(#999999, #666666); 17 | background-image: linear-gradient(#999999, #666666); 18 | -webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 0 25px; 19 | -moz-box-shadow: rgba(0, 0, 0, 0.4) 0 0 25px; 20 | box-shadow: rgba(0, 0, 0, 0.4) 0 0 25px; 21 | } 22 | 23 | .numpad[disabled] button { 24 | opacity: .5; 25 | } 26 | 27 | .numpad .button-wrapper { 28 | width: 200px; 29 | overflow: auto; 30 | } 31 | 32 | .numpad .button-wrapper button { 33 | -webkit-border-radius: 5px; 34 | -moz-border-radius: 5px; 35 | -ms-border-radius: 5px; 36 | -o-border-radius: 5px; 37 | border-radius: 5px; 38 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5)); 39 | background-image: -webkit-linear-gradient(#ffffff, #e5e5e5); 40 | background-image: -moz-linear-gradient(#ffffff, #e5e5e5); 41 | background-image: -o-linear-gradient(#ffffff, #e5e5e5); 42 | background-image: linear-gradient(#ffffff, #e5e5e5); 43 | -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 10px; 44 | -moz-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 10px; 45 | box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 10px; 46 | box-sizing: border-box; 47 | display: block; 48 | float: left; 49 | width: 51px; 50 | height: 51px; 51 | padding: 10px 0px; 52 | margin: 0 10px 10px 0; 53 | border: none; 54 | font-size: 20px; 55 | } 56 | 57 | .numpad .button-wrapper .smaller { 58 | font-size: 14px; 59 | } 60 | 61 | .numpad .button-wrapper .button-wide { 62 | width: 82px; 63 | } 64 | 65 | .numpad .button-wrapper .pressed { 66 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dadada), color-stop(100%, #bebebe)); 67 | background-image: -webkit-linear-gradient(#dadada, #bebebe); 68 | background-image: -moz-linear-gradient(#dadada, #bebebe); 69 | background-image: -o-linear-gradient(#dadada, #bebebe); 70 | background-image: linear-gradient(#dadada, #bebebe); 71 | -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px; 72 | -moz-box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px; 73 | box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px; 74 | } 75 | 76 | .numpad .drag-indicator { 77 | position: absolute; 78 | width: 24px; 79 | height: 80px; 80 | top: 115px; 81 | left: 208px; 82 | } 83 | 84 | .numpad .drag-indicator > span { 85 | display: block; 86 | height: 80px; 87 | width: 0px; 88 | border-left: 1px solid #666666; 89 | border-right: 1px solid #999999; 90 | float: left; 91 | margin-right: 3px; 92 | } 93 | 94 | .numpad .close { 95 | -webkit-border-radius: 15px; 96 | -moz-border-radius: 15px; 97 | -ms-border-radius: 15px; 98 | -o-border-radius: 15px; 99 | border-radius: 15px; 100 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=30); 101 | opacity: 0.3; 102 | -webkit-box-shadow: black 0px 1px 2px inset; 103 | -moz-box-shadow: black 0px 1px 2px inset; 104 | box-shadow: black 0px 1px 2px inset; 105 | position: absolute; 106 | box-sizing: border-box; 107 | width: 30px; 108 | height: 30px; 109 | top: 10px; 110 | left: 198px; 111 | background: #fff; 112 | border: none; 113 | padding-top: 4px; 114 | } 115 | 116 | .numpad .close:before { 117 | font-family: Arial; 118 | content: "X"; 119 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60); 120 | opacity: 0.6; 121 | font-weight: bold; 122 | color: #000; 123 | font-size: 16px; 124 | } 125 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tablet Numpad component test sandbox 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |

ngKeypad Actions

22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | 34 | 35 |
36 |

ngKeypad Events

37 | KEY_PRESSED event generated string : {{listenedString}} 38 |
39 |

ngKeypadInput

40 |
41 | No limits: {{input3}} 42 |
43 |
44 |

ngKeypadRestrict

45 |
46 | 3 characters, number only: {{input1}}
47 |
48 |
49 | 10 characters maximum: {{input2}}
50 |
51 |
52 |
53 | 54 | -------------------------------------------------------------------------------- /demo/js/DemoController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @copyright 2013 Tommy Rochette http://trochette.github.com/ 4 | * @author Tommy Rochette 5 | * @version 0.0.1 6 | * 7 | * @license MIT 8 | */ 9 | (function(){ 10 | var DemoController = function DemoController($scope) { 11 | var self = this; 12 | var selectedInputIndex = 0; 13 | 14 | /** 15 | * This example show how to toggle locked/unlocked 16 | * state of the keypad. 17 | */ 18 | $scope.toggleKeypadLock = function(){ 19 | toggleLock(); 20 | //Toggling KeyPad Locking state, second argument is Keypad ID 21 | $scope.$emit(Keypad.TOGGLE_LOCKING, "numeric"); 22 | } 23 | 24 | 25 | 26 | /** 27 | * This example show how to toggle open/close 28 | * state of the keypad. 29 | */ 30 | $scope.toggleKeypadOpening = function(){ 31 | //Toggling KeyPad Locking state, second argument is Keypad ID 32 | $scope.$emit(Keypad.TOGGLE_OPENING, "numeric"); 33 | } 34 | 35 | 36 | /** 37 | * This example show how to toggle open/close 38 | * state of the keypad and set a position at the same time. 39 | */ 40 | $scope.toggleKeypadPosition = function(){ 41 | var params = { 42 | position:{ 43 | x:600, 44 | y:70 45 | } 46 | }; 47 | //Toggling KeyPad Locking state, second argument is Keypad ID, third is the param object. 48 | $scope.$emit(Keypad.TOGGLE_OPENING, "numeric",params); 49 | } 50 | 51 | 52 | /** 53 | * This example show how to listen for the KEY_PRESSED event thrown 54 | * by the keypad and do what you need to do with it. 55 | */ 56 | $scope.listenedString = ""; 57 | 58 | $scope.$on(Keypad.KEY_PRESSED, function(event,data){ 59 | $scope.listenedString += data; 60 | $scope.$digest(); 61 | }); 62 | 63 | 64 | 65 | 66 | 67 | //DEMO APPLICATION CODE NOT WORTH READING 68 | 69 | $scope.opened = false; 70 | $scope.openLabel = "Open Keypad" 71 | 72 | $scope.$on(Keypad.OPENED, function(event,id){ 73 | $scope.openLabel = "Close Keypad"; 74 | $scope.opened = true; 75 | 76 | if(!$scope.$$phase){ 77 | $scope.$apply(); 78 | } 79 | }); 80 | 81 | $scope.$on(Keypad.CLOSED, function(event,id){ 82 | $scope.openLabel = "Open Keypad"; 83 | $scope.opened = false; 84 | 85 | if(!$scope.$$phase){ 86 | $scope.$apply(); 87 | } 88 | }); 89 | 90 | 91 | $scope.$on(Keypad.MODIFIER_KEY_PRESSED, function(event,key,id){ 92 | var focusedInput = $('a[data-ng-keypad-input]:focus'), 93 | inputs = $('a[data-ng-keypad-input]'), 94 | foundIndex = findInputInInputs(focusedInput,inputs), 95 | index = 0; 96 | 97 | switch(key){ 98 | case "PREVIOUS": 99 | if(!focusedInput.length){ 100 | index = inputs.length-1; 101 | }else{ 102 | if(foundIndex===0){ 103 | index = inputs.length-1; 104 | }else{ 105 | index = foundIndex-1; 106 | } 107 | } 108 | inputs.eq(index).focus(); 109 | break; 110 | case "NEXT": 111 | if(focusedInput.length){ 112 | if(foundIndex===inputs.length-1){ 113 | index = 0; 114 | }else{ 115 | index = foundIndex+1; 116 | } 117 | } 118 | inputs.eq(index).focus(); 119 | break; 120 | } 121 | }); 122 | 123 | 124 | function findInputInInputs(input,inputs){ 125 | var foundIndex = 0; 126 | inputs.each(function(index){ 127 | if($(this).is(input.eq(0))){ 128 | foundIndex = index; 129 | } 130 | }); 131 | 132 | return foundIndex; 133 | } 134 | 135 | 136 | $scope.locked = true; 137 | $scope.lockLabel = "Lock Keypad" 138 | 139 | function toggleLock(){ 140 | $scope.locked = !$scope.locked; 141 | 142 | if($scope.locked){ 143 | $scope.lockLabel = "Lock Keypad" 144 | }else{ 145 | $scope.lockLabel = "Unlock Keypad" 146 | } 147 | } 148 | } 149 | 150 | window.DemoController = DemoController; 151 | })(); -------------------------------------------------------------------------------- /demo/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @copyright 2013 Tommy Rochette http://trochette.github.com/ 4 | * @author Tommy Rochette 5 | * @version 0.0.1 6 | * 7 | * @license MIT 8 | */ 9 | (function(){ 10 | 'use strict'; 11 | angular.module('app',['ngKeypad','ngDraggable']); 12 | })(); 13 | 14 | -------------------------------------------------------------------------------- /demo/partials/keypad/numeric.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 |
-------------------------------------------------------------------------------- /src/ngDraggable/ngDraggable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draggable directive, allow to drag any element 3 | * targeted by this directive. 4 | * 5 | * Use of Namespace.js 6 | * 7 | * @author Tommy Rochette http://trochette.github.com/ 8 | * @version 0.0.1 9 | * @license MIT 10 | */ 11 | (function () { 12 | "use strict"; 13 | 14 | /** 15 | * @Class 16 | */ 17 | var Draggable = function($scope,$element,$attrs){ 18 | 19 | var offset = {}, 20 | body = $('body'), 21 | isDragging = false; 22 | 23 | /** 24 | * Initialize Draggable to default settings 25 | */ 26 | function init(){ 27 | $element.css("position","absolute"); 28 | 29 | if ("ontouchstart" in document.documentElement){ 30 | $element.bind('touchstart.ngDraggable',startDrag); 31 | body.bind('touchend.ngDraggable',stopDrag); 32 | body.bind('touchmove.ngDraggable',drag); 33 | }else{ 34 | $element.bind('mousedown.ngDraggable',startDrag); 35 | body.bind('mouseup.ngDraggable',stopDrag); 36 | body.bind('mousemove.ngDraggable',drag); 37 | } 38 | 39 | $scope.$on('destroy',destroy); 40 | }; 41 | 42 | 43 | /** 44 | * Triggered when a user start moving his mouse 45 | * or his finger on mobile application. 46 | * 47 | * @param event 48 | */ 49 | function drag(event){ 50 | if(isDragging){ 51 | var position = {}; 52 | 53 | if(event.type==="mousemove"){ 54 | position.x = Math.floor(event.pageX-offset.x); 55 | position.y = Math.floor(event.pageY-offset.y); 56 | 57 | }else{ 58 | var orig = event.originalEvent; 59 | position.x = Math.floor(orig.changedTouches[0].pageX - offset.x); 60 | position.y = Math.floor(orig.changedTouches[0].pageY - offset.y); 61 | } 62 | 63 | $element.css("left",position.x); 64 | $element.css("top",position.y); 65 | } 66 | } 67 | 68 | 69 | /** 70 | * Triggered when a user click or start touching the element. 71 | * 72 | * @param event 73 | */ 74 | function startDrag(event){ 75 | isDragging = true; 76 | 77 | if(event.type==="mousedown"){ 78 | var localoffsetX = $element.offset().left, 79 | localoffsetY = $element.offset().top, 80 | localx = Math.floor(event.pageX-localoffsetX), 81 | localy = Math.floor(event.pageY-localoffsetY); 82 | offset.x = localx; 83 | offset.y = localy; 84 | }else{ 85 | var orig = event.originalEvent; 86 | var pos = $(this).position(); 87 | offset.x = Math.floor(orig.changedTouches[0].pageX - pos.left); 88 | offset.y = Math.floor(orig.changedTouches[0].pageY - pos.top); 89 | } 90 | 91 | $scope.$emit("$startDrag"); 92 | event.preventDefault(); 93 | } 94 | 95 | 96 | /** 97 | * Triggered when a user remove his finger from the screen. 98 | * 99 | * @param event 100 | */ 101 | function stopDrag(event){ 102 | isDragging = false; 103 | offset = {}; 104 | $scope.$emit("$stopDrag"); 105 | } 106 | 107 | 108 | /** 109 | * Cleanup all listeners before destroying this directive. 110 | */ 111 | function destroy(){ 112 | $element.unbind('mousedown.ngDraggable',startDrag); 113 | $element.unbind('mousestart.ngDraggable',startDrag); 114 | body.unbind('mouseup.ngDraggable',stopDrag); 115 | body.unbind('touchend.ngDraggable',stopDrag); 116 | body.unbind('mousemove.ngDraggable',drag); 117 | body.unbind('touchmove.ngDraggable',drag); 118 | } 119 | 120 | 121 | init(); 122 | }; 123 | 124 | angular.module('ngDraggable',[]) 125 | .directive('ngDraggable',function () { 126 | return { 127 | restrict: 'A', 128 | link: function ($scope, $element, $attrs) { 129 | new Draggable($scope, $element, $attrs); 130 | } 131 | } 132 | }); 133 | })(); -------------------------------------------------------------------------------- /src/ngKeypad/ngKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @copyright 2013 Tommy Rochette http://trochette.github.com/ 4 | * @author Tommy Rochette 5 | * @version 0.0.1 6 | * 7 | * @license MIT 8 | */ 9 | (function () { 10 | "use strict"; 11 | 12 | /** 13 | * @Class 14 | */ 15 | window.Key = function ($scope, $element, $attrs) { 16 | 17 | var body = $('body'); 18 | 19 | /** 20 | * Initialize Key to default settings 21 | */ 22 | function init() { 23 | if ("ontouchstart" in document.documentElement) { 24 | $element.bind('touchstart.ngKey', keyPressed); 25 | body.bind('touchend.ngKey', keyDepressed); 26 | } else { 27 | $element.bind('mousedown.ngKey', keyPressed); 28 | body.bind('mouseup.ngKey', keyDepressed); 29 | } 30 | 31 | $scope.$on('destroy', destroy); 32 | }; 33 | 34 | 35 | /** 36 | * Triggered when a user press on this element 37 | * 38 | * @param event 39 | */ 40 | function keyPressed(event) { 41 | $element.addClass('pressed'); 42 | event.preventDefault(); 43 | event.stopImmediatePropagation(); 44 | $scope.$emit(Key.PRESSED, $attrs.ngKey); 45 | } 46 | 47 | 48 | /** 49 | * Triggered when a user stop pressing this element 50 | * 51 | * @param event 52 | */ 53 | function keyDepressed(event) { 54 | $element.removeClass('pressed'); 55 | } 56 | 57 | /** 58 | * Cleanup all listeners before destroying this directive. 59 | */ 60 | function destroy() { 61 | $element.bind('touchstart.ngKey', keyPressed); 62 | body.bind('touchend.ngKey', keyDepressed); 63 | $element.bind('mousedown.ngKey', keyPressed); 64 | body.bind('mouseup.ngKey', keyDepressed); 65 | } 66 | 67 | 68 | init(); 69 | }; 70 | 71 | /** 72 | * @Event 73 | */ 74 | Key.PRESSED = "Key.PRESSED"; 75 | 76 | angular.module('ngKeypad', []) 77 | .directive('ngKey', function () { 78 | return { 79 | restrict: 'A', 80 | link: function ($scope, $element, $attrs) { 81 | new Key($scope, $element, $attrs); 82 | } 83 | }; 84 | }); 85 | })(); 86 | -------------------------------------------------------------------------------- /src/ngKeypad/ngKeypad.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Keypad directive, display a keypad on the screen. Template for it 3 | * can be include inside the node containing the keypad attribute. 4 | * 5 | * Use of Namespace.js 6 | * 7 | * @author tommy.rochette[followed by the usual sign]universalmind.com 8 | */ 9 | 10 | (function () { 11 | "use strict"; 12 | 13 | 14 | /** 15 | * @Class 16 | */ 17 | window.Keypad = function ($scope, $element, $attrs, $rootScope) { 18 | 19 | var locked = false, 20 | opened = false, 21 | padId = $attrs.ngKeypad, 22 | body = $('body'); 23 | 24 | /** 25 | * Initialize Keypad to default settings 26 | * based on attributes. 27 | */ 28 | function init() { 29 | $scope.$on(Key.PRESSED, handleKeyPressed); 30 | $rootScope.$on(Keypad.TOGGLE_LOCKING, handleLockingToggle); 31 | $rootScope.$on(Keypad.TOGGLE_OPENING, handleOpeningToggle); 32 | $rootScope.$on(Keypad.OPEN, handleOpeningToggle); 33 | $rootScope.$on(Keypad.CLOSE, handleOpeningToggle); 34 | $element.addClass("closed"); 35 | 36 | initScope(); 37 | }; 38 | 39 | /** 40 | * Initialize scope variables 41 | */ 42 | function initScope() { 43 | $scope.close = function () { 44 | close(); 45 | } 46 | } 47 | 48 | 49 | /** 50 | * Triggered when a user press any key on the pad 51 | * check current status then dispatch KEY_PRESSED event. 52 | * 53 | * @param event 54 | * @param key 55 | */ 56 | function handleKeyPressed(event, key) { 57 | if (!locked) { 58 | if (key.indexOf('[') === -1 && key.indexOf(']') === -1) { 59 | $scope.$emit(Keypad.KEY_PRESSED, key, padId); 60 | } else { 61 | $scope.$emit(Keypad.MODIFIER_KEY_PRESSED, key.substring(1, key.length - 1), padId); 62 | } 63 | } 64 | } 65 | 66 | 67 | /** 68 | * Triggered when a user press any key on the pad 69 | * check current status then dispatch KEY_PRESSED event. 70 | * 71 | * @param event 72 | * @param key 73 | */ 74 | function handleLockingToggle(event, id) { 75 | if (padId === id || !id) { 76 | locked = !locked; 77 | 78 | if (locked) { 79 | $element.attr("disabled", "disabled"); 80 | } else { 81 | $element.removeAttr("disabled"); 82 | } 83 | } 84 | } 85 | 86 | 87 | /** 88 | * Triggered when a user toggle keypad opening. 89 | * 90 | * @param event 91 | * @param key 92 | */ 93 | function handleOpeningToggle(event, id, params) { 94 | if (padId === id || !id) { 95 | switch (event.name) { 96 | case Keypad.CLOSE: 97 | opened = false; 98 | break; 99 | case Keypad.OPEN: 100 | opened = true; 101 | break; 102 | default: 103 | opened = !opened; 104 | break; 105 | } 106 | 107 | if (!opened) { 108 | close(); 109 | } else { 110 | open(); 111 | applyOptions(params); 112 | } 113 | } 114 | } 115 | 116 | function applyOptions(params) { 117 | if (params && params.position) { 118 | $element.css("top", params.position.y); 119 | $element.css("left", params.position.x); 120 | } 121 | } 122 | 123 | 124 | /** 125 | * Open current pad 126 | */ 127 | function open() { 128 | $element.removeClass("closed"); 129 | $scope.$emit(Keypad.OPENED, padId); 130 | autoClose(); 131 | applyOptions(); 132 | } 133 | 134 | 135 | /** 136 | * Close current pad 137 | */ 138 | function close() { 139 | opened = false; 140 | $scope.$emit(Keypad.CLOSED, padId); 141 | body.off("click.keypad"); 142 | $element.addClass("closed"); 143 | } 144 | 145 | 146 | /** 147 | * Check if the attribute auto-close is set then 148 | * add event for automatic closing. 149 | * 150 | * @param event 151 | * @param key 152 | */ 153 | function autoClose() { 154 | if ($attrs.autoClose) { 155 | //Timeout use to break the event flow. 156 | setTimeout(function () { 157 | body.on("click.keypad", function () { 158 | opened = !opened; 159 | close(); 160 | if (!$scope.$$phase) { 161 | $scope.$apply(); 162 | } 163 | }); 164 | $element.on("click.keypad", cancelEvent); 165 | }, 1); 166 | } 167 | } 168 | 169 | 170 | /** 171 | * Dummy function to cancel event propagation. 172 | * 173 | * @param event 174 | */ 175 | function cancelEvent(event) { 176 | event.stopPropagation(); 177 | } 178 | 179 | 180 | init(); 181 | }; 182 | 183 | 184 | /** 185 | * @Events 186 | */ 187 | Keypad.KEY_PRESSED = "Keypad.KEY_PRESSED"; 188 | Keypad.MODIFIER_KEY_PRESSED = "Keypad.MODIFIER_KEY_PRESSED"; 189 | Keypad.TOGGLE_LOCKING = "Keypad.TOGGLE_LOCKING"; 190 | Keypad.TOGGLE_OPENING = "Keypad.TOGGLE_OPENING"; 191 | Keypad.OPENED = "Keypad.OPENED"; 192 | Keypad.CLOSED = "Keypad.CLOSED"; 193 | Keypad.OPEN = "Keypad.OPEN"; 194 | Keypad.CLOSE = "Keypad.CLOSE"; 195 | 196 | 197 | angular.module('ngKeypad') 198 | .directive('ngKeypad', ['$rootScope', function ($rootScope) { 199 | 200 | return { 201 | restrict: 'A', 202 | link: function ($scope, $element, $attrs) { 203 | new Keypad($scope, $element, $attrs, $rootScope); 204 | } 205 | } 206 | }]); 207 | })(); -------------------------------------------------------------------------------- /src/ngKeypad/ngKeypadInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview 3 | * @copyright 2013 Tommy Rochette http://trochette.github.com/ 4 | * @author Tommy Rochette 5 | * @version 0.0.1 6 | * 7 | * @license MIT 8 | */ 9 | (function () { 10 | "use strict"; 11 | 12 | /** 13 | * @Class 14 | */ 15 | window.KeypadInput = function ($scope, $element, $attrs, controller) { 16 | 17 | var self = this; 18 | 19 | this.modifierKeyListener = null; 20 | this.keyListener = null; 21 | this.element = $element; 22 | this.active = false; 23 | this.model = null; 24 | this.padId = $attrs.ngKeypadInput; 25 | 26 | 27 | /** 28 | * Initialize Key to default settings 29 | */ 30 | function init() { 31 | $element.on('click.ngKeypadInput', handleElementSelected); 32 | $element.on('focus.ngKeypadInput', handleElementSelected); 33 | $element.on('blur.ngKeypadInput', handleElementBlur); 34 | 35 | if (!$attrs.ngModel) { 36 | throw new Error("KeypadInput requires the use of ng-model on this element", $element); 37 | } 38 | 39 | $scope.$on('destroy', destroy); 40 | }; 41 | 42 | 43 | /** 44 | * Triggered when a user select a keypad input field. 45 | * Set listeners and add focused class. 46 | * 47 | * @param event 48 | */ 49 | function handleElementSelected(event) { 50 | event.stopPropagation(); 51 | 52 | if (!self.active) { 53 | clearSelectedElement(); 54 | 55 | self.active = true; 56 | self.keyListener = $scope.$on(Keypad.KEY_PRESSED, handleKeyPressed); 57 | self.closeListener = $scope.$on(Keypad.CLOSED, handleKeypadClosed); 58 | self.modifierKeyListener = $scope.$on(Keypad.MODIFIER_KEY_PRESSED, handleModifierKeyPressed); 59 | 60 | $element.addClass("focus"); 61 | $element.focus(); 62 | 63 | $scope.$emit(Keypad.OPEN, $attrs.ngKeypadInput); 64 | 65 | KeypadInput.selectedInput = self; 66 | } 67 | }; 68 | 69 | 70 | /** 71 | * Clear old reference of the selected element 72 | */ 73 | function clearSelectedElement() { 74 | if (KeypadInput.selectedInput) { 75 | KeypadInput.selectedInput.active = false; 76 | KeypadInput.selectedInput.keyListener(); 77 | KeypadInput.selectedInput.element.removeClass("focus"); 78 | KeypadInput.selectedInput = null; 79 | } 80 | } 81 | 82 | 83 | /** 84 | * Triggered when keypad is closing to clear 85 | * the current input being edited. 86 | * 87 | * @param event 88 | * @param padId 89 | */ 90 | 91 | function handleKeypadClosed(event, padId) { 92 | if (self.padId === padId && self.active) { 93 | clearSelectedElement(); 94 | } 95 | } 96 | 97 | 98 | /** 99 | * Trigerred when the input loose focus to 100 | * clear selected element. 101 | * 102 | * @param event 103 | */ 104 | function handleElementBlur(event) { 105 | clearSelectedElement(); 106 | } 107 | 108 | 109 | /** 110 | * Event triggered from ngKeyPad called only if the current 111 | * input is selected. Works with ngModel. 112 | * 113 | * @param event 114 | * @param key 115 | * @param padId 116 | */ 117 | function handleKeyPressed(event, key, padId) { 118 | if (self.padId === padId && self.active) { 119 | var value = controller.$viewValue; 120 | 121 | 122 | if (!value) { 123 | value = ""; 124 | } 125 | 126 | if (!isRestricted(value, key)) { 127 | value += key; 128 | } 129 | 130 | controller.$setViewValue(value); 131 | $scope.$apply(); 132 | } 133 | }; 134 | 135 | 136 | /** 137 | * Triggered when a modifier key is pressed 138 | * 139 | * @param event 140 | * @param key 141 | * @param padId 142 | */ 143 | function handleModifierKeyPressed(event, key, padId) { 144 | if (self.padId === padId && self.active) { 145 | if (key === "CLEAR") { 146 | controller.$setViewValue(""); 147 | $scope.$apply(); 148 | } 149 | } 150 | }; 151 | 152 | 153 | /** 154 | * Check if the input is retricted by a RegExp. 155 | * data-ng-keypad-restric is used to save the string 156 | * corresponding to the RegExp 157 | * 158 | * @param value 159 | * @param key 160 | */ 161 | function isRestricted(value, key) { 162 | var restricted = false, 163 | regExp = new RegExp($attrs.ngKeypadRestrict, 'gi'); 164 | 165 | if ($attrs.ngKeypadRestrict) { 166 | if (!key.match(regExp)) { 167 | restricted = true; 168 | } else if (!String(value + key).match(regExp, 'gi')) { 169 | restricted = true; 170 | } 171 | } 172 | 173 | return restricted; 174 | } 175 | 176 | 177 | /** 178 | * Cleanup all listeners before destroying this directive. 179 | */ 180 | function destroy() { 181 | $element.off('click.ngKeypadInput'); 182 | $element.on('focus.ngKeypadInput', this.handleElementSelected); 183 | }; 184 | 185 | 186 | init(); 187 | }; 188 | 189 | 190 | KeypadInput.selectedInput = null; 191 | 192 | 193 | angular.module('ngKeypad') 194 | .directive('ngKeypadInput', function () { 195 | return { 196 | restrict: 'A', 197 | require: '^ngModel', 198 | link: function ($scope, $element, $attrs, controller) { 199 | new KeypadInput($scope, $element, $attrs, controller); 200 | } 201 | } 202 | }); 203 | })(); --------------------------------------------------------------------------------