├── .github └── workflows │ └── .github-actions.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── config └── toaster.php ├── phpunit.xml ├── src ├── Helpers │ └── functions.php ├── Interfaces │ ├── SessionStore.php │ └── ViewBinder.php ├── LaravelSessionStore.php ├── Toast.php ├── Toaster.php ├── ToasterGroup.php ├── ToasterServiceProvider.php └── ToasterViewBinder.php └── tests ├── TestCase.php └── ToasterTest.php /.github/workflows/.github-actions.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | tags: 9 | - 5.* 10 | pull_request: 11 | branches: [ master ] 12 | 13 | workflow_dispatch: 14 | 15 | jobs: 16 | phpunit: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | version: ['8.1', '8.2', '8.3', '8.4'] 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Checkout the repository 25 | uses: actions/checkout@v2 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.version }} 33 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, mysql, mysqli, pdo_mysql, bcmath, intl, exif, iconv 34 | coverage: xdebug 35 | 36 | - name: Install composer packages 37 | run: | 38 | php -v 39 | composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts 40 | 41 | - name: Execute tests 42 | run: | 43 | php -v 44 | ./vendor/phpunit/phpunit/phpunit --version 45 | ./vendor/phpunit/phpunit/phpunit --coverage-clover=coverage.xml 46 | export CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} 47 | bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' 48 | 49 | - name: Upload code coverage 50 | run: | 51 | export CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} 52 | bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | node_modules/ 3 | .idea 4 | bootstrap/compiled.php 5 | app/storage/ 6 | bootstrap/cache/ 7 | .env.*.php 8 | .env.php 9 | .env 10 | *.DS_Store 11 | .phpunit.result.cache 12 | composer.lock 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 5 | 6 | ## [5.0.2] - 26-02-2025 7 | - Laravel 12 compatibility 8 | - Update phpunit configuration 9 | ## [5.0.1] - 13-03-2024 10 | - Laravel 11 compatibility 11 | ## [5.0.0] - 01-05-2023 12 | ### Changed 13 | - Laravel 10 compatibility 14 | - Remove old travis config and replace with GH actions 15 | ## [4.0.1] - 14-02-2022 16 | ### Changed 17 | - Laravel 9 compatibility 18 | - PHP 8 support 19 | ## [4.0.0] - 13-10-2020 20 | ### Changed 21 | - Laravel 8.0 compatibility 22 | - PHP 7.3+ required 23 | ## [3.0.3] - 26-03-2020 24 | ### Changed 25 | - Laravel 7.0 compatibility 26 | ## [3.0.2] - 22-10-2019 27 | ### Changed 28 | - Laravel 6.0 compatibility 29 | ## [3.0.1] - 10-03-2019 30 | ### Changed 31 | - Laravel 5.8 tests 32 | ## [3.0.0] - 27-05-2018 33 | #### BREAKING CHANGES, PLEASE SEE [DOCUMENTATION](https://docs.laralabs.uk/toaster) 34 | ### Added 35 | - Groups - groups of messages can now be created 36 | - Group Helper Functions - width(), classes(), position(), max() and reverse() 37 | - Message Helper Functions - duration() and speed() 38 | - Vue component npm package created to remove need for excess code/instructions [laralabs-vue-toaster](https://github.com/Laralabs/vue-toaster) 39 | - @toastcomponent blade directive to echo component markup into view 40 | - 'toast_stagger' config option - stagger message duration using the 'toast_lifetime' and 'toast_interval' config options 41 | ### Changed 42 | - Overhaul to work with a more advanced frontend component [euvl/vue-notification](https://github.com/euvl/vue-notification) 43 | ### Removed 44 | - expires() function. 45 | - 'js_namespace' config option removed, namespace will always be 'toaster' 46 | ## [2.0.1] - 29-01-2018 47 | ### Changed 48 | - composer.json updated to fix packagist 49 | ## [2.0.0] - 19-09-2017 50 | ### Added 51 | - Support for using redirect() 52 | - @toaster blade directive 53 | ### Changed 54 | - Unit tests altered to reflect changes 55 | ### Removed 56 | - toast() function no longer needed 57 | - 'bind_js_vars_to_this_view' config option 58 | ## [1.1.0] - 12-09-2017 59 | ### Added 60 | - update() function to mass update previous message properties 61 | - Unit Tests 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matt Clinton 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | Stable Build 6 | CI Status 7 | StyleCI 8 | 9 | 10 | 11 |

12 | The toaster package for Laravel provides a quick 'n' easy method for creating toast messages and binding the data to your view as a JS variable. 13 | 14 | You can then access them in your favourite JS component or use laralabs-vue-toaster, built for this package. 15 | 16 | ``` 17 | composer require laralabs/toaster 18 | ``` 19 | 20 | ## Documentation 21 | 22 | Full documentation can be found at the link below: 23 | 24 | [Toaster Documentation](https://docs.laralabs.uk/toaster) 25 | 26 | ## Preview 27 | > Example of Toaster being used with [laralabs-vue-toaster](https://github.com/Laralabs/vue-toaster), this setup is included within the documentation. 28 |

29 | 30 |

