CodePen

HTML

            
              <style>
@import url(http://weloveiconfonts.com/api/?family=entypo);
[class*="entypo-"]:before {
	font-family: 'entypo', sans-serif;
}
</style>

<div ng-app="android-addressbook">
	<div ng-controller="AddressBook.Init">
	<div class="row main-app">
		<div ng-view></div>
	</div>
	</div>

  
  
<script type="text/ng-template" id="list.html">
	<ul class="fixed top full bar topbar tabs two">
		<li class="icon active">
			<a href='#/contacts'><span class="entypo-user"></span></a>
		</li>
		<li class="icon">
			<a href='#/contacts/starred'><span class="entypo-star"></span></a>
		</li>
	</ul>
	<div class="content" id="wrapper">
		<div class="lists" id="scroller">
			<div ng:repeat="group in groups">
				<div class="title fleft full">{{ group.label }}</div>
				<ul class="list single-fill">
					<li class="list-item" ng:repeat="contact in group.contacts">
						<a class="item" href='#/contact/view/{{ contact._id.$oid }}'>
							<span class="fleft name">{{ contact.firstName + ' ' + contact.lastName }}</span>
							<span class="fright image"><img width="60" ng-src="{{ ProfileImage('60x60', contact) }}"/></span>
						</a>
					</li>
				</ul>
			</div>
		</div>
	</div>
	<ul class="fixed bottom full bar bottombar">
		<li class="icon icn left">
			<a href='#/contacts/search'><span class="entypo-search"></span></a>
		</li>
		<li class="icon icn center">
			<a href='#/contact/add'><span class="entypo-user-add"></span></a>
		</li>
	</ul>
</script>

  
  

<script type="text/ng-template" id="starred.html">
	<ul class="fixed top full bar topbar tabs two">
		<li class="icon">
			<a href='#/contacts'><span class="entypo-user"></span></a>
		</li>
		<li class="icon active">
			<a href='#/contacts/starred'><span class="entypo-star"></span></a>
		</li>
	</ul>
	
	<div class="content" id="wrapper">
		<div id="scroller" class="fleft">
			<div class="lists full">
				<ul class="list single-fill">
					<li class="list-item half" ng:repeat="contact in starred">
						<a class="item profile-image" href='#/contact/view/{{ contact._id.$oid }}'>
							<img width="100%" ng-src="{{ ProfileImage('480x480', contact) }}"/>
							<span class="absolute bottom caption">
								<span class="text">{{ contact.firstName + ' ' + contact.lastName }}</span>
								<span class="overlay"></span>
							</span>
						</a>
					</li>
				</ul>
			</div>
			<div class="lists">
				<div class="title fleft full">Frequently viewed</div>
				<ul class="list single-fill">
					<li class="list-item" ng:repeat="contact in contacts">
						<a class="item" href='#/contact/view/{{ contact._id.$oid }}'>
							<span class="fleft name">{{ contact.firstName + ' ' + contact.lastName }}</span>
							<span class="fright image"><img width="60" ng-src="{{ ProfileImage('60x60', contact) }}"/></span>
						</a>
					</li>
				</ul>
			</div>
		</div>
	</div>
	
	<ul class="fixed bottom full bar bottombar">
		<li class="icon icn left">
			<a href='#/contacts/search'><span class="entypo-search"></span></a>
		</li>
		<li class="icon icn center">
			<a href='#/contact/add'><span class="entypo-user-add"></span></a>
		</li>
	</ul>
</script>

<script type="text/ng-template" id="search.html">
	<ul class="fixed top full bar topbar">
		<li class="icon icn left">
			<a href='javascript:void(0)' ng-click="Back()"><span class="entypo-left-open-big"></span></a>
		</li>
		<li class="contact-name icn full form">
			<div class="form-item">
				<input placeholder="Find contacts" type="text" id="searchterm" name="searchterm" ng-model="searchterm"/>
				<span class="form-item-decorator"></span>
				<span class="cancel entypo-cancel-circled" ng-show="searchterm" ng-click="searchterm=''"></span>
			</div>
		</li>
	</ul>
	
	<div class="content no-bottombar" id="wrapper">
		<div class="lists" id="scroller">
			<div ng:repeat="group in groups">
				<div class="title fleft full">{{ group.label }}</div>
				<ul class="list single-fill">
					<li class="list-item" ng:repeat="contact in group.contacts | filter:searchterm">
						<a class="item" href='#/contact/view/{{ contact._id.$oid }}'>
							<span class="fleft name">{{ contact.firstName + ' ' + contact.lastName }}</span>
							<span class="fright image"><img width="60" ng-src="{{ ProfileImage('60x60', contact) }}"/></span>
						</a>
					</li>
				</ul>
			</div>
		</div>
	</div>
</script>

  
  
  
<script type="text/ng-template" id="view.html">
	<ul class="fixed top full bar topbar">
		<li class="icon icn left">
			<a href='javascript:void(0)' ng-click="Back()"><span class="entypo-left-open-big"></span></a>
		</li>
		<li class="contact-name icn full" ng-bind="FullName()"></li>
		<li class="icon icn right sec">
			<a href='javascript:void(0)'><span ng-click="StarUnStar()" ng-class="{'entypo-star': contact.starred, 'entypo-star-empty': !contact.starred}"></span></a>
		</li>
		<li class="icon icn right" ng-click="_submenu()" ng-class="{'show-submenu': submenu}">
			<a href='javascript:void(0)'><span class="entypo-pencil"></span></a>
			<ul class="submenu">
				<li>
					<a href='#/contact/edit/{{ contact._id.$oid }}'>edit</a>
				</li>
				<li>
					<a href='javascript:void(0)' ng-click="DeleteContact()">delete</a>
				</li>
			</ul>
		</li>
	</ul>
	
	<div class="content no-bottombar" id="wrapper">
		<div class="lists fleft" id="scroller">
			
			<div class="profile-image" ng-click="_showImage()" ng-class="{open: selected}">
				<img ng-src="{{ ProfileImage('480x480') }}"/>
			</div>
			
			<div ng-show="contact.phones.length">
				<div class="title fleft full">Phone</div>
				<ul class="list">
					<li class="list-item" ng:repeat="item in contact.phones">
						<a class="item" href="tel:{{ item.value }}">
							<span class="value">{{ item.value }}</span>
							<span class="label">{{ item.type }}</span>
						</a>
					</li>
				</ul>
			</div>
			
			<div ng-show="contact.emails.length">
				<div class="title fleft full">Email</div>
				<ul class="list">
					<li class="list-item" ng:repeat="item in contact.emails">
						<a class="item" target="_black" href="mailto:{{ item.value }}">
							<span class="value">{{ item.value }}</span>
							<span class="label">{{ item.type }}</span>
						</a>
					</li>
				</ul>
			</div>
			
			<div ng-show="contact.addresses.length">
				<div class="title fleft full">Address</div>
				<ul class="list">
					<li class="list-item" ng:repeat="item in contact.addresses">
						<a class="item" target="_black" href="https://maps.google.com/maps?q={{ item.value }}">
							<span class="value">{{ item.value }}</span>
							<span class="label">{{ item.type }}</span>
						</a>
					</li>
				</ul>
			</div>
			
			<div ng-show="contact.birthday">
				<div class="title fleft full">Birthday</div>
				<ul class="list">
					<li class="list-item">
						<span class="item">
							<span class="value">{{ contact.birthday }}</span>
						</span>
					</li>
				</ul>
			</div>
			
			<div ng-show="contact.websites.length">
				<div class="title fleft full">Websites</div>
				<ul class="list">
					<li class="list-item" ng:repeat="item in contact.websites">
						<a class="item" target="_black" href="{{ item.value }}">
							<span class="value">{{ item.value }}</span>
							<span class="label">{{ item.type }}</span>
						</a>
					</li>
				</ul>
			</div>
			
			<div ng-show="contact.notes">
				<div class="title fleft full">Notes</div>
				<ul class="list">
					<li class="list-item">
						<span class="item">
							<span class="value">{{ contact.notes }}</span>
						</span>
					</li>
				</ul>
			</div>
		</div>
	</div>
</script>

  
  
  
<script type="text/ng-template" id="edit.html">
	<ul class="fixed top full bar topbar">
		<li class="icon icn left">
			<a href='javascript:void(0)' ng-click="SaveContact()"><span ng-class="{'entypo-dot': contactForm.name.$invalid, 'entypo-check': !contactForm.name.$invalid}"></span></a>
		</li>
		<li class="contact-name icn full" ng-bind="FullName()"></li>
		<li class="icon icn right" ng-show="contact._id.$oid">
			<a ng-href="#/contact/view/{{ contact._id.$oid }}"><span class="entypo-cancel"></span></a>
		</li>
		<li class="icon icn right" ng-show="!contact._id.$oid">
			<a href='javascript:void(0)' ng-click="Back()"><span class="entypo-cancel"></span></a>
		</li>
	</ul>
	
	<div class="content no-bottombar" id="wrapper">
		<div class="lists fleft" id="scroller">
			
			<form class="form" name="contactForm">
				<div class="title fleft full">Name</div>
				<ul class="list">
					<li class="list-item">
						<div class="item form-item">
							<input type="text" name="name" maxlength="25" ng-model="contact.firstName" required />
								<span class="form-item-decorator"></span>
							</div>
						</li>
						<li class="list-item">
							<div class="item form-item">
								<input type="text" maxlength="25" ng-model="contact.lastName"/>
								<span class="form-item-decorator"></span>
							</div>
						</li>
					</ul>
					
					<div class="title fleft full">Picture URL</div>
					<ul class="list">
						<li class="list-item">
							<div class="item form-item field-left img">
								<input type="text" ng-model="contact.picture"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right img">
								<img width="60" ng-src="{{ ProfileImage('60x60') }}"/>
							</div>
						</li>
					</ul>
					
					<div class="title fleft full">Phone</div>
					<ul class="list">
						<li class="list-item" ng:repeat="item in contact.phones">
							<div class="item form-item field-left">
								<input type="text" ng-model="item.value"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right">
								<input type="text" ng-model="item.type"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right-right action">
								<button class="delete" ng-click="DiscardField('phones', $index)"><span class="entypo-cancel"></span></button>
							</div>
						</li>
						<li class="list-item action">
							<button class="save" ng-click="AddField('phones')">Add new</button>
						</li>
					</ul>
					
					<div class="title fleft full">Email</div>
					<ul class="list">
						<li class="list-item" ng:repeat="item in contact.emails">
							<div class="item form-item field-left">
								<input type="text" ng-model="item.value"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right">
								<input type="text" ng-model="item.type"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right-right action">
								<button class="delete" ng-click="DiscardField('emails', $index)"><span class="entypo-cancel"></span></button>
							</div>
						</li>
						<li class="list-item action">
							<button class="save" ng-click="AddField('emails')">Add new</button>
						</li>
					</ul>
					
					<div class="title fleft full">Address</div>
					<ul class="list">
						<li class="list-item" ng:repeat="item in contact.addresses">
							<div class="item form-item field-left">
								<input type="text" ng-model="item.value"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right">
								<input type="text" ng-model="item.type"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right-right action">
								<button class="delete" ng-click="DiscardField('addresses', $index)"><span class="entypo-cancel"></span></button>
							</div>
						</li>
						<li class="list-item action">
							<button class="save" ng-click="AddField('addresses')">Add new</button>
						</li>
					</ul>
					
					<div class="title fleft full">Birthday</div>
					<ul class="list">
						<li class="list-item">
							<div class="item form-item">
								<input type="date" ng-model="contact.birthday"/>
								<span class="form-item-decorator"></span>
							</div>
						</li>
					</ul>
					
					<div class="title fleft full">Websites</div>
					<ul class="list">
						<li class="list-item" ng:repeat="item in contact.websites">
							<div class="item form-item field-left">
								<input type="url" ng-model="item.value"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right">
								<input type="text" ng-model="item.type"/>
								<span class="form-item-decorator"></span>
							</div>
							<div class="item form-item field-right-right action">
								<button class="delete" ng-click="DiscardField('websites', $index)"><span class="entypo-cancel"></span></button>
							</div>
						</li>
						<li class="list-item action">
							<button class="save" ng-click="AddField('websites')">Add new</button>
						</li>
					</ul>
					
					<div class="title fleft full">Notes</div>
					<ul class="list">
						<li class="list-item">
							<div class="item form-item">
								<textarea ng-model="contact.notes"></textarea>
								<span class="form-item-decorator"></span>
							</div>
						</li>
					</ul>
				</form>
				
			</div>
		</div>
</script>

</div>
            
          
!

↑ Insert the most common viewport meta tag

CSS

            
              @width: 480px;

@base-fontsize: 16px;
@icons-fontsize: 20px;

@main-color: #51B4E3;
@light-color: #A6D6EA;
@bkg-color: #eee;
@bottombar-bkg: #555;

@text-color: #000;
@icons-color: #fff;

@list-bottomborder-color: #ddd;
@labels-color: #6F6F6F;

@topbar-height: 50px;
@bottombar-height: 50px;

@tabs-height: @topbar-height;
@starred-caption-height: 40px;
@profile-image-height: 180px;

@titles-height: 30px;
@list-padding: 20px;
@list-item-height: 60px;
@list-item-lineheight: 20px;
@list-item-padding: 10px;

@in-width: @width - (@list-padding * 2);

@submenu-minwidth: 200px;
@submenu-bkg: #eee;
@submenu-shadow: #999;
@submenu-bordercolor: #ccc;
@submenu-item-height: 40px;
@submenu-item-padding: 0 20px;

.opacity(@v) {
	-webkit-opacity: @v;
	-moz-opacity: @v;
	-o-opacity: @v;
	opacity: @v;
}

.box-shadow(@x, @y, @h, @color) {
	-webkit-box-shadow: @x @y @h @color;
	-moz-box-shadow: @x @y @h @color;
	-o-box-shadow: @x @y @h @color;
	box-shadow: @x @y @h @color;
}

.border-box() {
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	-o-box-sizing: border-box;
	box-sizing: border-box; 
}


/*****/

.fixed {
	position: fixed;
}

.absolute {
	position: absolute;
}

ul, ol,
.zero {
	margin: 0;
	padding: 0;
	list-style-type: none;
}

.full {
	width: 100%;
}

.fixed.full,
.absolute.full {
	width: @width;
}

.top {
	top: 0;
}

.bottom {
	bottom: 0;
}

.fleft {
	float: left;
}

.fright {
	float: right;
}

.overlay {
	background: #000;
	position: absolute;
	top: 0; bottom: 0;
	left: 0; right: 0;
	.opacity(0.6);
}

/*****/

body,
html {
	height: 100%;
}

body {
	margin: 0;
	padding: 0;
	font: @base-fontsize Helvetica, sans-serif;
	color: @main-color;
	background: #000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2M0Njb+f+/ePQYYYBQUFPyvpKTEABMEC4BkYYJwAZggADGyDjN7QaaVAAAAAElFTkSuQmCC);
}

a {
	text-decoration: none;
	color: @text-color;
}

#scroller {
	width: 100%;
	.border-box;
}

