<a href="https://egghead.io/series/angularjs-app-from-scratch-getting-started">![Build an AngularJS app from scratch - getting started](https://d2eip9sf3oo6c2.cloudfront.net/series/covers/000/000/006/full/egghead-angular-build-an-app-tutorial-lesson.png?1406647849)</a>
AngularJS is an incredibly powerful framework but sometimes it can be confusing and frustrating trying to figure out how all of these amazing features actually fit together. In this series, you will learn how to build a non-trivial AngularJS application from the ground up through a series of small, digestible lessons.
As we build out our sample application, Eggly, you will quickly start to identify useful techniques that you can apply to your own projects. The videos series is broken out into three series so that you can start at whatever level is most appropriate for you.
### Getting Started
In this first series, we are going to focus on the absolute essentials for getting an AngularJS application up and running. We will start with a static HTML page and learn how to bootstrap an AngularJS application and add in functionality using AngularJS views and controllers. When we complete this section, you will have a functioning Eggly application where you can create, update and delete bookmarks and filter them based on the selected bookmark category.
[Build an AngularJS App From Scratch: Getting Started](https://egghead.io/series/angularjs-app-from-scratch-getting-started) on egghead.io
angular.module('bookmarks', [
'categories.bookmarks.edit',
'categories.bookmarks.create',
'eggly.models.categories',
'eggly.models.bookmarks'
])
.config(function ($stateProvider) {
$stateProvider
.state('eggly.categories.bookmarks', {
url: 'categories/:category',
views: {
'bookmarks@': {
controller: 'BookmarksCtrl',
templateUrl: 'app/categories/bookmarks/bookmarks.tmpl.html'
}
}
})
;
})
.controller('BookmarksCtrl', function BookmarksCtrl($scope, $stateParams, bookmarks, categories) {
categories.setCurrentCategory();
if ($stateParams.category) {
categories.getCategoryByName($stateParams.category).then(function (category) {
categories.setCurrentCategory(category);
})
}
bookmarks.getBookmarks()
.then(function (result) {
$scope.bookmarks = result;
});
$scope.getCurrentCategory = categories.getCurrentCategory;
$scope.getCurrentCategoryName = categories.getCurrentCategoryName;
$scope.isSelectedBookmark = function (bookmarkId) {
return $stateParams.bookmarkId == bookmarkId;
};
$scope.deleteBookmark = bookmarks.deleteBookmark;
})
;
<div ui-sref-active="editing" bookmark="bookmark" ng-class="{active: isSelectedBookmark(bookmark.id)}" ng-repeat="bookmark in bookmarks | filter:{category:getCurrentCategoryName()}">
<button type="button" class="close" ng-click="deleteBookmark(bookmark)">×</button>
<button type="button" class="btn btn-link" ui-sref="eggly.categories.bookmarks.edit({category:bookmark.category, bookmarkId:bookmark.id})"><span class="glyphicon glyphicon-pencil"></span>
</button>
<a href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>
</div>
<hr/>
<ui-view>
<div ng-if="getCurrentCategory()">
<button type="button" class="btn btn-link"
ui-sref="eggly.categories.bookmarks.create({category:getCurrentCategoryName()})"><span
class="glyphicon glyphicon-plus"></span>
Create Bookmark
</button>
</div>
</ui-view>
angular.module('categories.bookmarks.create', [
'eggly.models.bookmarks'
])
.config(function ($stateProvider) {
$stateProvider
.state('eggly.categories.bookmarks.create', {
url: '/bookmarks/create',
views: {
'@eggly.categories.bookmarks': {
templateUrl: 'app/categories/bookmarks/create/create.bookmark.tmpl.html',
controller: 'CreateBookMarkCtrl',
}
}
})
;
})
.controller('CreateBookMarkCtrl', function ($scope, $stateParams, bookmarks, $state) {
$scope.isCreating = false;
function toggleCreating() {
$scope.isCreating = !$scope.isCreating;
}
function returnToBookmarks() {
$state.go('eggly.categories.bookmarks', {
category: $stateParams.category
})
}
function cancelCreating() {
$scope.isCreating = false;
returnToBookmarks();
}
function createBookmark() {
bookmarks.createBookmark($scope.newBookmark);
returnToBookmarks();
}
function resetForm() {
$scope.newBookmark = {
title: '',
url: '',
category: $stateParams.category
};
}
$scope.toggleCreating = toggleCreating;
$scope.cancelCreating = cancelCreating;
$scope.createBookmark = createBookmark;
resetForm();
toggleCreating();
})
;
<div class="createBookmark">
<form class="create-form" ng-show="isCreating" role="form" ng-submit="createBookmark()" novalidate>
<div class="form-group">
<label for="newBookmarkTitle">Bookmark Title</label>
<input type="text" class="form-control" id="newBookmarkTitle" ng-model="newBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label for="newBookmarkURL">Bookmark URL</label>
<input type="text" class="form-control" id="newBookmarkURL" ng-model="newBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Create</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelCreating()">Cancel</button>
</form>
</div>
angular.module('categories.bookmarks.edit', [
'eggly.models.bookmarks'
])
.config(function ($stateProvider) {
$stateProvider
.state('eggly.categories.bookmarks.edit', {
url: '/bookmarks/:bookmarkId/edit',
views: {
'@eggly.categories.bookmarks': {
templateUrl: 'app/categories/bookmarks/edit/edit.bookmark.tmpl.html',
controller: 'EditBookmarkCtrl'
}
}
})
;
})
.controller('EditBookmarkCtrl', function ($scope, bookmarks, $stateParams, $state) {
$scope.isEditing = false;
function returnToBookmarks() {
$state.go('eggly.categories.bookmarks', {
category: $stateParams.category
})
}
bookmarks.getBookmarkById($stateParams.bookmarkId).then(function (bookmark) {
if (bookmark) {
$scope.isEditing = true;
$scope.bookmark = bookmark;
$scope.editedBookmark = angular.copy($scope.bookmark);
} else {
returnToBookmarks();
}
});
function toggleEditing() {
$scope.isEditing = !$scope.isEditing;
}
function updateBookmark() {
$scope.bookmark = angular.copy($scope.editedBookmark);
bookmarks.updateBookmark($scope.editedBookmark);
returnToBookmarks();
}
function cancelEditing() {
$scope.isEditing = false;
returnToBookmarks();
}
$scope.toggleEditing = toggleEditing;
$scope.cancelEditing = cancelEditing;
$scope.updateBookmark = updateBookmark;
})
;
<h4>Editing {{bookmark.title}}</h4>
<form class="edit-form" ng-show="isEditing" role="form" ng-submit="updateBookmark()" novalidate>
<div class="form-group">
<label>Bookmark Title</label>
<input type="text" class="form-control" ng-model="editedBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label>Bookmark URL</label>
<input type="text" class="form-control" ng-model="editedBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Save</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelEditing()">Cancel</button>
</form>
angular.module('categories', [
'eggly.models.categories'
])
.config(function ($stateProvider) {
$stateProvider
.state('eggly.categories', {
url: '/',
views: {
'categories@': {
controller: 'CategoriesCtrl',
templateUrl: 'app/categories/categories.tmpl.html'
},
'bookmarks@': {
controller: 'BookmarksCtrl',
templateUrl: 'app/categories/bookmarks/bookmarks.tmpl.html'
}
}
});
})
.controller('CategoriesCtrl', function CategoriesCtrl($scope, categories) {
$scope.getCurrentCategoryName = categories.getCurrentCategoryName;
categories.getCategories()
.then(function (result) {
$scope.categories = result;
});
$scope.isCurrentCategory = function (category) {
return category.name === $scope.getCurrentCategoryName();
}
})
;
<a href="#/"><img class="logo" src="assets/img/eggly-logo.png"></a>
<ul class="nav nav-sidebar">
<li ng-repeat="category in categories" ng-class="{'active':isCurrentCategory(category)}">
<a ui-sref="eggly.categories.bookmarks({category: category.name})">
{{category.name}}
</a></li>
</ul>
angular.module('eggly.models.bookmarks', [
])
.service('bookmarks', function BookmarksService($http, $q) {
var URLS = {
FETCH: 'data/bookmarks.json'
},
bookmarks,
bookmarksModel = this;
function extract(result) {
return result.data;
}
function cacheBookmarks(result) {
bookmarks = extract(result);
return bookmarks;
}
bookmarksModel.getBookmarks = function () {
return (bookmarks) ? $q.when(bookmarks) : $http.get(URLS.FETCH).then(cacheBookmarks);
};
function findBookmark(bookmarkId) {
return _.find(bookmarks, function (bookmark) {
return bookmark.id === parseInt(bookmarkId, 10);
})
}
bookmarksModel.getBookmarkById = function (bookmarkId) {
var deferred = $q.defer();
if (bookmarks) {
deferred.resolve(findBookmark(bookmarkId))
} else {
bookmarksModel.getBookmarks().then(function () {
deferred.resolve(findBookmark(bookmarkId))
})
}
return deferred.promise;
};
bookmarksModel.createBookmark = function (bookmark) {
bookmark.id = bookmarks.length;
bookmarks.push(bookmark);
};
bookmarksModel.updateBookmark = function (bookmark) {
var index = _.findIndex(bookmarks, function (b) {
return b.id == bookmark.id
});
bookmarks[index] = bookmark;
};
bookmarksModel.deleteBookmark = function (bookmark) {
_.remove(bookmarks, function (b) {
return b.id == bookmark.id;
});
};
bookmarksModel.getBookmarksForCategory = function (category) {
_.filter(bookmarks, function (b) {
return b.category == category;
});
};
})
;
angular.module('eggly.models.categories', [
])
.service('categories', function CategoriesService($http, $q) {
var URLS = {
FETCH: 'data/categories.json'
},
categories,
currentCategory,
categoriesModel = this;
function extract(result) {
return result.data;
}
function cacheCategories(result) {
categories = extract(result);
return categories;
}
categoriesModel.getCategories = function () {
return (categories) ? $q.when(categories) : $http.get(URLS.FETCH).then(cacheCategories);
};
categoriesModel.getCurrentCategory = function () {
return currentCategory;
};
categoriesModel.getCurrentCategoryName = function () {
return currentCategory ? currentCategory.name : '';
};
categoriesModel.setCurrentCategory = function (category) {
currentCategory = category;
return currentCategory;
};
categoriesModel.createCategory = function (category) {
category.id = categories.length;
categories.push(category);
};
categoriesModel.deleteCategory = function (category) {
_.remove(categories, function (c) {
return c.id == category.id;
});
};
categoriesModel.getCategoryByName = function (categoryName) {
var deferred = $q.defer();
function findCategory() {
return _.find(categories, function (c) {
return c.name == categoryName;
})
}
if (categories) {
deferred.resolve(findCategory());
} else {
categoriesModel.getCategories().then(function () {
deferred.resolve(findCategory());
})
}
return deferred.promise;
};
})
;
angular.module('Eggly', [
'ngAnimate',
'ui.router',
'categories',
'bookmarks'
])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('eggly', {
url: '',
abstract: true
})
;
$urlRouterProvider.otherwise('/');
})
;
angular.module('Eggly', [
])
.controller('MainCtrl', function ($scope) {
$scope.categories = [
{"id": 0, "name": "Development"},
{"id": 1, "name": "Design"},
{"id": 2, "name": "Exercise"},
{"id": 3, "name": "Humor"}
];
$scope.bookmarks = [
{"id": 0, "title": "AngularJS", "url": "http://angularjs.org", "category": "Development" },
{"id": 1, "title": "Egghead.io", "url": "http://angularjs.org", "category": "Development" },
{"id": 2, "title": "A List Apart", "url": "http://alistapart.com/", "category": "Design" },
{"id": 3, "title": "One Page Love", "url": "http://onepagelove.com/", "category": "Design" },
{"id": 4, "title": "MobilityWOD", "url": "http://www.mobilitywod.com/", "category": "Exercise" },
{"id": 5, "title": "Robb Wolf", "url": "http://robbwolf.com/", "category": "Exercise" },
{"id": 6, "title": "Senor Gif", "url": "http://memebase.cheezburger.com/senorgif", "category": "Humor" },
{"id": 7, "title": "Wimp", "url": "http://wimp.com", "category": "Humor" },
{"id": 8, "title": "Dump", "url": "http://dump.com", "category": "Humor" }
];
$scope.isCreating = false;
$scope.isEditing = false;
$scope.currentCategory = null;
$scope.editedBookmark = null;
function isCurrentCategory(category) {
return $scope.currentCategory !== null && category.name === $scope.currentCategory.name;
}
function setCurrentCategory(category) {
$scope.currentCategory = category;
cancelCreating();
cancelEditing();
}
$scope.isCurrentCategory = isCurrentCategory;
$scope.setCurrentCategory = setCurrentCategory;
function setEditedBookmark(bookmark) {
$scope.editedBookmark = angular.copy(bookmark);
}
function isSelectedBookmark(bookmarkId) {
return $scope.editedBookmark !== null && $scope.editedBookmark.id === bookmarkId;
}
$scope.setEditedBookmark = setEditedBookmark;
$scope.isSelectedBookmark = isSelectedBookmark;
function resetCreateForm() {
$scope.newBookmark = {
title: '',
url: '',
category: $scope.currentCategory.name
};
}
//-------------------------------------------------------------------------------------------------
// CRUD
//-------------------------------------------------------------------------------------------------
function createBookmark(bookmark) {
bookmark.id = $scope.bookmarks.length;
$scope.bookmarks.push(bookmark);
resetCreateForm();
}
function updateBookmark(bookmark) {
var index = _.findIndex($scope.bookmarks, function (b) {
return b.id == bookmark.id
});
$scope.bookmarks[index] = bookmark;
$scope.editedBookmark = null;
$scope.isEditing = false;
}
function deleteBookmark(bookmark) {
_.remove($scope.bookmarks, function (b) {
return b.id == bookmark.id;
});
}
$scope.createBookmark = createBookmark;
$scope.updateBookmark = updateBookmark;
$scope.deleteBookmark = deleteBookmark;
//-------------------------------------------------------------------------------------------------
// CREATING AND EDITING STATES
//-------------------------------------------------------------------------------------------------
function shouldShowCreating() {
return $scope.currentCategory && !$scope.isEditing;
}
function startCreating() {
$scope.isCreating = true;
$scope.isEditing = false;
resetCreateForm();
}
function cancelCreating() {
$scope.isCreating = false;
}
$scope.shouldShowCreating = shouldShowCreating;
$scope.startCreating = startCreating;
$scope.cancelCreating = cancelCreating;
function shouldShowEditing() {
return $scope.isEditing && !$scope.isCreating;
}
function startEditing() {
$scope.isCreating = false;
$scope.isEditing = true;
}
function cancelEditing() {
$scope.isEditing = false;
$scope.editedBookmark = null;
}
$scope.startEditing = startEditing;
$scope.cancelEditing = cancelEditing;
$scope.shouldShowEditing = shouldShowEditing;
})
;
angular.module('Eggly', [
])
.controller('MainCtrl', function ($scope) {
$scope.categories = [
{"id": 0, "name": "Development"},
{"id": 1, "name": "Design"},
{"id": 2, "name": "Exercise"},
{"id": 3, "name": "Humor"}
];
$scope.bookmarks = [
{"id": 0, "title": "AngularJS", "url": "http://angularjs.org", "category": "Development" },
{"id": 1, "title": "Egghead.io", "url": "http://angularjs.org", "category": "Development" },
{"id": 2, "title": "A List Apart", "url": "http://alistapart.com/", "category": "Design" },
{"id": 3, "title": "One Page Love", "url": "http://onepagelove.com/", "category": "Design" },
{"id": 4, "title": "MobilityWOD", "url": "http://www.mobilitywod.com/", "category": "Exercise" },
{"id": 5, "title": "Robb Wolf", "url": "http://robbwolf.com/", "category": "Exercise" },
{"id": 6, "title": "Senor Gif", "url": "http://memebase.cheezburger.com/senorgif", "category": "Humor" },
{"id": 7, "title": "Wimp", "url": "http://wimp.com", "category": "Humor" },
{"id": 8, "title": "Dump", "url": "http://dump.com", "category": "Humor" }
];
$scope.isCreating = false;
$scope.isEditing = false;
$scope.currentCategory = null;
$scope.editedBookmark = null;
function isCurrentCategory(category) {
return $scope.currentCategory !== null && category.name === $scope.currentCategory.name;
}
function setCurrentCategory(category) {
$scope.currentCategory = category;
cancelCreating();
cancelEditing();
}
$scope.isCurrentCategory = isCurrentCategory;
$scope.setCurrentCategory = setCurrentCategory;
function setEditedBookmark(bookmark) {
$scope.editedBookmark = angular.copy(bookmark);
}
function isSelectedBookmark(bookmarkId) {
return $scope.editedBookmark !== null && $scope.editedBookmark.id === bookmarkId;
}
$scope.setEditedBookmark = setEditedBookmark;
$scope.isSelectedBookmark = isSelectedBookmark;
function resetCreateForm() {
$scope.newBookmark = {
title: '',
url: '',
category: $scope.currentCategory
};
}
//-------------------------------------------------------------------------------------------------
// CRUD
//-------------------------------------------------------------------------------------------------
function createBookmark(bookmark) {
bookmark.id = $scope.bookmarks.length;
$scope.bookmarks.push(bookmark);
resetCreateForm();
}
function updateBookmark(bookmark) {
var index = _.findIndex($scope.bookmarks, function (b) {
return b.id == bookmark.id
});
$scope.bookmarks[index] = bookmark;
$scope.editedBookmark = null;
$scope.isEditing = false;
}
$scope.createBookmark = createBookmark;
$scope.updateBookmark = updateBookmark;
//-------------------------------------------------------------------------------------------------
// CREATING AND EDITING STATES
//-------------------------------------------------------------------------------------------------
function shouldShowCreating() {
return $scope.currentCategory && !$scope.isEditing;
}
function startCreating() {
$scope.isCreating = true;
$scope.isEditing = false;
resetCreateForm();
}
function cancelCreating() {
$scope.isCreating = false;
}
$scope.shouldShowCreating = shouldShowCreating;
$scope.startCreating = startCreating;
$scope.cancelCreating = cancelCreating;
function shouldShowEditing() {
return $scope.isEditing && !$scope.isCreating;
}
function startEditing() {
$scope.isCreating = false;
$scope.isEditing = true;
}
function cancelEditing() {
$scope.isEditing = false;
$scope.editedBookmark = null;
}
$scope.startEditing = startEditing;
$scope.cancelEditing = cancelEditing;
$scope.shouldShowEditing = shouldShowEditing;
})
;
[ui-view].ng-enter, [ui-view].ng-leave {
position: absolute;
left: 0;
right: 0;
-webkit-transition:all .5s ease-in-out;
-moz-transition:all .5s ease-in-out;
-o-transition:all .5s ease-in-out;
transition:all .5s ease-in-out;
}
[ui-view].ng-enter {
opacity: 0;
}
[ui-view].ng-enter-active {
opacity: 1;
}
[ui-view].ng-leave {
opacity: 1;
}
[ui-view].ng-leave-active {
opacity: 0;
}
html,
body {
background-color: #D5DBD8;
width: 100%;
height: 100%;
color: #4F534E;
}
a {
color: #373A36;
font-size: 24px;
text-decoration: none;
}
a:hover {
color: #5bc0de;
}
.btn, .btn:focus {
outline: none !important;
}
.btn-link {
color: #373A36;
font-size: 24px;
text-decoration: none !important;
}
.btn-link:hover {
color: #5bc0de;
}
.sidebar a {
color: #5bc0de;
font-size: 30px;
text-decoration: none;
}
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #EEE;
}
.logo {
padding: 20px;
}
.sidebar {
display: none;
padding-left: 0px;
padding-right: 0px;
}
.sidebar > ul {
margin: 0px;
padding: 0px;
}
.sidebar .active {
background-color: #D5DBD8;
}
.sidebar .active > a {
text-decoration: none;
color: #2B2828;
}
.main .active span,
.main .active > a {
color: #5bc0de;
}
.editing > button {
color: #5bc0de !important;
}
.editing > a {
color: #5bc0de !important;
}
.nav > li > a:hover, .nav > li > a:focus {
text-decoration: none;
background-color: #D5DBD8;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #2B2828;
border-right: 1px solid #7B807E;
}
}
.main {
padding-top: 96px;
padding-left: 24px;
}
.createBookmark {
padding-left: 5px;
padding-right: 5px;
}
.create-form, .edit-form {
padding: 20px;
}
html,
body {
background-color: #D5DBD8;
width: 100%;
height: 100%;
color: #4F534E;
}
a {
color: #373A36;
font-size: 24px;
text-decoration: none;
}
a:hover {
color: #5bc0de;
}
.btn, .btn:focus {
outline: none !important;
}
.btn-link {
color: #373A36;
font-size: 24px;
text-decoration: none !important;
}
.btn-link:hover {
color: #5bc0de;
}
.sidebar a {
color: #5bc0de;
font-size: 30px;
text-decoration: none;
}
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #EEE;
}
.logo {
padding: 20px;
}
.sidebar {
display: none;
padding-left: 0px;
padding-right: 0px;
}
.sidebar > ul {
margin: 0px;
padding: 0px;
}
.sidebar .active {
background-color: #D5DBD8;
}
.sidebar .active > a {
text-decoration: none;
color: #2B2828;
}
.main .active span,
.main .active > a {
color: #5bc0de;
}
.editing > button {
color: #5bc0de !important;
}
.editing > a {
color: #5bc0de !important;
}
.nav > li > a:hover, .nav > li > a:focus {
text-decoration: none;
background-color: #D5DBD8;
}
@media (min-width: 382px) {
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #2B2828;
border-right: 1px solid #7B807E;
}
}
@media (max-width: 766px){
.bookmark-rows {
float: right;
}
.bookmark-item {
margin-right: 50px;
}
}
.main {
padding-top: 96px;
padding-left: 24px;
}
.createBookmark {
padding-left: 5px;
padding-right: 5px;
}
.create-form, .edit-form {
padding: 20px;
}
/* normalize.css v2.1.0 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined in IE 8/9.
*/
/* line 22, ../sass/normalize.scss */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* Correct `inline-block` display not defined in IE 8/9.
*/
/* line 32, ../sass/normalize.scss */
audio,
canvas,
video {
display: inline-block;
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
/* line 41, ../sass/normalize.scss */
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address styling not present in IE 8/9.
*/
/* line 50, ../sass/normalize.scss */
[hidden] {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
/* line 64, ../sass/normalize.scss */
html {
font-family: sans-serif;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-ms-text-size-adjust: 100%;
/* 2 */
}
/**
* Remove default margin.
*/
/* line 74, ../sass/normalize.scss */
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/**
* Address `outline` inconsistency between Chrome and other browsers.
*/
/* line 86, ../sass/normalize.scss */
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
/* line 95, ../sass/normalize.scss */
a:active,
a:hover {
outline: 0;
}
/* ==========================================================================
Typography
========================================================================== */
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari 5, and Chrome.
*/
/* line 108, ../sass/normalize.scss */
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9, Safari 5, and Chrome.
*/
/* line 117, ../sass/normalize.scss */
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
/* line 126, ../sass/normalize.scss */
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
/* line 134, ../sass/normalize.scss */
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
*/
/* line 142, ../sass/normalize.scss */
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 8/9.
*/
/* line 152, ../sass/normalize.scss */
mark {
background: #ff0;
color: #000;
}
/**
* Correct font family set oddly in Safari 5 and Chrome.
*/
/* line 164, ../sass/normalize.scss */
code,
kbd,
pre,
samp {
font-family: monospace, serif;
font-size: 1em;
}
/**
* Improve readability of pre-formatted text in all browsers.
*/
/* line 173, ../sass/normalize.scss */
pre {
white-space: pre-wrap;
}
/**
* Set consistent quote types.
*/
/* line 181, ../sass/normalize.scss */
q {
quotes: "\201C" "\201D" "\2018" "\2019";
}
/**
* Address inconsistent and variable font size in all browsers.
*/
/* line 189, ../sass/normalize.scss */
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
/* line 198, ../sass/normalize.scss */
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
/* line 205, ../sass/normalize.scss */
sup {
top: -0.5em;
}
/* line 209, ../sass/normalize.scss */
sub {
bottom: -0.25em;
}
/* ==========================================================================
Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9.
*/
/* line 221, ../sass/normalize.scss */
img {
border: 0;
}
/**
* Correct overflow displayed oddly in IE 9.
*/
/* line 229, ../sass/normalize.scss */
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari 5.
*/
/* line 241, ../sass/normalize.scss */
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/**
* Define consistent border, margin, and padding.
*/
/* line 253, ../sass/normalize.scss */
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
/* line 264, ../sass/normalize.scss */
legend {
border: 0;
/* 1 */
padding: 0;
/* 2 */
}
/**
* 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
*/
/* line 278, ../sass/normalize.scss */
button,
input,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 2 */
margin: 0;
/* 3 */
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
/* line 290, ../sass/normalize.scss */
button,
input {
line-height: normal;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
/* line 302, ../sass/normalize.scss */
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
/* line 317, ../sass/normalize.scss */
button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button;
/* 2 */
cursor: pointer;
/* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
/* line 327, ../sass/normalize.scss */
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to `content-box` in IE 8/9.
* 2. Remove excess padding in IE 8/9.
*/
/* line 337, ../sass/normalize.scss */
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
/* line 348, ../sass/normalize.scss */
input[type="search"] {
-webkit-appearance: textfield;
/* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
/* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
/* line 361, ../sass/normalize.scss */
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
/* line 370, ../sass/normalize.scss */
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* 1. Remove default vertical scrollbar in IE 8/9.
* 2. Improve readability and alignment in all browsers.
*/
/* line 380, ../sass/normalize.scss */
textarea {
overflow: auto;
/* 1 */
vertical-align: top;
/* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
/* line 393, ../sass/normalize.scss */
table {
border-collapse: collapse;
border-spacing: 0;
}
[
{"id":0, "title": "AngularJS", "url": "http://angularjs.org", "category": "Development" },
{"id":1, "title": "Egghead.io", "url": "http://angularjs.org", "category": "Development" },
{"id":2, "title": "A List Apart", "url": "http://alistapart.com/", "category": "Design" },
{"id":3, "title": "One Page Love", "url": "http://onepagelove.com/", "category": "Design" },
{"id":4, "title": "MobilityWOD", "url": "http://www.mobilitywod.com/", "category": "Exercise" },
{"id":5, "title": "Robb Wolf", "url": "http://robbwolf.com/", "category": "Exercise" },
{"id":6, "title": "Senor Gif", "url": "http://memebase.cheezburger.com/senorgif", "category": "Humor" },
{"id":7, "title": "Wimp", "url": "http://wimp.com", "category": "Humor" },
{"id":8, "title": "Dump", "url": "http://dump.com", "category": "Humor" }
]
[
{"id": 0, "name": "Development"},
{"id": 1, "name": "Design"},
{"id": 2, "name": "Exercise"},
{"id": 3, "name": "Humor"}
]
<!doctype html>
<html ng-app="Eggly">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Eggly</title>
<link rel="stylesheet" href="assets/css/normalize.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/eggly.css">
<link rel="stylesheet" href="assets/css/animations.css">
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar" ui-view="categories"></div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ui-view="bookmarks"></div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular-animate.min.js"></script>
<script src="vendor/angular-ui-router.min.js"></script>
<script src="app/eggly-app.complete.js"></script>
<script src="app/categories/categories.js"></script>
<script src="app/categories/bookmarks/bookmarks.js"></script>
<script src="app/categories/bookmarks/edit/bookmarks-edit.js"></script>
<script src="app/categories/bookmarks/create/bookmarks-create.js"></script>
<script src="app/common/models/bookmarks-model.js"></script>
<script src="app/common/models/categories-model.js"></script>
</body>
</html>
<!doctype html>
<html ng-app="Eggly">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Eggly</title>
<link rel="stylesheet" href="assets/css/normalize.css">
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/eggly.css">
<link rel="stylesheet" href="assets/css/animations.css">
</head>
<body ng-controller="MainCtrl">
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<a ng-click="setCurrentCategory(null)"><img class="logo" src="assets/img/eggly-logo.png"></a>
<ul class="nav nav-sidebar">
<li ng-repeat="category in categories" ng-class="{'active':isCurrentCategory(category)}">
<a ng-click="setCurrentCategory(category)">
{{category.name}}
</a>
</li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main bookmark-rows">
<div ng-class="{active: isSelectedBookmark(bookmark.id)}" ng-repeat="bookmark in bookmarks | filter:{category:currentCategory.name}">
<button type="button" class="close" ng-click="deleteBookmark(bookmark)">×</button>
<button type="button" class="btn btn-link" ng-click="setEditedBookmark(bookmark);startEditing();" ><span class="glyphicon glyphicon-pencil"></span>
</button>
<a class="bookmark-item" href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>
</div>
<hr/>
<!-- CREATING -->
<div ng-if="shouldShowCreating()">
<button type="button" class="btn btn-link" ng-click="startCreating()">
<span class="glyphicon glyphicon-plus"></span>
Create Bookmark
</button>
<form class="create-form" ng-show="isCreating" role="form" ng-submit="createBookmark(newBookmark)" novalidate>
<div class="form-group">
<label for="newBookmarkTitle">Bookmark Title</label>
<input type="text" class="form-control" id="newBookmarkTitle" ng-model="newBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label for="newBookmarkURL">Bookmark URL</label>
<input type="text" class="form-control" id="newBookmarkURL" ng-model="newBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Create</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelCreating()">Cancel</button>
</form>
</div>
<!-- EDITING -->
<div ng-show="shouldShowEditing()">
<h4>Editing {{editedBookmark.title}}</h4>
<form class="edit-form" role="form" ng-submit="updateBookmark(editedBookmark)" novalidate>
<div class="form-group">
<label>Bookmark Title</label>
<input type="text" class="form-control" ng-model="editedBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label>Bookmark URL</label>
<input type="text" class="form-control" ng-model="editedBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Save</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelEditing()">Cancel</button>
</form>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<script src="app/eggly-app.finish.js"></script>
</body>
</html>
<!doctype html>
<html ng-app="Eggly">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Eggly</title>
<link rel="stylesheet" href="assets/css/normalize.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/eggly.css">
<link rel="stylesheet" href="assets/css/animations.css">
</head>
<body ng-controller="MainCtrl">
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<a ng-click="setCurrentCategory(null)"><img class="logo" src="assets/img/eggly-logo.png"></a>
<ul class="nav nav-sidebar">
<li ng-repeat="category in categories" ng-class="{'active':isCurrentCategory(category)}">
<a ng-click="setCurrentCategory(category)">
{{category.name}}
</a>
</li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<div ng-class="{active: isSelectedBookmark(bookmark.id)}" ng-repeat="bookmark in bookmarks | filter:{category:currentCategory.name}">
<button type="button" class="close">×</button>
<button type="button" class="btn btn-link" ng-click="setEditedBookmark(bookmark);startEditing();" ><span class="glyphicon glyphicon-pencil"></span>
</button>
<a href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>
</div>
<hr/>
<!-- CREATING -->
<div ng-if="shouldShowCreating()">
<button type="button" class="btn btn-link" ng-click="startCreating()">
<span class="glyphicon glyphicon-plus"></span>
Create Bookmark
</button>
<form class="create-form" ng-show="isCreating" role="form" ng-submit="createBookmark(newBookmark)" novalidate>
<div class="form-group">
<label for="newBookmarkTitle">Bookmark Title</label>
<input type="text" class="form-control" id="newBookmarkTitle" ng-model="newBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label for="newBookmarkURL">Bookmark URL</label>
<input type="text" class="form-control" id="newBookmarkURL" ng-model="newBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Create</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelCreating()">Cancel</button>
</form>
</div>
<!-- EDITING -->
<div ng-show="shouldShowEditing()">
<h4>Editing {{editedBookmark.title}}</h4>
<form class="edit-form" role="form" ng-submit="updateBookmark(editedBookmark)" novalidate>
<div class="form-group">
<label>Bookmark Title</label>
<input type="text" class="form-control" ng-model="editedBookmark.title" placeholder="Enter title">
</div>
<div class="form-group">
<label>Bookmark URL</label>
<input type="text" class="form-control" ng-model="editedBookmark.url" placeholder="Enter URL">
</div>
<button type="submit" class="btn btn-info btn-lg">Save</button>
<button type="button" class="btn btn-default btn-lg pull-right" ng-click="cancelEditing()">Cancel</button>
</form>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<script src="app/eggly-app.start.js"></script>
</body>
</html>
/**
* State-based routing for AngularJS
* @version v0.2.10
* @link http://angular-ui.github.com/
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e<c.length;e++){var f=c[e];if(a[f]!=b[f])return!1}return!0}function k(a,b){var c={};return H(a,function(a){c[a]=b[a]}),c}function l(a,b){var d=1,f=2,g={},h=[],i=g,j=I(a.when(g),{$$promises:g,$$values:g});this.study=function(g){function k(a,c){if(o[c]!==f){if(n.push(c),o[c]===d)throw n.splice(0,n.indexOf(c)),new Error("Cyclic dependency: "+n.join(" -> "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g<x[c].length;g++)n(x[c][g]);return b}function o(a){return a.indexOf("*")>-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O<E.length;O++,D=E[O])G=H[O]=d(G),N=A(D,c,D===b,N,G);var P=v.transition=N.then(function(){var d,e,g;if(v.transition!==P)return B;for(d=o.length-1;d>=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d<E.length;d++)e=E[d],e.locals=H[d],e.self.onEnter&&m.invoke(e.self.onEnter,e.self,e.locals.globals);if(v.transition!==P)return B;v.$current=b,v.current=b.self,v.params=c,J(v.params,q),v.transition=null;var h=b.navigable;return f.location&&h&&(r.url(h.url.format(h.locals.globals.$stateParams)),"replace"===f.location&&r.replace()),f.notify&&a.$broadcast("$stateChangeSuccess",b.self,c,k.self,n),M=r.url(),v.current},function(d){return v.transition!==P?B:(v.transition=null,a.$broadcast("$stateChangeError",b.self,c,k.self,n,d),z(),e.reject(d))});return P},v.is=function(a,d){var e=l(a);return C(e)?v.$current!==e?!1:C(d)&&null!==d?b.equals(q,d):!0:c},v.includes=function(a,d){if(E(a)&&o(a)){if(!p(a))return!1;a=v.$current.name}var e=l(a);if(!C(e))return c;if(!C(v.$current.includes[e.name]))return!1;var f=!0;return b.forEach(d,function(a,b){C(q[b])&&q[b]===a||(f=!1)}),f},v.href=function(a,b,c){c=I({lossy:!0,inherit:!1,absolute:!1,relative:v.$current},c||{});var d=l(a,c.relative);if(!C(d))return null;b=h(q,b||{},v.$current,d);var e=d&&c.lossy?d.navigable:d,g=e&&e.url?e.url.format(i(d.params,b||{})):null;return!f.html5Mode()&&g&&(g="#"+f.hashPrefix()+g),"/"!==N&&(f.html5Mode()?g=N.slice(0,-1)+g:c.absolute&&(g=N.slice(1)+g)),c.absolute&&g&&(g=r.protocol()+"://"+r.host()+(80==r.port()||443==r.port()?"":":"+r.port())+(!f.html5Mode()&&g?"/":"")+g),g},v.get=function(a,b){if(!C(a)){var c=[];return H(w,function(a){c.push(a.self)}),c}var d=l(a,b);return d&&d.self?d.self:null},v}function t(a,b,c,d){return a!==b||(c!==b.locals||d.reload)&&a.self.reloadOnSearch!==!1?void 0:!0}var u,v,w={},x={},y="abstract",z={parent:function(a){if(C(a.parent)&&a.parent)return l(a.parent);var b=/^(.+)\.[^.]+$/.exec(a.name);return b?l(b[1]):u},data:function(a){return a.parent&&a.parent.data&&(a.data=a.self.data=I({},a.parent.data,a.data)),a.data},url:function(a){var b=a.url;if(E(b))return"^"==b.charAt(0)?e.compile(b.substring(1)):(a.parent.navigable||u).url.concat(b);if(e.isMatcher(b)||null==b)return b;throw new Error("Invalid url '"+b+"' in state '"+a+"'")},navigable:function(a){return a.url?a:a.parent?a.parent.navigable:null},params:function(a){if(!a.params)return a.url?a.url.parameters():a.parent.params;if(!G(a.params))throw new Error("Invalid params in state '"+a+"'");if(a.url)throw new Error("Both params and url specicified in state '"+a+"'");return a.params},views:function(a){var b={};return H(C(a.views)?a.views:{"":a},function(c,d){d.indexOf("@")<0&&(d+="@"+a.parent.name),b[d]=c}),b},ownParams:function(a){if(!a.parent)return a.params;var b={};H(a.params,function(a){b[a]=!0}),H(a.parent.params,function(c){if(!b[c])throw new Error("Missing required parameter '"+c+"' in state '"+a.name+"'");b[c]=!1});var c=[];return H(b,function(a,b){a&&c.push(b)}),c},path:function(a){return a.parent?a.parent.path.concat(a):[]},includes:function(a){var b=a.parent?I({},a.parent.includes):{};return b[a.name]=!0,b},$delegates:{}};u=n({name:"",url:"^",views:null,"abstract":!0}),u.navigable=null,this.decorator=q,this.state=r,this.$get=s,s.$inject=["$rootScope","$q","$view","$injector","$resolve","$stateParams","$location","$urlRouter","$browser"]}function r(){function a(a,b){return{load:function(c,d){var e,f={template:null,controller:null,view:null,locals:null,notify:!0,async:!0,params:{}};return d=I(f,d),d.view&&(e=b.fromConfig(d.view,d.params,d.locals)),e&&d.notify&&a.$broadcast("$viewContentLoading",d),e}}}this.$get=a,a.$inject=["$rootScope","$templateFactory"]}function s(){var a=!1;this.useAnchorScroll=function(){a=!0},this.$get=["$anchorScroll","$timeout",function(b,c){return a?b:function(a){c(function(){a[0].scrollIntoView()},0,!1)}}]}function t(a,c,d){function e(){return c.has?function(a){return c.has(a)?c.get(a):null}:function(a){try{return c.get(a)}catch(b){return null}}}function f(a,b){var c=function(){return{enter:function(a,b,c){b.after(a),c()},leave:function(a,b){a.remove(),b()}}};if(i)return{enter:function(a,b,c){i.enter(a,null,b,c)},leave:function(a,b){i.leave(a,b)}};if(h){var d=h&&h(b,a);return{enter:function(a,b,c){d.enter(a,null,b),c()},leave:function(a,b){d.leave(a),b()}}}return c()}var g=e(),h=g("$animator"),i=g("$animate"),j={restrict:"ECA",terminal:!0,priority:400,transclude:"element",compile:function(c,e,g){return function(c,e,h){function i(){k&&(k.remove(),k=null),m&&(m.$destroy(),m=null),l&&(q.leave(l,function(){k=null}),k=l,l=null)}function j(f){var h=c.$new(),j=l&&l.data("$uiViewName"),k=j&&a.$current&&a.$current.locals[j];if(f||k!==n){var r=g(h,function(a){q.enter(a,e,function(){(b.isDefined(p)&&!p||c.$eval(p))&&d(a)}),i()});n=a.$current.locals[r.data("$uiViewName")],l=r,m=h,m.$emit("$viewContentLoaded"),m.$eval(o)}}var k,l,m,n,o=h.onload||"",p=h.autoscroll,q=f(h,c);c.$on("$stateChangeSuccess",function(){j(!1)}),c.$on("$viewContentLoading",function(){j(!1)}),j(!0)}}};return j}function u(a,b,c){return{restrict:"ECA",priority:-400,compile:function(d){var e=d.html();return function(d,f,g){var h=g.uiView||g.name||"",i=f.inheritedData("$uiView");h.indexOf("@")<0&&(h=h+"@"+(i?i.state.name:"")),f.data("$uiViewName",h);var j=c.$current,k=j&&j.locals[h];if(k){f.data("$uiView",{name:h,state:k.$$state}),f.html(k.$template?k.$template:e);var l=a(f.contents());if(k.$$controller){k.$scope=d;var m=b(k.$$controller,k);k.$$controllerAs&&(d[k.$$controllerAs]=m),f.data("$ngControllerController",m),f.children().data("$ngControllerController",m)}l(d)}}}}}function v(a){var b=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function w(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function x(a,c){var d=["location","inherit","reload"];return{restrict:"A",require:"?^uiSrefActive",link:function(e,f,g,h){var i=v(g.uiSref),j=null,k=w(f)||a.$current,l="FORM"===f[0].nodeName,m=l?"action":"href",n=!0,o={relative:k},p=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in p&&(o[a]=p[a])});var q=function(b){if(b&&(j=b),n){var c=a.href(i.state,j,o);return h&&h.$$setStateInfo(i.state,j),c?void(f[0][m]=c):(n=!1,!1)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a){a!==j&&q(a)},!0),j=e.$eval(i.paramExpr)),q(),l||f.bind("click",function(b){var d=b.which||b.button;d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular);