Create Drag and Droppable Cards in Laravel using Vue Js


I think some of us are familiar with drag and droppable cards system in various task management applications like Trello. Today in this tutorial I will show you how to create similar drag and droppable cards with persistent in Laravel using Vue.js. I am using a cool library in Vue.js called Vue.Draggable. The Vue.Draggable is based on Sortable.jS and It helps us to create Vue component allowing drag-and-drop sorting in sync with View-Model. Very basic knowledge of Vue.js is recommended for following this tutorial. It is very easy to learn Vue.js if you know basics of Javascript and JQuery. If you are new to Vue.js and looking for a free and best tutorial then visit Complete Vue.js 2 Playlist by The Net Ninja. Here is the link,

 

Let's Start Coding

First, let's make a DemoTask Table migration and Model by running the following command.

php artisan make:model DemoTask -m

Now In the Migration file,

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateDemoTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('demo_tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->integer('order')->unsigned()->default(0);
            $table->integer('status')->unsigned()->default(0);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('demo_tasks');
    }
}

 

I used unsigned because the value of order and status fields will not be less than zero. So it helps us to save some space. The Order field is used to determine the order of our task and status field will have a boolean value which determines whether the task is completed or not.

Now let's migrate the table.

 php artisan migrate 

The complete code in DemoTask Model is given below.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class DemoTask extends Model
{
     /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'id', 'title','order', 'status',
    ];
}

 

The codes for the model is very basic. It has fillable fields predefined since we use mass assignments in our controller function.

Now let's makes our required routes in routes/web.php 

Route::get('demos/tasks','DemoController@showTasks');
Route::patch('demos/tasks/{id}', 'DemoController@updateTasksStatus');
Route::put('demos/tasks/updateAll', 'DemoController@updateTasksOrder');

 

We use the patch and put since we are doing update operation to change status and order of our task. You can use post method instead if you want.

Now Let's create our DemoController by running the following command.

php artisan make:controller DemoController

The complete code for DemoController is given below.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\DemoTask;

class DemoController extends Controller
{
    
    public function showTasks()
    {
        $tasks = DemoTask::orderBy('order')->select('id','title','order','status')->get();

        $tasksCompleted = $tasks->filter(function ($task, $key) {
            return $task->status;
        })->values();
        
        $tasksNotCompleted = $tasks->filter(function ($task, $key) {
            return  ! $task->status;
        })->values();

        return view('demos.alltasks',compact('tasksCompleted','tasksNotCompleted'));
    }

    public function updateTasksStatus(Request $request, $id)
    {
        $this->validate($request, [
            'status' => 'required|boolean',
        ]);

        $task = DemoTask::find($id);
        $task->status = $request->status;
        $task->save();
        
        return response('Updated Successfully.', 200);

    }

    public function updateTasksOrder(Request $request)
    {
        $this->validate($request, [
            'tasks.*.order' => 'required|numeric',
        ]);

        $tasks = DemoTask::all();

        foreach ($tasks as $task) {
            $id = $task->id;
            foreach ($request->tasks as $tasksNew) {
                if ($tasksNew['id'] == $id) {
                    $task->update(['order' => $tasksNew['order']]);
                }
            }
        }

        return response('Updated Successfully.', 200);
    }

}

 

The showTasks method is used to show tasks in our view page. The filter method is used to filters the collection using the given callback, keeping only those items that pass a given truth test, in this case, gives us tasks that are completed and tasks that are not completed. The updateTasksStatus method just updated the status of given task to completed(status 1) or not completed(status 0). The updateTasksOrder function is to update the order of the tasks.

Now let's see the complete code for our View page - demos/alltasks.blade.php file

@extends('main')

@section('content')

  <h3 class="text-center title-color">Drag and Droppable Cards using Vue.Draggable Demo</h3>
  <div class="well" id="app">
    <task-draggable :tasks-completed="{{ $tasksCompleted }}" :tasks-not-completed="{{ $tasksNotCompleted }}"></task-draggable>
  </div> <!-- end app -->
  <h5>Drag and Drop the cards and <button class="btn btn-default" onclick="window.location.reload()"><b>REFRESH</b></button> the page to check the Demo. For the complete tutorial of how to make this demo app visit the following <a href="#">Link</a>.</h5>

   <script src="{{ asset('js/app.js') }}"></script> 
    
@stop

 

I am skipping the layout details as it will vary from project to project. In our view page, we need to include our webpack bundled app.js file created by using Laravel Mix at bottom of our file. The task-draggable is our Vue component. We are passing task completed list and task not completed list to our Vue component as its properties. 

Now Before we move to Vue part let's set up our laravel project for using laravel Mix. You need Laravel 5.4 and above for using Laravel Mix. 

First, install Node.js and npm on your computer. You can install Node.js by visiting the following link 

https://nodejs.org/en/

Now inside your Laravel project 

npm install

After successfully install all required javascript libraries for our laravel (node_modules folder is created) now complete our assets ie app.js and app.scss in our resource/assets folders by typing

