How to order column by relationship in Laravel Livewire - laravel

I've just followed this tutorial about implementing a data table with Livewire in Laravel.
So far so good. Now I want to order a column that comes from a one-to-one relationship, but I just can't get the sorting work well when selecting this phones relationship column.
In this case, phones is the relationship method and number is the column I display in the table, which I want to allow the sorting, as well.
How do I implement the sorting by column relationship?
Here is part of my code of the livewire blade (original repo of the tutorial):
<select wire:model="orderBy" class="...">
<option value="firstname">Name</option>
<option value="lastname">Lastname</option>
<option value="email">E-mail</option>
<option value="phones">Phone number</option>
</select>
...
<table class="table-auto w-full mb-6">
<thead>
<tr>
<th class="px-4 py-2">Name</th>
<th class="px-4 py-2">Lastname</th>
<th class="px-4 py-2">E-mail</th>
<th class="px-4 py-2">Phone number</th>
</tr>
</thead>
<tbody>
#foreach($users as $user)
<tr>
<td class="border px-4 py-2">{{ $user->firstname }}</td>
<td class="border px-4 py-2">{{ $user->lastname }}</td>
<td class="border px-4 py-2">{{ $user->email }}</td>
<td class="border px-4 py-2">{{ $user->phones->number }}</td>
</tr>
#endforeach
</tbody>
</table>
{!! $users->links() !!}
And here is the Livewire controller (original repo of the tutorial):
class UsersTable extends Component
{
use WithPagination;
public $perPage=5;
public $search = '';//Search string
public $orderBy='firstname';
public $orderAsc=true;
public $relationship_columns=['phones'];
public function render()
{
if(!in_array($this->orderBy,$this->relationship_columns)){
$users = User::search($this->search)
->orderBy($this->orderBy,$this->orderAsc ? 'asc' : 'desc')
->simplePaginate($this->perPage);
}else if($this->orderBy=='phones'){
$order = $this->orderAsc ? 'asc': 'desc';
$users = User::search($this->search)
->with(['phones' => function ($q) use($order){
$q->orderBy('number',$order);
}])
->simplePaginate($this->perPage);
}
return view('livewire.users-table',compact('users'));
}
}
Reference.
The sorting of (order by) phones is not working well. It seems it sorts some parts well, but in general, sorting is flawed. It can be sorted neither ascendant nor descendant.
Looks like the sorting type (asc,desc) is not taking effect in
$q->orderBy('number',$order);
Moreover, if I use the whereHas() method:
$order = $this->orderAsc ? 'asc': 'desc';
$users = User::search($this->search)
->whereHas('phones',function ($q) use($order){
$q->orderBy('number',$order);
})
->simplePaginate($this->perPage);
I get the following error:
SQLSTATE[HY000]: General error: 20018 The ORDER BY clause is invalid
in views, inline functions, derived tables, subqueries, and common
table expressions, unless TOP, OFFSET or FOR XML is also specified.
[20018] (severity 15)
What am I missing? Any ideas on how to fix this?
How do I implement the order by functionality on the relationship column in Livewire?

Based on these posts Ordering database queries by relationship columns in Laravel and Laravel Eloquent whereColumn Query Example, I have solved the ordering by relationship this way:
I set any name to the relationship, in the parameter search function, let's say phone.number.
Then, with an if statement I detect if the ordering is that relationship:
if($this->field == 'phone.number'){
$users = $users->orderBy(\App\Models\Phone::select('number')->whereColumn('users.phone_id','phones.id'));
}else{
$users = $users->orderBy($this->field,$this->order);
}
Moreover, in this answer it is explained how to search inside a column relationship.

You can use
$user = User::search($this->search)
->whereHas('phones', function ($q){
$q->orderBy('number', $order);
})
->simplePaginate($this->perPage);
instead of
$users = User::search($this->search)
->with(['phones' => function ($q) use($order){
$q->orderBy('number',$order);
}])
->simplePaginate($this->perPage);

Related

How do i count for a specific data in a column in Laravel?

