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. 🙂

Just Another To Do App

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.