Back again with another question I am hoping someone might have an idea for regarding the app that I am working on.
In this educational assessment application, I have assessments which have a one-to-many relationship with Competencies (similar to a Blog Post Category) and a many-to-many relationship with Contexts (similar to a Blog Tag).
I am trying to create a report view which will allow me to show a card for each of the Competency & Context combinations and then count all assessments where that competency & context combination exists (think of this as counting how many blog posts are in each category & tag combination, even if that number is 0).
So far I am able to produce the report which lists a card for each competency and context combination but I can't figure out how to pass that information to the controller for use in the query which will find the relevant assessments.
Here is my Report View
<x-app-layout title="{{ config('app.name', 'Laravel') }}">
<div class="container grid px-6 mx-auto">
<h2 class="my-6 text-2xl font-semibold text-gray-700 dark:text-gray-200">
{{ __('Reports') }}
</h2>
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
#foreach ($competencies as $competency) <br>
#foreach ($contexts as $context)
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
{{ $competency->name}}
</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ $context->name }}
</p>
{{ $assessments }}
</div>
</div>
#endforeach
#endforeach
</div>
</x-app-layout>
Here is my Report Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Assessment;
use App\Models\Competency;
use App\Models\Context;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class ReportController extends Controller
{
public function index(Competency $competency, Context $context)
{
return view('dashboard.reports.index', [
'competencies' => Competency::where('team_id', Auth::user()->currentTeam->id)->get(),
'contexts' => Context::where('team_id', Auth::user()->currentTeam->id)->get(),
'assessments' => Assessment::where('competency_id', $competency->id)->whereRelation('contexts', 'context_id', $context->id)->count(),
]);
}
}
Here is an image of what this currently produced in the Report View: https://imgur.com/a/SQSf7UM
Please let me know if there is additional detail which would be helpful
it's better you declare the relation in the model, let's assume:
an assessment has one category,
an assessment has many contexts,
You can create an additional table with its model to store assessment contexts (because it assessent can has many contexts), let's say the table name is assessment_contexts and the model name is AssesmentContext. Or you can just run php artisan make:model AssessmentContext -m.
At least it has 2 columns, assessment_id and context_id,
Then inside AssessmentContext, add this function to create a simple relation,
public function assessment() {
return $this->hasOne(Assessment::class);
}
public function context() {
return $this->hasOne(Context::class);
}
Declare this functions inside the Assessment model,
public function competency() {
return $this->hasOne(Competency::class);
}
public function contexts() {
return $this->hasMany(AssesmentContext::class);
}
And add the this to the Competency model,
public function assessments() {
return $this->hasMany(Assessment::class);
}
And this for the Context model,
public function assessments() {
return $this->hasMany(AssessmentContext::class);
}
You have to add foreign key inside assessments table that refers to category primary key id, (I recommend you the column name is category_id).
Then finally in your controller, you can just declare the competencies list, and inside your view, you can access all the relate data. i.e:
return view('dashboard.reports.index', [
'competencies' => Competency::where('team_id', Auth::user()->currentTeam->id)->get()
]);
Inside view,
#foreach ($competencies as $competency) <br>
#foreach ($competency->assessments as $assessment)
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
{{ $competency->name}}
</p>
#foreach ($assessment->contexts as $context)
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ $context->name }}
</p>
#endforeach
</div>
</div>
#endforeach
#endforeach
This is just simple scheme of relation, there are other ways to do it that maybe better and more optimal.
Related
<div class="bg-white dark:bg-custom-black-600 dark:text-white rounded px-4 pb-4 pt-2">
<div class="flex-shrink-0 flex justify-between items-center">
<h4 class="font-semibold flex-shrink-0">
{{ __('services') }}
</h4>
<i class="ri-hard-drive-2-fill text-blue-600 dark:text-blue-600"></i>
</div>
<p class="text-3xl text-blue-600">
{{ Auth::user()->services->count() }}
</p>
</div>
this is my code from the component card.blade.php. Now the question: "Can I make a custom value that I have to specify in the tag <x-card title="" service=""> or is that not possible? I would like {{ __('services') }} and {{ Auth::user()->services->count() }} not to be static in the code example.
You'd need to write a component class to handle variables in the template. That class would interpret the input and store it in variables that can be accessed in the template. This is all described in the documentation.
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Card extends Component
{
public function __construct(
public string $title,
public string $service,
)
{}
public function render()
{
return view('components.card');
}
}
Call the component like this:
<x-card title="Something" service="something else"/>
And you should then be able to use $title and $service in the Blade template file. Note if you want to use PHP values, you'll need to do something like this:
<x-card :title="__('something')" service="something else"/>
Note the colon preceding the attribute name.
I'm new to Livewire with some laravel experience and a good amount of php experience. My issue I'm trying to get a livewire search component to work. I'm using laravel 8 and livewire is installed. My nav menu is using livewire. SO I wish to insert a search into my layout as follows:
<body class="font-sans antialiased">
<x-jet-banner />
<div class="min-h-screen bg-gray-100">
#livewire('navigation-menu')
#livewire('search-accounts')
... rest of my layout ...
</div>
</body>
I have a Model Account which has the Accounts used by my app. Account uses Eloquent and is working fine.
I have created a livewire component app/Http/Livewire/SearchAccounts.php with the following content:
<?php
namespace App\Http\Livewire;
use App\Models\Account;
use Livewire\Component;
class SearchAccounts extends Component
{
public $search = '';
public function render()
{
return view('livewire.search-accounts', [
'accounts' => Account::where( 'name', $this->search )->get(),
]);
}
}
My blade template is resouces/views/livewire/search-accounts.blade.php and is as follows:
<div class="px-4 space-y-4 mt-8">
<form method="get">
<input class="border-solid border border-gray-300 p-2 w-full md:w-1/4"
type="text" placeholder="Search Accounts" wire:model="search"/>
</form>
<div wire:loading>Searching accounts...</div>
<div wire:loading.remove>
<!--
notice that $search is available as a public
variable, even though it's not part of the
data array
-->
#if ($search == "")
<div class="text-gray-500 text-sm">
Enter a term to search for accounts.
</div>
#else
#if($accounts->isEmpty())
<div class="text-gray-500 text-sm">
No matching result was found.
</div>
#else
#foreach($accounts as $account)
<div>
<h3 class="text-lg text-gray-900 text-bold">{{$account->name}}</h3>
<p class="text-gray-500 text-sm">{{$account->url}}</p>
<p class="text-gray-500">{{$account->ipaddress}}</p>
</div>
#endforeach
#endif
#endif
</div>
</div>
When I view my app in the browser the search bar is shown and contains a placeholder called Search Accounts. I click the search bar and start typing but no search is done (my browsers dev tools show no Network or console activity).
Have I missed something? I've tried php artisan livewire:discover and cleared caches. I have followed a tutorial (https://laravel-livewire.com/) and not sure why I'm not getting the expected results. Other posts like this are actually getting some network activity as the ajax requests are made which I'm not seeing at all.
thanks
Craig
Turned out that my #livewireScripts wasn't included in my master blade template. Once this was added livewire worked.
Try changing this
<input class="border-solid border border-gray-300 p-2 w-full md:w-1/4"
type="text" placeholder="Search Accounts" wire:model="search"/>
to
<input class="border-solid border border-gray-300 p-2 w-full md:w-1/4"
type="text" placeholder="Search Accounts" wire:model.debounce.500ms="search"/>
This should send a network request at most every 500ms after an event (helps to not hammer the backend with every keypress).
Have a look at this page of the docs https://laravel-livewire.com/docs/2.x/actions
I am trying to display all entries of a database in the frontend, which worked fine until I needed to get Post via a join with another table.
Now when I loop through the posts I get a lot of duplicate outputs and I cant get my head around how to accomplish the correct output.
Here is my controller code:
public function getApplications()
{
$query = Bewerbungen::query();
$bewerbungen = $query->orderBy('Bewerbung_ID')->get();
$stellenanzeigen_name = Post::join('bewerbungens', 'posts.id', '=', 'bewerbungens.Stellenanzeigen_ID')
->get(['bewerbungens.Stellenanzeigen_ID' , 'posts.Titel']);
$data = [
'bewerbungen' => $bewerbungen,
'stellen_names' => $stellenanzeigen_name,
];
return view('bewerbungen_overview')->with($data);
}
Here is my blade code:
#foreach($bewerbungen as $bewerbung)
#foreach($stellen_names as $stellen_name)
#if($bewerbung->Stellenanzeigen_ID === $stellen_name->Stellenanzeigen_ID)
<div
class="p-10 grid-cols-3 grid-rows-3 gap-4 shadow-2xl mb-10 bg-gradient-to-r from-green-400 to-blue-500 border-solid border-2 border-black rounded-lg">
<!--Card 1-->
<div
class="overflow-hidden row-span-3 bg-gray-100 shadow-2xl border-solid border-2 border-gray-500 rounded-lg">
<div class="pt-2 pl-6 mt-3"> {{ $stellen_name->Titel }}</div>
<div class="pt-4 pl-8 font-medium text-xl font-bold font-serif">
Kontakt: {{ $bewerbung->bewerber_email }}</div>
</div>
</div>
#endif
#endforeach
#endforeach
So pretty much I am checking if the the ID's match and if they do I want to display the data. but I get a lot of duplicate outputs.
I thought about ending the loop when the number of database entries is reached, but I don't know if that's possible /makes sense.
I am fairly new to laravel so any help appreciated :)
Edit: Here is a picture of the output when I dd($data)
https://i.stack.imgur.com/ba7pc.png
This has the right amount of entries and the correct data
I want use Eager loading in Laravel 8 with a relations of two foreign keys to the same table.
Teams table
id
name
120
Germany
245
Italy
Fixtures table
id
timestamp
home_id
away_id
winner_id
home_goals
away_goals
1
1607803200
120
245
120
2
0
Fixture Model
public function home()
{
return $this->belongsTo(Team::class, "home_id");
}
public function away()
{
return $this->belongsTo(Team::class, "away_id");
}
public function winner()
{
return $this->belongsTo(Team::class, "winner_id");
}
In controller, even with $fixtures = Fixture::with(["home", "away", "winner"])->get(); the N+1 Query detector package advise me from that problem.
Are there any way to use eager loading with multiple foreign keys belongs to the same table??
If not, any suggestions to modify the database structure in any better way?
Thank you!
UPDATED:
Add the requested info. Seen in the laravel debugbar now, I see only one duplicate query and I think it´s makes sense, are the two relations loaded in with..
So, it´s correct?
Using the data in the view:
#foreach($fixtures as $fixture)
<div class="flex items-center ">
<div>
<img class="object-scale-down h-12 w-12 border border-indigo-600" src="{{ $fixture->home->logo }}" />
</div>
<div class="flex-auto ml-5">
{{ $fixture->home->name }}
</div>
<div class="flex-auto">
</div>
<div class="flex-auto mr-5 text-right">
{{ $fixture->away->name }}
</div>
<div class="">
<div>
<img class="object-scale-down h-12 w-12 border border-indigo-600" src="{{ $fixture->away->logo }}" />
</div>
</div>
</div>
#endforeach
Data from Laravel DebugBar:
Description
I am type hinting model properties and trying to delete the invitation data. Below is the error which its throwing me back. Please help me with it, as I am unable to spot what is it that I'm missing.
Typed property App\Http\Livewire\Backend\UserManagement\FormComponent\InvitationManagementModal::$invitation must not be accessed before initialization
Stripped-down, copy-pastable code snippets
Livewire\InvitationManagementModal.php
<?php
namespace App\Http\Livewire\Backend\UserManagement\FormComponent;
use Livewire\Component;
use Livewire\WithPagination;
use App\Http\Livewire\Backend\DataTable\WithCachedRows;
use App\Models\Invitation;
class InvitationManagementModal extends Component
{
use WithPagination, WithCachedRows;
public $showInvitationManagementModal = false;
public Invitation $invitation;
protected $listeners = ['manageInvitation'];
public function manageInvitation()
{
$this->showInvitationManagementModal = true;
}
public function deleteInvitation(Invitation $invitation)
{
$this->invitation->delete();
}
public function getInvitationRowsProperty()
{
return $this->cache(function () {
$invitations = Invitation::where('registered_at', null)->paginate(5);
return $invitations;
});
}
public function render()
{
return view('livewire.backend.user-management.form-component.invitation-management-modal', ['invitations' => $this->invitationRows]);
}
}
livewire\invitation-management-modal.blade.php
<div>
<x-modal.stacked wire:model.defer="showInvitationManagementModal" id="scroll-lock">
<x-slot name="title">Manage Invitation</x-slot>
<x-slot name="description">Manage all the invitations which are yet to be accepted.</x-slot>
<x-slot name="content">
<div class="p-8 space-y-4">
<ul class="flex flex-col divide divide-y w-full bg-white rounded-lg shadow">
#forelse($invitations as $key => $invitation)
<li class="flex flex-row">
<div class="flex flex-1 items-center px-8 py-4">
<div class="flex-1 mr-16">
<div class="text-sm dark:text-white">
{{ $invitation->email }}
</div>
</div>
<button wire:click="deleteInvitation" class="text-right flex justify-end">
<x-icon.trash />
</button>
</div>
</li>
#empty
#endforelse
</ul>
<div>
{{ $invitations->links() }}
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-button.secondary wire:click.defer="$set('showInvitationManagementModal', false)">Cancel</x-button.secondary>
</x-slot>
</x-modal.stacked>
</div>
Context
Livewire version: 2.3.5
Laravel version: 8.20.1
Alpine version: 2.8.0
Browser: Chrome
The other answers here both have some minor things to note about them. You don't have to check $invitation, because the typehinting Invitation makes Laravel use Model-Route-Binding, which fetches the corresponding record - or throws a HTTP 404 status code if not found.
Secondly, and this is the actual error you are currently seeing yourself, is that you don't have to do anything to the $this->invitation, since its not set. You should instead pass a parameter to the method.
When looping data in Livewire, it is always recommended to use wire:key, so that Livewire can keep track of each record in the loop.
So for the actual delete method, just call the delete method on the input-variable.
public function deleteInvitation(Invitation $invitation)
{
$invitation->delete();
// Emit an event to notify the user that the record was deleted
// Refresh the parent component to remove the invitation from the list
}
For your blade, add wire:key to the first element in the loop and pass the ID to the method.
(so wire:click="deleteInvitation({{ $invitation->id }})" instead of wire:click="deleteInvitation").
#forelse($invitations as $key => $invitation)
<li class="flex flex-row" wire:key="invitation_{{ $invitation->id }}">
<div class="flex flex-1 items-center px-8 py-4">
<div class="flex-1 mr-16">
<div class="text-sm dark:text-white">
{{ $invitation->email }}
</div>
</div>
<button wire:click="deleteInvitation({{ $invitation->id }})" class="text-right flex justify-end">
<x-icon.trash />
</button>
</div>
</li>
#empty
#endforelse
This in turn means that, since its never used, you can remove the declaration of the $invitation property of that class, the line just after public $showInvitationManagementModal = false;.
public Invitation $invitation;
Try this:
public function deleteInvitation(Invitation $invitation)
{
$this->invitation = $invitation;
$this->invitation->delete();
}