使用Laravel 5和AngularJS构建时间跟踪器–第1部分

Laravel and AngularJS work great together, but it can be a little tricky to get going at first, especially if you are new to the frameworks. In a previous article, Chris showed you how to make a Single Page Comment App with Laravel and Angular. This tutorial will again bring the two frameworks together as we build out a simple time tracking application.

Laravel和AngularJS可以很好地协作,但是刚开始时可能会有些棘手,特别是如果您不熟悉框架。 在上一篇文章中,克里斯向您展示了如何使用Laravel和Angular制作单页评论应用程序 。 当我们构建一个简单的时间跟踪应用程序时,本教程将再次将这两个框架结合在一起。

We'll be going into a lot of detail in this tutorial, so to make things manageable it has been broken into two parts. The first part will focus on getting the front-end setup with AngularJS and the second part on getting the backend setup with Laravel 5.

在本教程中,我们将进行很多详细的介绍,因此为了使内容易于管理,它分为两个部分。 第一部分将重点介绍如何使用AngularJS进行前端设置,而第二部分将重点在于使用Laravel 5进行后端设置。

应用程序 (The Application)

We'll be building a simple time tracking application that will give users the ability to track hours spent on tasks by clocking-in and clocking-out. We'll use the Timepicker directive offered by UI Bootstrap to let users enter their start and end times, and will give them a field to enter comments. Users will be able to list all of their time entries, create new ones, and also edit or delete existing entries.

我们将构建一个简单的时间跟踪应用程序,该应用程序将使用户能够通过记入和记出来跟踪花费在任务上的时间。 我们将使用UI Bootstrap提供的Timepicker指令让用户输入他们的开始时间和结束时间,并为他们提供一个用于输入评论的字段。 用户将能够列出其所有时间条目,创建新时间条目以及编辑或删除现有条目。

time-tracker-1-1

We will be using the AngularJS Style Guide by John Papa which sets out an opinionated set of conventions for Angular apps. If you haven't seen this style guide before, some of the ways we set things up might look a bit foreign. Don't worry though, it's easy to catch on, and you'll likely find our code easier to read and understand with the it applied.

我们将使用John Papa 撰写的AngularJS样式指南 ,该指南为Angular应用设置了一套自以为是的约定。 如果您以前没有看过此样式指南,那么我们进行设置的某些方法可能看起来有些陌生。 不过,请不要担心,它很容易上手,并且您可能会发现我们的代码通过应用它更易于阅读和理解。

设置文件夹结构并安装依赖项 (Setup the Folder Structure and Install Dependencies)

