├── resources └── views │ └── bootstrap │ ├── container.blade.php │ └── alert.blade.php ├── src ├── Contracts │ └── Renderer.php ├── helpers.php ├── RendererManager.php ├── Http │ └── Middleware │ │ ├── AddAlertsToJson.php │ │ └── StoreAlertsInSession.php ├── View │ └── Component │ │ └── LaralertsComponent.php ├── Renderers │ ├── BootstrapRenderer.php │ └── CompilesAlert.php ├── Facades │ └── Alert.php ├── LaralertsServiceProvider.php ├── Testing │ ├── Fakes │ │ └── BagFake.php │ └── Builder.php ├── BogusAlert.php ├── Bag.php └── Alert.php ├── LICENSE.md ├── config └── laralerts.php ├── composer.json └── README.md /resources/views/bootstrap/container.blade.php: -------------------------------------------------------------------------------- 1 | @if($alerts->isNotEmpty()) 2 |
3 | @each('laralerts::bootstrap.alert', $alerts, 'alert') 4 |
5 | @endif 6 | -------------------------------------------------------------------------------- /resources/views/bootstrap/alert.blade.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/Contracts/Renderer.php: -------------------------------------------------------------------------------- 1 | new()->message($message)->types(...$types); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/RendererManager.php: -------------------------------------------------------------------------------- 1 | config->get('laralerts.default'); 22 | } 23 | 24 | /** 25 | * Creates a Bootstrap renderer. 26 | * 27 | * @return \DarkGhostHunter\Laralerts\Contracts\Renderer 28 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 29 | */ 30 | protected function createBootstrapDriver(): Contracts\Renderer 31 | { 32 | return new Renderers\BootstrapRenderer($this->container->make(Factory::class)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Italo Israel Baeza Cabrera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Http/Middleware/AddAlertsToJson.php: -------------------------------------------------------------------------------- 1 | isSuccessful()) { 30 | $key ??= config('laralerts.key'); 31 | 32 | $data = $response->getData(); 33 | 34 | $response->setData( 35 | data_set($data, $key, app(Bag::class)->collect()->toArray(), false) 36 | ); 37 | } 38 | 39 | return $response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/View/Component/LaralertsComponent.php: -------------------------------------------------------------------------------- 1 | tags = (array) $tags ?: $bag->getDefaultTags(); 26 | } 27 | 28 | /** 29 | * Get the view / view contents that represent the component. 30 | * 31 | * @return string 32 | */ 33 | public function render(): string 34 | { 35 | return $this->renderer->render( 36 | $this->bag->collect()->filter->hasAnyTag(...$this->tags) 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/laralerts.php: -------------------------------------------------------------------------------- 1 | 'bootstrap', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Session key 21 | |-------------------------------------------------------------------------- 22 | | 23 | | For the Alerts to work, the bag containing them is registered inside the 24 | | Session store by an identifiable key. You may want to change this key 25 | | for any other in case it collides with a key you're already using. 26 | | 27 | */ 28 | 29 | 'key' => '_alerts', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Default tags 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Alerts support tagging, meanining you can filter which alerts to present 37 | | in your frontend by a name, like "global" or "admin". This contains the 38 | | default tags all Alerts made in your application will have by default. 39 | | 40 | | Supported: "array", "string". 41 | | 42 | */ 43 | 44 | 'tags' => 'default', 45 | ]; 46 | -------------------------------------------------------------------------------- /src/Renderers/BootstrapRenderer.php: -------------------------------------------------------------------------------- 1 | 'alert-primary', 27 | 'secondary' => 'alert-secondary', 28 | 'success' => 'alert-success', 29 | 'danger' => 'alert-danger', 30 | 'warning' => 'alert-warning', 31 | 'info' => 'alert-info', 32 | 'light' => 'alert-light', 33 | 'dark' => 'alert-dark', 34 | ]; 35 | 36 | /** 37 | * Classes that should be added when dismissing the alert. 38 | * 39 | * @var array|string[] 40 | */ 41 | protected const DISMISS_CLASSES = ['fade', 'show', 'alert-dismissible']; 42 | 43 | /** 44 | * Bootstrap Renderer constructor. 45 | * 46 | * @param \Illuminate\Contracts\View\Factory $factory 47 | */ 48 | public function __construct(protected Factory $factory) 49 | { 50 | // 51 | } 52 | 53 | /** 54 | * Returns the rendered alerts as a single HTML string. 55 | * 56 | * @param \Illuminate\Support\Collection|\DarkGhostHunter\Laralerts\Alert[] $alerts 57 | * @return string 58 | */ 59 | public function render(Collection $alerts): string 60 | { 61 | return $this->factory 62 | ->make(static::VIEW) 63 | ->with('alerts', $alerts->map([$this, 'compileAlert']))->render(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "darkghosthunter/laralerts", 3 | "description": "Quickly create one or multiple Alerts from your backend", 4 | "keywords": [ 5 | "darkghosthunter", 6 | "laralerts", 7 | "alerts", 8 | "notifications", 9 | "flash", 10 | "session", 11 | "laravel" 12 | ], 13 | "homepage": "https://github.com/darkghosthunter/laralerts", 14 | "license": "MIT", 15 | "type": "library", 16 | "minimum-stability": "dev", 17 | "prefer-stable": true, 18 | "authors": [ 19 | { 20 | "name": "Italo Israel Baeza Cabrera", 21 | "email": "darkghosthunter@gmail.com", 22 | "role": "Developer" 23 | } 24 | ], 25 | "require": { 26 | "php": "^8.0||^8.1", 27 | "ext-json": "*", 28 | "illuminate/config": "^8.0", 29 | "illuminate/http": "^8.0", 30 | "illuminate/support": "^8.0", 31 | "illuminate/collections": "^8.0", 32 | "illuminate/session": "^8.0", 33 | "illuminate/view": "^8.0", 34 | "illuminate/routing": "^8.0" 35 | }, 36 | "require-dev": { 37 | "orchestra/testbench": "^6.17", 38 | "mockery/mockery": "^1.4.2", 39 | "phpunit/phpunit": "^9.5.4" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "DarkGhostHunter\\Laralerts\\": "src" 44 | }, 45 | "files": [ 46 | "src/helpers.php" 47 | ] 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "Tests\\": "tests" 52 | } 53 | }, 54 | "scripts": { 55 | "test": "vendor/bin/phpunit --coverage-clover build/logs/clover.xml", 56 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 57 | }, 58 | "config": { 59 | "sort-packages": true 60 | }, 61 | "extra": { 62 | "laravel": { 63 | "providers": [ 64 | "DarkGhostHunter\\Laralerts\\LaralertsServiceProvider" 65 | ], 66 | "aliases": { 67 | "Alert": "DarkGhostHunter\\Laralerts\\Facades\\Alert" 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Facades/Alert.php: -------------------------------------------------------------------------------- 1 | collect() 14 | * @method static \DarkGhostHunter\Laralerts\Alert persistAs(string $key) 15 | * @method static bool abandon(string $key) 16 | * @method static bool hasPersistent(string $key) 17 | * @method static void flush() 18 | * @method static \DarkGhostHunter\Laralerts\Alert message(string $message) 19 | * @method static \DarkGhostHunter\Laralerts\Alert raw(string $message) 20 | * @method static \DarkGhostHunter\Laralerts\Alert types(string ...$types) 21 | * @method static \DarkGhostHunter\Laralerts\Alert dismiss(bool $dismissible = true) 22 | * @method static \DarkGhostHunter\Laralerts\Alert when(Closure|bool $condition) 23 | * @method static \DarkGhostHunter\Laralerts\Alert unless(Closure|bool $condition) 24 | * @method static \DarkGhostHunter\Laralerts\Alert away(string $replace, string $url, bool $blank = true) 25 | * @method static \DarkGhostHunter\Laralerts\Alert to(string $replace, string $url, bool $blank = false) 26 | * @method static \DarkGhostHunter\Laralerts\Alert route(string $replace, string $name, array $parameters = [], bool $blank = false) 27 | * @method static \DarkGhostHunter\Laralerts\Alert action(string $replace, string|array $action, array $parameters = [], bool $blank = false) 28 | * @method static \DarkGhostHunter\Laralerts\Alert tags(string ...$tags) 29 | * @method static \DarkGhostHunter\Laralerts\Alert fromJson(string $alert, int $options = 0) 30 | */ 31 | class Alert extends Facade 32 | { 33 | /** 34 | * Get the registered name of the component. 35 | * 36 | * @return string 37 | */ 38 | protected static function getFacadeAccessor(): string 39 | { 40 | return Bag::class; 41 | } 42 | 43 | /** 44 | * Creates a fake Alert Bag. 45 | * 46 | * @return \DarkGhostHunter\Laralerts\Testing\Fakes\BagFake 47 | */ 48 | public static function fake(): BagFake 49 | { 50 | $fake = static::$app->make(BagFake::class, [ 51 | 'tags' => Arr::wrap(Config::get('laralerts.tags')) 52 | ]); 53 | 54 | static::swap($fake); 55 | 56 | return $fake; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Renderers/CompilesAlert.php: -------------------------------------------------------------------------------- 1 | static::compileMessage($alert), 29 | 'classes' => static::compileClasses($alert), 30 | 'dismissible' => $alert->isDismissible(), 31 | ]; 32 | } 33 | 34 | /** 35 | * Parses the message, replacing each keyword by an HTML link. 36 | * 37 | * @param \DarkGhostHunter\Laralerts\Alert $alert 38 | * @return string 39 | */ 40 | protected static function compileMessage(Alert $alert): string 41 | { 42 | return str_replace( 43 | array_map( 44 | static function (string $replace): string { 45 | return '{' . $replace . '}'; 46 | }, array_column($alert->getLinks(), 'replace') 47 | ), 48 | array_map(static function (object $link): string { 49 | return "url\"".($link->blank ? ' target="_blank"' : '').">$link->replace"; 50 | }, $alert->getLinks()), 51 | $alert->getMessage() 52 | ); 53 | } 54 | 55 | /** 56 | * Returns a list of classes as a string for a "classes" HTML tag. 57 | * 58 | * @param \DarkGhostHunter\Laralerts\Alert $alert 59 | * @return string 60 | */ 61 | protected static function compileClasses(Alert $alert): string 62 | { 63 | $classes = []; 64 | 65 | foreach ($alert->getTypes() as $type) { 66 | if (defined('static::TYPE_CLASSES')) { 67 | array_push($classes, ...Arr::wrap(static::TYPE_CLASSES[$type] ?? $type)); 68 | } else { 69 | $classes[] = $type; 70 | } 71 | } 72 | 73 | if (defined('static::DISMISS_CLASSES') && $alert->isDismissible()) { 74 | $classes = array_merge($classes, static::DISMISS_CLASSES); 75 | } 76 | 77 | return implode(' ', array_unique($classes)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/LaralertsServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__ . '/../config/laralerts.php', 'laralerts'); 21 | 22 | $this->app->singleton(RendererManager::class); 23 | $this->app->singleton(Contracts\Renderer::class, static function (Container $app): Contracts\Renderer { 24 | return $app->make(RendererManager::class)->driver($app->make('config')->get('laralerts.renderer')); 25 | }); 26 | 27 | $this->app->singleton(Bag::class, static function (Container $app): Bag { 28 | return new Bag((array) $app->make('config')->get('laralerts.tags', ['default'])); 29 | }); 30 | 31 | $this->app->bind( 32 | Http\Middleware\StoreAlertsInSession::class, 33 | static function (Container $app): Http\Middleware\StoreAlertsInSession { 34 | return new Http\Middleware\StoreAlertsInSession( 35 | $app->make(Bag::class), 36 | $app->make('session.store'), 37 | $app->make('config')->get('laralerts.key') 38 | ); 39 | } 40 | ); 41 | } 42 | 43 | /** 44 | * Bootstrap any application services. 45 | * 46 | * @param \Illuminate\Foundation\Http\Kernel $http 47 | * @param \Illuminate\Routing\Router $router 48 | * 49 | * @return void 50 | */ 51 | public function boot(Kernel $http, Router $router): void 52 | { 53 | // Add the Global Middleware to the `web` group only if it exists. 54 | if (array_key_exists('web', $http->getMiddlewareGroups())) { 55 | $http->appendMiddlewareToGroup('web', Http\Middleware\StoreAlertsInSession::class); 56 | } 57 | 58 | $router->aliasMiddleware('laralerts.json', Http\Middleware\AddAlertsToJson::class); 59 | 60 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laralerts'); 61 | 62 | $this->callAfterResolving('blade.compiler', static function (BladeCompiler $blade): void { 63 | $blade->component(View\Component\LaralertsComponent::class, 'laralerts'); 64 | }); 65 | 66 | if ($this->app->runningInConsole()) { 67 | $this->publishes([__DIR__ . '/../resources/views' => resource_path('views/vendor/laralerts')], 'views'); 68 | $this->publishes([__DIR__ . '/../config/laralerts.php' => config_path('laralerts.php')], 'config'); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Testing/Fakes/BagFake.php: -------------------------------------------------------------------------------- 1 | added = $this->alerts; 26 | } 27 | 28 | /** 29 | * Finds an alert by a given key. 30 | * 31 | * @return \DarkGhostHunter\Laralerts\Testing\Builder 32 | */ 33 | public function assertAlert(): Builder 34 | { 35 | return new Builder($this); 36 | } 37 | 38 | /** 39 | * Assert that the alert bag has no alerts. 40 | * 41 | * @return void 42 | */ 43 | public function assertEmpty(): void 44 | { 45 | $this->assertAlert()->missing("Failed to assert that there is no alerts."); 46 | } 47 | 48 | /** 49 | * Assert that the alert bag has any alert. 50 | * 51 | * @return void 52 | */ 53 | public function assertNotEmpty(): void 54 | { 55 | $this->assertAlert()->exists("Failed to assert that there is any alert."); 56 | } 57 | 58 | /** 59 | * Assert the alert bag contains exactly one alert. 60 | * 61 | * @return void 62 | */ 63 | public function assertHasOne(): void 64 | { 65 | $this->assertAlert()->unique(); 66 | } 67 | 68 | /** 69 | * Assert the alert bag contains exactly the given number of alerts. 70 | * 71 | * @param int $count 72 | * @return void 73 | */ 74 | public function assertHas(int $count): void 75 | { 76 | $this->assertAlert()->count($count); 77 | } 78 | 79 | /** 80 | * Assert the alert bag contains an alert persisted by the given key. 81 | * 82 | * @param string $key 83 | * @return void 84 | */ 85 | public function assertPersisted(string $key): void 86 | { 87 | $this->assertAlert()->persistedAs($key); 88 | } 89 | 90 | /** 91 | * Assert the alert bag contains persistent alerts. 92 | * 93 | * @return void 94 | */ 95 | public function assertHasPersistent(): void 96 | { 97 | $this->assertAlert()->persisted()->exists("Failed to assert that there is any persistent alert."); 98 | } 99 | 100 | /** 101 | * Assert the alert bag doesn't contain persistent alerts. 102 | * 103 | * @return void 104 | */ 105 | public function assertHasNoPersistent(): void 106 | { 107 | $this->assertAlert()->persisted()->missing("Failed to assert that there is no persistent alerts."); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Http/Middleware/StoreAlertsInSession.php: -------------------------------------------------------------------------------- 1 | getSession()?->isStarted()) { 42 | return $next($request); 43 | } 44 | 45 | $this->sessionAlertsToBag(); 46 | 47 | $response = $next($request); 48 | 49 | $this->bagAlertsToSession($response instanceof RedirectResponse); 50 | 51 | return $response; 52 | } 53 | 54 | /** 55 | * Takes the existing alerts in the session and adds them to the bag. 56 | * 57 | * @return void 58 | */ 59 | protected function sessionAlertsToBag(): void 60 | { 61 | // Retrieve both persistent and non-persistent alerts and add them. 62 | app(Bag::class)->add(array_merge( 63 | $this->session->get("$this->key.persistent", []), 64 | $this->session->get("$this->key.alerts", []), 65 | )); 66 | 67 | // Removing the alerts from the session ensures these don't duplicate. 68 | $this->session->forget($this->key); 69 | } 70 | 71 | /** 72 | * Move the alerts back to the session. 73 | * 74 | * @param bool $flashIfRedirect 75 | * @return void 76 | */ 77 | protected function bagAlertsToSession(bool $flashIfRedirect): void 78 | { 79 | [$persistent, $nonPersistent] = $this->bag->collect() 80 | ->partition(function (Alert $alert): bool { 81 | return in_array($alert->index, $this->bag->getPersisted(), true); 82 | }); 83 | 84 | // Persistent keys will be put persistently into the session. 85 | if ($persistent->isNotEmpty()) { 86 | $this->session->put("$this->key.persistent", $persistent->all()); 87 | } 88 | 89 | // Non-persistent will be flashed if the response is as redirection. 90 | // This way we allow the next response from the app to have these 91 | // alerts without having to manually flash them from the app. 92 | if ($flashIfRedirect && $nonPersistent->isNotEmpty()) { 93 | $this->session->flash("$this->key.alerts", $nonPersistent->all()); 94 | } 95 | 96 | $this->bag->flush(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/BogusAlert.php: -------------------------------------------------------------------------------- 1 | alerts = new Collection; 39 | } 40 | 41 | /** 42 | * Returns all a key-index map of all persisted alerts. 43 | * 44 | * @return array 45 | */ 46 | public function getPersisted(): array 47 | { 48 | return $this->persisted; 49 | } 50 | 51 | /** 52 | * Returns the default list of tags injected in each Alert. 53 | * 54 | * @return array 55 | */ 56 | public function getDefaultTags(): array 57 | { 58 | return $this->tags; 59 | } 60 | 61 | /** 62 | * Creates a new Alert into this Bag instance. 63 | * 64 | * @return \DarkGhostHunter\Laralerts\Alert 65 | */ 66 | public function new(): Alert 67 | { 68 | $this->add($alert = new Alert(bag: $this, tags: $this->tags)); 69 | 70 | return $alert; 71 | } 72 | 73 | /** 74 | * Adds an Alert into the bag. 75 | * 76 | * @param \DarkGhostHunter\Laralerts\Alert|\DarkGhostHunter\Laralerts\Alert[] $alert 77 | * @return \DarkGhostHunter\Laralerts\Bag 78 | */ 79 | public function add(Alert|array $alert): static 80 | { 81 | foreach (Arr::wrap($alert) as $item) { 82 | $this->alerts->push($item); 83 | 84 | $item->index = array_key_last($this->alerts->all()); 85 | 86 | // The method is also used to put alerts from the session. Because 87 | // of that, we will check if it already has a persistent key and, 88 | // if it has one, we will add it to the internal map of alerts. 89 | if ($key = $item->getPersistKey()) { 90 | $this->persisted[$key] = $item->index; 91 | } 92 | 93 | $item->setBag($this); 94 | } 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Returns the underlying collection of alerts. 101 | * 102 | * @return \Illuminate\Support\Collection|\DarkGhostHunter\Laralerts\Alert[] 103 | */ 104 | public function collect(): Collection 105 | { 106 | return $this->alerts; 107 | } 108 | 109 | /** 110 | * Marks an existing Alert as persistent. 111 | * 112 | * @param string $key 113 | * @param int $index 114 | * @return $this 115 | */ 116 | public function markPersisted(string $key, int $index): static 117 | { 118 | // Find if there is a key already for the persisted alert and replace it. 119 | $this->abandon($key); 120 | 121 | $this->persisted[$key] = $index; 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * Abandons a persisted Alert. 128 | * 129 | * @param string $key 130 | * @return bool Returns true if successful. 131 | */ 132 | public function abandon(string $key): bool 133 | { 134 | if (null !== $index = $this->whichPersistent($key)) { 135 | $this->alerts->forget($index); 136 | unset($this->persisted[$key]); 137 | return true; 138 | } 139 | 140 | return false; 141 | } 142 | 143 | /** 144 | * Check if an Alert by the given key is persistent. 145 | * 146 | * @param string $key 147 | * @return bool 148 | */ 149 | public function hasPersistent(string $key): bool 150 | { 151 | return null !== $this->whichPersistent($key); 152 | } 153 | 154 | /** 155 | * Locates the key of a persistent alert. 156 | * 157 | * @param string $key 158 | * @return int|null 159 | */ 160 | protected function whichPersistent(string $key): ?int 161 | { 162 | return $this->persisted[$key] ?? null; 163 | } 164 | 165 | /** 166 | * Deletes all alerts. 167 | * 168 | * @return void 169 | */ 170 | public function flush(): void 171 | { 172 | $this->alerts = new Collection(); 173 | } 174 | 175 | /** 176 | * Creates an Alert only if the condition evaluates to true. 177 | * 178 | * @param \Closure|bool $condition 179 | * @return \DarkGhostHunter\Laralerts\Alert 180 | */ 181 | public function when(Closure|bool $condition): Alert 182 | { 183 | return value($condition, $this) ? $this->new() : new BogusAlert($this); 184 | } 185 | 186 | /** 187 | * Creates an Alert only if the condition evaluates to false. 188 | * 189 | * @param \Closure|bool $condition 190 | * @return \DarkGhostHunter\Laralerts\Alert 191 | */ 192 | public function unless(Closure|bool $condition): Alert 193 | { 194 | return ! value($condition, $this) ? $this->new() : new BogusAlert($this); 195 | } 196 | 197 | /** 198 | * Adds an Alert into the bag from a JSON string. 199 | * 200 | * @param string $alert 201 | * @param int $options 202 | * @return \DarkGhostHunter\Laralerts\Alert 203 | * @throws \JsonException 204 | */ 205 | public function fromJson(string $alert, int $options = 0): Alert 206 | { 207 | $this->add($instance = Alert::fromArray($this, json_decode($alert, true, 512, $options | JSON_THROW_ON_ERROR))); 208 | 209 | return $instance; 210 | } 211 | 212 | /** 213 | * Pass through all calls to a new Alert. 214 | * 215 | * @codeCoverageIgnore 216 | * @param string $method 217 | * @param array $parameters 218 | * @return \DarkGhostHunter\Laralerts\Alert 219 | */ 220 | public function __call(string $method, array $parameters) 221 | { 222 | if (static::hasMacro($method)) { 223 | return $this->macroCall($method, $parameters); 224 | } 225 | 226 | return $this->new()->{$method}(...$parameters); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Testing/Builder.php: -------------------------------------------------------------------------------- 1 | message = $message; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Expect an alert with the message. 65 | * 66 | * @param string $message 67 | * @return $this 68 | */ 69 | public function withMessage(string $message): static 70 | { 71 | return $this->withRaw(e($message)); 72 | } 73 | 74 | /** 75 | * Expect an alert with a translated message. 76 | * 77 | * @param string $key 78 | * @param array $replace 79 | * @param string|null $locale 80 | * @return $this 81 | */ 82 | public function withTrans(string $key, array $replace = [], string $locale = null): static 83 | { 84 | return $this->withRaw(trans($key, $replace, $locale)); 85 | } 86 | 87 | /** 88 | * Expect an alert with a translated (choice) message. 89 | * 90 | * @param string $key 91 | * @param Countable|int|array $number 92 | * @param array $replace 93 | * @param string|null $locale 94 | * @return $this 95 | */ 96 | public function withTransChoice( 97 | string $key, 98 | Countable|int|array $number, 99 | array $replace = [], 100 | string $locale = null 101 | ): static 102 | { 103 | return $this->withRaw(trans_choice($key, $number, $replace, $locale)); 104 | } 105 | 106 | /** 107 | * Expect an alert with a link away. 108 | * 109 | * @param string $replace 110 | * @param string $url 111 | * @param bool $blank 112 | * @return $this 113 | */ 114 | public function withAway(string $replace, string $url, bool $blank = true): static 115 | { 116 | $this->links[] = (object) [ 117 | 'replace' => $replace, 118 | 'url' => $url, 119 | 'blank' => $blank, 120 | ]; 121 | 122 | usort($this->links, static function (object $first, object $second): int { 123 | return strcmp($first->replace . $first->url, $second->replace . $second->url); 124 | }); 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Expect an alert with a link to a path. 131 | * 132 | * @param string $replace 133 | * @param string $url 134 | * @param bool $blank 135 | * @return $this 136 | */ 137 | public function withTo(string $replace, string $url, bool $blank = false): static 138 | { 139 | return $this->withAway($replace, url($url), $blank); 140 | } 141 | 142 | /** 143 | * Expect an alert with a link to a route. 144 | * 145 | * @param string $replace 146 | * @param string $name 147 | * @param array $parameters 148 | * @param bool $blank 149 | * @return $this 150 | */ 151 | public function withRoute(string $replace, string $name, array $parameters = [], bool $blank = false): static 152 | { 153 | return $this->withAway($replace, route($name, $parameters), $blank); 154 | } 155 | 156 | /** 157 | * Expect an alert with a link to an action. 158 | * 159 | * @param string $replace 160 | * @param string|array $action 161 | * @param array $parameters 162 | * @param bool $blank 163 | * @return $this 164 | */ 165 | public function withAction(string $replace, string|array $action, array $parameters = [], bool $blank = false): static 166 | { 167 | return $this->withAway($replace, action($action), $blank); 168 | } 169 | 170 | /** 171 | * Expect an alert with the issued types. 172 | * 173 | * @param string ...$types 174 | * @return $this 175 | */ 176 | public function withTypes(string ...$types): static 177 | { 178 | $this->types = $types; 179 | 180 | sort($this->types); 181 | 182 | return $this; 183 | } 184 | 185 | /** 186 | * Expect an alert persisted. 187 | * 188 | * @return $this 189 | */ 190 | public function persisted(): static 191 | { 192 | $this->persisted = true; 193 | 194 | return $this; 195 | } 196 | 197 | /** 198 | * Expect an alert not persisted. 199 | * 200 | * @return $this 201 | */ 202 | public function notPersisted(): static 203 | { 204 | $this->persisted = false; 205 | 206 | return $this; 207 | } 208 | 209 | /** 210 | * Expect an alert dismissible. 211 | * 212 | * @return $this 213 | */ 214 | public function dismissible(): static 215 | { 216 | $this->dismiss = true; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Expect an alert not dismissible. 223 | * 224 | * @return $this 225 | */ 226 | public function notDismissible(): static 227 | { 228 | $this->dismiss = false; 229 | 230 | return $this; 231 | } 232 | 233 | /** 234 | * Expect an alert with the given tags. 235 | * 236 | * @param string ...$tags 237 | * @return $this 238 | */ 239 | public function withTag(string ...$tags): static 240 | { 241 | $this->tags = $tags; 242 | 243 | return $this; 244 | } 245 | 246 | /** 247 | * Expect an alert with any of the given tags. 248 | * 249 | * @param string ...$tags 250 | * @return $this 251 | */ 252 | public function withAnyTag(string ...$tags): static 253 | { 254 | $this->anyTag = true; 255 | 256 | return $this->withTag(...$tags); 257 | } 258 | /** 259 | * Returns a collection of all matching alerts. 260 | * 261 | * @return \Illuminate\Support\Collection 262 | */ 263 | protected function matches(): Collection 264 | { 265 | return $this->bag->added->filter(function (Alert $alert): bool { 266 | return $this->is($alert); 267 | }); 268 | } 269 | 270 | /** 271 | * Check if the given alert matches the expectations. 272 | * 273 | * @param \DarkGhostHunter\Laralerts\Alert $alert 274 | * @return bool 275 | */ 276 | protected function is(Alert $alert): bool 277 | { 278 | if ($this->message !== null && $this->message !== $alert->getMessage()) { 279 | return false; 280 | } 281 | 282 | if ($this->dismiss !== null && $this->dismiss !== $alert->isDismissible()) { 283 | return false; 284 | } 285 | 286 | if ($this->types !== null && $this->types !== $alert->getTypes()) { 287 | return false; 288 | } 289 | 290 | if ($this->tags !== null) { 291 | if ($this->anyTag) { 292 | return $alert->hasAnyTag(...$this->tags); 293 | } 294 | 295 | return $this->tags === $alert->getTags(); 296 | } 297 | 298 | if ($this->persisted !== null) { 299 | if (is_string($this->persisted)) { 300 | return $this->persisted === $alert->getPersistKey(); 301 | } 302 | 303 | if (is_array($this->persisted)) { 304 | return in_array($alert->getPersistKey(), $this->persisted, true); 305 | } 306 | 307 | return $this->persisted === (bool) $alert->getPersistKey(); 308 | } 309 | 310 | if ($this->links !== null && $this->links != $alert->getLinks()) { 311 | return false; 312 | } 313 | 314 | return true; 315 | } 316 | 317 | /** 318 | * Expect an alert persisted with the issued key. 319 | * 320 | * @param string|array $key 321 | * @return void 322 | */ 323 | public function persistedAs(string ...$key): void 324 | { 325 | $this->persisted = $key; 326 | 327 | $count = count($key); 328 | 329 | $this->count($count, "Failed to assert that [$count] persistent alerts exist."); 330 | } 331 | 332 | /** 333 | * Assert that at least one Alert exists with the given expectations. 334 | * 335 | * @param string $message 336 | * @return void 337 | */ 338 | public function exists(string $message = 'Failed to assert that at least one alert matches the expectations.'): void 339 | { 340 | PHPUnit::assertNotEmpty($this->matches(), $message); 341 | } 342 | 343 | /** 344 | * Assert that no Alert exists with the given expectations. 345 | * 346 | * @param string $message 347 | * @return void 348 | */ 349 | public function missing(string $message = 'Failed to assert that no alert matches the expectations.'): void 350 | { 351 | PHPUnit::assertEmpty($this->matches(), $message); 352 | } 353 | 354 | /** 355 | * Assert that only one Alert exists with the given expectations. 356 | * 357 | * @param string $message 358 | * @return void 359 | */ 360 | public function unique(string $message = 'Failed to assert that there is only one alert.'): void 361 | { 362 | $this->count(1, $message); 363 | } 364 | 365 | /** 366 | * Assert that the given number of Alerts matches exactly the given expectations. 367 | * 368 | * @param int $count 369 | * @param string|null $message 370 | * @return void 371 | */ 372 | public function count(int $count, string $message = null): void 373 | { 374 | $matches = $this->matches(); 375 | 376 | PHPUnit::assertCount( 377 | $count, $matches, 378 | $message ?? "Failed to assert that [{$matches->count()}] alerts match the expected [$count] count." 379 | ); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/Alert.php: -------------------------------------------------------------------------------- 1 | bag = $bag; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Returns the key used to persist the alert, if any. 71 | * 72 | * @return string|null 73 | * @internal 74 | */ 75 | public function getPersistKey(): ?string 76 | { 77 | return $this->persistKey; 78 | } 79 | 80 | /** 81 | * Returns the message of the Alert. 82 | * 83 | * @return string 84 | * @internal 85 | */ 86 | public function getMessage(): string 87 | { 88 | return $this->message; 89 | } 90 | 91 | /** 92 | * Returns the types set for this Alert. 93 | * 94 | * @return string[] 95 | * @internal 96 | */ 97 | public function getTypes(): array 98 | { 99 | return $this->types; 100 | } 101 | 102 | /** 103 | * Returns the links to replace in the message. 104 | * 105 | * @return array 106 | * @internal 107 | */ 108 | public function getLinks(): array 109 | { 110 | return $this->links; 111 | } 112 | 113 | /** 114 | * Check if the Alert should be dismissible. 115 | * 116 | * @return bool 117 | * @internal 118 | */ 119 | public function isDismissible(): bool 120 | { 121 | return $this->dismissible; 122 | } 123 | 124 | /** 125 | * Returns the tags of this Alert. 126 | * 127 | * @return array 128 | * @internal 129 | */ 130 | public function getTags(): array 131 | { 132 | return $this->tags; 133 | } 134 | 135 | /** 136 | * Check if the alert contains any of the given tags. 137 | * 138 | * @param string ...$tags 139 | * @return bool 140 | * @internal 141 | */ 142 | public function hasAnyTag(string ...$tags): bool 143 | { 144 | foreach ($tags as $tag) { 145 | if (in_array($tag, $this->tags, true)) { 146 | return true; 147 | } 148 | } 149 | 150 | return false; 151 | } 152 | 153 | /** 154 | * Sets a safely-escaped message. 155 | * 156 | * @param string $message 157 | * @return $this 158 | */ 159 | public function message(string $message): static 160 | { 161 | return $this->raw(e($message)); 162 | } 163 | 164 | /** 165 | * Sets a raw, non-escaped, message. 166 | * 167 | * @param string $message 168 | * @return $this 169 | */ 170 | public function raw(string $message): static 171 | { 172 | $this->message = $message; 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Set a localized message into the Alert. 179 | * 180 | * @param string $key 181 | * @param array $replace 182 | * @param string|null $locale 183 | * @return $this 184 | */ 185 | public function trans(string $key, array $replace = [], string $locale = null): static 186 | { 187 | return $this->raw(trans($key, $replace, $locale)); 188 | } 189 | 190 | /** 191 | * Sets a localized pluralized message into the Alert. 192 | * 193 | * @param string $key 194 | * @param \Countable|int|array $number 195 | * @param array $replace 196 | * @param string|null $locale 197 | * @return $this 198 | */ 199 | public function transChoice( 200 | string $key, 201 | Countable|int|array $number, 202 | array $replace = [], 203 | string $locale = null 204 | ): static { 205 | return $this->raw(trans_choice($key, $number, $replace, $locale)); 206 | } 207 | 208 | /** 209 | * Sets one or many types for this alert. 210 | * 211 | * @param string ...$types 212 | * @return $this 213 | */ 214 | public function types(string ...$types): static 215 | { 216 | $this->types = $types; 217 | 218 | sort($this->types); 219 | 220 | return $this; 221 | } 222 | 223 | /** 224 | * Sets the Alert as dismissible. 225 | * 226 | * @param bool $dismissible 227 | * @return $this 228 | */ 229 | public function dismiss(bool $dismissible = true): static 230 | { 231 | $this->dismissible = $dismissible; 232 | 233 | return $this; 234 | } 235 | 236 | /** 237 | * Persists the key into the session, forever. 238 | * 239 | * @param string $key 240 | * @return $this 241 | */ 242 | public function persistAs(string $key): static 243 | { 244 | $this->persistKey = $key; 245 | 246 | $this->bag->markPersisted($key, $this->index); 247 | 248 | return $this; 249 | } 250 | 251 | /** 252 | * Abandons the Alert from persistence. 253 | * 254 | * @return $this 255 | */ 256 | public function abandon(): static 257 | { 258 | $this->bag->abandon($this->persistKey); 259 | 260 | $this->persistKey = null; 261 | 262 | return $this; 263 | } 264 | 265 | /** 266 | * Adds an external link that should be replaced before rendering the Alert. 267 | * 268 | * @param string $replace 269 | * @param string $url 270 | * @param bool $blank 271 | * @return $this 272 | */ 273 | public function away(string $replace, string $url, bool $blank = true): static 274 | { 275 | $this->links[] = (object) [ 276 | 'replace' => trim($replace, "{}"), 277 | 'url' => $url, 278 | 'blank' => $blank, 279 | ]; 280 | 281 | usort($this->links, static function (object $first, object $second): int { 282 | return strcmp($first->replace . $first->url, $second->replace . $second->url); 283 | }); 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Adds a link that should be replaced before rendering the Alert. 290 | * 291 | * @param string $replace 292 | * @param string $url 293 | * @param bool $blank 294 | * @return $this 295 | */ 296 | public function to(string $replace, string $url, bool $blank = false): static 297 | { 298 | return $this->away($replace, url($url), $blank); 299 | } 300 | 301 | /** 302 | * Adds a link to a route that should be replaced before rendering the Alert. 303 | * 304 | * @param string $replace 305 | * @param string $name 306 | * @param array $parameters 307 | * @param bool $blank 308 | * @return $this 309 | */ 310 | public function route(string $replace, string $name, array $parameters = [], bool $blank = false): static 311 | { 312 | return $this->away($replace, route($name, $parameters), $blank); 313 | } 314 | 315 | /** 316 | * Adds a link to an action that should be replaced before rendering the Alert. 317 | * 318 | * @param string $replace 319 | * @param string|array $action 320 | * @param array $parameters 321 | * @param bool $blank 322 | * @return $this 323 | */ 324 | public function action(string $replace, string|array $action, array $parameters = [], bool $blank = false): static 325 | { 326 | return $this->away($replace, action($action, $parameters), $blank); 327 | } 328 | 329 | /** 330 | * Tags the alert. 331 | * 332 | * @param string ...$tags 333 | * @return $this 334 | */ 335 | public function tag(string ...$tags): static 336 | { 337 | $this->tags = $tags; 338 | 339 | sort($this->tags); 340 | 341 | return $this; 342 | } 343 | 344 | /** 345 | * Get the instance as an array. 346 | * 347 | * @return array 348 | */ 349 | public function toArray(): array 350 | { 351 | return [ 352 | 'message' => $this->message, 353 | 'types' => $this->types, 354 | 'dismissible' => $this->dismissible, 355 | ]; 356 | } 357 | 358 | /** 359 | * Convert the object to its JSON representation. 360 | * 361 | * @param int $options 362 | * @return string 363 | * @throws \JsonException 364 | */ 365 | public function toJson($options = 0): string 366 | { 367 | return json_encode($this->jsonSerialize(), $options | JSON_THROW_ON_ERROR); 368 | } 369 | 370 | /** 371 | * Specify data which should be serialized to JSON. 372 | * 373 | * @return array 374 | */ 375 | public function jsonSerialize(): array 376 | { 377 | return $this->toArray(); 378 | } 379 | 380 | /** 381 | * Returns the string representation of the Alert. 382 | * 383 | * @return string 384 | * @throws \JsonException 385 | */ 386 | public function __toString() 387 | { 388 | return $this->toJson(); 389 | } 390 | 391 | /** 392 | * Serializes the Alert. 393 | * 394 | * @codeCoverageIgnore 395 | * @return array 396 | */ 397 | public function __serialize(): array 398 | { 399 | return [ 400 | 'persistKey' => $this->persistKey, 401 | 'message' => $this->message, 402 | 'types' => $this->types, 403 | 'links' => $this->links, 404 | 'dismissible' => $this->dismissible, 405 | 'tags' => $this->tags, 406 | ]; 407 | } 408 | 409 | /** 410 | * Unserializes the alert. 411 | * 412 | * @codeCoverageIgnore 413 | * @param array $data 414 | */ 415 | public function __unserialize(array $data): void 416 | { 417 | $this->persistKey = $data['persistKey']; 418 | $this->message = $data['message']; 419 | $this->types = $data['types']; 420 | $this->links = $data['links']; 421 | $this->dismissible = $data['dismissible']; 422 | $this->tags = $data['tags']; 423 | } 424 | 425 | /** 426 | * Creates a new Alert from a Bag and an array. 427 | * 428 | * @param \DarkGhostHunter\Laralerts\Bag|array $bag 429 | * @param array|null $alert 430 | * @return \DarkGhostHunter\Laralerts\Alert 431 | */ 432 | public static function fromArray(Bag|array $bag, array $alert = null): Alert 433 | { 434 | if (is_array($bag)) { 435 | [$bag, $alert] = [app(Bag::class), $bag]; 436 | } 437 | 438 | return new static($bag, null, $alert['message'], $alert['types'], [], $alert['dismissible'] ?? false); 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Package superseeded by [Laragear/Alerts](https://github.com/Laragear/Alerts) 2 | 3 | --- 4 | 5 | # Laralerts 6 | 7 | Quickly set one or multiple Alerts in your backend, and render them in the frontend. 8 | 9 | Laralerts is compatible with **any** frontend framework to better suit your app, not the other way around. 10 | 11 | ## Requirements 12 | 13 | * Laravel 8.x or later 14 | * PHP 8.0 or later. 15 | 16 | > For older versions support, consider helping by sponsoring or donating. 17 | 18 | ## Installation 19 | 20 | You can install the package via composer: 21 | 22 | ```bash 23 | composer require darkghosthunter/laralerts 24 | ``` 25 | 26 | If you don't have anything to start with in your frontend, you can use [Laravel Jetstream](https://jetstream.laravel.com/), or go the classic way and use [Bootstrap](https://getbootstrap.com), [Bulma.io](https://bulma.io/), [UI kit](https://getuikit.com/), [TailwindCSS](https://tailwindcss.com/) and [INK](http://ink.sapo.pt/), among many others. 27 | 28 | ## Usage 29 | 30 | Laralerts allows you to set a list of Alerts in your application and render them in the frontend in just a few minutes. 31 | 32 | The default Alert renderer uses Bootstrap code to transform each alert into [Bootstrap Alerts](https://getbootstrap.com/docs/5.0/components/alerts/). If you're not using Bootstrap, you can [create your own](#creating-a-custom-renderer) for your particular framework. 33 | 34 | ### Quickstart 35 | 36 | To set an Alert in your frontend, you can use the `alert()` helper, or the `Alert` Facade. A good place to use them is before sending a response to the browser, like in your HTTP Controllers. 37 | 38 | If you're sending a redirect, Laralerts will automatically flash the alert so the next request can show it. 39 | 40 | ```php 41 | validate([ 60 | 'title' => 'required|string|max:255', 61 | 'body' => 'required|string' 62 | ]); 63 | 64 | $article->fill($request)->save(); 65 | 66 | alert('Your article has been updated!', 'success'); 67 | 68 | return redirect()->action('ArticleController@edit', $article); 69 | } 70 | } 71 | ``` 72 | 73 | The `alert()` helper accepts the text *message* and the **types** of the alert. In the above example, we created a "success" alert. 74 | 75 | To render them in the frontend, use the `` Blade component which will take care of the magic, anywhere you want to put it. 76 | 77 | ```blade 78 |
79 |

Welcome to my site

80 | 81 |
82 | ``` 83 | 84 | If there is at least one Alert to be rendered, the above will be transformed into proper HTML: 85 | 86 | ```html 87 |
88 |

Welcome to my site

89 |
90 | 93 |
94 |
95 | ``` 96 | 97 | ### Message 98 | 99 | Add the text inside the Alert using the `message()` method. That's it. 100 | 101 | ```php 102 | message('You are gonna love this! 😍')->types('success'); 107 | 108 | alert()->message('We will email you 📨 a copy!')->types('info'); 109 | ``` 110 | 111 | ```html 112 | 115 | 116 | 119 | ``` 120 | 121 | > By default, the `message()` method escapes the text. If you want to send a raw message, you should use [`raw()`](#raw-message). 122 | 123 | ### Raw message 124 | 125 | Since the `message()` method escapes the text for safety, you can use the `raw()` method to output a string verbatim. This allows you to use HTML for personalized messages, like adding some _style_, links, or even scripts. 126 | 127 | ```php 128 | message('This is FUBAR.')->types('warning'); 131 | 132 | alert()->raw('But this is important.')->types('warning'); 133 | ``` 134 | 135 | ```html 136 | 139 | 140 | 143 | ``` 144 | 145 | **Warning: Don't use `raw()` to show user-generated content. YOU HAVE BEEN WARNED**. 146 | 147 | ### Alert Type 148 | 149 | You can set an alert "type" by its name by just setting it with the `types()` method. It also accepts multiple types. 150 | 151 | ```php 152 | message('Your message was sent!')->types('primary'); 155 | 156 | alert()->message('There is an unread message.')->types('info', 'fade'); 157 | ``` 158 | 159 | ```html 160 | 163 | 166 | ``` 167 | 168 | The types are just aliases for custom CSS classes and HTML, which are then translated by the Renderer to the proper code. 169 | 170 | > The Renderer receives the list of types and changes them into CSS classes accordingly. The default Bootstrap Renderer will set each unrecognized type as an additional CSS class. 171 | 172 | ### Localization 173 | 174 | To gracefully localize messages on the fly, use the `trans()` method, which is a mirror of [the `__()` helper](https://laravel.com/docs/localization#retrieving-translation-strings). 175 | 176 | ```php 177 | trans('email.changed', ['email' => $email], 'es')->types('success'); 180 | ``` 181 | 182 | ```html 183 | 186 | ``` 187 | 188 | You can also use `transChoice()` with the same parameters of [`trans_choice()`](https://laravel.com/docs/localization#pluralization). 189 | 190 | ```php 191 | transChoice('messages.apples', 1)->types('success'); 194 | alert()->transChoice('messages.apples', 10)->types('success'); 195 | ``` 196 | 197 | ```html 198 | 201 | 202 | 205 | ``` 206 | 207 | ### Dismiss 208 | 209 | Most of the frontend frameworks have alerts or notifications that can be dismissible, but require adding more than a single class to allow for interactivity. You can set an Alert to be dismissible using `dismiss()`. 210 | 211 | ```php 212 | alert()->message('You can disregard this')->type('success')->dismiss(); 213 | ``` 214 | 215 | If you want to change your mind, you can use `dismiss(false)`: 216 | 217 | ```php 218 | alert()->message('You can disregard this')->type('success')->dismiss(false); 219 | ``` 220 | 221 | How the dismissible alert is transformed into code will depend on the renderer itself. The default Bootstrap renderer adds the proper CSS classes and a dismiss button automatically. 222 | 223 | ```html 224 | 228 | ``` 229 | 230 | ### Conditional Alerts 231 | 232 | You can also push an Alert if a condition evaluates to true or false by using `when()` and `unless()`, respectively. Further method calls will be sent to the void. 233 | 234 | ```php 235 | when(Auth::check()) 240 | ->message('You are authenticated') 241 | ->types('success'); 242 | 243 | alert()->unless(Auth::user()->mailbox()->isNotEmpty()) 244 | ->message('You have messages in your inbox') 245 | ->types('warning'); 246 | ``` 247 | 248 | ### Persistent Alerts 249 | 250 | Alerts only last for the actual request being sent. On redirects, these are [flashed into the session](https://laravel.com/docs/8.x/session#flash-data) so these are available on the next request (the redirection target). 251 | 252 | To make any alert persistent you can use the `persistAs()` method with a key to identify the alert. 253 | 254 | ```php 255 | alert()->message('Your disk size is almost full')->types('danger')->persistAs('disk.full'); 256 | ``` 257 | 258 | > Setting a persistent alert replaces any previous set with the same key. 259 | 260 | Once you're done, you can delete the persistent Alert using `abandon()` directly from the helper using the key of the persisted Alert. It will return `true` if the persisted Alert is found, or `false` if not. For example, we can abandon the previous alert if the disk is no longer full. 261 | 262 | ```php 263 | if ($disk->notFull()) { 264 | alert()->abandon('disk.full'); 265 | } 266 | ``` 267 | 268 | ### Links 269 | 270 | Setting up links for an alert doesn't have to be cumbersome. You can easily replace a string between curly braces in your message for a link using `to()`, `route()`, `action()`, and `away()`. 271 | 272 | ```php 273 | message('Remember, you can follow your order in your {dashboard}.') 276 | ->types('success') 277 | ->to('dashboard', '/dashboard/orders') 278 | ``` 279 | 280 | Links can also work over translated messages, as long these have a word in curly braces. 281 | 282 | ```php 283 | trans('user.dashboard.tracking.order', ['order' => $order->tracking_number]) 287 | ->types('success') 288 | ->route('tracking', 'orders.tracking', ['order' => 45]) 289 | ``` 290 | 291 | If you have more than one link, you can chain multiple links to a message. 292 | 293 | ```php 294 | trans('Your {product} is contained in this {order}.') 297 | ->types('success') 298 | ->action('product', [\App\Http\Controllers\Product::class, 'show'], ['product' => 180]) 299 | ->to('order', '/dashboard/order/45') 300 | ``` 301 | 302 | > Links strings are case-sensitive, and replaces all occurrences of the same string. You can [create your own Renderer](#creating-a-custom-renderer) if this is not desired. 303 | 304 | ### Tags 305 | 306 | Sometimes you may have more than one place in your site to place Alerts, like one for global alerts and other for small user alerts. Tags can work to filter which Alerts you want to render. 307 | 308 | You can set the tags of the Alert using `tag()`. 309 | 310 | ```php 311 | alert()->message('Maintenance is scheduled for tomorrow') 312 | ->type('warning') 313 | ->tag('user', 'admin') 314 | ``` 315 | 316 | Using the [Laralerts directive](#quickstart), you can filter the Alerts to render by the tag names using the `:tags` slot. 317 | 318 | ```blade 319 | 320 | 321 | 322 | 323 | 324 | ``` 325 | 326 | ## Configuration 327 | 328 | Laralerts works out-of-the-box with some common defaults, but if you need a better approach for your particular application, you can configure some parameters. First, publish the configuration file. 329 | 330 | ```bash 331 | php artisan vendor:publish --provider="DarkGhostHunter\Laralerts\LaralertsServiceProvider" --tag="config" 332 | ``` 333 | 334 | Let's examine the configuration array, which is quite simple: 335 | 336 | ```php 337 | 'bootstrap', 341 | 'key' => '_alerts', 342 | 'tags' => 'default', 343 | ]; 344 | ``` 345 | 346 | ### Renderer 347 | 348 | ```php 349 | 'bootstrap', 353 | ]; 354 | ``` 355 | 356 | This picks the Renderer to use when transforming Alerts into HTML. 357 | 358 | This package ships with Bootstrap 5 renderer, but you can [create your own](#renderers) for other frontend frameworks like [Bulma.io](https://bulma.io/), [UI kit](https://getuikit.com/), [TailwindCSS](https://tailwindcss.com/) and [INK](http://ink.sapo.pt/), or even your own custom frontend framework. 359 | 360 | ### Session Key 361 | 362 | ```php 363 | '_alerts', 367 | ]; 368 | ``` 369 | 370 | When alerts are flashed or persisted, these are stored in the Session by a given key, which is `_alerts` by default. If you're using this key name for other things, you may want to change it. 371 | 372 | This key is also used when [sending JSON alerts](#sending-json-alerts). 373 | 374 | ### Default tag list 375 | 376 | ```php 377 | ['user', 'admin'], 381 | ]; 382 | ``` 383 | 384 | This holds the default tag list to inject to all Alerts when created. You can leave this alone if you're not using [tags](#tags). 385 | 386 | ## Renderers 387 | 388 | A Renderer takes a [collection](https://laravel.com/docs/collections) of Alerts and transforms each into an HTML string. This makes swapping a frontend framework easier, and allows greater flexibility when rendering HTML. 389 | 390 | ### Creating a custom renderer 391 | 392 | You can create your own using the `Renderer` contract, and registering it into the `RendererManager` in your `AppServiceProvider`. You can use the `BootstrapRenderer` as a starting point. 393 | 394 | ```php 395 | extend('tailwind', function ($app) { 408 | return new TailwindRenderer($app['blade.compiler']); 409 | }); 410 | } 411 | ``` 412 | 413 | Then, in your config file, set the renderer to the one you have registered. 414 | 415 | ```php 416 | // config/laralerts.php 417 | 418 | return [ 419 | 'renderer' => 'tailwind' 420 | 421 | // ... 422 | ]; 423 | ``` 424 | 425 | When you issue an alert, the alert will be rendered using your own custom renderer. 426 | 427 | ```php 428 | message('Popping colors!')->types('primary'); 431 | ``` 432 | 433 | ```html 434 |
435 | Popping colors! 436 |
437 | ``` 438 | 439 | ### Alerts Container HTML 440 | 441 | When the Renderer receives Alerts to render, it will call a "container" view which will render all the Alerts by using a loop. 442 | 443 | For example, the included `BootstrapRenderer` calls the `laralerts::bootstrap.container`. 444 | 445 | ```html 446 | @if($alerts->isNotEmpty()) 447 |
448 | @each('laralerts::bootstrap.alert', $alerts, 'alert') 449 |
450 | @endif 451 | ``` 452 | 453 | You may be using another frontend framework different from Bootstrap 5, or you may want to change the HTML to better suit your application design. In any case, you can override the View files in `views/vendor/laralerts`: 454 | 455 | * `container.blade.php`: The HTML that contains all the Alerts. 456 | * `alert.blade.php`: The HTML for a single Alert. 457 | 458 | The variables the `alert.blade.php` view receives are set from by Renderer. For the case of the included Bootstrap renderer, these are: 459 | 460 | * `$alert->message`: The message to show inside the Alert. 461 | * `$alert->classes`: The CSS classes to incorporate into the Alert. 462 | * `$alert->dismissible`: A boolean that sets the alert as dismissible or not. 463 | 464 | As you're suspecting, you can publish the views and override them to suit your needs. 465 | 466 | php artisan vendor:publish --provider="DarkGhostHunter\Laralerts\LaralertsServiceProvider" --tag="views" 467 | 468 | ## JSON Alerts 469 | 470 | ### Receiving JSON Alerts 471 | 472 | Sometimes your application may receive a JSON Alert from an external service using this package. You can quickly add this JSON as an Alert to your application using the `fromJson()` method. 473 | 474 | ```json 475 | { 476 | "alert": { 477 | "message": "Email delivered", 478 | "types": [ 479 | "success", 480 | "important" 481 | ], 482 | "dismissible": false 483 | } 484 | } 485 | ``` 486 | 487 | ```php 488 | alert()->fromJson($json); 489 | ``` 490 | 491 | This will work as long the JSON **has the `message` key** with the text to include inside the Alert. Additionally, you can add the `types` and `dismiss` keys to add an Alert, with the possibility of override them afterwards. 492 | 493 | ### Sending JSON Alerts 494 | 495 | This library has a convenient way to add Alerts into your JSON Responses. This can be very useful to add your alerts to each response being sent to the browser, like combining this package with [Laravel Jetstream](https://jetstream.laravel.com/). 496 | 497 | Just simply [add the `laralerts.json` middleware](https://laravel.com/docs/middleware#registering-middleware) into your `api` routes or, if you're using [Laravel Jetstream](https://jetstream.laravel.com/) or similar, as a [global middleware](https://laravel.com/docs/8.x/middleware#global-middleware). 498 | 499 | When you return a `JsonResponse` to the browser, the middleware will append the alert as JSON using the same [session key](#session-key) defined in the configuration, which is `_alerts` by default. It also accepts the `key` parameter to use as an alternative, compatible with *dot notation*. Here is an example: 500 | 501 | ```php 502 | uses('UserController@create'); 509 | Route::post('update')->uses('UserController@update'); 510 | 511 | })->middleware('laralerts.json:_status.alerts'); 512 | ``` 513 | 514 | When you receive a JSON Response, you will see the alerts appended to whichever key you issued. Using the above example, we should see the `alerts` key under the `_status` key: 515 | 516 | ```json 517 | { 518 | "resource": "users", 519 | "url": "/v1/users", 520 | "_status": { 521 | "timestamp": "2019-06-05T03:47:24Z", 522 | "action" : "created", 523 | "id": 648, 524 | "alerts": [ 525 | { 526 | "message": "The user has been created!", 527 | "types" : ["success", "important"], 528 | "dismiss": true 529 | } 530 | ] 531 | } 532 | } 533 | ``` 534 | 535 | > If your key is already present in the JSON response, Laralerts **won't overwrite the key value**. Ensure the key is never present in the response. 536 | 537 | ## Testing 538 | 539 | To test if alerts were generated, you can use `Alert::fake()`, which works like any other faked services. It returns a fake Alert Bag that holds a copy of all alerts generated, which exposes some convenient assertion methods. 540 | 541 | ```php 542 | use \DarkGhostHunter\Laralerts\Facades\Alert; 543 | 544 | public function test_alert_sent() 545 | { 546 | $alerts = Alert::fake(); 547 | 548 | $this->post('/comment', ['body' => 'cool'])->assertOk(); 549 | 550 | $alerts->assertHasOne(); 551 | } 552 | ``` 553 | 554 | The following assertions are available: 555 | 556 | | Method | Description | 557 | |---------------------------|------------------------------------------------------------------------| 558 | | `assertEmpty()` | Check if the alert bag doesn't contains alerts. | 559 | | `assertNotEmpty()` | Check if the alert bag contains any alert. | 560 | | `assertHasOne()` | Check if the alert bag contains only one alert. | 561 | | `assertHas($count)` | Check if the alert bag contains the exact amount of alerts. | 562 | | `assertHasPersistent()` | Check if the alert bag contains at least one persistent alert. | 563 | | `assertHasNoPersistent()` | Check if the alert bag doesn't contains a persistent alert. | 564 | | `assertPersistentCount()` | Check if the alert bag contains the exact amount of persistent alerts. | 565 | 566 | ### Asserting specific alerts 567 | 568 | The fake Alert bag allows building conditions for the existence (or nonexistence) of alerts with specific properties, by using `assertAlert()`. 569 | 570 | Once you build your conditions, you can use `exists()` to check if any alert matches, or `missing()` to check if no alert should match. 571 | 572 | ```php 573 | $bag->assertAlert()->withMessage('Hello world!')->exists(); 574 | 575 | $bag->assertAlert()->withTypes('danger')->dismissible()->missing(); 576 | ``` 577 | 578 | Alternatively, you can use `count()` if you expect a specific number of alerts to match the given conditions, or `unique()` for matching only one alert. 579 | 580 | ```php 581 | $bag->assertAlert()->persisted()->count(2); 582 | 583 | $bag->assertAlert()->notDismissible()->withTag('toast')->unique(); 584 | ``` 585 | 586 | The following conditions are available: 587 | 588 | | Method | Description | 589 | |---------------------|---------------------------------------------------| 590 | | `withRaw()` | Find alerts with the given raw message. | 591 | | `withMessage()` | Find alerts with the given message. | 592 | | `withTrans()` | Find alerts with the translated message. | 593 | | `withTransChoice()` | Find alerts with the translated (choice) message. | 594 | | `withAway()` | Find alerts with a link away. | 595 | | `withTo()` | Find alerts with a link to a path. | 596 | | `withRoute()` | Find alerts with a link to a route. | 597 | | `withAction()` | Find alerts with a link to a controller action. | 598 | | `withTypes()` | Find alerts with exactly the given types. | 599 | | `persisted()` | Find alerts persisted. | 600 | | `notPersisted()` | Find alerts not persisted. | 601 | | `persistedAs()` | Find alerts persisted with the issued keys. | 602 | | `dismissible()` | Find alerts dismissible. | 603 | | `notDismissible()` | Find alerts not dismissible. | 604 | | `withTag()` | Find alerts with all the given tags. | 605 | | `withAnyTag()` | Find alerts with any of the given tags. | 606 | 607 | ## Security 608 | 609 | If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker. 610 | 611 | ## License 612 | 613 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 614 | --------------------------------------------------------------------------------