Display tree structure in laravel blade - php

i am trying to display tree structure in blade, and i need i little help.
So Here is my algorithm
public function fetchData($entry_id)
{
$results = TreeEntry::where('parent_entry_id', $entry_id)->get();
$treeEntryList = [];
foreach ($results as $result) {
$data = [
'id' => $result->entry_id,
'parent_entry_id' => $result->parent_entry_id,
'children' => $this->fetchData($result->entry_id)
];
$treeEntryList[] = $data;
}
And getting this kind of tree array
And trying something like this, but only first child node getting displayed, and i want to show all elements.
Any idea how to solve this? Recursion here, or maybe something else?
#extends('layouts.master')
#section('content')
{{dd($resultList)}}
<div class="container">
<br id="lang-container">
#foreach ($resultList as $result)
<div>{{$result['id']}}</div></br>
#foreach ($result['children'] as $child)
<div>{{$child['id']}}</div></br>
#endforeach
#endforeach
</div>
#endsection
#section('js')
#endsection

Idea in my mind, is to use components, but didn't really used them yet, so, not sure if this work or not. First of all, run php artisan make:component TreeEntryListing, to create the component. Then, go to the created component, and insert this piece of code (Fix it if there is any problem with it)
#foreach ($children as $child)
<div>{{$child['id']}}</div></br>
#if(isset($children['children']))
<x-tree-entry-listing children={{$children}}/> // If it had syntax error, use " for children
#endif
#endforeach
Then, go to TreeEntryListing file and add $children to its constructor:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class TreeEntryListing extends Component
{
public $children;
/**
* Create a new component instance.
*
* #param $children
*/
public function __construct($children)
{
$this->children = $children;
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.tree-entry-listing');
}
}
And then, use this for the primary view:
...
#foreach ($resultList as $result)
<div>{{$result['id']}}</div></br>
<x-tree-entry-listing children={{$result['children']}} />
#endforeach
...
Hope it work!

Related

Facing strange problem in Laravel Components

I am passing data to the view of the component by $user->profile->profile_pic when I dd in that view it shows the desired value perfectly. But when I use that in some conditions or in tags to print that value it says that Attempt to read property "profile_pic" on null. Although, It is not because I can die and dump that and that value can be seen
Usage of the component:
<x-details
:user="$user->id"
class="w-96 mt-10 py-4"
letter="{{ $user->username[0] }}"
editable="{{ Auth::user()->username == $user->username }}"
profile_pic="{{ $user->profile->profile_pic }}"
/>
The component
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class details extends Component
{
/**
* Create a new component instance.
*
* #return void
*/
public $user;
public function __construct($user = 1)
{
$this->user = $user;
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
$user = User::with(['profile'])->firstWhere("id", $this->user);
$pic = $user->profile->profile_pic;
return view('components.details', compact("pic"));
}
}
The view of the component
<div>
#props([
"letter" => "A",
"editable" => 0,
"profile_pic" => 0
])
{{-- #php
$src = "";
if($profile_pic) {
$src = "/uploads/$profile_pic";
} else {
$src = url("fonts/icons/avatars/$letter.svg");
}
#endphp --}}
<div>
{{-- #dd($pic) --}}
{{ $pic }}
{{-- #if(!$editable)
#else
<form id="fileUpload">
<input class="hidden" type="file" name="upload_pic" id="upload_pic">
</form>
#endif --}}
</div>
</div>
It's a common issue when you trying to dd() something in foreach, it will always dump first item only and die, so you are always confirm first item and think it work as well as expected.
In your case, there is probably some user doesn't have profile_pic or profile don't have any profile_pic related on it.
Try to use the code below to debug with it in your component.
public function render()
{
try {
$user = User::with(['profile'])->firstWhere("id", $this->user);
$pic = $user->profile->profile_pic;
return view('components.details', compact("pic"));
} catch (Exception $e) {
dd($user->profile);
}
}
Inside the component, you should use $this:
So instead of
$pic = $user->profile->profile_pic
You should do
$pic = $this->user->profile->profile_pic

How to remove query string page from the first page of laravel pagination?

I'm using laravel-5.4 pagination like the following:
public function index()
{
$jobs = Job::paginate(5);
return view('job.index', compact('jobs'));
}
In the view:
{{ $jobs->links() }}
There is a problem of generating two typical pages: /job and /job?page=1 the two page has the same contents.
I want to do anything that removes the query string page from the first page of the pagination.
I have tried the following:
if ($jobs->onFirstPage()){
$jobs->setPageName('');
}
But this corrupt the pagination, i.e the links of pages does not load correctly and the query string value remains for all pages.
The effective solution for this issue that I have found is to edit the pagination template.
First publish the pagination template from the vendors using the following command from the root of the project:
php artisan vendor:publish --tag=laravel-pagination
Now a file at resources/views/vendor/pagination/default.blade.php should be found and it could be edited like the following using str_replace() for the urls of each page and back navigation button:
<li>{{$foxPrev}}</li>
and
<li>{{ $page }}</li>
Update:
A bug was found with ?page=10 so instead of using str_replace we should using preg_replace like the following:
<li>{{ $page }}</li>
Update 2:
In case of using any customized name for the page number parameter other than page, we could use the paginator getter for the $pageName property like the following in the pagination template:
<li>{{ $page }}</li>
To know more about how to use more than one pagination on the same page or how to customize the page number parameter $pageName from view, checkout this answer
You can extend LengthAwarePaginator method url($page)
/**
* Get the URL for a given page number.
*
* #param int $page
* #return string
*/
public function url($page)
{
if ($page <= 0) {
$page = 1;
}
// If we have any extra query string key / value pairs that need to be added
// onto the URL, we will put them in query string form and then attach it
// to the URL. This allows for extra information like sortings storage.
$parameters = ($page > 1) ? [$this->pageName => $page] : [];
if (count($this->query) > 0) {
$parameters = array_merge($this->query, $parameters);
}
return rtrim($this->path
.(Str::contains($this->path, '?') ? '&' : '?')
.http_build_query($parameters, '', '&')
.$this->buildFragment(), '?');
}
improved Aksi answer
app/Services/CustomLengthAwarePaginator.php
<?php
namespace App\Services;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class CustomLengthAwarePaginator extends LengthAwarePaginator
{
/**
* Get the URL for a given page number.
*
* #param int $page
* #return string
*/
public function url($page)
{
if ($page <= 0) {
$page = 1;
}
// If we have any extra query string key / value pairs that need to be added
// onto the URL, we will put them in query string form and then attach it
// to the URL. This allows for extra information like sortings storage.
$parameters = ($page > 1) ? [$this->pageName => $page] : [];
if (count($this->query) > 0) {
$parameters = array_merge($this->query, $parameters);
}
return $this->path()
. (count($parameters) > 0
? (Str::contains($this->path(), '?') ? '&' : '?')
: '')
. Arr::query($parameters)
. $this->buildFragment();
}
}
in AppServiceProvider.php or another
public function boot()
{
app()->bind(LengthAwarePaginator::class, CustomLengthAwarePaginator::class);
}
add canonical tag in if you care SEO
i don't have any idea...
if($pageNum==1){
return redirect()->to($path);
}
$pageNum id get Request or urself Class
$path = "/jobs"
In your routes, give a name to the route which leads to your function:
Route::get('/yourRoute','YourController#foo')->name('yourRouteName');
Then, In your function in the controller, use this:
public function foo() {
if( request()->page=='1' )
{
return redirect()->route('yourRouteName',[$id]);
}
else {
// Your function content
}
}
I think you can try this:
{{ $jobs->links() }}
To
{{ $jobs->nextPageUrl() }}
Hope this work for you!
You can simply modify the view like this:
#if ($page==1)
{{ $page }}
#else
{{ $page }}
#endif
I found the following works and takes into account page=10 issues:
After you export your pagination views, make the following 2 modifications:
MODIFICATION 1
Replace this:
{{ $paginator->previousPageUrl() }}
with this:
{{ preg_replace('/(?:(&|\\?)page=[1])(?!\\d)/ui','', $paginator->previousPageUrl()) }}
MODIFICATION 2
Replace this:
{{ $url }}
with this:
{{ preg_replace('/(?:(&|\\?)page=[1])(?!\\d)/ui','', $url ) }}
I've done it this way:
{{ $posts->currentPage() == 2 ? route('home') : $posts->previousPageUrl() }}
Just create a Middleware by using this command
php artisan make:middleware RemovePageQuery
Write this code inside middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class RemovePageQuery
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->page == 1)
{
return redirect(url()->current());
}
return $next($request);
}
}
Add middleware global in App/Http/Kernel.php inside $middleware.
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\RemovePageQuery::class, //Here
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
I was searching to fix similar issue to prevent content duplication but for symfony not laravel. I fixed it in my way and decided to publish it. May be it will help somebody.
In symfony I use KNP paginator. It provides paginator template, like this:
{{ page }}
'Query' array contains route params and the task is to remove page param if it == 1.
Very simple:
{% set page_seo = (page == 1) ? null : page %}
{{ page }}
Actually - set to null page if it == 1.
By this way you can remove form field from the request in Laravel Controller. It will work 100%. You will get in request without page param.
$request->request->remove('page');
Better answer than #SaidbakR has provided, as I it's not right. Why? Because you will receive link for the same page as URL will be empty. And for example if you make request from url?page=2, you'll get link to same url?page=2 where link to first page must appear.
My easy variant:
Also publish pagination views: php artisan vendor:publish --tag=laravel-pagination
Go to /views/vendor/pagination/default.blade.php
Add elseif case to previous page link:
#elseif($paginator->currentPage() === 2)
<li class="pagination-next-prev link">
<a class="prev page-url" href="?{{ http_build_query(Arr::except(Request::query(), 'page')) }}" rel="prev"
aria-label="#lang('pagination.previous')">
Prev
</a>
</li>
#else
Add similar elseif to array of links:
#elseif(1 === $page)
<li class="page">
<a class="page-url" href="?{{ http_build_query(Arr::except(Request::query(), 'page')) }}">
{{ $page }}
</a>
</li>
#else
This case is also working if you are using ajax request to another page for pagination.
Done
You will have link to the page you opened, but without params. Yes it will be showed as ? in the URL, but it's fair price to avoid duplications of URLs, as link with just question mark for google same as without.

Pagination in Laravel doesn't work

When i put in my view {!! $questions->links(); !!} i don't see the pagination style and the page dont take 6 post per page like i put in my controller ..
My post Controller :
class QuestionsController extends Controller {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$questions = \App\Question::latest()->paginate(6);
$questions = \App\Question::unsolved();
$bars = \App\Question::unsolvedbar();
$links = str_replace('/?', '?', $questions->render());
return view('questions.index',compact('questions','bars','links'));
}
My Pagination Links in my View:
{!! $questions->links(); !!}
This is because you are overwriting your $questions variable
$questions = \App\Question::latest()->paginate(6);
$questions = \App\Question::unsolved();
Cant really tell you more without the unsolved() function declaration.
Add this to your view:
{!! $questions->render() !!}
If you want to paginate the links you should do something like this:
$links = $questions->links()->paginate();
And in the view add:
{!! $links->render() !!}

laravel pagination : Call to a member function render() on array

In laravel controller I have following code:
public function getAdmins(){
//$users = $this->user->all();
$search[] =array();
$search['name']= Input::get('name','');
$search['uname']= Input::get('uname','');
$search['role']= Input::get('role','');
$users = $this->user->findUsers($search);
$exceptSuperadmin = array();
foreach($users as $user){
if(!$user->isUser())
$staffs[] = $user;
}
$users = #$staffs;
return view('users::admins.list')->with('staffs',$users)->with('search',$search);
}
In Model I have:
public function findUsers($search)
{
return self::where('name','like','%'.$search['name'].'%')
->where('username','like','%'.$search['uname'].'%')
->where('role','like','%'.$search['role'].'%')
->paginate(5);
}
And In blade file I have:
#if($staffs)
#foreach($staffs as $staff)
<!-- Some code here to loop array -->
#endforeach
#else
No Staffs
#endif
{!! $staffs->render() !!} Error comes at this line
I am not geeting why this error comes....staffs is an array and render() a function to echo the pagination pages...but can't getting the error...Anybody to help.
By applying foreach the pager object and assign an array you lose the paging properties, so you will have an array rather than a pager object.
I recommend the following solution for your case:
Controller:
public function getAdmins(){
$search[] =array();
$search['name']= Input::get('name','');
$search['uname']= Input::get('uname','');
$search['role']= Input::get('role','');
$users = $this->user->findUsers($search);
return view('users::admins.list')->with('users',$users)->with('search',$search);
}
Blade file:
#if($users)
#foreach($users as $user)
#if(!$user->isUser())
<!-- Some code here to loop array -->
#endif
#endforeach
#else
No Staffs
#endif
{!! $users->render() !!}
NO, render() doesn't work on an object per se, neither on the array you are creating out of the required object for the pagination to work (LengthAwarePaginator)
Since you have a collection, and you need one, you could use one of the methods provided to do your filtering, such as filter.
Something like (untested but should work):
$staff = $users->filter(function ($value, $key) {
return !$value->isUser();
});

How to pass collection to view in Laravel?

I think I am missing something very simple. But I have no more patience to seek for it, so I need to ask.
I am trying to render view with list of elements of type Event
In my view I have a foreach loop:
#foreach ($events as $e)
......
{{ $e->title }}
......
#endforeach
Controller:
$account = Account::find(\Session::get('account'));
$events = $account->events()->get();
return view('events.index')->with('events', $events);
In my understanding it should be working this way. But instead I get
Invalid argument supplied for foreach()
I also tried:
$account = Account::find(\Session::get('account'));
$events = $account->events();
return view('events.index')->with('events', $events);
but in this approach my foreach loop will not run even once (no error).
Of course I have everything defined in my models.
Account:
public function events()
{
return $this->hasMany('App\Event');
}
One approach which is working is passing data as array like this:
$account = Account::find(\Session::get('account'));
$events = $account->events()->get()->toArray();
return view('events.index')->with('events', $events);
But then I need to work with array indexes in my view like this:
#foreach ($events as $e)
......
{{ $e['title'] }}
......
#endforeach
and I really, really don't want to do it this way.
So please tell my what am I missing.
Update:
I can't pass $account to my view and use $account->events in the view because I need to perform some filtering on events before I pass it.
use like this
$account = Account::find(\Session::get('account'));
$events = $account->events;
return view('events.index',['events'=> $events]);
use collect:
$account = Account::find(\Session::get('account'));
$events = $account->events;
return view('events.index', collect('events'))
;

Categories