本文共 44413 字,大约阅读时间需要 148 分钟。
This is the second of a two-part series on using Laravel 5 and AngularJS together to build a simple time tracking application. If you've gone through , you'll have seen that we put together the front-end first and used a simple JSON file with some mocked-up data to test with. We left off with the ability to add new time entries and have the total time from all of them display on the side. We didn't include any way to edit or delete the time entries, and of course there was no persistence to a database.
这是将Laravel 5和AngularJS一起使用以构建简单的时间跟踪应用程序的两部分系列的第二部分。 如果您已经读完了 ,您将看到我们首先将前端放在一起,并使用了一个简单的JSON文件和一些模拟数据进行测试。 我们放弃了添加新时间条目的功能,并在侧面显示了所有时间的总时间。 我们没有提供任何编辑或删除时间条目的方法,当然,数据库也没有持久性。
In this part we will complete the application so that the time entries get stored in a database and our Angular front-end and Laravel backend work together to create, read, update and delete from it.
在这一部分中,我们将完成应用程序,以便将时间条目存储在数据库中,并且我们的Angular前端和Laravel后端可以一起创建,读取,更新和删除数据库。
To get us started, let's grab a fresh install of Laravel. We won't go into great detail about the Laravel installation process here, and if you're using Laravel for the first time there might be some snags you run into when setting it up. The are the best place to go to get full instructions on installation.
为了让我们开始,我们来重新安装Laravel。 我们不会在这里详细介绍Laravel的安装过程,如果您是第一次使用Laravel,在安装时可能会遇到一些麻烦。 是获得完整安装说明的最佳去处。
In the terminal, navigate to where your localhost files are served from. Assuming you have Composer and the Laravel installer properly installed globally, from the command line:
在终端中,导航到提供localhost文件的位置。 假设您已经从命令行正确地全局安装了Composer和Laravel安装程序:
laravel new time-tracker-2
This command will create a new directory called time-tracker-2
and will populate it with Laravel 5 files. It also does a lot of the initial setup work for us, like setting the application key. With Laravel's Artisan CLI we can spin up a web server. Let's do that now to make sure the application is working. From the command line,
该命令将创建一个名为time-tracker-2
的新目录,并使用Laravel 5文件填充该目录。 它还为我们完成了许多初始设置工作,例如设置应用程序密钥。 使用Laravel的Artisan CLI,我们可以启动Web服务器。 现在让我们这样做,以确保该应用程序正常运行。 在命令行中
cd time-tracker-2php artisan serve
If everything is working properly, you should see the Laravel 5 welcome page:
如果一切正常,您应该看到Laravel 5欢迎页面:
For this tutorial we're going to use a SQLite database for the sake of simplicity. SQLite is a local database that sits on the filesystem and gives us a quick and flexible way of storing data without the need for connecting to an external database. You might not have used SQLite before, so if you feel more comfortable with MySQL or even something else, feel free to change the database up as you like. All of the Laravel code we write to run migrations, seed the database, and setup relationships will work across all databases that Laravel supports.
在本教程中,为简单起见,我们将使用SQLite数据库。 SQLite是位于文件系统上的本地数据库,它使我们能够快速灵活地存储数据,而无需连接到外部数据库。 您可能以前没有使用过SQLite,因此,如果您对MySQL甚至其他方面感到更满意,请随时更改数据库。 我们编写的用于运行迁移,播种数据库和建立关系的所有Laravel代码将在Laravel支持的所有数据库中工作。
Navigate to the time-tracker-2/storage
folder and add a new sqlite database. I like to do this from my Sublime editor by right-clicking the folder, then selecting new file
. Save the file and name it database.sqlite
.
导航到time-tracker-2/storage
文件夹并添加一个新的sqlite数据库。 我喜欢通过Sublime编辑器执行此操作,方法是右键单击文件夹,然后选择new file
。 保存该文件并将其命名为database.sqlite
。
Next, let's modify the database config file to let Laravel know that we're using SQLite. In congfig/database.php
, switch this line:
接下来,让我们修改数据库配置文件,以使Laravel知道我们正在使用SQLite。 在congfig/database.php
,切换此行:
'default' => 'mysql',
for this:
为了这:
'default' => 'sqlite',
Since we are using a database stored on our filesystem, we don't need to update the .env
file with any database user credentials. If, however, you are using MySQL, you'll need to modify it so that your local web server's database user is recognized. To do so, open time-tracker-2/.env
and modify the credentials. These are the ones that come with the Laravel installation:
由于我们使用的是存储在文件系统上的数据库,因此不需要使用任何数据库用户凭据来更新.env
文件。 但是,如果您使用的是MySQL,则需要对其进行修改,以便识别本地Web服务器的数据库用户。 为此,打开time-tracker-2/.env
并修改凭据。 这些是Laravel安装随附的软件:
DB_HOST=localhostDB_DATABASE=homesteadDB_USERNAME=homesteadDB_PASSWORD=secret
If you're used to using phpMyAdmin for MySQL, you'll likely want to have a similar tool for viewing your SQLite database. There is a Firefox add-on called which is great for this job.
如果您习惯使用MySQL的phpMyAdmin,则可能需要一个类似的工具来查看SQLite数据库。 有一个名为的Firefox插件,非常适合此工作。
Now that the database is setup, the first thing we'll want to do is run some migrations to setup our tables, and also seed the database with some initial data.
现在已经建立了数据库,我们要做的第一件事是运行一些迁移来建立我们的表,并为数据库提供一些初始数据。
If you look in the database/migrations
folder, you'll see that Laravel comes with two migrations---one that creates a users table and another that creates a password reset table. We'll use the default migrations, but let's make a small change to the columns used for storing the user's name in the users
table migration so that both first name and last name are stored separately:
如果查看database/migrations
文件夹,您会看到Laravel附带了两个迁移-一个创建用户表,另一个创建密码重置表。 我们将使用默认迁移,但是让我们对users
表迁移中用于存储用户名的列进行一些小的更改,以便将名字和姓氏分别存储:
// database/migrations/2014_10_12_000000_create_users_table.php...// the up method creates the below fields in our users tablepublic function up(){ Schema::create('users', function(Blueprint $table) { $table->increments('id'); $table->string('first_name'); $table->string('last_name'); $table->string('email')->unique(); $table->string('password', 60); $table->rememberToken(); $table->timestamps(); });}...
Next, let's create a migration for the time_entries
table. A cool feature in Laravel 5 is that when you use Artisan to create a model, it will automatically create a migration for that model for you. Let's hit two birds with one stone here. From the command line:
接下来,让我们为time_entries
表创建迁移。 Laravel 5的一个很酷的功能是,当您使用Artisan创建模型时,它将自动为您创建该模型的迁移。 让我们在这里用一块石头打两只鸟。 在命令行中:
php artisan make:model TimeEntries
Let's specify in our TimeEntry
model that we want it to use the time_entries
table.
让我们在我们的TimeEntry
模型中指定我们希望它使用time_entries
表。
// app/TimeEntry.php...use IlluminateDatabaseEloquentModel;class TimeEntry extends Model { // Use the time_entries table protected $table = 'time_entries';}...
We'll need to specify the fields we want to add to the table in the migration script.
我们需要在迁移脚本中指定要添加到表中的字段。
// database/migrations/0000_00_00_000000_create_time_entries_table.php...use IlluminateDatabaseSchemaBlueprint;use IlluminateDatabaseMigrationsMigration;class CreateTimeEntriesTable extends Migration { // the up method creates the below fields in our time entries table public function up() { Schema::create('time_entries', function(Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->dateTime('start_time'); $table->dateTime('end_time'); $table->string('comment')->nullable(); $table->timestamps(); }); } public function down() { Schema::drop('time_entries'); }}
Next, let's run the migrations. From the command line:
接下来,让我们运行迁移。 在命令行中:
php artisan migrate
If everything worked, you should see that the migrations were created successfully. We can also check SQLite Manager to be sure:
如果一切正常,您应该看到迁移已成功创建。 我们还可以检查SQLite Manager以确保:
We'll need to provide some intial data to work with, much like we did in the first part of this series, only this time it will be in a database instead of a static JSON file. Laravel offers a great method for seeding databases, and we'll make use of it here. Let's modify the already existing database/seeds/DatabaseSeeder.php
file.
我们需要提供一些初始数据,就像我们在本系列第一部分中所做的一样,只是这一次它将在数据库中,而不是静态JSON文件中。 Laravel为种子数据库提供了一种很好的方法,我们将在这里使用它。 让我们修改已经存在的database/seeds/DatabaseSeeder.php
文件。
// database/seeds/DatabaseSeeder.php...use IlluminateDatabaseSeeder;use IlluminateDatabaseEloquentModel;use AppUser;use AppTimeEntry;class DatabaseSeeder extends Seeder { public function run() { Model::unguard(); // Call the seed classes to run the seeds $this->call('UsersTableSeeder'); $this->call('TimeEntriesTableSeeder'); }}class UsersTableSeeder extends Seeder { public function run() { // We want to delete the users table if it exists before running the seed DB::table('users')->delete(); $users = array( ['first_name' => 'Ryan', 'last_name' => 'Chenkie', 'email' => 'ryanchenkie@gmail.com', 'password' => Hash::make('secret')], ['first_name' => 'Chris', 'last_name' => 'Sevilleja', 'email' => 'chris@scotch.io', 'password' => Hash::make('secret')], ['first_name' => 'Holly', 'last_name' => 'Lloyd', 'email' => 'holly@scotch.io', 'password' => Hash::make('secret')], ['first_name' => 'Adnan', 'last_name' => 'Kukic', 'email' => 'adnan@scotch.io', 'password' => Hash::make('secret')], ); // Loop through each user above and create the record for them in the database foreach ($users as $user) { User::create($user); } }}class TimeEntriesTableSeeder extends Seeder { public function run() { DB::table('time_entries')->delete(); $time_entries = array( ['user_id' => 1, 'start_time' => '2015-02-21T18:56:48Z', 'end_time' => '2015-02-21T20:33:10Z', 'comment' => 'Initial project setup.'], ['user_id' => 2, 'start_time' => '2015-02-27T10:22:42Z','end_time' => '2015-02-27T14:08:10Z','comment' => 'Review of project requirements and notes for getting started.'], ['user_id' => 3, 'start_time' => '2015-03-03T09:55:32Z','end_time' => '2015-03-03T12:07:09Z','comment' => 'Front-end and backend setup.'], ); foreach($time_entries as $time_entry) { TimeEntry::create($time_entry); } }}
With database seeding, we can either break the seeds for each table into separate files, or just pile them all into one big file. If you are doing a lot of seeding, I'd certainly recommend breaking them out, but we'll just put everything in one file in our case. You'll see that we're creating an array with a list of names, along with email addresses and a hashed password. We then loop through the array and populate the database with the data. The same thing goes for the time_entries
table, only that the data structure is different. Up at the top in the run
function, we are calling on the seeder classes.
使用数据库种子,我们可以将每个表的种子分解为单独的文件,也可以将它们全部堆积为一个大文件。 如果您正在做大量播种工作,我当然建议您将它们分解,但是我们只将所有内容放在一个文件中。 您会看到我们正在创建一个包含名称列表,电子邮件地址和哈希密码的数组。 然后,我们遍历数组,并使用数据填充数据库。 time_entries
表也有time_entries
,只是数据结构不同。 在run
函数的顶部,我们正在调用seeder类。
Let's run the seed. From the command line:
让我们播下种子。 在命令行中:
php artisan db:seed
Browsing back to SQLite Manager, we can see that the database has been seeded successfully:
回到SQLite Manager,我们可以看到数据库已经成功播种:
How that we've got our seed data in place, it's time to migrate all of the HTML, CSS, and Javascript we wrote in part 1. If you've worked with Laravel before, you'll likely know that there is a public
folder at the root of the project. Let's copy the bower_components
, data
, and scripts
folders and paste them right into the public
directory of our Laravel project. Then, let's copy css/style.css
and paste it into the css
folder.
我们如何获取种子数据,是时候迁移我们在第1部分中编写的所有HTML,CSS和Javascript了。如果您以前使用过Laravel,您可能会知道有一个public
文件夹位于项目的根目录。 让我们复制bower_components
, data
和scripts
文件夹,然后将它们直接粘贴到Laravel项目的public
目录中。 然后,让我们复制css/style.css
并将其粘贴到css
文件夹中。
There are a couple different ways to deal with the index file when making Angular and Laravel work together, but the way I like to do it is to create an index.php
file in the Laravel views folder, located at resources/views
, and then create a new route within routes.php
to handle the rendering.
使Angular和Laravel一起工作时,有两种处理索引文件的方法,但是我喜欢的方法是在位于resources/views
的Laravel views文件夹中创建一个index.php
文件,然后在routes.php
创建一个新路由来处理渲染。
Let's create a new file in resources/views
called index.php
and copy all of the index.html
content from part 1 into it. Note: You'll see that the other views are named with .blade.php
to make use of Laravel's Blade syntax. We won't be using Blade, so we'll just use standard naming.
让我们在resources/views
创建一个名为index.php
的新文件,并将第1部分中的所有index.html
内容复制到其中。 注意:您会看到其他视图都以.blade.php
命名,以使用Laravel的Blade语法。 我们不会使用Blade,因此我们将仅使用标准命名。
Time Tracker { {time.user_firstname}} { {time.user_lastname}}
{ {time.comment}}
{ {time.end_time | date:'mediumDate'}}
{ {time.loggedTime.duration._data.hours}} hours
{ {time.loggedTime.duration._data.minutes}} minutes
Total Time
{ {vm.totalTime.hours}} hours
{ {vm.totalTime.minutes}} minutes
Next, let's add a route to routes.php
to handle this view, and remove the already existing route that renders the welcome view.
接下来,让我们向routes.php
添加一条路由来处理此视图,并删除呈现欢迎视图的现有路由。
// app/Http/routes.php...Route::get('/', function() { return view('index');});// Remove or comment this out// Route::get('/', 'WelcomeController@index');...
What we've done here is registered a route such that when a GET
request is made to the default route of the site (signified by '/'), Laravel will render the index
view, which we have in our index.php
file. Let's make sure the page is being displayed as it should. Make sure you still have the local web server fired up, and head to the page. For me, it's at localhost:8000. If everything is working, you should see the application we had from part 1:
我们在这里所做的是注册一条路由,以便当对站点的默认路由(以'/'表示)进行GET
请求时,Laravel将呈现index
视图,该视图位于我们的index.php
文件中。 让我们确保页面显示正确。 确保仍启动本地Web服务器,然后转到页面。 对我而言,它位于localhost:8000。 如果一切正常,您应该看到第1部分中的应用程序:
Awesome, we're done migrating the front-end from part 1. However, the data that is being displayed is still from the static JSON file. Let's fix that in the next section.
太棒了,我们已经从第1部分迁移了前端。但是,正在显示的数据仍然来自静态JSON文件。 让我们在下一节中修复它。
There is a bit more we need to do with our TimeEntry
model before we can proceed. By default, Laravel protects against , so we'll need to tell the model which fields are fillable. We'll also need to setup an Eloquent relationship so that our TimeEntry
model talks to the User
model.
在继续之前,我们还需要处理TimeEntry
模型。 默认情况下,Laravel防止 ,因此我们需要告诉模型哪些字段是可填充的。 我们还需要设置一个Eloquent关系,以便我们的TimeEntry
模型与User
模型对话。
// app/TimeEntry.php...use IlluminateDatabaseEloquentModel;class TimeEntry extends Model { protected $table = "time_entries"; // An array of the fields we can fill in the time_entries table protected $fillable = ['user_id', 'start_time', 'end_time', 'comment']; protected $hidden = ['user_id']; // Eloquent relationship that says one user belongs to each time entry public function user() { return $this->belongsTo('AppUser'); }}
An Eloquent relationship is setup by creating a new method called user
that calls belongsTo
on the User
model (namespaced here with AppUser
). Setting up relationships with Eloquent is a whole other article, but using belongsTo
here means that we're going to have our TimeEntry
model use it's user_id
and look up that id in the users
table to match up time entries with users.
通过创建一个名为user
的新方法来建立belongsTo
,该方法在User
模型上(在此使用AppUser
命名)来调用belongsTo
。 与belongsTo
建立关系是另一篇完整的文章,但是在这里使用belongsTo
意味着我们将让我们的TimeEntry
模型使用它的user_id
并在users
表中查找该id以与users
匹配时间条目。
For the sake of reducing unnecessary data in our API, we're also hiding the user_id
field from being returned since we won't need it for the app to function as it should.
为了减少API中不必要的数据,我们还隐藏了user_id
字段,以免其返回,因为我们不需要它来使应用程序正常运行。
The User
model is already setup for us out of the box with Laravel, but I like to hide the created_at
and updated_at
fields since we won't require them on the front-end:
Laravel已经为我们提供了User
模型的现成设置,但是我想隐藏created_at
和updated_at
字段,因为我们不需要在前端使用它们:
// app/User.php...protected $hidden = ['password', 'remember_token', 'created_at', 'updated_at'];...
To pull data from our database, we'll first need to setup our API. Laravel offers a very easy way to create a REST API along with the resource controllers necessary for it. We won't get into details about what REST is in this article, but if you are new to the concept, you should check out .
要从数据库中提取数据,我们首先需要设置我们的API。 Laravel提供了一种非常轻松的方法来创建REST API及其所需的资源控制器。 我们不会在本文中详细介绍什么是REST,但是如果您是不的概念,则应该查看 。
First, let's define a route group and resources within it in routes.php
.
首先,让我们在routes.php
定义一个路由组和其中的资源。
// app/Http/routes.php...// A route group allows us to have a prefix, in this case apiRoute::group(array('prefix' => 'api'), function(){ Route::resource('time', 'TimeEntriesController'); Route::resource('users', 'UsersController');});
Our RESTful controllers are going to respond to HTTP requests that we make to various routes. It's a good practice to prefix the group of routes that we will send requests to with something that signifies that it is an API. In our case, we'll just use the prefix api
, but you could also prefix it with a version number.
我们的RESTful控制器将响应我们对各种路由发出的HTTP请求。 优良作法是在发送请求的路由组之前添加一些前缀,以表示这是API。 在本例中,我们将仅使用前缀api
,但您也可以为其添加版本号。
Within this api
route group, we are defining two resources. The first is a time
resource, which will be for our time entries. The other is a users
resource, which will make use of the users
table to give us options for who the time entries are assigned to.
在此api
路由组中,我们定义了两个资源。 首先是time
资源,它将用于我们的时间输入。 另一个是users
资源,它将利用users
表为我们提供时间条目分配给谁的选项。
Now that we have our route groups and resources, let's use Artisan to create our resource controllers. From the command line:
现在我们有了路由组和资源,让我们使用Artisan创建我们的资源控制器。 在命令行中:
php artisan make:controller TimeEntriesController
php artisan make:controller UsersController
If you now navigate to app/Http/Controllers
, you should see that TimeEntriesController.php
and UsersController.php
have been created.
如果现在导航到app/Http/Controllers
,则应该看到已经创建了TimeEntriesController.php
和UsersController.php
。
When we register route resources, Laravel will enumerate the endpoints and we can view them using Artisan. From the command line:
注册路线资源时,Laravel将枚举端点,我们可以使用Artisan进行查看。 在命令行中:
php artisan route:list
This will return a list of all the defined routes along with which HTTP verbs they respond to. Since we've registered our API resource routes, you should see them listed:
这将返回所有已定义路由的列表以及它们响应的HTTP动词。 由于我们已经注册了API资源路由,因此您应该在列表中看到它们:
The first thing we'll want to do with the TimeEntriesController
is make it return all of our time entries. Let's use the index
method to accomplish that:
我们要对TimeEntriesController
进行的第一件事是使其返回我们所有的时间条目。 让我们使用index
方法来实现这一点:
// app/Http/Controllers/TimeEntriesController.php...use AppHttpRequests;use AppHttpControllersController; use AppTimeEntry;use IlluminateSupportFacadesRequest;class TimeEntriesController extends Controller { // Gets time entries and eager loads their associated users public function index() { $time = TimeEntry::with('user')->get(); return $time; }...
The index
method is usually responsibile for displaying all of the data for a given resource, and that's just what we're doing with it here. We are using to get all the results from our time_entries
table along with the associated users and returning it. Note that we're also pulling in our TimeEntry
model at the top with a use
statement.
index
方法通常负责显示给定资源的所有数据,这就是我们在此所做的。 我们正在使用time_entries
从time_entries
表中获取所有结果以及相关联的用户,然后将其返回。 请注意,我们还将在顶部use
语句引入TimeEntry
模型。
Let's check to make sure we are getting all the results for this endpoint. I like the Chrome app which is a REST client to help test and debug REST APIs.
我们进行检查以确保获得该端点的所有结果。 我喜欢Chrome应用 ,它是一个REST客户端,可帮助测试和调试REST API。
If we make a GET
request to the api/time
endpoint, we should see a list of all our time entries from the database:
如果我们向api/time
端点发出GET
请求,则应该从数据库中看到所有时间条目的列表:
Let's also setup our UsersController
to display all users when we do a GET
request to the /users
endpoint:
我们还要设置UsersController
,以便在对/users
端点执行GET
请求时显示所有用户:
// app/Http/Controllers/UsersController.php...use AppHttpRequests;use AppHttpControllersController;use AppUser;use IlluminateHttpRequest;class UsersController extends Controller { // Gets all users in the users table and returns them public function index() { $users = User::all(); return $users; }...
Here you can see that we're doing the same thing as the TimeEntriesController
on the index method: using the model to get all of the users and then returning them. If I use Postman to hit the /users
endpoint, I see the data returned properly.
在这里您可以看到我们在index方法上所做的与TimeEntriesController
相同:使用模型获取所有用户,然后返回它们。 如果我使用Postman击中/users
端点,则可以看到正确返回的数据。
Now that we've got Laravel serving our API, let's update the front-end Anuglar code to make calls to it. The first thing we'll need to change is our time
service that currently makes a call to the static JSON file.
现在我们已经有了Laravel服务我们的API,让我们更新前端Anuglar代码以对其进行调用。 我们需要更改的第一件事是我们的time
服务,该服务当前正在调用静态JSON文件。
// public/scripts/services/time.js(function() { 'use strict'; angular .module('timeTracker') .factory('time', time); function time($resource) { // ngResource call to the API with id as a paramaterized URL // and setup for the update method var Time = $resource('api/time/:id', {}, { update: { method: 'PUT' } }); ...
You'll see here that we are again using ngResource to make a call for the data, but this time it is to our api/time
endpoint. We've also put in a bit of extra stuff here to set us up for later. For updating and deleting time entries, we are going to require the id
of the entry as a URL parameter so that we can properly communicate with our API, so we use :id
here as a URL template. The second argument that $resource
expects is an object for parameter defaults, but we don't need that in our case so we pass in an empty object. Finally you'll see that we are defining a custom action for updating our time entries. We call the custom action update
and specify that it uses the PUT
method.
您将在此处看到我们再次使用ngResource调用数据,但这一次是针对我们的api/time
端点。 我们还在这里添加了一些额外的东西,以备将来使用。 为了更新和删除时间条目,我们将要求条目的id
作为URL参数,以便我们能够正确地与我们的API通信,因此我们在此处使用:id
作为URL模板。 $resource
期望的第二个参数是参数默认值的对象,但是在我们的情况下我们不需要它,因此我们传入一个空对象。 最终,您将看到我们正在定义一个自定义动作来更新我们的时间条目。 我们调用自定义操作update
并指定它使用PUT
方法。
Let's check to see if that worked. If you refresh the page, you should see that we are still getting time entries but they are now coming from the API:
让我们检查一下是否可行。 如果刷新页面,您应该看到我们仍在获取时间条目,但现在它们来自API:
The only thing that is different here is that we aren't getting the names of the users for each time entry. This is because our data structure has changed a little bit from our test data. When we use eager loading with Laravel, it nests related data one level deeper than the rest of the data. Let's change up our view to reflect this new structure.
唯一不同的是,我们没有获得每次输入的用户名。 这是因为我们的数据结构与测试数据相比发生了一些变化。 当我们对Laravel使用急切加载时,它会将相关数据嵌套比其他数据深一层。 让我们改变看法以反映这种新结构。
...{ {time.user.first_name}} { {time.user.last_name}}
...
You should now be seeing the names displayed:
现在,您应该看到显示的名称:
Earlier we setup the UsersController
with Laravel and successfully retrieved a list of all users with the API using Postman. Now, let's setup an Anuglar service that will make a call for that data. The user
service will be similar to our time
service, but we'll make it be responsible only for retrieving the user data.
之前,我们使用Laravel设置了UsersController
,并使用Postman使用API成功检索了所有用户的列表。 现在,让我们设置一个Anuglar服务,该服务将调用该数据。 user
服务将类似于我们的time
服务,但我们将使其仅负责检索用户数据。
//public/scripts/services/user.js(function() { 'use strict'; angular .module('timeTracker') .factory('user', user); function user($resource) { // ngResource call to the API for the users var User = $resource('api/users'); // Query the users and return the results function getUsers() { return User.query().$promise.then(function(results) { return results; }, function(error) { console.log(error); }); } return { getUsers: getUsers } } })();
This looks quite similar to the first part of the time
service. We setup the factory, define User
which is a call to our API at the /users
endpoint, and then define a method that uses ngResource
to retrieve all the users. Finally, we return an object that has on it a key called getUsers
which references out getUsers
method.
这看起来与time
服务的第一部分非常相似。 我们设置工厂,定义User
(在/users
端点处对我们的API的调用),然后定义一个使用ngResource
检索所有用户的方法。 最后,我们返回一个对象,该对象上有一个名为getUsers
的键,该键引用了getUsers
方法。
We'll need to add the newly created user
service to the application scripts we call in the main view.
我们需要将新创建的user
服务添加到在主视图中调用的应用程序脚本中。
...
Let's now make use of this in the controller:
现在让我们在控制器中使用它:
// public/scripts/controllers/TimeEntry.js(function() { 'use strict'; angular .module('timeTracker') .controller('TimeEntry', TimeEntry); function TimeEntry(time, user, $scope) { var vm = this; vm.timeentries = []; vm.totalTime = {}; vm.users = []; // Initialize the clockIn and clockOut times to the current time. vm.clockIn = moment(); vm.clockOut = moment(); // Grab all the time entries saved in the database getTimeEntries(); // Get the users from the database so we can select // who the time entry belongs to getUsers(); function getUsers() { user.getUsers().then(function(result) { vm.users = result; }, function(error) { console.log(error); }); } // Fetches the time entries and puts the results // on the vm.timeentries array function getTimeEntries() { time.getTime().then(function(results) { vm.timeentries = results; updateTotalTime(vm.timeentries); console.log(vm.timeentries); }, function(error) { console.log(error); }); } ...
Notice here that we are injecting the user
service into our TimeEntry
method so that we can use it as a dependency. We then define a getUsers
method on the controller that allows us to make use of the user
service to grab a list of all users. Finally, we put the list of users we retrieve onto the vm.users
array.
请注意,这里我们将user
服务注入到我们的TimeEntry
方法中,以便可以将其用作依赖项。 然后,我们在控制器上定义一个getUsers
方法,该方法允许我们利用user
服务来获取所有用户的列表。 最后,我们将检索到的用户列表放入vm.users
数组。
You'll also notice that we've wrapped our call to time.getTime
from part 1 into a function that we call getTimeEntries
. This will come in handy later when we need to update time entries after creating, editing and deleting them.
您还将注意到,我们已经将第1部分中对time.getTime
的调用包装到一个名为getTimeEntries
的函数中。 当我们需要在创建,编辑和删除时间条目之后更新时间条目时,这将派上用场。
Finally for this part, let's create a dropdown list of the users so we can choose who time entries should be attributed to.
最后,对于这一部分,让我们创建一个用户下拉列表,以便我们可以选择应将时间条目归于谁。
......
We've added in a select
element just beside our comment input box and we're using the ng-options
directive to loop through and display the users on the vm.users
array. You should now see the list of users available in our app:
我们在注释输入框旁边添加了一个select
元素,并使用ng-options
指令遍历并在vm.users
数组上显示用户。 现在,您应该在我们的应用程序中看到可用的用户列表:
Now that we are able to read the time entries and users from the database, in the next sections we will look at creating, updating, and deleting time entries.
现在我们已经能够从数据库中读取时间条目和用户,在接下来的部分中,我们将介绍如何创建,更新和删除时间条目。
In part 1 we had the ability to create time entries, but we were just pushing the new entries onto the already existing array. This time, let's create the Laravel logic to capture and store time entries, and then the Angular parts send the HTTP request to the API.
在第1部分中,我们具有创建时间条目的能力,但是我们只是将新条目推入已经存在的数组中。 这次,让我们创建Laravel逻辑来捕获和存储时间条目,然后Angular部分将HTTP请求发送到API。
First, let's add to the already existing store
method in the TimeEntriesController
.
首先,让我们添加到TimeEntriesController
已经存在的store
方法中。
// app/Http/Controllers/TimeEntriesController.php...use AppHttpRequests;use AppHttpControllersController;use AppTimeEntry;use IlluminateSupportFacadesRequest;...// Grab all the data passed into the request and save a new recordpublic function store(){ $data = Request::all(); $timeentry = new TimeEntry(); $timeentry->fill($data); $timeentry->save();}...
The store method accepts a POST to the /time
endpoint of our API. In the method we are grabbing all of the data passed along with the request, creating a new instance of our TimeEntry
class, then using Laravel's fill
method to store all of the data. The fill
method is a nice helper that goes through all of the data and puts it in the appropriate database fields. Finally we call save
to finish things out. Notice here that we are also using Larave's Request
facade which we bring in with the use
statement. To avoid conflicts, we'll have to get rid of the already existing HttpRequest
class that was previously brought in.
store方法接受POST到我们API的/time
端点。 在该方法中,我们获取与请求一起传递的所有数据,创建TimeEntry
类的新实例,然后使用Laravel的fill
方法存储所有数据。 fill
方法是一个很好的助手,它可以遍历所有数据并将其放入适当的数据库字段中。 最后,我们调用save
完成操作。 请注意,这里我们还使用了Larave的Request
门面,它是通过use
语句引入的。 为了避免冲突,我们必须摆脱以前引入的已经存在的HttpRequest
类。
Now let's setup Angular to send data to the API to store new time entries. There are a few spots we'll need to change from part 1. First, let's update the time
service:
现在,我们将Angular设置为将数据发送到API以存储新的时间条目。 在第1部分中,我们需要更改一些地方。首先,让我们更新time
服务:
// public/scripts/services/time.js... // Grab data passed from the view and send// a POST request to the API to save the datafunction saveTime(data) { return Time.save(data).$promise.then(function(success) { console.log(success); }, function(error) { console.log(error); });}...return { getTime: getTime, getTimeDiff: getTimeDiff, getTotalTime: getTotalTime, saveTime: saveTime}...
Here we've created a new method called saveTime
which gets all the user input and uses ngResource
's save
method to send a POST
request to the API with the data. We have to be sure to add this new method to our returned object at the end of the service.
在这里,我们创建了一个名为saveTime
的新方法,该方法获取所有用户输入,并使用ngResource
的save
方法向数据发送POST
请求到API。 我们必须确保在服务结束时将此新方法添加到返回的对象中。
Next, let's change the vm.logNewTime
method in our TimeEntry
controller from part 1 to use this new method on our service.
接下来,让我们将第1部分中的TimeEntry
控制器中的vm.logNewTime
方法更改为在我们的服务上使用此新方法。
// public/scripts/controllers/TimeEntry.js...// Submit the time entry that will be called // when we click the "Log Time" buttonvm.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; } // Call to the saveTime method on the time service // to save the new time entry to the database time.saveTime({ "user_id":vm.timeEntryUser.id, "start_time":vm.clockIn, "end_time":vm.clockOut, "comment":vm.comment }).then(function(success) { getTimeEntries(); console.log(success); }, function(error) { console.log(error); }); getTimeEntries(); // Reset clockIn and clockOut times to the current time vm.clockIn = moment(); vm.clockOut = moment(); // Clear the comment field vm.comment = ""; // Deselect the user vm.timeEntryUser = "";}...
In the newly renovated logNewTime
method, we are still checking to be sure that the clock-in time isn't after the clock-out time and then we call the saveTime
method on the time
service. We pass an object into the saveTime
method where we specify the fields in our table that will need to be filled. If the save was successfull, we call our getTimeEntries
method to refresh the listing of time entries.
在新近更新的logNewTime
方法中,我们仍在检查以确保时钟输入时间不在时钟输出时间之后,然后在time
服务上调用saveTime
方法。 我们将一个对象传递到saveTime
方法中,在saveTime
方法中,我们在表中指定需要填充的字段。 如果保存成功,则调用getTimeEntries
方法刷新时间条目列表。
There are a couple different ways we could refresh the listing of time entries on save. I've elected here to get the full list again each time a new time entry is saved, but some people prefer to push the new data onto the local array. The problem I see with this is that the local data and what is in the database become out of sync. For a simple application like this, I prefer to take the expense of doing a database query again to make sure what is being shown to the user is what is in the database.
我们可以通过几种不同的方式来刷新保存的时间条目列表。 我选择在这里选择是在每次保存新的时间条目时再次获取完整列表,但是有些人喜欢将新数据推送到本地数组中。 我看到的问题是本地数据和数据库中的内容不同步。 对于像这样的简单应用程序,我宁愿花钱再次进行数据库查询,以确保向用户显示的是数据库中的内容。
We'll need to provide the user a way to update their time entries in case they need to change the clock-in or clock-out times, the user the time entry belongs to, or the comment provided. To get us started, let's first adjust the view to give us a UI for editing the time entries.
我们需要为用户提供一种更新其时间条目的方法,以防他们需要更改输入或输出时间,该时间条目所属的用户或提供的注释。 首先,让我们开始调整视图,以提供一个用于编辑时间条目的UI。
......{ {time.user.first_name}} { {time.user.last_name}}
{ {time.comment}}
{ {time.end_time | date:'mediumDate'}}
{ {time.loggedTime.duration._data.hours}} hours
{ {time.loggedTime.duration._data.minutes}} minutes
Edit Time Entry
Clock In Clock OutUser
Comment
We've added in quite a bit since the original markup we had in part 1. We've included some controls for opening the edit dialog and deleting the time entry (which we will handle next) and we've put in the structure for the editing screen. We're hiding and showing the edit area conditionally using ng-show
to which we attach a property called showEditDialog
, the state of which determines whether the edit screen is shown.
自从第1部分中的原始标记以来,我们已经添加了很多内容。我们提供了一些控件,用于打开编辑对话框并删除时间条目(我们将在接下来处理),并在其中添加了编辑屏幕。 我们使用ng-show
有条件地隐藏和显示编辑区域,在该区域中附加了一个名为showEditDialog
的属性,该属性的状态决定是否显示编辑屏幕。
The select element that uses ng-options
looks a bit different for the editing screen. We use the track by
statement for ng-options
here to let the select box know which user should be pre-selected when the screen is opened.
使用ng-options
的select元素在编辑屏幕上看起来有些不同。 我们在这里使用ng-options
的track by
语句来使选择框知道打开屏幕时应预先选择哪个用户。
Finally, we are passing the whole time
record into the vm.updateTimeEntry
method which is called when we click the save button.
最后,我们将整个time
记录传递到vm.updateTimeEntry
方法中,该方法在单击保存按钮时被调用。
We'll need some additional CSS to make things look a bit better:
我们将需要一些其他CSS来使外观看起来更好:
/* public/css/style.css */.edit-time-entry { border-top: 1px solid #ccc; background-color: #e0e0e0; padding: 15px; margin: 10px !important;}
Next, let's add some logic to the Laravel controller to update the database.
接下来,让我们向Laravel控制器添加一些逻辑以更新数据库。
// app/Http/Controllers/TimeEntriesController.php...// Grab all the data passed into the request and fill the database record with the new datapublic function update($id){ $timeentry = TimeEntry::find($id); $data = Request::all(); $timeentry->fill($data); $timeentry->save();}...
The update method needs to know which time entry we want to update, so we pass in an id
to work with. We use Laravel's find
helper to get the appropriate time entry and also capture all the data passed to the endpoint. We again use fill
to fill in all of the database fields with the data we passed to the controller and then save the record.
update方法需要知道我们要更新哪个时间条目,因此我们传入一个id
进行处理。 我们使用Laravel的find
助手来获取适当的时间条目,并捕获传递给端点的所有数据。 我们再次使用fill
将传递给控制器的数据fill
到所有数据库字段中,然后保存记录。
Now that the markup and Laravel logic is in place, let's update the service and the controller.
现在标记和Laravel逻辑已经就绪,让我们更新服务和控制器。
// public/scripts/services/time.js...// Use a PUT request to save the updated data passed infunction updateTime(data) { return Time.update({id:data.id}, data).$promise.then(function(success) { console.log(success); }, function(error) { console.log(error); });}...return { getTime: getTime, getTimeDiff: getTimeDiff, getTotalTime: getTotalTime, saveTime: saveTime, updateTime: updateTime}...
Here we've added in a new method called updateTime
which takes the data passed from the controller and calls update
on the Time
resource. This sends a PUT
request to the Laravel update
controller which takes the data in and saves it.
在这里,我们添加了一个名为updateTime
的新方法,该方法接收从控制器传递的数据,并在Time
资源上调用update
。 这会将PUT
请求发送到Laravel update
控制器,后者将数据放入并保存。
// public/scripts/controllers/TimeEntry.js...vm.updateTimeEntry = function(timeentry) { // Collect the data that will be passed to the updateTime method var updatedTimeEntry = { "id":timeentry.id, "user_id":timeentry.user.id, "start_time":timeentry.start_time, "end_time":timeentry.end_time, "comment":timeentry.comment } // Update the time entry and then refresh the list time.updateTime(updatedTimeEntry).then(function(success) { getTimeEntries(); $scope.showEditDialog = false; console.log(success); }, function(error) { console.log(error); });} ...
In the controller we have added a method called vm.updateTimeEntry
which is called from the view using ng-click
when the save button is clicked. This method accepts the time
object which has all the properties of a single time record---more specifically, the record that is being edited. In this method we call the updateTime
method on the time
service and after the request has succeeded we call getTimeEntries
to refresh the list, as well as close the edit dialog by setting our showEditDialog
to false.
在控制器中,我们添加了一个名为vm.updateTimeEntry
的方法, ng-click
保存按钮后会使用ng-click
从视图中调用该方法。 此方法接受具有单个时间记录的所有属性的time
对象-更具体地说,是正在编辑的记录。 在此方法中,我们在time
服务上调用updateTime
方法,在请求成功后,我们调用getTimeEntries
刷新列表,以及通过将showEditDialog
设置为false来关闭编辑对话框。
All the pieces are now in place to edit our time entries. The only thing left now is to give the user the ability to delete them.
现在所有部分都已准备就绪,可以编辑我们的时间条目。 现在剩下的唯一一件事就是使用户能够删除它们。
We've already put a button in place for deleting time entries so all we need to do now is put the Laravel logic in place and update the Angular service and controller to handle the delete request.
我们已经放置了一个删除时间条目的按钮,因此我们现在要做的就是放置Laravel逻辑并更新Angular服务和控制器以处理删除请求。
// app/Http/Controllers/TimeEntriesController.php...// Find the time entry to be deleted and then call deletepublic function destroy($id){ $timeentry = TimeEntry::find($id); $timeentry->delete(); }...
The Laravel logic for deleting a time entry is pretty straight forward---we just need to find the record based on an id
we pass into the controller and then call delete on it.
用于删除时间条目的Laravel逻辑非常简单-我们只需要根据传递给控制器的id
查找记录,然后对其调用delete。
Now let's update the Angular service:
现在让我们更新Angular服务:
// public/scripts/services/time.js...// Send a DELETE request for a specific time entryfunction deleteTime(id) { return Time.delete({id:id}).$promise.then(function(success) { console.log(success); }, function(error) { console.log(error); });}...return { getTime: getTime, getTimeDiff: getTimeDiff, getTotalTime: getTotalTime, saveTime: saveTime, updateTime: updateTime, deleteTime: deleteTime}
And finally the controller:
最后是控制器:
// public/scripts/controllers/TimeEntry.js// Specify the time entry to be deleted and pass it to the deleteTime method on the time servicevm.deleteTimeEntry = function(timeentry) { var id = timeentry.id; time.deleteTime(id).then(function(success) { getTimeEntries(); console.log(success); }, function(error) { console.log(error); }); }
The vm.timeentry
method is called when we click the delete button in the view and uses the id
property of the time object to tell Laravel which record to delete
当我们单击视图中的删除按钮时,将调用vm.timeentry
方法,并使用时间对象的id
属性告诉Laravel要删除的记录
There we have it! A fully functioning app that lets users create, read, update and delete time entries. Although we have the base functionality in place for the app to work, there are still a few items that are missing which could improve it:
到了! 一个功能齐全的应用程序,可让用户创建,读取,更新和删除时间条目。 尽管我们具备该应用程序可以使用的基本功能,但是仍然缺少一些可以改进它的项目:
Now that we're done building the app together, here's a challenge for you: put the necessary code in place to accomplish the suggested improvements I mentioned above. I'd love to see your ideas for making those improvements or anything else you think the app could benefit from. Feel free to send pull requests our way!
现在我们已经完成了一起构建应用程序,这对您来说是一个挑战:放置必要的代码以完成我上面提到的建议的改进。 我很乐意看到您的想法,以进行这些改进或您认为应用程序可以从中受益的任何其他内容。 随意发送请求请求我们的方式!
If you’d like to get more AngularJS and Laravel tutorials, feel free to head over to and . You should follow me on ---I'd love to hear about what you're working on!
如果您想获得更多AngularJS和Laravel教程,请随时访问并 。 您应该在关注我---我很想知道您在做什么!
翻译自:
转载地址:http://knywd.baihongyu.com/