Get expert guidance in a few days with a Laravel code review

Laravel Widgets

arrilot/laravel-widgets image

Laravel Widgets stats

Downloads
3M
Stars
1,093
Open Issues
4
Forks
113

View on GitHub →

A powerful alternative to view composers. Asynchronous widgets, reloadable widgets, console generator, caching - everything you can think of.

Widgets for Laravel

A powerful alternative to view composers. Asynchronous widgets, reloadable widgets, console generator, caching - everything that you can imagine.

Installation

Run composer require arrilot/laravel-widgets

Laravel >=5.5 uses Package Auto-Discovery, so you don't need to manually add the ServiceProvider and Facades

Usage

Let's consider that we want to make a list of recent news and reuse it in several views.

First of all we can create a Widget class using the artisan command provided by the package.

php artisan make:widget RecentNews

This command generates two files:

  1. resources/views/widgets/recent_news.blade.php is an empty view.

Add "--plain" option if you do not need a view.

  1. app/Widgets/RecentNews is a widget class.
<?php
 
namespace App\Widgets;
 
use Arrilot\Widgets\AbstractWidget;
 
class RecentNews extends AbstractWidget
{
/**
* The configuration array.
*
* @var array
*/
protected $config = [];
 
/**
* Treat this method as a controller action.
* Return view() or other content to display.
*/
public function run()
{
//
 
return view('widgets.recent_news', [
'config' => $this->config,
]);
}
}

Note: You can use your own stubs if you need. Publish config file to change paths.

The last step is to call the widget. There are several ways to do so.

@widget('recentNews')

or

{{ Widget::run('recentNews') }}

or even

{{ Widget::recentNews() }}

There is no real difference between these. The choice is up to you.

Passing variables to widget

Via config array

Let's carry on with the "recent news" example.

Imagine that we usually need to show five news, but in some views we need to show ten. This can be easily achieved by:

class RecentNews extends AbstractWidget
{
...
protected $config = [
'count' => 5
];
...
}
 
...
@widget('recentNews') // shows 5
@widget('recentNews', ['count' => 10]) // shows 10

['count' => 10] is a config array that can be accessed by $this->config.

Config array is available in every widget method so you can use it to configure placeholder and container too (see below)

Note: Config fields that are not specified when you call a widget aren't overridden:

class RecentNews extends AbstractWidget
{
...
protected $config = [
'count' => 5,
'foo' => 'bar'
];
 
...
}
 
@widget('recentNews', ['count' => 10]) // $this->config['foo'] is still 'bar'

Note 2: You may want (but you probably don't) to create your own BaseWidget and inherit from it. That's fine. The only edge case is merging config defaults from a parent and a child. In this case do the following:

  1. Do not add protected $config = [...] line to a child.

  2. Instead add defaults like this:

public function __construct(array $config = [])
{
$this->addConfigDefaults([
'child_key' => 'bar'
]);
 
parent::__construct($config);
}

Directly (works only for Laravel versions below 7)

You can also choose to pass additional parameters to run() method directly.

@widget('recentNews', ['count' => 10], 'date', 'asc')
...
public function run($sortBy, $sortOrder) { }
...

run() method is resolved via Service Container, so method injection is also available here.

Namespaces

By default the package tries to find your widget in the App\Widgets namespace.

You can override this by publishing package config (php artisan vendor:publish --provider="Arrilot\Widgets\ServiceProvider") and setting default_namespace property.

Although using the default namespace is very convenient, in some cases you may wish to have more flexibility. For example, if you've got dozens of widgets it makes sense to group them in namespaced folders.

No problem, there are several ways to call those widgets:

  1. Pass a full widget name from the default_namespace (basically App\Widgets) to the run method.
@widget('News\RecentNews', $config)
  1. Use dot notation.
@widget('news.recentNews', $config)
  1. FQCN is also an option.
@widget('\App\Http\Some\Namespace\Widget', $config)

Asynchronous widgets

In some situations it can be very beneficial to load widget content with AJAX.

Fortunately, this can be achieved very easily! All you need to do is to change facade or blade directive - Widget:: => AsyncWidget::, @widget => @asyncWidget

Widget params are encrypted (by default) and sent via ajax call under the hood. So expect them to be json_encoded() and json_decoded() afterwards.

Note: You can turn encryption off for a given widget by setting public $encryptParams = false; on it. However, this action will make widget params publicly accessible, so please make sure to not leave any vulnerabilities. For example, if you pass something like user_id through widget params and turn encryption off, you do need to add one more access check inside the widget.

Note: You can set use_jquery_for_ajax_calls to true in the config file to use it for ajax calls if you want to, but you need to manually add jquery to your page in this case.

By default nothing is shown until ajax call is finished.

This can be customized by adding a placeholder() method to the widget class.

public function placeholder()
{
return 'Loading...';
}

Side note: If you need to do something with the routes package used to load async widgets (e.g. you run app in a subfolder http://site.com/app/) you need to copy Arrilot\Widgets\ServiceProvider to your app, modify it according to your needs and register it in Laravel instead of the former one.

Reloadable widgets

You can go even further and automatically reload widget every N seconds.

Just set the $reloadTimeout property of the widget class and it is done.

class RecentNews extends AbstractWidget
{
/**
* The number of seconds before each reload.
*
* @var int|float
*/
public $reloadTimeout = 10;
}

Both sync and async widgets can become reloadable.

You should use this feature with care, because it can easily spam your app with ajax calls if timeouts are too low. Consider using web sockets too but they are way harder to set up.

Container

Async and Reloadable widgets both require some DOM interaction so they wrap all widget output in a html container. This container is defined by AbstractWidget::container() method and can be customized too.

/**
* Async and reloadable widgets are wrapped in container.
* You can customize it by overriding this method.
*
* @return array
*/
public function container()
{
return [
'element' => 'div',
'attributes' => 'style="display:inline" class="arrilot-widget-container"',
];
}

Note: Nested async or reloadable widgets are not supported.

Caching

There is also a simple built-in way to cache entire widget output. Just set $cacheTime property in your widget class and you are done.

class RecentNews extends AbstractWidget
{
/**
* The number of minutes before cache expires.
* False means no caching at all.
*
* @var int|float|bool
*/
public $cacheTime = 60;
}

No caching is turned on by default. A cache key depends on a widget name and each widget parameter. Override cacheKey method if you need to adjust it.

Cache tagging

When tagging is supported (see the Laravel cache documentation) and to simplify cache flushing, a tag widgets is assigned by default to all widgets. You can define one or more additional tags to your widgets by setting the values in the $cacheTags property in your widget class. Example :

class RecentNews extends AbstractWidget
{
/**
* Cache tags allow you to tag related items in the cache
* and then flush all cached values that assigned a given tag.
*
* @var array
*/
public $cacheTags = ['news', 'frontend'];
}

For this example, if you need to flush :

// Clear widgets with the tag news
Cache::tags('news')->flush();
 
// Clear widgets with the tag news OR backend
Cache::tags(['news', 'frontend'])->flush();
 
// Flush all widgets cache
Cache::tags('widgets')->flush();

Widget groups (extra)

In most cases Blade is a perfect tool for setting the position and order of widgets. However, sometimes you may find useful the following approach:

// add several widgets to the 'sidebar' group anywhere you want (even in controller)
Widget::group('sidebar')->position(5)->addWidget('widgetName1', $config1);
Widget::group('sidebar')->position(4)->addAsyncWidget('widgetName2', $config2);
 
// display them in a view in the correct order
@widgetGroup('sidebar')
// or
{{ Widget::group('sidebar')->display() }}

position() can be omitted from the chain.

Widget::group('sidebar')->addWidget('files');

equals

Widget::group('sidebar')->position(100)->addWidget('files');

You can set a separator that will be display between widgets in a group. Widget::group('sidebar')->setSeparator('<hr>')->...;

You can also wrap each widget in a group using wrap method like that.

Widget::group('sidebar')->wrap(function ($content, $index, $total) {
// $total is a total number of widgets in a group.
return "<div class='widget-{$index}'>{$content}</div>";
})->...;

Removing widgets from a group

There is a couple of ways to remove widget/widgets from a group after they've been already added.

  1. Remove one widget by its unique id
$id1 = Widget::group('sidebar')->addWidget('files');
$id2 = Widget::group('sidebar')->addAsyncWidget('files');
Widget::group('sidebar')->removeById($id1); // There is only second widget in the group now
  1. Remove all widgets with specific name
Widget::group('sidebar')->addWidget('files');
Widget::group('sidebar')->addAsyncWidget('files');
Widget::group('sidebar')->removeByName('files'); // Widget group is empty now
  1. Remove all widgets that are placed on a specific position.
Widget::group('sidebar')->position(42)->addWidget('files');
Widget::group('sidebar')->position(42)->addAsyncWidget('files');
Widget::group('sidebar')->removeByPosition(42); // Widget group is empty now
  1. Remove all widgets at once.
Widget::group('sidebar')->addWidget('files');
Widget::group('sidebar')->addAsyncWidget('files');
Widget::group('sidebar')->removeAll(); // Widget group is empty now

Checking the state of a group

Widget::group('sidebar')->isEmpty(); // bool

Widget::group('sidebar')->any(); // bool

Widget::group('sidebar')->count(); // int

Namespaces for third party packages (extra)

In some cases, it may be useful to deliver widgets with your own packages. For example, if your package allows you to manage news, it would be convenient to have immediately configurable widgets, ready for display, directly delivered with your package.

To avoid having to use the fqcn each time, you can set a widget namespace into your package provider. This way the widgets from your package can be more easily identified, and especially the syntax will be shorter.

To do that, all you have to do is to register the namespace in your package service provider :

public function boot()
{
app('arrilot.widget-namespaces')->registerNamespace('my-package-name', '\VendorName\PackageName\Path\To\Widgets');
}

After that you can use the namespace in your views :

@widget('my-package-name::foo.bar')
 
// is equivalent to
@widget('\VendorName\PackageName\Path\To\Widgets\Foo\Bar')
Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.


Arrilot Laravel Widgets Related Articles

`hasSole()` Collection Method in Laravel 12.49.0 image

`hasSole()` Collection Method in Laravel 12.49.0

Read article
Map Eloquent Attributes into an Object Using the Collection Cast in Laravel 12.10 image

Map Eloquent Attributes into an Object Using the Collection Cast in Laravel 12.10

Read article
Laravel 10.40 - With a Number Clamp Method, an APA-Style Helper, Vite Asset Path Customization, and More image

Laravel 10.40 - With a Number Clamp Method, an APA-Style Helper, Vite Asset Path Customization, and More

Read article
Laravel 10.38 Released with a new Blade @session directive image

Laravel 10.38 Released with a new Blade @session directive

Read article
Laravel 8.74 Released image

Laravel 8.74 Released

Read article
The Certification of Competence for Laravel logo

The Certification of Competence for Laravel

A community-driven, proctored assessment across 4 levels designed to validate real-world Laravel knowledge, from Junior to mastery-level Artisan. Official Vue.js, Official Nuxt, Angular, React, JS certifications also available.

The Certification of Competence for Laravel
Celebian logo

Celebian

Celebian is a social media marketing agency specializing in helping their clients go viral on TikTok. Whether you're looking to reach a bigger audience or gain more Tiktok followers, likes, and views, they've got you covered.

Celebian
Statamic logo

Statamic

The drop-in ready Laravel CMS you’re been waiting for. Go full-stack or headless, flat file or database – it’s up to you.

Statamic
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review