31 | 32 | ## Demo 33 | 34 | A live demo is available [here](https://toaster.laralabs.uk) 35 | 36 | ## Support 37 | Please raise an issue on GitHub if there is a problem. 38 | 39 | ## License 40 | This is open-sourced software licensed under the [MIT License](http://opensource.org/licenses/MIT). 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laralabs/toaster", 3 | "description": "Easily generate and bind message JSON data to the view for use in frontend toast components", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Matt Clinton", 8 | "email": "matt@laralabs.uk" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": { 13 | "php": "^8.1", 14 | "illuminate/support": "^10.0|^11.0|^12.0", 15 | "illuminate/database": "^10.0|^11.0|^12.0", 16 | "illuminate/contracts": "^10.0|^11.0|^12.0", 17 | "illuminate/session": "^10.0|^11.0|^12.0" 18 | }, 19 | "require-dev": { 20 | "roave/security-advisories": "dev-latest", 21 | "mockery/mockery": "^1.0", 22 | "orchestra/testbench": "^8.0|^9.0|^10.0", 23 | "phpunit/phpunit": "^9.0|^10.5|^11.5.3" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Laralabs\\Toaster\\": "src/", 28 | "Laralabs\\Toaster\\Tests\\": "tests/" 29 | }, 30 | "files": [ 31 | "src/Helpers/functions.php" 32 | ] 33 | }, 34 | "scripts": { 35 | "test": "./vendor/bin/phpunit", 36 | "test:coverage": [ 37 | "@putenv XDEBUG_MODE=coverage", 38 | "vendor/bin/phpunit --log-junit=coverage/phpunit.junit.xml --coverage-cobertura=coverage/cobertura.xml --coverage-text" 39 | ] 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Laralabs\\Toaster\\ToasterServiceProvider" 45 | ], 46 | "aliases": { 47 | "Toaster": "Laralabs\\Toaster\\Facades\\Toaster" 48 | } 49 | } 50 | }, 51 | "config": { 52 | "allow-plugins": { 53 | "dealerdirect/phpcodesniffer-composer-installer": true 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/toaster.php: -------------------------------------------------------------------------------- 1 | 10, 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Toast Stagger 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Enable/Disable stagger function, this uses the Toast Lifetime and 20 | | Toast Interval defined below. 21 | | 22 | */ 23 | 'toast_stagger' => true, 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Toast Stagger All 28 | |-------------------------------------------------------------------------- 29 | | 30 | | If true, stagger all toasts starting with the first group added. 31 | | If false, reset the lifetime for each group. 32 | | 33 | */ 34 | 'toast_stagger_all' => true, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Toast Lifetime 39 | |-------------------------------------------------------------------------- 40 | | 41 | | When a toast is not set as important, this is the amount of time it 42 | | stays visible for (Milliseconds). 43 | | 44 | */ 45 | 'toast_lifetime' => 2000, 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Toast Interval 50 | |-------------------------------------------------------------------------- 51 | | 52 | | The amount of time between each toast closing (Milliseconds). 53 | | 54 | */ 55 | 'toast_interval' => 500, 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Toast Position 60 | |-------------------------------------------------------------------------- 61 | | 62 | | The position of the toast on the page, i.e. 'top left', 'top right', 63 | | 'bottom right' and 'bottom left'. 64 | | 65 | */ 66 | 'toast_position' => 'top right', 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Toast Width 71 | |-------------------------------------------------------------------------- 72 | | 73 | | Classes specified here will be added to the toast component. 74 | | 75 | */ 76 | 'toast_width' => '300px', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Toast Classes 81 | |-------------------------------------------------------------------------- 82 | | 83 | | Classes specified here will be added to the toast component. 84 | | 85 | */ 86 | 'toast_classes' => [], 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Reverse Order 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Show toasts in reverse order. 94 | | 95 | */ 96 | 'reverse_order' => false, 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Animation Type 101 | |-------------------------------------------------------------------------- 102 | | 103 | | The animation type used, i.e. 'css' or 'velocity'. 104 | | 105 | */ 106 | 'animation_type' => 'css', 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Animation Speed 111 | |-------------------------------------------------------------------------- 112 | | 113 | | The animation type used, i.e. 'css' or 'velocity'. 114 | | 115 | */ 116 | 'animation_speed' => 300, 117 | ]; 118 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | src 25 | 26 | 27 | vendor 28 | tests 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Helpers/functions.php: -------------------------------------------------------------------------------- 1 | session = $session; 23 | } 24 | 25 | /** 26 | * Flash a message to the session. 27 | * 28 | * @param $name 29 | * @param $data 30 | */ 31 | public function flash($name, $data) 32 | { 33 | $this->session->flash($name, $data); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Toast.php: -------------------------------------------------------------------------------- 1 | update($attributes); 64 | } 65 | 66 | /** 67 | * Update the attributes. 68 | * 69 | * @param array $attributes 70 | * 71 | * @return $this 72 | */ 73 | public function update($attributes = []) 74 | { 75 | $attributes = array_filter($attributes); 76 | 77 | foreach ($attributes as $key => $attribute) { 78 | $this->$key = $attribute; 79 | } 80 | 81 | return $this; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Toaster.php: -------------------------------------------------------------------------------- 1 | session = $session; 54 | $this->groups = collect(); 55 | $this->lifetime = config('toaster.toast_lifetime'); 56 | $this->interval = config('toaster.toast_interval'); 57 | $this->limit = config('toaster.max_toasts'); 58 | $this->position = config('toaster.toast_position'); 59 | $this->currentGroup = 'default'; 60 | } 61 | 62 | /** 63 | * Set message info theme. 64 | * 65 | * @return Toaster 66 | */ 67 | public function info() 68 | { 69 | $this->groups->last()->updateLastMessage(['type' => 'info']); 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Set message success theme. 76 | * 77 | * @return Toaster 78 | */ 79 | public function success() 80 | { 81 | $this->groups->last()->updateLastMessage(['type' => 'success']); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Set message error theme. 88 | * 89 | * @return Toaster 90 | */ 91 | public function error() 92 | { 93 | $this->groups->last()->updateLastMessage(['type' => 'error']); 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Set message warning theme. 100 | * 101 | * @return Toaster 102 | */ 103 | public function warning() 104 | { 105 | $this->groups->last()->updateLastMessage(['type' => 'warn']); 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Set message title. 112 | * 113 | * @param $value string 114 | * 115 | * @return Toaster 116 | */ 117 | public function title(string $value) 118 | { 119 | $this->groups->last()->updateLastMessage(['title' => $value]); 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * Set message as important. 126 | * 127 | * @return Toaster 128 | */ 129 | public function important() 130 | { 131 | $this->groups->last()->updateLastMessage(['duration' => -1, 'customDuration' => true]); 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Set message duration. 138 | * 139 | * @param $value int 140 | * 141 | * @throws \InvalidArgumentException 142 | * 143 | * @return Toaster 144 | */ 145 | public function duration(int $value) 146 | { 147 | $this->groups->last()->updateLastMessage(['duration' => $value, 'customDuration' => true]); 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Set message animation speed. 154 | * 155 | * @param $value int 156 | * 157 | * @return Toaster 158 | */ 159 | public function speed(int $value) 160 | { 161 | $this->groups->last()->updateLastMessage(['speed' => $value]); 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * Add a message to the toaster. 168 | * 169 | * @param $message string 170 | * @param $title null|string 171 | * @param $properties null|array 172 | * 173 | * @throws \Exception 174 | * 175 | * @return Toaster 176 | */ 177 | public function add(string $message, $title = null, $properties = null) 178 | { 179 | if (is_array($properties)) { 180 | $properties['message'] = $message; 181 | $properties['title'] = is_null($title) ? isset($properties['title']) ? $properties['title'] : $title : $title; 182 | $properties['group'] = isset($properties['group']) ? $properties['group'] : $this->currentGroup; 183 | $group = $properties['group']; 184 | $message = new Toast($properties); 185 | } else { 186 | $group = $this->currentGroup; 187 | $message = new Toast(compact('message', 'title', 'group')); 188 | } 189 | 190 | if ($this->groups->count() < 1) { 191 | $this->group($this->currentGroup); 192 | } 193 | 194 | try { 195 | $this->groups->where('name', '=', $group)->first()->add($message); 196 | 197 | return $this->flash(); 198 | } catch (\Throwable $e) { 199 | throw new \Exception('No group found with the specified name'); 200 | } 201 | } 202 | 203 | /** 204 | * Create a new group or update existing group. 205 | * 206 | * @param $name 207 | * @param $properties null|array 208 | * 209 | * @return Toaster 210 | */ 211 | public function group($name, $properties = null) 212 | { 213 | if ($group = $this->groups->where('name', '=', $name)->first()) { 214 | if (is_array($properties)) { 215 | $group->updateProperties($properties); 216 | } 217 | } else { 218 | $group = new ToasterGroup($name, $properties); 219 | $this->groups->push($group); 220 | } 221 | 222 | $this->currentGroup = $name; 223 | 224 | return $this; 225 | } 226 | 227 | /** 228 | * Set group width. 229 | * 230 | * @param string $width 231 | * 232 | * @return Toaster 233 | */ 234 | public function width(string $width) 235 | { 236 | $this->groups->last()->updateProperty('width', $width); 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * Set group classes. 243 | * 244 | * @param array $classes 245 | * 246 | * @return Toaster 247 | */ 248 | public function classes(array $classes) 249 | { 250 | $this->groups->last()->updateProperty('classes', $classes); 251 | 252 | return $this; 253 | } 254 | 255 | /** 256 | * Set group position. 257 | * 258 | * @param string $position 259 | * 260 | * @return Toaster 261 | */ 262 | public function position(string $position) 263 | { 264 | $this->groups->last()->updateProperty('position', $position); 265 | 266 | return $this; 267 | } 268 | 269 | /** 270 | * Set group max. 271 | * 272 | * @param int $max 273 | * 274 | * @return Toaster 275 | */ 276 | public function max(int $max) 277 | { 278 | $this->groups->last()->updateProperty('max', $max); 279 | 280 | return $this; 281 | } 282 | 283 | /** 284 | * Set group reverse order. 285 | * 286 | * @param bool $reverse 287 | * 288 | * @return Toaster 289 | */ 290 | public function reverse(bool $reverse) 291 | { 292 | $this->groups->last()->updateProperty('reverse', $reverse); 293 | 294 | return $this; 295 | } 296 | 297 | /** 298 | * Updates the previous message. 299 | * 300 | * @param array $attributes 301 | * 302 | * @return Toaster 303 | */ 304 | public function update(array $attributes) 305 | { 306 | $this->groups->last()->updateLastMessage($attributes); 307 | 308 | return $this; 309 | } 310 | 311 | /** 312 | * Clear all registered groups. 313 | * 314 | * @return Toaster 315 | */ 316 | public function clear() 317 | { 318 | $this->groups = collect(); 319 | $this->flash(); 320 | 321 | return $this; 322 | } 323 | 324 | /** 325 | * Stagger messages with lifetime and interval. 326 | * 327 | * 328 | * @param bool $all 329 | */ 330 | protected function stagger($all = true) 331 | { 332 | $current = $this->lifetime - $this->interval; 333 | 334 | foreach ($this->groups->all() as $group) { 335 | $current = $all ? $current : $this->lifetime - $this->interval; 336 | foreach ($group->messages->all() as $message) { 337 | $current = $current + $this->interval; 338 | $message->duration = $message->customDuration ? $message->duration : $current; 339 | $current = $message->customDuration ? $current - $this->interval : $current; 340 | } 341 | } 342 | } 343 | 344 | /** 345 | * Flash all messages to the session. 346 | * 347 | * @return Toaster 348 | */ 349 | public function flash() 350 | { 351 | if (config('toaster.toast_stagger')) { 352 | config('toaster.toast_stagger_all') ? $this->stagger() : $this->stagger(false); 353 | } 354 | 355 | $this->session->flash('toaster', $this->parse()); 356 | 357 | return $this; 358 | } 359 | 360 | /** 361 | * Parse groups and messages into array. 362 | * 363 | * @return array 364 | */ 365 | protected function parse() 366 | { 367 | $payload = ['data' => []]; 368 | 369 | foreach ($this->groups->all() as $group) { 370 | $payload['data'][$group->name] = array_merge($group->properties, ['messages' => $group->messages->toArray()]); 371 | } 372 | 373 | return $payload; 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/ToasterGroup.php: -------------------------------------------------------------------------------- 1 | name = $name; 25 | $this->properties = [ 26 | 'name' => $name, 27 | 'width' => config('toaster.toast_width'), 28 | 'classes' => config('toaster.toast_classes'), 29 | 'animation_type' => config('toaster.animation_type'), 30 | 'animation_name' => null, 31 | 'velocity_config' => 'velocity', 32 | 'position' => config('toaster.toast_position'), 33 | 'max' => config('toaster.max_toasts'), 34 | 'reverse' => config('toaster.reverse_order'), 35 | ]; 36 | if (is_array($properties)) { 37 | $this->properties = array_merge($this->properties, $properties); 38 | } 39 | $this->messages = collect(); 40 | } 41 | 42 | /** 43 | * @param $toast 44 | * 45 | * @return \Illuminate\Support\Collection 46 | */ 47 | public function add($toast) 48 | { 49 | return $this->messages->push($toast); 50 | } 51 | 52 | /** 53 | * Update property. 54 | * 55 | * @param $key 56 | * @param $value 57 | * 58 | * @return \Laralabs\Toaster\Toaster 59 | */ 60 | public function updateProperty($key, $value) 61 | { 62 | $this->properties[$key] = $value; 63 | 64 | return app('toaster')->flash(); 65 | } 66 | 67 | /** 68 | * Update properties. 69 | * 70 | * @param $properties 71 | * 72 | * @return \Laralabs\Toaster\Toaster 73 | */ 74 | public function updateProperties($properties) 75 | { 76 | foreach ($properties as $key => $value) { 77 | $this->properties[$key] = $value; 78 | } 79 | 80 | return app('toaster')->flash(); 81 | } 82 | 83 | /** 84 | * Modify the most recently added message. 85 | * 86 | * @param array $overrides 87 | * 88 | * @throws \Exception 89 | * 90 | * @return \Laralabs\Toaster\Toaster 91 | */ 92 | public function updateLastMessage($overrides = []) 93 | { 94 | if ($this->messages->count() > 0) { 95 | $this->messages->last()->update($overrides); 96 | 97 | return app('toaster')->flash(); 98 | } 99 | 100 | throw new \Exception('Use the add() function to add a message before attempting to modify it'); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/ToasterServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind( 18 | 'Laralabs\Toaster\Interfaces\SessionStore', 19 | 'Laralabs\Toaster\LaravelSessionStore' 20 | ); 21 | 22 | $this->app->singleton('toaster', function () { 23 | return $this->app->make('Laralabs\Toaster\Toaster'); 24 | }); 25 | 26 | $this->app->singleton('toasterViewBinder', function () { 27 | return $this->app->make('Laralabs\Toaster\ToasterViewBinder'); 28 | }); 29 | 30 | $this->mergeConfigFrom( 31 | __DIR__.'/../config/toaster.php', 32 | 'toaster' 33 | ); 34 | } 35 | 36 | /** 37 | * Bootstrap the application events. 38 | * 39 | * @return void 40 | */ 41 | public function boot() 42 | { 43 | $this->publishes([ 44 | __DIR__.'/../config/toaster.php' => config_path('toaster.php'), 45 | ], 'config'); 46 | 47 | Blade::directive('toaster', function () { 48 | return "bind(); ?>"; 49 | }); 50 | 51 | Blade::directive('toastcomponent', function () { 52 | return "component(); ?>"; 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ToasterViewBinder.php: -------------------------------------------------------------------------------- 1 | router = $router; 35 | $this->store = $store; 36 | 37 | $this->namespace = 'toaster'; 38 | } 39 | 40 | /** 41 | * Generates a JS variable. 42 | * 43 | * @return mixed 44 | */ 45 | public function generateJs() 46 | { 47 | if ($this->store->has('toaster')) { 48 | $data = $this->store->get('toaster'); 49 | reset($data); 50 | $js = 'window.'.$this->namespace.' = window.'.$this->namespace.' || {};'.$this->namespace.'.'.key($data).' = '; 51 | $js = $js.json_encode($data[key($data)]); 52 | 53 | return $js; 54 | } 55 | 56 | return 'window.'.$this->namespace.' = window.'.$this->namespace.' || {};'.$this->namespace.'.data = {};'; 57 | } 58 | 59 | /** 60 | * Generate component data. 61 | * 62 | * @return array 63 | */ 64 | protected function generateComponents() 65 | { 66 | $components = []; 67 | 68 | if ($this->store->has('toaster')) { 69 | $data = $this->store->get('toaster'); 70 | 71 | foreach ($data['data'] as $group => $properties) { 72 | unset($properties['messages']); 73 | $components[$group] = $properties; 74 | } 75 | 76 | $this->store->forget('toaster'); 77 | 78 | return $components; 79 | } 80 | 81 | return $components; 82 | } 83 | 84 | /** 85 | * Return the JavaScript variable to the view. 86 | * 87 | * @return string 88 | */ 89 | public function bind() 90 | { 91 | return ''; 92 | } 93 | 94 | /** 95 | * Generate vue-notification component markup. 96 | * 97 | * @return string 98 | */ 99 | public function component() 100 | { 101 | $components = ''; 102 | 103 | foreach ($this->generateComponents() as $group => $props) { 104 | $components = $components.' '. 105 | ''.''.PHP_EOL; 113 | } 114 | 115 | return $components.''; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | session = app('Laralabs\Toaster\Interfaces\SessionStore'); 75 | 76 | $this->toaster = new Toaster($this->session); 77 | $this->binder = app('toasterViewBinder'); 78 | 79 | $this->limit = config('toaster.max_toasts'); 80 | $this->position = config('toaster.toast_position'); 81 | $this->lifetime = config('toaster.toast_lifetime'); 82 | $this->interval = config('toaster.toast_interval'); 83 | $this->width = config('toaster.toast_width'); 84 | $this->classes = config('toaster.toast_classes'); 85 | $this->reverse = config('toaster.reverse_order'); 86 | $this->animationType = config('toaster.animation_type'); 87 | $this->animationSpeed = config('toaster.animation_speed'); 88 | } 89 | 90 | protected function tearDown(): void 91 | { 92 | parent::tearDown(); 93 | 94 | \Mockery::close(); 95 | } 96 | 97 | protected function getPackageProviders($app) 98 | { 99 | return [ 100 | ToasterServiceProvider::class, 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/ToasterTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Toaster::class, $toaster); 18 | } 19 | 20 | /** @test */ 21 | public function it_displays_default_toast_and_can_clear_all(): void 22 | { 23 | $this->toaster->add('cheese'); 24 | 25 | $this->assertCount(1, $this->toaster->groups); 26 | 27 | $group = $this->toaster->groups->first(); 28 | 29 | $this->assertInstanceOf(ToasterGroup::class, $group); 30 | $this->assertCount(1, $group->messages); 31 | 32 | $toast = $group->messages->first(); 33 | 34 | $this->assertEquals('cheese', $toast->message); 35 | $this->assertEquals('info', $toast->type); 36 | $this->assertEquals('', $toast->title); 37 | $this->assertEquals($this->lifetime, $toast->duration); 38 | $this->assertEquals($this->animationSpeed, $toast->speed); 39 | 40 | $this->assertSessionHas('toaster', [ 41 | 'data' => [ 42 | 'default' => [ 43 | 'name' => 'default', 44 | 'width' => $this->width, 45 | 'classes' => [], 46 | 'animation_type' => $this->animationType, 47 | 'animation_name' => null, 48 | 'position' => $this->position, 49 | 'max' => $this->limit, 50 | 'reverse' => $this->reverse, 51 | 'messages' => $this->toaster->groups->first()->messages->toArray(), 52 | 'velocity_config' => 'velocity', 53 | ], 54 | ], 55 | ]); 56 | 57 | $this->toaster->clear(); 58 | 59 | $this->assertSessionHas('toaster', [ 60 | 'data' => [], 61 | ]); 62 | } 63 | 64 | /** @test */ 65 | public function it_can_create_a_toaster_group_and_set_name(): void 66 | { 67 | $this->toaster->group('toastie'); 68 | 69 | $group = $this->toaster->groups->first(); 70 | 71 | $this->assertInstanceOf(ToasterGroup::class, $group); 72 | $this->assertEquals('toastie', $group->name); 73 | 74 | $this->toaster->clear(); 75 | } 76 | 77 | /** @test */ 78 | public function it_can_create_a_toaster_group_and_set_properties(): void 79 | { 80 | $properties = [ 81 | 'name' => 'toastie', 82 | 'width' => '500px', 83 | 'classes' => ['salt', 'pepper'], 84 | 'animation_type' => 'css', 85 | 'animation_name' => 'animation-name', 86 | 'velocity_config' => 'velocity', 87 | 'position' => 'bottom left', 88 | 'max' => 15, 89 | 'reverse' => true, 90 | ]; 91 | 92 | $this->toaster->group($properties['name'], $properties); 93 | 94 | $group = $this->toaster->groups->last(); 95 | 96 | $this->assertInstanceOf(ToasterGroup::class, $group); 97 | $this->assertEquals('toastie', $group->name); 98 | $this->assertEquals($properties, $group->properties); 99 | 100 | $this->toaster->clear(); 101 | } 102 | 103 | /** @test */ 104 | public function it_can_update_existing_group_properties(): void 105 | { 106 | $properties = [ 107 | 'name' => 'toastie', 108 | 'width' => '500px', 109 | 'classes' => ['salt', 'pepper'], 110 | 'animation_type' => 'css', 111 | 'animation_name' => 'animation-name', 112 | 'velocity_config' => 'velocity', 113 | 'position' => 'bottom left', 114 | 'max' => 15, 115 | 'reverse' => true, 116 | ]; 117 | 118 | $this->toaster->group('toastie')->group($properties['name'], $properties); 119 | 120 | $group = $this->toaster->groups->last(); 121 | 122 | $this->assertInstanceOf(ToasterGroup::class, $group); 123 | $this->assertCount(1, $this->toaster->groups); 124 | $this->assertEquals('toastie', $group->name); 125 | $this->assertEquals($properties, $group->properties); 126 | 127 | $this->toaster->clear(); 128 | } 129 | 130 | /** @test */ 131 | public function it_can_set_group_width(): void 132 | { 133 | $this->toaster->group('toastie')->width('100%'); 134 | 135 | $group = $this->toaster->groups->first(); 136 | 137 | $this->assertInstanceOf(ToasterGroup::class, $group); 138 | $this->assertEquals('toastie', $group->name); 139 | $this->assertEquals('100%', $group->properties['width']); 140 | 141 | $this->toaster->clear(); 142 | } 143 | 144 | /** @test */ 145 | public function it_can_set_group_classes(): void 146 | { 147 | $this->toaster->group('toastie')->classes(['salt', 'pepper']); 148 | 149 | $group = $this->toaster->groups->first(); 150 | 151 | $this->assertInstanceOf(ToasterGroup::class, $group); 152 | $this->assertEquals('toastie', $group->name); 153 | $this->assertEquals(['salt', 'pepper'], $group->properties['classes']); 154 | 155 | $this->toaster->clear(); 156 | } 157 | 158 | /** @test */ 159 | public function it_can_set_group_position(): void 160 | { 161 | $this->toaster->group('toastie')->position('top left'); 162 | 163 | $group = $this->toaster->groups->first(); 164 | 165 | $this->assertInstanceOf(ToasterGroup::class, $group); 166 | $this->assertEquals('toastie', $group->name); 167 | $this->assertEquals('top left', $group->properties['position']); 168 | 169 | $this->toaster->clear(); 170 | } 171 | 172 | /** @test */ 173 | public function it_can_set_group_max_toasts(): void 174 | { 175 | $this->toaster->group('toastie')->max(10); 176 | 177 | $group = $this->toaster->groups->first(); 178 | 179 | $this->assertInstanceOf(ToasterGroup::class, $group); 180 | $this->assertEquals('toastie', $group->name); 181 | $this->assertEquals(10, $group->properties['max']); 182 | 183 | $this->toaster->clear(); 184 | } 185 | 186 | /** @test */ 187 | public function it_can_set_group_reverse_order(): void 188 | { 189 | $this->toaster->group('toastie')->reverse(true); 190 | 191 | $group = $this->toaster->groups->first(); 192 | 193 | $this->assertInstanceOf(ToasterGroup::class, $group); 194 | $this->assertEquals('toastie', $group->name); 195 | $this->assertEquals(true, $group->properties['reverse']); 196 | 197 | $this->toaster->clear(); 198 | } 199 | 200 | /** @test */ 201 | public function it_can_mass_update_last_toast_properties(): void 202 | { 203 | $properties = [ 204 | 'group' => 'toastie', 205 | 'message' => 'cheese', 206 | 'type' => 'warn', 207 | 'title' => 'Toastie Ingredients', 208 | 'duration' => $this->lifetime, 209 | 'speed' => $this->animationSpeed, 210 | ]; 211 | 212 | $this->toaster->group($properties['group']) 213 | ->add('cheese') 214 | ->update($properties); 215 | 216 | $group = $this->toaster->groups->last(); 217 | $toast = $group->messages->last(); 218 | 219 | $this->assertInstanceOf(ToasterGroup::class, $group); 220 | $this->assertEquals('toastie', $group->name); 221 | 222 | foreach ($properties as $property => $value) { 223 | $this->assertEquals($value, $toast->$property); 224 | } 225 | 226 | $this->toaster->clear(); 227 | } 228 | 229 | /** @test */ 230 | public function it_can_add_toast_with_properties(): void 231 | { 232 | $properties = [ 233 | 'group' => 'toastie', 234 | 'message' => 'cheese', 235 | 'type' => 'warn', 236 | 'title' => 'Toastie Ingredients', 237 | 'duration' => $this->lifetime, 238 | 'speed' => $this->animationSpeed, 239 | ]; 240 | 241 | $this->toaster->group($properties['group']) 242 | ->add('cheese', null, $properties); 243 | 244 | $group = $this->toaster->groups->last(); 245 | $toast = $group->messages->last(); 246 | 247 | $this->assertInstanceOf(ToasterGroup::class, $group); 248 | $this->assertEquals('toastie', $group->name); 249 | 250 | foreach ($properties as $property => $value) { 251 | $this->assertEquals($value, $toast->$property); 252 | } 253 | 254 | $this->toaster->clear(); 255 | } 256 | 257 | /** @test */ 258 | public function it_can_add_toast_to_specified_group(): void 259 | { 260 | $properties = [ 261 | 'group' => 'toastie', 262 | 'message' => 'cheese', 263 | 'type' => 'warn', 264 | 'title' => 'Toastie Ingredients', 265 | 'duration' => $this->lifetime + $this->interval, 266 | 'speed' => $this->animationSpeed, 267 | ]; 268 | 269 | $this->toaster->group('toastie')->add('ham') 270 | ->group('toastie-two') 271 | ->add('cheese') 272 | ->add('cheese', null, $properties); 273 | 274 | $group = $this->toaster->groups->first(); 275 | $toast = $group->messages->last(); 276 | 277 | $this->assertInstanceOf(ToasterGroup::class, $group); 278 | $this->assertEquals('toastie', $group->name); 279 | 280 | foreach ($properties as $property => $value) { 281 | $this->assertEquals($value, $toast->$property); 282 | } 283 | 284 | $this->toaster->clear(); 285 | } 286 | 287 | /** @test */ 288 | public function it_throws_exception_for_invalid_group(): void 289 | { 290 | $this->expectExceptionMessage('No group found with the specified name'); 291 | 292 | $properties = [ 293 | 'group' => 'toastie-invalid-group', 294 | 'message' => 'cheese', 295 | 'type' => 'warn', 296 | 'title' => 'Toastie Ingredients', 297 | 'duration' => $this->lifetime + $this->interval, 298 | 'speed' => $this->animationSpeed, 299 | ]; 300 | 301 | $this->toaster->group('toastie')->add('ham') 302 | ->group('toastie-two') 303 | ->add('cheese') 304 | ->add('cheese', null, $properties); 305 | 306 | $this->toaster->clear(); 307 | } 308 | 309 | /** @test */ 310 | public function it_can_stagger_groups(): void 311 | { 312 | Config::set('toaster.toast_stagger_all', false); 313 | 314 | $this->toaster->group('toastie') 315 | ->add('ham') 316 | ->add('cheese') 317 | ->group('toastie-two') 318 | ->add('cheese') 319 | ->add('tomato'); 320 | 321 | foreach ($this->toaster->groups->all() as $group) { 322 | $current = $this->lifetime - $this->interval; 323 | foreach ($group->messages->all() as $message) { 324 | $current = $current + $this->interval; 325 | $message->customDuration ? null : $this->assertEquals($current, $message->duration); 326 | $current = $message->customDuration ? $current - $this->interval : $current; 327 | } 328 | } 329 | } 330 | 331 | /** @test */ 332 | public function it_can_stagger_all_groups_and_retain_custom_duration(): void 333 | { 334 | Config::set('toaster.toast_stagger_all', true); 335 | 336 | $this->toaster->group('toastie') 337 | ->add('ham') 338 | ->add('cheese') 339 | ->group('toastie-two') 340 | ->add('cheese')->duration(10000) 341 | ->add('tomato'); 342 | 343 | $current = $this->lifetime - $this->interval; 344 | foreach ($this->toaster->groups->all() as $group) { 345 | $current = config('toaster.toast_stagger_all') ? $current : $this->lifetime - $this->interval; 346 | foreach ($group->messages->all() as $message) { 347 | $current = $current + $this->interval; 348 | $message->customDuration ? $this->assertEquals(10000, $message->duration) : $this->assertEquals($current, $message->duration); 349 | $current = $message->customDuration ? $current - $this->interval : $current; 350 | } 351 | } 352 | } 353 | 354 | /** @test */ 355 | public function it_displays_multiple_toast(): void 356 | { 357 | $this->toaster->group('toastie') 358 | ->add('ham') 359 | ->add('cheese'); 360 | 361 | $this->assertCount(2, $this->toaster->groups->first()->messages); 362 | 363 | $this->toaster->clear(); 364 | } 365 | 366 | /** @test */ 367 | public function it_sets_default_duration_and_speed(): void 368 | { 369 | $this->toaster->group('toastie')->add('cheese'); 370 | 371 | $this->assertCount(1, $this->toaster->groups->first()->messages); 372 | 373 | $toast = $this->toaster->groups->first()->messages->first(); 374 | 375 | $this->assertEquals($this->lifetime, $toast->duration); 376 | $this->assertEquals($this->animationSpeed, $toast->speed); 377 | 378 | $this->toaster->clear(); 379 | } 380 | 381 | /** @test */ 382 | public function it_sets_custom_duration_and_speed(): void 383 | { 384 | $this->toaster->group('toastie')->add('cheese')->duration(5000)->speed(800); 385 | 386 | $this->assertCount(1, $this->toaster->groups->first()->messages); 387 | 388 | $toast = $this->toaster->groups->first()->messages->first(); 389 | 390 | $this->assertEquals('cheese', $toast->message); 391 | $this->assertTrue($toast->customDuration); 392 | $this->assertEquals(5000, $toast->duration); 393 | $this->assertEquals(800, $toast->speed); 394 | 395 | $this->toaster->clear(); 396 | } 397 | 398 | /** @test */ 399 | public function it_sets_info_toast(): void 400 | { 401 | $this->toaster->group('toastie')->add('cheese')->info(); 402 | 403 | $this->assertCount(1, $this->toaster->groups->first()->messages); 404 | 405 | $toast = $this->toaster->groups->first()->messages->first(); 406 | 407 | $this->assertEquals('cheese', $toast->message); 408 | $this->assertEquals('info', $toast->type); 409 | 410 | $this->toaster->clear(); 411 | } 412 | 413 | /** @test */ 414 | public function it_sets_success_toast(): void 415 | { 416 | $this->toaster->group('toastie')->add('cheese')->success(); 417 | 418 | $this->assertCount(1, $this->toaster->groups->first()->messages); 419 | 420 | $toast = $this->toaster->groups->first()->messages->first(); 421 | 422 | $this->assertEquals('cheese', $toast->message); 423 | $this->assertEquals('success', $toast->type); 424 | 425 | $this->toaster->clear(); 426 | } 427 | 428 | /** @test */ 429 | public function it_sets_warning_toast(): void 430 | { 431 | $this->toaster->group('toastie')->add('cheese')->warning(); 432 | 433 | $this->assertCount(1, $this->toaster->groups->first()->messages); 434 | 435 | $toast = $this->toaster->groups->first()->messages->first(); 436 | 437 | $this->assertEquals('cheese', $toast->message); 438 | $this->assertEquals('warn', $toast->type); 439 | 440 | $this->toaster->clear(); 441 | } 442 | 443 | /** @test */ 444 | public function it_sets_error_toast(): void 445 | { 446 | $this->toaster->group('toastie')->add('cheese')->error(); 447 | 448 | $this->assertCount(1, $this->toaster->groups->first()->messages); 449 | 450 | $toast = $this->toaster->groups->first()->messages->first(); 451 | 452 | $this->assertEquals('cheese', $toast->message); 453 | $this->assertEquals('error', $toast->type); 454 | 455 | $this->toaster->clear(); 456 | } 457 | 458 | /** @test */ 459 | public function it_sets_important_toast(): void 460 | { 461 | $this->toaster->group('toastie')->add('cheese')->important(); 462 | 463 | $this->assertCount(1, $this->toaster->groups->first()->messages); 464 | 465 | $toast = $this->toaster->groups->first()->messages->first(); 466 | 467 | $this->assertEquals('cheese', $toast->message); 468 | $this->assertEquals('info', $toast->type); 469 | $this->assertEquals(-1, $toast->duration); 470 | 471 | $this->toaster->clear(); 472 | } 473 | 474 | /** @test */ 475 | public function it_sets_toast_title(): void 476 | { 477 | $this->toaster->group('toastie')->add('cheese')->title('Toastie Ingredients'); 478 | 479 | $this->assertCount(1, $this->toaster->groups->first()->messages); 480 | 481 | $toast = $this->toaster->groups->first()->messages->first(); 482 | 483 | $this->assertEquals('cheese', $toast->message); 484 | $this->assertEquals('info', $toast->type); 485 | $this->assertEquals('Toastie Ingredients', $toast->title); 486 | 487 | $this->toaster->clear(); 488 | } 489 | 490 | /** @test */ 491 | public function it_generates_correctly_structured_json(): void 492 | { 493 | $this->toaster->group('toastie')->add('ham')->success()->add('cheese'); 494 | 495 | $validJson = 'window.toaster = window.toaster || {};toaster.data = {"toastie":{"name":"toastie","width":"300px","classes":[],"animation_type":"css","animation_name":null,"velocity_config":"velocity","position":"top right","max":10,"reverse":false,"messages":[{"group":"toastie","message":"ham","type":"success","title":"","duration":2000,"speed":300,"customDuration":null},{"group":"toastie","message":"cheese","type":"info","title":"","duration":2500,"speed":300,"customDuration":null}]}}'; 496 | 497 | $this->assertEquals($validJson, $this->binder->generateJs()); 498 | 499 | $this->toaster->clear(); 500 | } 501 | 502 | /** @test */ 503 | public function it_generates_correct_component_html(): void 504 | { 505 | $this->toaster->group('toastie')->add('ham')->success()->add('cheese'); 506 | 507 | $validComponent = ' '.PHP_EOL.''; 508 | 509 | $this->assertEquals($validComponent, $this->binder->component()); 510 | } 511 | 512 | /** @test */ 513 | public function it_has_mandatory_message_argument(): void 514 | { 515 | $this->expectException('ArgumentCountError'); 516 | 517 | $this->toaster->add(); 518 | } 519 | 520 | /** @test */ 521 | public function it_aborts_editing_non_message(): void 522 | { 523 | $this->expectExceptionMessage('Use the add() function to add a message before attempting to modify it'); 524 | 525 | $this->toaster->group('toastie')->success(); 526 | } 527 | 528 | protected function assertSessionHas($name, $value = null): void 529 | { 530 | $this->assertTrue(Session::has($name), "Session doesn't contain '$name'"); 531 | if ($value) { 532 | $this->assertEquals($value, Session::get($name), "Session '$name' are not equal to".print_r($value).''); 533 | } 534 | } 535 | } 536 | --------------------------------------------------------------------------------