Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/docs/8.x/datatables/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Button::add()
| [class](#class) | Sets additional class |
| [route](#route) | Sets the button link href by using a route |
| [link](#link) | Sets the button link href |
| [tooltip](#tooltip) | Sets a tooltip for the button |
| [attributes](#attributes) | Sets HTML attributes to the button |
| [make](#make) | Renders the button |

Expand Down Expand Up @@ -93,6 +94,27 @@ Sets the button link href.
->link(route('boilerplate.users.edit', $user->id))
```

## tooltip

Sets a tooltip for the button using the HTML `title` attribute.

```php
->tooltip('Edit this user')
```

The tooltip is only rendered when a non-empty string is provided, avoiding unnecessary HTML attributes.

**Example with tooltip:**

```php
Button::add('Edit')
->route('boilerplate.users.edit', $user->id)
->icon('pencil-alt')
->color('primary')
->tooltip('Edit this user')
->make();
```

## attributes

Sets HTML attributes.
Expand All @@ -113,6 +135,8 @@ Renders the button.

## Button aliases

### Predefined buttons

```php
Button::show('route.to.resource.show', $resource);
```
Expand All @@ -126,3 +150,33 @@ Button::delete('route.to.resource.destroy', $resource);
```

> `Button::delete` will show a modal to confirm the deletion. You can set another confirmation message by using the [`Datatable::locale()` method](options#locale).

### Custom button helper

The `custom()` method provides a convenient way to create custom buttons with all parameters in a single call:

```php
Button::custom(
'route.name', // Route name (required)
$args, // Route arguments (optional, default: [])
'icon-name', // FontAwesome icon (optional, default: '')
'Tooltip text', // Tooltip text (optional, default: '')
'primary', // Button color (optional, default: 'default')
['data-action' => 'x'] // HTML attributes (optional, default: [])
);
```

**Example:**

```php
Button::custom(
'users.export',
['id' => $user->id],
'download',
'Export user data',
'success',
['data-confirm' => 'Export this user?']
);
```

**Note:** The icon parameter comes before the tooltip for better ergonomics. If no icon is provided, no empty HTML markup is generated.
55 changes: 46 additions & 9 deletions src/Datatables/Button.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

class Button
{
protected $class = '';
protected $color = 'default';
protected $href = '#';
protected $icon = '';
protected $label = '';
protected $attributes = [];
protected string $class = '';
protected string $color = 'default';
protected string $href = '#';
protected string $icon = '';
protected string $label = '';
protected array $attributes = [];
protected string $tooltip = '';

/**
* Instanciate a new button.
Expand All @@ -32,6 +33,28 @@ public static function add(string $label = ''): self
return new static($label);
}

/**
* Returns a custom button.
*
* @param string $route
* @param array|string $args
* @param string $icon
* @param string $tooltip
* @param string $color
* @param array $attributes
* @return string
*/
public static function custom(string $route, array|string $args = [], string $icon = '', string $tooltip = '', string $color = 'default', array $attributes = []): string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept mixed route args in Button::custom

Button::custom() narrows $args to array|string, which breaks the model-object usage that existing helpers already support (for example Button::edit(..., $role) in the codebase). In PHP this can coerce an Eloquent model to a JSON string via __toString(), so route() receives the wrong parameter value and generates incorrect URLs; for non-stringable objects it will throw a TypeError. Matching the mixed behavior used by show/edit/delete avoids this regression.

Useful? React with 👍 / 👎.

{
$button = self::add()->route($route, $args)->tooltip($tooltip);

if (! empty($icon)) {
$button->icon($icon);
}

return $button->color($color)->attributes($attributes)->make();
}

/**
* Returns an edit button.
*
Expand Down Expand Up @@ -168,8 +191,6 @@ public function link(string $href): self
*/
public function make(): string
{
$str = '<a href="%s" class="btn btn-sm btn-%s ml-1%s" %s>%s%s</a>';

if (! empty($this->label) && ! empty($this->icon)) {
$this->label = $this->label.' ';
}
Expand All @@ -182,6 +203,22 @@ public function make(): string
return sprintf('%s="%s"', $k, $this->attributes[$k]);
}, array_keys($this->attributes)));

return sprintf($str, $this->href, $this->color, $this->class, $attributes, $this->label, $this->icon);
$tooltip = ! empty($this->tooltip) ? sprintf(' title="%s"', $this->tooltip) : '';
$str = '<a href="%s"%s class="btn btn-sm btn-%s ml-1%s" %s>%s%s</a>';

return sprintf($str, $this->href, $tooltip, $this->color, $this->class, $attributes, $this->label, $this->icon);
}

/**
* Sets tooltip of button.
*
* @param string $tooltip
* @return $this
*/
public function tooltip(string $tooltip): self
{
$this->tooltip = $tooltip;

return $this;
}
}
128 changes: 128 additions & 0 deletions tests/Datatables/ButtonTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Sebastienheyd\Boilerplate\Tests\Datatables;

use Sebastienheyd\Boilerplate\Datatables\Button;
use Sebastienheyd\Boilerplate\Tests\TestCase;

class ButtonTest extends TestCase
{
public function testTooltipMethod()
{
$button = Button::add('Test Button')
->link('/test')
->tooltip('This is a tooltip')
->make();

$this->assertStringContainsString('title="This is a tooltip"', $button);
}

public function testTooltipNotAddedWhenEmpty()
{
$button = Button::add('Test Button')
->link('/test')
->make();

$this->assertStringNotContainsString('title=', $button);
}

public function testTooltipMethodChaining()
{
$button = Button::add('Test')
->link('/test')
->tooltip('Tooltip text')
->color('primary')
->icon('star')
->make();

$this->assertStringContainsString('title="Tooltip text"', $button);
$this->assertStringContainsString('btn-primary', $button);
$this->assertStringContainsString('fa-star', $button);
}

public function testButtonHtmlStructure()
{
$button = Button::add('Click me')
->link('/action')
->tooltip('Button tooltip')
->color('success')
->make();

$this->assertMatchesRegularExpression('/<a href="\/action" title="Button tooltip" class="btn btn-sm btn-success/', $button);
}

public function testCustomButtonWithRoute()
{
// Define a test route
$this->app['router']->get('/test/{id}', function () {
return 'test';
})->name('test.route');

$button = Button::custom(
'test.route',
['id' => 1],
'star',
'Custom tooltip',
'success'
);

$this->assertStringContainsString('title="Custom tooltip"', $button);
$this->assertStringContainsString('btn-success', $button);
$this->assertStringContainsString('fa-star', $button);
$this->assertStringContainsString('/test/1', $button);
}

public function testCustomButtonWithAttributes()
{
$this->app['router']->get('/test', function () {
return 'test';
})->name('test.route2');

$button = Button::custom(
'test.route2',
[],
'heart',
'Custom tooltip',
'primary',
['data-action' => 'custom-action', 'data-id' => '123']
);

$this->assertStringContainsString('data-action="custom-action"', $button);
$this->assertStringContainsString('data-id="123"', $button);
$this->assertStringContainsString('btn-primary', $button);
$this->assertStringContainsString('fa-heart', $button);
}

public function testCustomButtonWithDefaults()
{
$this->app['router']->get('/default', function () {
return 'test';
})->name('test.default');

$button = Button::custom('test.default');

$this->assertStringNotContainsString('title=', $button);
$this->assertStringContainsString('btn-default', $button);
$this->assertStringNotContainsString('fa-', $button); // No icon by default
}

public function testCustomButtonArgumentOrder()
{
// Test that $icon comes before $tooltip in the method signature
$this->app['router']->get('/order', function () {
return 'test';
})->name('test.order');

$button = Button::custom(
'test.order',
[],
'cog', // $icon (3rd parameter)
'Tooltip', // $tooltip (4th parameter)
'warning' // $color (5th parameter)
);

$this->assertStringContainsString('fa-cog', $button);
$this->assertStringContainsString('title="Tooltip"', $button);
$this->assertStringContainsString('btn-warning', $button);
}
}
Loading