.row {
	position: relative;
	margin: auto;
	width: @width;
}

.content {
	z-index: 1;
	position: fixed;
	background: @bkg-color;
	top: @topbar-height;
	bottom: @bottombar-height;
	width: @width;
}

.content.no-bottombar {
	bottom: 0;
}

.topbar {
	z-index: 2;
	height: @topbar-height;
	background: @main-color;
	.box-shadow(0, 2px, 10px, #888);
}

.bottombar {
	z-index: 2;
	height: @bottombar-height;
	background: @bottombar-bkg;
}

.title {
	font-size: @base-fontsize - 2%;
	color: @main-color;
	border-bottom: 2px solid @main-color;
	height: @titles-height;
	line-height: @titles-height;
	text-transform: uppercase;
}

/*****/

.lists {
	float: left;
	width: @in-width;
	padding: (@list-padding / 2) @list-padding (@list-padding / 2) @list-padding;
}

.lists.full {
	width: @width;
	padding: 0;
}

.list,
.list-item,
.list-item .item {
	float: left;
	width: 100%;
	position: relative;
}

.list-item .item {
	display: block;
	line-height: @list-item-lineheight;
	padding: @list-item-padding 0;
	border-top: 1px solid @list-bottomborder-color;
}


.list.single-fill .list-item .item {
	position: relative;
	padding: 0;
	height: @list-item-height;
	line-height: @list-item-height;
}

.list.single-fill .list-item.half {
	width: 50%;
	overflow: hidden;
}

.list.single-fill .list-item.half .item {
	overflow: hidden;
	height: @profile-image-height;
	border-top: none;
}

.list.single-fill .list-item.half .item.profile-image {
	left: 0;
	top: 0;
}

.list.single-fill .list-item.half .item.profile-image img {
	margin-top: -15%;
}

.list.single-fill .caption {
	height: @starred-caption-height;
	width: 100%;
}

.caption .text{
	position: absolute;
	top: 0; bottom: 0;
	left: 0; right: 0;
	line-height: @starred-caption-height;
	padding: 0 20px;
	width: 100%;
	color: #fff;
	z-index: 2;
	.border-box;
}

.list-item:first-child .item {
	color: @text-color;
	border-top: none;
}

.list-item span.fleft {
	width: @in-width - @list-item-height;
}

.list-item span.fright {
	width: @list-item-height;
	height: @list-item-height;
	overflow: hidden;
}

.list-item span.fright img {
	float: right;
}

.list-item .item .label {
	display: block;
	text-transform: uppercase;
	font-size: @base-fontsize - 2%;
	color: #666;
}


/*****/

.tabs > li > a,
.tabs > li {
	float: left;
}

.tabs > li {
	width: 50%;
	height: @tabs-height - 5px;
}

.tabs.three > li {
	width: 33.33%;
}

.tabs > li:hover,
.tabs > li.active {
	border-bottom: 5px solid @icons-color;
}

.tabs > li > a {
	width: 100%;
	height: @tabs-height - 20%;
	line-height: @tabs-height - 20%;
	margin-top: (@tabs-height - (@tabs-height - 20%)) / 2;
	text-align: center;
	border-right: 1px solid @light-color;
}

.tabs > li:last-child > a {
	border-right: none;
}

/*****/

.icon > a {
	color: @icons-color;
	font-size: @icons-fontsize;
}

.bar .icn,
.bar .icn > a {
	display: block;
	width: @bottombar-height;
	height: @bottombar-height;
	line-height: @bottombar-height;
	text-align: center;
}

.bar .icn {
	position: absolute;
	z-index: 2;
}

.bar .icn.left {
	left: 0;
}

.bar .icn.right {
	right: 0;
}

.bar .icn.right.sec {
	right: 60px;
}

.bar .icn.center {
	left: 50%;
	margin-left: -(@bottombar-height / 2);
}

.bar .icn.full {
	z-index: 1;
	float: left;
	padding-left: 60px;
	width: auto;
}

.bar .icn.full.centred {
	padding-left: 0;
	float: none;
	width: 100%;
}

.bar .icn.action,
.bar .icn.action a {
	width: auto;
	font-size: @base-fontsize;
}

.bar .icn.action a span {
	font-size: @base-fontsize + 6%;
}

.bar .icn.action a {
	padding: 0 10px;
}

.bar .icn.action a span{
	padding-right: 10px;
}

.contact-name {
	color: @icons-color;
	font-size: @base-fontsize + 2%;
	font-weight: bold;
}

/*****/

.submenu {
	display: none;
	position: absolute;
	text-align: left;
	min-width: @submenu-minwidth;
	background: @submenu-bkg;
	right: @list-padding / 2;
	.box-shadow(0, 2px, 5px, @submenu-shadow);
}

.show-submenu .submenu {
	display: block;
}

.submenu li a {
	height: @submenu-item-height;
	line-height: @submenu-item-height;
	padding: @submenu-item-padding;
	display: block;
	text-transform: capitalize;
}

.submenu li {
	border-bottom: 1px solid @submenu-bordercolor;
}

.submenu li:last-child a {
	border-bottom:none;
}

/*****/

.profile-image {
	position: relative;
	overflow: hidden;
	height: @profile-image-height;
	width: @width;
	left: -@list-padding;
	top: -(@list-padding / 2);
}

.profile-image img {
	position: absolute;
	margin-top: -40%;
	width: 100%;
}

.profile-image.open {
	height:auto;
}

.profile-image.open img {
	position: static;
	margin-top: 0;
}

/******/

.form .title {
	padding-left: 10px;
	.border-box; 
}

.form .list-item:last-child {
	margin-bottom: 20px;
}

.form .form-item {
	position: relative;
	height: auto;
	min-height: auto;
	border-top: none;
	width: 100%;
	padding: 10px;
	.border-box;
}

.form .list-item button {
	border: none;
	background: none;
	margin: 0;
	text-transform: uppercase;
	font-size: @base-fontsize - 2%;
	color: #666;
}

.form .list-item.action button {
	padding: 5px 10px 0;
	margin: 10px 0 0;
}

.form .form-item.action {
	text-align: center;
}

.form .form-item.action button {
	font-size: @base-fontsize + 10%;
}

.form .form-item input,
.form .form-item textarea {
	width: 100%;
	border: none;
	background: none;
	margin: 0;
	padding: 7px 0 3px;
}

.form .form-item .form-item-decorator {
	width: 99%;
	height: 5px;
	position: absolute;
	left: 0;
	bottom: 0;
	border-bottom: 1px solid @list-bottomborder-color;
	border-left: 1px solid @list-bottomborder-color;
	border-right: 1px solid @list-bottomborder-color;
}

.form .form-item input:focus,
.form .form-item textarea:focus {
	outline: none
}

form .form-item input:focus + .form-item-decorator,
form .form-item textarea:focus + .form-item-decorator {
	border-color: @main-color;
}

form .form-item input.ng-invalid + .form-item-decorator {
	border-color: red;
}

.form .form-item.field-left {
	width: 62%;
}

.form .form-item.field-right {
	width: 23%;
}

.form .form-item.field-right-right {
	width: 15%;
	padding: 10px 0
}

.form .form-item.field-left.img {
	width: 70%;
}

.form .form-item.field-right.img {
	width: 30%;
	padding: (@list-padding / 2) 0;
}

.form .form-item.field-right img {
	float: right;
	padding: 0 @list-padding;
}

.bar .icn.full.contact-name {
	text-align: left;
	max-width: 50%;
	overflow: hidden;
}

.bar .icn.full.contact-name.form {
	width: @width - @list-padding;
	max-width: @width - @list-padding;
	.border-box;
}

.contact-name.form .form-item {
	height: @topbar-height - (@list-padding / 2);
	padding-top: 0;
}

.contact-name.form .form-item .cancel {
	position: absolute;
	right: @list-padding / 2;
	top: 0;
}

.contact-name.form .form-item input {
	color: @icons-color;
}

.contact-name.form .form-item ::-webkit-input-placeholder {
	color: @icons-color;
}
.contact-name.form .form-item :-moz-placeholder {
	color: @icons-color;
}
.contact-name.form .form-item ::-moz-placeholder {
	color: @icons-color;
}
.contact-name.form .form-item placeholder {
	color: @icons-color;
}


/*********************/



@media only screen and (max-device-width : 480px) {
	.fixed.full,
	.absolute.full,
	.row,
	.content,
	.lists.full {
		width: 100%;
	}

	.profile-image {
		width: 108%;
	}

	.lists {
		padding: 2% 4% 2% 4%;
		width: 92%;
	}

	.lists {
		width: 92%;
	}

	.list-item span.fleft {
		width: 80%;
	}

	.list-item span.fright {
		width: 20%;
	}

	.submenu {
		right: 20%;
	}

	.profile-image {
		left: -4%;
		margin-top: -2%;
	}

	.form .form-item.field-right img {
		padding: 0 2%;
	}

	.list.single-fill .list-item.half .item.profile-image img {
		margin-top: -5%;
	}

	.list.single-fill .list-item.half .item.profile-image img {
		margin-top: -5%; 
	}

	.bar .icn.full.contact-name.form {
		width: 96%;
	}

	.contact-name.form .form-item .cancel {
		right: 2%; 
	}
}

            
          
!
? ?
? ?
Must be a valid URL.
+ add another resource
via CSS Lint

JS

            
              'use strict';

/*
Android Address Book replica with AngularJs 
===========================================

GitHub project: https://github.com/danielemoraschi/android-addressbook

Touch scrolling by iScroll: http://cubiq.org/iscroll-4
Fake contacts list by: http://www.generatedata.com/

DB reset every 2h

Best in Mobile / Chrome / Safari 

Released under the MIT License:
http://www.opensource.org/licenses/mit-license.php
*/
var AddressBook = (function() {

	var iscroll, current_route,

	_init = function($scope) {
		iscroll = null;
		current_route = '/contacts'; 
	},

	_iScroll = function() {
		iscroll && iscroll.destroy();
		iscroll = new iScroll('wrapper', { hScroll: false });
		setTimeout(function() { 
			iscroll.refresh(); 
		}, 0);
	},

	_detail_ctrl = function($scope, $location, $routeParams, Utils, Contacts) {
		var self = this;
		$scope.selected = false;
		$scope.submenu = false;
		$scope.contact = {
			starred: false,
			firstName: "",
			lastName: "",
			birthday: "",
			picture: "",
			phones: [],
			emails: [],
			addresses: [],
			websites: [],
			notes: ""
		};

		$scope._showImage = function() {
			$scope.selected = !$scope.selected;
		}
		
		$scope._submenu = function() {
			$scope.submenu = !$scope.submenu;
		}

		$scope.Back = function() {
			$location.path(current_route);
		}

		$scope.ProfileImage = function(dim) {
			return ($scope.contact && $scope.contact.picture) || "https://raw.github.com/danielemoraschi/android-addressbook/master/imgs/ic_contact_picture_"+dim+".png";
		}

		$scope.FullName = function(dim) {
			return ($scope.contact.firstName && $scope.contact.firstName.trim()) 
				? $scope.contact.firstName + ' ' + $scope.contact.lastName 
				: ($scope.contact._id ? 'No name' : 'New contact');
		}

		$scope.StarUnStar = function () {
			$scope.contact.starred = !$scope.contact.starred;
			$scope.contact.update();
		}

		$scope.AddField = function(type) {
			$scope.contact[type] || ($scope.contact[type] = []);
			$scope.contact[type].push({
				type: '',
				value: ''
			});
		}

		$scope.DiscardField = function(type, index) {
			if($scope.contact[type] && $scope.contact[type][index]) {
				$scope.contact[type].splice(index,1);
			}
		}

		$scope.SaveContact = function () {
	   if($scope.contact.firstName && $scope.contact.firstName.trim()) {
		  var arrays = {'phones': [], 'emails': [], 'addresses': []};
		  angular.forEach(arrays, function(v, k) {
						angular.forEach($scope.contact[k], function(val, key) {
						  if(val.value.trim()) {
								arrays[k].push(val);
						   }
					  });
					$scope.contact[k] = arrays[k];
				});

		if($scope.contact._id) {
			$scope.contact.update(function() {
						$location.path('/contact/view/' + $scope.contact._id.$oid);
					});
				}
				else {
					Contacts.save($scope.contact, function(contact) {
						$location.path('/contact/edit/' + contact._id.$oid);
					});
				}
			}
		}

		$scope.DeleteContact = function () {
			if($scope.contact._id.$oid) {
				var c = confirm("Delete this contact?")
				if (c==true) {
					self.original.delete(function() {
						$location.path('/contacts');
					});
				}
			}
		}

		if($routeParams.id) {
			Contacts.get({id: $routeParams.id}, function(contact) {
				self.original = contact;
				if(!self.original.views) {
					self.original.views = 0;
				}
				self.original.views++;
				$scope.contact = new Contacts(self.original);
				$scope.contact.update();
				_iScroll();
			});
		} else {
			_iScroll();
		}
	},

	_list_ctrl = function($scope, $location, $routeParams, Utils, Contacts) {
		var i, ch, self = this;

		$scope.orderProp = 'firstName';
		$scope.groups = {};
		$scope.contacts = {};
		$scope.starred = {};
		$scope.searchterm = '';

		$scope.ProfileImage = function(dim, contact) {
			return contact.picture ? contact.picture.replace("480x480", dim) : "https://raw.github.com/danielemoraschi/android-addressbook/master/imgs/ic_contact_picture_"+dim+".png";
		}

		$scope.Back = function() {
			$location.path(current_route);
		}

		switch($location.$$url) {
      
			case "/contacts/starred": 
				current_route = $location.$$url;
				$scope.starred = Contacts.query({q: '{"starred":true}'}, function() {
					$scope.contacts = Contacts.query({q: '{"views":{"$gt":0}}', l: 10}, function() {
						_iScroll();
					});
				});
				break;

			case "/contacts/search": 
				$scope.contacts = Contacts.query(function() {
					$scope.groups = [{
						label: 'All contacts',
						contacts: $scope.contacts
					}];
					_iScroll();
				});
				break;

			default:
				current_route = $location.$$url;
				$scope.contacts = Contacts.query(function() {
					Utils.groupify($scope.contacts, $scope.groups);
					_iScroll();
				});
				break;
		}
	};


	return {
		Init: _init,
		DetailCtrl: _detail_ctrl,
		ListCtrl: _list_ctrl
	}

})();



    
angular.module('mongolab', ['ngResource']).
factory('Contacts', function($resource) {
  var Contacts = $resource(
    'https://api.mongolab.com/api/1/databases/addressbook/collections/contacts/:id',
    { apiKey: 'RO27EEbdFsJfycTn_JUiAnr3qIcsgyxS' },
    { update: { method: 'PUT' } }
  );
  
  Contacts.prototype.update = function(cb) {
    return Contacts.update({id: this._id.$oid},
                           angular.extend({}, this, {_id:undefined}), cb);
  };
  
  Contacts.prototype.delete = function(cb) {
    return Contacts.remove({id: this._id.$oid}, cb);
  };
  
  return Contacts;
});

angular.module('helpers', []).
factory('Utils', function() {
  return {
    groupify : function(source, into) {
      var i, ch;
      for (i = source.length - 1; i >= 0; i--) {
        ch = source[i].firstName.charAt(0);
        into[ch] || (into[ch] = {
          label: ch,
          contacts: []
        });
        into[ch].contacts.push(source[i]);
      };
    }
  }
});


angular.module('android-addressbook', ['mongolab', 'helpers']).
config(['$routeProvider', function($routeProvider, $locationProvider) {
  $routeProvider.
  when('/contacts', {templateUrl: 'list.html', controller: AddressBook.ListCtrl}).
  when('/contacts/starred', {templateUrl: 'starred.html', controller: AddressBook.ListCtrl}).
  when('/contacts/search', {templateUrl: 'search.html', controller: AddressBook.ListCtrl}).
  when('/contact/add', {templateUrl: 'edit.html', controller: AddressBook.DetailCtrl}).
  when('/contact/view/:id', {templateUrl: 'view.html', controller: AddressBook.DetailCtrl}).
  when('/contact/edit/:id', {templateUrl: 'edit.html', controller: AddressBook.DetailCtrl}).
  otherwise({redirectTo: '/contacts'});
}]);
            
          
!
Must be a valid URL.
+ add another resource
via JS Hint
Loading ..................