var app = angular.module('yeomanContactsApp', ['ngResource']);
// Set up our mappings between URLs, templates, and controllers
app.config(function($routeProvider, $locationProvider) {
$routeProvider.
when('/', {
controller: 'MainCtrl',
templateUrl: 'main.html'
}).
when('/view/:id', {
controller: 'DetailCtrl',
templateUrl: 'detail.html'
}).
when('/contacts.json', {
controller: 'ContactsCtrl',
templateUrl: 'contacts.html',
resolve: {
contacts: function(ContactsLoader){
return ContactsLoader();
}
}
}).
otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(false);
});
//Contact Resource
app.factory('Contact', ['$resource', function($resource){
return $resource('/contacts/:id', {id: '@id'});
}]);
//Contacts Loader
app.factory('ContactsLoader', ['Contact', '$q', '$http', function(Contact, $q, $http){
return function(){
var delay = $q.defer();
var contacts = [];
$http.get('/contacts.json').success(function(results){
console.log(results);
contacts = results.results;
delay.resolve(contacts);
});
/*
Contact.query(function(contacts){
delay.resolve(contacts);
}, function(){
delay.reject('Unable to fetch contacts.');
});
*/
return delay.promise;
}
}]);
//Contact Loader
app.factory('ContactLoader',['Contact', '$route', '$q', function(Contact, $route, $q){
return function(){
var delay = $q.defer();
var id = $route.current.params.id;
Contact.get({id: id}, function(contact){
delay.resolve(contact);
}, function(){
delay.reject('Unable to fetch contact.' + id);
});
return delay.promise;
}
}]);
//Contact Directive - Custom html for our contact.
app.directive('contact', function () {
return {
templateUrl: 'contact_directive.html',
replace: true,
restrict: 'E',
link: function postLink(scope, element, attrs) {
}
};
});
//Contacts Service - Connects to Parse.com and fetches the data.
app.factory('ContactsService', ['$q', '$http', function($q, $http){
return function(successCb, errorCb){
Parse.initialize("mNf6N9zKOa0iqoyhXjlnNWQiJATFWkcb5ukvuOkX", "tjgFaPiYlrTPbNkI0qu62RSl4tE8VH0y0W7yF687");
var ContactModel = Parse.Object.extend('Contact');
var ContactsCollection = Parse.Collection.extend(ContactModel);
var delay = $q.defer();
var data = null;
var query = new Parse.Query('Contact');
query.find({
success: function(results) {
data = results.map(function(obj){
return {
id: obj.get('id'),
name: obj.get('name'),
phone: obj.get('phone'),
website: obj.get('website'),
email: obj.get('email'),
avatar: 'http://www.gravatar.com/avatar/' + calcMD5(obj.get('email'))
}
});
if(successCb){
successCb(data);
} else {
delay.resolve(data);
}
},
error: function(error) {
if(errorCb){
errorCb(error);
}
delay.reject(error);
}
});
return delay.promise;
}
}]);
//Contact Tel filter -
app.filter('tel', function () {
return function (input) {
var pattern = /\b[1]?[(-|\(|\.|\s)]?([\d]{3})(-|\)|\.|\s)[\s]?([\d]{3})(-|\.|\s)([\d]{4})\b/g,
replace = '($1)$3-$5';
return input.replace(pattern, replace);
};
});
//Main Controller
app.controller('MainCtrl', function($scope, $rootScope, ContactsService) {
$scope.query = '';
$scope.limit = 10;
$scope.order = 'name';
$scope.query = '';
$scope.App = {
icon: 'book',
name: 'AngularJS Contacts',
loading: true,
model: null,
nav: [
{ title: 'View Contacts', href:'/contacts' },
{ title: 'Add Contact', href:'/add' }
],
filter:{
limit: 10,
order: 'name',
reverse: false,
query: null
},
init: function(){
this.getContacts();
return this;
},
getContacts: function(){
new ContactsService(function(data){
$scope.$apply(function(){
$scope.App.loading = false;
$scope.App.model = data;
})
});
}
};
window.App = $scope.App.init();
});
//Home Controller
app.controller('HomeCtrl', function ($scope) {
$scope.awesomeThings = ['HTML5 Boilerplate','AngularJS','Karma'];
});
//Contacts Controller
app.controller('ContactsCtrl', function($scope, $http) {
$scope.name = 'Contacts';
$scope.contacts = [];
$http.get('/contacts.json').then(function(results){
console.log(results);
$scope.contacts = results.results;
});
console.log($scope.contacts);
});
//Detail Controller
app.controller('DetailCtrl', function($scope, $rootScope, $routeParams) {
$scope.name = 'Detail';
$scope.contact = $rootScope.App.model[$routeParams.id];
});
describe('App:', function() {
var $scope = null, ctrl = null
//you need to indicate your module in a test
beforeEach(module('yeomanContactsApp'));
//Create a new scope for each
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
}));
//Add custom matchers to jasmine
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected){
return angular.equals(this.actual, expected);
}
})
});
//Tests on the Main controller
it('should say AngularJS Contacts', function() {
expect($scope.App.name).toEqual('AngularJS Contacts');
});
//Home Controller Tests
describe('Controller: MainCtrl', function () {
var MainCtrl, scope, mockBackend;
// Initialize the controller and a mock scope
beforeEach(inject(function ( _$httpBackend_, $controller, $rootScope) {
//mockBackend = _$httpBackend_;
// because this call will get triggered when the controller is created
//mockBackend.expectGET('http://server/names?filter=none').
//respond(['Brad', 'Shyam']);
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it("should try to call the service, but we intercept it", inject(function(ContactsService) {
//spyOn($scope.App, 'getContacts').andReturn([1, 2, 3]);
//expect($scope.App.model).toBe(3);
}));
it('should fetch names from server on load', function() {
// Initially, the request has not returned a response
//expect(scope.App.model).toBeNull();
// Tell the fake backend to return responses to all current requests that are in flight.
//mockBackend.flush();
// Now App.model should be set on the scope
//expect(scope.App.model).toBe(3);
});
});
//Directive Tests
describe('Directive: contact', function () {
var element;
it('should make element a contact', inject(function ($rootScope, $compile) {
element = angular.element('<contact></contact>');
element = $compile(element)($rootScope);
expect(true).toBe(true);
}));
});
//Filter Tests
describe('Filter: tel', function () {
// load the filter's module
//beforeEach(module('yeomanContactsApp'));
// initialize a new instance of the filter before each test
var tel;
beforeEach(inject(function ($filter) {
tel = $filter('tel');
}));
it('should return the string formated as (555)555-5555', function () {
var text = '555-555-5555';
expect(tel(text)).toBe('(555)555-5555');
});
});
//Service Tests
describe('Service: ContactsService', function () {
// load the service's module
//beforeEach(module('yeomanContactsApp'));
// instantiate service
var ContactsService;
beforeEach(inject(function (_ContactsService_) {
ContactsService = _ContactsService_;
}));
it('should do something', function () {
expect(!!ContactsService).toBe(true);
});
});
describe("ContactsController", function () {
var scope, httpBackend, http, controller;
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
controller = $controller;
http = $http;
httpBackend.when("GET", "/contacts.json").respond([{id: 1}, {id: 2}, {id: 3}]);
$controller('ContactsCtrl', {
$scope: scope,
$http: $http
});
}));
it('should make a http GET request for contacts', function () {
httpBackend.expectGET('/contacts.json');
controller('ContactsCtrl', {
$scope: scope,
$http: http
});
httpBackend.flush();
});
});
});
<!DOCTYPE html>
<html ng-app="yeomanContactsApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Contacts</title>
<link data-require="jasmine" data-semver="1.3.1" rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" />
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.no-icons.min.css" rel="stylesheet" />
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet" />
<script src="http://www.parsecdn.com/js/parse-1.2.8.min.js"></script>
<script data-require="json2" data-semver="0.0.2012100-8" src="//cdnjs.cloudflare.com/ajax/libs/json2/20121008/json2.js"></script>
<script data-require="jasmine" data-semver="1.3.1" src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script data-require="jasmine" data-semver="1.3.1" src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script data-require="angular.js" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<script data-require="angular-mocks" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-mocks.js"></script>
<script data-require="jquery@*" data-semver="2.0.1" src="http://code.jquery.com/jquery-2.0.1.min.js"></script>
<script data-require="bootstrap@2.3.2" data-semver="2.3.2" src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
<script data-require="angular-resource@1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-resource.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="md5.js"></script>
<script src="app.js"></script>
<script src="appSpec.js"></script>
<script src="jasmineBootstrap.js"></script>
</head>
<body ng-controller="MainCtrl">
<!-- #app - This is the application view html, this is the static view layout. -->
<div id="app">
<div class="navbar navbar-invers navbar-stati-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#">
<i class="icon-{{App.icon}} icon-1x"></i> {{App.name}}</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li ng-repeat="item in App.nav">
<a ng-href="#{{item.href}}">{{item.title}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="container" class="container-fluid">
<article class="row-fluid">
<section id="view" class="span12" ng-view=""></section>
</article>
<footer class="row-fluid">
<div class="span12">
<p>Copywrite © 2013 by Jonnie Spratley</p>
</div>
</footer>
</div>
</div>
<!-- main.html - This is the contact main view html for the view. -->
<script id="main.html" type="text/ng-template">
<section id="contacts-list">
<p ng-show="App.loading">
<i class="icon-spinner icon-spin icon-large"></i> Please wait while loading content..
</p>
<ul class="contacts unstyled">
<li class="">
<form class="form-search">
<input type="text" ng-model="$.query" class="span12 search-query" placeholder="Filter by name"/>
</form>
</li>
<li ng-repeat="item in App.model | filter:query | limitTo:limit | orderBy:order:reverse">
<contact ng-model="item"></contact>
</li>
</ul>
</section>
</script>
<!-- detail.html - This is the contact details html for the view -->
<script id="detail.html" type="text/ng-template">
<h2>Details</h2>
</script>
<!-- contacts.html - This is contacts view -->
<script id="contacts.html" type="text/ng-template">
<pre>{{contacts | json}}</pre>
</script>
<!-- contact_direct.html - This is the contact directive custom html for the view. -->
<script id="contact_directive.html" type="text/ng-template">
<div id="contact-{{$index}}" class="contact well well-small clearfix">
<a class="pull-left thumb" ng-href="/view/{{$index}}">
<img class="media-object" ng-src="{{item.avatar}}" src="http://placehold.it/100x100&text=Image" alt="Contact {{item.name}} Image"/>
</a>
<div class="media-body">
<h4 class="media-heading">
{{item.name}}
</h4>
<p class="meta">
<ul class="unstyled">
<li class="phone"><a ng-href="tel:{{item.phone}}"><i class="icon-phone"></i> {{item.phone | tel}} </a></li>
<li class="email"><a ng-href="mailto:{{item.email}}"><i class="icon-envelope"></i> {{item.email}} </a></li>
</ul>
</p>
</div>
</div>
</script>
<div id="HTMLReporter" class="jasmine_reporter"></div>
</body>
</html>
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
/**
Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
*/
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
/**
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
*/
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/**
Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler
### Test Results
Scroll down to see the results of all of these specs.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
//document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
body {
background-color: #fff;
padding: 0;
margin: 8px;
}
footer{
text-align:center;
font-size: .8em;
color:#888;
}
.jasmine_reporter {
background-color: #eee;
margin: 0;
}
.contact:hover{
cursor: pointer;
border: 1px solid #888;
}
.contact a{
color: #777;
}
.contact a:hover{
text-decoration: none;
}
.contact .email, .contact .phone{
font-size: .9em;
white-space: nowrap;
color: #777;
}
.contact .thumb{
margin-right: 10px;
}
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Copyright (C) Paul Johnston 1999 - 2000.
* Updated by Greg Holt 2000 - 2001.
* See http://pajhome.org.uk/site/legal.html for details.
*/
/*
* Convert a 32-bit number to a hex string with ls-byte first
*/
var hex_chr = "0123456789abcdef";
function rhex(num)
{
str = "";
for(j = 0; j <= 3; j++)
str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) +
hex_chr.charAt((num >> (j * 8)) & 0x0F);
return str;
}
/*
* Convert a string to a sequence of 16-word blocks, stored as an array.
* Append padding bits and the length, as described in the MD5 standard.
*/
function str2blks_MD5(str)
{
nblk = ((str.length + 8) >> 6) + 1;
blks = new Array(nblk * 16);
for(i = 0; i < nblk * 16; i++) blks[i] = 0;
for(i = 0; i < str.length; i++)
blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
blks[i >> 2] |= 0x80 << ((i % 4) * 8);
blks[nblk * 16 - 2] = str.length * 8;
return blks;
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left
*/
function rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* These functions implement the basic operation for each round of the
* algorithm.
*/
function cmn(q, a, b, x, s, t)
{
return add(rol(add(add(a, q), add(x, t)), s), b);
}
function ff(a, b, c, d, x, s, t)
{
return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t)
{
return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t)
{
return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t)
{
return cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Take a string and return the hex representation of its MD5.
*/
function calcMD5(str)
{
x = str2blks_MD5(str);
a = 1732584193;
b = -271733879;
c = -1732584194;
d = 271733878;
for(i = 0; i < x.length; i += 16)
{
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = ff(c, d, a, b, x[i+10], 17, -42063);
b = ff(b, c, d, a, x[i+11], 22, -1990404162);
a = ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = ff(d, a, b, c, x[i+13], 12, -40341101);
c = ff(c, d, a, b, x[i+14], 17, -1502002290);
b = ff(b, c, d, a, x[i+15], 22, 1236535329);
a = gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = gg(c, d, a, b, x[i+11], 14, 643717713);
b = gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = gg(d, a, b, c, x[i+10], 9 , 38016083);
c = gg(c, d, a, b, x[i+15], 14, -660478335);
b = gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = gg(b, c, d, a, x[i+12], 20, -1926607734);
a = hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = hh(c, d, a, b, x[i+11], 16, 1839030562);
b = hh(b, c, d, a, x[i+14], 23, -35309556);
a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = hh(b, c, d, a, x[i+10], 23, -1094730640);
a = hh(a, b, c, d, x[i+13], 4 , 681279174);
d = hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = hh(d, a, b, c, x[i+12], 11, -421815835);
c = hh(c, d, a, b, x[i+15], 16, 530742520);
b = hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = ii(c, d, a, b, x[i+14], 15, -1416354905);
b = ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = ii(c, d, a, b, x[i+10], 15, -1051523);
b = ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = ii(d, a, b, c, x[i+15], 10, -30611744);
c = ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = ii(b, c, d, a, x[i+13], 21, 1309151649);
a = ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = ii(d, a, b, c, x[i+11], 10, -1120210379);
c = ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = add(a, olda);
b = add(b, oldb);
c = add(c, oldc);
d = add(d, oldd);
}
return rhex(a) + rhex(b) + rhex(c) + rhex(d);
}
{"results":[{"id":"57","name":"Emi Winters","email":"Nunc@vel.com","phone":"483-246-0559","address":"Ap #580-3097 Auctor Ave","city":"Machynlleth","state":"NE","createdAt":"2013-06-23T02:23:46.537Z","updatedAt":"2013-06-23T02:23:46.537Z","objectId":"ukfqvqw8NM"},{"address":"7824 Spring Valley","city":"Citrus Heights","email":"jonniespratley@gmail.com","name":"Jonnie Spratley","phone":"916-241-3613","state":"CA","website":"http://jonniespratley.me","createdAt":"2013-06-25T02:28:22.220Z","updatedAt":"2013-06-30T00:22:27.533Z","objectId":"FP0NiW098X"},{"address":"735-715 Luctus Rd.","city":"Newquay","email":"Maecenas@Nullainterdum.com","id":"60","name":"Kenneth Stokes","phone":"530-652-8089","state":"NY","website":"http://somesite.com","createdAt":"2013-06-23T02:23:46.576Z","updatedAt":"2013-06-30T00:22:37.555Z","objectId":"y1w1tLSGGR"}]}