I have two models, One is Employees and other one is Skill. One Employee can have many skills. Now I'm trying to show number of employees that possesses each consecutive skill in skills view page.
My Skills View Page:
<tbody>
#foreach ($skills as $skill)
<tr>
<th scope="row">{{ $loop->index+1 }}</th>
<td>{{ $skill->skill_name }}</td>
<td>{{ $skill->totalEmp($skill->id) }}</td>
<td style="width: 25%;">
<button class="btn btn-outline-danger" type="button" title="Delete Skill">Delete Skill</button>
</td>
</tr>
#endforeach
</tbody>
Skills Model:
class Skill extends Model
{
use HasFactory;
protected $fillable = [
'skill_name'
];
public function Employee()
{
return $this->hasMany(Employees::class, 'skill_id', 'id');
}
public function totalEmp($id){
return Employees::where('status', 1)->where('skill_id','=',$id)->count();
}
}
Employees Model:
class Employees extends Model
{
use HasFactory;
protected $guarded = [];
protected $table = 'employees';
public function Skills(){
return $this->hasMany(Skill::class);
}
}
Employee table has->skill_id, other irreverent fields || Skill Model has->id and skill_name field.
You can use withCount method for this.
https://laravel.com/docs/9.x/eloquent-relationships#counting-related-models
On your Skill model you have Employee relationship defined.
So you will be able to do something like this:
$skills = Skill::withCount(['Employee'])->get();
// Prints out number of employees that posses first skill
echo $skills[0]->employee_count;
Of course you can iterate through $skills with for loop, or foreach and similar, or even use collection methods.
<tbody>
#foreach ($skills as $skill)
<tr>
<th scope="row">{{ $loop->index+1 }}</th>
<td>{{ $skill->skill_name }}</td>
<td>{{ $skill->employee_count }}</td>
<td style="width: 25%;">
<button class="btn btn-outline-danger" type="button" title="Delete Skill">Delete Skill</button>
</td>
</tr>
#endforeach
</tbody>
If you still want to use your method totalEmp you can modify it like this:
public function totalEmp(){
return $this->employee()->count();
}
One other thing that I strongly recommend is to make sure you name your models and relationships properly.
Models should be named like: Skill and Employee
Relationships should be named like: employees() and skills()
Please see example here: https://laravel.com/docs/9.x/eloquent-relationships#many-to-many

Foreach inside Foreach based on column value

I have an sql table knowledgebase like so
id , categoryid, title
id
categoryid
title
1
1
apple
2
1
fb
3
2
google
4
2
DB
5
3
Reebok
In my laravel blade, I am trying to create a tree view to look like the following
-1
--apple
--FB
-2
--google
--DB
-3
--Reebok
My controller does a basic query and returns the entire table to the view. I am a newbie to laravel so far, I can get a basic table to work like
#foreach($knowledgebase as $key => $value)
<tr>
<td>{!! $knowledgebase ->id !!}</td>
<td>{!! $knowledgebase ->title!!}</td>
<td>{!! $knowledgebase ->categoryid !!}</td>
</tr>
#endforeach
How would I iterate categroyid column , display first category and all child titles and then move on to the next categoryid.
Update
public function show($id) {
//get article
$knowledgebase = \App\Models\Knowledgebase::Where('knowledgebase_slug', request('knowledgebase_slug'))->first();
return view('knowledgebase', compact('knowledgebase'));
}
You will need to retrieve in controller the categories before ($categories)
#foreach($categories as $category)
<tr>
<td>{{ $category->id }}</td>
<td>{{ $category->title}}</td>
<td>{{ $category->categoryid }}</td>
</tr>
#endforeach
Anyways, I managed to solve this by adding a function children in my model. So in my Model added
public function children(){
return $this->hasMany('App\Models\Knowledgebase', 'categoryid');
}
After this, I simply called it as a foreach inside my foreach
#foreach($categories as $category)
<li>{{ $category->title}}</li>
#if($category->children)
#foreach($category->children as $child)
<ul>
<li class="pl-3"> {{ $child->title}} </li>
</ul>
#endforeach
#endif
#endforeach

How to handle missing records of a Product's attribute value in laravel 7