npm run dev

Now when you are deploying the project don't forget to minify them by using this command. 

npm run production

Now when you are coding use watch to auto-compile our assets on saving by running the following commands

npm run watch

Now before we create our Vue component we need to install Vue.Draggable by running the following command.

npm install vuedraggable

Now let's create our Vue Component - components/TaskDraggable.vue. The complete code is given below.

<template>
    <div class="row">
        <div class="col-md-4 col-md-offset-2">
            <section class="list">
                <header>UPCOMING</header>
                <draggable class="drag-area" :list="tasksNotCompletedNew" :options="{animation:200, group:'status'}" :element="'article'" @add="onAdd($event, false)"  @change="update">
                    <article class="card" v-for="(task, index) in tasksNotCompletedNew" :key="task.id" :data-id="task.id">
                        <header>
                            {{ task.title }}
                        </header>
                    </article>
                </draggable>   
            </section>
        </div>
        <div class="col-md-4">   
            <section class="list">
                <header>COMPLETED</header>
                <draggable class="drag-area"  :list="tasksCompletedNew" :options="{animation:200, group:'status'}" :element="'article'" @add="onAdd($event, true)"  @change="update">
                    <article class="card" v-for="(task, index) in tasksCompletedNew" :key="task.id" :data-id="task.id">
                        <header>
                            {{ task.title }}
                        </header>
                    </article>
                </draggable>  
            </section>
        </div>
    </div>
</template>

<script>
    import draggable from 'vuedraggable'

    export default {
        components: {
            draggable
        },
        props: ['tasksCompleted', 'tasksNotCompleted'],
        data() {
            return {
                tasksNotCompletedNew: this.tasksNotCompleted,
                tasksCompletedNew: this.tasksCompleted
            }
        },
        methods: {
            onAdd(event, status) {
                let id = event.item.getAttribute('data-id');
                axios.patch('/demos/tasks/' + id, {
                    status: status
                }).then((response) => {
                    console.log(response.data);
                }).catch((error) => {
                    console.log(error);
                })
            },
            update() {
                this.tasksNotCompletedNew.map((task, index) => {
                    task.order = index + 1;
                });

                this.tasksCompletedNew.map((task, index) => {
                    task.order = index + 1;
                });

                let tasks = this.tasksNotCompletedNew.concat(this.tasksCompletedNew);

                axios.put('/demos/tasks/updateAll', {
                    tasks: tasks
                }).then((response) => {
                    console.log(response.data);
                }).catch((error) => {
                    console.log(error);
                })
            }

        }
    }
</script>

<style>
    .list {
      background-color: #26004d;
      border-radius: 3px;
      margin: 5px 5px;
      padding: 10px;
      width: 100%;
    }
    .list>header {
      font-weight: bold;
      color: white;
      text-align: center;
      font-size: 20px;
      line-height: 28px;
      cursor: grab;
    }
    .list article {
      border-radius: 3px;
      margin-top: 10px;
    }

    .list .card {
      background-color: #FFF;
      border-bottom: 1px solid #CCC;
      padding: 15px 10px;
      cursor: pointer;
      font-size: 16px;
      font-weight: bolder;
    }
    .list .card:hover {
      background-color: #F0F0F0;
    }
    .drag-area{
     min-height: 10px;  
    }
</style>

 

Remember: Do not update props directly inside our component as props are not mutatable in Vue.js. So we need to create two separate data models for handling our tasks completed and tasks not completed lists. We use draggable component from vuedraggable and pass our draggable cards as slots. The :list props accept the collection we need to make draggable. The :element props accept the element we need to make draggable. 

On drag and drop from one list to another list, the onAdd method is triggered and we use event.item.getAttribute('data-id') to grab the id of the task we need to update. The axios is used for ajax calls and Laravel Mix includes them by default. You can use jquery ajax method instead but in case of jquery, you need to pass the CSRF token along with it.  

When we are reordering our tasks upside down the update method is triggered. Since the Vue.Draggable only update the index position, not values of our collection we need to maps order of our tasks according to change in index position using map method in javascript. You can concatenate two arrays into one by using the concat method in javascript.

Note: I am using ES6 syntax for reducing the number of lines. So please not confuse with the new syntax. The map() method creates a new array with the results of calling a function for every array element.

And special thanks to Anthony Pothin for making Trello like card interface in Codepen. Here is the original CSS link.

Now finally let's register our component in app.js file. 

Vue.component('task-draggable', require('./components/TaskDraggable.vue'));

 

The output image of above program is given below.

Drag and Droppable Cards in Laravel using Vue Js - ShareUrcodes

The Demo

You can demo the above application by visiting following link.

https://shareurcodes.com/demos/tasks

If anybody has any suggestions or doubts or need any help comment below and I try will respond to every one of you as early as possible.





Web development
1 Oct 2017 01:03pm
PHP Laravel Javascript Ajax Vue.js
246