Building a User-Friendly Data Table with Laravel: Showcasing IMDb Data
Displaying data in a clear and manageable way is a common hurdle in web development. This post will guide you through setting up an interactive data table with Laravel Livewire, using IMDb as our case study. We're going to look at every step from importing CSV data to creating a table that users can sort and search through with ease.
The finished product can be viewed here
Project Overview
We're aiming to build a dynamic data table for IMDb data within a Laravel app. Our table will have some neat features like sorting, pagination, and a search function, all thanks to Livewire. It’s a great tool that lets us add these complex functions without complicating our Laravel setup.
Prerequisites
- A working Laravel setup
- Livewire ready to go in your Laravel project
- A good grasp of Laravel, Livewire, and how to use Blade templates
Step 1: Importing IMDb Data
First off, we're importing IMDb data from a CSV file straight into our Laravel app's database. A custom Artisan command will do the heavy lifting for us here.
I stumbled upon a handy CSV on GitHub full of IMDb movie data. Once you've grabbed the file, you'll create an Artisan command that neatly slots this data into a database table. It goes something like this:
php artisan make:command ImportCsv
Running this command sets up ImportCsvData.php in the app/Console/Commands directory. Inside this file, we'll write the logic to read the CSV and populate our database with its content.
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use League\Csv\Reader;
class ImportCsvData extends Command
{
protected $signature = 'import:csv';
protected $description = 'Import CSV data into the movies table';
public function handle()
{
$path = storage_path('app/csv/imdb.csv');
$csv = Reader::createFromPath($path, 'r');
$csv->setHeaderOffset(0);
$records = $csv->getRecords();
foreach ($records as $record) {
DB::table('imdbs')->insert([
'rank' => $record['Rank'],
'title' => $record['Title'],
'genre' => $record['Genre'],
'description' => $record['Description'],
'director' => $record['Director'],
'actors' => $record['Actors'],
'year' => $record['Year'],
'runtime_minutes' => $record['Runtime (Minutes)'],
'rating' => $record['Rating'],
'votes' => $record['Votes'],
'revenue_millions' => $record['Revenue (Millions)'] === '' ? null : $record['Revenue (Millions)'],
'metascore' => $record['Metascore'] === '' ? null : $record['Metascore'],
]);
}
$this->info('CSV data imported successfully.');
}
}
Step 2: Setting Up the IMDb Table
Next, we'll transform our CSV data into a proper database table using a Laravel migration. This migration file is like a script that tells Laravel how to build our table with columns that match our CSV file.
php artisan make:migration create_imdb_table
This will create a migration file that will allow you to create the table, it will look something like this:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
//
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};
Within this migration file we will add the column names and datatypes. The column names will be an exact match of the column names in our downloaded CSV.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('imdbs', function (Blueprint $table) {
$table--->id();
$table->timestamps();
$table->integer('rank')->unique();
$table->string('title');
$table->string('genre');
$table->text('description');
$table->string('director');
$table->text('actors');
$table->year('year');
$table->integer('runtime_minutes');
$table->decimal('rating', 3, 1);
$table->bigInteger('votes');
$table->decimal('revenue_millions', 10, 2)->nullable();
$table->integer('metascore')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('imdb');
}
};
After we've got our migration file sorted, a quick Artisan command creates our table
php artisan migrate
Then, we run our import command
php artisan import:csv
and just like that, our IMDb data is snug in its new database home.
Step 3: Crafting the Livewire Component
Now it's time for the fun part: making our Livewire component. This is where we pull the data into a view and add those interactive features like sorting and searching.
php artisan make:livewire ImdbTable
With the Livewire Compenent created, we will update the code to include pagination and sorting as well as grab the data from our model
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\Imdb;
use Illuminate\Support\Facades\Log;
class ImdbTable extends Component
{
use WithPagination; // Use the trait here
public $sortBy = 'rank';
public $sortDirection = 'asc';
public $search = '';
protected $paginationTheme = 'tailwind';
public function updateSearch($search)
{
$this->search = $search;
$this->resetPage();
}
public function render()
{
$searchTerm = '%'.$this->search.'%';
$imdb = Imdb::query()
->where(function ($query) {
$searchTerm = '%'.$this->search.'%';
$query->where('title', 'like', $searchTerm)
->orWhere('genre', 'like', $searchTerm)
->orWhere('year', 'like', $this->search);
})
->orderBy($this->sortBy, $this->sortDirection)
->paginate(10);
Log::debug($imdb);
return view('livewire.imdb-table', compact('imdb'));
}
public function changeSort($field)
{
Log::debug("Sorting by field: {$field}");
if ($this->sortDirection == 'asc' && $this->sortBy == $field) {
$this->sortDirection = 'desc';
} else {
$this->sortDirection = 'asc';
$this->sortBy = $field;
}
}
}
With our component in place, we'll tweak our Blade template to show off the data. And there you have it: a slick, interactive table full of IMDb data, ready for users to explore.
<div class="bg-gray-900 p-4 rounded-lg shadow-lg overflow-hidden relative">
<div class="mb-4">
<input wire:input.debounce.500ms="updateSearch($event.target.value)" type="text"
class="text-black px-4 py-2 rounded" placeholder="Search...">
</div>
<div class="overflow-x-auto">
<table class="min-w-full bg-gray-800 text-white rounded-lg">
<thead>
<tr>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('rank')">Rank</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('title')">Title</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('genre')">Genre</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('year')">Year</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('runtime_minutes')">Runtime (Mins)</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-200 uppercase tracking-wider"
wire:click="changeSort('rating')">Rating</th>
</tr>
</thead>
<tbody class="bg-gray-700 divide-y divide-gray-600">
@foreach ($imdb as $movie)
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->rank }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->title }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->genre }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->year }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->runtime_minutes }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $movie->rating }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="mt-4">
{{ $imdb->links() }}
</div>
</div>
Wrapping Up
So there you have it! By following the steps above, we've taken raw CSV data and turned it into a fully interactive table in our Laravel app. Livewire made it surprisingly straightforward to add cool features without leaving Laravel's comfortable ecosystem.
As developers, we're often tasked with presenting data in a way that is both informative and accessible. The tools and techniques we've explored here provide a foundation upon which we can build more complex data-driven applications. The combination of Laravel's robust architecture and Livewire's modern reactive capabilities proves to be a powerful duo for crafting rich, interactive web experiences.
In conclusion, we encourage you to continue exploring the possibilities that Laravel Livewire offers. Whether you're managing movie data or any other type of content, the principles remain the same. Keep experimenting, keep learning, and above all, keep building great things!