Background Information:
I'm learning laravel 7 by creating an inventory system.
I have several models: Product, Stock, and Attribute.
The Product is like a group that holds all of the stock. Because I can have a Product like a speaker. But I can have many quantities of that same product that have different attribute values. This is when the Stock becomes important.
The Stock is where I have all of the items in my inventory.
For example: If I have 20 speakers, there will be one record in the products table and 20 records in the stocks table.
The Attribute is because I want to track different attributes depending on the Product. But the value of those attributes doesn't belong to the product; It belongs to the Stock of the product.
This is how my models relate:
A Product hasMany Stock
A Stock belongsTo a Product
A Product hasMany Attribute
An Attribute belongsTo a Product
An Attribute belongsToMany Stock
A Stock belongsToMany Attribute (I'm not very confident about this one.)
Goal:
The Product is L-Acoustics LA12X
The product's Attributes are Serial Number and Firmware
The product's Stock is the card that says "Stock Level".
For testing and simplicity, I copied the same values of Serial Number and Firmware to all of the Product's Stock; but in real life they should all have different serial numbers and firmware versions.
Problem:
I only get the output from the picture above when there is a value for each attribute. For example: if I delete the attribute value of the Serial Number from the first stock I get this.
Do you see how it shifted the Firmware value to the Serial Numbers column?
In the case that the record is deleted or the value hasn't been created I would like to put an empty cell in that table so that it looks like this. (I created this image by editing the HTML of the page, I still don't know how to get this output)
What I have tried:
I quickly thought of creating empty cells in my database belonging to the attribute so that when I loop through my records I get a perfect table. But this will quickly become a problem when my database increases size.
You could think of why would there be records that don't exist? Well one example is that my inventory is so old that there are some Serial Numbers that I can't retrieve. Therefore, there will be no records in the table.
Showing some code:
This is my database design:
This is my Product Model:
class Product extends Model {
protected $guarded = [];
protected $casts = [
'brand_id' => 'integer',
'category_id' => 'integer',
'price' => 'decimal:2',
'rental_price' => 'decimal:2',
'active' => 'boolean'
];
public function setNameAttribute($value)
{
$this->attributes['name'] = $value;
$this->attributes['slug'] = Str::slug($value);
}
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function stocks()
{
return $this->hasMany(Stock::class)->orderBy('barcode', 'asc');
}
public function attributes()
{
return $this->hasMany(Attribute::class);
}
}
This is my Stock Model:
class Stock extends Model {
protected $guarded = [];
protected $casts = [
'quantity' => 'integer',
'product_id' => 'integer',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function attributes()
{
return $this->belongsToMany(Attribute::class)->withPivot('value');
}
}
This is my Attribute Model:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
// This method is empty because I don't know how to relate the attribute to its values.
}
I know I have dumped a lot of information but I really need help. I have an open mind and I'm open to new implementations.
UPDATE
Here is the code of my view:
<div class="block-content block-content-full">
<div class="table-responsive">
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($stock->attributes as $attribute)
<td class="text-center">{{ $attribute->pivot->value }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
</div>
</div>
Here is the show method in my controller:
public function show(Product $product)
{
return view('products.show', compact('product'));
}
UPDATE 2:
I updated my Attribute model to this:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function getValueByBarcode($stock)
{
return $this->values()->where('barcode', $stock)->first()->value ?? '';
}
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
return $this->belongsToMany(Stock::class)->select('value');
}
}
and in my blade I got the values like this:
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($product->attributes as $attribute)
<td class="text-center">{{ $attribute->getValueByBarcode($stock->barcode) }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
This is the result of that change
I finally managed to get what I wanted but is there a cleaner way of doing the same thing?
I would use keyBy on your collection of stock attributes (Untested)
#php($s_attributes = $stock->attributes->keyBy('attribute_id'))
#foreach($product->attributes as $p_attribute)
<td class="text-center">{{ isset($s_attributes[$p_attribute->id]) ? $s_attributes[$p_attribute->id]->pivot->value : ''}}</td>
#endforeach

Display list of purchase order base from filtered vendor in Laravel eloquent

I am trying to display the list of vendor with their purchase orders in a table.
VENDOR MODEL
public function vendorPo()
{
return $this->hasMany('App\PurchasedOrder', 'vendor_id','vendor_id');
}
VENDOR CONTROLLER
public function vendor()
{
$vendorPo = Vendor::with('vendorPo')
->get()
->keyBy('id')
->groupBy('vendor_name');
$purchaseOrders = PurchasedOrder::all()->where('publish','=','1');
return view('backend.purchase-order.vendor', compact('purchaseOrders'))
->with('vendorPo', $vendorPo);
}
for my index
#foreach ($vendorPo as $vendor => $vendor_list)
<tr>
<th colspan="7"
style="background-color: #F7F7F7"> {{ $vendor }}: ({{ $vendor_list->count() }} vendors)
</th>
</tr>
#foreach ($vendor_list as $key => $vendorPoList)
<tr>
<td>
{{ $vendorPoList->vendorPo->po_id}}
</td>
</tr>
#endforeach
#endforeach
if I do this
...
#foreach ($vendor_list as $key => $vendorPoList)
<tr>
<td>
{{ $vendorPoList}}
</td>
</tr>
#endforeach
...
the result is this
and if I
...
<td>
{{ $vendorPoList->vendorPO->po_id}}}
</td>
...
the result is error
Facade\Ignition\Exceptions\ViewException
Property [po_id] does not exist on this collection instance. (View:
Please help me. Thanks!
You are getting this issue because your vendorPO relation is a HasMany relation. If you pull back the records from that sort of relation, the results are always an array (even if there is only one record), which is always converted by Eloquent into a Collection.
Therefore, $vendorPoList->vendorPO is a Collection, and there is no variable on a Collection called po_id.
Depending on your data model, you should either revisit the relation, or do a #foreach on $vendorPoList->vendorPO.

Accessing and displaying pivot table data

Theory:
Users can attend many events and many events can be attended by many users. Therefore, I have two many-to-many relationships within my models, linking to a pivot table (event_user). On attending each event, I want to be able to access the pivot table data (event_user) to see if they're already attending.
event_user:
--id
--event_id
--user_id
For example, my seed data is:
user 1 attending both event 2 and 3. I want to be able to show this within a view.
The closest I have got is (logically):
public function index()
{
$id = Auth::user()->id;
$attending = myApp\Event::find($id)->event;
var_dump($attending); **But this var_dump returns NULL, but $id returns the correct ID.**
$events = myApp\Event::all();
$this->layout->content = View::make('events.index')->with('events', $events);
}
My aim is to disable the 'attend' button, on any event where they are already attending, only leaving the attend-able events available!
Any help would be hugely appreciated. Thank you in advance.
Any additional code which you may find necessary:
Events Model:
<?php
namespace myApp;
use Eloquent;
class Event extends Eloquent {
public function consultant()
{
return $this->belongsToMany('myApp\Consultant');
}
public function location()
{
return $this->belongsToMany('myApp\Location');
}
public function user()
{
return $this->belongsToMany('myApp\User');
}
}
User Model:
<?php
namespace myApp;
use Eloquent;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
public function event()
{
return $this->belongsToMany('Event');
}
public function practice()
{
return $this->belongToMany('Practice');
}
index.blade.php (showing the event list)
<div class="panel panel-success">
<!-- Default panel contents -->
<div class="panel-heading"><strong>Events</strong></div>
<!-- <div class="panel-body">
</div> -->
<!-- Table -->
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Date</th>
<th>Presenter</th>
<th>Location</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach($events as $event)
<tr>
<td>{{ $event->title }}</td>
<td>{{ date("j F Y", strtotime($event->date)) }}</td>
<td>{{ $event->consultant()->first()->title }} {{ $event->consultant()->first()->surname }}</td>
<td>{{ $event->location()->first()->address_1 }}</td>
<td><button type="button" class="btn btn-info">Attend</button></td>
</tr>
#endforeach
</tbody>
</table>
</div>
</table>
I think you're going about this the wrong way, that or I have misunderstood.
Firstly, you're trying to find an event with the same primary key as the currently authenticated user, which isn't correct, although it's an easy hole to fall down.
$id = Auth::user()->id;
$attending = myApp\Event::find($id)->event;
// equivalent of: SELECT * FROM `events` WHERE `id` = ?
Instead you'll want to do this
$id = Auth::user()->id;
$attending = myApp\Event::where('user_id', $id)->get();
// equivalent of: SELECT * FROM `events` WHERE `user_id` = ? (user_id insted of events.id)
That being said, surely the user events can be accessed by just calling the event property on the auth user?
$user = Auth::user();
$attending = $user->event;
To take this one step further, and make it so that you can check inside the foreach loop, you could advanced the above code to look like the following
$user = Auth::user();
$attending = $user->event->lists('id');
This will make an array of ids from the returned events that you need to assign to the view
$this->layout->content = View::make('events.index', array('events' => $events, 'attending' => $attending));
Now you can freely access it in your foreach
#foreach($events as $event)
<tr>
<td>{{ $event->title }}</td>
<td>{{ date("j F Y", strtotime($event->date)) }}</td>
<td>{{ $event->consultant()->first()->title }} {{ $event->consultant()->first()->surname }}</td>
<td>{{ $event->location()->first()->address_1 }}</td>
<td>
#if (!in_array($event->id, $attending))
<button type="button" class="btn btn-info">Attend</button>
#endif
</td>
</tr>
#endforeach
Also, seeing as Event is a reserved Alias (unless you've modified the config, which I wouldn't recommend), you'll want to specify the namespace in the relationship declaration within User
public function event()
{
return $this->belongsToMany('myApp\Event');
}
As a final point, it's not an issue as such, but in my own code I try to name relationships that have the potential to return multiple objects in the plural, so it would be public function events();.

Resources