Angular JS – Just Another To-Do App
- In the last year, I have made quite enough To-Do apps – just in VitoshAcademy.com I have published one with PHP with login functionality, and I had quite a big project (about 3 days of coding from 3 coders ) here with Python with Djangon. Thus, I have decided to build one ever nicer with Angular JS. At the end, it appeared to be a ToDo app with a great search functionality. If I manage, one day I would add a database as well. But currently – not, the scopes in Angular JS were really somehow hard to understand. After all, I have managed. Somehow. 🙂
So long story short – wysiwyg, if you get the files from GitHub. These are the features:
- The small bootstrap icons have 5 different features:
- The plus adds a new Web Site To-Do Application part.
- The info gives hidden info for the To-Do.
- The heart changes the info at two label on the bottom, where currently is written “Here be dragons”.
- The pencil is used for edit.
- The X sign deletes the line.
- The search works fantastically.
- And that is all… The whole CRUD works, so I can rest and drink my beer.
How is this made? Pretty much with the sweat of a developer, google and simple implementation of the MVC framework. Here are the two main files:
<!DOCTYPE html>
<html>
<head>
<title>TO DO Planner</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="/bootstrap335/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="./project.js"></script>
<link href='https://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
<style>
div {
margin: 15px;
padding: 15px;
font-family: 'Oswald', sans-serif;
font-size: 22px;
background-color: whitesmoke;
}
hr {
border-top: 10px;
border-color: #000000;
border-style: dotted;
}
</style>
</head>
<body>
<div ng-app="ToDoPlanner" id="main_div">
<h1>Just Another To-Do Application with some links</h1>
<div ng-view></div>
<script type="text/ng-template" id="list.html">
<input type="text" ng-model="query" ng-change="search()" class="search-query" placeholder="Search for anything in the table">
<i class="icon-search"></i>
<a href=".">Reset Filter (Simply Refresh or Click Here)</a>
<hr>
<table class="table table-hover">
<thead>
<tr>
<th>WebSite Full Address</a>
</th>
<th>ToDo</th>
<th>Responsible</th>
<th><a href="#/new"><i class="icon-plus"></i></a></th>
<th><a href="http://www.vitoshacademy.com"><i class="icon-flag"></i></a></th>
<th><a href="https://www.vitoshacademy.com"><i class="icon-fire"></i></a></th>
<th><a href="http://www.vitoshacademy.com"><i class="icon-star"></i></a></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="link in pagedItems[currentPage]">
<td><a href="{{link.projectaddress}}">{{link.nameproject}}</a></td>
<td>{{link.todo}}</td>
<td>{{link.responsible}}</td>
<td>
<a href="#/edit/{{link.Id}}"><i class="icon-pencil"></i></a>
</td>
<td>
<i class="icon-heart" ng-click="update_label(link.info)" ng-controller="myCtrl"></i>
</td>
<td>
<a href="#/info/{{link.Id}}"><i class="icon-info-sign"></i></a>
</td>
<td>
<a href="#/delete/{{link.Id}}"><i class = "icon-remove"></i></a>
</td>
</tr>
</tbody>
</table>
<button ng-disabled="currentPage == 0" ng-click="setPage( currentPage=currentPage-1 )" class="btn btn-warning">
Previous
</button>
Page: {{currentPage+1}}/{{numberOfPages()}}
<button ng-disabled="currentPage >= filteredItems.length/pageSize - 1" ng-click="setPage( currentPage=currentPage+1 )" class="btn btn-warning">
Next
</button>
Links: {{filteredItems.length}}
<hr>
<label class="control-label" id="label_with_data">Here be dragons</label>
<label class="control-label" id="label_with_data_2"></label>
</script>
<script type="text/ng-template" id="delete.html">
<form name="my_delete_form" class="form-horizontal">
<div class="control-group" ng-class="{error: my_delete_form.nameproject.$invalid}">
<div class="control-group">
<label>Are you sure that you want to delete {{alink.nameproject}}?</label>
<button ng-click="close()" ng-show="alink.Id" class="btn btn-info">Back</button>
<button ng-click="delete()" ng-show="alink.Id" class="btn btn-danger">Delete</button>
</div>
</div>
</form>
</script>
<script type="text/ng-template" id="error404.html">
<form name="me_error" class="form-horizontal">
<div class="control-group">
<label>404 is the bus to Druzhba :)</label>
<button ng-click="close()" class="btn ">Back To Do Stuff</button>
</div>
</form>
</script>
<script type="text/ng-template" id="info.html">
<form name="my_info_form" class="form-horizontal">
<div class="control-group" ng-class="{error: my_info_form.nameproject.$invalid}">
<div class="control-group">
<label>{{alink.info}}</label>
<button ng-click="close()" ng-show="alink.Id" class="btn btn-info">Back</button>
</div>
</div>
</form>
</script>
<script type="text/ng-template" id="detail.html">
<form name="my_edit_form" class="form-horizontal">
<div class="control-group" ng-class="{error: my_edit_form.nameproject.$invalid}">
<label class="control-label">Name the Project:</label>
<input type="text" name="nameproject" ng-model="alink.nameproject" required>
<span ng-show="my_edit_form.name.$error.required" class="help-inline">Required</span>
</div>
<div class="control-group" ng-class="{error: my_edit_form.projectaddress.$invalid}">
<label class="control-label">Website:</label>
<input type="url" name="projectaddress" ng-model="alink.projectaddress" required>
<span ng-show="my_edit_form.site.$error.required" class="help-inline">Required</span>
<span ng-show="my_edit_form.site.$error.url" class="help-inline">Not a URL</span>
</div>
<div class="control-group">
<label class="control-label">To do:</label>
<textarea name="todo" rows="5" ng-model="alink.todo"></textarea>
</div>
<div class="control-group">
<label class="control-label">Responsible:</label>
<input type="text" name="responsible" ng-model="alink.responsible">
</div>
<div class="control-group">
<label class="control-label">Additional info:</label>
<textarea name="info" rows="5" ng-model="alink.info"></textarea>
</div>
<div class="control-group">
<label class="control-label"></label>
<a href="#/" class="btn" ng-click="close()">Cancel</a>
<button ng-click="save()" ng-disabled="isClean() || my_edit_form.$invalid" class="btn btn-primary">Save</button>
<button ng-click="destroy()" ng-show="alink.Id" class="btn btn-danger">Delete
</button>
</div>
</form>
</script>
</div>
</body>
</html>
And the project.js file:
var app = angular.module('ToDoPlanner', [])
.config(function($routeProvider) {
$routeProvider.
when('/', {
controller: ListController,
templateUrl: 'list.html'
}).
when('/edit/:linkId', {
controller: EditController,
templateUrl: 'detail.html'
}).
when('/new', {
controller: CreateController,
templateUrl: 'detail.html'
}).
when('/info/:linkId', {
controller: InfoController,
templateUrl: 'info.html'
}).
when('/delete/:linkId', {
controller: DeleteController,
templateUrl: 'delete.html'
}).
otherwise({
controller: Error404Controller,
templateUrl: 'error404.html'
});
});
function ListController($scope, $rootScope, $filter, linkService) {
$scope.filteredItems = linkService.getLinks();
$scope.pageSize = 5;
$scope.pagedItems = [];
$scope.numberOfPages = function() {
return Math.ceil($scope.filteredItems.length / $scope.pageSize);
}
$scope.search = function() {
if (!$scope.query || $scope.query === "") {
$scope.filteredItems = linkService.getLinks();
} else {
$scope.filteredItems = $filter('customFilter')(linkService.getLinks(), $scope.query, ["nameproject", "projectaddress", "todo", "responsible"]);
$rootScope.query = $scope.query;
}
$scope.groupToPages();
if (!$scope.currentPage) {
$scope.currentPage = 0;
}
$scope.currentPage = Math.min($scope.currentPage, $scope.pagedItems.length - 1);
$rootScope.currentPage = $scope.currentPage;
};
$scope.groupToPages = function() {
$scope.pagedItems = [];
for (var i = 0; i < $scope.filteredItems.length; i++) {
if (i % $scope.pageSize == 0) {
$scope.pagedItems[Math.floor(i / $scope.pageSize)] = [$scope.filteredItems[i]];
} else {
$scope.pagedItems[Math.floor(i / $scope.pageSize)].push($scope.filteredItems[i]);
}
}
};
$scope.search();
$scope.setPage = function(newpage) {
$scope.currentPage = newpage;
$rootScope.currentPage = newpage;
}
}
function CreateController($scope, $location, linkService) {
$scope.save = function() {
linkService.addLink(angular.copy($scope.alink));
$location.path('/');
}
}
function Error404Controller($scope, $location, $routeParams, linkService) {
$scope.alink = angular.copy(linkService.findById($routeParams.linkId));
$scope.original = angular.copy($scope.alink);
$scope.close = function() {
$scope.alink = null;
$scope.project = null;
$location.path('/');
}
}
function InfoController($scope, $location, $routeParams, linkService) {
$scope.alink = angular.copy(linkService.findById($routeParams.linkId));
$scope.original = angular.copy($scope.alink);
$scope.close = function() {
$scope.alink = null;
$scope.project = null;
$location.path('/');
}
}
function UpdateLabels($scope, $location, $routeParams, linkService) {
$scope.alink = angular.copy(linkService.findById($routeParams.linkId));
$scope.original = angular.copy($scope.alink);
}
function DeleteController($scope, $location, $routeParams, linkService) {
$scope.alink = angular.copy(linkService.findById($routeParams.linkId));
$scope.original = angular.copy($scope.alink);
$scope.delete = function() {
linkService.deleteLink($scope.alink);
$scope.close();
};
$scope.close = function() {
$scope.alink = null;
$scope.project = null;
$location.path('/');
}
}
function EditController($scope, $location, $routeParams, linkService) {
$scope.alink = angular.copy(linkService.findById($routeParams.linkId));
$scope.original = angular.copy($scope.alink);
$scope.isClean = function() {
return angular.equals($scope.original, $scope.alink);
};
$scope.destroy = function() {
linkService.deleteLink($scope.alink);
$scope.close();
};
$scope.save = function() {
linkService.updateLink($scope.alink);
$scope.close();
};
$scope.close = function() {
$scope.alink = null;
$scope.project = null;
$location.path('/');
}
}
app.factory('linkService', function() {
var data = [{
Id: 1,
nameproject: 'VitoshAcademy - fix the outlook!',
projectaddress: 'https://www.vitoshacademy.com',
todo: 'Fix the old picture from the main page.',
info: 'The idea is to fix the outlook',
responsible: 'The new intern, that I would hire one day.'
}, {
Id: 2,
nameproject: 'VitoshAcademy - write more VBA articles',
projectaddress: 'https://www.vitoshacademy.com/vba',
todo: 'Write some more articles for VBA',
info: 'Write more articles - this is extremely important for the SEO! :)',
responsible: 'Vitosh'
}, {
Id: 3,
nameproject: 'HateGame - Monetarize it!',
projectaddress: 'http://hategame.com',
todo: 'Tell Yavor to make more games.',
info: 'Finally this site should start making money, Yavore!',
responsible: 'Vitosh'
}, {
Id: 4,
nameproject: 'VitoshAcademy - fix the outlook - a little better!',
projectaddress: 'https://www.vitoshacademy.com',
todo: 'Fix the old picture from the main page.',
info: 'The idea is to fix the outlook',
responsible: 'The new intern, that I would hire one day.'
}, {
Id: 5,
nameproject: 'VitoshAcademy - write more VBA articles',
projectaddress: 'https://www.vitoshacademy.com/vba',
todo: 'Write some more articles for VBA',
info: 'Write more articles - this is extremely important for the SEO! :)',
responsible: 'Vitosh'
}, {
Id: 6,
nameproject: 'HateGame - Monetarize it!',
projectaddress: 'http://hategame.com',
todo: 'Tell Yavor to make more games.',
info: 'Finally this site should start making money, Yavore!',
responsible: 'Vitosh'
}, {
Id: 7,
nameproject: 'vitoshacademy- Promote',
projectaddress: 'http://www.vitoshacademy.com',
todo: 'Promote the new name of the site',
info: 'It is a nice domain, or so I have thought when I bought it :)',
responsible: 'Vitosh'
}];
return {
findById: function(id) {
for (var i = 0; i < data.length; i++) {
if (data[i].Id == id) {
return data[i];
}
}
return null;
},
getLinks: function() {
return data;
},
addLink: function(item) {
item.Id = new Date().getTime();
data.push(item);
},
deleteLink: function(item) {
var tempItem = this.findById(item.Id);
data.splice(data.indexOf(tempItem), 1);
},
updateLink: function(item) {
var p = this.findById(item.Id);
if (!p)
this.addLink(item);
copy(item, p);
}
};
});
function copy(o, t) {
if (!t)
t = {};
Object.keys(o).forEach(function(val) {
t[val] = o[val];
});
return t;
}
function searchMatch(place_to_search, thing_to_search) {
if (typeof(place_to_search) != "string")
return false;
if (!thing_to_search) {
return true;
}
return place_to_search.toLowerCase().indexOf(thing_to_search.toLowerCase()) !== -1;
};
app.filter('startFrom', function() {
return function(input, start) {
start = +start;
return input.slice(start);
}
});
app.filter('customFilterTags', function() {
return function(links, queryStr) {
var arr = [];
for (var i = links.length; i--;) {
if (links[i].tag.toLowerCase().indexOf(queryStr.toLowerCase()) !== -1) {
arr.push(links[i]);
}
}
return arr;
}
});
app.filter('customFilter', function() {
return function(data, queryStr, includedAttributes) {
var resultArray = [];
for (var i = data.length; i--;) {
var item = data[i];
var found = false;
for (var attr in item) {
if (includedAttributes.indexOf(attr) > -1) {
if (searchMatch(item, queryStr)) {
found = true;
break;
}
}
}
if (found) {
resultArray.push(item);
}
}
return resultArray;
}
});
app.controller('myCtrl', ['$scope', function($scope) {
$scope.counter = 6;
$scope.update_label = function(my_value) {
console.log(my_value);
var lbl = document.getElementById("label_with_data");
var lbl_2 = document.getElementById("label_with_data_2");
$scope.counter++;
lbl_2.innerHTML = my_value;
switch ($scope.counter % 7) {
case 0:
lbl.innerHTML = "You're my heart!";
break;
case 1:
lbl.innerHTML = "You're my soul!";
break;
case 2:
lbl.innerHTML = "La lalalala!~";
break;
case 3:
lbl.innerHTML = "Thank you for clicking ...";
break;
case 4:
lbl.innerHTML = "And for your interest ...";
break;
case 5:
lbl.innerHTML = "Now from the beginning ...";
break;
case 6:
lbl.innerHTML = "Ready - Steady - Go";
break;
}
};
}]);
And at the end – the repository of the angular todo project. If you are willing, please feel free to commit to it.
