var app = angular.module('plunker', ['ui.mask']);
app.controller('MainCtrl', function($scope) {
$scope.invoiceItems = [{"invoiceNumber":"3067095","displayInvoiceNumber":"260996530","invoiceDate":"2013-08-01T05:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2013-08-31T05:00:00.0000000","hasSubAccount":false},{"invoiceNumber":"3086446","displayInvoiceNumber":"260374907","invoiceDate":"2013-07-01T05:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2013-07-31T05:00:00.0000000","hasSubAccount":false},{"invoiceNumber":"3053215","displayInvoiceNumber":"255453017","invoiceDate":"2012-12-01T06:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2012-12-31T06:00:00.0000000","hasSubAccount":false}];
$scope.productItems = [{"productNumber":"3067095","displayInvoiceNumber":"260996530","productOnlineDate":"2013-08-01T05:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2013-08-31T05:00:00.0000000","hasSubAccount":false},{"productNumber":"3086446","displayInvoiceNumber":"260374907","productOnlineDate":"2013-07-01T05:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2013-07-31T05:00:00.0000000","hasSubAccount":false},{"productNumber":"3053215","displayInvoiceNumber":"255453017","productOnlineDate":"2012-12-01T06:00:00.0000000","invoiceAmount":0,"balanceDue":0,"dueDate":"2012-12-31T06:00:00.0000000","hasSubAccount":false}];
$scope.telephoneNumber = "";
$scope.fruitList =[{
"fruitName": "Apple",
"fruitColor": "Red"
},
{
"fruitName": "Banana",
"fruitColor": "Yellow"
},
{
"fruitName": "Orange",
"fruitColor": "Orange"
},
{
"fruitName": "Mango",
"fruitColor": "Orange"
}
];
$scope.moneyList =[{
"amountDue": "10000.00",
"customerAccountNumber": "1234"
},
{
"amountDue": "70000.00",
"customerAccountNumber": "5678"
},
{
"amountDue": "2000001.50",
"customerAccountNumber": "0987"
},
{
"amountDue": "100450.00",
"customerAccountNumber": "7654"
}
];
$scope.phoneList = [{
"phoneNumber": "2145557813",
"name": "Bob"
},
{
"phoneNumber": "+17145555432",
"name": "John"
},
{
"phoneNumber": "303.555.5987",
"name": "Sam"
},
{
"phoneNumber": "214-555-9876",
"name": "Mary"
}
];
$scope.callbackdateselectionchanged = function (value) {
alert(value);
};
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js@*" data-semver="1.2.0" src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.6.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script src="app.js"></script>
<script src="telephone-filter.js"></script>
<script src="telephonedirective.js"></script>
<script src="mask.js"></script>
</head>
<body ng-controller="MainCtrl">
<div telephone ng-Model="telephoneNumber"></div>
</body>
</html>
/* Put your css in here */
<div>
<label>Number:</label>
<input ui-mask="(999) 999-9999" type="text" ng-model="telNumb" />
<input ui-mask="x9999" type="text" ng-model="telExt" />
<hr />
<h1>Phone Number: {{telNumb |tel}} Ext:{{telExt}}</h1>
</div>
app.directive('telephone', function() {
return {
restrict: 'A',
scope: {
data: '=',
clickCallback: '&',
selectedItem: '=',
ngMode: '='
},
templateUrl: 'telephone.html',
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optDescription = attrs.optDescription;
scope.optFilter = attrs.optFilter;
}
};
});
angular.module('ui.mask', [])
.value('uiMaskConfig', {
'maskDefinitions': {
'9': /\d/,
'A': /[a-zA-Z]/,
'*': /[a-zA-Z0-9]/
}
})
.directive('uiMask', ['uiMaskConfig', function (maskConfig) {
return {
priority: 100,
require: 'ngModel',
restrict: 'A',
compile: function uiMaskCompilingFunction(){
var options = maskConfig;
return function uiMaskLinkingFunction(scope, iElement, iAttrs, controller){
var maskProcessed = false, eventsBound = false,
maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
// Minimum required length of the value to be considered valid
minRequiredLength,
value, valueMasked, isValid,
// Vars for initializing/uninitializing
originalPlaceholder = iAttrs.placeholder,
originalMaxlength = iAttrs.maxlength,
// Vars used exclusively in eventHandler()
oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;
function initialize(maskAttr){
if (!angular.isDefined(maskAttr)) {
return uninitialize();
}
processRawMask(maskAttr);
if (!maskProcessed) {
return uninitialize();
}
initializeElement();
bindEventListeners();
return true;
}
function initPlaceholder(placeholderAttr) {
if(! angular.isDefined(placeholderAttr)) {
return;
}
maskPlaceholder = placeholderAttr;
// If the mask is processed, then we need to update the value
if (maskProcessed) {
eventHandler();
}
}
function formatter(fromModelValue){
if (!maskProcessed) {
return fromModelValue;
}
value = unmaskValue(fromModelValue || '');
isValid = validateValue(value);
controller.$setValidity('mask', isValid);
return isValid && value.length ? maskValue(value) : undefined;
}
function parser(fromViewValue){
if (!maskProcessed) {
return fromViewValue;
}
value = unmaskValue(fromViewValue || '');
isValid = validateValue(value);
// We have to set viewValue manually as the reformatting of the input
// value performed by eventHandler() doesn't happen until after
// this parser is called, which causes what the user sees in the input
// to be out-of-sync with what the controller's $viewValue is set to.
controller.$viewValue = value.length ? maskValue(value) : '';
controller.$setValidity('mask', isValid);
if (value === '' && controller.$error.required !== undefined) {
controller.$setValidity('required', false);
}
return isValid ? value : undefined;
}
var linkOptions = {};
if (iAttrs.uiOptions) {
linkOptions = scope.$eval('[' + iAttrs.uiOptions + ']');
if (angular.isObject(linkOptions[0])) {
// we can't use angular.copy nor angular.extend, they lack the power to do a deep merge
linkOptions = (function(original, current){
for(var i in original) {
if (Object.prototype.hasOwnProperty.call(original, i)) {
if (!current[i]) {
current[i] = angular.copy(original[i]);
} else {
angular.extend(current[i], original[i]);
}
}
}
return current;
})(options, linkOptions[0]);
}
} else {
linkOptions = options;
}
iAttrs.$observe('uiMask', initialize);
iAttrs.$observe('placeholder', initPlaceholder);
controller.$formatters.push(formatter);
controller.$parsers.push(parser);
function uninitialize(){
maskProcessed = false;
unbindEventListeners();
if (angular.isDefined(originalPlaceholder)) {
iElement.attr('placeholder', originalPlaceholder);
} else {
iElement.removeAttr('placeholder');
}
if (angular.isDefined(originalMaxlength)) {
iElement.attr('maxlength', originalMaxlength);
} else {
iElement.removeAttr('maxlength');
}
iElement.val(controller.$modelValue);
controller.$viewValue = controller.$modelValue;
return false;
}
function initializeElement(){
value = oldValueUnmasked = unmaskValue(controller.$modelValue || '');
valueMasked = oldValue = maskValue(value);
isValid = validateValue(value);
var viewValue = isValid && value.length ? valueMasked : '';
if (iAttrs.maxlength) { // Double maxlength to allow pasting new val at end of mask
iElement.attr('maxlength', maskCaretMap[maskCaretMap.length - 1] * 2);
}
iElement.attr('placeholder', maskPlaceholder);
iElement.val(viewValue);
controller.$viewValue = viewValue;
// Not using $setViewValue so we don't clobber the model value and dirty the form
// without any kind of user interaction.
}
function bindEventListeners(){
if (eventsBound) {
return;
}
//iElement.bind('blur', blurHandler);
iElement.bind('mousedown mouseup', mouseDownUpHandler);
iElement.bind('input keyup click', eventHandler);
eventsBound = true;
}
function unbindEventListeners(){
if (!eventsBound) {
return;
}
//iElement.unbind('blur', blurHandler);
iElement.unbind('mousedown', mouseDownUpHandler);
iElement.unbind('mouseup', mouseDownUpHandler);
iElement.unbind('input', eventHandler);
iElement.unbind('keyup', eventHandler);
iElement.unbind('click', eventHandler);
eventsBound = false;
}
function validateValue(value){
// Zero-length value validity is ngRequired's determination
return value.length ? value.length >= minRequiredLength : true;
}
function unmaskValue(value){
var valueUnmasked = '',
maskPatternsCopy = maskPatterns.slice();
// Preprocess by stripping mask components from value
value = value.toString();
angular.forEach(maskComponents, function (component){
value = value.replace(component, '');
});
angular.forEach(value.split(''), function (chr){
if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
valueUnmasked += chr;
maskPatternsCopy.shift();
}
});
return valueUnmasked;
}
function maskValue(unmaskedValue){
var valueMasked = '',
maskCaretMapCopy = maskCaretMap.slice();
angular.forEach(maskPlaceholder.split(''), function (chr, i){
if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
valueMasked += unmaskedValue.charAt(0) || '_';
unmaskedValue = unmaskedValue.substr(1);
maskCaretMapCopy.shift();
}
else {
valueMasked += chr;
}
});
return valueMasked;
}
function getPlaceholderChar(i) {
var placeholder = iAttrs.placeholder;
if (typeof placeholder !== "undefined" && placeholder[i]) {
return placeholder[i];
} else {
return "_";
}
}
// Generate array of mask components that will be stripped from a masked value
// before processing to prevent mask components from being added to the unmasked value.
// E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value.
// If a maskable char is followed by a mask char and has a mask
// char behind it, we'll split it into it's own component so if
// a user is aggressively deleting in the input and a char ahead
// of the maskable char gets deleted, we'll still be able to strip
// it in the unmaskValue() preprocessing.
function getMaskComponents() {
return maskPlaceholder.replace(/[_]+/g, '_').replace(/([^_]+)([a-zA-Z0-9])([^_])/g, '$1$2_$3').split('_');
}
function processRawMask(mask){
var characterCount = 0;
maskCaretMap = [];
maskPatterns = [];
maskPlaceholder = '';
if (typeof mask === 'string') {
minRequiredLength = 0;
var isOptional = false,
splitMask = mask.split("");
angular.forEach(splitMask, function (chr, i){
if (linkOptions.maskDefinitions[chr]) {
maskCaretMap.push(characterCount);
maskPlaceholder += getPlaceholderChar(i);
maskPatterns.push(linkOptions.maskDefinitions[chr]);
characterCount++;
if (!isOptional) {
minRequiredLength++;
}
}
else if (chr === "?") {
isOptional = true;
}
else {
maskPlaceholder += chr;
characterCount++;
}
});
}
// Caret position immediately following last position is valid.
maskCaretMap.push(maskCaretMap.slice().pop() + 1);
maskComponents = getMaskComponents();
maskProcessed = maskCaretMap.length > 1 ? true : false;
}
//*************************************************************
//joe h - this will clear the contents if the input doesn't match the mask
function blurHandler(){
oldCaretPosition = 0;
oldSelectionLength = 0;
if (!isValid || value.length === 0) {
valueMasked = '';
iElement.val('');
scope.$apply(function (){
controller.$setViewValue('');
});
}
}
function mouseDownUpHandler(e){
if (e.type === 'mousedown') {
iElement.bind('mouseout', mouseoutHandler);
} else {
iElement.unbind('mouseout', mouseoutHandler);
}
}
iElement.bind('mousedown mouseup', mouseDownUpHandler);
function mouseoutHandler(){
oldSelectionLength = getSelectionLength(this);
iElement.unbind('mouseout', mouseoutHandler);
}
function eventHandler(e){
e = e || {};
// Allows more efficient minification
var eventWhich = e.which,
eventType = e.type;
// Prevent shift and ctrl from mucking with old values
if (eventWhich === 16 || eventWhich === 91) { return;}
var val = iElement.val(),
valOld = oldValue,
valMasked,
valUnmasked = unmaskValue(val),
valUnmaskedOld = oldValueUnmasked,
valAltered = false,
caretPos = getCaretPosition(this) || 0,
caretPosOld = oldCaretPosition || 0,
caretPosDelta = caretPos - caretPosOld,
caretPosMin = maskCaretMap[0],
caretPosMax = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),
selectionLenOld = oldSelectionLength || 0,
isSelected = getSelectionLength(this) > 0,
wasSelected = selectionLenOld > 0,
// Case: Typing a character to overwrite a selection
isAddition = (val.length > valOld.length) || (selectionLenOld && val.length > valOld.length - selectionLenOld),
// Case: Delete and backspace behave identically on a selection
isDeletion = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
isSelection = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes
isKeyLeftArrow = eventWhich === 37,
// Necessary due to "input" event not providing a key code
isKeyBackspace = eventWhich === 8 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
isKeyDelete = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0 ) && !wasSelected),
// Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
// ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
// non-mask character. Also applied to click since users are (arguably) more likely to backspace
// a character when clicking within a filled input.
caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;
oldSelectionLength = getSelectionLength(this);
// These events don't require any action
if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup'))) {
return;
}
// Value Handling
// ==============
// User attempted to delete but raw value was unaffected--correct this grievous offense
if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)) {
caretPos--;
}
while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1) {
caretPos++;
}
var charIndex = maskCaretMap.indexOf(caretPos);
// Strip out non-mask character that user would have deleted if mask hadn't been in the way.
valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
valAltered = true;
}
// Update values
valMasked = maskValue(valUnmasked);
oldValue = valMasked;
oldValueUnmasked = valUnmasked;
iElement.val(valMasked);
if (valAltered) {
// We've altered the raw value after it's been $digest'ed, we need to $apply the new value.
scope.$apply(function (){
controller.$setViewValue(valUnmasked);
});
}
// Caret Repositioning
// ===================
// Ensure that typing always places caret ahead of typed character in cases where the first char of
// the input is a mask char and the caret is placed at the 0 position.
if (isAddition && (caretPos <= caretPosMin)) {
caretPos = caretPosMin + 1;
}
if (caretBumpBack) {
caretPos--;
}
// Make sure caret is within min and max position limits
caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;
// Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax) {
caretPos += caretBumpBack ? -1 : 1;
}
if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))) {
caretPos++;
}
oldCaretPosition = caretPos;
setCaretPosition(this, caretPos);
}
function isValidCaretPosition(pos){ return maskCaretMap.indexOf(pos) > -1; }
function getCaretPosition(input){
if (input.selectionStart !== undefined) {
return input.selectionStart;
} else if (document.selection) {
// Curse you IE
input.focus();
var selection = document.selection.createRange();
selection.moveStart('character', -input.value.length);
return selection.text.length;
}
return 0;
}
function setCaretPosition(input, pos){
if (input.offsetWidth === 0 || input.offsetHeight === 0) {
return; // Input's hidden
}
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(pos, pos);
}
else if (input.createTextRange) {
// Curse you IE
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
function getSelectionLength(input){
if (input.selectionStart !== undefined) {
return (input.selectionEnd - input.selectionStart);
}
if (document.selection) {
return (document.selection.createRange().text.length);
}
return 0;
}
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */){
"use strict";
if (this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n !== n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
};
}
};
}
]);
app.filter('tel', function() {
return function(phonenum) {
var regexObj = /^(?:\+?1[-. ]?)?(?:\(?([0-9]{3})\)?[-. ]?)?([0-9]{3})[-. ]?([0-9]{4})$/;
if (regexObj.test(phonenum)) {
var parts = phonenum.match(regexObj);
var phone = "";
if (parts[1]) {
phone += "+1 (" + parts[1] + ") ";
}
phone += parts[2] + "-" + parts[3];
return phone;
} else {
//invalid phone number
return phonenum;
}
};
});