How to build a Project Management App in Laravel 5 – Part 7
When you click on a project to see the details, it shows the tasks, files and comments form.
Let’s see how we can add tasks to each Project.
Create a new folder named tasks in the views directory and create a form.blade.php inside the tasks directory.
Move the part of the show.blade.php that has to do with the task form into form.blade.php file like so:
form.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<div class="col-md-5"> <h4 class="page-header"> Tasks </h4> <div class="row" style="border:1px solid #ccc;margin-left:5px;width:100%;padding:15px;"> <form class="form-vertical" role="form" method="post" action="{{ route('projects.tasks.create', $project->id) }}"> <div class="form-group{{ $errors->has('task_name') ? ' has-error' : '' }}"> <input type="text" name="task_name" class="form-control" id="name" value="{{ old('task_name') ?: '' }}"> @if ($errors->has('task_name')) <span class="help-block">{{ $errors->first('task_name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Create Task</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> |
Replace the part in show.blade.php with @include('tasks.form')
like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
.... .... <div class="row"> @include('tasks.form') <div class="col-md-5"> <h4 class="page-header"> Files </h4> <div class="row" style="border:1px solid #ccc;margin-left:5px;width:100%;padding:15px;"> <form class="form-vertical" role="form" method="post" action="#"> <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <input type="text" name="name" class="form-control" id="name" value="{{ old('name') ?: '' }}"> @if ($errors->has('name')) <span class="help-block">{{ $errors->first('name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Add Files</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> </div> |
Go to routes.php and add this:
1 2 3 4 5 |
# Task routes Route::post('projects/{projects}/tasks', [ 'uses' => '\Prego\Http\Controllers\ProjectTasksController@postNewTask', 'as' => 'projects.tasks.create' ]); |
Create a ProjectTasksController.php and add this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php namespace Prego\Http\Controllers; use Illuminate\Http\Request; use Auth; use Prego\Task; use Prego\Http\Requests; use Prego\Http\Controllers\Controller; class ProjectTasksController extends Controller { /** * Display a listing of the resource. * * @return Response */ public function postNewTask(Request $request, $id, Task $task) { $this->validate($request, [ 'task_name' => 'required|min:5', ]); $task->task_name = $request->input('task_name'); $task->project_id = $id; $task->save(); return redirect()->back()->with('info', 'Task created successfully'); } } |
We are going to use the principle of Dependency Injection here by creating an instance of Task and passing it as an argument to postNewTask function.
Create the Task Model by creating a Task.php file in the app
directory
Task.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php namespace Prego; use Auth; use Illuminate\Database\Eloquent\Model; class Task extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['task_name', 'project_id']; public function scopeProject($query, $id) { return $query->where('project_id', $id); } } |
We just wrote a query scope to get all the tasks that belongs to one Project.
Go to ProjectController.php
Import the Task by writing use Prego\Task
from the top of the Controller file.
Add this function to retrieve all the tasks
1 2 3 4 5 6 7 8 9 10 |
/** * Get all the tasks for a Project * @param [type] $id [description] * @return [type] [description] */ public function getTasks($id) { $tasks = Task::project($id)->get(); return $tasks; } |
Just scroll up the show function. We will call the getTasks function in the show function. We want to return all the tasks to the projects show view.
Modify the Show function to look like this:
1 2 3 4 5 6 |
public function show($id) { $project = Project::find($id); $tasks = $this->getTasks($id); return view('projects.show')->withProject($project)->withTasks($tasks); } |
Now, the function returns the project with all its tasks to the show view.
the form.blade.php in tasks folder will change, because we will add the ability to see all the tasks with an edit and delete button.
form.blade.php now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<div class="col-md-5"> <h4 class="page-header"> Tasks </h4> <div class="row" style="border:1px solid #ccc;margin-left:5px;width:100%;padding:15px;"> @if( $tasks) @foreach( $tasks as $task) <div> <div><i class="fa fa-check-square-o"></i> <span>{{ $task->task_name }}</span></div> <a href="/projects/{{ $project->id }}/tasks/{{ $task->id }}/edit">Edit</a> <button class="btn btn-danger delete pull-right" data-action="#" data-token="{{csrf_token()}}"> <i class="fa fa-trash-o"></i>Delete </button> </div> <hr/> @endforeach @endif <form class="form-vertical" role="form" method="post" action="{{ route('projects.tasks.create', $project->id) }}"> <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <input type="text" name="task_name" class="form-control" id="name" value="{{ old('task_name') ?: '' }}"> @if ($errors->has('task_name')) <span class="help-block">{{ $errors->first('task_name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Create Task</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> |
Go to routes.php
Add this:
1 2 3 4 5 6 7 8 |
Route::get('projects/{projects}/tasks/{tasks}/edit', [ 'uses' => '\Prego\Http\Controllers\ProjectTasksController@getOneProjectTask', 'as' => 'projects.tasks' ]); Route::put('projects/{projects}/tasks/{tasks}', [ 'uses' => '\Prego\Http\Controllers\ProjectTasksController@updateOneProjectTask', ]); |
We are using Route::put
because we want to carry out an update operation.
Go to ProjectTaskController.php
Add this function to get just one task for a particular project.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Get just one task for a particular Project * @param [type] $projectId [description] * @param [type] $taskId [description] * @return [type] [description] */ public function getOneProjectTask($projectId, $taskId) { $task = Task::where('project_id', $projectId) ->where('id', $taskId) ->first(); return view('tasks.edit')->withTask($task)->with('projectId', $projectId); } |
We are also returning the project id so that we can use it in the actual update function.
Create an edit.blade.php in the tasks folder
edit.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@extends('layouts.master') @section('content') @include('layouts.partials.sidebar') <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header"> Edit Task</h1> @include('layouts.partials.alerts') <div class="col-lg-6"> <form class="form-vertical" role="form" method="post" action="{{ url('projects/' .$projectId .'/tasks/' . $task->id) }}"> <div class="form-group{{ $errors->has('task_name') ? ' has-error' : '' }}"> <input type="text" name="task_name" class="form-control" id="name" value="{!! $task->task_name ?: '' !!}"> @if ($errors->has('task_name')) <span class="help-block">{{ $errors->first('task_name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Update Task</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> {!! method_field('PUT') !!} </form> </div> </div> @stop |
The edit task form will output each the name of an individual task in a text field and when the “Update task” button is clicked, it sends a PUT request to /projects/{projects}/tasks/{tasks} route
With this, if you click on the Edit link of each task, you should have something like so:
Now, let’s work on Updating each task that belongs to a Project.
Go to ProjectTasksController.php
Add the updateOneProjectTask function like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * Update One Project Task * @param Request $request [description] * @param [type] $projectId [description] * @param [type] $taskId [description] * @return [type] [description] */ public function updateOneProjectTask(Request $request, $projectId, $taskId) { $this->validate($request, [ 'task_name' => 'required|min:3', ]); DB::table('tasks') ->where('project_id', $projectId) ->where('id', $taskId) ->update(['task_name' => $request->input('task_name')]); return redirect()->back()->with('info','Your Task has been updated successfully'); } |
This validates the field and updates the tasks table with the Query Builder functionality provided by Laravel via the DB Facade.
Make sure to include use DB
at the top of the Controller file just before use Auth
;
Try it out, Yaaay! it updates successfully
Let’s implement the Delete functionality.
Add this to the routes.php
1 2 3 |
Route::delete('projects/{projects}/tasks/{tasks}', [ 'uses' => '\Prego\Http\Controllers\ProjectTasksController@deleteOneProjectTask', ]); |
This will enable us to perform a delete operation on a particular task.
Go to ProjectTasksController.php
Add the deleteOneProjectTask function like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Delete One Project Task * @param [type] $projectId [description] * @param [type] $taskId [description] * @return [type] [description] */ public function deleteOneProjectTask($projectId, $taskId) { DB::table('tasks') ->where('project_id', $projectId) ->where('id', $taskId) ->delete(); return redirect()->route('projects.show')->with('info', 'Task deleted successfully'); } |
Go back to form.blade.php
Make sure the Delete button is looking like this:
1 2 3 4 5 |
<button class="btn btn-danger delete pull-right" data-action="/projects/{{ $project->id }}/tasks/{{ $task->id }}" data-token="{{csrf_token()}}"> <i class="fa fa-trash-o"></i>Delete </button> |
Brace Up, Another Feature is Coming
Let’s add the ability for a user to attach files to the Project
We already have the files form.
Create a files folder in views directory
Then, go ahead to create a form.blade.php in the files folder. Let’s abstract the files form part from the projects/show.blade.php into this new form file we created.
files/form.blade.php will look like this now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<div class="col-md-5"> <h4 class="page-header"> Files </h4> <div class="row" style="border:1px solid #ccc;margin-left:5px;width:100%;padding:15px;"> <form class="form-vertical" role="form" method="post" action="#"> <div class="form-group{{ $errors->has('file_name') ? ' has-error' : '' }}"> <input type="file" name="file_name" class="form-control" id="file_name"> @if ($errors->has('file_name')) <span class="help-block">{{ $errors->first('file_name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Add Files</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> |
Then replace this part in the show.blade.php with @include('files.form')
Create a FilesController.php file
FilesController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php namespace Prego\Http\Controllers; use Illuminate\Http\Request; use Cloudder; use Prego\File as File; use Prego\Http\Requests; use Prego\Http\Controllers\Controller; class FilesController extends Controller { /** * Displays the index page of the app * * @return Response */ public function uploadAttachments(Request $request, $id) { $this->validate($request, [ 'file_name' => 'required|mimes:jpeg,bmp,png,pdf|between:1,7000', ]); $filename = $request->file('file_name')->getRealPath(); Cloudder::upload($filename, null); list($width, $height) = getimagesize($filename); $fileUrl = Cloudder::show(Cloudder::getPublicId(), ["width" => $width, "height" => $height]); $this->saveUploads($request, $fileUrl, $id); return redirect()->back()->with('info', 'Your Attachment has been uploaded Successfully'); } private function saveUploads(Request $request, $fileUrl, $id) { $file = new File; $file->file_name = $request->file('file_name')->getClientOriginalName(); $file->file_url = $fileUrl; $file->project_id = $id; $file->save(); } } |
There are two functions here. uploadAttachments() gets the file to be uploaded, uploads them to Cloudinary and then return the link to the file. It then calls the saveUploads() which stores it in the database.
1 |
Cloudder::upload($filename, null); |
Cloudder is a Facade gotten from a package I installed. This line of code above uploads your file to a Cloudinary account.
1 |
$fileUrl = Cloudder::show(Cloudder::getPublicId(), ["width" => $width, "height" => $height]); |
This line of code returns the URL to the image uploaded to Cloudinary with the appropriate width and height of the image.
First, head over to Cloudinary and create an account. In your Dashboard, you will see your account details like CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET & CLOUDINARY_CLOUD_NAME
Now, go to your .env file and add this details because we’ll need them.
1 2 3 |
CLOUDINARY_API_KEY=xxxxxxxx CLOUDINARY_API_SECRET=xxxxxxx CLOUDINARY_CLOUD_NAME=xxxxxxxx |
Replace the xxxxxx with your own details.
Go ahead and require jrm2k6/cloudder Laravel package for easily interacting with Cloudinary.
When you are done doing composer install and the package has been installed, Add the serviceProvider and aliases in your config/app.php
1 2 3 4 5 6 7 |
'providers' => array( 'JD\Cloudder\CloudderServiceProvider' ); 'aliases' => array( 'Cloudder' => 'JD\Cloudder\Facades\Cloudder' ); |
Now run:
1 |
php artisan vendor:publish --provider="JD\Cloudder\CloudderServiceProvider" |
It copies the config file to the Laravel app.
Let’s go ahead and create the File Model. Create File.php
File.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php namespace Prego; use Auth; use Illuminate\Database\Eloquent\Model; class File extends Model { public function scopeProject($query, $id) { return $query->where('project_id', $id); } } |
Now, let’s go back to ProjectController.php. We need to be able to view our files in show.blade.php , so we will tweak it a bit.
Add this function:
1 2 3 4 5 |
public function getFiles($id) { $files = File::project($id)->get(); return $files; } |
Make sure you don’t forget to require Prego\File
at the top.
Tweak the show function to become like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { $project = Project::find($id); $tasks = $this->getTasks($id); $files = $this->getFiles($id); return view('projects.show')->withProject($project)->withTasks($tasks)->withFiles($files); } |
We are getting the files and returning them with projects and tasks to the projects show view.
Now, head over to the routes.php file and add this:
1 2 3 4 5 |
Route::post('projects/{projects}/files', [ 'uses' => 'Prego\Http\Controllers\FilesController@uploadAttachments', 'as' => 'projects.files', 'middleware' => ['auth'] ]); |
This enables us to make a post request from the Files form to this route and upload our attachments for each project.
The middleware is there to make sure unregistered users can’t access this route.
file/form.blade.php will look like this now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<div class="col-md-5"> <h4 class="page-header"> Files </h4> <div class="row" style="border:1px solid #ccc;margin-left:5px;width:100%;padding:15px;"> @if( $files) @foreach( $files as $file) <div> <div><i class="fa fa-check-square-o"></i> <span> <a href="{{ $file->file_url }}" target="_blank">{{ $file->file_name }}</a> </span> </div> </div> <hr/> @endforeach @endif <form class="form-vertical" role="form" enctype="multipart/form-data" method="post" action="{{ route('projects.files', ['projects' => $project->id]) }}"> <div class="form-group{{ $errors->has('file_name') ? ' has-error' : '' }}"> <input type="file" name="file_name" class="form-control" id="file_name"> @if ($errors->has('file_name')) <span class="help-block">{{ $errors->first('file_name') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn btn-info">Add Files</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> |
So all the files uploaded can be displayed on the page too. Awesome!
Now, test the Add Attachment functionality
Yaaay!!..It’s working.
Take a break and nod your head, then clap for yourself. You have done well by getting the basic features of Prego working properly.
In the next lesson, we will deal with Adding Comments and Collaborators to a Project.
Please let me know if you have any questions or observations, feel free to drop it in the comments section below.

- How to build your own Youtube – Part 10 - August 1, 2016
- How to build your own Youtube – Part 9 - July 25, 2016
- How to build your own Youtube – Part 8 - July 23, 2016
- How to build your own Youtube – Part 6 - July 6, 2016
- Introducing Laravel Password v1.0 - July 3, 2016
- How to build your own Youtube – Part 5 - June 28, 2016
- How to build your own Youtube – Part 4 - June 23, 2016
- How to build your own Youtube – Part 3 - June 15, 2016
- How to build your own Youtube – Part 2 - June 8, 2016
- How to build your own Youtube – Part 1 - June 1, 2016