As we focus on the front-end for this part of the tutorial (the Angular side, we'll deal with the Laravel side in part 2), let's keep our folder structure simple. Create a new directory and give it the following structure:

当我们专注于本部分教程的前端时(在Angular方面,我们将在第2部分中讨论Laravel方面),让我们保持文件夹结构简单。 创建一个新目录并为其提供以下结构:

|--bower_components
|--css
|--data
|--scripts
    |--controllers
    |--services

Next, let's install our dependencies with Bower. From the command line:

接下来,让我们使用Bower安装依赖项。 在命令行中:

bower install angular angular-bootstrap angular-resource bootstrap moment

That's a lot of dependencies! Let's go through them to see what we've got:

那有很多依赖! 让我们通过他们来看看我们所拥有的:

  • Angular - the AngularJS framework.

    Angular -AngularJS框架。
  • Angular Bootstrap - UI Boostrap, which gives us native Angular directives for Boostrap. For more on using the two together, read up on How to Use Bootstrap and Angular.

    Angular Bootstrap -UI Boostrap,它为Boostrap提供了本机Angular指令。 有关将两者结合使用的更多信息,请阅读如何使用Bootstrap和Angular
  • Angular Resource - ngResource gives us a way to interact with RESTful server-side data sources.

    Angular Resource -ngResource为我们提供了一种与RESTful服务器端数据源进行交互的方法。
  • Bootstrap - We'll need the standard Boostrap styles, so let's grab the original framework too.

    Bootstrap-我们需要标准的Boostrap样式,所以我们也来获取原始框架。
  • Moment.js - A great library that drastically simplifies working with time in JavaScript. We will use it to calculate our tracked time.

    Moment.js-一个很棒的库,极大地简化了JavaScript的工作时间。 我们将使用它来计算我们的跟踪时间。

设置JavaScript文件 (Setup the JavaScript Files)

Let's create our JavaScript files and setup their basic structure. There are three files that we will need:

让我们创建JavaScript文件并设置其基本结构。 我们将需要三个文件:

  • scripts/app.js - where our application module is defined, the starting point for the application.

    scripts / app.js-定义我们的应用程序模块的位置,是应用程序的起点。
  • scripts/controllers/TimeEntry.js - our main controller for the time entries.

    scripts / controllers / TimeEntry.js-我们用于输入时间的主控制器。
  • scripts/services/time.js - an Angular factory that will be used to abstract away common tasks.

    scripts / services / time.js-一个Angular工厂,将用于提取常见任务。

Our app.js file is very simple---we just need to define the application module, which we will call timeTracker, and put in our dependencies:

我们的app.js文件非常简单-我们只需要定义应用程序模块,我们将其称为timeTracker ,并放入我们的依赖项:

/* scripts/app.js */

(function() {

    'use strict';

    angular
        .module('timeTracker', [
            'ngResource',
            'ui.bootstrap'
        ]);

})();

In our TimeEntry.js file we need to declare our controller which we will call TimeEntry. Following the AngularJS style guide mentioned earlier, we will use a capture variable called vm (ViewModel) for our controller:

在我们的TimeEntry.js文件中,我们需要声明我们将称为TimeEntry控制器。 按照前面提到的AngularJS样式指南,我们将为控制器使用一个名为vm (ViewModel)的捕获变量:

/* scripts/controllers/TimeEntry.js */

(function() {

  'use strict';

    angular
        .module('timeTracker')
        .controller('TimeEntry', TimeEntry);

        function TimeEntry(time) {

            // vm is our capture variable
            var vm = this;

            vm.timeentries = [];

        }
})();

Here you can see that we've declared an empty array called vm.timeentries. This array will eventually hold the time entry data that we grab with ngResource. You'll also notice that we create a named function called TimeEntry and pass it to the controller. The time argument we pass into this function is actually a dependency that we are injecting, and it references the time service that we will create next.

在这里您可以看到我们已经声明了一个名为vm.timeentries的空数组。 该数组最终将保存我们使用ngResource捕获的时间输入数据。 您还将注意到,我们创建了一个名为TimeEntry的命名函数,并将其传递给控制器​​。 我们传递给该函数的time参数实际上是我们要注入的依赖项,它引用了接下来将要创建的time服务。

Finally, our time.js file will be the service for abstracting common code, especially the ngResource pieces.

最后,我们的time.js文件将成为抽象通用代码(尤其是ngResource片段)的服务。

/* scripts/services/time.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .factory('time', time);

        function time($resource) {

            // ngResource call to our static data
            var Time = $resource('data/time.json');

            return {};

        }
})();

This service follows the same pattern that we saw in our controller, but this time uses a factory called time. Our named function time takes ngResource as a dependency and currently returns an empty object.

该服务遵循在控制器中看到的相同模式,但是这次使用的factory称为time 。 我们的命名函数time将ngResource作为依赖项,当前返回一个空对象。

There's one other piece in there right now---a variable called Time that uses ngResource to make a call to a static JSON file called time.json. So what is happening here and what is this JSON file for? We will eventually wire things up with Laravel acting as a RESTful API for our app, and will have it return results as JSON. This time.json file is a simple way to get started with some mocked out data without the need for a backend just yet. It allows us to use ngResource off the bat, and when we're ready, we can simply switch out the call to this static file for a call to the RESTful API that we build with Laravel.

现在还有另外一块-名为Time的变量,该变量使用ngResource调用名为time.json的静态JSON文件。 那么这里发生了什么,这个JSON文件是做什么用的呢? 最终,我们将使用Laravel作为我们应用程序的RESTful API进行连接,并将其返回结果作为JSON。 这个time.json文件是一种简单的方法,可以开始使用一些time.json数据,而无需后端。 它允许我们立即使用ngResource,并且当我们准备就绪时,我们可以简单地将对此静态文件的调用切换为对使用Laravel构建的RESTful API的调用。

Now that we know what it's for, let's put some data in the time.json file. Feel free to change things up with your own details:

现在我们知道它的用途了,让我们将一些数据放入time.json文件中。 随时使用您自己的详细信息进行更改:

/* data/time.json */

[
    {
      "id":1,
      "user_id":1,
      "user_firstname":"Ryan",
      "user_lastname":"Chenkie",
      "start_time":"2015-02-21T18:56:48Z",
      "end_time":"2015-02-21T20:33:10Z",
      "comment": "Initial project setup."
    },
    {
      "id":2,
      "user_id":1,
      "user_firstname":"Ryan",
      "user_lastname":"Chenkie",
      "start_time":"2015-02-27T10:22:42Z",
      "end_time":"2015-02-27T14:08:10Z",
      "comment": "Review of project requirements and notes for getting started."
    },
    {
      "id":3,
      "user_id":1,
      "user_firstname":"Ryan",
      "user_lastname":"Chenkie",
      "start_time":"2015-03-03T09:55:32Z",
      "end_time":"2015-03-03T12:07:09Z",
      "comment": "Front-end and backend setup."
    }
]
Note: You must omit the file comment at the top of the JSON file in your actual code 注意:您必须在实际代码中省略JSON文件顶部的文件注释。

设置视图 (Setting up the View)

Let's now setup our index.html file that will provide the structure to our single-page app. For the time being, we'll put in the basic elements and link up our CSS and JavaScript files.

现在,让我们设置index.html文件,该文件将为我们的单页应用程序提供结构。 目前,我们将放入基本元素并链接我们CSS和JavaScript文件。

<!-- index.html -->

<!doctype html>
<html>
    <head>
        <title>Time Tracker</title>
        <link rel="stylesheet" href="css/style.css">
        <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    </head>
    <body ng-app="timeTracker" ng-controller="TimeEntry as vm">

        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#">Time Tracker</a>
                </div>
            </div>
        </nav>

        <div class="container">

        </div>  
    </body>

    <!-- Application Dependencies -->
    <script type="text/javascript" src="bower_components/angular/angular.js"></script>
    <script type="text/javascript" src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
    <script type="text/javascript" src="bower_components/angular-resource/angular-resource.js"></script>
    <script type="text/javascript" src="bower_components/moment/moment.js"></script>

    <!-- Application Scripts -->
    <script type="text/javascript" src="scripts/app.js"></script>
    <script type="text/javascript" src="scripts/controllers/TimeEntry.js"></script>
    <script type="text/javascript" src="scripts/services/time.js"></script>
</html>

As you can see, we have bootstrapped our application on the body tag by setting ng-app to the timeTracker module. We're also making use of the controller as syntax in our controller declaration. The controller as syntax is a big part of the AngularJS Style Guide. It can make for cleaner code because we are able to bind methods and properties directly onto the controller by using the this keyword, like you saw earlier when we setup the JavaScript files. This means that we don't have to rely on using $scope.

如您所见,通过将ng-app设置为timeTracker模块,我们已在body标签上引导了我们的应用程序。 我们还在controller声明中将controller as语法。 controller as语法的controller as是《 AngularJS样式指南》的重要组成部分。 它可以使代码更简洁,因为我们可以使用this关键字将方法和属性直接绑定到控制器上,就像您在设置JavaScript文件时所看到的那样。 这意味着我们不必依赖使用$scope

We've now got the main structure for the application in place, but it isn't doing much just yet. Next up we will fetch our sample data and display it.

现在,我们已经有了应用程序的主要结构,但是目前还没有做很多事情。 接下来,我们将获取示例数据并显示它。

额外样式 (Extra Styling)

Here is some CSS that we'll use for adjusting the elements to display properly:

这是一些CSS,我们将使用它们来调整元素以使其正确显示:

/* css/style.css */

.time-numbers > *, .time-entry > * {
    display: inline-block;
}

.timepicker {
    margin-right: 15px;
}

@media (min-width: 546px) {
    .time-entry-comment {
        position: relative;
        top: 60px;
    }
}

获取数据 (Fetching the Data)

We're going to let the time service be responsible for fetching data. We've already seen that a reference exists for ngResource to talk to the static time.json file, but now we need to get a method in place to fetch the data. Let's update our time.js file with a method to handle this request:

我们将让time服务负责获取数据。 我们已经看到存在ngResource与静态time.json文件对话的引用,但是现在我们需要一个适当的方法来获取数据。 让我们使用一种处理该请求的方法来更新我们的time.js文件:

/* scripts/services/time.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .factory('time', time);

        function time($resource) {

            // ngResource call to our static data
            var Time = $resource('data/time.json');

            function getTime() {
                // $promise.then allows us to intercept the results
                // which we will use later
                return Time.query().$promise.then(function(results) {
                    return results;
                }, function(error) { // Check for errors
                    console.log(error);
                });
            }

            return {
                getTime: getTime,
            }
        }

})();

Here we've added a method called getTime. This method is responsible for using ngResource to make a query to the static data and then return the results. The $promise.then syntax is necessary in our case because a little later we will be modifying the returned array on the fly. For now, it is simply returning the results of the query. Finally, we've modified the object that the time service returns to include the getTime method.

在这里,我们添加了一个名为getTime的方法。 此方法负责使用ngResource对静态数据进行query ,然后返回结果。 $promise.then语法在我们的例子中是必需的,因为稍后我们将即时修改返回的数组。 目前,它只是返回查询结果。 最后,我们修改了time服务返回的对象以包括getTime方法。

Next, let's hook into this new method in our controller:

接下来,让我们在控制器中加入这个新方法:

/* scripts/controllers/TimeEntry.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .controller('TimeEntry', TimeEntry);

        function TimeEntry(time) {

            // vm is our capture variable
            var vm = this;

            vm.timeentries = [];

            // Fetches the time entries from the static JSON file
            // and puts the results on the vm.timeentries array
            time.getTime().then(function(results) {
                vm.timeentries = results;
                console.log(vm.timeentries);
            }, function(error) { // Check for errors
                console.log(error);
            });

        }
})();

We use then to grab the results and put them on our vm.timeentries array. To make sure we're getting data back, we can log the array out to the console. If everything worked properly, you should see it show up in developer tools:

then ,我们使用它来获取结果并将其放在vm.timeentries数组中。 为了确保我们能取回数据,我们可以将阵列注销到控制台。 如果一切正常,您应该在开发人员工具中看到它:

time-tracker-1-2

Finally, let's have the results show up in our application. We can add in some additional HTML and put the templating in place to display our results. At this time, we can also add in the controls for making new time entries:

最后,让结果显示在我们的应用程序中。 我们可以添加一些其他HTML并将模板放在适当的位置以显示我们的结果。 这时,我们还可以添加用于输入新时间的控件:

<!-- index.html --> 
...
<body ng-app="timeTracker" ng-controller="TimeEntry as vm">

    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">Time Tracker</a>
            </div>
        </div>
        <div class="container-fluid time-entry">
            <div class="timepicker">
                <span class="timepicker-title label label-primary">Clock In</span>
                <timepicker ng-model="vm.clockIn" hour-step="1" minute-step="1" show-meridian="true">
                </timepicker> 
            </div>
            <div class="timepicker">
                <span class="timepicker-title label label-primary">Clock Out</span>
                <timepicker ng-model="vm.clockOut" hour-step="1" minute-step="1" show-meridian="true">
                </timepicker>
            </div>
            <div class="time-entry-comment">                
                <form class="navbar-form">
                    <input class="form-control" ng-model="vm.comment" placeholder="Enter a comment">
                    </input>
                    <button class="btn btn-primary" ng-click="vm.logNewTime()">Log Time</button>
                </form>
            </div>    
        </div>
    </nav>

    <div class="container">
        <div class="col-sm-8">
            <div class="well timeentry" ng-repeat="time in vm.timeentries">
                <div class="row">
                    <div class="col-sm-8">
                        <h4><i class="glyphicon glyphicon-user"></i> 
                        {{time.user_firstname}} {{time.user_lastname}}</h4>
                        <p><i class="glyphicon glyphicon-pencil"></i> {{time.comment}}</p>                  
                    </div>
                    <div class="col-sm-4 time-numbers">
                        <h4><i class="glyphicon glyphicon-calendar"></i> 
                        {{time.end_time | date:'mediumDate'}}</h4>
                    </div>
                </div>
            </div>
        </div>
    </div>

</body>    
...

There's a lot going on here, so let's go through the changes step by step.

这里有很多事情,所以让我们一步一步地进行更改。

  • Firstly, we've made some changes to the navbar. We've added in the timepicker directive that is offered by UI Bootstrap and set some default parameters for it. The timepicker directive gives us a nice little widget that will allow users to clock-in and clock-out at specific times.

    首先,我们对导航栏进行了一些更改。 我们已经添加了UI Bootstrap提供的timepicker指令,并为其设置了一些默认参数。 timepicker指令为我们提供了一个不错的小部件,它允许用户在特定时间进行timepicker出。
  • We've also added a field for users to include comments with their time entries. You'll see that for all of these we have specified names for them on ng-model and that they are prefaced with vm. This is because we are using the controller as syntax and need an alias for our View Model (you can use anything as an alias, but we'll stick to vm for now).

    我们还为用户添加了一个字段,以便在其时间条目中包含评论。 您会看到,对于所有这些,我们都在ng-model上为其指定了名称,并且以vm开头。 这是因为我们使用controller as语法,并且需要为我们的视图模型使用别名(您可以使用任何东西作为别名,但是现在我们将坚持使用vm )。
  • On our "Log Time" button we have specified that a method called logNewTime is called when the button is clicked. We haven't defined this method yet, but we will in the next steps.

    在“日志时间”按钮上,我们指定了单击按钮时将调用名为logNewTime的方法。 我们尚未定义此方法,但我们将在后续步骤中进行定义。

Below the navbar, we are setting up an ng-repeat to display the time entries. You'll see that for the date of the time entry we are taking the end_date and formatting it with an Angular date filter.

在导航栏下方,我们正在设置ng-repeat以显示时间条目。 您会看到,对于时间输入的日期,我们将使用end_date并使用Angular日期过滤器对其进行格式化。

If everything is wired up correctly, you should see our entries displayed:

如果一切都正确连接,您应该看到显示的条目:

time-tracker-1-3

Things are looking good so far, but we aren't actually displaying the amount of time that is involved for each time entry. In the next section we're going to get the hour and minute calculations setup with Moment.js, after which we can update our app to display the amount of time logged.

到目前为止,情况看起来不错,但实际上并没有显示每次输入所涉及的时间。 在下一节中,我们将使用Moment.js进行小时和分钟的计算设置,此后,我们可以更新我们的应用程序以显示记录的时间量。

用Moment.js计算时间 (Calculating Time with Moment.js)

You might be wondering why we don't bother just storing a calculated value for the total amount of time in each time entry. This is a good question, and we could certainly set things up this way; however, when it comes to database schemas, it's a best practice to not store calculated values. If we simply store the start and end times, we have more flexibility when it comes to updating the database schema or when we make edits to our time entries. Say, for instance, that we wanted to update the start time for a given entry. If we store calculated values, we would have to write additional logic that updates the total time once our start time is updated. It is much simpler if we calculate things on the fly.

您可能想知道为什么我们不花时间在每个时间条目中存储总时间的计算值。 这是一个很好的问题,我们当然可以通过这种方式进行设置。 但是,对于数据库模式,最好的做法是不存储计算值。 如果仅存储开始时间和结束时间,则在更新数据库架构或对时间条目进行编辑时,我们将具有更大的灵活性。 举例来说,假设我们要更新给定条目的开始时间。 如果存储计算的值,则必须编写其他逻辑,以在开始时间更新后更新总时间。 如果我们即时进行计算,则要简单得多。

时差和总时间 (Time Diff and Total Time)

Moment.js gives us some great tools, and for this task we want the diff and duration methods. Let's setup two new methods in our time service, one for getting the time difference and the other for getting the total time:

Moment.js为我们提供了一些出色的工具,为此,我们需要diffduration方法。 让我们在time服务中设置两种新方法,一种用于获取时差,另一种用于获取总时间:

/* scripts/services/time.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .factory('time', time);

        function time($resource) {

            // ngResource call to our static data
            var Time = $resource('data/time.json');

            // $promise.then allows us to intercept the results of the 
            // query so we can add the loggedTime property
            function getTime() {
                return Time.query().$promise.then(function(results) {
                    angular.forEach(results, function(result) {

                        // Add the loggedTime property which calls 
                        // getTimeDiff to give us a duration object
                        result.loggedTime = getTimeDiff(result.start_time, result.end_time);
                    });
                    return results;
                }, function(error) { // Check for errors
                    console.log(error);
                });
            }

            // Use Moment.js to get the duration of the time entry
            function getTimeDiff(start, end) {
                var diff = moment(end).diff(moment(start));
                var duration = moment.duration(diff);
                return {
                    duration: duration
                }
            }

            // Add up the total time for all of our time entries
            function getTotalTime(timeentries) {
                var totalMilliseconds = 0;

                angular.forEach(timeentries, function(key) {
                    totalMilliseconds += key.loggedTime.duration._milliseconds;
                });

                // After 24 hours, the Moment.js duration object
                // reports the next unit up, which is days.
                // Using the asHours method and rounding down with
                // Math.floor instead gives us the total hours
                return {
                    hours: Math.floor(moment.duration(totalMilliseconds).asHours()),
                    minutes: moment.duration(totalMilliseconds).minutes()
                }
            }

            return {
                getTime: getTime,
                getTimeDiff: getTimeDiff,
                getTotalTime: getTotalTime
            }
        }
})();

Our getTimeDiff method has two parameters, a start time and an end time, which will be the times at which the user clocks in and clocks out. To find the difference between the start and end times, we use Moment's diff method, which returns the total time in milliseconds. We could work with the total number of milliseconds for each time entry and do the math to get our total number of hours, but that could get cumbersome. Instead, let's use Moment's duration method. It will return an object containing a nice breakdown of the total time spent on the task.

我们的getTimeDiff方法有两个参数,一个开始时间和一个结束时间,这将是用户切入和切出的时间。 为了找到开始时间和结束时间之间的时差,我们使用Moment的diff方法,该方法返回以毫秒为单位的总时间。 我们可以计算每次输入的总毫秒数,并进行数学运算以获得小时总数,但这可能会很麻烦。 相反,让我们使用Moment的duration方法。 它将返回一个对象,其中包含花费在该任务上的总时间的详细细分。

time-tracker-1-4

Finally, we return an object with a duration key that equals the duration we just derived. When we exceed 24 hours worth of time entries, the Moment.js duration object will report the next unit up, which is days. We could use this unit, but I think it's a bit nicer to work with hours as the highest unit, so to get around this we use the asHours method that Moment.js offers. We use Math.floor to round it down and then keep using minutes from the duration object to report the number of minutes.

最后,我们返回一个带有持续时间键的对象,该键等于我们刚刚得出的持续时间。 当我们输入的时间超过24小时时,Moment.js持续时间对象将报告下一个单位,即天。 我们可以使用这个单位,但是我认为将小时作为最高单位会更好一些,因此为了解决这个问题,我们使用Moment.js提供的asHours方法。 我们使用Math.floor舍下来,然后继续使用minutes从时间对象报告的分钟数。

You'll also notice that we've amended the getTime method with an angular.forEach loop. This loop allows us to intercept the results that are returned from our call to the static JSON file and add in a new property called loggedTime, which is equal to the result of a call to our new getTimeDiff method. We pass in the start and end times found in the static data to the getTimeDiff method.

您还将注意到,我们已经用angular.forEach循环修改了getTime方法。 此循环使我们可以拦截从对静态JSON文件的调用返回的结果,并添加一个名为loggedTime的新属性,该属性等于对新的getTimeDiff方法的调用的结果。 我们将在静态数据中找到的开始时间和结束时间传递给getTimeDiff方法。

Finally, we want to have a way to calculate the total time that all of our time entries make up. To do this, we create a getTotalTime method that accepts our timeentries array, uses angular.forEach to loop through them, and adds the number of milliseconds from each to a variable called totalMilliseconds. Since we'll want access to both the number of hours and number of minutes, we return an object with keys for each. We once again use Moment's duration method here, but in this case we ask for access directly to hours and minutes.

最后,我们希望有一种方法来计算所有时间条目所占的总时间。 为此,我们创建了一个getTotalTime方法,该方法接受我们的timeentries数组,使用angular.forEach遍历它们,并将每个毫秒数添加到名为totalMilliseconds的变量中。 因为我们要访问小时数和分钟数,所以我们返回一个带有键的对象。 我们在这里再次使用Moment的duration方法,但是在这种情况下,我们要求直接访问hoursminutes

视图中的总时间 (Total Time in the View)

Now that we have methods in place to calculate our time, let's put them to use in the view.

现在我们已经有了计算时间的方法,现在让我们在视图中使用它们。

<!-- index.html --> 
... 
<div class="container">
    <div class="col-sm-8">
        <div class="well timeentry" ng-repeat="time in vm.timeentries">
            <div class="row">
                <div class="col-sm-8">
                    <h4><i class="glyphicon glyphicon-user"></i> 
                    {{time.user_firstname}} {{time.user_lastname}}</h4>
                    <p><i class="glyphicon glyphicon-pencil"></i> {{time.comment}}</p>
                </div>
                <div class="col-sm-4 time-numbers">
                    <h4><i class="glyphicon glyphicon-calendar"></i> 
                    {{time.end_time | date:'mediumDate'}}</h4>
                    <h2>
                        <span class="label label-primary" 
                              ng-show="time.loggedTime.duration._data.hours > 0">
                              {{time.loggedTime.duration._data.hours}} hour
                              <span ng-show="time.loggedTime.duration._data.hours > 1">s</span>
                        </span>
                    </h2>
                    <h4><span class="label label-default">
                    {{time.loggedTime.duration._data.minutes}} minutes</span></h4>
                </div>
            </div>
        </div>
    </div>

    <div class="col-sm-4">
        <div class="well time-numbers">
            <h1><i class="glyphicon glyphicon-time"></i> Total Time</h1>
            <h1><span class="label label-primary">{{vm.totalTime.hours}} hours</span></h1>
            <h3><span class="label label-default">{{vm.totalTime.minutes}} minutes</span></h3>
        </div>
    </div>
</div>    
...

We've made a few changes to the view to reflect our time calculations. Let's first take a look at the new h2 and h4 tags that have been added into the time-numbers div. These are used to display the number of hours and minutes that each time entry makes up. For the number of hours, we're putting some checks in place to determine whether we need to display the hour span, and then whether we need to pluralize the word "hour".

我们对视图进行了一些更改以反映我们的时间计算。 首先让我们看一下已添加到time-numbers div中的新h2h4标签。 这些用于显示每个时间条目组成的小时和分钟数。 对于小时数,我们将进行一些检查以确定是否需要显示小时span ,然后是否需要对单词“ hour”进行复数。

Next, we've added in another box to the right which gives us the total number of hours that all of the displayed time entries make up. However, if you run this right now you'll see that there are no values for the total time box, and that's because we still need to complete this functionality in the controller.

接下来,我们在右侧添加了另一个框,该框为我们提供了所有显示的时间条目组成的总小时数。 但是,如果立即运行此功能,您将看到“总时间”框没有任何值,这是因为我们仍然需要在控制器中完成此功能。

time-tracker-1-5

完成控制器 (Finishing Up the Controller)

There are just a couple more pieces left to finish out in the controller before we have a working app. First, we need an object called totalTime that is responsible for tallying up the time from each time entry. Second, we need to complete the logNewTime method that we mentioned earlier. This method is called when we click the "Log Time" button up in the navbar.

在我们有一个可运行的应用程序之前,还需要完成几项工作才能在控制器中完成。 首先,我们需要一个名为totalTime的对象,该对象负责计算每次输入的时间。 其次,我们需要完成前面提到的logNewTime方法。 当我们单击导航栏中的“登录时间”按钮时,将调用此方法。

Let's handle the time aggregation by putting in a method called updateTotalTime:

让我们通过放入一个名为updateTotalTime的方法来处理时间聚合:

/* scripts/controllers/TimeEntry.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .controller('TimeEntry', TimeEntry);

        function TimeEntry(time) {

            // vm is our capture variable
            var vm = this;

            vm.timeentries = [];

            vm.totalTime = {};

            // Fetches the time entries from the static JSON file
            // and puts the results on the vm.timeentries array
            time.getTime().then(function(results) {
                vm.timeentries = results;
                updateTotalTime(vm.timeentries);            
            }, function(error) { // Check for errors
                console.log(error);
            });

            // Updates the values in the total time box by calling the
            // getTotalTime method on the time service
            function updateTotalTime(timeentries) {
                vm.totalTime = time.getTotalTime(timeentries);
            }
        }

})();

You'll see that we've added an updateTotalTime method at the bottom. This method takes the timeentries array as an argument and updates the newly declared vm.totalTime object to equal the result of a call to the getTotalTime method from the time service. Like we saw earlier, the getTotalTime method from the time service takes the array of time entries and loops through to count the total number of milliseconds from each. It then aggregates all that time and uses Moment.js to find and return the total number of hours and minutes.

您会看到我们在底部添加了updateTotalTime方法。 此方法将timeentries数组作为参数,并更新新声明的vm.totalTime对象,使其等于从time服务调用getTotalTime方法的结果。 就像我们之前看到的, time服务中的getTotalTime方法获取时间条目数组,并循环遍历每个时间条目的毫秒总数。 然后,它将汇总所有时间,并使用Moment.js查找并返回小时数和分钟数的总数。

If we refresh the page we now see that the total number of hours and minutes are being displayed properly:

如果刷新页面,我们现在可以看到小时和分钟总数已正确显示:

time-tracker-1-6

Finally, let's add in the logNewTime method that will handle new time entries. Because we are working with static JSON data for demo purposes here, all we need to do to update the list is push a new object onto the already existing timeentries array. This is a temporary shim that will no longer be necessary once we get database persistence with Laravel working.

最后,让我们添加将处理新时间条目的logNewTime方法。 因为我们在这里使用静态JSON数据进行演示,所以更新列表所需要做的就是将一个新对象推送到已经存在的timeentries数组中。 这是一个临时的填充程序,一旦我们在Laravel工作的情况下实现了数据库持久性,就不再需要它了。

Here is the finished TimeEntry controller:

这是完成的TimeEntry控制器:

/* scripts/controllers/TimeEntry.js */

(function() {

    'use strict';

    angular
        .module('timeTracker')
        .controller('TimeEntry', TimeEntry);

        function TimeEntry(time) {

            // vm is our capture variable
            var vm = this;

            vm.timeentries = [];

            vm.totalTime = {};

            // Initialize the clockIn and clockOut times to the current time.
            vm.clockIn = new Date();
            vm.clockOut = new Date();

            // Fetches the time entries from the static JSON file
            // and puts the results on the vm.timeentries array
            time.getTime().then(function(results) {
                vm.timeentries = results;
                updateTotalTime(vm.timeentries);            
            }, function(error) { // Check for errors
                console.log(error);
            });

            // Updates the values in the total time box by calling the
            // getTotalTime method on the time service
            function updateTotalTime(timeentries) {
                vm.totalTime = time.getTotalTime(timeentries);
            }

            // Submits the time entry that will be called 
            // when we click the "Log Time" button
            vm.logNewTime = function() {

                // Make sure that the clock-in time isn't after
                // the clock-out time!
                if(vm.clockOut < vm.clockIn) {
                    alert("You can't clock out before you clock in!");
                    return;                 
                }

                // Make sure the time entry is greater than zero
                if(vm.clockOut - vm.clockIn === 0) {
                    alert("Your time entry has to be greater than zero!");
                    return;
                }

                vm.timeentries.push({
                    "user_id":1,
                    "user_firstname":"Ryan",
                    "user_lastname":"Chenkie",
                    "start_time":vm.clockIn,
                    "end_time":vm.clockOut,
                    "loggedTime": time.getTimeDiff(vm.clockIn, vm.clockOut),
                    "comment":vm.comment
                });

                updateTotalTime(vm.timeentries);

                vm.comment = "";
            }
        }

})();

There are few things going on in the logNewTime method, so let's break it down.

logNewTime方法中发生的事情很少,因此让我们对其进行分解。

Firstly, we want to make sure that new time entries make sense---the user shouldn't be able to enter a clock-in time that is after the clock-out time. For that, we put in a simple conditional that checks whether the clock-in time is after the clock-out time, and if it is, alerts the user of the error. We're also checking to make sure the user has actually logged some time and that they haven't just left the clock-in and clock-out times the same.

首先,我们要确保新的时间输入有意义-用户不应输入超出超时时间的进入时间。 为此,我们放置了一个简单的条件,该条件检查签入时间是否在签出时间之后,如果是,则向用户发出错误提示。 我们还在检查以确保用户实际上已经记录了一些时间,并且他们没有使输入和输出时间保持相同。

Next, we push a new object onto our time entries array. Notice here that we're specifying that start_time and end_time are equal to vm.clockIn and vm.clockOut respectively. These values come directly from the timepicker directives we used in our view. Because of the way the timepicker directive works, if the user doesn't make a change to the clock-in and clock-out times, it will give null values. This can cause problems, so to get around it we are initializing vm.clockIn and vm.clockOut to the current date.

接下来,我们将一个新对象推入我们的时间条目数组。 请注意,这里我们指定的是start_timeend_time分别等于vm.clockInvm.clockOut 。 这些值直接来自我们在视图中使用的timepicker指令。 由于timepicker指令的工作方式,如果用户不更改输入和输出时间,它将给出null值。 这可能会导致问题,因此要解决此问题,我们将vm.clockInvm.clockOut初始化为当前日期。

If you look again at the data we started with in the static time.json file, you'll notice that there is no loggedTime property. This is because we have things setup in the time service to add this property on as the data is read. Now that we're adding new time entries in after the initial data is loaded, we'll need to specify this property. We can reuse the getTimeDiff method here, passing in the clock-in and clock-out times.

如果再次查看我们在static time.json文件中开始使用的数据,您会注意到没有任何loggedTime属性。 这是因为我们在time服务中进行了一些设置,以便在读取数据时添加此属性。 现在,我们要在加载初始数据后添加新的时间条目,我们需要指定此属性。 我们可以在此处重用getTimeDiff方法,传入传入和传出时间。

Finally, we make a call to our updateTotalTime method so that the total time box reflects the right amount of time. We also want to clear the comment field so that it is ready for the next time entry.

最后,我们调用updateTotalTime方法,以便总时间框反映正确的时间量。 我们还希望清除注释字段,以便下次输入时可以使用。

结语 (Wrapping Up)

There we go---we've got a good start at a time tracking application! There are, however, some obvious limitations with the app as it is now:

我们走了-在一个时间跟踪应用程序上我们有了一个良好的开端! 但是,现在的应用程序存在一些明显的局限性:

  • We don't have any database persistence

    我们没有任何数据库持久性
  • We're hard-coding in the name of the user when we add new time entries

    添加新的时间条目时,我们会在用户名中进行硬编码
  • We don't have a way to edit or delete the time entries

    我们没有办法编辑或删除时间条目

We're going to fix these problems in the next part of this tutorial when we get a Laravel 5 backend running. We'll have Laravel expose a RESTful API and let our Angular front-end consume it. We'll also provide a way to log time as a specific user and edit or delete time entries. Stay tuned for more!

当我们运行Laravel 5后端时,我们将在本教程的下一部分中解决这些问题。 我们将让Laravel公开一个RESTful API,并让我们的Angular前端使用它。 我们还将提供一种以特定用户身份记录时间以及编辑或删除时间条目的方法。 敬请期待更多!

给我留言 (Drop me a Line)

If you’d like to get more AngularJS and Laravel tutorials, feel free to head over to my website and signup for my mailing list. You should follow me on Twitter---I'd love to hear about what you're working on!

如果您想获得更多AngularJS和Laravel教程,请随时访问我的网站注册我的邮件列表 。 您应该在Twitter上关注我---我很想知道您在做什么!

翻译自: https://scotch.io/tutorials/build-a-time-tracker-with-laravel-5-and-angularjs-part-1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值