How to build a Project Management App in Laravel 5 – Part 6
Let’s go to show.blade.php
1 |
<p><a href="/projects/{{ $project->id }}/edit">Edit</a></p> |
I have added the <a href="/projects/{{ $project->id }}/edit>
link. So if you click on it right now, it shows an empty page.
Let’s add the view for that.
Go to ProjectController.php
Add this to the edit function
1 2 |
$project = Project::find($id); return view('projects.edit')->withProject($project); |
So it fetches the details of the project and sends it to the edit view to present them to the user.
Now, create an edit.blade.php in the views/projects 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
@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 Project</h1> <div class="col-lg-6"> <form class="form-vertical" role="form" method="post" action="{{ route('projects.update') }}"> <div class="form-group{{ $errors->has('status') ? ' has-error' : '' }}"> <label for="status" class="control-label">Choose Status</label> <select name="status" id="status"> <option value="{!! $project->project_status !!}">{!! $project->project_status !!}</option> @if( $project->project_status === "Upcoming" ) <option value="Active">Active</option> <option value="Completed">Completed</option> @elseif( $project->project_status === "Active" ) <option value="Upcoming">Upcoming</option> <option value="Completed">Completed</option> @elseif( $project->project_status === "Completed") <option value="Upcoming">Upcoming</option> <option value="Active">Active</option> @endif </select> @if ($errors->has('status')) <span class="help-block">{{ $errors->first('status') }}</span> @endif </div> <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <label for="name" class="control-label">Name</label> <input type="text" name="name" class="form-control" id="name" value="{!! $project->project_name ?: '' !!}"> @if ($errors->has('name')) <span class="help-block">{{ $errors->first('name') }}</span> @endif </div> <div class="form-group{{ $errors->has('due-date') ? ' has-error' : '' }}"> <label for="due-date" class="control-label">Due date</label> <input type="date" name="due-date" class="form-control" id="due-date" value="{!! $project->due_date !!}"> @if ($errors->has('due-date')) <span class="help-block">{{ $errors->first('due-date') }}</span> @endif </div> <div class="form-group{{ $errors->has('notes') ? ' has-error' : '' }}"> <label for="notes" class="control-label">Notes</label> <textarea name="notes" class="form-control" id="notes" rows="10" cols="10"> {!! $project->project_notes ?: '' !!} </textarea> @if ($errors->has('notes')) <span class="help-block">{{ $errors->first('notes') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn bt">Update</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> @stop |
In the edit view, you can see that the action for the form routes to the update function in the ProjectController
Take a look at this part of the edit.blade.php
1 2 3 4 5 6 7 8 9 10 |
@if( $project->project_status === "Upcoming" ) <option value="Active">Active</option> <option value="Completed">Completed</option> @elseif( $project->project_status === "Active" ) <option value="Upcoming">Upcoming</option> <option value="Completed">Completed</option> @elseif( $project->project_status === "Completed") <option value="Upcoming">Upcoming</option> <option value="Active">Active</option> @endif |
This is in the select form field. We did this to determine which other project states to show in the drop down if the project status is a certain state.
So if the project status is Upcoming
, then the other options would be Active
and Completed
.
Now this implementation is actually ugly and it bloats our view file and it means we are now putting real logic in our view files which is wrong to do.
Let’s do it in a better way.
We will create a helper function to take care of that.
Create a helpers.php
file in the app/Http
directory.
helpers.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php function getStatus($projectStatus) { $projectStates = ["Upcoming" => "Upcoming", "Active" => "Active", "Completed" => "Completed"]; if( array_key_exists($projectStatus, $projectStates) ) { unset($projectStates[$projectStatus]); foreach( $projectStates as $key => $value) { echo "<option value='{$value}'>{$value}</option>"; } } } |
Here we just loop through the states, remove the current project state and return the other potential project status.
Go to your composer.json file and make it look like this:
1 2 3 4 5 6 7 8 9 10 11 |
"autoload": { "classmap": [ "database" ], "psr-4": { "Prego\": "app/" }, "files": [ "app/Http/helpers.php" ] }, |
The only thing we are actually adding is this:
1 2 3 |
"files": [ "app/Http/helpers.php" ] |
We want Laravel to be able to see our helper function, so that we can use it in any part of the app.
Now run composer dumpautoload
from the terminal.
This makes sure the autoloader can see the helper file.
Go back to the edit.blade.php and replace our earlier code with
1 |
{{ getStatus($project->project_status) }} |
Just one line of code. Is that not beautiful and clean?.
Now, the edit.blade.php 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
@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 Project</h1> <div class="col-lg-6"> <form class="form-vertical" role="form" method="post" action="{{ route('projects.update', $project->id) }}"> <div class="form-group{{ $errors->has('project_status') ? ' has-error' : '' }}"> <label for="status" class="control-label">Choose Status</label> <select name="project_status" id="status"> <option value="{!! $project->project_status !!}">{!! $project->project_status !!}</option> {{ getStatus($project->project_status) }} </select> @if ($errors->has('project_status')) <span class="help-block">{{ $errors->first('project_status') }}</span> @endif </div> <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <label for="name" class="control-label">Name</label> <input type="text" name="project_name" class="form-control" id="name" value="{!! $project->project_name ?: '' !!}"> @if ($errors->has('project_name')) <span class="help-block">{{ $errors->first('project_name') }}</span> @endif </div> <div class="form-group{{ $errors->has('due-date') ? ' has-error' : '' }}"> <label for="due-date" class="control-label">Due date</label> <input type="date" name="due-date" class="form-control" id="due-date" value="{!! $project->due_date !!}"> @if ($errors->has('due-date')) <span class="help-block">{{ $errors->first('due-date') }}</span> @endif </div> <div class="form-group{{ $errors->has('project_notes') ? ' has-error' : '' }}"> <label for="notes" class="control-label">Notes</label> <textarea name="project_notes" class="form-control" id="notes" rows="10" cols="10"> {!! $project->project_notes ?: '' !!} </textarea> @if ($errors->has('project_notes')) <span class="help-block">{{ $errors->first('project_notes') }}</span> @endif </div> <div class="form-group"> <button type="submit" class="btn bt">Update</button> </div> <input type="hidden" name="_token" value="{{ csrf_token() }}"> {!! method_field('PUT') !!} </form> </div> </div> @stop |
Go to the Project Model
Project.php Add this:
1 2 3 4 5 6 |
/** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['project_name', 'project_notes', 'project_status', 'due_date']; |
You are making the fields fillable so that the update operation can work seamlessly.
Go to ProjectController.php
In the update function add this:
1 2 3 4 5 6 7 8 9 10 11 12 |
$project = Project::findOrFail($id); $this->validate($request, [ 'project_name' => 'required|min:3', 'due-date' => 'required|date|after:today', 'project_notes' => 'required|min:10', 'project_status' => 'required' ]); $values = $request->all(); $project->fill($values)->save(); return redirect()->back()->with('info','Your Project has been updated successfully'); |
That validates the fields and gets all the form values.
$request->all() returns all the form values in an array
$project->fill($values)->save() actually replaces the database values with the new values from the edit form.
..then a success message is shown to the user.
Time to implement the Delete functionality.
Go to show.blade.php
Replace the delete link with this:
1 2 3 4 5 |
<button class="btn btn-circle btn-danger delete" data-action="{{ url('projects/' . $project->id) }}" data-token="{{csrf_token()}}"> <i class="fa fa-trash-o"></i>Delete </button> |
We are going to use Javascript here to make the interaction more user-friendly and fast.
Go to the public directory and create a js folder.
Create an app.js inside the js folder and copy this into it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$(document).ready(function() { $("button.delete").on('click', function(e){ e.preventDefault(); if ( ! confirm('Are you sure?')) { return false; } var action = $(this).data("action"); var parent = $(this).parent(); var token = $(this).data("token"); $.ajax({ type: 'POST', url: action, data: { _token: token, _method: 'delete' }, error: function(msg) { alert(msg.responseJSON[0]); }, success: function() { window.location.href = '/projects' } }); }); }); |
The gist file is here: Gist
This fakes a delete request by making a post request to the /projects/{project}
route and sends a delete method alongside. So when the user clicks OK, it sends an ajax request to the destroy method in the ProjectController.php, then redirects back to the projects index page.
Go to your master.blade.php and add this:
1 2 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script> <script src="{{ asset('js/app.js') }}"></script> |
You are linking a jQuery file and the app.js file.
The destroy method in ProjectController.php should contain this:
1 2 3 4 5 6 7 |
public function destroy($id) { $project = Project::findOrFail($id); $project->delete(); return redirect()->route('projects.index')->with('info', 'Project deleted successfully'); } |
This does the actual deletion.
Hurray!!!..Now we can create, edit and delete a project.
Next post, we’ll learn how to create tasks and attachments for each project. Stay tuned.
Please if you have any question or observation, feel free to drop it in the comments section.
- 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