I have a module in my Laravel app (it could also be a composer package).
This module has a view composer which sets an array to all views containing the routes which should be included in the main navigation of the app.
The composer looks like this:
class ContactNavigationComposer
{
/**
* Bind data to the view.
*
* #param \Illuminate\View\View $view
* #return void
*/
public function compose(View $view)
{
$view->with('contactNavigation', config('contact.navigation'));
}
}
Then in the mainNav of the app this variable $contactNavigation becomes iterated to generate the entries:
<ul>
# ...
<li>
<a
class="{{ (request()->is('navigations/areas')) ? 'active' : '' }}"
href="{{ route('areas.index') }}">
Navigation Areas
</a>
</li>
<li>
<a
class="{{ (request()->is('languages')) ? 'active' : '' }}"
href="{{ route('languages.index') }}">
Languages
</a>
</li>
#foreach($contactNavigation as $text => $url)
<li>
<a
class="{{ (request()->is($url)) ? 'active' : '' }}"
href="{{ $url }}">
{{ $text }}
</a>
</li>
#endforeach
</ul>
This works perfectly fine but I was wondering if I can have this behavior in a more dynamic way and let composers of different modules using the same array e.g. $modules which contains navigation entries (and other stuff) of different modules.
This way I wouldn't have to add module extensions to the applications views later on.
So my suggested solution would be smth. like that:
class ContactNavigationComposer
{
/**
* Bind data to the view.
*
* #param \Illuminate\View\View $view
* #return void
*/
public function compose(View $view)
{
$view->with('modules.navigation.contact', config('contact.navigation'));
}
}
<ul>
# ...
#if (isset($modules['navigation']))
#foreach($modules['navigation'] as $moduleNavigation)
#foreach($moduleNavigation as $text => $url)
<li>
<a
class="{{ (request()->is($url)) ? 'active' : '' }}"
href="{{ $url }}">
{{ $text }}
</a>
</li>
#endforeach
#endforeach
#endif
</ul>
Of course with the dot-notation, this modules.navigation.contact-key is never translated into a view variable especially not as an array.
Is there any way to achieve something like that?
For those who came across this issue as well:
I changed my compose method to this
// ViewComposer
class ContactNavigationComposer extends BaseComposer
{
public function compose(View $view)
{
$moduleDataBag = $this->getModuleDataBag($view);
$moduleData = $moduleDataBag->mergeRecursive([
'contact' => [
'navigation' => config('contact.navigation.entries')
]
]);
$view->with('modules', $moduleData);
}
}
The getModuleDataBag-method comes from the BaseComposer-class from which each ViewComposer inherits from.
class BaseComposer
{
protected function getModuleDataBag(View $view)
{
if (!array_key_exists(config('contact.view.dataKey'), $view->gatherData())) {
ViewFacade::share(config('contact.view.dataKey'), collect([]));
}
return $view->gatherData()[config('contact.view.dataKey')];
}
}
The key in which all the configuration is stored is defined in the (publishable) config. This way this setup is flexible for modifications by the surrounding application.
(The package is called contact, that's why the view data key is in the contact-config.)
If the expected key is not found, the BaseComposer sets it to all views using the ::share-method on the View-facade as an empty collection.
Each ViewComposer always call the getModuleDataBag-method first to receive the current data probably already set by other ViewComposers.
As this $moduleDataBag is a collection, all additional values can be merged into as long as they are arrays. This is done recursively so that each existing key and value is preserved.
Hope that can help someone later on.
Related
I'm writing blade policy for a drop down menu in laravel using #can.
<li>
<i class="zmdi zmdi-assignment-o"></i><span>Models</span>
<ul class="ml-menu">
#can('index',App\Model1::class)
<li>
{{__('Model1 Manger')}}
</li>
#endcan
#can('index',App\Model2::class)
<li>
{{__('Model2 Manager')}}
</li>
#endcan
</ul>
</li>
My question is how to hide the 'Model' option from the menu If user don't have permisssion to access model1 and model2?
You can use can within an if statement similar to below:
#if (Auth::user()->can('index',App\Model1::class) && Auth::user()->can('index',App\Model2::class))
#endif
You can find further information on Laravel's website at the link below:
https://laravel.com/docs/5.8/authorization#via-blade-templates
To take my answer further, it might be worth creating a separate class to handle authorization and then call it in the blade:
#if(BlogPermissions('showModal'))
#endif
you have to create policy for that. check the below code and for more reference check this https://laravel.com/docs/5.8/authorization#generating-policies
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* #param \App\User $user
* #param \App\Post $post
* #return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
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.
I am trying to access a page using a nested url in Laravel 5.1 but I have reached a dead end.
I would like to make a GET request with a parameter in the middle of the url. To be precise, cars/{cars}/edit.
This is my code:
In the routes file
Route::resource('cars', 'carController');
In the cars controller file
class carController extends Controller
{
public function index(){
$cars = Car::all();
return view('carshome', compact('cars'));
}
public function edit($id){
return 'Welcome: '.$id.'page';
}
}
In the carshome blade template file
#foreach ($cars as $car)
<tr>
<td>{{ $car->name }}</td>
<td>{{ $car->type }}</td>
<td class="text-center">
<a href = {{url('/cars',[$car->name])}}>
<i class="fi-clipboard-pencil"></i>
</a>
<a href = {{url('/cars',[$car->name])}}>
<i class="fi-x-circle"></i>
</a>
</td>
</tr>
#endforeach
In the car model file
class Car extends Model
{
protected $fillable = [
'name', 'type'
];
}
The helper function url can take parameters as part of the url. Am not sure how I can it to create the custom url. How can I access the url resource using blade?
This is just to mark the question as answered. As #Tezla shared:
In the carshome blade template file I can use:
route('cars.edit', [$car->name])
#lamzozo suggested another working method to use in the carshome blade template file:
url('cars', [$car->name, 'edit'])
In Twig partial rendered by separate controller, I want to check if current main route equals to compared route, so I can mark list item as active.
How can I do that? Trying to get current route in BarController like:
$route = $request->get('_route');
returns null.
Uri is also not what I'm looking for, as calling below code in bar's twig:
app.request.uri
returns route similar to: localhost/_fragment?path=path_to_bar_route
Full example
Main Controller:
FooController extends Controller{
public function fooAction(){}
}
fooAction twig:
...some stuff...
{{ render(controller('FooBundle:Bar:bar')) }}
...some stuff...
Bar controller:
BarController extends Controller{
public function barAction(){}
}
barAction twig:
<ul>
<li class="{{ (item1route == currentroute) ? 'active' : ''}}">
Item 1
</li>
<li class="{{ (item2route == currentroute) ? 'active' : ''}}">
Item 2
</li>
<li class="{{ (item3route == currentroute) ? 'active' : ''}}">
Item 3
</li>
</ul>
pabgaran's solution should work. However, the original problem occurs probably because of the request_stack.
http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
Since you are in a subrequest, you should be able to get top-level (master) Request and get _route. Something like this:
public function barAction(Request $request) {
$stack = $this->get('request_stack');
$masterRequest = $stack->getMasterRequest();
$currentRoute = $masterRequest->get('_route');
...
return $this->render('Template', array('current_route' => $currentRoute );
}
Haven't run this but it should work...
I think that the best solution in your case is past the current main route in the render:
{{ render(controller('FooBundle:Bar:bar', {'current_route' : app.request.uri})) }}
Next, return it in the response:
public function barAction(Request $request) {
...
return $this->render('Template', array('current_route' => $request->query->get('current_route'));
}
And in your template compares with the received value.
Otherwise, maybe is better to use a include instead a render, if you don't need extra logic for the partial.
in twig you can send request object from main controller to sub-controller as parameter:
{{ render(controller('FooBundle:Bar:bar', {'request' : app.request})) }}
in sub-controller:
BarController extends Controller{
public function barAction(Request $request){
// here you can use request object as regular
$country = $request->attributes->get('route_country');
}
}
following simple code:
<li>List</li>
is there a simple way to add an class="active" if the current page matches the _list route?
using the newest PR-Release of symfony2 and twig as template engine
Twig allows for conditionals and the Request object is available throughout the application. If you are including the template, to get the route you want to use:
app.request.attributes.get('_route')
If you are using the render function, you want to use:
app.request.attributes.get('_internal')
With that, you should be able to use:
class="{% if app.request.attributes.get('_route') == '_list' %}active{% endif %}"
or shorter:
class="{{ app.request.get('_route') == '_list' ? 'active' }}"
Sometimes you don't want to do exact matching of a route. For those cases, you can use the "starts with" conditional logic of twig.
As an example, lets assume you are working with books. You have the following routes: book, book_show, book_new, book_edit. You want the navigation item Book to be highlighted for any of those cases. This code would accomplish that.
<a class="{% if app.request.attributes.get('_route') starts with 'book' %}active{% endif %}">Books</a>
<a class="{% if app.request.attributes.get('_route') starts with 'author' %}active{% endif %}">Authors</a>
This example works with at least Symfony 2.3.x
Shortest version:
{% set route = app.request.get('_route') %}
<li class="{{ route starts with 'post' ? 'open' }}"></li>
<li class="{{ route starts with 'category' ? 'open' }}"></li>
Sometimes useful:
{% set route = app.request.get('_route') %}
<li class="{{ 'post' in route ? 'open' }}"></li>
<li class="{{ 'category' in route ? 'open' }}"></li>
With ternary operator:
{% set route = app.request.attributes.get('_route') %}
<ul class="nav navbar-nav">
<li {{ route == 'profile_index' ? 'class="active"' }}><i class="icon-profile position-left"></i> My Profile</li>
<li {{ route == 'influencers_index' ? 'class="active"'}}><i class="icon-crown position-left"></i> Influencers</li>
<li {{ route == 'task_manager_index' ? 'class="active"'}}><i class="icon-alarm-check position-left"></i> Task Manager</li>
</ul>
This is done with symfony 3.4, but probably something similar can be done with SF2.
src\AppBundle\Twig\AppExtension.php
<?php
namespace AppBundle\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
class AppExtension extends \Twig_Extension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('activeMenu', [$this, 'activeMenu'])
];
}
/**
* Pass route names. If one of route names matches current route, this function returns
* 'active'
* #param array $routesToCheck
* #return string
*/
public function activeMenu(array $routesToCheck)
{
$currentRoute = $this->requestStack->getCurrentRequest()->get('_route');
foreach ($routesToCheck as $routeToCheck) {
if ($routeToCheck == $currentRoute) {
return 'active';
}
}
return '';
}
}
Add this to services.yml
services:
#... some other services
AppBundle\Twig\AppExtension:
arguments: ["#request_stack"]
Usage:
<ul class="nav navbar-nav">
<li class="{{ activeMenu(['form', 'edit_form']) }}">Form</li>
<li class="{{ activeMenu(['list']) }}">List</li>
</ul>
i found a very good Bundle that handles all this stuff automagically:
https://github.com/KnpLabs/KnpMenuBundle
SF2.2
{{ dump(app.request.server.get('PATH_INFO')) }}
Symfony2.3, in Twig, try this to get uri:
{{ dump(app.request.server.get("REQUEST_URI")) }}
I found more efficient to know if we are on the active page and the link or not:
in file xx.html.twig :
In on your header file
{% set route_name = app.request.attributes.get('_route') %}
And add in html class of twig
{% if route_name matches '{^issue}' %}active{% endif %}
This is how I do it (using Symfony 2.6)
<li {% if app.request.get('_route') == '_homepage' %} class="active" {% endif %}>Student</li>
'_homepage' is the name of route in routing.yml of your bundle and the route looks like this
_homepage:
path: /
defaults: { _controller: CoreBundle:Default:index }
class="class_name {% if loop.index0 == 0 %}CLASSNAME{% endif %}"