AngularJS Phone Book + Bootstrap v3

2 lists of "white" and "spam", w/o backend, add record, edit, remove, mark as "spam", unmark it, unit tests, filter lists. Solved as task "Implement Address Book" with list of requirements (written in HTML section). For all "SUCCESS" tests remove all records without phone numbers or any text in "phone" field.

by denis_miroshnikov_ts

HTML

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.min.js"></script>
<div class="bg-info">
	<div style=float:right;padding-right:4px><i>Created: 2014-03-10 by <a href=http://spmbt.github.io/spmbt/>spmbt</a></i></div>
	<strong>Task</strong>: <u>Implement Address Book</u>.</div>
<blockquote><small><div class=task>

	<div class="text-info"><strong>Technology stack</strong>: <span class="text-danger"><abbr title="Google MVC Library" class="initialism">AngularJS</abbr>, code should be
		covered
		by unit tests</span> as much as possible along with <abbr title="End-to-End Test" class="initialism"><a href=http://www.techopedia.com/definition/7035/end-to-end-test>e2e</a> tests</abbr>.</div>
	<div class="text-info"><strong>Nice to have feature</strong>: Dynamic support of both English
		and Russian (or any other) languages in UI with
		ability	toswitch languages.</div>
	<div class="text-info"><strong>Non functional requirements</strong>: Client Side only, persistence
		can be done into variable or to Html Local Storage.</div>
	<div class="text-danger"><strong>HTML layout</strong>: Use <a href=http://getbootstrap.com/css/>Bootstrap v3</a></div>

	<div class="text-info"><strong>Application Flow</strong>:
	"List" -  of existed contacts (contact has at least 4 fields:
		<ul class="text-warning"><li><em><u>name</u></em>: mandatory,</li>
			<li><em><u>surname</u></em>: optional,</li>
			<li><em><u>phone number</u></em>: mandatory,</li>
			<li><em><u>group</u></em>: optional)</li></ul>
		supports search, and group by group field. Link to "Insert New Contact".
		Link to "Edit Existed Contact".
	</div>
	<div class="text-info">"<span class="text-warning">Insert New Contact</span>" - adding to the list of existed.
		Preferable implementation is sexy popup.</div>
	<div class="text-info">"<span class="text-warning">Edit Existed Contact</span>" - edit one from the...

CSS

body{margin: 12px;}
	blockquote small:before{content:""}
	.bg-danger{background-color: #F2DEDE;}
	.task{margin:6px 12px 6px 3em}
	.listItem.spam{text-decoration: line-through; color: gray;}
	.tasks,	.stats{margin: 10px;}
	.w8,.w12,.w24,.w96{display: inline-block;}
	.w8{width: 80px;}
	.w12{width: 120px;}
	.w24{width: 240px; margin: 3px;}
	.w96{width: 97%; margin: 3px;}
	.toggleItem,
	.addressItem{padding: 6px 9px; cursor: pointer;}
	.addressItem:hover{background-color: #eee;}
	.addItem{display: none; position: relative;}
		.addItemInner{
			position: absolute;
			bottom: 16px;
			left: 150px;
			width: 390px;
			margin: 4px auto 0;
			padding: 2px 0 6px 11px;
			border: 1px solid #aaa;
				border-radius: 4px;
				box-shadow: 2px 2px 4px 1px #aaa;
			background-color: #fceef5;
		}
	.contactListApp select option{background-color: #fceef5;}
	.contactListApp select option[value=work]{background-color: #B5C7F5;}
	.contactListApp select option[value=home]{background-color: #89DFD0;}
	.contactListApp select option[value=friends]{background-color: #95e88A;}
	.contactListApp select option[value=shops]{background-color: #dfe480;}
	.contactListApp select option[value=spam]{background-color: #f4bb88;}
	.contactListApp .btn-link{color: #787C44}
	.tasks li[data-group="spam"]{display: none}
	.bg-info{padding-left: 4px;}
	.btn-del{background-color: #f5e5e5;}
	.btn-del:hover{background-color: #d5a5a0;}
	.filter{padding-left:6em;}
	.contactList{line-height: 2.7em;}
	.testArea{
		margin: 2px 0;
		padding: 12px 4px;
		border: 1px solid #e2e2e2;
			border-radius: 4px;
		background-color: #f5f5f5;}
	dl.tests { width: 660px; margin-bottom: 4px;}
	.tests dt{display: inline-block; width: 330px; padding-left: 6px; background-color: #ddd;}
		.tests.bg-danger dt{display: inline-block; width: 330px; background-color: #F2DEDE;}
	.tests dd{display: inline-block; width: 330px;}

JavaScript

var ddd2 = angular.module("ddd", []);
function addressController($scope){
	$scope.groups =',work,home,friends,shops,spam'.split(',');
	$scope.version = angular.version.full;

	$scope.list =[{
		fname: "Peter"
		,surname: "Gray"
		,group:'work'
	},{
		fname: "Michael"
		,surname: "Edison"
		,phone:'+1(945)754-2391'
		,group:'home'
	},{
		fname: "Gilbert"
		,surname: "Brown"
		,group:'shops'
	},{
		fname: "Manfred"
		,surname: "Richdown"
		,group:'friends'
	},{
		fname: 'Elsa'
		,group:'spam'
	},{
		fname: 'Nikita'
		,phone:'235-7856'
		,group:'spam'}];

	$scope.newContact = function(){ //add new record in database
		var addItem = document.querySelector('.addItem');
		if(!addItem.querySelector('.fname').value || !addItem.querySelector('.phone').value){
			if(!$scope.fname) addItem.querySelector('.fname').classList.add('bg-danger');
			if(!$scope.phone) addItem.querySelector('.phone').classList.add('bg-danger');
			return false;
		}
		addItem.querySelector('.fname').classList.remove('bg-danger');
		addItem.querySelector('.phone').classList.remove('bg-danger');
		$scope.list.push({
			fname: addItem.querySelector('.fname').value,
			surname: addItem.querySelector('.surname').value,
			phone: addItem.querySelector('.phone').value,
			group: addItem.querySelector('.group').options[addItem.querySelector('.group').selectedIndex].value
		});
		addItem.querySelector('.fname').value ='';
		addItem.querySelector('.surname').value ='';
		addItem.querySelector('.phone').value ='';
		addItem.querySelector('.group').selectedIndex =0;
		addItem.style.display ='none';

	};
	$scope.removeContact = function(item){
		for(var i in $scope.list) if($scope.list[i] === item)
			$scope.list.splice(i, 1);
	};
	$scope.notSpam = function(listItem){
		listItem.group = listItem.groupSaved;
		delete listItem.groupSaved;
	};
	$scope.toggleAddForm = function(){
		var addItem = document.querySelector('.addItem');
		addItem.style.display = addItem.style.display =='block'...