diff --git a/7.x-dev/add-ons-community.md b/7.x-dev/add-ons-community.md new file mode 100644 index 00000000..70fea4c4 --- /dev/null +++ b/7.x-dev/add-ons-community.md @@ -0,0 +1,17 @@ +# Community Add-ons + +--- + +We've been blessed with a wonderful, supportive community, where developers help each other out. Some of them have even created add-ons, so that we can all reuse functionality across our projects: + +| Name | Description | License | +| ------------- |:-------------:| --------:| +| [LogViewer](https://github.com/eduardoarandah/backpacklogviewer) | advanced logging interface - brings the ArcaneDev/LogViewer package to Backpack admin panels | [MIT](https://github.com/eduardoarandah/backpacklogviewer/blob/master/LICENSE.md) | +| [UserManager](https://github.com/eduardoarandah/UserManager) | manage the default Laravel users table (no permissions, no groups) | [MIT](https://github.com/eduardoarandah/UserManager/blob/master/LICENSE) | +| [laravel-backpack-redirection-manager](https://github.com/novius/laravel-backpack-redirection-manager) | manage missing page redirections | [AGPL-3](https://github.com/eduardoarandah/backpacklogviewer/blob/master/LICENSE.md) | +| [laravel-backpack-translation-manager](https://github.com/novius/laravel-backpack-translation-manager) | manage translations stored in the database | - | +| [estarter-ecommerce-for-laravel](https://github.com/updivision/estarter-ecommerce-for-laravel) | complete e-commerce back-end (products, categories, clients, orders) | [YUMMY](https://github.com/updivision/estarter-ecommerce-for-laravel/blob/master/LICENSE.md) | +| [laravel-generators](https://github.com/webfactor/laravel-generators) | CLI to generate migrations, factories, seeders, CRUDs, lang files, route files | [MIT](https://github.com/webfactor/laravel-generators/blob/master/LICENSE.md) | +| [laravel-backpack-gallery-crud](https://gitlab.com/seandowney/laravel-backpack-gallery-crud) | manage photo galleries | [MIT](https://gitlab.com/seandowney/laravel-backpack-gallery-crud/blob/master/LICENSE.md) | +| [signature-field-for-backpack](https://github.com/iMokhles/signature-field-for-backpack) | field type that lets admins draw their signature | [MIT](https://github.com/iMokhles/signature-field-for-backpack/blob/master/license.md) | +| [DynamicFieldHintsForBackpack](https://github.com/DoDSoftware/DynamicFieldHintsForBackpack) | automatically add db comments as field hints | [MIT](https://github.com/DoDSoftware/DynamicFieldHintsForBackpack/blob/master/license.md) | diff --git a/7.x-dev/add-ons-custom-operation.md b/7.x-dev/add-ons-custom-operation.md new file mode 100644 index 00000000..fa19ab02 --- /dev/null +++ b/7.x-dev/add-ons-custom-operation.md @@ -0,0 +1,197 @@ +# Create an Add-On for a Custom Operation + +----- + +This tutorial will help you package a custom operation into a Composer package, so that you (or other people) can use it in multiple Laravel projects. If you haven't already, please [create your custom operation](/docs/{{version}}/crud-operations#creating-a-custom-operation) first, and make sure it's working well, before you move it to a package. It's just easier that way. + + + +## Part A. Create The Package + + + +### Step 1. Generate the package folder + +Install this excellent package that will create the boilerplate code for you: +```sh +composer require jeroen-g/laravel-packager --dev +``` + +Ask the package to generate the boilerplate code for your new package: + +```sh +php artisan packager:new MyName SomeCustomOperation --i +``` + +Keep in mind: +- the ```MyName``` should be your GitHub handle (or organisation), in studly case (```CompanyName```); +- the ```SomeCustomOperation``` should be the package name you want, in studly case (```ModerateOperation```); +- the ```website``` should be a valid URL, so include the protocol too: ```http://example.com```; +- the ```description``` should be pretty short; +- the ```license``` is just the license name, if it's a common one (ex: ```MIT```, ```GPLv2```); + +This will create a ```/packages/MyName/SomeCustomOperation``` folder in your root directory, which will hold all the code for your package. It has pulled a basic package template, that we need to customize. + +It will also modify your project's ```composer.json``` file to point to this new folder. + + +### Step 2. Define dependencies + +Inside your ```/packages/MyName/SomeCustomOperation/composer.json``` file, make sure you require the version of Backpack your package will support. If unsure, copy-paste the requirement from your project's ```composer.json``` file. + +```diff + "require": { ++ "backpack/crud": "^4.0.0" +- "illuminate/support": "~5|~6" + }, +``` + +Note: +- you can also remove the ```illuminate/support``` requirement in most cases - if Backpack is installed, so is that; +- feel free to add any other requirements your package might have; + +Notice that this ```composer.json``` will also: +- define your package namespace (in ```autoload/psr-4```); +- set up Laravel package autoloading (in ```extra/laravel/providers```); + + +### Step 3. Instruct your Laravel Project to use your package + +```sh +composer require myname/somecustomoperation +``` + +Congratulations! Now you have a basic package, installed in ```packages/MyName/SomeCustomOperation```, that is loaded in your current Laravel project. Note that: +- The command above added a requirement to your **project's ```composer.json``` file**, to require the package; Because previously Packager has pointed to the packages folder, it will pick it up from there, instead of the Internet; +- Then your **package's ```composer.json```** file will point to the ```ServiceProvider``` inside your package's ```src``` folder; +- The ServiceProvider is the class that ties your package to Laravel's inner workings. + +> If you want to test that your package is being loaded, you can do a ```dd('got here')``` inside your package's ```ServiceProvider::boot``` method. If you refresh the page, you should see that ```dd()``` statement executed. + + + +### Step 4. Move the files needed for the operation + +You can choose whatever folder structure you want for your package. But within Backpack add-ons, we follow the convention that the package folder should look as much as possible like a Laravel project folder. That way, when someone looks at the addon's source code, they instantly understand where everything is. + +**Operation Trait** + +Notice a file has already been created, with the operation name, inside your package's ```src``` folder. You can **move your operation trait code** from ```app/Http/Controllers/Admin/Operations``` to this ```src/SomeCustomOperation``` file, but make sure: +- you use the proper namespace (```MyName\SomeCustomOperation```); +- you define it as a Trait, not a Class; + +**Views** + +If your operation has a user interface, consider moving all the views this operation needs inside your package folder, inside a ```resources/views``` folder. + +Then in your package's ServiceProvider, make sure inside ```boot()``` that you load the views: +```php + $this->loadViewsFrom(__DIR__.'/../resources/views', 'somecustomoperation'); +``` + +**Config** + +For most custom Operations, there's really no need to define your own config file. You can just instruct people to use the ```config/backpack/crud.php``` file, and define stuff inside ```operations```, inside an array with your operation's name. If this works for you, then: +- delete the ```config``` folder entirely; +- inside your ServiceProvider's ```register()``` method, delete the line with ```mergeConfigFrom()```; +- inside your ServiceProvider's ```bootForConsole()``` method, delete the line that publishes the config file; +- inside your ServiceProvider's ```bootFromConsole()``` method, include: +```bash + $this->publishes([ + __DIR__.'/../resources/views' => base_path('resources/views/vendor/backpack'), + ], 'somecustomoperation.views'); +``` + +That way, developers define config values for your custom operation the same way they define them for a default Backpack operation. + +**Translations** + +If your Operation has an interface, it most likely also needs a translation file, so that strings are translatable. To add a translation file: +- inside your ServiceProvider's ```boot()``` method, include: +```bash +$this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'backpack'); +``` +- inside your ServiceProvider's ```bootFromConsole()``` method, include: +```bash + $this->publishes([ + __DIR__.'/../resources/lang' => resource_path('lang/vendor/backpack'), + ], 'somecustomoperation'); +``` +- create the ```resources/lang/en``` folder; +- create a PHP file with a shorter representative name inside that folder, for example ```somecustom.php```, with the translation lines there; +- you'll be able to use ```trans('somecustomoperation::somecustom.line_key')``` throughout your operation's controller/views; + + + +### Step 5. Delete the package files you don't need + +- in most cases you won't need a Facade for the operation, so you can delete the ```src/Facades``` folder; if you do that, also remove the alias to that Facade, at the bottom of your package's ```composer.json``` file; +- in most cases you won't need the ```register()```, ```provides()``` methods in your ServiceProvider; it's best to remove them; + + + +### Step 6. Customize Markdown Files + +Inside your package folder, go through all markdown files and make them your own. At the very least, go through: +- LICENSE.md - use the [MIT license](https://opensource.org/licenses/MIT) if unsure; +- README.md - write a clear description and instructions for how to use your operation; if you include clear documentation and screenshots, more people will use your package, guaranteed; + + + +### Step 7. Make your first git commit + +Inside your package folder, run: +```bash +cd packages/myname/somecustomoperation +git init +git add . +git commit -m "first commit" +``` + + + +## Part B. Put The Package Online + + + +### Put it on GitHub + +First, [create a new GitHub Repository](https://github.com/new) for it. Remember to use the same name you defined in your package's ```composer.json```. If in doubt, double-check. + +Second, add that new GitHub Repo as a remote, and push your code to your new GitHub repo. + +```bash +git remote add origin git@github.com:yourusername/yourrepository.git +git push -u origin master +git tag -a 1.0.0 -m 'First version' +git push --tags +``` + +The tags are the way you will version your package, so it's important you do it. + + +### Put it on Packagist + +In order for people to be able to install your package using composer, your package needs to be registered with Packagist, Composer's free package registry. + +On [Packagist.org](https://packagist.org/), submit a new package. Enter your package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. When you're done, you're taken to your package's packagist page. + +**Congrats, you have a working package online**, you can now install it using Composer. + +Note: On the package page, you might get a notice like this: _This package is not auto-updated. Please set up the [GitHub Service Hook](https://packagist.org/profile/) for Packagist so that it gets updated whenever you push!_ Let's take care of that. Click that link, get your API token and go to your package's GitHub page, in Settings / Webhooks & Services / Add a new service. Search for Packagist. Enter your username and the token and hit Submit. Your error in Packagist should disappear in 5–10 minutes. + + +### Feedback and Promotion + +Congratulations on your new Backpack addon! + +To get feedback, ask people to try it on: +- [our subreddit](https://www.reddit.com/r/BackpackForLaravel/) +- [our Gitter chatroom](https://gitter.im/BackpackForLaravel/Lobby) + +Make sure you write something nice, so people are interested to click. + +After you've got some feedback, and a few users have installed your package and everything seems fine, time to promote it big time: +- post it to [laravel-news.com/links](https://laravel-news.com/links) +- show it off in the [Laracasts forum](https://laracasts.com/discuss) +- ask people to try it in [laravel.io](https://laravel.io/forum) diff --git a/7.x-dev/add-ons-how-to-create-a-backpack-addon.md b/7.x-dev/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..41a8e7d7 --- /dev/null +++ b/7.x-dev/add-ons-how-to-create-a-backpack-addon.md @@ -0,0 +1,216 @@ +# How to Create an Add-on + +--- + + +### Intro + +There's nothing special about add-ons. They are simple Composer packages. + +But for consistency, we recommend you follow our simple folder structure. Our rule of thumb: **organize your ```src``` folder like it were a Laravel application**. We do this because it's easier for users to understand how the package works, and it makes it easy to copy-paste the code inside their apps and modify, for complicated use cases. That way, add-ons can be kept super-simple, with everybody adding functionality _in their own apps_. Example folder structure: +- [src] + - [app] + - [Http] + - [Models] + - [Requests] + - [database] + - [migrations] + - [seeds] + - [routes] + - YourPackageNameServiceProvider.php +- [tests] +- composer.json +- CHANGELOG.md +- LICENSE.md +- README.md + +Requirements: +- a working installation of the [Backpack demo](https://github.com/laravel-backpack/demo) +- 1-2 hours + + +## Step 1. Create a package + +### Install Backpack Demo + +We're going to use [the Backpack demo project](https://github.com/laravel-backpack/demo) to create a new package. Follow the instructions in [the Installation chapter](https://github.com/laravel-backpack/demo#install). + +Any Laravel & Backpack app would work. But since you're going to require packages that you only need during package development, and make various changes to app files, we recommended you _create_ the package using a Backpack demo. After the package is online (with zero functionality), you will _install_ it in a real application, and _modify_ it right there, in the ```vendor``` folder. You will then delete this Backpack demo project. + + +### Install CLI tool + +We're going to use [Jeroen-G/laravel-packager](https://github.com/Jeroen-G/laravel-packager) to generate a new package. Follow the instructions in the Installation chapter. + +### Generate Package Files + +Next up, decide what your vendor name will be. This is NOT the package name, it's the name all your packages will reside under. For example, Laravel uses "laravel". Backpack for Laravel uses "backpack". Jeffrey Way uses "way". If unsure, use your GitHub username for the vendor name. That's what people usually do, if they don't run a company / brand. + +Then decide what your package name will be. Ex: ```newscrud```, ```usermanager```, etc. + +Then run: +``` +php artisan packager:new myvendor mypackage +``` + +This will create a ```/packages/``` folder in your root directory, where your package will be stored, so you can build it. It will also pull [a very basic package template](https://github.com/thephpleague/skeleton), created by thephpleague. Everything you have right now is in ```packages/myvendor/mypackage``` - check it out. + +### Customize Generated Files + +Now let's customise it and add some boilerplate code, everything that most Laravel Packages will need. Replace everything you need in ```composer.json```, ```CHANGELOG.md```, ```CONTRIBUTING.md```, ```LICENSE.md```, ```README.md```. Make it yours. + +If you want to use Laravel package auto-discovery (and why wouldn't you), make sure to include the Laravel providers section in your ```composer.json```'s ```extra``` section, like so: +``` + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Backpack\\NewsCRUD\\NewsCRUDServiceProvider" + ] + } + } +``` + +In ```/src/``` you'll find your service provider. That's where your package's logic is, but it's empty. Use this Service Provider template and replace ```League``` with your ```myvendor``` and ```Skeleton``` with your ```mypackage```. Your package will probably need some Controllers, routes and config files. + +### Create The Files Your Package Needs + +Here are a few commands that could help you do that: + +```bash +# make sure everything is inside your src folder +cd src/ + +# to create a controller +echo "app/Http/Controllers/ControllerName.php + +# to create a request file +echo "app/Http/Requests/EntityRequest.php + +# to create a route file +echo "routes/mypackage.php + +# to create a config file +echo "config/mypackage.php + +# to create a views folder +mkdir resources/views/ +``` + +You use the routes, config and controller files just like you use the ones in your application. Nothing changes there. But remember that all classes should have the package's namespace: + +```php +namespace MyVendor\MyPackage\Http\Controllers; +``` + +### Make Sure Your Laravel App Loads The Package + +Add your service provider to your app's ```/config/app.php``` + +If not, add it: +``` +"MyVendor\MyPackage\MyPackageServiceProvider", +``` + +Check that you autoload your package in composer.json: +``` +"autoload" : { + "psr-4": { + "Domain\\PackageName\\": "packages/Domain/PackageName/src" + } +}, +``` + +Let's recreate the autoload +``` +cd ../../../.. +composer dump-autoload +``` + +If you have a config file to publish, do: +``` +php artisan vendor:publish +``` + +Now test it. Start by doing a ```dd('testing)``` in your service provider's ```boot()``` method. If your package is working fine, I recommend you put it online first, even before it does _anything useful_. You'll get the setup out of the way, and be able to focus on code. Plus, you'll be able to install it in a _real_ Backpack application, and edit it from the ```vendor/myvendor/mypackage``` folder (and push to your git remote). + +--- + + +## Step 2. Put it on GitHub + +``` +cd packages/domain/packagename/ +git init +git add . +git commit -m "first commit" +``` + +Create [a new GitHub repository](https://github.com/new). + +``` +git remote add origin git@github.com:yourusername/yourrepository.git +git push -u origin master +git tag -a 1.0.0 -m 'First version' +git push --tags +``` + +Tags are the way you will version your package, so it's important you do it. People will only be able to get updates if you tag them. + +--- + + +## Step 3. Put it on Packagist + +On [Packagist.org](https://packagist.org), submit a new package. Enter your package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. + +When you're done, you'll be taken to your packagist page, where you'll probably get a notice like this: + +>This package is not auto-updated. Please set up the [GitHub Service Hook](https://packagist.org/profile/) for Packagist so that it gets updated whenever you push! + +Let's take care of that. Click that link, get your API token and go to your package's GitHub page, in ```Settings / Webhooks & Services / Add a new service```. Search for Packagist. Enter your username and the token and hit Submit. Your error in Packagist should disappear in 5–10 minutes. + +Congrats! You now have a working package online. You can now require it with composer. + + +--- + + +## Step 4. Install in a Real Project + +We've instructed you to create the package in a disposable backpack-demo install. If you've done so, you can now install your package **in your _real_ project**: + +```bash +composer require myvendor/mypackage --prefer-source +``` + +Using the ```prefer-source``` flag will actually _clone the git repo_ inside your ```vendor/myvendor/mypackage``` directory. So you can do: + +```bash +cd /vendor/myvendor/mypackage +git checkout master +``` + +Then after each change you want to publish, you would mark that change in your ```CHANGELOG.md``` file and do: +```bash +git pull origin master +git add . +git commit -am "fixes #14189 - some problem or feature with an id" +git tag 1.0.3 +git push origin master --tags +``` + +--- + +**That's it. Go build your package!** If you end up with something you like, please share it with the community in the [Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby), and add it to [the Community Add-Ons page](/docs/{{version}}/add-ons-community), so other people know about it (_login, then click Edit in the top-right corner of the page_). + +You can now delete the Backpack project, and the database you've created for it (if any). + +For extra reading credits, these are the resources we've used to create this guide: +- https://laravel.com/docs/packages +- https://laracasts.com/discuss/channels/tips/developing-your-packages-in-laravel-5 +- https://github.com/jaiwalker/setup-laravel5-package +- https://github.com/Jeroen-G/laravel-packager +- https://laracasts.com/lessons/package-development-101 diff --git a/7.x-dev/add-ons-official.md b/7.x-dev/add-ons-official.md new file mode 100644 index 00000000..f94e4c53 --- /dev/null +++ b/7.x-dev/add-ons-official.md @@ -0,0 +1,27 @@ +# Official Add-ons + +In addition to our core packages (CRUD and PRO), we've developed a few packages you can install or download, that treat common use cases. + +Premium add-ons (paid separately): +- [Backpack PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) - adds 6 more operations, 10 filters, 28 more fields, 28 more columns and 1 more widget to your toolbelt; we believe it's everything you need to build admin panels... of any complexity PAID EXTRA +- [Backpack DevTools](https://backpackforlaravel.com/products/devtools) - a GUI to easily generate Migrations, Models, Seeders, Factories and CRUDs, right from your browser window; a power user's dream come true! PAID EXTRA +- [Backpack Figma Template](https://backpackforlaravel.com/products/figma-template) - quickly create designs and mockups, using Backpack's design, screens and components; empower your designers to design admin panels that are easy-to-code; PAID EXTRA +- [Backpack EditableColumns](https://backpackforlaravel.com/products/editable-columns) - let admins make quick edits, right from the table view; PAID EXTRA +- [Backpack CalendarOperation](https://backpackforlaravel.com/products/calendar-operation) - adds a Calendar view to your CRUD toolkit; let admins list, search and preview db entries on a calendar, as well as make quick edits with drag&drop; PAID EXTRA + + +Free add-ons: + - [PermissionManager](https://github.com/Laravel-Backpack/PermissionManager) - interface to manage users & permissions, using [spatie/laravel-permission](https://github.com/spatie/laravel-permission); FREE + - [TranslationManager](https://github.com/Laravel-Backpack/translation-manager) - UI to translate language strings, using [spatie/laravel-translation-loader](https://github.com/spatie/laravel-translation-loader); FREE + - [Settings](https://github.com/Laravel-Backpack/Settings) - interface to edit site-wide settings; FREE + - [PageManager](https://github.com/Laravel-Backpack/PageManager) - interface to manage content for custom pages, using page templates; FREE + - [MenuCRUD](https://github.com/Laravel-Backpack/MenuCRUD) - interface to create/update/reorder menu items; FREE + - [NewsCRUD](https://github.com/Laravel-Backpack/NewsCRUD) - interface to manage news articles, categories and tags; FREE + - [LogManager](https://github.com/Laravel-Backpack/LogManager) - interface to preview Laravel log files; FREE + - [BackupManager](https://github.com/Laravel-Backpack/BackupManager) - interface to backup your files & db using [spatie/laravel-backup](https://github.com/spatie/laravel-backup); FREE + - [Download Operation](https://github.com/Laravel-Backpack/download-operation) - download PDFs related to your entries, using [spatie/laravel-browsershot](https://github.com/spatie/browsershot); FREE + - [MediaLibrary Uploaders](https://github.com/Laravel-Backpack/medialibrary-uploaders) - attach files to your Eloquent models using [spatie/laravel-medialibrary](https://github.com/spatie/laravel-medialibrary); FREE + - [Activity Log](https://github.com/Laravel-Backpack/activity-log) - see who changed what, when using [spatie/laravel-activitylog](https://github.com/spatie/laravel-activitylog); FREE + + +>**The free add-ons only provide basic functionality.** What will be enough for _most_ projects. They do not intend to be a complete solution for all use cases. If you need to customize a package for your specific use case, you can easily do that, by copy-pasting their code in your project and modifying it. Every official package has been created with this in mind, has a very simple architecture, uses Backpack best practices. Find the "extend" section in each of their docs for more about this. diff --git a/7.x-dev/add-ons-tutorial-how-to-create-a-theme.md b/7.x-dev/add-ons-tutorial-how-to-create-a-theme.md new file mode 100644 index 00000000..7358a316 --- /dev/null +++ b/7.x-dev/add-ons-tutorial-how-to-create-a-theme.md @@ -0,0 +1,420 @@ +# Create a new Backpack Theme + +----- + +This tutorial will create and package a Backpack theme, so that you can use it in multiple Laravel projects. And if you open-source it, others can do the same. + + + +## Part A. Build the Theme in Your Project + +Here are the steps to easily build a **Backpack** theme using a template you got from **GetBootstrap**, **WrapBootstrap** or **ThemeForest**. + +### Step 1. Create theme directory + +Create a view folder anywhere in your `resources/views`. This is the directory where you will place all the files for your theme. + +```bash +mkdir resources/views/my-cool-theme +``` + +### Step 2. Use that directory as your view namespace + +In your `config/backpack/ui.php`, add that as the primary view namespace: + +```php + 'view_namespace' => 'my-cool-theme.', +``` + +> **Notes:** +> - Notice the `.` at the end of the namespace, that's important. +> - The namespace must match the name of the folder created from the previous step. + +### Step 3. Choose and use a fallback theme + +A fallback theme is needed in cases when Backpack attempts to load a view that doesn't exist in your theme. It means you don't need to create all the views in order to create a theme... Phew! You can easily rely on your fallback theme and only create the views you need to customize. In order to do so, edit your config file `config/backpack/ui.php` as it follows: + +```php + 'view_namespace' => 'my-cool-theme.', + 'view_namespace_fallback' => 'backpack.theme-coreuiv4::', // <--- this line +``` + +If it's your first time creating a Backpack theme, we recommend you start from our CoreUIv4 theme, and use that as your fallback. It's the simplest modern theme we have. It uses Bootstrap v5, but doesn't have any extra features you'd need to also support (like Tabler does). If you don't already have it installed, you will need to do `composer require backpack/theme-coreuiv4` + +### Step 4. Create the main blade files + +If you refresh the page right now, it will show up IDENTICAL to CoreUIv4. Why? Because you're using all the blade files from that theme (through the fallback system). How can you make _your theme_ look like _your theme_? By overriding some of blade files the fallback theme provides. Similar to how child themes work in Wordpress. + +Feel free to look at your fallback theme's views (eg. `vendor/backpack/theme-coreuiv4/resources/views`). If you create a file with the same file in your theme directory (eg. `resources/views/my-cool-theme`), your view will be picked up. + +So let's do that. Let's create _most_ of the files you'll need to customize, to provide _your theme_ with _your style_: +``` +- my-cool-theme/ + - assets/ + - css/ + ...here you can place all css files provided by your theme + - js/ + ...here you can place all js files provided by your theme + - inc/ + theme_styles.blade.php + theme_scripts.blade.php + ... + - components/ + ...here you can override widgets with your own + - layouts/ + app.blade.php + - widgets/ + ...here you can override widgets with your own +``` + +Now let's build those files... Let's start with what makes a theme different. + +#### Theme styles + +The `my-cool-theme/inc/theme_styles.blade.php` file should hold all custom CSS that your theme needs. For example: + +```php +{{-- You can load files directly from CDNs, they will get cached and loaded from local --}} +@basset('https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css') + +{{-- You can also load files from any place in your application directories --}} +@basset(views_path('my-cool-theme/assets/css/extra.css')) + +{{-- You can also write inline CSS blocks --}} +@bassetBlock('my-cool-theme/custom-styling') + +@endBassetBlock +``` + +Note: Don't forget to load the Bootstrap CSS. Backpack does NOT load it by default. + +#### Theme scripts + +The `my-cool-theme/inc/theme_scripts.blade.php` file should hold all custom JS that your theme needs: + +```php +{{-- You can load files directly from CDNs, they will get cached and loaded from local --}} +@basset('https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js') + +{{-- You can also load files from any place in your application directories --}} +@basset(views_path('my-cool-theme/assets/css/extra.js')) + +{{-- You can also write inline JS blocks --}} +@bassetBlock('my-cool-theme/custom-scripting') + +@endBassetBlock +``` + +Note: Don't forget to load the Bootstrap JS. Backpack does NOT load it by default. + +#### Default layout + +`my-cool-theme/layouts/default.blade.php` will be the primary layout for your theme. So it has to contain a full HTML page: the HTML doctype declaration, head, body components, everything. The following example will help you get started. + +```html + + + + @include(backpack_view('inc.head')) + + + + @include(backpack_view('inc.sidebar')) + +
+ + @include(backpack_view('inc.main_header')) + +
+ +
+ + @yield('before_breadcrumbs_widgets') + @includeWhen(isset($breadcrumbs), backpack_view('inc.breadcrumbs')) + @yield('after_breadcrumbs_widgets') + + @yield('header') + +
+ + @yield('before_content_widgets') + @yield('content') + @yield('after_content_widgets') + +
+ +
+ +
{{-- ./app-body --}} + + +
+ + @include(backpack_view('inc.bottom')) + + +``` + +We recommend you copy-paste your own HTML above it, then include the `@directives` where they make sense in your layout. Their names should explain pretty well what they do. + +Next up, we'll have to drill down. And move any custom content that's needed for the layout... for example for the sidebar, the header, the topbar... into their own respective views. + + +#### Head + +There should be no reason for you to create and customize a `my-cool-theme/inc/head.blade.php` file. + +#### Sidebar + +Regarding `my-cool-theme/inc/sidebar.blade.php` we recommend you: +- create the file; +- paste the `main_header.blade.php` from your fallback theme; +- paste the HTML content you want; +- copy the useful things from the fallback theme to your own HTML, then delete whatever you don't want; + +Do not drill down to customize `sidebar_content.blade.php` too. Menu items work a little different than other views - they are _view components_. So instead of customizing `sidebar_content.blade.php` take a few minutes and customize the menu items HTML, by copy-pasting the `components` directory from your fallback theme, then customizing the files inside it (`menu-item`, `menu-dropdown`, `menu-dropdown-item`, `menu-separator` etc). + +#### Main header + +Regarding `my-cool-theme/inc/main_header.blade.php` we recommend you: +- create the file; +- paste the `main_header.blade.php` from your fallback theme; +- paste the HTML content you want; +- copy the useful things from the fallback theme to your own HTML, then delete whatever you don't want; + +#### Breadcrumbs + +Most often `my-cool-theme/inc/breadcrumbs.blade.php` is not needed - breadrcumbs will look ok out-of-box, because they use regular Bootstrap structure and style. But if you do need to customize the breadcrumbs, follow the same process above to re-use everything you need from the fallback theme file. + +#### Footer + +Most often `my-cool-theme/inc/footer.blade.php` is not needed, the footer will look ok out-of-box. But if you do need to customize the breadcrumbs, follow the same process above to re-use everything you need from the fallback theme file. + +#### Bottom + +There should be no reason for you to create and customize a `my-cool-theme/inc/bottom.blade.php` file. + +#### Other blade files + +Feel free to duplicate any blade files from your fallback theme into your own theme, to customize them. But do this with moderation - if you're only changing style (not HTML structure), it's much better to make those changes using CSS. + +### Step 5. Add CSS to make it pretty + +For any Backpack pages or components that don't look pretty in your theme, feel free to customize them using CSS. In step 4 in `theme_styles.blade.php` we have already shown you how to include a custom CSS file, to hold all your custom styles. + +### Step 6. Make it public + +If you're proud of how your theme looks and want to share it with others in the Backpack community: +- make sure you have the rights to make the code public; if you've purchased an HTML template, you most likely _do not_ have the right to make their HTML & CSS public; +- consider adding the rest of the views from your fallback theme to yours; there's a choice here - either you make your package depend on your fallback theme (add it to `composer.json`)... or you copy-paste their files in yours, so that your theme be independent; +- follow the steps below to create a Backpack add-on using your theme; + + +## Part B. Create The Package + + + +### Step 1. Generate the package folder + +Let's install this excellent package that will make everything a lot faster: +```sh +composer require jeroen-g/laravel-packager --dev +``` + +Let's create our package. Instead of using their skeleton, we're going to use the Backpack [addon-skeleton](https://github.com/Laravel-Backpack/theme-skeleton): + +```sh +php artisan packager:new --i --skeleton="https://github.com/Laravel-Backpack/theme-skeleton/archive/master.zip" +``` + +It will then ask you some basic information about the package. Keep in mind: +- the ```vendor-name``` should probably be your GitHub handle (or organisation), in kebab-case (ex: `company-name`); it will be used for folder names, but also for GitHub and Packagist links; +- the ```package-name``` should be in `kebab-case` too (ex: ```moderate-operation```); +- the `skeleton`, if you haven't copied the entire command above, should probably be the one we provide: `https://github.com/Laravel-Backpack/addon-skeleton/archive/master.zip`, which has everything you need to quickly create a Backpack add-on, including an innovative `AddonServiceProvider` that "_just works_"; +- the ```website``` should be a valid URL, so include the protocol too: ```http://example.com```; +- the ```description``` should be pretty short; you can change it later in `composer.json`; +- the ```license``` is just the license name, if it's a common one (ex: ```MIT```, ```GPLv2```); our skeleton assumes you want `MIT` but you can easily change it; + +OK great. The command has: +- created a ```/packages/vendor-name/package-name``` folder in your root directory; +- modified your project's ```composer.json``` file to load the files in this new folder; + +This new folder should hold all your package files. You're off to a great start, because you're using our package skeleton (aka template), so it's already a _working package_. And it's already got a good file structure. Excited by how easy it'll be to make it work? Excellent, let's do it. + +> If you want to test that your package is being loaded, you can do a ```dd('got here')``` inside your package's ```AddonServiceProvider::boot``` method. If you refresh the page, you should see that ```dd()``` statement executed. + + +### Step 1. Initialize a git repo and make your first package commit + +Let's save what we have so far - the generated files: +```bash +# go to the package folder (of course - use your names here) +cd packages/vendor-name/package-name + +# create a new git repo +git init + +# (optional, but recommended) +# by default the skeleton includes folders for most of the stuff you need +# so that it's easier to just drag&drop files there; but you really +# shouldn't have folders that you don't use in your package; +# so an optional but recommended step here would be to +# delete all .gitkeep files, so that you leave the +# empty folders empty; that way, you can still +# drag&drop stuff into them, but Git will +# ignore the empty folders +find . -name ".gitkeep" -print # shows all .gitkeep files, to double-check +find . -name ".gitkeep" -exec rm -rf {} \; # deletes all .gitkeep files + +# commit the initially generated files +git add . +git commit -am "generated package code using laravel-packager and the backpack theme-skeleton" +``` + +Excellent. Now we have _two_ git repos, that we can use as a progress indicator: +- the _project_ repo, where the files are uncommitted; +- the _package_ repo, where we'll be moving the functionality; + +If you've used a git client you can even place them side-by-side, and see the progress as you move files from the project (left) to the package (right). But you don't _have to_ do that, it's just a nice visual indicator if it's your first package: + +![Two git repos - the project and the new package](https://user-images.githubusercontent.com/1032474/101024271-85af3100-357c-11eb-9a51-605b003a0a53.png) + + +### Step 2. Define any extra dependencies + +Your ```/packages/vendor-name/package-name/composer.json``` file already requires the latest version of Backpack (thanks to the addon skeleton). If your package needs any third-party packages apart from Backpack and Laravel, make sure to add them to the `require` section. If you theme does NOT provide all needed files, and still sues something from a fallback theme, you MUST require that theme in your package's `composer.json` and instruct people to use it as the fallback. + + +### Step 3. Move the blade files from your project to your package + +Time to move files from your _project_ to your _package_. You can use whatever you want for that - drag&drop, the command line, your IDE or editor, whatever you want. + +![Moving files from your project to your package](https://user-images.githubusercontent.com/1032474/101025619-821ca980-357e-11eb-82fb-0d0e57ad6e3f.gif) + + +As you do that, your `git status` or git client should show fewer and fewer files in your _project_, and more and more files in your _package_. + + +### Step 4. Test that the package works + +To use the blade files from your _package_ instead of your _project_, change the view namespace in `config/backpack/ui.php` to point to this new package you created: + +```php + 'view_namespace' => 'vendor-name.package-name::', + 'view_namespace_fallback' => 'backpack.theme-coreuiv4::', +``` + +That's pretty much it. You've created your package! 🥳 All the files your package need are inside your package, and the only remaining changes in your project (as reflected by `git status`) should be the minimal changes that users need to do to install your package. + +Go ahead and test it in the browser. Make sure the functionality that was working inside your _project_ is still working now that it's inside a _package_. You might have forgotten something - we all do sometimes. + + +### Step 5. Delete the package files you don't need + +Now that you know your package is working, go through the package folder and delete whatever your package isn't actually using: empty directories, empty files, placeholder files. Clean it up a little bit. + + + +### Step 6. Customize Markdown Files + +Inside your package folder, go through all markdown files and make them your own. At the very least, open the `README.md` file and spend a little time on it, give it some love: +- write a clear description; +- add a screenshot if appropriate; +- write clear installation instructions (step-by-step) for how to use your package; +- answer any questions users might have about what the package does; +- link to whatever dependencies or resources your add-on uses, so that they check out their docs instead of bugging you about it; + +If you plan to make this package public, take the `README.md` seriously, because it's a HUGE factor in how popular your package can become. If you include clear documentation and screenshots, more people will use your package - guaranteed. + + + +## Part C. Put The Package Online + + +First, [create a new GitHub Repository](https://github.com/new) for it. Remember to use the same name you defined in your package's ```composer.json```. If in doubt, double-check. + +Second, add that new GitHub Repo as a remote, and push your code to your new GitHub repo. + +```bash +# save your working files to Git +git add . +git commit -am "working code for v1.0.0" + +# add the remote to Github +git remote add origin git@github.com:yourusername/yourrepository.git +git branch -M main +git push -u origin main +git tag -a 1.0.0 -m 'First version' +git push --tags +``` + +The tags are the way you will version your package, so it's important you do it. + + + +## Part D. Make the package public (on Packagist) + +In order for people to be able to install your package using Composer, your package needs to be registered with [Packagist.org](https://packagist.org/), Composer's free package registry. + +On [Packagist.org](https://packagist.org/), submit a new package. Enter your package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. When you're done, you're taken to your package's Packagist page. + +**Congrats, you have a working package online**, you can now install it using Composer. + +Note: On the package page, you might get a notice like this: _This package is not auto-updated. Please set up the [GitHub Service Hook](https://packagist.org/profile/) for Packagist so that it gets updated whenever you push!_ Let's take care of that. Click that link, get your API token and go to your package's GitHub page, in Settings / Webhooks & Services / Add a new service. Search for Packagist. Enter your username and the token and hit Submit. Your error in Packagist should disappear in 5–10 minutes. + + + +## Part E. Install the repo from GitHub + +If you look close to your project's `composer.json` file, you'll notice your project is loading the package from `packages/vendor-name/package-name`. Which is fine, it's worked fine until now. But it's now time to install the package like your users will, and have it in `vendor/vendor-name/package-name`. That way: +- you test that your users are able to install the package; +- you don't commit anything extra inside your project; +- you can _easily_ make changes to your package, from whatever project you're using it in; + +To do that, go ahead and do this to uninstall your package from your project: +```bash +cd ../../.. # so that you're inside your project, not package + +# discard the changes in your composer files +# and delete the files from packages/vendor-name/package-name +php artisan packager:remove vendor-name package-name +``` + +And now install it exactly the same as your users will: +- if it's closed-source, add the GitHub repo to your `composer.json` then move on to the next step; do NOT do this if your package is open-source: + +```json + "repositories": { + "vendor-name/package-name": { + "type": "vcs", + "url": "https://github.com/vendor-name/package-name" + } + } +``` + +- both for closed-source and open-source (on Packagist), install it using Composer's `--prefer-source` flag, so that it pulls the actual GitHub repo: + +```bash +composer require vendor-name/package-name --prefer-source +``` + +That's it. It should be working fine now, but from the `vendor/vendor-name/package-name` directory. You can `cd vendor/vendor-name/package-name` and you'll see that you can `git checkout master`, make changes, tag releases, push to GitHub, everything. + + + +### Feedback and Promotion + +Congratulations on your new Backpack theme! To get feedback, ask people to try it by opening a Discussion in the [Backpack Community Forum](https://github.com/Laravel-Backpack/community-forum/discussions). After you've gotten some feedback, and a few users have installed your package and everything seems fine, time to promote it big time: +- post it to [laravel-news.com/links](https://laravel-news.com/links) +- show it off in the [Laracasts forum](https://laracasts.com/discuss) +- ask people to try it in [laravel.io](https://laravel.io/forum) + +Have patience. It takes time to build up a user base, especially if it's your first open-source package. But treat every user as a friend, and you'll soon get there! diff --git a/7.x-dev/add-ons-tutorial-using-the-addon-skeleton.md b/7.x-dev/add-ons-tutorial-using-the-addon-skeleton.md new file mode 100644 index 00000000..06e09c15 --- /dev/null +++ b/7.x-dev/add-ons-tutorial-using-the-addon-skeleton.md @@ -0,0 +1,302 @@ +# Create an Add-On using our Addon Skeleton + +----- + +This tutorial will help you package a Backpack operation or entire CRUD into a Composer package, so that you can use it in multiple Laravel projects. And if you open-source it, others can do the same. + + + +## Part A. Build the Functionality in Your Project + +Make sure you have working code in your project. Code everything the package will need, the same way you normally do. Before even _thinking_ about turning it into a package, you should have working code. This is easier because: +- you don't have to do anything new while working on functionality +- you don't have to think about package namespaces +- you don't have to think about what to make configurable or translatable +- you can test the functionality alone (without the package wiring and stuff) + +**(optional) Hot tip:** Don't commit the code to your package yet. Or, if you've already done a commit (and it's the last commit), run `git reset HEAD^` to undo the commit (but keep the changes). This is _not necessary_ but we find it super-helpful. If you changes are inside your project, but uncommitted, you can easily see the files that have changed using `git status`, hence... the files that contain the functionality you should move to the package. It's like a progress screen - everything here should disappear - that's how you know you're done. + +**(optional) Bonus points:** You can use a Git client (like [Git Fork](https://git-fork.com/)) instead of `git status`, because that'll also show the atomic changes you've made to route files, sidebar etc... not only the file names: + +![Using Git Fork to see the files and changes that need to be moved to a package or Backpack add-on](https://user-images.githubusercontent.com/1032474/101012182-78d61180-356b-11eb-8aa9-85d6959468ea.png) + +As you move things to your new package, they'll disappear from here. You know you're done when you only have the changes the users of your package need to make, to use it. Normally that's just a change to the `composer.json` and (maybe) a configuration file. + + + +## Part B. Create The Package + + + +### Step 1. Generate the package folder + +Let's install this excellent package that will make everything a lot faster: +```sh +composer require jeroen-g/laravel-packager --dev +``` + +Let's create our package. Instead of using their skeleton, we're going to use the Backpack [addon-skeleton](https://github.com/Laravel-Backpack/addon-skeleton): + +```sh +php artisan packager:new --i --skeleton="https://github.com/Laravel-Backpack/addon-skeleton/archive/master.zip" +``` + +It will then ask you some basic information about the package. Keep in mind: +- the ```vendor-name``` should probably be your GitHub handle (or organisation), in kebab-case (ex: `company-name`); it will be used for folder names, but also for GitHub and Packagist links; +- the ```package-name``` should be in `kebab-case` too (ex: ```moderate-operation```); +- the `skeleton`, if you haven't copied the entire command above, should probably be the one we provide: `https://github.com/Laravel-Backpack/addon-skeleton/archive/master.zip`, which has everything you need to quickly create a Backpack add-on, including an innovative `AddonServiceProvider` that "_just works_"; +- the ```website``` should be a valid URL, so include the protocol too: ```http://example.com```; +- the ```description``` should be pretty short; you can change it later in `composer.json`; +- the ```license``` is just the license name, if it's a common one (ex: ```MIT```, ```GPLv2```); our skeleton assumes you want `MIT` but you can easily change it; + +OK great. The command has: +- created a ```/packages/vendor-name/package-name``` folder in your root directory; +- modified your project's ```composer.json``` file to load the files in this new folder; + +This new folder should hold all your package files. You're off to a great start, because you're using our package skeleton (aka template), so it's already a _working package_. And it's already got a good file structure. + +Let's take a look at the generated files inside ```/packages/vendor-name/package-name```: + +![Blank Backpack addon as generated using the addon skeleton](https://user-images.githubusercontent.com/1032474/101022657-5992b080-357a-11eb-8f85-8e0718b66fb2.png) + + +You'll notice that it looks _exactly_ like a Laravel project, with a few exceptions: +- PHP classes live in `src` instead of `app`; +- inside that `src` folder you also have an `AddonServiceProvider`; so let's take a moment to explain why it's there, and what it does: + - normally a package needs a ServiceProvider to tell Laravel "load the views from here", "load the migrations from here", "load configs from here", things like that; because a Composer package can also be a general PHP package (non-Laravel), normally you have to code a ServiceProvider for your package, that tells Laravel how to use your package - you have to write all that wiring logic; + - but thanks to `AddonServiceProvicer`, you don't have to do any of that; it's all done _automatically_ if the files are in the right directories, just like Laravel does itself, in your project's folders; + - the only thing you should worry about is placing your route files in `routes`, your migrations in `database/migrations` etc. and the `AddonServiceProvider` will understand and tell Laravel to load them; easy-peasy; + +Excited by how easy it'll be to make it work? Excellent, let's do it. + +> If you want to test that your package is being loaded, you can do a ```dd('got here')``` inside your package's ```AddonServiceProvider::boot``` method. If you refresh the page, you should see that ```dd()``` statement executed. + + +### Step 1. Initialize a git repo and make your first package commit + +Let's save what we have so far - the generated files: +```bash +# go to the package folder (of course - use your names here) +cd packages/vendor-name/package-name + +# create a new git repo +git init + +# (optional, but recommended) +# by default the skeleton includes folders for most of the stuff you need +# so that it's easier to just drag&drop files there; but you really +# shouldn't have folders that you don't use in your package; +# so an optional but recommended step here would be to +# delete all .gitkeep files, so that you leave the +# empty folders empty; that way, you can still +# drag&drop stuff into them, but Git will +# ignore the empty folders +find . -name ".gitkeep" -print # shows all .gitkeep files, to double-check +find . -name ".gitkeep" -exec rm -rf {} \; # deletes all .gitkeep files + +# commit the initially generated files +git add . +git commit -am "generated package code using laravel-packager and the backpack addon-skeleton" +``` + +Excellent. Now we have _two_ git repos, that we can use as a progress indicator: +- the _project_ repo, where the files are uncommitted; +- the _package_ repo, where we'll be moving the functionality; + +If you've used a git client you can even place them side-by-side, and see the progress as you move files from the project (left) to the package (right). But you don't _have to_ do that, it's just a nice visual indicator if it's your first package: + +![Two git repos - the project and the new package](https://user-images.githubusercontent.com/1032474/101024271-85af3100-357c-11eb-9a51-605b003a0a53.png) + + +### Step 2. Define any extra dependencies + +Your ```/packages/vendor-name/package-name/composer.json``` file already requires the latest version of Backpack (thanks to the addon skeleton). If your package needs any third-party packages apart from Backpack and Laravel, make sure to add them to the `require` section. Normally this just means cutting&pasting the line from your project's `composer.json` to your package's `composer.json`. + + + +### Step 3. Move the functionality from your project to your package + +Time to move files from your _project_ to your _package_. You can use whatever you want for that - drag&drop, the command line, your IDE or editor, whatever you want. + +![Moving files from your project to your package](https://user-images.githubusercontent.com/1032474/101025619-821ca980-357e-11eb-82fb-0d0e57ad6e3f.gif) + + +As you do that, your `git status` or git client should show fewer and fewer files in your _project_, and more and more files in your _package_. + +Below we'll take each project directory, one by one, and explain where its files should go. But this should all pretty intuitive: + +#### Files inside your project's ```app``` directory + +Move from the subdirectory there to the same subdirectory inside your package's `src`; that means: + - controllers go inside `src/Http/Controllers`; + - requests go inside `src/Http/Requests`; + - models go inside `src/Models`; + - commands go inside `src/Commands`; + - etc. + +IMPORTANT: Since you're moving PHP classes, **after moving them you must also change their namespaces**. Your class is no longer `App\Http\Controllers\Admin\ExampleCrudController` but `VendorName\PackageName\Http\Controllers\ExampleCrudController`. + +I'd love to tell you you can it using a search&replace, but... no. In this case search&replace is not worth it, because it might also replace other stuff you don't want replaced. So it's best to just go ahead: +- open all the files you've moved; +- manually replace stuff like `App\Http` with `VendorName\PackageName\Http`; + +#### Files inside your project's `config` directory + +If you _won't_ be using config files, just delete the entire `config` package directory. + +If you _will_ be using config files, to let the users of your package publish it and change how stuff works that way, it's super-simple to use: +- you already have a file generated in `/packages/vendor-name/package-name/config/package-name.php`; +- cut&paste any config values you want over here; if you add for example `config_key`, it'll be available in your classes using `config('vendor-name.package-name.config_key')`; + +If you don't have any configs right now, but will want to add later, that's OK too. Do it later. + +#### Files inside your project's `database` directory + +You have the same directory in your package, just move them there. + +That means: +- `database/migrations` +- `database/seeds` or `database/seeders` +- `database/factories` + + +#### Files inside your project's `resources\views` directory + +You have the same directory in your package, just move them there. Views moved in your package folder will be automatically available in the `vendor-name.package-name` namespace, so you can load them using `view('vendor-name.package-name::path.to.file')`. + +For views that need to be changed by the user upon installation, and cannot be moved to the package (for example, menu items inside `sidebar_content.blade.php`), add the changes the user needs to do inside your package's `readme.md` file, under Installation. + +#### Files inside your project's `resources\lang` directory + +If your package won't support translations yet, just skip this. + +If it will, notice you already have a lang file created for English, in your package - `/packages/vendor-name/package-name/resources/lang/en/package-name.php`. Populate that file with the language lines you need, by cutting&pasting from your project. They'll be available as `lang('vendor-name.package-name::package-name.line_key')` so you need to also find&replace your old keys with the new ones. + +#### Files inside your project's `routes` directory + +If your package only adds a view (ex: a field, a column, a filter, a widget) then it probably won't need a route, you can just delete the entire `routes` directory in your package. + +If you package _does_ need routes (like when it provides an entire CRUD), you'll find there's already a file in your `/packages/vendor-name/package-name/route/package-name.php`, waiting for your routes, with a few helpful comments. Just cut&paste the routes from your project inside that file. + +#### Helper functions inside your project's `bootstrap\helpers.php` file + +If you've added any functions there that you need inside the package, you'll notice there's already a `/packages/vendor-name/package-name/bootstrap/helpers.php` file waiting for you. Cut&paste them there. + +If your package does not any extra need helper functions, just delete the entire `bootstrap` directory in the package. + + +### Step 4. Test that the package works + +That's pretty much it. You've created your package! 🥳 All the files your package need are inside your package, and the only remaining changes in your project (as reflected by `git status`) should be the minimal changes that users need to do to install your package. + +Go ahead and test it in the browser. Make sure the functionality that was working inside your _project_ is still working now that it's inside a _package_. You might have forgotten something - we all do sometimes. + + +### Step 5. Delete the package files you don't need + +Now that you know your package is working, go through the package folder and delete whatever your package isn't actually using: empty directories, empty files, placeholder files. Clean it up a little bit. + + + +### Step 6. Customize Markdown Files + +Inside your package folder, go through all markdown files and make them your own. At the very least, open the `README.md` file and spend a little time on it, give it some love: +- write a clear description; +- add a screenshot if appropriate; +- write clear installation instructions (step-by-step) for how to use your package; +- answer any questions users might have about what the package does; +- link to whatever dependencies or resources your add-on uses, so that they check out their docs instead of bugging you about it; + +If you plan to make this package public, take the `README.md` seriously, because it's a HUGE factor in how popular your package can become. If you include clear documentation and screenshots, more people will use your package - guaranteed. + + + +## Part C. Put The Package Online + + +First, [create a new GitHub Repository](https://github.com/new) for it. Remember to use the same name you defined in your package's ```composer.json```. If in doubt, double-check. + +Second, add that new GitHub Repo as a remote, and push your code to your new GitHub repo. + +```bash +# save your working files to Git +git add . +git commit -am "working code for v1.0.0" + +# add the remote to Github +git remote add origin git@github.com:yourusername/yourrepository.git +git branch -M main +git push -u origin main +git tag -a 1.0.0 -m 'First version' +git push --tags +``` + +The tags are the way you will version your package, so it's important you do it. + + + +## Part D. Make the package public (on Packagist) + +In order for people to be able to install your package using Composer, your package needs to be registered with [Packagist.org](https://packagist.org/), Composer's free package registry. + +On [Packagist.org](https://packagist.org/), submit a new package. Enter your package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. When you're done, you're taken to your package's Packagist page. + +**Congrats, you have a working package online**, you can now install it using Composer. + +Note: On the package page, you might get a notice like this: _This package is not auto-updated. Please set up the [GitHub Service Hook](https://packagist.org/profile/) for Packagist so that it gets updated whenever you push!_ Let's take care of that. Click that link, get your API token and go to your package's GitHub page, in Settings / Webhooks & Services / Add a new service. Search for Packagist. Enter your username and the token and hit Submit. Your error in Packagist should disappear in 5–10 minutes. + + + +## Part E. Install the repo from GitHub + +If you look close to your project's `composer.json` file, you'll notice your project is loading the package from `packages/vendor-name/package-name`. Which is fine, it's worked fine until now. But it's now time to install the package like your users will, and have it in `vendor/vendor-name/package-name`. That way: +- you test that your users are able to install the package; +- you don't commit anything extra inside your project; +- you can _easily_ make changes to your package, from whatever project you're using it in; + +To do that, go ahead and do this to uninstall your package from your project: +```bash +cd ../../.. # so that you're inside your project, not package + +# discard the changes in your composer files +# and delete the files from packages/vendor-name/package-name +php artisan packager:remove vendor-name package-name +``` + +And now install it exactly the same as your users will: +- if it's closed-source, add the GitHub repo to your `composer.json` then move on to the next step; do NOT do this if your package is open-source: + +```json + "repositories": { + "vendor-name/package-name": { + "type": "vcs", + "url": "https://github.com/vendor-name/package-name" + } + } +``` + +- both for closed-source and open-source (on Packagist), install it using Composer's `--prefer-source` flag, so that it pulls the actual GitHub repo: + +```bash +composer require vendor-name/package-name --prefer-source +``` + +That's it. It should be working fine now, but from the `vendor/vendor-name/package-name` directory. You can `cd vendor/vendor-name/package-name` and you'll see that you can `git checkout master`, make changes, tag releases, push to GitHub, everything. + + + +### Feedback and Promotion + +Congratulations on your new Backpack addon! + +To get feedback, ask people to try it on: +- [our addons repo](https://github.com/laravel-backpack/addons) +- [our subreddit](https://www.reddit.com/r/BackpackForLaravel/) +- [our Gitter chatroom](https://gitter.im/BackpackForLaravel/Lobby) + +Make sure you write something nice, so people are interested to click. + +After you've got some feedback, and a few users have installed your package and everything seems fine, time to promote it big time: +- post it to [laravel-news.com/links](https://laravel-news.com/links) +- show it off in the [Laracasts forum](https://laracasts.com/discuss) +- ask people to try it in [laravel.io](https://laravel.io/forum) diff --git a/7.x-dev/add-ons-tutorial.md b/7.x-dev/add-ons-tutorial.md new file mode 100644 index 00000000..01da4d66 --- /dev/null +++ b/7.x-dev/add-ons-tutorial.md @@ -0,0 +1,232 @@ +# How to Create a Backpack Add-on + +--- + + +### Intro + +So you've created a custom field, column, filter, or an entire CRUD. Great! If you want to re-use it across projects, or you think _other people_ would like to use it too, there's a good way to do that. + +The process below will involve creating a new package on GitHub, Composer & Packagist - which is a little challenging to wrap your head around, the first time you do it. But if you were able to create a custom field, you will be able to do that too. And in doing this, you'll learn the basics of creating and maintaining a PHP package. That's something that not all PHP developers can do, so it's pretty cool, I think. + +> If you already know how to create & maintain a PHP package, this tutorial might be too easy for you. Try to skim it, because we give useful tips. But you can also just go to [:DigitallyHappy/toggle-field-for-backpack](https://github.com/DigitallyHappy/toggle-field-for-backpack), clone the repo and make the changes you see fit. + +Requirements: +- a working installation of Laravel & Backpack 4 (alternative: you can install the [Backpack demo](https://github.com/laravel-backpack/demo)); +- a GitHub account (free or paid); +- 15-30 minutes; + + + +## Step 0. Scope and Constants + +**Decide what your package is going to do.** Try to keep the package as small as possible. If you're trying to share multiple fields/columns/etc, we recommend you create a different package for each field type. This will make it: +- easier for you to communicate what the package does; +- easier for you to maintain a field type (or abandon it); +- more likely for people to install & use your package; + +In the tutorial below, we'll assume you're trying to share one custom field - ```dummy.blade.php```. But the process will be the same no matter what you're building, starting from the skeletons packages below. + +Once you know what you're building, there are a few constants which you need to decide upon. Names that once you've chosen, it will be _possible_, but _very difficult_ to change: + +**Package Name.** Try to find a name that is as explicit as possible. So that users, just by reading the name, will pretty much understand what the package does. Also, try to include "_for Backpack_" - that way it will stand out to developers who use Backpack (your target audience). +- Good Examples: ```json-editor-field-for-backpack```, ```user-column-for-backpack```, ```users-crud-for-backpack```; +- Bad Examples: ```custom-field``` (too general), ```cbf``` (too cryptic), ```aurora``` (this is not the place to be creative :smile: ); +Since in this example we're trying to build a package for the new ```dummy``` field type, we'll choose ```dummy-field-for-backpack``` as the package name. + +**Vendor Name.** You noticed how every time you install a composer package, it's ```composer install something/package-name```? Well that's what that "something" is - the _vendor name_. It's usually the name of the company or person behind the project. The easiest to remember would be your GitHub username, or your company's GitHub username. But, if you're not happy with those, you _can_ choose a different vendor name - basically a brand name, under which you build packages. This could be the place to be creative :smile:, if you don't have a company name already. In the example below we'll use ```company-name```. + +**Class Namespace.** When people reference your package's classes, this is what they see first. It's a good practice to use VendorName/PackageName as the namespace for your package. But notice it's no longer kebab-case (using dashes - ```my-company/dummy-field-for-backpack```), it is PascalCase (```MyCompany/DummyFieldForBackpack```). + + + +## Step 1. Clone the skeleton package + +To get your package online ASAP, we've prepared a few "skeleton" packages, that you can fork and modify: +- [:DigitallyHappy/toggle-field-for-backpack](https://github.com/DigitallyHappy/toggle-field-for-backpack) - field add-on example; +- TODO - column add-on example; +- TODO - filter add-on example; +- TODO - button add-on example; +- TODO - CRUD add-on example; +- TODO - multiple CRUD add-on example; +- TODO - CRUD with dependencies add-on example; + +Pick the skeleton package that's as similar as possible to what you want to build. On its GitHub page, under if you click the green **Clone or Download** button, you'll get the path to that repo. In your project, let's clone that repo: +```bash +# git clone {REPO URL} packages/{VENDOR NAME}/{PACKAGE NAME} +git clone git@github.com:DigitallyHappy/toggle-field-for-backpack.git packages/my-company/dummy-field-for-backpack +``` + + +## Step 2. Make it your own + +Take a look at the files you've copied - it's a very simple package. In you package's root folder we have: +- ```README.md``` - your package's "home page" on Github; this should hold all the information needed to use your package; +- ```composer.json``` - configuration file that tells Composer (the PHP package installer) more about your package; +- ```CHANGELOG.md``` - where you should write every time you make changes to the field; +- ```LICENSE.md``` - so that people know how they're allowed to use your package (MIT is the default); + +Take a look at all of them and modify to fit your needs. It should be faster to modify by hand, and pretty intuitive. But, if it's the first time you create a PHP package, you can use the process below, to make sure you don't mess up anything, since making a casing mistake somewhere (```my-vendor``` instead of ```MyVendor```) could take you very long to debug: +- **namespace** - find ```DigitallyHappy\ToggleFieldForBackpack``` and replace with your _VendorName\PackageName_ (ex: ```MyCompany\DummyFieldForBackpack```); +- **escaped namespace** - find ```DigitallyHappy\\ToggleFieldForBackpack``` and replace with your _VendorName\\PackageName_ (ex: ```MyCompany\\DummyFieldForBackpack```); +- **vendor and package name** - find ```digitallyhappy/toggle-field-for-backpack``` and replace with your _vendor-name/package-name_ (ex: ```my-company/dummy-field-for-backpack```); +- **package name** - find ```toggle-field-for-backpack``` and replace with your _package-name_ (ex: ```dummy-field-for-backpack```); +- **vendor name** - find ```digitallyhappy``` and replace with your _vendor-name_ (ex: ```my-company```); +- **author name** - find ```Cristian Tabacitu``` and replace with your name (ex: ```John Doe```); +- **author email** - find ```hello@tabacitu.ro``` and replace with your email (ex: ```john@example.com```); +- **author website** - find ```https://tabacitu.ro``` and replace with your website or GitHub page (ex: ```http://example.com```); +- open ```changelog.md``` and keep only the 1.0.0 version; put today's date; +- open ```composer.json``` and change the description of your package; +- open ```readme.md``` and change the text to better describe _your_ package; don't worry about the screenshot, we'll change that one in a future step; + +In ```/src/``` you'll find your service provider, which does one thing: it loads the views in your ```src/resources/views``` under the ```dummy-field-for-backpack``` view namespace. So that anybody who installs your package can use a view that your package includes, by referencing ```dummy-field-for-backpack::path.to.view```. + +Also in ```/src/``` you'll notice ```src/resources/views/fields/toggle.blade.php```. This is the example field, which you can rename and use to start coding you field. Or if you already have your field ready, you can just delete this file, and copy-paste the finished blade file from your project + + +## Step 3. Put it on GitHub + +``` +# make sure you replace the below with your actual vendor name and package name +cd packages/my-company/dummy-field-for-backpack/ +git add . +git commit -m "turned the skeleton package into dummy-field package" +``` + +Create [a new GitHub repository](https://github.com/new). Then get the Git URL the same way you did for the Toggle package, from the green "Clone or Download" button. With that Git URL: + +``` +# remove the old origin (pointing to the toggle package) +git remote rm origin + +# add a remote pointing to YOUR github repo +git remote add origin git@github.com:yourusername/yourrepository.git + +# refresh the new origin remote everywhere +git config master.remote origin +git config master.merge refs/heads/master + +# push your code to your github repo +git push -u origin master + +# tag your release as 1.0.0 +git tag -a 1.0.0 -m 'First version' + +# push your tags to the Repo - this is very important to Packagist; +git push --tags +``` + +You might not have used git tags until now. Tags are the way you will version your package, so it's important you do it. For every new version, you need to: +- write your changes inside the ```changelog.md``` file, so people can easily see what's new; +- tag your release with the proper tag, so that Packagist will know you've pushed a new version; + +--- + + +## Step 4. Put it on Packagist + +On [Packagist.org](http://packagist.org), create an account if you don't have one already, then click "Submit package" in the top-right corner. Enter your package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. + +When you're done, you'll be taken to your Packagist page, where you'll probably get a notice like this: + +>This package is not auto-updated. Please set up the [GitHub Service Hook](https://packagist.org/profile/) for Packagist so that it gets updated whenever you push! + +Let's take care of that. Click [that link](https://packagist.org/profile/), click "Show API Token", copy it and go to _your package's GitHub page_, in ```Settings / Webhooks & Services / Add a new service```. Search for Packagist. Enter your username and the token and hit Submit. Your error in Packagist should disappear in 5–10 minutes. + +Congrats! You now have a working package online. You can now require it with composer. + + +--- + + +## Step 5. Install it + +Since your package is now online, you can now install it using composer. + +```bash +# go to the root of you Laravel app +cd ../../.. + +# delete the folder from packages +rm -r packages + +composer require my-company/dummy-field-for-backpack --prefer-source +``` + +Notice we've installed it using the ```prefer-source``` flag. This will actually _clone the git repo_ inside your ```vendor/myvendor/mypackage``` directory. So you can do: + +```bash +cd /vendor/my-company/dummy-field-for-backpack +git checkout master +``` + +**That's it. Your package is online and installable!** You have most of the knowledge needed to build and maintain a PHP package. + + + +## Step 6. Double-check, then triple-check + +### Are you sure it's working? +After your package is online and ready to use, double-check that it's working well. Then triple-check. + +### Your README is your home page +Afterwards go to your README file again, and make sure it's the best it can be. Remember, your README file is the first thing people see when they find your package. If it's not appealing, they won't use it. If it doesn't do a good job of explaining how to use it, they won't use it. Take this seriously. This is where A LOT of packages go wrong - the authors do not spend the right amount of time on their README page. + +IMPORTANT. Make sure your README has a nice screenshot of the functionality you're offering. The easier it is for a developer to see the benefit of using your package, the more likely it is for them to install it. There's a trick in uploading images to GitHub, then using them in your README file. Go to your package's GitHub page, and add an issue. In that issue's body, drag&drop the screenshot image. GitHub will upload it, and give it an URL. Copy-paste that URL, submit the issue (you can also already close the issue), then use that image URL inside your README file. Boom! Free image hosting. + +By now you should have made some changes to your files, inside your ```vendor/my-company/dummy-field-for-backpack``` directory. + +After each change you want to publish, you should: +1. Write about that change in your ```CHANGELOG.md``` file. Increment the version sticking to SEMVER. + +2. Commit and push your changes, remembering to also create a new tag with the version. +```bash +git pull origin master +git add . +git commit -am "fixes #14189 - some problem or feature with an id from Github" +git tag 1.0.1 +git push origin master --tags +``` + + +## Step 7. Feedback and Promotion + +Congratulations on your new Backpack addon! + +To get feedback, ask people to try it on: +- [our subreddit](https://www.reddit.com/r/BackpackForLaravel/) +- [our Gitter chatroom](https://gitter.im/BackpackForLaravel/Lobby) + +Make sure you write something nice, so people are interested to click. + +After you've got some feedback, and a few users have installed your package and everything seems fine, time to promote it big time: +- post it to [laravel-news.com/links](https://laravel-news.com/links) +- show it off in the [Laracasts forum](https://laracasts.com/discuss) +- ask people to try it in [laravel.io](https://laravel.io/forum) + + +## Extra Credits + +If you're building a bigger package, with one CRUD or more, we recommend you follow the simple folder structure we use across all Backpack packages. Our rule of thumb: **organize your ```src``` folder like it were a Laravel application**. We do this because it's easier for developers to understand how the package works, and it makes it easy to copy-paste the code inside their apps and modify, for complicated use cases. That way, add-ons can be kept super-simple, with everybody adding functionality _in their own apps_. Example folder structure: +- [src] + - [app] + - [Http] + - [Models] + - [Requests] + - [database] + - [migrations] + - [seeds] + - [routes] + - AddonServiceProvider.php +- [tests] +- composer.json +- CHANGELOG.md +- LICENSE.md +- README.md + +For extra reading credits, these are the resources we've used to create this guide: +- https://laravel.com/docs/packages +- https://laracasts.com/discuss/channels/tips/developing-your-packages-in-laravel-5 +- https://github.com/jaiwalker/setup-laravel5-package +- https://github.com/Jeroen-G/laravel-packager +- https://laracasts.com/lessons/package-development-101 diff --git a/7.x-dev/base-about.md b/7.x-dev/base-about.md new file mode 100644 index 00000000..e1adba73 --- /dev/null +++ b/7.x-dev/base-about.md @@ -0,0 +1,223 @@ +# About Backpack's User Interface + +--- + +Backpack provides an admin interface that includes: +- HTML components and layouts, provided by Backpack themes; +- [sweetalert](https://sweetalert.js.org/) for triggering pretty confirmation modals; +- [noty](https://ned.im/noty/#/) to show notification bubbles upon error/success/warning/info - triggered from JavaScript; +- [prologue/alerts](https://github.com/prologuephp/alerts) for triggering notification bubbles from PHP (both on the same page and using flashdata); +- a separate authentication system for your admins; +- pretty error pages for most common errors; +- a menu you can customize; +- a few helpers you can use throughout your admin panel; + + +## Layout & Design + + +### General + +Depending on what theme you are using, Backpack pulls in a different Bootstrap HTML Template. You can use its HTML components for any custom pages or sections you create, by copy-pasting the HTML from their website: +- [`theme-tabler`](https://github.com/Laravel-Backpack/theme-tabler) uses [Tabler](https://tabler.io/preview) (the default) ; +- [`theme-coreuiv4`](https://github.com/Laravel-Backpack/theme-coreuiv4) uses [CoreUI v4](https://coreui.io/demos/bootstrap/4.2/free/); +- [`theme-coreuiv2`](https://github.com/Laravel-Backpack/theme-coreuiv2) uses [Backstrap](https://backstrap.net/) which is a fork of CoreUI v2 (not recommended - only use if you absolutely need support for IE); + + +### New Files in Your App + +After installation, you'll notice Backpack has added a few files: + +**1) View to ```resources/views/vendor/backpack/ui/inc/menu_items.blade.php```** + +This file is used to show the contents of the menu. It's been published there so that you can easily modify its contents, by editing its HTML. + +**2) Middleware to ```app/Http/Middleware/CheckIfAdmin.php```** + +This middleware is used to test if users have access to admin panel pages. You can (and should customize it) if you have both users and admins in your app. + +**3) Route file to ```routes/backpack/custom.php```** + +This route file is for convenience and convention. We recommend you place all your admin panel routes here. + + + +### Published Views + +After installation, you'll notice Backpack has added a new blade file in ```resources/views/vendor/backpack/ui/```: + - ```inc/menu_items.blade.php```; + +That file is used to show the contents of the menu. It's been published there so that you can easily modify its contents, by editing its HTML or adding dynamic content through [widgets](/docs/{{version}}/base-widgets). + + +### Unpublished Views + +You can change any blade file to your own needs. Determine what file you'd need to modify if you were to edit directly in the project's vendor folder (eg. `vendor/backpack/theme-tabler`), then go to ```resources/views/vendor/backpack/theme-tabler``` and create a file with the exact same name. Backpack will use this new file, instead of the one in the package. + +For example: +- when using the CoreUI v2 theme, if you want to add an item to the top menu, you could just create a file called ```resources/views/vendor/backpack/theme-coreuiv2/inc/topbar_left_content.blade.php```; Backpack will now use this file's contents, instead of ```vendor/backpack/theme-coreuiv2/resources/views/inc/topbar_left_content.php```; +- if you want to change the contents of the dashboard page, you can just create a file called `resources/views/vendor/backpack/ui/dashboard.blade.php` and Backpack will use that one, instead of the one in the package; + +You can create blade views from scratch, or you can use our command to publish the view from the package and edit it to your liking: +``` +php artisan backpack:publish ui/dashboard +``` + +Then inside the blade files, you can use either plain-old HTML or add dynamic content through [Backpack widgets](/docs/{{version}}/base-widgets). + +Please note that it is NOT recommended to publish and override too many theme files. If you discover you're creating many of these files, you're basically creating a new theme. So we recommend you do just that. Please follow the docs to create a new "child theme". + + +### Folder Structure + +If you'll take a look inside any Backpack package, you'll notice the packages are organised like a standard Laravel app. This is intentional. It should help you easily understand how the package works, and how you can overwrite/customize its functionality. + +- ```app``` + - ```Console``` + - ```Commands``` + - ```Http``` + - ```Controllers``` + - ```Middleware``` + - ```Requests``` + - ```Notifications``` +- ```config``` +- ```resources``` + - ```lang``` + - ```views``` +- ```routes``` + + +## Authentication + +When installed, Backpack provides a way for admins to login, recover password and register (don't worry, register is only enabled on ```localhost```). It does so with its own authentication controllers, models and middleware. If you have regular end-users (not admins), you can keep the _user_ authentication completely separate from _admin_ authentication. You can change which model, middleware classes Backpack uses, inside the ```config/backpack/base.php``` config file. + +> **Backpack uses Laravel's default ```App\Models\User``` model**. This assumes you weren't already using this model, or the ```users``` table, for anything else. Or that you plan to use it for both users & admins. Otherwise, please read below. + + +### Using a Different User Model + +If you want to use a different User model than ```App\Models\User``` or you've changed its location, you can tell Backpack to use _a different_ model in ```config/backpack/base.php```. Look for ```user_model_fqn```. + + + +### Having Both Regular Users and Admins + +If you already use the ```users``` table to store end-users (not admins), you will need a way to differentiate admins from regular users. Backpack does not force one method on you. Here are two methods we recommend, below: +- (A) adding an ```is_admin``` column to your ```users``` table, then changing the ```app/Http/Middleware/CheckIfAdmin::checkIfUserIsAdmin()``` method to test that attribute exists, and is true; +- (B) using the [PermissionManager](https://github.com/Laravel-Backpack/PermissionManager) extension - this will also add groups and permissions; Then tell Backpack to use _your new permission middleware_ to check if a logged in user is an admin, inside ```config/backpack/base.php```; + + +### Routes + +By default, all administration panel routes will be behind an ```/admin/``` prefix, and under an ```CheckIfAdmin``` middleware. You can change that inside ```config/backpack/base.php```. + +Inside your _admin controllers or views_, please: +- use ```backpack_auth()``` instead of ```auth()```; +- use ```backpack_user()``` instead of ```auth()->user```; +- use ```backpack_url()``` instead of ```url()```; + +This will make sure you're using the model, prefix & middleware that you've defined in ```config/backpack/base.php```. In case you decide to make changes there later, you won't need to change anything else. There are also [other backpack helpers you can use](#helpers). + + +## Admin Account + +When logged in, the admin can click on their name to go to their "account" page. There, they will be able to do a few basic operations: change name, email or password. + + +### Change Name and Email + +Changing name and email is done inside ```Backpack\CRUD\app\Http\Controllers\Auth\MyAccountController```, using the ```getAccountInfoForm()``` and ```postAccountInfoForm()``` methods. If you want to change how this works, we recommend you create a ```routes/backpack/base.php``` file, copy-paste all Backpack\CRUD routes there and change whatever routes you need, to point to _your own controller_, where you can do whatever you want. + +If you only want to add a few new inputs, you can do that by creating a file in ```resources/views/vendor/backpack/theme-xxx/my_account.blade.php``` that uses code from the same file in the theme, but adds the inputs you need. Remember to also make these fields ```$fillable``` in your User model. + + +### Change Password + +Password changing is done inside ```Backpack\CRUD\app\Http\Controllers\Auth\MyAccountController```. If you want to change how this works, we recommend you create a ```routes/backpack/base.php``` file, copy-paste all Backpack\CRUD routes there and change whatever you need. You can then point the route to your own controller, where you can do whatever you want. + + + +## Helpers + +You can use these helpers anywhere in your app (models, views, controllers, requests, etc), except the config files, since the config files are loaded _before_ the helpers. + +- **```backpack_url($path)```** - Use this helper instead of ```url()``` to generate paths with the admin prefix prepended. +- **```backpack_auth()```** - Returns the Auth facade, using the current Backpack guard. Basically a shorthand for ```\Auth::guard(backpack_guard_name())```. Use this instead of ```auth()``` inside your admin panel pages. +- **```backpack_middleware()```** - Returns the key for the admin middleware. Default is ```admin```. +- ```backpack_authentication_column()``` - Returns the username column. The Laravel and Backpack default is ```email```. +- ```backpack_users_have_email()``` - Tests that the ```email``` column exists on the users table and returns true/false. +- ```backpack_avatar($user)``` - Receives a user object and returns a path to an avatar image, according to the preferences in the config file (gravatar, placeholder or custom). +- ```backpack_guard_name()``` - Returns the guard used for Backpack authentication. +- ```backpack_user()``` - Returns the current Backpack user, if logged in. Basically a shorthand for ```\Auth::guard(backpack_guard_name())->user()```. Use this instead of ```auth()->user()``` inside your admin pages. + + +## Error Pages + +When installing Backpack, a few error views are published into ```resources/views/errors```, if you don't already have other files there. This is because Laravel does not provide error pages for all HTTP error codes. Having these files in your project will make sure that, if a user gets an HTTP error, at least it will look decent. Error pages are provided for the following error codes: ```400```, ```401```, ```403```, ```404```, ```405```, ```408```, ```429```, ```500```, ```503```. + + + +## Custom Pages + +To create a new page for your admin panel, you can follow the same process you would if you created a normal Laravel page (a Route, View and maybe a Controller). Just make sure that: +- the route file is under the `admin` middleware; +- the view extends one of our layout files (so that you get the design and the topbar+sidebar layout; + +You can do exactly that by running `php artisan backpack:page PageName`, or manually by following the steps below: + +### Add a custom page to your admin panel (dynamic page) + +```php + +# Step 1. Create the controller (we recommend you place it in your `app/Http/Controllers/Admin`) + +Example page +@endsection +``` + +### Add a custom page to your admin panel (static page) + +Alternatively, if you are not getting any information from the database, and are just creating a quick static page, here's a quicker way: + + +```php + +# Step 1. Create a route for it (we recommend you place it in your `routes/backpack/custom.php` for simplicity) + +Route::get('example-page', function () { return view('admin.example_page'); }); + +# Step 2. Create that view (we recommend you place it in your `resources/views/admin`: + +@extends(backpack_view('blank')) + +@section('content') +

Example page

+@endsection + +``` + diff --git a/7.x-dev/base-alerts.md b/7.x-dev/base-alerts.md new file mode 100644 index 00000000..d2c91701 --- /dev/null +++ b/7.x-dev/base-alerts.md @@ -0,0 +1,63 @@ +# Alerts + +--- + + +## About + +When building custom functionality, you'll probably need to give feedback to the admin for something that happened in the background. You can easily do that in Backpack by triggering alerts (aka notification bubbles, aka notifications). You can do that both from JavaScript and from PHP - and they will look exactly the same. In fact, Backpack operations use this same API. By using Alerts in your custom pages too, you make sure your alerts look the same across your admin panel - and your UI will be consistent. + + + +### Triggering Alerts in PHP + +We use [prologue/alerts](https://github.com/prologuephp/alerts#adding-alerts-through-alert-levels) to trigger notifications. Check out its documentation for the entire syntax. + +Most of the time, all you need to do is trigger a notification of a certain type, or trigger a notification using flash data, so that it shows after a redirect: + +```php +public function foo() +{ + \Alert::add('info', 'This is a blue bubble.'); + \Alert::add('warning', 'This is a yellow/orange bubble.'); + \Alert::add('error', 'This is a red bubble.'); + \Alert::add('success', 'Got it
This is HTML in a green bubble.'); + \Alert::add('primary', 'This is a dark blue bubble.'); + \Alert::add('secondary', 'This is a grey bubble.'); + \Alert::add('light', 'This is a light grey bubble.'); + \Alert::add('dark', 'This is a black bubble.'); + + // the layout will make sure to show your notifications + return view('some_view'); +} + +public function bar() +{ + \Alert::add('success', 'You have successfully logged in')->flash(); + + // please note the above flash() method; this will store your notification in a session variable, so that you can redirect to another page, but the notification will still be shown (on the page you redirect to) + return Redirect::to('some-url'); +} +``` + + +### Triggering Alerts in JavaScript + +We use [Noty](https://ned.im/noty/#/) to show notifications from JavaScript, on the same page. Check out its docs for detailed use. Most of the time you'll only need to do: + +```php +new Noty({ + type: "success", + text: 'Some notification text', +}).show(); + +// available types: +// - success +// - info +// - warning/notice +// - error/danger +// - primary +// - secondary +// - dark +// - light +``` \ No newline at end of file diff --git a/7.x-dev/base-breadcrumbs.md b/7.x-dev/base-breadcrumbs.md new file mode 100644 index 00000000..1512ee39 --- /dev/null +++ b/7.x-dev/base-breadcrumbs.md @@ -0,0 +1,89 @@ +# Breadcrumbs + +--- + + +## About + +Breadcrumbs show a path to the current page in the top-right corner of the screen, and can be a useful part of the UI for deeply nested admin panels. + +Breadcrumbs are loaded in the default layout _before_ the ```header``` section. + + +## Enable / Disable Breadcrumbs + +You can hide or show breadcrumbs on ALL of your admin panel pages by changing a boolean variable in your ```config/backpack/ui.php``` to change it for any active Backpack theme, or in that theme's config file: + +```php + // Show / hide breadcrumbs on admin panel pages. + 'breadcrumbs' => true, +``` + + +## How Breadcrumbs Work + +The ```inc.breadcrumbs``` view will show all breadcrumbs from the ```$breadcrumbs``` variable, if it's present on the page. The ```$breadcrumbs``` variable should be a simple associative array ```[ $label1 => $link1, $label2 => $link2 ]```. Examples: + +```php + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + 'Admin' => route('dashboard'), + 'Dashboard' => false, + ]; +``` + +Notice the last item should NOT have a link, it should be ```false```. + + +## How to Define Breadcrumbs + + +### Define Breadcrumbs Inside the Controller + +Make sure a ```$breadcrumbs``` variable is present inside your views: +```php + /** + * Show the admin dashboard. + * + * @return \Illuminate\Http\Response + */ + public function dashboard() + { + $this->data['title'] = trans('backpack::base.dashboard'); // set the page title + $this->data['breadcrumbs'] = [ + trans('backpack::crud.admin') => backpack_url('dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + return view('backpack::dashboard', $this->data); + } +``` + + +### Define Breadcrumbs Inside the View + +Make sure a ```$breadcrumbs``` variable is present: + +```php +@extends('backpack::layout') + +@php + $breadcrumbs = [ + 'Admin' => backpack_url('dashboard'), + 'Dashboard' => false, + ]; +@endphp + +@section('content') + some other content here +@endsection +``` diff --git a/7.x-dev/base-components.md b/7.x-dev/base-components.md new file mode 100644 index 00000000..8f61b769 --- /dev/null +++ b/7.x-dev/base-components.md @@ -0,0 +1,93 @@ +# Blade Components + +--- + + +## About + +[Blade components](https://laravel.com/docs/blade#components) are a quick way for you to output a bit of HTML... that can then be customized by each Backpack theme. It's a clean way of echoing theme-agnostic components, because for each Component that Backpack provides, the theme itself can customize and make it pretty in their own way. + + +### How to Use + +Anywhere in your blade files, you can use the Blade components we have. But most likely you'll be using them in your `resources/views/vendor/backpack/ui/inc/menu_items.blade.php`, because all our components currently are concerned with outputting menu items in a theme-agnostic way. + + + +### Mandatory Attributes + +There are no mandatory attributes. + + +### Optional Attributes + +All components also allow you to specify custom attributes. When you specify a custom attribute, that attribute will be placed on the most likely element of that component. In most cases, that is the anchor element. For example: + +```php + +``` + +Even though the 'target' attribute doesn't _technically_ exist in the component, that attribute will be placed on that component's `a` element. + + + +## Available Components + + +### Menu Item + +Shows a menu item, with the title and link you specify: + +```php + +``` + +Note that you can further customize this using custom attributes. If you define a `target` on it, that will be passed down to the `a` element. + +
+ + + +### Menu Separator + +Shows a menu separator, with the title you specify: + +```php + +``` + +Note that you can further customize this using custom attributes. If you define a `class` on it, that will be passed down to the `li` element. + +
+ + + +### Menu Dropdown & Menu Dropdown Item + +To show a dropdown menu, with elements, you can use the `menu-dropdown` and `menu-dropdown-item` components: + +```php + + + + + +``` + +Notes: +- on `menu-dropdown` you can define `nested="true"` to flag that dropdown as nested (aka. having a parent); so you can have dropdown in dropdown in dropdown; +- on both components, you can also define custom attributes; eg. if you define a `target` on one, that will be passed down to the `a` element; + +
+ + +## Overriding Default Components + +You can override a component by placing a file with the same name in your ```resources\views\vendor\backpack\ui\components``` directory. When a file is there, Backpack will pick that one up, instead of the one in the package. + +>**Avoid doing this.** When you're overwriting a component, you're forfeiting any future updates for that component. We can't push updates to a file that you're no longer using. + + +## Creating a Custom Component + +We do not support creating custom components within the `backpack` namespace. If you want to create a custom component, please create one in the `app` namespace. The [Laravel docs on Blade Components](https://laravel.com/docs/blade#components) will teach you everything you need to know. diff --git a/7.x-dev/base-how-to.md b/7.x-dev/base-how-to.md new file mode 100644 index 00000000..4b8efe38 --- /dev/null +++ b/7.x-dev/base-how-to.md @@ -0,0 +1,515 @@ +# FAQs for the admin UI + +--- + + + +## Look and feel + + +### Customize the menu or sidebar + +During installation, Backpack publishes `resources/views/vendor/backpack/ui/inc/menu_items.blade.php`. That file is meant to contain all menu items, using [menu item components](/docs/{{version}}/base-components#available-components) for example: + +``` + + + + + + + + + +``` + +Change that file as you please. You can also add custom HTML there, but please take note that if you change the theme, your custom HTML might not look good in that new theme. + + +### Customize the dashboard + +The dashboard is shown from ```Backpack\Base\app\Http\Controller\AdminController.php::dashboard()```. If you take a look at that method, you'll see that the only thing it does is to set a title, breadcrumbs, and return a view: ```backpack::dashboard```. + +In order to place something else inside that view, like [widgets](/docs/{{version}}/base-widgets), simply publish that view in your project, and Backpack will pick it up, instead of the one in the package. Create a ```resources/views/vendor/backpack/ui/dashboard.blade.php``` file: + +```html +@extends(backpack_view('blank')) + +@php + $widgets['before_content'][] = [ + 'type' => 'jumbotron', + 'heading' => trans('backpack::base.welcome'), + 'content' => trans('backpack::base.use_sidebar'), + 'button_link' => backpack_url('logout'), + 'button_text' => trans('backpack::base.logout'), + ]; +@endphp + +@section('content') +

Your custom HTML can live here

+@endsection +``` + +To use information from the database, you can: +- [use view composers](https://laravel.com/docs/5.7/views#view-composers) to push variables inside this view, when it's loaded; +- load all your dashboard information using AJAX calls, if you're loading charts, reports, etc, and the DB queries might take a long time; +- use the full namespace for your models, like ```\App\Models\Product::count()```; + +Take a look at the [widgets](/docs/{{version}}/base-widgets) we have - you can easily use those in your dashboard. You can also add whatever HTML you want inside the content block - check the [Backstrap HTML Template](https://backstrap.net/widgets.html) for design components you can copy-paste to speed up your custom HTML. + + +### Customizing the design of the menu / sidebar / footer + +Starting with Backpack v6, we have multiple themes. Each theme provides some configuration options, for you to change CSS classes in the header, body, footer, tabler etc. + +Please take a look at your theme's config file or README on Github, to see what you can change and how. + + +### Publish mobile and favicon headers and assets + +A very common use case is that your users bookmark or add your admin panel to their home screen on their mobile devices. In order to make that experience better, you can publish the mobile and favicon headers and assets. You can do that by running: + +```bash +php artisan backpack:publish-header-metas +``` + +This will ask you a few questions and then publish the necessary files. You can then customize them as you please to fit your branding. +Files that already exist will not be replaced, so if you want to re-publish Backpack files you need to delete the already published first. + + + +### Create a new theme / child theme + +You can create a theme with your own HTML. Create a folder with all the views you want to overwrite, then change ```view_namespace``` inside your ```config/backpack/ui.php``` to point to that folder. All views will be loaded from _that_ folder if they exist, then from ```resources/views/vendor/backpack/base```, then from the Base package. + +You can use child themes to create packages for your Backpack admin panels to look different (and re-use across projects). For more info on how to create a theme, see [this guide](/docs/{{version}}/add-ons-tutorial-how-to-create-a-theme). + + + +### Add custom JavaScript to all admin panel pages + +In ```config/backpack/ui.php``` you'll notice this config option: + +```php + // JS files that are loaded in all pages, using Laravel's asset() helper + 'scripts' => [ + // Backstrap includes jQuery, Bootstrap, CoreUI, PNotify, Popper + 'packages/backpack/base/js/bundle.js?v='.\PackageVersions\Versions::getVersion('backpack/base'), + + // examples (everything inside the bundle, loaded from CDN) + // 'https://code.jquery.com/jquery-3.4.1.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', + // 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', + // 'https://unpkg.com/@coreui/coreui/dist/js/coreui.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/pace/1.0.2/pace.min.js', + // 'https://unpkg.com/sweetalert/dist/sweetalert.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js' + + // examples (VueJS or React) + // 'https://unpkg.com/vue@2.4.4/dist/vue.min.js', + // 'https://unpkg.com/react@16/umd/react.production.min.js', + // 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js', + ], +``` + +You can add files to this array, and they'll be loaded in all admin panels pages. + + +### Add custom CSS to all admin panel pages + +In ```config/backpack/ui.php``` you'll notice this config option: + +```php + // CSS files that are loaded in all pages, using Laravel's asset() helper + 'styles' => [ + 'packages/@digitallyhappy/backstrap/css/style.min.css', + + // Examples (the fonts above, loaded from CDN instead) + // 'https://maxcdn.icons8.com/fonts/line-awesome/1.1/css/line-awesome-font-awesome.min.css', + // 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic', + ], +``` + +You can add files to this array, and they'll be loaded in all admin panels pages. + + +### Customize the look and feel of the admin panel (using CSS) + +If you want to change the look and feel of the admin panel, you can create a custom CSS file wherever you want. We recommend you do it inside ```public/packages/myname/mycustomthemename/css/style.css``` folder so that it's easier to turn into a theme, if you decide later to share or re-use your CSS in other projects. + +In ```config/backpack/ui.php``` add your file to this config option: + +```php + // CSS files that are loaded in all pages, using Laravel's asset() helper + 'styles' => [ + 'packages/@digitallyhappy/backstrap/css/style.min.css', + // ... + 'packages/myname/mycustomthemename/css/style.css', + ], +``` + +This config option allows you to add CSS files that add style _on top_ of Backstrap, to make it look different. You can create a CSS file anywhere inside your ```public``` folder, and add it here. + + +### How to add VueJS to all Backpack pages + +You can add any script you want inside all Backpack's pages by just adding it in your ```config/backpack/ui.php``` file: + +```php + + // JS files that are loaded in all pages, using Laravel's asset() helper + 'scripts' => [ + // Backstrap includes jQuery, Bootstrap, CoreUI, PNotify, Popper + 'packages/backpack/base/js/bundle.js', + + // examples (everything inside the bundle, loaded from CDN) + // 'https://code.jquery.com/jquery-3.4.1.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', + // 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', + // 'https://unpkg.com/@coreui/coreui/dist/js/coreui.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/pace/1.0.2/pace.min.js', + // 'https://unpkg.com/sweetalert/dist/sweetalert.min.js', + // 'https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js' + + // examples (VueJS or React) + // 'https://unpkg.com/vue@2.4.4/dist/vue.min.js', + // 'https://unpkg.com/react@16/umd/react.production.min.js', + // 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js', + ], +``` + +You should be able to load Vue.JS by just uncommenting that one line. Or providing a link to a locally stored VueJS file. + + + +### Customize the translated strings (aka overwrite the language files) + +Backpack uses the default Laravel lang configuration, to choose the admin panel language. So it will use whatever you set in `config/app.php` inside the `locale` key. By default it's `en` (english). We provide translations in more than 20 languages including RTL (arabic). + +Backpack uses Laravel translations across the admin panel, to easily translate strings (ex: `{{ trans('backpack::base.already_have_an_account') }}`). +If you don't like a translation, you're welcome to submit a PR to [Backpack CRUD repository](https://github.com/Laravel-Backpack/CRUD) to correct it for all users of your language. If you only want to correct it inside your app, or need to add a new translation string, you can *create a new file in your `resources/lang/vendor/backpack/en/base.php`* (similarly, `crud.php` or any other file). Any language strings that are inside your app, in the right folder, will be preferred over the ones in the package. + +Alternatively, if you need to customize A LOT of strings, you can use: +```bash +php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag="lang" +``` +which will publish ALL lang files, for ALL languages, inside `resources/lang/vendor/backpack`. But it's highly unlikely you need to modify all of them. In case you do publish all languages, please delete the ones you didn't change. That way, you only keep what's custom in your custom files, and it'll be easier to upgrade those files in the future. + +#### Translate the Laravel Framework strings + +Please note that **Backpack does NOT provide** translation strings for validation errors and other internal Laravel messages like in email templates. Those are provided by Laravel itself, and Laravel only provides the English versions. +To get validation error messages in all languages you want, we **highly recommend** installing and using https://github.com/Laravel-Lang/lang which provides exactly that. + + + +### Use the HTML & CSS for the front-end (Backstrap for front-facing website) + +If you like how Backpack looks and feels you can use the same interface to power your front-end, simply by making sure your blade view extend Backpack's layout file, instead of a layout file you'd create. Make sure your blade views extend `backpack_view('blank')` or create a layout file similar to our `layouts/top_left.blade.php` that better fits your needs. Then use it across your app: + +```php +@extends(backpack_view('blank')) + +
Something
+``` + +It's a good idea to go through our main layout file - [`layouts/top_left.blade.php`](https://github.com/Laravel-Backpack/CRUD/blob/master/src/resources/views/base/layouts/top_left.blade.php) - to understand how it works and how you can use it to your advantage. Most notably, you can: +- use our `before_styles` and `after_styles` sections to easily _include_ CSS there - `@section('after_styles')`; +- use our `before_styles` and `after_styles` stacks to easily _push_ CSS there - `@push('after_styles')`; +- use our `before_scripts` and `after_scripts` sections to easily _include_ JS there - `@section('after_scripts')`; +- use our `before_scripts` and `after_scripts` stacks to easily _push_ JS there - `@push('after_scripts')`; + + + + +## Authentication + + +### Customizing the Auth controllers + +In ```config/backpack/base.php``` you'll find these configuration options: + +```php + // Set this to false if you would like to use your own AuthController and PasswordController + // (you then need to setup your auth routes manually in your routes.php file) + 'setup_auth_routes' => true, +``` + +You can change both ```setup_auth_routes``` to ```false```. This means Backpack\Base won't register the Auth routes any more, so you'll have to manually register them in your route file, to point to the Auth controllers you want. If you're going to use the Auth controllers that Laravel generates, these are the routes you can use: +```php +Route::group(['middleware' => 'web', 'prefix' => config('backpack.base.route_prefix')], function () { + Route::auth(); + Route::get('logout', 'Auth\LoginController@logout'); +}); +``` + + +### Customize the routes + +#### Custom routes - option 1 + +You can place a new routes file in your ```app/routes/backpack/base.php```. If a file is present there, no default Backpack\Base routes will be loaded, only what's present in that file. You can use the routes file ```vendor/backpack/base/src/resources/views/base.php``` as an example, and customize whatever you want. + +#### Custom routes - option 2 + +In ```config/backpack/base.php``` you'll find these configuration options: + +```php + + /* + |-------------------------------------------------------------------------- + | Routing + |-------------------------------------------------------------------------- + */ + + // The prefix used in all base routes (the 'admin' in admin/dashboard) + 'route_prefix' => 'admin', + + // Set this to false if you would like to use your own AuthController and PasswordController + // (you then need to setup your auth routes manually in your routes.php file) + 'setup_auth_routes' => true, + + // Set this to false if you would like to skip adding the dashboard routes + // (you then need to overwrite the login route on your AuthController) + 'setup_dashboard_routes' => true, +``` + +In order to completely customize the auth routes, you can change both ```setup_auth_routes``` and ```setup_dashboard_routes``` to ```false```. This means Backpack\Base won't register any routes any more, so you'll have to manually register them in your route file. Here's what you can use to get started: +```php +Route::group(['middleware' => 'web', 'prefix' => config('backpack.base.route_prefix'), 'namespace' => 'Backpack\Base\app\Http\Controllers'], function () { + Route::auth(); + Route::get('logout', 'Auth\LoginController@logout'); + Route::get('dashboard', 'AdminController@dashboard'); + Route::get('/', 'AdminController@redirect'); +}); +``` + + +### Use separate login/register forms for users and admins + +This is a default in Backpack v4. + +Backpack's authentication uses a completely separate authentication driver, provider, guard and password broker. They're all named ```backpack```, and registered in the vendor folder, invisible to you. + +If you need a separate login for user, just go ahead and create it. [Add the Laravel authentication, like instructed in the Laravel documentation](https://laravel.com/docs/5.7/authentication#authentication-quickstart): ```php artisan make:auth```. You'll then have: +- the user login at ```/login``` -> using the AuthenticationController Laravel provides +- the admin login at ```/admin/login``` -> using the AuthenticationControllers Backpack provides + +The user login will be using Laravel's default authentication driver, provider, guard and password broker, from ```config/auth.php```. + +Backpack's authentication driver, provider, guard and password broker can easily be overwritten by creating a driver/provider/guard/broker with the ```backpack``` name inside your ```config/auth.php```. If one named ```backpack``` exists there, Backpack will use that instead. + + +### Overwrite Backpack authentication driver, provider, guard or password broker + +Backpack's authentication uses a completely separate authentication driver, provider, guard and password broker. Backpack adds them to what's defined in ```config/auth.php``` on runtime, and they're all named ```backpack```. + +To change a setting in how Backpack's driver/provider/guard or password broker works, create a driver/provider/guard/broker with the ```backpack``` name inside your ```config/auth.php```. If one named ```backpack``` exists there, Backpack will use that instead. + + +### Use separate sessions for admin&user authentication + +This is a default in Backpack v4. + + +### Login with username instead of email + +1. Create a ```username``` column in your users table and add it in ```$fillable``` on your ```User``` model. Best to do this with a migration. +2. Remove the UNIQUE and NOT NULL constraints from ```email``` on your table. Best to do this with a migration. Alternatively, delete your ```email``` column and remove it from ```$fillable``` on your ```User``` model. If you already have a CRUD for users, you might also need to delete it from the Request, and from your UserCrudController. +3. Change your ```config/backpack/base.php``` config options: +```php + // Username column for authentication + // The Backpack default is the same as the Laravel default (email) + // If you need to switch to username, you also need to create that column in your db + 'authentication_column' => 'username', + 'authentication_column_name' => 'Username', +``` +That's it. This will: +- use ```username``` for login; +- use ```username``` for registration; +- use ```username``` in My Account, when a user wants to change his info; +- completely disable the password recovery (if you've deleted the ```email``` db column); + + + +### Use your own User model instead of App\User + +By default, authentication and everything else inside Backpack is done using the ```App\User``` model. If you change the location of ```App\User```, or want to use a different User model for whatever other reason, you can do so by changing ```user_model_fqn``` in ```config/backpack/base.php``` to your new class. + + + +### Use your own profile image (avatar) + +By default, Backpack will use Gravatar to show the profile image for the currently logged in backpack user. In order to change this, you can use the option in ```config/backpack/base.php```: +```php +// What kind of avatar will you like to show to the user? +// Default: gravatar (automatically use the gravatar for his email) +// +// Other options: +// - placehold (generic image with his first letter) +// - example_method_name (specify the method on the User model that returns the URL) +'avatar_type' => 'gravatar', +``` + +Please note that this does not allow the user to change his profile image. + + + +### Add one or more fields to the Register form + +To add a new field to the Registration page, you should: + +**Step 1.** Overwrite the registration route, so it leads to _your_ controller, instead of the one in the package. We recommend you add it your ```routes/backpack/custom.php```, BEFORE the route group where you define your CRUDs: + +```php +Route::get('admin/register', 'App\Http\Controllers\Admin\Auth\RegisterController@showRegistrationForm')->name('backpack.auth.register'); +``` + +**Step 2.** Create the new RegisterController somewhere in your project, that extends the RegisterController in the package, and overwrites the validation & user creation methods. For example: + +```php +getTable(); + $email_validation = backpack_authentication_column() == 'email' ? 'email|' : ''; + + return Validator::make($data, [ + 'name' => 'required|max:255', + backpack_authentication_column() => 'required|'.$email_validation.'max:255|unique:'.$users_table, + 'password' => 'required|min:6|confirmed', + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * + * @return User + */ + protected function create(array $data) + { + $user_model_fqn = config('backpack.base.user_model_fqn'); + $user = new $user_model_fqn(); + + return $user->create([ + 'name' => $data['name'], + backpack_authentication_column() => $data[backpack_authentication_column()], + 'password' => bcrypt($data['password']), + ]); + } +} +``` +Add whatever validation rules & inputs you want, in addition to name and password. + +**Step 3.** Add the actual inputs to your HTML. You can easily overwrite the register view by adding this method to the same RegisterController: + +```php + public function showRegistrationForm() + { + + // if registration is closed, deny access + if (! config('backpack.base.registration_open')) { + abort(403, trans('backpack::base.registration_closed')); + } + + $this->data['title'] = trans('backpack::base.register'); // set the page title + + return view(backpack_view('auth.register'), $this->data); + } +``` +This will make the registration process pick up a view you can create, in ```resources/views/vendor/backpack/{theme}/auth/register.blade.php```. You can copy-paste the original view, and modify as you please. Including adding your own custom inputs. (replace {theme} with the theme you are using, by default is `theme-tabler`) + + +### Enable email verification in Backpack routes + +In Backpack CRUD 6.2 we introduced the ability to require email verification when accessing Backpack routes. To enable this feature please do the following: + +**Step 1** - Make sure your user model (usually `App\Models\User`) implements the `Illuminate\Contracts\Auth\MustVerifyEmail` contract. [More info](https://laravel.com/docs/10.x/verification#model-preparation). + +```php +addColumn('timestamp', 'email_verified_at', ['nullable' => true])->after('email'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['email_verified_at']); + }); + } +}; + +``` +Then run `php artisan migrate`. [More info](https://laravel.com/docs/10.x/verification#database-preparation). + +**Step 3** - New Laravel 10/11 installations already have them in place so you can skip this step. If you came from earlier versions it's possible that they are missing in your app, in that case you can add them manually. + +```php +// for Laravel 10: +protected $middlewareAliases = [ + // ... other middleware aliases + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + // if you don't have the VaidateSignature middleware you can copy it from here: + // https://github.com/laravel/laravel/blob/10.x/app/Http/Middleware/ValidateSignature.php + 'signed' => \App\Http\Middleware\ValidateSignature::class, + ]; +``` + +**Step 4** - Enable the functionality in `config/backpack/base.php` by changing `setup_email_validation_routes` to `true`. If you don't have this config key there, now is a good time to add it. + + + diff --git a/7.x-dev/base-themes.md b/7.x-dev/base-themes.md new file mode 100644 index 00000000..3ce384a1 --- /dev/null +++ b/7.x-dev/base-themes.md @@ -0,0 +1,148 @@ +# Themes + +--- + + +## About + +Backpack v6 provides three new themes: +- `backpack/theme-tabler` - includes Tabler, the excellent Bootstrap 5 HTML template; +- `backpack/theme-coreuiv2` - includes Backstrap, which is a CoreUI v2 fork we used in Backpack v5; +- `backpack/theme-coreuiv4` - includes CoreUI v4; + +Each theme has its PROs and CONs. We will discuss them in detail below, as well as presenting how the theming system works, and how you can use it. + + + +## How Themes Work + +Each theme is a separate Composer package, containing the blade files needed for Backpack to show an interface to its admins. Backpack knows what theme to use by looking in `config/backpack/ui.php`. In there you'll see: + +```php + /* + |-------------------------------------------------------------------------- + | Theme (User Interface) + |-------------------------------------------------------------------------- + */ + // Change the view namespace in order to load a different theme than the one Backpack provides. + // You can create child themes yourself, by creating a view folder anywhere in your resources/views + // and choosing that view_namespace instead of the default one. Backpack will load a file from there + // if it exists, otherwise it will load it from the fallback namespace. + + 'view_namespace' => 'backpack.theme-tabler::', + 'view_namespace_fallback' => 'backpack.theme-tabler::', +``` + +Notice that: +- you can specify a new theme by specifying a different view namespace; so if you want to point to a local view directory, you can do that too; +- we have a fallback mechanism in place; if a view isn't found in the first view namespace, Backpack will look in a second view namespace; this allows you to quickly create "child themes"; + +You can think of themes as extending `UI`. Both in terms of blade views and configs. + + +### Theme View Fallbacks + +When you're doing `backpack_view('path.to.view')`, Backpack will look both the namespace and fallback namespace configured in `config/backpack/ui.php`. If nothing is found, it will also look in the `backpack/ui` directory. For example if you have: +```php + 'view_namespace' => 'admin.theme-custom.', + 'view_namespace_fallback' => 'backpack.theme-tabler::', +``` +Backpack will use the first file it finds, in the order below. If none of these views exist, it will throw an error: +- `resources/views/admin/theme-custom/path/to/view.blade.php` +- `resources/views/vendor/backpack/theme-tabler/path/to/view.blade.php` +- `vendor/backpack/theme-tabler/resources/views/path/to/view.blade.php` +- `resources/views/vendor/backpack/ui/path/to/view.blade.php` +- `vendor/backpack/crud/src/resources/views/ui/path/to/view.blade.php` + +This fallback mechanism might look complex at first, but you'll quickly get used to it, and it provides A LOT of power and convenience. It allows you to: +- override a blade file for one theme, by placing it in the theme directory; +- create a blade file for all themes, by placing it in the `ui` directory; +- easily create new themes, that extends a different theme; +- easily add/remove themes from your project, using Composer; + + +### Theme Configs + +Each theme can provide its own config file. That file overrides anything that was set in `config/backpack/ui.php`, because each theme may want to do things differently, include other assets etc. So: +- if you change `config/backpack/ui.php`, your change will apply to ALL themes, unless that theme has overriden the config in its own config file; +- if you change `config/backpack/theme-tabler.php`, your change will only apply to that theme; + +Inside your files you can access a theme config by using `backpack_theme_config('something')`. + + + +### Translate Theme Strings + +Some themes may provide languages strings that can be translated into other languages. Those themes are stored inside the `backpack.theme-{themeName}` namespace so to change any strings or create new translations you can place your files in `lang/vendor/backpack.theme-{themeName}/{language}/theme-{themeName}.php`. + +Replace `{themeName}` and `{language}` with the theme name and language you want to translate. For example, if you want to translate the `backpack/theme-tabler` theme into Spanish, you would create a file at `lang/vendor/backpack.theme-tabler/es/theme-tabler.php`. + +> Better than just creating a file, feel free to submit a PR or just open an issue with the translated keys and we may add them into the package so all users can benefit from them. We highly appreciate your efforts. Thank you! + + +## Official Themes + + +### Tabler Theme + +![](https://user-images.githubusercontent.com/1032474/240274915-f45460a7-b876-432c-82c3-b0b3c60a39f2.png) + +[`backpack/theme-tabler`](https://github.com/Laravel-Backpack/theme-tabler) was created in 2023 and brings the full power of Tabler, the excellent Bootstrap HTML Template, to Backpack developers. In addition to "normal" Backpack views it also offers: +- dark mode; +- 3 alternative authentication views (default, cover, image); +- 9 alternative layouts (horizontal, horizontal overlap, horizontal dark, vertical, vertical dark, vertical transparent, right vertical, right vertical dark, right vertical transparent); + +We believe Tabler is the best HTML template on the market right now, with many _many_ HTML components to choose from, which is why we've chosen Tabler as the default theme for Backpack v6. + +For more information and installation steps, see [`backpack/theme-tabler`](https://github.com/Laravel-Backpack/theme-tabler) on Github. + +To get more information on Backpack specific built-in components for tabler, please check the [Tabler Theme](https://backpackforlaravel.com/docs/theme-tabler) page. + + +### CoreUIv2 Theme + +![](https://user-images.githubusercontent.com/1032474/240272550-456499a0-ef31-48a1-a985-1de3ff6107e5.png) + +[`backpack/theme-coreuiv2`](https://github.com/Laravel-Backpack/theme-coreuiv2) is a legacy theme. It provides the views from Backpack v5... to Backpack v6 users. It serves three purposes: +- allows Backpack developers who need Internet Explorer support to upgrade to Backpack v6; +- allows Backpack developers who have _heavily_ customized their blade files to upgrade to Backpack v6; +- allows Backpack developers to easily upgrade from v5 to v6, by using this theme with few breaking changes, before moving to one with a lot more breaking changes; + +We do not recommend using this theme in production, long-term. We recommend using `theme-coreuiv4` or ideally `theme-tabler`, which use Bootstrap 5. But we do understand some projects are too difficult to upgrade or migrate, and for those projects we've spent the time to create this theme. + +For more information and installation steps, see [`backpack/theme-coreuiv2`](https://github.com/Laravel-Backpack/theme-coreuiv2) on Github. + + +### CoreUIv4 Theme + +![](https://user-images.githubusercontent.com/1032474/240274314-184d328e-0e6c-4d67-942b-4e4d4efd96c8.png) + +[`backpack/theme-coreuiv4`](https://github.com/Laravel-Backpack/theme-coreuiv4) uses the CoreUI v4 Bootstrap HTML Template. It's an easy upgrade from CoreUI v2 that we've used in Backpack v5, so if you're upgrading a project with a few custom blade files, you'll find this is an easiest theme to adopt. Not many breaking changes from v5 to v6. + +For more information and installation steps, see [`backpack/theme-coreuiv4`](https://github.com/Laravel-Backpack/theme-coreuiv4) on Github. + + +## What Theme Should I Use + +Backpack v6 has launched with 3 themes from day one, to cater for the most scenarios possible: +- **if you're starting a new project, use `backpack/theme-tabler`**; it's the newest theme, with the most features: dark mode, vertical layouts, alternative auth views and many more HTML components to choose from, in your custom pages; +- **if you're upgrading an old project**, depending on how many files there are in your `resources/views/vendor/backpack/`, under the `base` or `ui` directories: + - if you don't have many files (1-5), use `backpack/theme-tabler`; + - if you have a few files (5-10), use `backpack/theme-coreuiv4`; + - if you have many files (10+), use `backpack/theme-coreuiv2`; + +Even if you plan to use `theme-tabler` or `theme-coreuiv4`, when upgrading it's a good idea to use `theme-coreuiv2` during the upgrade process, while you make your changes in your PHP classes, configs, etc. Then once everything's working, start using the new theme, and make the needed changes in your blade files. + + +## How to Create a New Theme + +Creating a new Backpack theme should only take you about 5 hours. If it takes you more, please contact us and let us know what was the most time-consuming part, so we can improve the process. In order to create a new Backpack theme, please [follow the guide](/docs/{{version}}/add-ons-tutorial-how-to-create-a-theme). + + +## How to Uninstall a Theme + +Each theme can have its own uninstallation process. So please check the theme's docs on Github. But in principle, uninstalling a Backpack theme should involve following these steps: + +1. Remove the composer package. Eg. `composer remove backpack/theme-coreuiv2` +2. Delete the config file. Eg. `rm -rf config/backpack/theme-coreuiv2.php` +3. Install a new theme (eg. `php artisan backpack:require:theme-tabler`) or change the `view_namespace` in `config/backpack/ui.php` to the theme you want to be active. diff --git a/7.x-dev/base-widgets.md b/7.x-dev/base-widgets.md new file mode 100644 index 00000000..d984ee64 --- /dev/null +++ b/7.x-dev/base-widgets.md @@ -0,0 +1,702 @@ +# Widgets + +--- + + +## About + +Widgets (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. + + + +### Requirements + +In order to use the ```Widget``` class, you should make sure your main views (for new admin panel pages) extend the ```backpack::blank``` or ```backpack_view('blank')``` blade template. This template includes two sections where you can push widgets: +- ```before_content``` +- ```after_content``` + + + +### How to Use + +You can easily push widgets to these sections, by using the autoloaded ```Widget``` class. You can think of the ```Widget``` class as a global container for widgets, for the current page being rendered. That means you can call the ```Widget``` container inside a ```Controller```, inside a ```view```, or inside a service provider you create - wherever you want. + +```php +use Backpack\CRUD\app\Library\Widget; + +Widget::add($widget_definition_array)->to('before_content'); + +// alternatively, use a fluent syntax to define each widget attribute +Widget::add() + ->to('before_content') + ->type('card') + ->content(null); +``` + + + +### Mandatory Attributes + +When passing a widget array, you need to specify at least these attributes: +```php +[ + 'type' => 'card' // the kind of widget to show + 'content' => null // the content of that widget (some are string, some are array) +], +``` + + +### Optional Attributes + +Most widget types also have these attributes present, which you can use to tweak how the widget looks inside the page: +```php +'wrapper' => [ + 'class' => 'col-sm-6 col-md-4', // customize the class on the parent element (wrapper) + 'style' => 'border-radius: 10px;', +] +``` + + +### Widgets API + +To manipulate widgets, you can use the methods below. The action will be performed on the page being constructed for the current request. And the ```Widget``` class is a global container, so you can add widgets to it both from the Controller, and from the view. + +```php +// to add a widget to a different section than the default 'before_content' section: +Widget::add($widget_definition_array)->to('after_content'); +Widget::add($widget_definition_array)->section('after_content'); +Widget::add($widget_definition_array)->group('after_content'); + +// to create a widget, WITHOUT adding it to a section +Widget::make($widget_definition_array); + +// to define the contents of a widget, pass the definition array to the make()/add() methods +Widget::add($widget_definition_array); +Widget::make($widget_definition_array); +// alternatively, define each widget attribute one by one, using a fluent syntax +Widget::add() + ->to('after_content') + ->type('card') + ->content('something'); + +// to reference a widget later on, give it a unique 'name' +Widget::add($widget_definition_array)->name('my_widget'); + +// you can then easily modify it +Widget::name('my_widget')->content('some other content'); // change the 'content' attribute +Widget::name('my_widget')->forget('attribute_name'); // unset a widget attribute +Widget::name('my_widget')->makeFirst(); // make a widget the first one in its section +Widget::name('my_widget')->makeLast(); // to make a widget the last one in its section +Widget::name('my_widget')->remove(); // remove the widget from its section +``` + + + + +## Default Widget Types + + +### Alert + +Shows a notification bubble, with the heading and text you specify: + +```php +[ + 'type' => 'alert', + 'class' => 'alert alert-danger mb-2', + 'heading' => 'Important information!', + 'content' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Corrupti nulla quas distinctio veritatis provident mollitia error fuga quis repellat, modi minima corporis similique, quaerat minus rerum dolorem asperiores, odit magnam.', + 'close_button' => true, // show close button or not +] +``` + +For different colors, you can use the following classes: ```alert-success```, ```alert-warning```, ```alert-info```, ```alert-danger``` ```alert-primary```, ```alert-secondary```, ```alert-light```, ```alert-dark```. + +Widget Preview: + +![Backpack alert widget](https://backpackforlaravel.com/uploads/v4/widgets/alert.png) + +
+ + +### Card + +Shows a Bootstrap card, with the heading and body you specify. You can customize the class name to get cards of different color, size, etc. + +```php +[ + 'type' => 'card', + // 'wrapper' => ['class' => 'col-sm-6 col-md-4'], // optional + // 'class' => 'card bg-dark text-white', // optional + 'content' => [ + 'header' => 'Some card title', // optional + 'body' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non mi nec orci euismod venenatis. Integer quis sapien et diam facilisis facilisis ultricies quis justo. Phasellus sem turpis, ornare quis aliquet ut, volutpat et lectus. Aliquam a egestas elit. Nulla posuere, sem et porttitor mollis, massa nibh sagittis nibh, id porttitor nibh turpis sed arcu.', + ] +] +``` + +For different background colors, you can use the following classes: ```bg-success```, ```bg-warning```, ```bg-info```, ```bg-danger``` ```bg-primary```, ```bg-secondary```, ```bg-light```, ```bg-dark```. + +Other useful helper classes: ```text-center```, ```text-white```. + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/card.png) + +
+ + +### Chart PRO + +Shows a pie chart / line chart / bar chart inside a Bootstrap card, with the heading and body you specify. + +![Backpack chart widgets](https://backpackforlaravel.com/uploads/docs-4-2/release_notes/chart_widget_small.gif) + +To create and use a new widget chart, you need to: + +**Step 1.** Install laravel-charts, that offers a single PHP syntax for 6 different charting libraries: +``` +composer require consoletvs/charts:"6.*" +``` + +**Step 2.** Create a new ChartController: + +``` +php artisan backpack:chart WeeklyUsers + +``` + +This will create: +- a new ChartController inside ```app\Http\Controllers\Admin\Charts\WeeklyUsersChartController``` +- a route towards that ChartController in your ```routes/backpack/custom.php``` + +**Step 3.** Add the widget that points to that ChartController you just created: +```php +Widget::add([ + 'type' => 'chart', + 'controller' => \App\Http\Controllers\Admin\Charts\WeeklyUsersChartController::class, + + // OPTIONALS + + // 'class' => 'card mb-2', + // 'wrapper' => ['class'=> 'col-md-6'] , + // 'content' => [ + // 'header' => 'New Users', + // 'body' => 'This chart should make it obvious how many new users have signed up in the past 7 days.

', + // ], +]); +``` + +**Step 4.** Configure the ChartController you just created: +- ```public function setup()``` (MANDATORY) + - initialize and configure ```$this->chart```, using the methods detailed in the [laravel-charts documentation](https://charts.erik.cat/getting_started.html); + - you _can_ define your dataset here, if you want your DB queries to be called upon page load; +- ```public function data()``` (OPTIONAL, but recommended) + - use ```$this->chart->dataset()``` to configure what the chart should contain; + - if it's defined, the chart will loads its contents using AJAX; + +Optionally: +- you can _easily_ switch the JavaScript library used, by changing the use statement at the top of this file: + +```diff +-use ConsoleTVs\Charts\Classes\Chartjs\Chart; ++use ConsoleTVs\Charts\Classes\Echarts\Chart; ++use ConsoleTVs\Charts\Classes\Fusioncharts\Chart; ++use ConsoleTVs\Charts\Classes\Highcharts\Chart; ++use ConsoleTVs\Charts\Classes\C3\Chart; ++use ConsoleTVs\Charts\Classes\Frappe\Chart; +``` +- you can change the path to the JS library; if you don't want it loaded from a CDN, you can define ```$library``` or ```getLibraryFilePath()``` on your ChartController: + +```php +protected $library = 'http://path/to/file'; + +// or + +public function getLibraryFilePath() +{ + return asset('path/to/your/js/file'); + + // or + + return [ + asset('path/to/first/js/file'), + asset('path/to/second/js/file'), + ]; +} +``` + + + +**That's it!** Refresh the page to see your new chart widget. Below, you'll find a few examples of how the ChartController can end up looking. + +
+ +**Example 1:** ChartController that loads results using AJAX: +```php +chart = new Chart(); + + // MANDATORY. Set the labels for the dataset points + $labels = []; + for ($days_backwards = 30; $days_backwards >= 0; $days_backwards--) { + if ($days_backwards == 1) { + } + $labels[] = $days_backwards.' days ago'; + } + $this->chart->labels($labels); + + // RECOMMENDED. + // Set URL that the ChartJS library should call, to get its data using AJAX. + $this->chart->load(backpack_url('charts/new-entries')); + + // OPTIONAL. + $this->chart->minimalist(false); + $this->chart->displayLegend(true); + } + + /** + * Respond to AJAX calls with all the chart data points. + * + * @return json + */ + public function data() + { + for ($days_backwards = 30; $days_backwards >= 0; $days_backwards--) { + // Could also be an array_push if using an array rather than a collection. + $users[] = User::whereDate('created_at', today() + ->subDays($days_backwards)) + ->count(); + $articles[] = Article::whereDate('created_at', today() + ->subDays($days_backwards)) + ->count(); + $categories[] = Category::whereDate('created_at', today() + ->subDays($days_backwards)) + ->count(); + $tags[] = Tag::whereDate('created_at', today() + ->subDays($days_backwards)) + ->count(); + } + + $this->chart->dataset('Users', 'line', $users) + ->color('rgb(77, 189, 116)') + ->backgroundColor('rgba(77, 189, 116, 0.4)'); + + $this->chart->dataset('Articles', 'line', $articles) + ->color('rgb(96, 92, 168)') + ->backgroundColor('rgba(96, 92, 168, 0.4)'); + + $this->chart->dataset('Categories', 'line', $categories) + ->color('rgb(255, 193, 7)') + ->backgroundColor('rgba(255, 193, 7, 0.4)'); + + $this->chart->dataset('Tags', 'line', $tags) + ->color('rgba(70, 127, 208, 1)') + ->backgroundColor('rgba(70, 127, 208, 0.4)'); + } +} + +``` + +**Example 2.** Pie chart with both labels and dataset defined in the ```setup()``` method (no AJAX): + +```php +chart = new Chart(); + + $this->chart->dataset('Red', 'pie', [10, 20, 80, 30]) + ->backgroundColor([ + 'rgb(70, 127, 208)', + 'rgb(77, 189, 116)', + 'rgb(96, 92, 168)', + 'rgb(255, 193, 7)', + ]); + + // OPTIONAL + $this->chart->displayAxes(false); + $this->chart->displayLegend(true); + + // MANDATORY. Set the labels for the dataset points + $this->chart->labels(['HTML', 'CSS', 'PHP', 'JS']); + } +} + +``` + +
+ + + +### Div + +Allows you to include multiple widgets within a "div" element with the attributes of your choice. For example, you can include multiple widgets within a ```
``` with the code below: + +```php +[ + 'type' => 'div', + 'class' => 'row', + 'content' => [ // widgets + [ 'type' => 'card', 'content' => ['body' => 'One'] ], + [ 'type' => 'card', 'content' => ['body' => 'Two'] ], + [ 'type' => 'card', 'content' => ['body' => 'Three'] ], + ] +] +``` + +Anything you specify on this widget, other than ```type``` and ```content```, has to be a string, and will be considered an attribute of the "div" element. +For example, in the following snippet, ```class``` and ```custom-attribute``` are attributes of the "div" element: + +```php +[ + 'type' => 'div', + 'class' => 'row my-custom-widget-class', + 'custom-attribute' => 'my-custom-value', + 'content' => [ // widgets + [ 'type' => 'card', 'content' => ['body' => 'One'] ], + [ 'type' => 'card', 'content' => ['body' => 'Two'] ], + [ 'type' => 'card', 'content' => ['body' => 'Three'] ], + ] +] +``` + +and the generated output will be: + +```html +
+ // The HTML code of the three card widgets will be here +
+``` + +
+ + +### Jumbotron + +Shows a Bootstrap jumbotron component, with the heading and body you specify. + +```php +[ + 'type' => 'jumbotron', + 'heading' => 'Welcome!', + 'content' => 'My magnific headline! Lets build something awesome together.', + 'button_link' => backpack_url('logout'), + 'button_text' => 'Logout', + // OPTIONAL: + 'heading_class' => 'display-3 text-white', + 'content_class' => 'text-white', +] +``` + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/jumbotron.png) + +
+ + +### Livewire + +Add a Livewire component to a page. If you haven't created your component yet, head to [Livewire documentation](https://livewire.laravel.com/docs/components) and create the component you want to use. + +**Note Livewire v2**: Livewire v2 does not automatically inject the `@livewireScripts` and `@livewireStyles` tags. If you **are NOT using** Livewire outside of this widget you can load them here by setting `livewireAssets => true` + +```php +[ + 'type' => 'livewire', + 'content' => 'my-livewire-component', // the component name + 'parameters' => ['user' => backpack_user(), 'param2' => 'value2'], // optional: pass parameters to the component + 'livewireAssets' => false, // optional: set true to load livewire assets in the widget +] +``` + +**Note:** The ```parameters``` attribute will be passed to the component on initialization, and should be present in the `mount($user, $param2)`. + +##### HelloWord Example: + +```php +use Livewire\Component; + +class HelloWorld extends Component +{ + public $name; + + public function mount(string $name) + { + $this->name = $name; + } + + public function render() + { + return view('livewire.hello-world'); + } +} +``` + +```blade + +
+ Hello {{ $name }} +
+``` + +```php +// add the widget to the page +Widget::add()->type('livewire')->content('hello-world')->parameters(['name' => 'John Doe'])->wrapperClass('col-md-12 text-center'); +``` + +Widget Preview: + +![Backpack Livewire Widget](https://github.com/Laravel-Backpack/CRUD/assets/7188159/e0aca2cb-f471-43c7-82e6-014704d576f3) + +
+ + +### Progress + +Shows a colorful card to signify the progress towards a goal. You can customize the class name to get cards of different color, size, etc. + +```php +[ + 'type' => 'progress', + 'class' => 'card text-white bg-primary mb-2', + 'value' => '11.456', + 'description' => 'Registered users.', + 'progress' => 57, // integer + 'hint' => '8544 more until next milestone.', +] +``` + +For different background colors, you can use the following classes: ```bg-success```, ```bg-warning```, ```bg-info```, ```bg-danger``` ```bg-primary```, ```bg-secondary```, ```bg-light```, ```bg-dark```. + +Other useful helper classes: ```text-center```, ```text-white```. + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/progress.png) + +
+ + +### Progress White + +Shows a white card to signify the progress towards a goal. You can customize the class name to get progress bars of different color. + +```php +[ + 'type' => 'progress_white', + 'class' => 'card mb-2', + 'value' => '11.456', + 'description' => 'Registered users.', + 'progress' => 57, // integer + 'progressClass' => 'progress-bar bg-primary', + 'hint' => '8544 more until next milestone.', +] +``` + +For different progress bar colors, you can use the following classes: ```bg-success```, ```bg-warning```, ```bg-info```, ```bg-danger``` ```bg-primary```, ```bg-secondary```, ```bg-light```, ```bg-dark```. + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/progress_white.png) + +
+ + +### Script + +Loads a JavaScript file from a location you specify using a ` + +@endpush +``` + + + +## Using a Widget Type from a Package + +You can choose the view namespace when loading a widget: + +```php + +// using the fluent syntax, use the 'from' alias +Widget::add($widget_definition_array)->from('package::widgets'); + +// using the widget definition array, specify its 'viewNamespace' +Widget::add([ + 'type' => 'card', + 'viewNamespace' => 'package::widgets', + 'wrapper' => ['class' => 'col-sm-6 col-md-4'], + 'class' => 'card text-white bg-primary text-center', + 'content' => [ + // 'header' => 'Another card title', + 'body' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non mi nec orci euismod venenatis. Integer quis sapien et diam facilisis facilisis ultricies quis justo. Phasellus sem turpis, ornare quis aliquet ut, volutpat et lectus. Aliquam a egestas elit.', + ], +]); + +``` + +Similarly, if you want to create widgets somewhere else than in ```resources/views/vendor/backpack/ui/widgets```, you can pass that directory as the namespace of your widget. For example, ```resources/views/admin/widgets``` would have ```admin.widgets``` as the namespace. diff --git a/7.x-dev/crud-api.md b/7.x-dev/crud-api.md new file mode 100644 index 00000000..c55e1f8b --- /dev/null +++ b/7.x-dev/crud-api.md @@ -0,0 +1,545 @@ +# CRUD API + +--- + +Here are all the features you will be using **inside your EntityCrudController**, grouped by the operation you will most likely use them for. + +## Operations + +- **operation()** - allows you to add a set of instructions inside ```setup()```, that only gets called when a certain operation is being performed; +```php +public function setup() { + // ... + $this->crud->operation('list', function() { + $this->crud->addColumn('name'); + }); +} +``` + + +### List Operation + + +#### Columns + +Manipulate what columns are shown in the table view. + +- **addColumn()** - add a column, at the end of the stack +```php +$this->crud->addColumn($column_definition_array); +``` + +- **addColumns()** - add multiple columns, at the end of the stack +```php +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); +``` + +- **modifyColumn()** - change column attributes +```php +$this->crud->modifyColumn($name, $modifs_array); +``` + +- **removeColumn()** - remove one column from all operations +```php +$this->crud->removeColumn('column_name'); +``` + +- **removeColumns()** - remove multiple columns from all operations +```php +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +``` + +- **setColumnDetails()** - change the attributes of one column; alias of ```modifyColumn()```; +```php +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +``` + +- **setColumnsDetails()** - change the attributes of multiple columns; alias of ```modifyColumn()```; +```php +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); +``` + +- **setColumns()** - remove previously set columns and only use the ones give now; +```php +$this->crud->setColumns(); +// sets the columns you want in the table view, either as array of column names, or multidimensional array with all columns detailed with their types +``` + +- **Chained - beforeColumn()** - insert current column _before_ the given column +```php +// ------ REORDER COLUMNS +$this->crud->addColumn()->beforeColumn('name'); +``` + +- **Chained - afterColumn()** - insert current column _after_ the given column +```php +$this->crud->addColumn()->afterColumn('name'); +``` + +- **Chained - makeFirstColumn()** - make this column the first one in the list +```php +$this->crud->addColumn()->makeFirstColumn(); +// Please note: you need to also specify priority 1 in your addColumn statement for details_row or responsive expand buttons to show +``` + + +#### Buttons + +- **addButton()** - add a button in the given stack +```php +$this->crud->addButton($stack, $name, $type, $content, $position); +// stacks: top, line, bottom +// types: view, model_function +// positions: beginning, end (defaults to 'beginning' for the 'line' stack, 'end' for the others); +``` + +- **addButtonFromModelFunction()** - add a button whose HTML is returned by a method in the CRUD model +```php +$this->crud->addButtonFromModelFunction($stack, $name, $model_function_name, $position); +``` + +- **addButtonFromView()** - add a button whose HTML is in a view placed at ```resources\views\vendor\backpack\crud\buttons``` +```php +$this->crud->addButtonFromView($stack, $name, $view, $position); +``` + +- **modifyButton()** - modify the attributes of a button +```php +$this->crud->modifyButton($name, $modifications); +``` + +- **removeButton()** - remove a button from whatever stack it's in +```php +$this->crud->removeButton($name); // remove a single button +$this->crud->removeButtons($names); // or multiple +``` + +- **removeButtonFromStack()** - remove a button from a particular stack +```php +$this->crud->removeButtonFromStack($name, $stack); +``` + +- **removeAllButtons()** - remove all buttons from any stack +```php +$this->crud->removeAllButtons(); +``` + +- **removeAllButtonsFromStack()** - remove all buttons from a particular stack +```php +$this->crud->removeAllButtonsFromStack($stack); +``` + + +#### Filters + +Manipulate what filters are shown in the table view. Check out [CRUD > Operations > ListEntries > Filters](/docs/{{version}}/crud-filters) to see examples of ```$filter_definition_array``` + +- **addFilter()** - add a filter to the list view +```php +$this->crud->addFilter($filter_definition_array, $values, $filter_logic); +``` + +- **modifyFilter()** - change the attributes of a filter +```php +$this->crud->modifyFilter($name, $modifs_array); +``` + +- **removeFilter()** - remove a certain filter from the list view +```php +$this->crud->removeFilter($name); +``` + +- **removeAllFilters()** - remove all filters from the list view +```php +$this->crud->removeAllFilters(); +``` + +- **filters()** - get all the registered filters for the list view +```php +$this->crud->filters(); +``` + + +#### Details Row + +Shows a ```+``` (plus sign) next to each table row, so that the user can expand that row and reveal details. You are responsible for creating the view with those details. + +- **enableDetailsRow()** - show the + sign in the table view +```php +$this->crud->enableDetailsRow(); +// NOTE: you also need to do allow access to the right users: +$this->crud->allowAccess('details_row'); +// NOTE: you also need to do overwrite the showDetailsRow($id) method in your EntityCrudController to show whatever you'd like in the details row OR overwrite the views/backpack/crud/details_row.blade.php +$this->crud->setDetailsRowView('your-view'); +``` + +- **disableDetailsRow()** - hide the + sign in the table view +```php +$this->crud->disableDetailsRow(); +``` + + +#### Export Buttons + +Please note it will only export the current _page_ of results. So in order to export all entries the user needs to make the current page show "All" entries from the top-left picker. + +- **enableExportButtons()** - Show export to PDF, CSV, XLS and Print buttons on the table view +```php +$this->crud->enableExportButtons(); +``` + + +#### Responsive Table + +- **disableResponsiveTable()** - stop the listEntries view from showing/hiding columns depending on viewport width +```php +$this->crud->disableResponsiveTable(); +``` + +- **enableResponsiveTable()** - make the listEntries view show/hide columns depending on viewport width +```php +$this->crud->enableResponsiveTable(); +``` + + +#### Persistent Table + +- **enablePersistentTable()** - make the listEntries remember the filters, search and pagination for a user, even if he leaves the page, for 2 hours +```php +$this->crud->enablePersistentTable(); +``` + +- **disablePersistentTable()** - stop the listEntries from remembering the filters, search and pagination for a user, even if he leaves the page +```php +$this->crud->disablePersistentTable(); +``` + + +#### Page Length + +- **setDefaultPageLength()** - change the number of items per page in the list view +```php +$this->crud->setDefaultPageLength(10); +``` + +- **setPageLengthMenu()** - change the entire page length menu in the list view +```php +$this->crud->setPageLengthMenu([100, 200, 300]); +``` + + +#### Actions Column + +- **setActionsColumnPriority()** - make the actions column (in the table view) hide when not enough space is available, by giving it an unreasonable priority +```php +$this->crud->setActionsColumnPriority(10000); +``` + + +#### Custom / Advanced Queries + +- **addClause()** - change what entries are shown in the table view; this allows _developers_ to forcibly change the query used by the table view, as opposed to filters, that allow _users_ to change the query with new inputs; +```php +$this->crud->addClause('active'); // apply local scope +$this->crud->addClause('type', 'car'); // apply local dynamic scope +$this->crud->addClause('where', 'name', '=', 'car'); +$this->crud->addClause('whereName', 'car'); +$this->crud->addClause('whereHas', 'posts', function($query) { + $query->activePosts(); + }); +``` + +- **groupBy()** - shorthand to add a **groupBy** clause to the query +```php +$this->crud->groupBy(); +``` + +- **limit()** - shorthand to add a **limit** clause to the query +```php +$this->crud->limit(); +``` + +- **orderBy()** - shorthand to add an **orderBy** clause to the query +```php +$this->crud->orderBy(); +``` +- **setQuery(Builder $query)** - replaces the query with the new provided query. +```php +$this->crud->setQuery(User::where('status', 'active')); +``` + + +### Show Operation + +Use [the same Columns API as for the ListEntries operation](#columns-api), but inside your ```show()``` method. + + +### Create & Update Operations + +Manipulate what fields are shown in the create / update forms. Check out [CRUD > Operations > Create & Update > Fields](/docs/{{version}}/crud-fields) in the docs to see examples of ```$field_definition_array```. + +**Note:** The call is being performed for the current operation. So it's important to pay attention _where_ you're calling fields. Most likely, you'll want to do this inside ```setupCreateOperation()``` or ```setupUpdateOperation()```. + +- **addField()** - add one field +```php +$this->crud->addField($field_definition_array); +$this->crud->addField('db_column_name'); // a lazy way to add fields: let the CRUD decide what field type it is and set it automatically, along with the field label +``` + +- **addFields()** - add multiple fields +```php +$this->crud->addFields($array_of_fields_definition_arrays); +``` + +- **modifyField()** - change the attributes of an existing field +```php +$this->crud->modifyField($name, $modifs_array); +``` + +- **removeField()** - remove a given field from the current operation +```php +$this->crud->removeField('name'); +``` + +- **removeFields()** - remove multiple fields from the current operation +```php +$this->crud->removeFields($array_of_names); +``` + +- **removeAllFields()** - remove all registered fields +```php +$this->crud->removeAllFields(); +``` +- **Chained - beforeField()** - add a field _before_ a given field +```php +$this->crud->addField()->beforeField('name'); +``` + +- **Chained - afterField()** - add a field _after_ a given field +```php +$this->crud->addField()->afterField('name'); +``` + +- **setRequiredFields()** - check the FormRequests used in this EntityCrudController for required fields, and add an asterisk to them in the create or edit forms +```php +$this->crud->setRequiredFields(StoreRequest::class); +``` + +- **setValidation()** - makes sure validation and authorization in the FormRequest you've passed is being performed; also uses that file to figure out asterisk to show in the forms (calls ```setRequiredFields()``` above): +```php +$this->crud->setValidation(ArticleRequest::class); +``` + + +### Reorder Operation + +Show a reorder button in the table view, next to Add. Provides an interface to reorder & nest elements, provided the ```parent_id```, ```lft```, ```rgt```, ```depth``` columns are in the database, and ```$fillable``` on the model. + +```php +$this->crud->set('reorder.label', 'name'); // which model attribute to use for labels +$this->crud->set('reorder.max_level', 3); // maximum nesting depth; this example will prevent the user from creating trees deeper than 3 levels; +``` + +- **disableReorder()** - disable the Reorder functionality +```php +$this->crud->disableReorder(); +``` + +- **isReorderEnabled()** - returns ```true```/```false``` if the Reorder operation is enabled or not +```php +$this->crud->isReorderEnabled(); +``` + + +### Revise Operation + +A.k.a. Audit Trail. Tracks all changes to an entry and provides an interface to revert to a previous state. This operation is not installed by default - please check out [Revise Operation](/docs/{{version}}/crud-operation-revisions) for the installation & usage steps. + + +## All Operations + +### Access + +Prevent or allow users from accessing different CRUD operations. + +- **allowAccess()** - give users access to one or multiple operations +```php +$this->crud->allowAccess('list'); +$this->crud->allowAccess(['list', 'create', 'delete']); +``` + +- **allowAccessOnlyTo()** - give users access only to one or some operations, denying the rest of them +```php +$this->crud->allowAccessOnlyTo('list'); +$this->crud->allowAccessOnlyTo(['list', 'create', 'delete']); +``` + +- **denyAccess()** - prevent users from accessing one or multiple operations +```php +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); +``` + +- **denyAllAccess()** - prevent users from accessing all operations (you may allow some operations then) +```php +$this->crud->denyAllAccess(); +``` + +- **hasAccess()** - check if the current user has access to one or multiple operations +```php +$this->crud->hasAccess('something'); // returns true/false +$this->crud->hasAccessOrFail('something'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false +``` + +### Eager Loading Relationships + +- **with()** - when the current entry is loaded (in any operation) also get its relationships, so that only one query is made to the database per entry +```php +$this->crud->with('relationship_name'); +``` + +### Custom Views + +- **setShowView()**, **setEditView()**, **setCreateView()**, **setListView()**, **setReorderView()**, **setRevisionsView()**, **setRevisionsTimelineView()**, **setDetailsRowView()** - set the view for a certain CRUD operation or feature + +```php +// use a custom view for a CRUD operation +$this->crud->setShowView('path.to.your.view'); +$this->crud->setEditView('path.to.your.view'); +$this->crud->setCreateView('path.to.your.view'); +$this->crud->setListView('path.to.your.view'); +$this->crud->setReorderView('path.to.your.view'); +$this->crud->setRevisionsView('path.to.your.view'); +$this->crud->setRevisionsTimelineView('path.to.your.view'); +$this->crud->setDetailsRowView('path.to.your.view'); + +// more generally, you can use the Settings API: +$this->crud->set('create.view', 'path.to.your.view'); + +// if you want to load something from the /resources/vendor/backpack/crud directory, you can do +$this->crud->set('create.view', 'crud::yourfolder.yourview'); +// or +$this->crud->set('create.view', 'resources.vendor.backpack.crud.yourfolder.yourview'); +``` + +### Content Class + +- **setShowContentClass()**, **setEditContentClass()**, **setCreateContentClass()**, **setListContentClass()**, **setReorderContentClass()**, **setRevisionsContentClass()**, **setRevisionsTimelineContentClass()** - set the CSS class for an operation view, to make the main area bigger or smaller: + +```php +// use a custom view for a CRUD operation +$this->crud->setShowContentClass('col-md-8'); +$this->crud->setEditContentClass('col-md-8'); +$this->crud->setCreateContentClass('col-md-8'); +$this->crud->setListContentClass('col-md-8'); +$this->crud->setReorderContentClass('col-md-8'); +$this->crud->setRevisionsContentClass('col-md-8'); +$this->crud->setRevisionsTimelineContentClass('col-md-8'); + +// more generally, you can use the Settings API: +$this->crud->set('create.contentClass', 'col-md-12'); +``` + +### Getters + +- **getEntry()** - get a certain entry of the current model type +```php +$this->crud->getEntry($entry_id); +``` +- **getEntries()** - get all entries using the current CRUD query +```php +$this->crud->getEntries(); +``` + +- **getFields()** - get all fields for a certain operation, or for both +```php +$this->crud->getFields('create/update/both'); +``` + +- **getCurrentEntry()** - get the current entry, for operations that work on a single entry +```php +$this->crud->getCurrentEntry(); +// ex: in your update() method, after calling parent::updateCrud() +``` + +### Operations + +- **getOperation()** - get the name of the operation that is currently being performed +```php +$this->crud->getOperation(); +``` + +- **setOperation()** - set the name of the operation that is currently being performed +```php +$this->crud->setOperation('ListEntries'); +``` + +### Actions + +An action is the controller method that is currently being run. + +- **getActionMethod()** - returns the method on the controller that was called by the route; ex: ```create()```, ```update()```, ```edit()``` etc; +```php +$this->crud->getActionMethod(); +``` + +- **actionIs()** - checks if the given controller method is the one called by the route +```php +$this->crud->actionIs('create'); +``` + +### Title, Heading, Subheading + +Legend: +- _operation_ - a collection of functions in a CrudController, that together allow the admin to perform something on the current model; +- _action_ - a method (aka function) of an operation; it is the actual PHP function's name; + +- **getTitle()** - get the Title for the create action +```php +$this->crud->getTitle('create'); +``` + +- **getHeading()** - get the Heading for the create action +```php +$this->crud->getHeading('create'); +``` + +- **getSubheading()** - get the Subheading for the create action +```php +$this->crud->getSubheading('create'); +``` + +- **setTitle()** - set the Title for the create action +```php +$this->crud->setTitle('some string', 'create'); +``` + +- **setHeading()** - set the Heading for the create action +```php +$this->crud->setHeading('some string', 'create'); +``` + +- **setSubheading()** - set the Subheading for the create action +```php +$this->crud->setSubheading('some string', 'create'); +``` + +### CrudPanel Basic Info + +- **setModel()** - set the Eloquent object that should be used for all operations +```php +$this->crud->setModel("App\Models\Example"); +``` + +- **setRoute()** - set the main route to this CRUD +```php +$this->crud->setRoute("admin/example"); +// OR $this->crud->setRouteName("admin.example"); +``` + +- **setEntityNameStrings()** - set how the entity name should be shown to the user, in singular and in plural +```php +$this->crud->setEntityNameStrings("example", "examples"); +``` diff --git a/7.x-dev/crud-basics.md b/7.x-dev/crud-basics.md new file mode 100644 index 00000000..8e231a8b --- /dev/null +++ b/7.x-dev/crud-basics.md @@ -0,0 +1,46 @@ +# Basics + +--- + +Backpack\CRUD provides a fast way to build administration panels - places where your administrators can Create, Read, Update, Delete entries for a specific Eloquent model. **One CRUD Panel provides functionality for one Eloquent Model.** + + +## Requirements + +In order to create a CRUD Panel, you'll need: +- **a table in the database** (and maybe connection tables for relationships); +- **an Eloquent Model** that points to that db table; + +If you don't already have the models, don't worry, Backpack also includes a faster way to generate database migrations and models. + + +## Architecture + +A Backpack CRUD Panel uses _the same elements_ you would have created for an administration panel, if you were doing it from scratch: +- a custom **route** - will be generated in ```routes/backpack/custom.php```; points to a controller; +- a custom **controller** - will be generated in ```app/Http/Controllers/Admin```; holds the logic for the all operations an admin can perform on that Eloquent model; +- a **request** file (optional) - will be generated in ```app/Http/Requests```; used to validate Create/Update forms; + +**The only differences** between building it from scratch and using Backpack\CRUD are that: +- your controller will be extending ```Backpack\CRUD\app\Http\Controllers\CrudController```, which allows you to easily add traits to handle the already-built operations: Create, Update, Delete, List, Show, Reorder, Revisions etc. +- your model will ```use \Backpack\CRUD\CrudTrait```; + +This simple architecture (```ProductCrudController extends CrudController```) means: +- **your CRUD Panel will not be a _black box_**; you can easily see the logic for each operation, by checking the methods on this controller, or the traits you'll be using; +- **you can _easily_ override what happens inside each operation**; +- **you can _easily_ create custom operations**; + +For example: +- want to change how a single ```Product``` is shown to the admin? just create a method called ```show()``` in your ```ProductCrudController```; simple OOP dictates that your method will be picked up, instead of the one in the `ShowOperation` trait; some goes for ```create()```, ```store()```, etc - you have complete control; +- want to create a new "Publish" operation on a ```Product```? your ```ProductCrudController``` is a great place for that logic; just create a custom ```publish()``` method and a route that points to it; + + +## Example Files + +For a ```Tag``` entity, your CRUD Panel would consist of: +- your existing model (```app/Models/Tag.php```); +- a route inside ```routes/backpack/custom.php```; +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); + +To further your understanding of how a CRUD Panel works, [read more about this example in the tutorial](/docs/{{version}}/crud-tutorial). diff --git a/7.x-dev/crud-buttons.md b/7.x-dev/crud-buttons.md new file mode 100644 index 00000000..b0b064c0 --- /dev/null +++ b/7.x-dev/crud-buttons.md @@ -0,0 +1,387 @@ +# Buttons + +--- + + +## About + +Buttons are used inside the [List operation](/docs/{{version}}/crud-operation-list-entries) and [Show operation](/docs/{{version}}/crud-operation-show), to allow the admin to trigger other operations. Some buttons point to entirely new routes (eg. `create`, `update`, `show`), others perform the operation on the current page using AJAX (eg. `delete`). + + +### Button Stacks + +The ShowList operation has 3 places where buttons can be placed: + - `top` (where the Add button is) + - `line` (where the Edit and Delete buttons are) + - `bottom` (after the table) + +![](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/backpack_buttons.png) + +When adding a button to the stack, you can choose whether to insert it at the `beginning` or `end` of the stack by specifying that as a last parameter. + + +### Default Buttons + +There are no "default buttons". But each operation can add buttons to other operations. Most commonly, operations add their own button to the List operation, since that's the "home page" for performing operations on entries. So if you go to a CRUD where you're using the most common operations (Create, Update, List, Show) you will notice in the List operation that: +- the `create` button in `top` stack; +- the `update`, `delete` and `show` buttons in the `line` stack; + +Most buttons are invisible if an operation has been disabled. For example, you can: +- hide the "delete" button using `CRUD::denyAccess('delete')`; +- show a "preview" button by using `CRUD::allowAccess('show')`; + + + +### Buttons API + +Here are a few things you can call in your EntityCrudController's `setupListOperation()` method, to manipulate buttons: + +```php +// possible stacks: 'top', 'line', 'bottom'; +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; + +// collection of all buttons +CRUD::buttons(); + +// add a button; possible types are: view, model_function +CRUD::addButton($stack, $name, $type, $content, $position); + +// add a button whose HTML is returned by a method in the CRUD model +CRUD::addButtonFromModelFunction($stack, $name, $model_function_name, $position); + +// add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +CRUD::addButtonFromView($stack, $name, $view, $position); + +// remove a button +CRUD::removeButton($name); + +// remove a button for a certain stack +CRUD::removeButtonFromStack($name, $stack); + +// remove multiple buttons +CRUD::removeButtons($names, $stack); + +// remove all buttons +CRUD::removeAllButtons(); + +// remove all buttons for a certain stack +CRUD::removeAllButtonsFromStack($stack); + +// order buttons in a stack, order is an array with the ordered names of the buttons +CRUD::orderButtons($stack, $order); + +// modify button, modifications are the attributes and their new values. +CRUD::modifyButton($name, $modifications); + +// Move the target button to the destination position, target and destion are the button names, where is 'before' or 'after' +CRUD::moveButton($target, $where, $destination); +``` + + +### Overriding a Button + +Before showing any buttons, Backpack will check your ```resources\views\vendor\backpack\crud\buttons``` directory, to see if you've overriden any buttons. If it finds a blade file with the same name there as the operation buttons, it will use your blade file, instead of the one in the package. + +That means **you can override an existing button simply by creating a blade file with the same name inside this directory**. + + +### Creating a Quick Button + +Most of the times, the buttons you want to create aren't complex at all. They're just an `` element, with a `href` and `class` that is show **if the admin has access** to that particular operation. That's why we've created the `quick.blade.php` button, that allows you to _quickly_ create a button, right from your Operation or CrudController. This covers most simple use cases: + +```php +// the following example will create a button for each entry in the table with: +// label: Email +// access: Email +// href: /entry/{id}/email +CRUD::button('email')->stack('line')->view('crud::buttons.quick'); + +// you can also add buttons on the "top" stack +CRUD::button('export')->stack('top')->view('crud::buttons.quick'); + +// if you need to control the access to "Email" per entry, you can do: +CRUD::setAccessCondition('Email', function ($entry) { + return $entry->hasVerifiedEmail(); +}); + +// or enable it for all entries: +CRUD::allowAccess('Email'); + +// directly in the button also works: +CRUD::button('email')->stack('line')->view('crud::buttons.quick')->meta([ + 'access' => true, +]); + +// you can easily customize Access, Name, Label, Icon in `meta` +// and even the attributes of the element in meta `wrapper` +CRUD::button('email')->stack('line')->view('crud::buttons.quick')->meta([ + 'access' => true, + 'label' => 'Email', + 'icon' => 'la la-envelope', + 'wrapper' => [ + 'element' => 'a', + 'href' => url('something'), + 'target' => '_blank', + 'title' => 'Send a new email to this user', + ] +]); + +// build custom URL using closure +CRUD::button('email')->stack('line')->view('crud::buttons.quick')->meta([ + 'wrapper' => [ + 'href' => function ($entry, $crud) { + return backpack_url("invoice/$entry->id/email"); + }, + ], +]); +``` + +> You should always control the access of your buttons. The key for access by default is the button name `->studly()` with a fallback to the button name without modifications. It means that for a button named `some_button`, the access key will be either `SomeButton` or `some_button`. Eg: `CRUD::allowAccess('some_button')` or `CRUD::allowAccess('SomeButton')`. + + +#### Create a Quick Button with Ajax + +Quick Buttons can be easily configured to make an AJAX request. This is useful when you want to perform an operation without leaving the page. For example, you can send an email to a user without leaving the page. + +```php +// enable ajax +CRUD::button('email')->stack('line')->view('crud::buttons.quick')->meta([ + 'label' => 'Email', + 'icon' => 'la la-envelope', + 'wrapper' => [ + 'href' => function ($entry, $crud) { + return backpack_url("invoice/$entry->id/email"); + }, + 'ajax' => true, // <- just add `ajax` and it's ready to make ajax request +]); + +// optional ajax configuration +'ajax' => [ + 'method' => 'POST', + 'refreshCrudTable' => false, // should the crud table be refreshed after a successful request ? + 'success_title' => "Payment Reminder Sent", // the title of the success notification + 'success_message' => 'The payment reminder has been sent successfully.', // the message of the success notification + 'error_title' => 'Error', // the title of the error notification + 'error_message' => 'There was an error sending the payment reminder. Please try again.', // the message of the error notification +], +``` + +You can overwrite the success/error messages by returning a `message` key from the response or providing the exception message. + +```php +public function email($id) +{ + CRUD::hasAccessOrFail('email'); + + $user = CRUD::getEntry($id); + + if($user->alreadyPaid()) { + return abort(400, 'The user has already paid.'); + } + + $user->schedulePaymentEmail(); + + return response()->json([ + 'message' => 'The payment reminder has been sent successfully.', + ]); + + // to return the default or field messages just return the response status without message: + // return reponse(''); + // return response('', 400); + // abort(400); + +} +``` + + +### Creating a Custom Button + +To create a completely custom button: +- run `php artisan backpack:button new-button-name` to create a new blade file in `resources\views\vendor\backpack\crud\buttons` +- add that button using the ```addButton()``` syntax, in the EntityCrudControllers you want, inside the ```setupListOperation()``` method; + +```php +// add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +CRUD::addButtonFromView($stack, $name, $view, $position); +``` + +In the blade file, you can use: +- `$entry` - the database entry you're showing (only inside the `line` stack); +- `$crud` - the entire CrudPanel object; +- `$button` - the button you're currently showing; +- `$meta['something']` - any custom attribute the developer has passed, using the `metas()` method; + +Note: If you've opted to add a button from a model function (not a blade file), inside your model function you can use `$this` to get the current entry (so for example, you can do `$this->id`. + + +## Examples + + +### Adding a Custom Button with a Blade File + +Let's say we want to create a simple ```moderate.blade.php``` button. This button would just open a ```user/{id}/moderate/``` route, which would point to ```UserCrudController::moderate()```. The steps would be: + +- Create the ```resources\views\vendor\backpack\crud\buttons\moderate.blade.php``` file: +```php +@if ($crud->hasAccess('update', $entry)) + Moderate +@endif +``` +- Add the new route, next to ```UserCrudController```'s route (most likely inside ```routes/backpack/custom.php```): +```php +Route::get('user/{id}/moderate', 'UserCrudController@moderate'); +``` + +- We can now add a ```moderate()``` method to our ```UserCrudController```, which would moderate the user, and redirect back. +```php +public function moderate() +{ + // show a form that does something +} +``` + +- Now we can actually add this button to any of ```UserCrudController::setupListOperation()```: +```php +CRUD::addButtonFromView('line', 'moderate', 'moderate', 'beginning'); +``` + + +### Adding a Custom Button without a Blade File + +Instead of creating a blade file for your button, you can use a function on your model to output the button's HTML. + +In your ```ArticleCrudController::setupListOperation()```: +```php +// add a button whose HTML is returned by a method in the CRUD model +CRUD::addButtonFromModelFunction('line', 'open_google', 'openGoogle', 'beginning'); +``` + +In your ```Article``` model: + +```php +public function openGoogle($crud = false) +{ + return ' Google it'; +} +``` + + + +### Adding a Custom Button with JavaScript to the "top" stack + +Let's say we want to create an ```import.blade.php``` button. For simplicity, this button would just run an AJAX call which handles everything, and shows a status report to the user through notification bubbles. + +The "top" buttons are not bound to any certain entry, like buttons from the "list" stack. They can only do general things. And if they do general things, it's _generally_ recommended that you move their JavaScript to the bottom of the page. You can easily do that with ```@push('after_scripts')```, because the Backpack default layout has an ```after_scripts``` stack. This way, you can make sure your JavaScript is moved at the bottom of the page, after all other JavaScript has been loaded (jQuery, DataTables, etc). Check out the example below. + +The steps would be: + +- Create the ```resources\views\vendor\backpack\crud\buttons\import.blade.php``` file: + +```php +@if ($crud->hasAccess('create')) + + Import {{ $crud->entity_name }} + +@endif + +@push('after_scripts') + +@endpush +``` +- Add the new route, next to ```UserCrudController```'s route (most likely inside ```routes/backpack/custom.php```): +```php +Route::get('user/import', 'UserCrudController@import'); +``` + +- We can now add a ```import()``` method to our ```UserCrudController```, which would import the users. +```php +public function import() +{ + // whatever you decide to do +} +``` + +- Now we can actually add this button to any of ```UserCrudController::setupListOperation()```: +```php +CRUD::addButtonFromView('top', 'import', 'import', 'end'); +``` + + +### Adding a Custom Button That Is Visible Only for Some Entries + +Let's say we want to create a simple ```approve.blade.php``` button. But not all entries can be approved. In that case, you will want your `approve` button to pass the `$entry` as a second parameter, when checking for access: +```php +// resources\views\vendor\backpack\crud\buttons\approve.blade.php + +@if ($crud->hasAccess('approve', $entry)) + Approve +@endif +``` + +Then in your ProductCrudController you can define the access to this `approve` operation per entry: + +```php +// allow or deny access depending on the entry +$this->crud->setAccessCondition('approve', function ($entry) { + return $entry->category !== 1 ? true : false; +}); +``` + +Similarly, you can define the access per user: + +```php +// allow or deny access depending on the user +$this->crud->setAccessCondition('approve', function ($entry) { + return backpack_user()->id == 1 ? true : false; +}); +``` + +### Reorder buttons + +The default order of line stack buttons is 'edit', 'delete'. Let's say you are using the `ShowOperation`, by default the preview button gets placed in the beggining of that stack, if you want to move it to the end of the stack you may use `orderButtons` or `moveButton`. + +```php +CRUD::orderButtons('line', ['update', 'delete', 'show']); +``` + +```php +CRUD::moveButton('show', 'after', 'delete'); +``` + + diff --git a/7.x-dev/crud-cheat-sheet.md b/7.x-dev/crud-cheat-sheet.md new file mode 100644 index 00000000..74a74cff --- /dev/null +++ b/7.x-dev/crud-cheat-sheet.md @@ -0,0 +1,401 @@ +# CRUD API Cheat Sheet + +--- + +Here are all the functions you will be using **inside your EntityCrudController's ```setup()``` method**, grouped by the operation you will most likely use them for. + +## Operations + + +### List + + +#### Columns + +Methods: column(), addColumn(), addColumns(), modifyColumn(), removeColumn(), removeColumns(), setColumnDetails(), setColumnsDetails(), setColumns(), beforeColumn(), afterColumn(), makeFirstColumn() + +```php +// Adding and configuring columns: +CRUD::column('name')->someOtherAttribute($value)->anotherAttribute($anotherValue); // add a column, at the end of the stack +CRUD::column($column_definition_array)->someChainedAttribute($value); // add a column, at the end of the stack + +// Changing columns +CRUD::column('target')->attributeName($attributeValue); // change column attribute value +CRUD::column('target')->remove(); // remove column +CRUD::columns('target')->forget('someAttribute'); + +// Reordering columns: +CRUD::column('target')->before('destination'); // move target column before destination column +CRUD::column('target')->after('destination'); // move target column after destination column +CRUD::column('target')->makeFirst(); +CRUD::column('target')->makeLast(); + +// Bulk actions on columns: +CRUD::addColumns([$column_definition_array, $another_column_definition_array]); // add multiple columns, at the end of the stack +CRUD::removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +CRUD::setColumns(['name', 'description']); // set the columns you want in the table view, as array of column names +CRUD::setColumns([$firstColumnDefinitionArray, $secondColumnDefinitionArray]); // set the columns to be exactly these +CRUD::setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); // change attributes for multiple columns at once +``` + + +#### Buttons + +Methods: button(), buttons(), addButton(), addButtonFromModelFunction(), addButtonFromView(), removeButton(), removeButtonFromStack(), removeButtons(), removeAllButtons(), removeAllButtonsFromStack(), modifyButton(), moveButton() + +```php +// possible stacks: 'top', 'line', 'bottom'; +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; +// possible types: 'view', 'model_function' + +// Adding buttons: +CRUD::button('name'); +CRUD::button('name')->stack('line')->position('end')->type('view')->content('path.to.blade.file'); + +CRUD::addButtonFromModelFunction($stack, $name, $model_function_name, $position); // its HTML is returned by a method in the CRUD model +CRUD::addButtonFromView($stack, $name, $view, $position); // its HTML is in a view placed at resources\views\vendor\backpack\crud\buttons + +// changing buttons +CRUD::button('name')->remove(); +CRUD::button('name')->remove(); +CRUD::button('name')->before('destination'); +CRUD::button('name')->after('destination'); +CRUD::button('name')->makeFirst(); +CRUD::button('name')->makeLast(); +CRUD::button('name')->forget('someAttribute'); + +// bulk actions on buttons +CRUD::buttons(); // get a collection of all buttons +CRUD::orderButtons($stack, $order); // order is an array with button names in the new order +CRUD::removeButtons($names, $stack); +CRUD::removeAllButtons(); +CRUD::removeAllButtonsFromStack($stack); + +// supported for backwards-compatibility: +CRUD::addButton($stack, $name, $type, $content, $position); +CRUD::modifyButton($name, $modifications); // modifications are the attributes and their new values +CRUD::moveButton($target, $where, $destination); // move the target button to the destination position, target and destion are the button names, where is 'before' or 'after' +CRUD::removeButton($name); +CRUD::removeButtonFromStack($name, $stack); +``` + + +#### Filters + +Methods: addFilter(), modifyFilter(), removeFilter(), removeAllFilters(), filters() + +```php +// Add filters +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->else(function ($value) { + $this->crud->addClause('where', 'active', '0'); + }); + +// alternative syntax, manually applied right away +CRUD::filter('active') + ->type('simple') + ->logic(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->fallbackLogic(function ($value) { + $this->crud->addClause('where', 'active', '0'); + })->apply(); + +// changing filters: +CRUD::filter('active')->remove(); +CRUD::filter('active')->forget('attributeName'); +CRUD::filter('active')->attributeName($newValue); + +// reordering filters +CRUD::filter('active')->before('destination'); +CRUD::filter('active')->after('destination'); +CRUD::filter('active')->makeFirst(); +CRUD::filter('active')->makeLast(); + +// bulk filter methods +CRUD::filters(); // gets all the filters +CRUD::removeAllFilters(); + +// other methods for backwards compatibility +// Note: check out CRUD > Features > Filters in the docs to see examples of $filter_definition_array +CRUD::addFilter($filter_definition_array, $values, $filter_logic); +CRUD::modifyFilter($name, $modifs_array); +CRUD::removeFilter($name); +``` + + +#### Details Row + +Methods: enableDetailsRow(), disableDetailsRow() + +```php +// Shows a + sign next to each table row, so that the user can expand that row and reveal details. You are responsible for creating the view with those details. +CRUD::enableDetailsRow(); +// NOTE: you also need to do allow access to the right users: CRUD::allowAccess('details_row'); +// NOTE: you also need to do overwrite the showDetailsRow($id) method in your EntityCrudController to show whatever you'd like in the details row OR overwrite the views/backpack/crud/details_row.blade.php + +CRUD::disableDetailsRow(); +``` + + +#### Export Buttons + +Methods: enableExportButtons() + +```php +// Show export to PDF, CSV, XLS and Print buttons on the table view. Please note it will only export the current _page_ of results. So in order to export all entries the user needs to make the current page show "All" entries from the top-left picker. +CRUD::enableExportButtons(); +``` + + +#### Responsive Table + +Methods: enableResponsiveTable(), disableResponsiveTable() + +```php +CRUD::disableResponsiveTable(); +CRUD::enableResponsiveTable(); +``` + + +#### Persistent Table + +Methods: enablePersistentTable(), disablePersistentTable() + +```php +CRUD::disablePersistentTable(); +CRUD::enablePersistentTable(); +``` + + +#### Page Length + +Methods: setDefaultPageLength(), setPageLengthMenu() + +```php +// you can define the default page length. If it does not exist we will add it to the pagination array. +CRUD::setDefaultPageLength(10); + +// you can configure the paginator shown to the user in various ways + +// values and labels, 1st array the values, 2nd array the labels: +CRUD::setPageLengthMenu([[100, 200, 300], ['one hundred', 'two hundred', 'three hundred']]); + +// values and labels in one array: +CRUD::setPageLengthMenu([100 => 'one hundred', 200 => 'two hundred', 300 => 'three hundred']); + +// only values, we will use the values as labels: +CRUD::setPageLengthMenu([100, 200, 300]); // OR +CRUD::setPageLengthMenu([[100, 200, 300]]); + +// only one option available: +CRUD::setPageLengthMenu(10); +``` + +NOTE: Do not use 0 as a key, if you want to represent "ALL" use -1 instead. + + +#### Actions Column + +Methods: setActionsColumnPriority() + +```php +// make the actions column (in the table view) hide when not enough space is available, by giving it an unreasonable priority +CRUD::setActionsColumnPriority(10000); +``` + + +#### Custom / Advanced Queries + +Methods: addClause(), groupBy(), limit(), orderBy() + +```php +// Change what entries are shown in the table view. +// This changes all queries on the table view, +// as opposed to filters, who only change it when that filter is applied. +CRUD::addClause('active'); // apply local scope +CRUD::addClause('type', 'car'); // apply local dynamic scope +CRUD::addClause('where', 'name', '=', 'car'); +CRUD::addClause('whereName', 'car'); +CRUD::addClause('whereHas', 'posts', function($query) { + $query->activePosts(); + }); +CRUD::groupBy(); +CRUD::limit(); + +CRUD::orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!CRUD::getRequest()->has('order')) {}"; that way, your custom order is applied ONLY IF the user hasn't forced another order (by clicking a column heading) +``` + + +### Show + +Use the same Columns API as for the ListEntries operation, but inside your ```show()``` method. + + +### Create & Update Operations + +Methods: field(), addField(), addFields(), modifyField(), modifyFields(), removeField(), removeFields(), removeAllFields(), beforeField(), afterField() + +```php +// Adding and configuring form fields: +CRUD::field($field_name)->someOtherAttribute($value)->anotherAttribute($another_value); // add a field to the form +CRUD::field($field_definition_array)->someChainedAttribute($value); // add a field to the form + +// Changing fields: +CRUD::field('name')->remove(); +CRUD::field('name')->attributeName($newValue); // change the value of a field attribute +CRUD::field('name')->size(6); // shorthand for changing the CSS classes on the field (will set col-md-6 so the max is 12) +CRUD::field('name')->forget('someAttribute'); + +// Reordering fields: +CRUD::field('name')->before('destination'); // will show this before the given field +CRUD::field('name')->after('destination'); // will show this after the given field +CRUD::field('name')->makeFirst(); +CRUD::field('name')->makeLast(); + +// Bulk actions on fields: +CRUD::addFields($array_of_fields_definition_arrays); +CRUD::removeFields($array_of_names); +CRUD::removeAllFields(); +``` + + +### Reorder + +Methods: enableReorder(), disableReorder(), isReorderEnabled() + +```php + protected function setupReorderOperation() + { + // model attribute to be shown on draggable items + CRUD::set('reorder.label', 'name'); + // maximum number of nesting allowed + CRUD::set('reorder.max_level', 2); + + // extras: + CRUD::disableReorder(); + CRUD::isReorderEnabled(); + } +``` + + +### Revisions + +```php +// ------------------------- +// REVISIONS aka Audit Trail +// ------------------------- +// Tracks all changes to an entry and provides an interface to revert to a previous state. +// +// IMPORTANT: You also need to use \Venturecraft\Revisionable\RevisionableTrait; +// Please check out: https://backpackforlaravel.com/docs/crud-operation-revisions +CRUD::allowAccess('revisions'); +``` + + +## All Operations + +Methods: allowAccess(), denyAccess(), hasAccess(), setAccessCondition(), hasAccessOrFail(), hasAccessToAll(), hasAccessToAny(), setShowView(), setEditView(), setCreateView(), setListView(), setReorderView(), setRevisionsView, setRevisionsTimelineView(), setDetailsRowView(), getEntry(), getFields(), getColumns(), getCurrentEntry(), getTitle(), setTitle(), getHeading(), setHeading(), getSubheading(), setSubheading(), + +```php +// ------ +// ACCESS +// ------ +// Prevent or allow users from accessing different CRUD operations. + +CRUD::allowAccess('list'); +CRUD::allowAccess(['list', 'create', 'delete']); +CRUD::denyAccess('list'); +CRUD::denyAccess(['list', 'create', 'delete']); +CRUD::setAccessCondition(['update', 'delete'], function ($entry) { + return backpack_user()->isSuperAdmin() ? true : false; +}); + +CRUD::hasAccess('add'); // returns true/false +CRUD::hasAccessOrFail('add'); // throws 403 error +CRUD::hasAccessToAll(['create', 'update']); // returns true/false +CRUD::hasAccessToAny(['create', 'update']); // returns true/false + +// ------------- +// EAGER LOADING +// ------------- + +// eager load a relationship +CRUD::with('relationship_name'); + +// ------------ +// CUSTOM VIEWS +// ------------ + +// use a custom view for a CRUD operation +CRUD::setShowView('your-view'); +CRUD::setEditView('your-view'); +CRUD::setCreateView('your-view'); +CRUD::setListView('your-view'); +CRUD::setReorderView('your-view'); +CRUD::setRevisionsView('your-view'); +CRUD::setRevisionsTimelineView('your-view'); +CRUD::setDetailsRowView('your-view'); + +// ------------- +// CONTENT CLASS +// ------------- + +// use a custom CSS class for the content of a CRUD operation +CRUD::setShowContentClass('col-md-12'); +CRUD::setEditContentClass('col-md-12'); +CRUD::setCreateContentClass('col-md-12'); +CRUD::setListContentClass('col-md-12'); +CRUD::setReorderContentClass('col-md-12'); +CRUD::setRevisionsContentClass('col-md-12'); +CRUD::setRevisionsTimelineContentClass('col-md-12'); + +// ------- +// GETTERS +// ------- + +CRUD::getEntry($entry_id); +CRUD::getEntries(); + +CRUD::getFields('create/update/both'); + +// in your update() method, after calling parent::updateCrud() +CRUD::getCurrentEntry(); + +// ------- +// OPERATIONS +// ------- + +CRUD::setOperation('list'); +CRUD::getOperation(); + +// ------- +// ACTIONS +// ------- + +CRUD::getActionMethod(); // returns the method on the controller that was called by the route; ex: create(), update(), edit() etc; +CRUD::actionIs('create'); // checks if the controller method given is the one called by the route + +CRUD::getTitle('create'); // get the Title for the create action +CRUD::getHeading('create'); // get the Heading for the create action +CRUD::getSubheading('create'); // get the Subheading for the create action + +CRUD::setTitle('some string', 'create'); // set the Title for the create action +CRUD::setHeading('some string', 'create'); // set the Heading for the create action +CRUD::setSubheading('some string', 'create'); // set the Subheading for the create action + +// --------------------------- +// CrudPanel Basic Information +// --------------------------- +CRUD::setModel("App\Models\Example"); +CRUD::setRoute("admin/example"); +// OR CRUD::setRouteName("admin.example"); +CRUD::setEntityNameStrings("example", "examples"); + +// check the FormRequests used in that EntityCrudController for required fields, and add an asterisk to them in the create/edit form +CRUD::setRequiredFields(StoreRequest::class, 'create'); +CRUD::setRequiredFields(UpdateRequest::class, 'edit'); +``` diff --git a/7.x-dev/crud-columns.md b/7.x-dev/crud-columns.md new file mode 100644 index 00000000..44b875fb --- /dev/null +++ b/7.x-dev/crud-columns.md @@ -0,0 +1,1781 @@ +# Columns + +--- + + +## About + +A column shows the information of an Eloquent attribute, in a user-friendly format. + +It's used inside default operations to: +- show a table cell in **ListEntries**; +- show an attribute value in **Show**; + +A column consists of only one file - a blade file with the same name as the column type (ex: ```text.blade.php```). Backpack provides you with [default column types](#default-column-types) for the common use cases, but you can easily [change how a default field type works](#overwriting-default-column-types), or [create an entirely new field type](#creating-a-custom-column-type). + + +### Mandatory Attributes + +When passing a column array, you need to specify at least these attributes: +```php +[ + 'name' => 'options', // the db column name (attribute name) + 'label' => "Options", // the human-readable label for it + 'type' => 'text' // the kind of column to show +], +``` + + +### Optional Attributes + +- [```searchLogic```](#custom-search-logic) +- [```orderLogic```](#custom-order-logic) +- [```orderable```](#custom-order-logic) +- [```wrapper```](#custom-wrapper-for-columns) +- [```visibleInTable```](#choose-where-columns-are-visible) +- [```visibleInModal```](#choose-where-columns-are-visible) +- [```visibleInExport```](#choose-where-columns-are-visible) +- [```visibleInShow```](#choose-where-columns-are-visible) +- [```priority```](#define-which-columns-to-hide-in-responsive-table) +- [```escaped```](#escape-column-output) + + +### Columns API + +Inside your ```setupListOperation()``` or ```setupShowOperation()``` method, there are a few calls you can make to configure or manipulate columns: + +```php +// add a column, at the end of the stack +$this->crud->addColumn($column_definition_array); + +// add multiple columns, at the end of the stack +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); + +// to change the same attribute across multiple columns you can wrap them in a `group` +// this will add the '$' prefix to both columns +CRUD::group( + CRUD::column('price'), + CRUD::column('discount') +)->prefix('$'); + +// remove a column from the stack +$this->crud->removeColumn('column_name'); + +// remove an array of columns from the stack +$this->crud->removeColumns(['column_name_1', 'column_name_2']); + +// change the attributes of a column +$this->crud->modifyColumn($name, $modifs_array); +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); + +// change the attributes of multiple columns +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +// forget what columns have been previously defined, only use these columns +$this->crud->setColumns([$column_definition_array, $another_column_definition_array]); + +// ------------------- +// New in Backpack 4.1 +// ------------------- +// add a column with this name +$this->crud->column('price'); + +// change the type and prefix attributes on the 'price' column +$this->crud->column('price')->type('number')->prefix('$'); +``` + +In addition, to manipulate the order columns are shown in, you can: + +```php +// add this column before a given column +$this->crud->addColumn('text')->beforeColumn('name'); + +// add this column after a given column +$this->crud->addColumn()->afterColumn('name'); + +// make this column the first one in the list +$this->crud->addColumn()->makeFirstColumn(); +``` + + +## FREE Column Types + + +### boolean + +Show Yes/No (or custom text) instead of 1/0. + +```php +[ + 'name' => 'name', + 'label' => 'Status', + 'type' => 'boolean', + // optionally override the Yes/No texts + // 'options' => [0 => 'Active', 1 => 'Inactive'] +], +``` + +
+ + +### check + +Show a favicon with a checked or unchecked box, depending on the given boolean. +```php +[ + 'name' => 'featured', // The db column name + 'label' => 'Featured', // Table column heading + 'type' => 'check' +], +``` + +
+ + +### checkbox + +Shows a checkbox (the form element), and inserts the js logic needed to select/deselect multiple entries. It is mostly used for [the Bulk Delete action](/docs/{{version}}/crud-operation-delete#delete-multiple-items-bulk-delete), and [custom bulk actions](/docs/{{version}}/crud-operations#creating-a-new-operation-with-a-bulk-action-no-interface). + +Shorthand: +```php +$this->crud->enableBulkActions(); +``` +(will also add an empty custom_html column) + +Verbose: +```php +$this->crud->addColumn([ + 'type' => 'checkbox', + 'name' => 'bulk_actions', + 'label' => ' ', + 'priority' => 1, + 'searchLogic' => false, + 'orderable' => false, + 'visibleInModal' => false, +])->makeFirstColumn(); +``` + +
+ + +### checklist + +The checklist column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the checklist field type: + +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'checklist', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model + // OPTIONALS + // 'limit' => 32, // Limit the number of characters shown + // 'escaped' => false, // echo using {!! !!} instead of {{ }}, in order to render HTML + // 'prefix' => 'Foo: ', + // 'suffix' => '(bar)', +], +``` + +
+ + +### checklist_dependency + +Show connected items selected via checklist_dependency field. It's definition is totally similar to the [checklist_dependency *field type*](/docs/{{version}}/crud-fields#checklist_dependency). + +```php +[ + 'label' => 'User Role Permissions', + 'type' => 'checklist_dependency', + 'name' => 'roles,permissions', + 'subfields' => [ + 'primary' => [ + 'name' => 'roles', // the method that defines the relationship in your Model + 'entity' => 'roles', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + ], + 'secondary' => [ + 'name' => 'permissions', // the method that defines the relationship in your Model + 'entity' => 'permissions', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + ], + ], +] +``` + +
+ + +### closure + +Show custom HTML based on a closure you specify in your EntityCrudController. + +```php +[ + 'name' => 'created_at', + 'label' => 'Created At', + 'type' => 'closure', + 'function' => function($entry) { + return 'Created on '.$entry->created_at; + } +], +``` + +> **DEPRECATED**: closure column will be removed in a future version of Backpack, since the same thing can now be achieved using any column (including the `text` column) and the `value` attribute - just pass the same closure to the `value` attribute of any column type. + +
+ + +### color + +Show color with hex code. + +```php +[ + 'name' => 'color', + 'type' => 'color', + 'label' => 'Color', + // OPTIONALS + // 'showColorHex' => false //show or hide hex code +] +``` + +
+ + +### custom_html + +Show the HTML that you provide in the page. You can optionally escape the text when displaying it on page, if you don't trust the value. + +```php +[ + 'name' => 'my_custom_html', + 'label' => 'Custom HTML', + 'type' => 'custom_html', + 'value' => 'Something', + + // OPTIONALS + // 'escaped' => true // echo using {{ }} instead of {!! !!} +], +``` + +> IMPORTANT As opposed to most other Backpack columns, the output of `custom_html` is **NOT escaped by default**. That means if the database value contains malicious JS, that JS might be run when the admin previews it. Make sure to purify the value of this column in an accessor on your Model. At a minimum, you can use `strip_tags()` (here's [an example](https://github.com/Laravel-Backpack/demo/commit/509c0bf0d8b9ee6a52c50f0d2caed65f1f986385)), but a lot better would be to use an [HTML Purifier package](https://github.com/mewebstudio/Purifier) (do that [manually](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1) or by casting the attribute to `CleanHtmlOutput::class`). + +
+ + +### date + + +The date column will show a localized date in the default date format (as specified in the ```config/backpack/ui.php``` file), whether the attribute is cast as date in the model or not. + +Note that the ```format``` attribute uses ISO date formatting parameters and not PHP ```date()``` formatters. See for more information. + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'date', + // 'format' => 'l j F Y', // use something else than the base.default_date_format config value +], +``` + +
+ + +### datetime + + +The date column will show a localized datetime in the default datetime format (as specified in the ```config/backpack/ui.php``` file), whether the attribute is cast as datetime in the model or not. + +Note that the ```format``` attribute uses ISO date formatting parameters and not PHP ```date()``` formatters. See for more information. + + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'datetime', + // 'format' => 'l j F Y H:i:s', // use something else than the base.default_datetime_format config value +], +``` + +
+ + +### email + +The email column will output the email address in the database (truncated to 254 characters if needed), with a ```mailto:``` link towards the full email. Its definition is: +```php +[ + 'name' => 'email', // The db column name + 'label' => 'Email Address', // Table column heading + 'type' => 'email', + // 'limit' => 500, // if you want to truncate the text to a different number of characters +], +``` + +
+ + +### enum + +The enum column will output the value of your database ENUM column or your PHP enum attribute. +```php +[ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum', +], +``` + +By default, in case it's a `BackedEnum` it will show the `value` of the enum (when casted), in `database` or `UnitEnum` it will show the the enum value without parsing the value. + +If you want to output something different than what your enum stores you have two options: +- For `database enums` you need to provide the `options` that translates the enums you store in database. +- For PHP enums you can provide the same `options` or provide a `enum_function` from the enum to gather the final result. + +```php +// for database enums +[ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum', + 'options' => [ + 'DRAFT' => 'Is draft', + 'PUBLISHED' => 'Is published' + ] +], + +// for PHP enums, given the following enum example + +enum StatusEnum +{ + case DRAFT; + case PUBLISHED; + + public function readableText(): string + { + return match ($this) { + StatusEnum::DRAFT => 'Is draft', + StatusEnum::PUBLISHED => 'Is published', + }; + } +} + +[ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum', + 'enum_function' => 'readableText', + 'enum_class' => 'App\Enums\StatusEnum' +], +``` + +
+ + +### hidden + +The text column will output the text value of a db column (or model attribute). Its definition is: + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'hidden', + // 'prefix' => 'Name: ', + // 'suffix' => '(user)', + // 'limit' => 120, // character limit; default is 50, +], +``` + +
+ + +### image + + +Show a thumbnail image. + +```php +[ + 'name' => 'profile_image', // The db column name + 'label' => 'Profile image', // Table column heading + 'type' => 'image', + // 'prefix' => 'folder/subfolder/', + // image from a different disk (like s3 bucket) + // 'disk' => 'disk-name', + // optional width/height if 25px is not ok with you + // 'height' => '30px', + // 'width' => '30px', +], +``` + +
+ + +### json + + +Display database stored JSON in a prettier way to your users. + +```php +[ + 'name' => 'my_json_column_name', + 'label' => 'JSON', + 'type' => 'json', + // 'escaped' => false, // echo using {!! !!} instead of {{ }}, in order to render HTML +], +``` + +
+ + +### model_function + + +The model_function column will output a function on your main model. Its definition is: +```php +[ + // run a function on the CRUD model and show its return value + 'name' => 'url', + 'label' => 'URL', // Table column heading + 'type' => 'model_function', + 'function_name' => 'getSlugWithLink', // the method in your Model + // 'function_parameters' => [$one, $two], // pass one/more parameters to that method + // 'limit' => 100, // Limit the number of characters shown + // 'escaped' => false, // echo using {!! !!} instead of {{ }}, in order to render HTML +], +``` +For this example, if your model would feature this method, it would return the link to that entity: +```php +public function getSlugWithLink() { + return ''.$this->slug.''; +} +``` + +
+ + +### model_function_attribute + + +If the function you're trying to use returns an object, not a string, you can use the model_function_attribute column, which will output the attribute on the function result. Its definition is: +```php +[ + 'name' => 'url', + 'label' => 'URL', // Table column heading + 'type' => 'model_function_attribute', + 'function_name' => 'getSlugWithLink', // the method in your Model + // 'function_parameters' => [$one, $two], // pass one/more parameters to that method + 'attribute' => 'route', + // 'limit' => 100, // Limit the number of characters shown + // 'escaped' => false, // echo using {!! !!} instead of {{ }}, in order to render HTML +], +``` + +
+ + +### month + +Show month and year with default format `MMMM Y`. You can also change to your format using `format` attribute. + +```php +[ + 'name' => 'month', + 'type' => 'month', + 'label' => 'Month', + //OPTIONAL + 'format' => 'MMMM Y' +], +``` + +
+ + +### multidimensional_array + + +Enumerate the values in a multidimensional array, stored in the db as JSON. + +```php +[ + 'name' => 'options', // The db column name + 'label' => 'Options', // Table column heading + 'type' => 'multidimensional_array', + 'visible_key' => 'name' // The key to the attribute you would like shown in the enumeration +], +``` + +
+ + +### number + + +The text column will just output the number value of a db column (or model attribute). Its definition is: +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'number', + // 'prefix' => '$', + // 'suffix' => ' EUR', + // 'decimals' => 2, + // 'dec_point' => ',', + // 'thousands_sep' => '.', + // decimals, dec_point and thousands_sep are used to format the number; + // for details on how they work check out PHP's number_format() method, they're passed directly to it; + // https://www.php.net/manual/en/function.number-format.php +], +``` + +
+ + +### password + +Show asterisk symbols `******` representing hidden value. + +```php +[ + 'name' => 'password', + 'label' => 'Password', + 'type' => 'password' + //'limit' => 4, // limit number of asterisk symbol +], +``` +
+ + +### phone + +The phone column will output the phone number from the database (truncated to 254 characters if needed), with a ```tel:``` link so that users on mobile can click them to call (or with Skype or similar browser extensions). Its definition is: +```php +[ + 'name' => 'phone', // The db column name + 'label' => 'Phone number', // Table column heading + 'type' => 'phone', + // 'limit' => 10, // if you want to truncate the phone number to a different number of characters +], +``` + +
+ + +### radio + + +Show a pretty text instead of the database value, according to an associative array. Usually used as a column for the "radio" field type. + +```php +[ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'radio', + 'options' => [ + 0 => 'Draft', + 1 => 'Published' + ] +], +``` + +This example will show: +- "Draft" when the value stored in the db is 0; +- "Published" when the value stored in the db is 1; + +
+ + +### range + +Show progress bar + +```php +[ + 'name' => 'range', + 'type' => 'range', + 'label' => 'Range', + //OPTIONALS + 'max' => 100,// change default max value + 'min' => 0, // change default min value + 'showMaxValue' => false, // show/hide max value + 'showValue' => false, // show only progress bar without value + 'progressColor' => 'bg-success', // change progress bar color using class + 'striped' => true, // set stripes to progress bar +] +``` + +
+ + +### relationship_count + +Shows the number of items that are related to the current entry, for a particular relationship. + +```php +[ + // relationship count + 'name' => 'tags', // name of relationship method in the model + 'type' => 'relationship_count', + 'label' => 'Tags', // Table column heading + // OPTIONAL + // 'suffix' => ' tags', // to show "123 tags" instead of "123 items" + + // if you need that column to be orderable in table, you need to manually provide the orderLogic + // 'orderable' => true, + // 'orderLogic' => function ($query, $column, $columnDirection) { + $query->orderBy('tags_count', $columnDirection); + }, +], +``` + +**Important Note:** This column will load ALL related items onto the page. Which is not a problem normally, for small tables. But if your related table has thousands or millions of entries, it will considerably slow down the page. For a much more performant option, with the same result, you can add a fake column to the results using Laravel's `withCount()` method, then use the `text` column to show that number. That will be a lot faster, and the end-result is identical from the user's perspective. For the same example above (number of tags) this is how it will look: +``` +$this->crud->query->withCount('tags'); // this will add a tags_count column to the results +$this->crud->addColumn([ + 'name' => 'tags_count', // name of relationship method in the model + 'type' => 'text', + 'label' => 'Tags', // Table column heading + 'suffix' => ' tags', // to show "123 tags" instead of "123" +]); +``` + +
+ + +### row_number + + +Show the row number (index). The number depends strictly on the result set (x records per page, pagination, search, filters, etc). It does not get any information from the database. It is not searchable. It is only useful to show the current row number. + +```php +$this->crud->addColumn([ + 'name' => 'row_number', + 'type' => 'row_number', + 'label' => '#', + 'orderable' => false, +])->makeFirstColumn(); +``` + +Notes: +- you can have a different ```name```; just make sure your model doesn't have that attribute; +- you can have a different label; +- you can place the column as second / third / etc if you remove ```makeFirstColumn()```; +- this column type allows the use of suffix/prefix just like the text column type; +- if upon placement you notice it always shows ```false``` then please note there have been changes in the ```search()``` method - you need to add another parameter to your ```getEntriesAsJsonForDatatables()``` call; + +
+ + +### select + +The select column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select *field type*: +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model + // OPTIONAL + // 'limit' => 32, // Limit the number of characters shown +], +``` + +
+ +### select_from_array + +Show a particular text depending on the value of the attribute. + +```php +[ + // select_from_array + 'name' => 'status', + 'label' => 'Status', + 'type' => 'select_from_array', + 'options' => ['draft' => 'Draft (invisible)', 'published' => 'Published (visible)'], +], +``` + +
+ + +### select_grouped + +The `select_grouped` column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select field type: + +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select_grouped', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model +], +``` + +
+ + +### select_multiple + +The select_multiple column will output a comma separated list of its connected entities. Used for relationships like hasMany() and belongsToMany(). Its name and definition is the same as the select_multiple field: +```php +[ + // n-n relationship (with pivot table) + 'label' => 'Tags', // Table column heading + 'type' => 'select_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + 'entity' => 'tags', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => 'App\Models\Tag', // foreign key model +], +``` + +
+ + +### summernote + +The summernote column will output the non-escaped text value of a db column (or model attribute). Its definition is: + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'summernote', +], +``` + +
+ + +### switch + +Show a favicon with a checked or unchecked box, depending on the given boolean. + +```php +[ + 'name' => 'featured', // The db column name + 'label' => 'Featured', // Table column heading + 'type' => 'switch' +], +``` + +
+ + +### text + +The text column will just output the text value of a db column (or model attribute). Its definition is: +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + // 'prefix' => 'Name: ', + // 'suffix' => '(user)', + // 'limit' => 120, // character limit; default is 50, +], +``` + +**Advanced use case:** The ```text``` column type can also show the attribute of a 1-1 relationship. If you have a relationship (like ```parent()```) set up in your Model, you can use relationship and attribute in the ```name```, using dot notation: +```php +[ + 'name' => 'parent.title', + 'label' => 'Title', + 'type' => 'text' +], +``` + +
+ + +### textarea +The text column will just output the text value of a db column (or model attribute) in a textarea field. Its definition is: +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + // 'prefix' => 'Name: ', + // 'suffix' => '(user)', + // 'limit' => 120, // character limit; default is 50 + // 'escaped' => false, // echo using {!! !!} instead of {{ }}, in order to render HTML +], +``` + +
+ + +### time + +Show time in 24-Hour format `H:mm` by default . You are also free to change the format. + +```php +[ + 'name' => 'time', // The db column name + 'label' => 'time', // Table column heading + 'type' => 'time', + // 'format' => 'H:mm', // use something else than the default. +], +``` + +
+ + +### upload + +Show link to the file which let's you open it in the new tab. + +```php +[ + 'name' => 'upload', + 'type' => 'upload', + 'label' => 'Upload', + 'disk' => 'uploads', +] +``` + +
+ + +### upload_multiple + +The ```upload_multiple``` column will output a list of files and links, when used on an attribute that stores a JSON array of file paths. It is meant to be used inside the show functionality (not list, though it also works there), to preview files uploaded with the ```upload_multiple``` field type. + +Its definition is very similar to the [upload_multiple *field type*](/docs/{{version}}/crud-fields#upload_multiple). + +```php +[ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + // 'disk' => 'public', // filesystem disk if you're using S3 or something custom +], +``` + +
+ + +### url + +Show a link which opens in the new tab by default. + +```php +[ + 'name' => 'url', + 'type' => 'url', + 'label' => 'URL', + //'target' => '_blank' // let's you change link target window. +], +``` + +
+ + +### view + +Display any custom column type you want. Usually used by Backpack package developers, to use views from within their packages, instead of having to publish the views. + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'view', + 'view' => 'package::columns.column_type_name', // or path to blade file +], +``` + +
+ + +### week + +Show the ISO-8601 week number of **year** (weeks starting on Monday). Example: `Week 25 2023` + +```php +[ + 'name' => 'week', + 'type' => 'week', + 'label' => 'Week', +], +``` + +
+ + +## PRO Column Types + + +### address_google PRO + +Show `value` attribute as text stored in the db column as JSON array created by `address_google` field. Example: Jaipur, India. + +```php +[ + 'name' => 'address_google', + 'type' => 'address_google', + 'label' => 'Address Google', +], +``` + +
+ + +### array PRO + +Enumerate an array stored in the db column as JSON. +```php +[ + 'name' => 'options', // The db column name + 'label' => 'Options', // Table column heading + 'type' => 'array' +], +``` + +
+ + +### array_count PRO + +Count the items in an array stored in the db column as JSON. + +```php +[ + 'name' => 'options', // The db column name + 'label' => 'Options', // Table column heading + 'type' => 'array_count', + // 'suffix' => 'options', // if you want it to show "2 options" instead of "2 items" +], +``` + +
+ + +### base64_image PRO + +Show a thumbnail image stored in the db column as `base64` image string. + +```php +[ + 'name' => 'profile_image', // The db column name + 'label' => 'Profile image', // Table column heading + 'type' => 'base64_image', + // optional width/height if 25px is not ok with you + // 'height' => '30px', + // 'width' => '30px', +], +``` + +
+ + +### browse PRO + +Show link to the selected file. + +```php +[ + 'name' => 'browse', + 'type' => 'browse', + 'label' => 'Browse', +], +``` + +
+ + +### browse_multiple PRO + +The ```browse_multiple``` column will output a list of files and links, when used on an attribute that stores a JSON array of file paths. It is meant to be used inside the show functionality (not list, though it also works there), to preview files uploaded with the ```browse_multiple``` field type. + +Its definition is very similar to the [browse_multiple *field type*](/docs/{{version}}/crud-fields#browse_multiple-pro). + +```php +[ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'browse_multiple', + // 'disk' => 'public', // filesystem disk if you're using S3 or something custom +], +``` + +
+ + +### ckeditor PRO + +The ckeditor column will just output the non-escaped text value of a db column (or model attribute). Its definition is: + +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'ckeditor', +], +``` + +
+ + +### date_picker PRO + +It's the same [date](#date-1) column with an alias, named after it's field name `date_picker`. + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'date', + // 'format' => 'l j F Y', // use something else than the base.default_date_format config value +], +``` + +
+ + +### datetime_picker PRO + +It's the same [datetime](#datetime-1) column with an alias, named after [datetime_picker *field type*](/docs/{{version}}/crud-fields#datetime_picker-pro).. + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'datetime', + // 'format' => 'l j F Y H:i:s', // use something else than the base.default_datetime_format config value +], +``` + +
+ + +### date_range PRO + +Show two date columns in a single column as a date range. Example: `18 Mar 2000 - 30 Nov 1985` + +Its definition is very similar to the [date_range *field type*](/docs/{{version}}/crud-fields#date_range-pro). + +```php +[ // Date_range + 'name' => 'start_date,end_date', // two columns with a comma + 'label' => 'Date Range', + 'type' => 'date_range', +] +``` + +
+ + +### dropzone PRO + +The ```dropzone``` column will output a list of files and links, when used on an attribute that stores a JSON array of file paths. It is meant to be used inside the show functionality (not list, though it also works there), to preview files uploaded with the ```dropzone``` field type. + +Its definition is very similar to the [dropzone *field type*](/docs/{{version}}/crud-fields#dropzone-pro). + + +```php +[ + 'name' => 'dropzone', // The db column name + 'label' => 'Images', // Table column heading + 'type' => 'dropzone', + // 'disk' => 'public', specify disk name +] +``` + +
+ + +### easymde PRO + +Convert easymde generated markdown string to HTML, using Illuminate\Mail\Markdown. Since Markdown is usually used for long texts, this column is most helpful in the "Show" operation - not so much in the "ListEntries" operation, where only short snippets make sense. + +It's the same [markdown](#markdown-pro) column with an alias, the field name. + +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'easymde', +], +``` + +
+ + +### icon_picker PRO + +Show the selected icon. Supported icon sets are fontawesome, lineawesome, glyphicon, ionicon, weathericon, mapicon, octicon, typicon, elusiveicon, materialdesign as per the jQuery plugin, [bootstrap-iconpicker](http://victor-valencia.github.io/bootstrap-iconpicker/). + +It's definition is totally similar to the [icon_picker *field type*](/docs/{{version}}/crud-fields#icon_picker-pro). + +```php +[ + 'name' => 'icon_picker', + 'type' => 'icon_picker', + 'label' => 'Icon Picker', + 'iconset' => 'fontawesome' // options: fontawesome, lineawesome, glyphicon, ionicon, weathericon, mapicon, octicon, typicon, elusiveicon, materialdesign +] +``` + +
+ + +### markdown PRO + + +Convert a markdown string to HTML, using ```Illuminate\Mail\Markdown```. Since Markdown is usually used for long texts, this column is most helpful in the "Show" operation - not so much in the "ListEntries" operation, where only short snippets make sense. + +```php +[ + 'name' => 'text', // The db column name + 'label' => 'Text', // Table column heading + 'type' => 'markdown', +], +``` + +> IMPORTANT As opposed to most other Backpack columns, the output of `markdown` is **NOT escaped by default**. That means if the database value contains malicious JS, that JS might be run when the admin previews it. Make sure to purify the value of this column in an accessor on your Model. At a minimum, you can use `strip_tags()` (here's [an example](https://github.com/Laravel-Backpack/demo/commit/509c0bf0d8b9ee6a52c50f0d2caed65f1f986385)), but a lot better would be to use an [HTML Purifier package](https://github.com/mewebstudio/Purifier) (do that [manually](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1) or by casting the attribute to `CleanHtmlOutput::class`). + +
+ + +### relationship PRO + +Output the related entries, no matter the relationship: +- 1-n relationships - outputs the name of its one connected entity; +- n-n relationships - enumerates the names of all its connected entities; + +Its name and definition is the same as for the relationship *field type*: +```php +[ + // any type of relationship + 'name' => 'tags', // name of relationship method in the model + 'type' => 'relationship', + 'label' => 'Tags', // Table column heading + // OPTIONAL + // 'entity' => 'tags', // the method that defines the relationship in your Model + // 'attribute' => 'name', // foreign key attribute that is shown to user + // 'model' => App\Models\Category::class, // foreign key model +], +``` + +Backpack tries to guess which attribute to show for the related item. Something that the end-user will recognize as unique. If it's something common like "name" or "title" it will guess it. If not, you can manually specify the ```attribute``` inside the column definition, or you can add ```public $identifiableAttribute = 'column_name';``` to your model, and Backpack will use that column as the one the user finds identifiable. It will use it here, and it will use it everywhere you haven't explicitly asked for a different attribute. + +
+ + +### repeatable PRO + +Show stored JSON in a table. It's definition is similar to the [repeatable *field type*](/docs/{{version}}/crud-fields#repeatable-pro). + +```php +[ + 'name' => 'features', + 'label' => 'Features', + 'type' => 'repeatable', + 'subfields' => [ + [ + 'name' => 'feature', + 'wrapper' => [ + 'class' => 'col-md-3', + ], + ], + [ + 'name' => 'value', + 'wrapper' => [ + 'class' => 'col-md-6', + ], + ], + [ + 'name' => 'quantity', + 'type' => 'number', + 'wrapper' => [ + 'class' => 'col-md-3', + ], + ], + ], +] +``` + +
+ + +### select2 PRO + +The select2 column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select field type: +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select2', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model +], +``` + +
+ + +### select2_multiple PRO + +The select2_multiple column will output a comma separated list of its connected entities. Used for relationships like hasMany() and belongsToMany(). Its name and definition is the same as the select2_multiple field: + +```php +[ + // n-n relationship (with pivot table) + 'label' => 'Tags', // Table column heading + 'type' => 'select2_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + 'entity' => 'tags', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => 'App\Models\Tag', // foreign key model +], +``` + +
+ + +### select2_nested PRO + +The select2_nested column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select field type: +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select2_nested', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model +], +``` +
+ + +### select2_grouped PRO + +The select2_grouped column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select field type: +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select2_grouped', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model +], +``` +
+ + +### select_and_order PRO + +Show selected values in the order they are saved. + +Its definition is very similar to the [select_and_order *field type*](/docs/{{version}}/crud-fields#select_and_order-pro). + +```php +[ + // select_from_array + 'name' => 'status', + 'label' => 'Status', + 'type' => 'select_and_order', + 'options' => ['draft' => 'Draft (invisible)', 'published' => 'Published (visible)'], +], +``` + +
+ + +### select2_from_array PRO + +Show a particular text depending on the value of the attribute. + +```php +[ + // select_from_array + 'name' => 'status', + 'label' => 'Status', + 'type' => 'select2_from_array', + 'options' => ['draft' => 'Draft (invisible)', 'published' => 'Published (visible)'], +], +``` + +
+ + +### select2_from_ajax PRO + +The select2_from_ajax column will output its connected entity. Used for relationships like hasOne() and belongsTo(). Its name and definition is the same as for the select field type: +```php +[ + // 1-n relationship + 'label' => 'Parent', // Table column heading + 'type' => 'select2_from_ajax', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "App\Models\Category", // foreign key model +], +``` + +
+ + +### select2_from_ajax_multiple PRO + +The select2_from_ajax_multiple column will output a comma separated list of its connected entities. Used for relationships like hasMany() and belongsToMany(). Its name and definition is the same as the select2_multiple field: + +```php +[ + // n-n relationship (with pivot table) + 'label' => 'Tags', // Table column heading + 'type' => 'select2_from_ajax_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + 'entity' => 'tags', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => 'App\Models\Tag', // foreign key model +], +``` + +
+ + +### slug PRO + +Show stored text value of slug. + +```php +[ + 'name' => 'slug', + 'type' => 'slug', + 'label' => 'Slug', +] +``` + +
+ + +### table PRO + + +The ```table``` column will output a condensed table, when used on an attribute that stores a JSON array or object. It is meant to be used inside the show functionality (not list, though it also works there). + +Its definition is very similar to the [table *field type*](/docs/{{version}}/crud-fields#table). + +```php +[ + 'name' => 'features', + 'label' => 'Features', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'description' => 'Description', + 'price' => 'Price', + 'obs' => 'Observations' + ] +], +``` + +
+ + +### tinymce PRO + +The tinymce column will just output the non-escaped text value of a db column (or model attribute). Its definition is: + +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'tinymce', +], +``` + +
+ + +### video PRO + + +Display a small screenshot for a YouTube or Vimeo video, stored in the database as JSON using the "video" field type. + +```php +[ + 'name' => 'name', // The db column name + 'label' => 'Tag Name', // Table column heading + 'type' => 'video', +], +``` + +
+ + +### wysiwyg PRO + +The wysiwyg column will just output the non-escaped text value of a db column (or model attribute). Its definition is: + +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'wysiwyg', +], +``` + +
+ + +## Overwriting Default Column Types + +You can overwrite a column type by placing a file with the same name in your ```resources\views\vendor\backpack\crud\columns``` directory. When a file is there, Backpack will pick that one up, instead of the one in the package. You can do that from command line using ```php artisan backpack:column --from=column-file-name``` + +Examples: +- creating a ```resources\views\vendor\backpack\crud\columns\number.blade.php``` file would overwrite the ```number``` column functionality; +- ```php artisan backpack:column --from=text``` will take the view from the package and copy it to the directory above, so you can edit it; + +>Keep in mind that when you're overwriting a default column type, you're forfeiting any future updates for that column. We can't push updates to a file that you're no longer using. + +
+ + +## Creating a Custom Column Type + +Columns consist of only one file - a blade file with the same name as the column type (ex: ```text.blade.php```). You can create one by placing a new blade file inside ```resources\views\vendor\backpack\crud\columns```. Be careful to choose a distinctive name, otherwise you might be overwriting a default column type (see above). + +For example, you can create a ```markdown.blade.php```: +```php +{!! \Markdown::convertToHtml($entry->{$column['name']}) !!} +``` + +The most useful variables you'll have in this file here are: +- ```$entry``` - the database entry you're showing (Eloquent object); +- ```$crud``` - the entire CrudPanel object, with settings, options and variables; + +By default, custom columns are not searchable. In order to make your column searchable you need to [specify a custom ```searchLogic``` in your declaration](#custom-search-logic). + + +
+ + +## Advanced Columns Use + + +### Custom Search Logic for Columns + +If your column points to something atypical (not a value that is stored as plain text in the database column, maybe a model function, or a JSON, or something else), you might find that the search doesn't work for that column. You can choose which columns are searchable, and what those columns actually search, by using the column's ```searchLogic``` attribute: + +```php +// column with custom search logic +$this->crud->addColumn([ + 'name' => 'slug_or_title', + 'label' => 'Title', + 'searchLogic' => function ($query, $column, $searchTerm) { + $query->orWhere('title', 'like', '%'.$searchTerm.'%'); + } +]); + + +// 1-n relationship column with custom search logic +$this->crud->addColumn([ + 'label' => 'Cruise Ship', + 'type' => 'select', + 'name' => 'cruise_ship_id', + 'entity' => 'cruise_ship', + 'attribute' => 'cruise_ship_name_date', // combined name & date column + 'model' => 'App\Models\CruiseShip', + 'searchLogic' => function ($query, $column, $searchTerm) { + $query->orWhereHas('cruise_ship', function ($q) use ($column, $searchTerm) { + $q->where('name', 'like', '%'.$searchTerm.'%') + ->orWhereDate('depart_at', '=', date($searchTerm)); + }); + } +]); + + +// column that doesn't need to be searchable +$this->crud->addColumn([ + 'name' => 'slug_or_title', + 'label' => 'Title', + 'searchLogic' => false +]); + +// column whose search logic should behave like it were a 'text' column type +$this->crud->addColumn([ + 'name' => 'slug_or_title', + 'label' => 'Title', + 'searchLogic' => 'text' +]); +``` + + +### Custom Order Logic for Columns + +If your column points to something atypical (not a value that is stored as plain text in the database column, maybe a model function, or a JSON, or something else), you might find that the ordering doesn't work for that column. You can choose which columns are orderable, and how those columns actually get ordered, by using the column's ```orderLogic``` attribute. + +For example, to order Articles not by its Category ID (as default, but by the Category Name), you can do: + +```php +$this->crud->addColumn([ + // Select + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category_id', // the db column for the foreign key + 'entity' => 'category', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'orderable' => true, + 'orderLogic' => function ($query, $column, $columnDirection) { + return $query->leftJoin('categories', 'categories.id', '=', 'articles.select') + ->orderBy('categories.name', $columnDirection)->select('articles.*'); + } +]); +``` + + + +### Wrap Column Text in an HTML Element + +Sometimes the text that the column echoes is not enough. You want to add interactivity to it, by adding a link to that column. Or you want to show the value in a green/yellow/red badge so it stands out. You can do both of that - with the ```wrapper``` attribute, which most columns support. + +```php +$this->crud->column([ + // Select + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category_id', // the db column for the foreign key + 'entity' => 'category', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'wrapper' => [ + // 'element' => 'a', // the element will default to "a" so you can skip it here + 'href' => function ($crud, $column, $entry, $related_key) { + return backpack_url('article/'.$related_key.'/show'); + }, + // 'target' => '_blank', + // 'class' => 'some-class', + ], +]); +``` + +If you specify ```wrapper``` to a column, the entries in that column will be wrapped in the element you specify. Note that: +- To get an HTML anchor (a link), you can specify ```a``` for the element (but that's also the default); to get a paragraph you'd specify ```p``` for the element; to get an inline element you'd specify ```span``` for the element; etc; +- Anything you declare in the ```wrapper``` array (other than ```element```) will be used as HTML attributes for that element (ex: ```class```, ```style```, ```target``` etc); +- Each wrapper attribute, including the element itself, can be declared as a `string` OR as a `callback`; + +Let's take another example, and wrap a boolean column into a green/red span: + +```php +$this->crud->column([ + 'name' => 'published', + 'label' => 'Published', + 'type' => 'boolean', + 'options' => [0 => 'No', 1 => 'Yes'], // optional + 'wrapper' => [ + 'element' => 'span', + 'class' => function ($crud, $column, $entry, $related_key) { + if ($column['text'] == 'Yes') { + return 'badge badge-success'; + } + + return 'badge badge-default'; + }, + ], +]); +``` + + + +### Link Column To Route + +To make a column link to a route URL, you can use the `linkTo($routeNameOrClosure, $parameters = [])` helper. Behind the scenes, this helper will use the `wrapper` helper to set up a link towards the route you want. See the section above for details on the `wrapper` helper. + +It's dead-simple to use the `linkTo()` helper to point to a route name: +```php +// you can do: +$this->crud->column('category')->linkTo('category.show'); + +// instead of: +$this->crud->column('category')->wrapper([ + 'href' => function ($crud, $column, $entry, $related_key) { + return backpack_url('category/'.$related_key.'/show'); + }, +]); + +// or as a closure shortcut: +$this->crud->column('category')->linkTo(fn($entry, $related_key) => backpack_url('category/'.$related_key.'/show')); +``` + +You can also link to non-related urls, as long as the route has a name. + +```php +$this->crud->column('my_column')->linkTo('my.route.name'); + +// you can also add additional parameters in your urls +$this->crud->column('my_column')->linkTo('my.route.name', ['myParameter' => 'value']); + +// you can use the closure in the parameters too +$this->crud->column('my_column') + ->linkTo('my.route.name', [ + 'myParameter' => fn($entry, $related_key) => $entry->something ? 'value' : $related_key ?? 'fallback_value', + ]); + +// array syntax is also supported +$this->crud->column([ + 'name' => 'category', + // simple route name + 'linkTo' => 'category.show', + + // alternatively with additional parameters + 'linkTo' => [ + 'route' => 'category.show', + 'parameters' => ['myParameter' => 'value'], + ], + + // or as closure + 'linkTo' => fn($entry, $related_key) => route('category.show', ['id' => $related_key]), +]); +``` + +If you want to have it simple and just link to the show route, you can use the `linkToShow()` helper. +It's just a shortcut for `linkTo('entity.show')`. + +```php +$this->crud->column('category') + ->linkToShow(); +``` + +If you want to open the link in a new tab, you can use the `linkTarget()` helper. + +```php +$this->crud->column('category') + ->linkToShow() + ->linkTarget('_blank'); +``` + +For more complex use-cases, we recommend you use the `wrapper` attribute directly. It accepts an array of HTML attributes which will be applied to the column text. You can also use callbacks to generate the attributes dynamically. + + + +### Choose Where Columns are Visible + +Starting with Backpack\CRUD 3.5.0, you can choose to show/hide columns in different contexts. You can pass ```true``` / ```false``` to the column attributes below, and Backpack will know to show the column or not, in different contexts: + +```php +$this->crud->addColumn([ + 'name' => 'description', + 'visibleInTable' => false, // no point, since it's a large text + 'visibleInModal' => false, // would make the modal too big + 'visibleInExport' => false, // not important enough + 'visibleInShow' => true, // boolean or closure - function($entry) { return $entry->isAdmin(); } +]); +``` + +This also allows you to do tricky things like: +- add a column that's hidden from the table view, but WILL get exported; +- adding a column that's hidden everywhere, but searchable (even with a custom ```searchLogic```); + + +### Multiple Columns With the Same Name + +Starting with Backpack\CRUD 3.3 (Nov 2017), you can have multiple columns with the same name, by specifying a unique ```key``` property. So if you want to use the same column name twice, you can do that. Notice below we have the same name for both columns, but one of them has a ```key```. This additional key will be used as an array key, if provided. + +```php +// column that shows the parent's first name +$this->crud->addColumn([ + 'label' => 'Parent First Name', // Table column heading + 'type' => 'select', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'first_name', // foreign key attribute that is shown to user + 'model' => 'App\Models\User', // foreign key model +]); + +// column that shows the parent's last name +$this->crud->addColumn([ + 'label' => 'Parent Last Name', // Table column heading + 'type' => 'select', + 'name' => 'parent_id', // the column that contains the ID of that connected entity; + 'key' => 'parent_last_name', // the column that contains the ID of that connected entity; + 'entity' => 'parent', // the method that defines the relationship in your Model + 'attribute' => 'last_name', // foreign key attribute that is shown to user + 'model' => 'App\Models\User', // foreign key model +]); +``` + + +### Escape column output + +For security purposes, Backpack escapes the output of all column types except for `markdown` and `custom_html` (those columns would be useless escaped). That means it uses `{{ }}` to echo the output, not `{!! !!}`. If you have any HTML inside a db column, it will be shown as HTML instead of interpreted. It does that because, if the value was added by a malicious user (not admin), it could contain malicious JS code. + +However, if you trust that a certain column contains _safe_ HTML, you can disable this behaviour by setting the `escaped` attribute to `false`. + +Our recommendation, in order to trust the output of a column, is to either: +- (a) only allow the admin to add/edit that column; +- (b) purify the value in an accessor on the Model, so that every time you get it, it's cleaned; you can use an [HTML Purifier package](https://github.com/mewebstudio/Purifier) for that (do it [manually](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1) or by casting the attribute to `CleanHtmlOutput::class`); + + +### Define which columns to show or hide in the responsive table + +By default, DataTables-responsive will try his best to show: +- **the first column** (since that usually is the most important for the user, plus it holds the modal button and the details_row button so it's crucial for usability); +- **the last column** (the actions column, where the action buttons reside); + +When giving priorities, lower is better. So a column with priority 4 will be hidden BEFORE a column with priority 2. The first and last columns have a priority of 1. You can define a different priority for a column using the ```priority``` attribute. For example: + +```php +$this->crud->addColumn([ + 'name' => 'details', + 'type' => 'text', + 'label' => 'Details', + 'priority' => 2, +]); +$this->crud->addColumn([ + 'name' => 'obs', + 'type' => 'text', + 'label' => 'Observations', + 'priority' => 3, +]); +``` +In the example above, depending on how much space it's got in the viewport, DataTables will first hide the ```obs``` column, then ```details```, then the last column, then the first column. + +You can make the last column be less important (and hide) by giving it an unreasonable priority: + +```php +$this->crud->setActionsColumnPriority(10000); +``` + +>Note that responsive tables adopt special behavior if the table is not able to show all columns. This includes displaying a vertical ellipsis to the left of the row, and making the row clickable to reveal more detail. This behavior is automatic and is not manually controllable via a field property. + + +### Adding new methods to the CrudColumn class + +You can add your own methods Backpack CRUD columns, so that you can do `CRUD::column('name')->customThing()`. You can easily do that, because the `CrudColumn` class is Macroable. It's as easy as this: + +```php +use Backpack\CRUD\app\Library\CrudPanel\CrudColumn; + +// register media upload macros on CRUD columns +if (! CrudColumn::hasMacro('customThing')) { + CrudColumn::macro('customThing', function ($firstParamExample = [], $secondParamExample = null) { + /** @var CrudColumn $this */ + + // TODO: do anything you want to $this + + return $this; + }); +} +``` + +A good place to do this would be in your AppServiceProvider, in a custom service provider. That way you have it across all your CRUD panels. diff --git a/7.x-dev/crud-fields-javascript-api.md b/7.x-dev/crud-fields-javascript-api.md new file mode 100644 index 00000000..91113898 --- /dev/null +++ b/7.x-dev/crud-fields-javascript-api.md @@ -0,0 +1,308 @@ +# CrudField JavaScript Library + +--- + + +## About + +If you need to add custom interactions (if field is X then do Y), we have just the thing for you. You can easily add custom interactions, using our **CrudField JavaScript Library**. It's already loaded on our Create / Update pages, in the global `crud` object, and it makes it dead-simple to select a field - `crud.field('title')` - using a syntax that's very familiar to our PHP syntax, then do the most common things on it. + + +## Syntax + +Here's everything our **CrudField JavaScript Library** provides: +- selectors: + - `crud.field('title')` -> returns the `CrudField` object for a field with the name `title`; + - `crud.field('testimonials').subfield('text')` -> returns the `CrudField` for the `text` subfield within the `testimonials` repeatable field; +- properties on `CrudField`: + - `.name` - returns the field name (eg. `title`); + - `.type` - returns the field type (eg. `text`); + - `.input` - returns the DOM element that actually holds the value (the input/textarea/select); + - `.value` - returns the value of that field (as a `string`); +- events on `CrudField`: + - `.onChange(function(field) { do_someting(); })` - calls that function every time the field changes (which for most fields is upon each keytype); +- methods on `CrudField`: + - `.hide()` - hides that field; + - `.show()` - shows that field, if it was previously hidden; + - `.disable()` - makes that field's input `disabled`; + - `.enable()` - removes `disabled` from that field's input; + - `.require()` - adds an asterisk next to that field's label; + - `.unrequire()` - removes any asterisk next to that field's label; + - `.change()` - trigger the change event (useful for a default state on pageload); +- specialty methods on `CrudField`: + - `.check()` - if the field is a checkbox, checks it; + - `.uncheck()` - if the field is a checkbox, unchecks it; +- current action: + - `crud.action` -> returns the current action ("create" or "edit") + +The beauty of this solution is that... it's flexible. Since it's only a JS library that makes the most difficult things easy... there is _no limit_ to what you can do with it. Just write pure JS or jQuery on top of it, to achieve your business logic. + + +## How to Use + +### Step by Step + +**Step 1.** Create a file to hold the custom JS. As a convention, we recommend you split up the JS by entity name. For example, for a Product we recommend creating `public/assets/js/admin/forms/product.js`. + +**Step 2.** Load that script file in your CrudController, within `setupCreateOperation()` or `setupUpdateOperation()`, depending on when you want it loaded: + +```php + Widget::add()->type('script')->content('assets/js/admin/forms/product.js'); +``` + +or + +```php + Widget::add()->type('script')->content(asset('assets/js/admin/forms/product.js')); +``` + +**Step 3.** Inside that JS file, use the CrudField JS Library to manipulate fields, in any way you want. For example: + +```javascript + crud.field('agree_to_terms').onChange(function(field) { + if (field.value == 1) { + crud.field('agree_to_marketing_email').show(); + } else { + crud.field('agree_to_marketing_email').hide(); + } + }).change(); +``` + +Alternatively, since all action methods also accept a `boolean` as a parameter, the above can also become: + +```javascript + crud.field('agree_to_terms').onChange(function(field) { + crud.field('agree_to_marketing_email').show(field.value == 1); + }).change(); +``` + +Notice that we did three things here: +- selected a field using `crud.field('agree_to_terms')`; +- defined what happens when that field gets changed, using `.onChange()`; +- triggered the change event using `.change()` - that way, the closure will get evaluated first thing when the pageloads, not only when the first field actually gets changed; + + + +### Examples + +We've identified the most common ways developers need to add interaction to their forms. Then we've documented them below. That way, it's easy for you to just copy-paste a solution, then customize to your needs. You can also see these examples fully working, in [our demo](https://demo.backpackforlaravel.com/admin/field-monster/create). + + +#### (1) Show / hide a field + +When a checkbox is checked, show a second field: + +```javascript +crud.field('visible').onChange(function(field) { + crud.field('visible_where').show(field.value == 1); +}).change(); +``` +![Scenario 1 - when a checkbox is checked, show a second field](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_01.gif) + + +#### (2) Show/hide and enable/disable a field + +When a checkbox is checked, show a second field _and_ un-disable it, by chaining the action methods: + +```javascript +crud.field('visible').onChange(function(field) { + crud.field('displayed_where').show(field.value == 1).enable(field.value == 1); +}).change(); +``` + +Alternatively, a more readable but verbose version: + +```javascript +crud.field('visible').onChange(function(field) { + if (field.value == 1) { + crud.field('displayed_where').show().enable(); + } else { + crud.field('displayed_where').hide().disable(); + } +}).change(); +``` + + +#### (3) When a radio option is selected, show a second field + +When a radio has something specific selected, show a second field: + +```javascript +crud.field('type').onChange(function(field) { + crud.field('custom_type').show(field.value == 3); +}).change(); +``` + +![Scenario 3 - when a radio option is selected, show a second field](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_03.gif) + + +#### (4) When a select has a specific value, show a second field + +When a select has something specific selected, show a second field: + +```javascript +crud.field('parent').onChange(function(field) { + crud.field('custom_parent').show(field.value == 6); +}).change(); +``` + +![Scenario 4 - when a select has a specific value, show a second field](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_04.gif) + + +#### (5) When a checkbox is checked and has a certain value, do something + +```javascript +function do_something() { + console.log('Displayed AND custom parent.'); +} + +crud.field('parent').onChange(field => { + if (field.value === 6 && crud.field('displayed').value == 1) { + do_something(); + } +}); +``` + + +#### (6) When a checkbox is checked or a select has a certain value, show a third field + +```javascript +let do_something_else = () => { + console.log('Displayed OR custom parent.'); +} + +crud.field('displayed').onChange(field => { + if (field.value === 1 || crud.field('parent').value == 6) { + do_something_else(); + } +}); + +crud.field('parent').onChange(field => { + if (field.value === 6 || crud.field('displayed').value == 1) { + do_something_else(); + } +}); +``` + + +#### (7) When a select is a certain value, do something, otherwise do something else + +```javascript +crud.field('parent').onChange(function(field) { + switch(field.value) { + case 2: + console.log('doing something'); + break; + case 3: + console.log('doing something else'); + break; + default: + console.log('not doing anything'); + } +}); +``` + + +#### (8) When a checkbox is checked, automatically check another one + +When a checkbox is checked, automatically check a different checkbox or radio: + +```javascript +crud.field('visible').onChange(field => { + crud.field('displayed').check(field.value == 1); +}); +``` + +![Scenario 8 - when a checkbox is checked, automatically check another one](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_08.gif) + + +#### (9) When a text input is written into, write in a second input (eg. slug) + +Create a slugged version of an input and put it into a second input: + +```javascript +let slugify = text => + text.toString().toLowerCase().trim() + .normalize('NFD') // separate accent from letter + .replace(/[\u0300-\u036f]/g, '') // remove all separated accents + .replace(/\s+/g, '-') // replace spaces with - + .replace(/[^\w\-]+/g, '') // remove all non-word chars + .replace(/\-\-+/g, '-') // replace multiple '-' with single '-' + +crud.field('title').onChange(field => { + crud.field('slug').input.value = slugify(field.value); +}); +``` + +![Scenario 9 - when a text input is written into, write in a second input - eg. slug](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_09.gif) + + +#### (10) Calculate a total of multiple fields + +When multiple inputs change, change a last input to calculate the total (or average, or difference): + +```javascript +// Notice that we have to convert the input values from STRING to NUMBER +function calculate_discount_percentage() { + let full_price = Number(crud.field('full_price').value); + let discounted_price = Number(crud.field('discounted_price').value); + let discount_percentage = (full_price - discounted_price) * 100 / full_price; + + crud.field('discount_percentage').input.value = discount_percentage; +} + +crud.fields(['full_price', 'discounted_price']).forEach(function(field) { + field.onChange(calculate_discount_percentage); +}); +``` + +![Scenario 10 - update a total field](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_10.gif) + + +#### (11) When a repeatable subfield changes, disable another subfield + +When a select subfield has been selected, enable a second subfield: + +```javascript +crud.field('wish').subfield('country').onChange(function(field) { + crud.field('wish').subfield('body', field.rowNumber).enable(field.value == ''); + }); +``` + +![Scenario 11 - when a repeatable subfield changed, disable another subfield](https://backpackforlaravel.com/uploads/docs-5-0/fields/js-api/scenario_11.gif) + + + +#### (12) When a checkbox is checked, hide repeatable and disable all subfields + +When a checkbox is checked, disable all subfields in a repeatable and hide the repetable field entirely: + +```javascript +crud.field('visible').onChange(field => { + var subfields = $(crud.field('wish').input).parent().find('[data-repeatable-holder]').data('subfield-names'); + + // disable/enable all subfields + subfields.forEach(element => { + crud.field('wish').subfield(element).enable(field.value == 1); + }); + + // hide/show the repeatable entirely + crud.field('wish').show(field.value == 1); +}).change(); +// this last change() call makes the code above also run on pageload, +// so that if the checkbox starts checked, it's visible, +// if the checkbox starts unchecked, it's hidden +``` + + +#### (13) When a morphable_type is selected, show another field + +When using the `relationship` field on a morph relation, we can target the ID or TYPE inputs using brackets (the are _not_ subfields): + +```javascript +crud.field('commentable[commentable_type]').onChange(field => { + // hide/show the other input + crud.field('wish').show(field.value == 'article'); +}).change(); +// this last change() call makes the code above also run on pageload +``` diff --git a/7.x-dev/crud-fields.md b/7.x-dev/crud-fields.md new file mode 100644 index 00000000..082edb94 --- /dev/null +++ b/7.x-dev/crud-fields.md @@ -0,0 +1,2865 @@ +# Fields + +--- + + +## About + +Field types define how the admin can manipulate an entry's values. They're used by the Create and Update operations. + +Think of the field type as the type of input: ``````. But for most entities, you won't just need text inputs - you'll need datepickers, upload buttons, 1-n relationship, n-n relationships, textareas, etc. + +We have a lot of default field types, detailed below. If you don't find what you're looking for, you can [create a custom field type](/docs/{{version}}/crud-fields#creating-a-custom-field-type). Or if you just want to tweak a default field type a little bit, you can [overwrite default field types](/docs/{{version}}/crud-fields#overwriting-default-field-types). + +> NOTE: If the _field name_ is the exact same as a relation method in the model, Backpack will assume you're adding a field for that relationship and infer relation attributes from it. To disable this behaviour, you can use `'entity' => false` in your field definition. + + +### Fields API + +To manipulate fields, you can use the methods below. The action will be performed on the currently running operation. So make sure you run these methods inside ```setupCreateOperation()```, ```setupUpdateOperation()``` or in ```setup()``` inside operation blocks: + +```php +// to add a field with this name +CRUD::field('price'); + + // or directly with the type attribute on it +CRUD::field('price')->type('number'); + +// or set multiple attributes in one go +CRUD::field([ + 'name' => 'price', + 'type' => 'number', +]); + +// to change an attribute on a field, you can target it at any point +CRUD::field('price')->prefix('$'); + +// to change the same attribute across multiple fields you can wrap them in a `group` +// this will add the '$' prefix to both fields +CRUD::group( + CRUD::field('price'), + CRUD::field('discount') +)->prefix('$'); + +// to move fields before or after other fields +CRUD::field('price')->before('name'); +CRUD::field('price')->after('name'); + +// to remove a field from both operations +CRUD::field('name')->remove(); + +// to perform bulk actions +CRUD::removeAllFields(); +CRUD::addFields([$field_definition_array_1, $field_definition_array_2]); + +``` + + +### Field Attributes + + +#### Mandatory Field Attributes + +**The only attribute that's mandatory when you define a field is its `name`**, which will be used: +- inside the inputs, as ``; +- to store the information in the database, so your `name` should correspond to a database column (if the field type doesn't have different instructions); + +This also means the `name` attribute is UNIQUE. You cannot add multiple fields with the same `name` - because if more inputs are added with the same name in an HTML form... only the last input will actually be submitted. That's just how HTML forms work. + + +#### Recommended Field Attributes + +Usually developers define the following attributes for all fields: +- the ```name``` of the column in the database (ex: "title") +- the human-readable ```label``` for the input (ex: "Title") +- the ```type``` of the input (ex: "text") + +So at minimum, a field definition array usually looks like: +```php +CRUD::field([ + 'name' => 'description', + 'label' => 'Article Description', + 'type' => 'textarea', +]); +``` + +Please note that `label` and `type` are not _mandatory_, just _recommended_: +- `label` can be omitted, and Backpack will try to construct it from the `name`; +- `type` can be omitted, and Backpack will try to guess it from the database column type, or if there's a relationship on the Model with the same `name`; + + +#### Optional - Field Attributes for Presentation Purposes + +There are a few optional attributes on most default field types, that you can use to easily achieve a few common customisations: + +```php +[ + 'prefix' => '', + 'suffix' => '', + 'default' => 'some value', // set a default value + 'hint' => 'Some hint text', // helpful text, shows up after the input + 'attributes' => [ + 'placeholder' => 'Some text when empty', + 'class' => 'form-control some-class', + 'readonly' => 'readonly', + 'disabled' => 'disabled', + ], // change the HTML attributes of your input + 'wrapper' => [ + 'class' => 'form-group col-md-12' + ], // change the HTML attributes for the field wrapper - mostly for resizing fields +] +``` + +These will help you: + +- **prefix** - add a text or icon _before_ the actual input; +- **suffix** - add a text or icon _after_ the actual input; +- **default** - specify a default value for the input, on create; +- **hint** - add descriptive text for this input; +- **attributes** - change or add actual HTML attributes of the input (ex: readonly, disabled, class, placeholder, etc); +- **wrapper** - change or add actual HTML attributes to the div that contains the input; + + +#### Optional but Recommended - Field Attributes for Accessibility + +By default, field labels are not directly associated with input fields. To improve the accessibility of CRUD fields for screen readers and other assistive technologies (ensuring that a user entering a field will be informed of the name of the field), you can use the ```aria-label``` attribute: + +```php +CRUD::field([ + 'name' => 'email', + 'label' => 'Email Address', + 'type' => 'email', + 'attributes' => [ + 'aria-label' => 'Email Address', + ], +]); +``` + +In most cases, the ```aria-label``` will be the same as the ```label``` but there may be times when it is helpful to provide more context to the user. For example, the field ```hint``` text appears _after_ the field itself and therefore a screen reader user will not encounter the ```hint``` until they leave the field. You might therefore want to provide a more descriptive ```aria-label```, for example: + +```php +CRUD::field([ + 'name' => 'age', + 'label' => 'Age', + 'type' => 'number', + 'hint' => 'Enter the exact age of the participant (as a decimal, e.g. 2.5)', + 'attributes' => [ + 'step' => 'any', + 'aria-label' => 'Participant Age (as a decimal number)', + ], +]); +``` + + +#### Optional - Fake Field Attributes (stores fake attributes as JSON in the database) + +In case you want to store information for an entry that doesn't need a separate database column, you can add any number of Fake Fields, and their information will be stored inside a column in the db, as JSON. By default, an ```extras``` column is used and assumed on the database table, but you can change that. + +**Step 1.** Use the fake attribute on your field: +```php +CRUD::field([ + 'name' => 'name', // JSON variable name + 'label' => "Tag Name", // human-readable label for the input + + 'fake' => true, // show the field, but don't store it in the database column above + 'store_in' => 'extras' // [optional] the database column name where you want the fake fields to ACTUALLY be stored as a JSON array +]); +``` + +**Step 2.** On your model, make sure the db columns where you store the JSONs (by default only ```extras```): +- are in your ```$fillable``` property; +- are on a new ```$fakeColumns``` property (create it now); +- are cast as array in ```$casts```; + +>If you need your fakes to also be translatable, remember to also place ```extras``` in your model's ```$translatable``` property and remove it from ```$casts```. + +Example: +```php +CRUD::field([ + 'name' => 'meta_title', + 'label' => "Meta Title", + 'fake' => true, + 'store_in' => 'metas' // [optional] +]); +CRUD::field([ + 'name' => 'meta_description', + 'label' => "Meta Description", + 'fake' => true, + 'store_in' => 'metas' // [optional] +]); +CRUD::field([ + 'name' => 'meta_keywords', + 'label' => "Meta Keywords", + 'fake' => true, + 'store_in' => 'metas' // [optional] +]); +``` + +In this example, these 3 fields will show up in the create & update forms, the CRUD will process as usual, but in the database these values won't be stored in the ```meta_title```, ```meta_description``` and ```meta_keywords``` columns. They will be stored in the ```metas``` column as a JSON array: + +```php +{"meta_title":"title","meta_description":"desc","meta_keywords":"keywords"} +``` + +If the ```store_in``` attribute wasn't used, they would have been stored in the ```extras``` column. + + +#### Optional - Tab Attribute Splits Forms into Tabs + +You can now split your create/edit inputs into multiple tabs. + +![CRUD Fields Tabs](https://backpackforlaravel.com/uploads/docs-4-0/operations/create_tabs.png) + +To use this feature, specify the tab name for each of your fields. For example: + +```php +CRUD::field('price')->tab('Tab name here'); +``` + +If you don't specify a tab name for a field, then Backpack will place it above all of the tabs, for example: + +```php +CRUD::field('product'); +CRUD::field('description')->tab('Information'); +CRUD::field('price')->tab('Prices'); +``` + + +#### Optional - Attributes for Fields Containing Related Entries + +When a field works with related entities (relationships like `BelongsTo`, `HasOne`, `HasMany`, `BelongsToMany`, etc), Backpack needs to know how the current model (being create/edited) and the other model (that shows up in the field) are related. And it stores that information in a few additional field attributes, right after you add the field. + +*Normally, Backpack will guess all this relationship information for you.* If you have your relationships properly defined in your Models, you can just use a relationship field the same way you would a normal field. Pretend that _the method in your Model that defines your relationship_ is a real column, and Backpack will do all the work for you. + +But if you want to overwrite any of the relationship attributes Backpack guesses, here they are: +- `entity` - points to the method on the model that contains the relationship; having this defined, Backpack will try to guess from it all other field attributes; ex: `category` or `tags`; +- `model` - the classname (including namespace) of the related model (ex: `App\Models\Category`); usually deduced from the relationship function in the model; +- `attribute` - the attribute on the related model (aka foreign attribute) that will be show to the user; for example, you wouldn't want a dropdown of categories showing IDs - no, you'd want to show the category names; in this case, the `attribute` will be `name`; usually deduced using the [identifiable attribute functionality explained below](#identifiable-attribute); +- `multiple` - boolean, allows the user to pick one or multiple items; usually deduced depending on whether it's a 1-to-n or n-n relationship; +- `pivot` - boolean, instructs Backpack to store the information inside a pivot table; usually deduced depending on whether it's a 1-to-n or n-n relationship; +- `relation_type` - text, deduced from `entity`; not a good idea to overwrite; + +If you do need a field that contains relationships to behave a certain way, it's usually enough to just specify a different `entity`. However, you _can_ specify any of the attributes above, and Backpack will take your value for it, instead of trying to guess one. + + + +**Identifiable Attribute for Relationship Fields** + +Fields that work with relationships will allow you to select which ```attribute``` on the related entry you want to show to the user. All relationship fields (relationship, select, select2, select_multiple, select2_multiple, select2_from_ajax, select2_from_ajax_multiple) let you define the ```attribute``` for this specific purpose. + +For example, when the admin creates an ```Article``` they'll have to select a ```Category``` from a dropdown. It's important to show an attribute for ```Category``` that will help the admin easily identify the category, even if it's not the ID. In this example, it would probably be the category name - that's what you'd like the dropdown to show. + +In Backpack, you can explicitly define this, by giving the field an ```attribute```. But you can also NOT explicitly define this - Backpack will try to guess it. If you don't like what Backpack guessed would be a good identifiable attribute, you can either: +- (A) explicitly define an ```attribute``` for that field + +>**Note**: If the attribute you want to show is an acessor in Model, you need to add it to the `$appends` property of the said Model. https://laravel.com/docs/10.x/eloquent-serialization#appending-values-to-json + +- (B) you can specify the identifiable attribute in your model, and all fields will pick this up: + +```php + +use Backpack\CRUD\app\Models\Traits\CrudTrait; + +class Category +{ + use CrudTrait; + + // you can define this + + /** + * Attribute shown on the element to identify this model. + * + * @var string + */ + protected $identifiableAttribute = 'title'; + + // or for more complicated use cases you can do + + /** + * Get the attribute shown on the element to identify this model. + * + * @return string + */ + public function identifiableAttribute() + { + // process stuff here + return 'whatever_you_want_even_an_accessor'; + } +} +``` + +## FREE Field Types + + +### checkbox + +Checkbox for true/false. + +```php +CRUD::field([ // Checkbox + 'name' => 'active', + 'label' => 'Active', + 'type' => 'checkbox' +]); +``` + +Input preview: + +![CRUD Field - checkbox](https://backpackforlaravel.com/uploads/docs-4-2/fields/checkbox.png) + +
+ + +### checklist + +Show a list of checkboxes, for the user to check one or more of them. + +```php +CRUD::field([ // Checklist + 'label' => 'Roles', + 'type' => 'checklist', + 'name' => 'roles', + 'entity' => 'roles', + 'attribute' => 'name', + 'model' => "Backpack\PermissionManager\app\Models\Role", + 'pivot' => true, + 'show_select_all' => true, // default false + // 'number_of_columns' => 3, + +]); +``` + +**Note: If you don't use a pivot table (pivot = false), you need to cast your db column as `array` in your model,by adding your column to your model's `$casts`. ** + +Input preview: + +![CRUD Field - checklist](https://backpackforlaravel.com/uploads/docs-4-2/fields/checklist.png) + +
+ + +### checklist_dependency + +```php +CRUD::field([ // two interconnected entities + 'label' => 'User Role Permissions', + 'field_unique_name' => 'user_role_permission', + 'type' => 'checklist_dependency', + 'name' => 'roles,permissions', // the methods that define the relationship in your Models + 'subfields' => [ + 'primary' => [ + 'label' => 'Roles', + 'name' => 'roles', // the method that defines the relationship in your Model + 'entity' => 'roles', // the method that defines the relationship in your Model + 'entity_secondary' => 'permissions', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "Backpack\PermissionManager\app\Models\Role", // foreign key model + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries?] + 'number_columns' => 3, //can be 1,2,3,4,6 + 'options' => (function ($query) { + return $query->where('name', '!=', 'admin'); + }), // force the related options to be a custom query, instead of all(); you can use this to filter the available options + ], + 'secondary' => [ + 'label' => 'Permission', + 'name' => 'permissions', // the method that defines the relationship in your Model + 'entity' => 'permissions', // the method that defines the relationship in your Model + 'entity_primary' => 'roles', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'model' => "Backpack\PermissionManager\app\Models\Permission", // foreign key model + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries?] + 'number_columns' => 3, //can be 1,2,3,4,6 + 'options' => (function ($query) { + return $query->where('name', '!=', 'admin'); + }), // force the related options to be a custom query, instead of all(); you can use this to filter the available options + ], + ], +]); +``` + +Input preview: + +![CRUD Field - checklist_dependency](https://backpackforlaravel.com/uploads/docs-4-2/fields/checklist_dependency.png) + +
+ + +### color + +```php +CRUD::field([ // Color + 'name' => 'background_color', + 'label' => 'Background Color', + 'type' => 'color', + 'default' => '#000000', +]); +``` + +Input preview: + +![CRUD Field - color](https://backpackforlaravel.com/uploads/docs-4-2/fields/color.png) + +
+ + +### custom_html + +Allows you to insert custom HTML in the create/update forms. Usually used in forms with a lot of fields, to separate them using h1-h5, hr, etc, but can be used for any HTML. + +```php +CRUD::field([ // CustomHTML + 'name' => 'separator', + 'type' => 'custom_html', + 'value' => '
' +]); +``` +**NOTE** If you would like to disable the `wrapper` on this field, eg. when using a `
` tag in your custom html, you can achieve it by using `wrapper => false` on field definition. + + +### date + +```php +CRUD::field([ // Date + 'name' => 'birthday', + 'label' => 'Birthday', + 'type' => 'date' +]); +``` + +Input preview: + +![CRUD Field - date](https://backpackforlaravel.com/uploads/docs-4-2/fields/date.png) + +
+ + +### datetime + +```php +CRUD::field([ // DateTime + 'name' => 'start', + 'label' => 'Event start', + 'type' => 'datetime' +]); +``` + +**Please note:** if you're using datetime [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model, you also need to place this mutator inside your model: +```php + public function setDatetimeAttribute($value) { + $this->attributes['datetime'] = \Carbon\Carbon::parse($value); + } +``` +Otherwise the input's datetime-local format will cause some errors. + +Input preview: + +![CRUD Field - datetime](https://backpackforlaravel.com/uploads/docs-4-2/fields/datetime.png) + +
+ + +### email + +```php +CRUD::field([ // Email + 'name' => 'email', + 'label' => 'Email Address', + 'type' => 'email' +]); +``` + +Input preview: + +![CRUD Field - email](https://backpackforlaravel.com/uploads/docs-4-2/fields/email.png) + + +
+ + +### enum + +Show a select with the values for an ENUM database column, or an PHP enum (introduced in PHP 8.1). + +##### Database ENUM +When used with a database enum it requires that the database column type is `enum`. In case it's nullable it will also show `-` (empty) option. + +PLEASE NOTE the `enum` field using database enums only works for MySQL. + +```php +CRUD::field([ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum', + // optional, specify the enum options with custom display values + 'options' => [ + 'DRAFT' => 'Is Draft', + 'PUBLISHED' => 'Is Published' + ] +]); +``` + +##### PHP enum + +If you are using a `BackedEnum` your best option is to cast it in your model, and Backpack know how to handle it without aditional configuration. + +```php +// in your model (eg. Article) + +protected $casts = ['status' => \App\Enums\StatusEnum::class]; //assumes you have this enum created + +// and in your controller +CRUD::field([ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum' + // optional + //'enum_class' => 'App\Enums\StatusEnum', + //'enum_function' => 'readableStatus', +]); +``` + +In case it's not a `BackedEnum` or you don't want to cast it in your Model, you should provide the enum class to the field: + +```php +CRUD::field([ + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum', + 'enum_class' => \App\Enums\StatusEnum::class +]); +``` + +Input preview: + +![CRUD Field - enum](https://backpackforlaravel.com/uploads/docs-4-2/fields/enum.png) + +
+ + +### hidden + +Include an `` in the form. + +```php +CRUD::field([ // Hidden + 'name' => 'status', + 'type' => 'hidden', + 'value' => 'active', +]); +``` + +
+ + +### month + +Include an `` in the form. + +**Important**: This input type is supported by most modern browsers, but not all. [See compatibility table here](https://caniuse.com/mdn-html_elements_input_type_month). We have a workaround below. + +```php +CRUD::field([ // Month + 'name' => 'month', + 'label' => 'Month', + 'type' => 'month' +]); +``` + +Input preview: + +![CRUD Field - month](https://backpackforlaravel.com/uploads/docs-4-2/fields/month.png) + +**Workaround** + +Since not all browsers support this input type, if you are using [Backpack PRO](https://backpackforlaravel.com/products/pro-for-one-project) you can customize the `date_picker` field to have a similar behavior: +```php +CRUD::field([ + 'name' => 'month', + 'type' => 'date_picker', + 'date_picker_options' => [ + 'format' => 'yyyy-mm', + 'minViewMode' => 'months' + ], +]); +``` +**Important**: you should be using a date/datetime column as database column type if using `date_picker`. + +
+ + +### number + +Shows an input type=number to the user, with optional prefix and suffix: + +```php +CRUD::field([ // Number + 'name' => 'number', + 'label' => 'Number', + 'type' => 'number', + + // optionals + // 'attributes' => ["step" => "any"], // allow decimals + // 'prefix' => "$", + // 'suffix' => ".00", +]); +``` + +Input preview: + +![CRUD Field - number](https://backpackforlaravel.com/uploads/docs-4-2/fields/number.png) + +
+ + +### password + +```php +CRUD::field([ // Password + 'name' => 'password', + 'label' => 'Password', + 'type' => 'password' +]); +``` + +Input preview: + +![CRUD Field - password](https://backpackforlaravel.com/uploads/docs-4-2/fields/password.png) + + +Please note that this will NOT hash/encrypt the string before it stores it to the database. You need to hash the password manually. The most popular way to do that are: + +1. Using [a mutator on your Model](https://laravel.com/docs/7.x/eloquent-mutators#defining-a-mutator). For example: + +```php +public function setPasswordAttribute($value) { + $this->attributes['password'] = Hash::make($value); +} +``` + +2. By overwriting the Create/Update operation methods, inside the Controller. There's a working example [in our PermissionManager package](https://github.com/Laravel-Backpack/PermissionManager/blob/master/src/app/Http/Controllers/UserCrudController.php#L103-L124) but the gist of it is this: + +```php + use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; } + + public function store() + { + CRUD::setRequest(CRUD::validateRequest()); + + /** @var \Illuminate\Http\Request $request */ + $request = CRUD::getRequest(); + + // Encrypt password if specified. + if ($request->input('password')) { + $request->request->set('password', Hash::make($request->input('password'))); + } else { + $request->request->remove('password'); + } + + CRUD::setRequest($request); + CRUD::unsetValidation(); // Validation has already been run + + return $this->traitStore(); + } +``` + + +
+ + +### radio + +Show radios according to an associative array you give the input and let the user pick from them. You can choose for the radio options to be displayed inline or one-per-line. + +```php +CRUD::field([ // radio + 'name' => 'status', // the name of the db column + 'label' => 'Status', // the input label + 'type' => 'radio', + 'options' => [ + // the key will be stored in the db, the value will be shown as label; + 0 => "Draft", + 1 => "Published" + ], + // optional + //'inline' => false, // show the radios all on the same line? +]); +``` + +Input preview: + +![CRUD Field - radio](https://backpackforlaravel.com/uploads/docs-4-2/fields/radio.png) + +
+ + +### range + +Shows an HTML5 range element, allowing the user to drag a cursor left-right, to pick a number from a defined range. + +```php +CRUD::field([ // Range + 'name' => 'range', + 'label' => 'Range', + 'type' => 'range', + //optional + 'attributes' => [ + 'min' => 0, + 'max' => 10, + ], +]); +``` + +Input preview: + +![CRUD Field - range](https://backpackforlaravel.com/uploads/docs-4-2/fields/range.png) + +
+ + +### select (1-n relationship) + +Show a Select with the names of the connected entity and let the user select one of them. +Your relationships should already be defined on your models as hasOne() or belongsTo(). + +```php +CRUD::field([ // Select + 'label' => "Category", + 'type' => 'select', + 'name' => 'category_id', // the db column for the foreign key + + // optional + // 'entity' should point to the method that defines the relationship in your Model + // defining entity will make Backpack guess 'model' and 'attribute' + 'entity' => 'category', + + // optional - manually specify the related model and attribute + 'model' => "App\Models\Category", // related model + 'attribute' => 'name', // foreign key attribute that is shown to user + + // optional - force the related options to be a custom query, instead of all(); + 'options' => (function ($query) { + return $query->orderBy('name', 'ASC')->where('depth', 1)->get(); + }), // you can use this to filter the results show in the select +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select](https://backpackforlaravel.com/uploads/docs-4-2/fields/select.png) + + +
+ + + +### select_grouped + +Display a select where the options are grouped by a second entity (like Categories). + +```php +CRUD::field([ // select_grouped + 'label' => 'Articles grouped by categories', + 'type' => 'select_grouped', //https://github.com/Laravel-Backpack/CRUD/issues/502 + 'name' => 'article_id', + 'entity' => 'article', + 'attribute' => 'title', + 'group_by' => 'category', // the relationship to entity you want to use for grouping + 'group_by_attribute' => 'name', // the attribute on related model, that you want shown + 'group_by_relationship_back' => 'articles', // relationship from related model back to this model +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select_grouped](https://backpackforlaravel.com/uploads/docs-4-2/fields/select_grouped.png) + +
+ + +### select_multiple (n-n relationship) + +Show a Select with the names of the connected entity and let the user select any number of them. +Your relationship should already be defined on your models as belongsToMany(). + +```php +CRUD::field([ // SelectMultiple = n-n relationship (with pivot table) + 'label' => "Tags", + 'type' => 'select_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + + // optional + 'entity' => 'tags', // the method that defines the relationship in your Model + 'model' => "App\Models\Tag", // foreign key model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + + // also optional + 'options' => (function ($query) { + return $query->orderBy('name', 'ASC')->where('depth', 1)->get(); + }), // force the related options to be a custom query, instead of all(); you can use this to filter the results show in the select +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/select_multiple.png) + + +
+ + +### select_from_array + +Display a select with the values you want: + +```php +CRUD::field([ // select_from_array + 'name' => 'template', + 'label' => "Template", + 'type' => 'select_from_array', + 'options' => ['one' => 'One', 'two' => 'Two'], + 'allows_null' => false, + 'default' => 'one', + // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; +]); +``` + +Input preview: + +![CRUD Field - select_from_array](https://backpackforlaravel.com/uploads/docs-4-2/fields/select_from_array.png) + +
+ + +### summernote + +Show a [Summernote wysiwyg editor](http://summernote.org/) to the user. + +```php +CRUD::field([ // Summernote + 'name' => 'description', + 'label' => 'Description', + 'type' => 'summernote', + 'options' => [], +]); + +// the summernote field works with the default configuration options but allow developer to configure to his needs +// optional configuration check https://summernote.org/deep-dive/ for a list of available configs +CRUD::field([ + 'name' => 'description', + 'label' => 'Description', + 'type' => 'summernote', + 'options' => [ + 'toolbar' => [ + ['font', ['bold', 'underline', 'italic']] + ] + ], +]); +``` + +> NOTE: Summernote does NOT sanitize the input. If you do not trust the users of this field, you should sanitize the input or output using something like HTML Purifier. Personally we like to use install [mewebstudio/Purifier](https://github.com/mewebstudio/Purifier) and add an [accessor or mutator](https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators) on the Model, so that wherever the model is created from (admin panel or app), the output will always be clean. [Example here](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1). + +Input preview: + +![CRUD Field - summernote](https://backpackforlaravel.com/uploads/docs-4-2/fields/summernote.png) + +
+ + +### switch + +Show a switch (aka toggle) for boolean attributes (true/false). It's an alternative to the `checkbox` field type - prettier and more customizable: it allows the dev to choose the background color and what shows up on the on/off sides of the switch. + +```php +CRUD::field([ // Switch + 'name' => 'switch', + 'type' => 'switch', + 'label' => 'I have not read the terms and conditions and I never will', + + // optional + 'color' => 'primary', // May be any bootstrap color class or an hex color + 'onLabel' => '✓', + 'offLabel' => '✕', +]); +``` + +Input preview: + +![CRUD Field - switch](https://backpackforlaravel.com/uploads/docs-5-0/fields/switch.png) + +
+ + +### text + +The basic field type, all it needs is the two mandatory parameters: name and label. + +```php +CRUD::field([ // Text + 'name' => 'title', + 'label' => "Title", + 'type' => 'text', + + // OPTIONAL + //'prefix' => '', + //'suffix' => '', + //'default' => 'some value', // default value + //'hint' => 'Some hint text', // helpful text, show up after input + //'attributes' => [ + //'placeholder' => 'Some text when empty', + //'class' => 'form-control some-class', + //'readonly' => 'readonly', + //'disabled' => 'disabled', + //], // extra HTML attributes and values your input might need + //'wrapper' => [ + //'class' => 'form-group col-md-12' + //], // extra HTML attributes for the field wrapper - mostly for resizing fields +]); +``` + +You can use the optional 'prefix' and 'suffix' attributes to display something before and after the input, like icons, path prefix, etc: + +Input preview: + +![CRUD Field - text](https://backpackforlaravel.com/uploads/docs-4-2/fields/text.png) + +
+ + +### textarea + +Show a textarea to the user. + +```php +CRUD::field([ // Textarea + 'name' => 'description', + 'label' => 'Description', + 'type' => 'textarea' +]); +``` + +Input preview: + +![CRUD Field - textarea](https://backpackforlaravel.com/uploads/docs-4-2/fields/textarea.png) + +
+ + +### time + +```php +CRUD::field([ // Time + 'name' => 'start', + 'label' => 'Start time', + 'type' => 'time' +]); +``` + +
+ + +### upload + +**Step 1.** Show a file input to the user: +```php +CRUD::field([ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', +]); +``` + +**Step 2.** Choose how to handle the file upload process. Starting v6, you have two options: +- **Option 1.** Let Backpack handle the upload process for you. This is by far the most convenient option, because it's the easiest to implement and fully customizable. All you have to do is add the `withFiles => true` attribute to your field definition: +```php +CRUD::field([ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', + 'withFiles' => true +]); +``` +To know more about the `withFiles`, how it works and how to configure it, [ click here to read the documentation ](https://backpackforlaravel.com/docs/6.x/crud-uploaders). + +- **Option 2.** Handle the upload process yourself. This is what happened in v5, so if you want to handle the upload by yourself you can [read the v5 upload docs here](https://backpackforlaravel.com/docs/5.x/crud-fields#upload-1). + +**Upload Field Validation** + +You can use standard Laravel validation rules. But we've also made it easy for you to validate the `upload` fields, using a [Custom Validation Rule](/docs/{{version}}/custom-validation-rules). The `ValidUpload` validation rule allows you to define two sets of rules: +- `::field()` - the field rules (independent of the file content); +- `->file()` - rules that apply to the sent file; + +This helps you avoid most quirks when validating file uploads using Laravel's validation rules. + +```php +use Backpack\CRUD\app\Library\Validation\Rules\ValidUpload; + +'image' => ValidUpload::field('required') + ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'), +``` + +Input preview: + +![CRUD Field - upload](https://backpackforlaravel.com/uploads/docs-4-2/fields/upload.png) + +
+ + +### upload_multiple + +Shows a multiple file input to the user and stores the values as a JSON array in the database. + +**Step 0.** Make sure the db column can hold the amount of text this field will have. For example, for MySQL, `VARCHAR(255)` might not be enough all the time (for 3+ files), so it's better to go with `TEXT`. Make sure you're using a big column type in your migration or db. + +**Step 1.** Show a multiple file input to the user: +```php +CRUD::field([ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', +]); +``` + +**Step 2.** Choose how to handle the file upload process. Starting v6, you have two options: +- **Option 1.** Let Backpack handle the upload process for you. This is by far the most convenient option, because it's the easiest to implement and fully customizable. All you have to do is add the `withFiles => true` attribute to your field definition: +```php +CRUD::field([ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + 'withFiles' => true +]); +``` +To know more about the `withFiles`, how it works and how to configure it, [ click here to read the documentation ](https://backpackforlaravel.com/docs/6.x/crud-uploaders). + +- **Option 2.** Handle the upload process yourself. This is what happened in v5, so if you want to handle the upload by yourself you can [read the v5 upload docs here](https://backpackforlaravel.com/docs/5.x/crud-fields#upload_multiple). + +**Validation** + +You can use standard Laravel validation rules. But we've also made it easy for you to validate the `upload` fields, using a [Custom Validation Rule](/docs/{{version}}/custom-validation-rules). The `ValidUploadMultiple` validation rule allows you to define two sets of rules: +- `::field()` - the input rules, independant of the content; +- `file()` - rules that apply to each file that gets sent; + +This will help you avoid most quirks of using Laravel's standard validation rules alone. + +```php +use Backpack\CRUD\app\Library\Validation\Rules\ValidUploadMultiple; + +'photos' => ValidUploadMultiple::field('required|min:2|max:5') + ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'), +``` + +**NOTE**: This field uses a `clear_{fieldName}` input to send the deleted files from the frontend to the backend. In case you are using `$guarded` add it there. +Eg: `protected $guarded = ['id', 'clear_photos'];` + +Input preview: + +![CRUD Field - upload_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/upload_multiple.png) + +
+ + +### url + +```php +CRUD::field([ // URL + 'name' => 'link', + 'label' => 'Link to video file', + 'type' => 'url' +]); +``` + +
+ + +### view + +Load a custom view in the form. + +```php +CRUD::field([ // view + 'name' => 'custom-ajax-button', + 'type' => 'view', + 'view' => 'partials/custom-ajax-button' +]); +``` + +**Note:** the same functionality can be achieved using a [custom field type](/docs/{{version}}/crud-fields#creating-a-custom-field-type), or using the [custom_html field type](/docs/{{version}}/crud-fields#custom-html) (if the content is really simple). + +**NOTE** If you would like to disable the `wrapper` on this field, you can achieve it by using `wrapper => false` on field definition. + +
+ + +### week + +Include an `` in the form. + +**Important**: This input type is supported by most modern browsers, not all. [See compatibility table here](https://caniuse.com/mdn-html_elements_input_type_week). + +```php +CRUD::field([ // Week + 'name' => 'first_week', + 'label' => 'First week', + 'type' => 'week' +]); +``` + +Input preview: + +![CRUD Field - week](https://backpackforlaravel.com/uploads/docs-4-2/fields/week.png) + + + + +## PRO Field Types + + + +### address_google PRO + +Use [Google Places Search](https://developers.google.com/places/web-service/search) to help users type their address faster. With the ```store_as_json``` option, it will store the address, postcode, city, country, latitude and longitude in a JSON in the database. Without it, it will just store the complete address string. + +```php +CRUD::field([ // Address google + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_google', + // optional + 'store_as_json' => true +]); +``` + +Using Google Places API is dependent on using an API Key. Please [get an API key](https://console.cloud.google.com/apis/credentials) - you do have to configure billing, but you qualify for $200/mo free usage, which covers most use cases. Then copy-paste that key as your ```services.google_places.key``` value. + +So inside your ```config/services.php``` please add the items below: +```php +'google_places' => [ + 'key' => 'the-key-you-got-from-google-places' +], +``` +Alternatively you can set the key in your field definition, but we do **not recommend** it: +```php +[ + 'name' => 'google_field', + 'api_key' => 'the-key-you-got-from-google-places' +] +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.com/blog/laravel-5.0-eloquent-attribute-casting) to ```array``` or ```object```. That way, every time you get the info from the database you'd get it in a usable format. Also, it is heavily recommended that your database column can hold a large JSON - so use `text` rather than `string` in your migration (in MySQL this translates to `text` instead of `varchar`). + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-4-2/fields/address_google.png) + +
+ + +### browse PRO + +This button allows the admin to open [elFinder](http://elfinder.org/) and select a file from there. Run ```composer require backpack/filemanager && php artisan backpack:filemanager:install``` to install [FileManager](https://github.com/laravel-backpack/filemanager), then you can use the field: + +```php +CRUD::field([ // Browse + 'name' => 'image', + 'label' => 'Image', + 'type' => 'browse' +]); +``` + + +Input preview: + +![CRUD Field - browse](https://backpackforlaravel.com/uploads/docs-4-2/fields/browse.png) + +Onclick preview: + +![CRUD Field - browse popup](https://backpackforlaravel.com/uploads/docs-4-2/fields/browse_popup.png) + +
+ + +### browse_multiple PRO + +Open elFinder and select multiple files from there. Run ```composer require backpack/filemanager && php artisan backpack:filemanager:install``` to install [FileManager](https://github.com/laravel-backpack/filemanager), then you can use the field: + +```php +CRUD::field([ // Browse multiple + 'name' => 'files', + 'label' => 'Files', + 'type' => 'browse_multiple', + // 'multiple' => true, // enable/disable the multiple selection functionality + // 'sortable' => false, // enable/disable the reordering with drag&drop + // 'mime_types' => null, // visible mime prefixes; ex. ['image'] or ['application/pdf'] +]); +``` + +The field assumes you've cast your attribute as ```array``` on your model. That way, when you do ```$entry->files``` you get a nice array. +**NOTE:** If you use `multiple => false` you should NOT cast your attribute as ```array``` + +Input preview: + +![CRUD Field - browse_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/browse_multiple.png) + +
+ + +### base64_image PRO + +Upload an image and store it in the database as Base64. Notes: +- make sure the column type is LONGBLOB; +- detailed [instructions and customisations here](https://github.com/Laravel-Backpack/CRUD/pull/56#issue-164712261); + +```php +// base64_image +CRUD::field([ + 'label' => "Profile Image", + 'name' => "image", + 'filename' => "image_filename", // set to null if not needed + 'type' => 'base64_image', + 'aspect_ratio' => 1, // set to 0 to allow any aspect ratio + 'crop' => true, // set to true to allow cropping, false to disable + 'src' => NULL, // null to read straight from DB, otherwise set to model accessor function +]); +``` + +Input preview: + +![CRUD Field - base64_image](https://backpackforlaravel.com/uploads/docs-4-2/fields/base64_image.png) + +
+ + +### ckeditor PRO + +Show a wysiwyg CKEditor to the user. + +```php +CRUD::field([ // CKEditor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'ckeditor', + + // optional: + 'custom_build' => [ // check a bit down in this docs on how to work with custom builds + public_path('assets/js/ckeditor/ckeditor.js'), + public_path('assets/js/ckeditor/ckeditor-init.js'), + ], + 'extra_plugins' => [], + 'options' => [ + 'autoGrow_minHeight' => 200, + 'autoGrow_bottomSpace' => 50, + 'removePlugins' => [], + ], + + // elfinder configuration options when using [the file manager package](https://github.com/Laravel-Backpack/FileManager) + // to use this feature you need to be running backpack/pro:2.2.1 or higher and backpack/filemanager:3.0.8 or higher + 'elfinderOptions' => [], // it's the same as `true`, will enable with default options, by default is: `false` +]); +``` + +If you'd like to be able to select files from elFinder, you need to install [Backpack/FileManager](https://github.com/Laravel-Backpack/FileManager) package and enable it in your field: `elfinderOptions => true`. + +#### CKEditor Custom Builds + +You can create a custom build on the official CKEditor website and use it in your Backpack application. This is useful if you want to have more control over the plugins and features that are included in your CKEditor instance. To use a custom build, you need to follow these steps: + +**1)** - Go to the [CKEditor Builder](https://ckeditor.com/ckeditor-5/online-builder/) and select the plugins you want to include in your build. + +**2)** - Place the downloaded ckeditor.js file in your app, for example in `public/assets/js/ckeditor/ckeditor.js`. + +**3)** - Create a new file, for example `public/assets/js/ckeditor/ckeditor-init.js`, and include the following code: + +```javascript +async function bpFieldInitCustomCkeditorElement(element) { + let myConfig = { + 'language': '{{ app()->getLocale() }}', + }; + // To create CKEditor 5 classic editor + let ckeditorInstance = await ClassicEditor.create(element[0], myConfig).catch(error => { + console.error( error ); + }); + + if (!ckeditorInstance) return; + + element.on('CrudField:delete', function (e) { + ckeditorInstance.destroy(); + }); + + // trigger the change event on textarea when ckeditor changes + ckeditorInstance.editing.view.document.on('layoutChanged', function (e) { + element.trigger('change'); + }); + + element.on('CrudField:disable', function (e) { + ckeditorInstance.enableReadOnlyMode('CrudField'); + }); + + element.on('CrudField:enable', function (e) { + ckeditorInstance.disableReadOnlyMode('CrudField'); + }); +} +``` + +**4)** - Use the `custom_build` option in your field definition to include the custom build and overwrite the init function: + +```php +'custom_build' => [ // check a bit down in this docs on how to work with custom builds + public_path('assets/js/ckeditor/ckeditor.js'), + public_path('assets/js/ckeditor/ckeditor-init.js'), +], +'attributes' => [ + 'data-init-function' => 'bpFieldInitCustomCkeditorElement', +] + +``` + +**NOTE**: As you have noticed, using a custom build your initialization script completely overwrites Backpack behavior, for that reason you need to handle all the events and methods that are needed for the field to work properly with Backpack functionality. + +Input preview: + +![CRUD Field - ckeditor](https://backpackforlaravel.com/uploads/docs-4-2/fields/ckeditor.png) + +
+ + +### date_range PRO + +Show a DateRangePicker and let the user choose a start date and end date. + +```php +CRUD::field([ // date_range + 'name' => 'start_date,end_date', // db columns for start_date & end_date + 'label' => 'Event Date Range', + 'type' => 'date_range', + + // OPTIONALS + // default values for start_date & end_date + 'default' => ['2019-03-28 01:01', '2019-04-05 02:00'], + // options sent to daterangepicker.js + 'date_range_options' => [ + 'drops' => 'down', // can be one of [down/up/auto] + 'timePicker' => true, + 'locale' => ['format' => 'DD/MM/YYYY HH:mm'] + ] +]); +``` + +Please note it is recommended that you use [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model (cast to date). + +Your end result will look like this: + +Input preview: + +![CRUD Field - date_range](https://backpackforlaravel.com/uploads/docs-4-2/fields/date_range.png) + + + +
+ + +### date_picker PRO + +Show a pretty [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/). + +```php +CRUD::field([ // date_picker + 'name' => 'date', + 'type' => 'date_picker', + 'label' => 'Date', + + // optional: + 'date_picker_options' => [ + 'todayBtn' => 'linked', + 'format' => 'dd-mm-yyyy', + 'language' => 'fr' + ], +]); +``` + +Please note it is recommended that you use [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model (cast to date). + + +Input preview: + +![CRUD Field - date_picker](https://backpackforlaravel.com/uploads/docs-4-2/fields/date_picker.png) + +
+ + +### datetime_picker PRO + +Show a [Bootstrap Datetime Picker](https://eonasdan.github.io/bootstrap-datetimepicker/). + +```php +CRUD::field([ // DateTime + 'name' => 'start', + 'label' => 'Event start', + 'type' => 'datetime_picker', + + // optional: + 'datetime_picker_options' => [ + 'format' => 'DD/MM/YYYY HH:mm', + 'language' => 'pt', + 'tooltips' => [ //use this to translate the tooltips in the field + 'today' => 'Hoje', + 'selectDate' => 'Selecione a data', + // available tooltips: today, clear, close, selectMonth, prevMonth, nextMonth, selectYear, prevYear, nextYear, selectDecade, prevDecade, nextDecade, prevCentury, nextCentury, pickHour, incrementHour, decrementHour, pickMinute, incrementMinute, decrementMinute, pickSecond, incrementSecond, decrementSecond, togglePeriod, selectTime, selectDate + ] + ], + 'allows_null' => true, + // 'default' => '2017-05-12 11:59:59', +]); +``` + +**Please note:** if you're using date [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model, you may also need to place this mutator inside your model: +```php + public function setDatetimeAttribute($value) { + $this->attributes['datetime'] = \Carbon\Carbon::parse($value); + } +``` +Otherwise the input's datetime-local format will cause some errors. Remember to change "datetime" with the name of your attribute (column name). + +Input preview: + +![CRUD Field - datetime_picker](https://backpackforlaravel.com/uploads/docs-4-2/fields/datetime_picker.png) + +
+ + +### dropzone PRO + +Show a [Dropzone JS Input](https://docs.dropzone.dev/). + +**Step 0.** Make sure the model attribute can hold all the information needed. Ideally, your model should cast this attribute as `array` and your migration should make the db column either `TEXT` or `JSON`. Other db column types such as `VARCHAR(255)` might not be enough all the time (for 3+ files). + +**Step 1:** Add the `DropzoneOperation` to your `CrudController` + +```php +class UserCrudController extends CrudController +{ + // ... other operations + use \Backpack\Pro\Http\Controllers\Operations\DropzoneOperation; +} +``` + +**Step 2:** Add the field in CrudController + +```php +CRUD::field([ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'dropzone', + + // optional configuration. + // check available options in https://docs.dropzone.dev/configuration/basics/configuration-options + // 'configuration' => [ + // 'parallelUploads' => 2, + // ] +]); +``` + +**Step 3:** Configure the file upload process. + +At this point you have the dropzone field showing up, and the ajax routes setup to upload/delete files, but the process is not complete. Your files are now only uploaded to the temporary folder, they need to be moved to the permanent location and their paths stored in the database. The easiest way to do that is to add `withFiles => true` to your field definition, this will use the standard `AjaxUploader` that Backpack provides: + +```php +CRUD::field([ + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'dropzone', + 'withFiles' => true, +]); +``` + +Alternatively, you can manually implement the saving process yourself using model events, mutators or any other solution that suits you. To know more about the `withFiles`, how it works and how to configure it, [read its documentation](https://backpackforlaravel.com/docs/6.x/crud-uploaders). + + +**Step 4:** Configure validation. Yes you can do some basic validation in Javascript, but we highly advise you prioritize server-side validation. To make validation easy we created `ValidDropzone` validation rule. It allows you to define two set of rules: +- `::field()` - the field rules (independent of the file content); +- `->file()` - rules that apply to the sent files; + +```php +use Backpack\Pro\Uploads\Validation\ValidDropzone; + +'photos' => ValidDropzone::field('required|min:2|max:5') + ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'), +``` + + +**Step 5:** (optional) Configure the temp directory. Whenever new files are uploaded using the Dropzone operation, old files are automatically deleted from the temp directory. But you can also manually clean the temp directory. For more info and temp directory configuration options, see [this link](/docs/{{version}}/crud-how-to#configuring-the-temporary-directory). + + +Input preview: + +![CRUD Field - dropzone](https://user-images.githubusercontent.com/7188159/236273902-ca7fb5a5-e7ce-4a03-91a7-2af81598331c.png) + +
+ + +### easymde PRO + +Show an [EasyMDE - Markdown Editor](https://github.com/Ionaru/easy-markdown-editor) to the user. EasyMDE is a well-maintained fork of SimpleMDE. + +```php +CRUD::field([ // easymde + 'name' => 'description', + 'label' => 'Description', + 'type' => 'easymde', + // optional + // 'easymdeAttributes' => [ + // 'promptURLs' => true, + // 'status' => false, + // 'spellChecker' => false, + // 'forceSync' => true, + // ], + // 'easymdeAttributesRaw' => $some_json +]); +``` + +> NOTE: The contents displayed in this editor are NOT stripped, sanitized or escaped by default. Whenever you store Markdown or HTML inside your database, it's HIGHLY recommended that you sanitize the input or output. Laravel makes it super-easy to do that on the model using [accessors](https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators). If you do NOT trust the admins who have access to this field (or end-users can also store information to this db column), please make sure this attribute is always escaped, before it's shown. You can do that by running the value through `strip_tags()` in an accessor on the model (here's [an example](https://github.com/Laravel-Backpack/demo/commit/509c0bf0d8b9ee6a52c50f0d2caed65f1f986385)) or better yet, using an [HTML Purifier package](https://github.com/mewebstudio/Purifier) (here's [an example](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1)). + +Input preview: + +![CRUD Field - easymde](https://backpackforlaravel.com/uploads/docs-4-2/fields/easymde.png) + +
+ + +### google_map PRO + +Shows a map and allows the user to navigate and select a position on that map (using the Google Places API). The field stores the latitude, longitude and the address string as a JSON in the database ( eg. `{lat: 123, lng: 456, formatted_address: 'Lisbon, Portugal'}`). If you want to save the info in separate db columns, continue reading below. + +```php +CRUD::field([ + 'name' => 'location', + 'type' => 'google_map', + // optionals + 'map_options' => [ + 'default_lat' => 123, + 'default_lng' => 456, + 'locate' => false, // when false, only a map is displayed. No value for submition. + 'height' => 400 // in pixels + ] +]); +``` + +Using Google Places API is dependent on using an API Key. Please [get an API key](https://console.cloud.google.com/apis/credentials) - you do have to configure billing, but you qualify for $200/mo free usage, which covers most use cases. Then copy-paste that key as your ```services.google_places.key``` value. So inside your ```config/services.php``` please add the items below: + +```php +'google_places' => [ + 'key' => 'the-key-you-got-from-google-places' +], +``` + +**How to save in multiple inputs?** + +There are cases where you rather save the information on separate inputs in the database. In that scenario you should use [Laravel mutators and accessors](https://laravel.com/docs/10.x/eloquent-mutators). Using the same field as previously shown (**field name is `location`**), and having `latitude`, `longitude`, `full_address` as the database columns, we can save and retrieve them separately too: +```php + +//add all the fields to model fillable property, including the one that we are not going to save (location in the example) +$fillable = ['location', 'latitude', 'longitude', 'full_address']; + +// +protected function location(): \Illuminate\Database\Eloquent\Casts\Attribute +{ + return \Illuminate\Database\Eloquent\Casts\Attribute::make( + get: function($value, $attributes) { + return json_encode([ + 'lat' => $attributes['lat'], + 'lng' => $attributes['lng'], + 'formatted_address' => $attributes['full_address'] ?? '' + ], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_THROW_ON_ERROR); + }, + set: function($value) { + $location = json_decode($value); + return [ + 'lat' => $location->lat, + 'lng' => $location->lng, + 'full_address' => $location->formatted_address ?? '' + ]; + } + ); +} + +``` + +Input preview: + +![image](https://user-images.githubusercontent.com/7188159/208295372-f2dcbe71-73b7-452d-9904-428f725cdbce.png) + +
+ + +### icon_picker PRO + +Show an icon picker. Supported icon sets are fontawesome, glyphicon, ionicon, weathericon, mapicon, octicon, typicon, elusiveicon, materialdesign as per the jQuery plugin, [bootstrap-iconpicker](http://victor-valencia.github.io/bootstrap-iconpicker/). + +The stored value will be the class name (ex: fa-home). + +```php +CRUD::field([ // icon_picker + 'label' => "Icon", + 'name' => 'icon', + 'type' => 'icon_picker', + 'iconset' => 'fontawesome' // options: fontawesome, glyphicon, ionicon, weathericon, mapicon, octicon, typicon, elusiveicon, materialdesign +]); +``` + +Your input will look like button, with a dropdown where the user can search or pick an icon: + +Input preview: + +![CRUD Field - icon_picker](https://backpackforlaravel.com/uploads/docs-4-2/fields/icon_picker.png) + +
+ + +### image PRO + +Upload an image and store it on the disk. + +**Step 1.** Show the field. +```php +// image +CRUD::field([ + 'label' => 'Profile Image', + 'name' => 'image', + 'type' => 'image', + 'crop' => true, // set to true to allow cropping, false to disable + 'aspect_ratio' => 1, // omit or set to 0 to allow any aspect ratio +]); +``` +**NOTE:** `aspect_ratio` is a float that represents the ratio of the cropping rectangle height and width. Eg: Square = 1, Landscape = 2, Portrait = 0.5. You can, of course, use any value for more extreme rectangles. + +**Step 2.** Choose how to handle the file upload process. Starting v6, you have two options: +- **Option 1.** Let Backpack handle the upload process for you. This is by far the most convenient option, because it's the easiest to implement and fully customizable. All you have to do is add the `withFiles => true` attribute to your field definition: +```php +CRUD::field([ + 'name' => 'image', + 'label' => 'Profile Image', + 'type' => 'image', + 'withFiles' => true +]); +``` +To know more about the `withFiles`, how it works and how to configure it, [ click here to read the documentation ](https://backpackforlaravel.com/docs/6.x/crud-uploaders). + +- **Option 2.** Handle the upload process yourself. This is what happened in v5, so if you want to handle the upload by yourself you can [read the v5 upload docs here](https://backpackforlaravel.com/docs/5.x/crud-fields#image-pro). + +Input preview: + +![CRUD Field - image](https://backpackforlaravel.com/uploads/docs-4-2/fields/image.png) + +> NOTE: if you are having trouble uploading big images, please check your php extensions **apcu** and/or **opcache**, users have reported some issues with these extensions when trying to upload very big images. REFS: https://github.com/Laravel-Backpack/CRUD/issues/3457 + +
+ + +### phone PRO + +Show a telephone number input. Lets the user choose the prefix using a flag from dropdown. + +```php +CRUD::field([ // phone + 'name' => 'phone', // db column for phone + 'label' => 'Phone', + 'type' => 'phone', + + // OPTIONALS + // most options provided by intlTelInput.js are supported, you can try them out using the `config` attribute; + // take note that options defined in `config` will override any default values from the field; + 'config' => [ + 'onlyCountries' => ['bd', 'cl', 'in', 'lv', 'pt', 'ro'], + 'initialCountry' => 'cl', // this needs to be in the allowed country list, either in `onlyCountries` or NOT in `excludeCountries` + 'separateDialCode' => true, + 'nationalMode' => true, + 'autoHideDialCode' => false, + 'placeholderNumberType' => 'MOBILE', + ] +]); +``` + +For more info about parameters please see this JS plugin's [official documentation](https://github.com/jackocnr/intl-tel-input). + +Your end result will look like this: + +![CRUD Field - phone](https://user-images.githubusercontent.com/1032474/204588174-48935030-54e6-4a30-b34c-7e94220ae242.png) + +> NOTE: you can validate this using Laravel's default **numeric** or if you want something advanced, we recommend [Laravel Phone](https://github.com/Propaganistas/Laravel-Phone) + +
+ + +### relationship PRO + +Shows the user a `select2` input, allowing them to choose one/more entries of a related Eloquent Model. In order to work, the relationships need to be properly defined on the Model. + +Input preview (for both 1-n and n-n relationships): + +![CRUD Field - relationship](https://backpackforlaravel.com/uploads/docs-4-2/fields/relationship.png) + +To achieve the above, you just need to point the field to the relationship method on your Model (eg. `category`, not `category_id`): + +```php +CRUD::field([ // relationship + 'name' => 'category', // the method on your model that defines the relationship + 'type' => "relationship", + + // OPTIONALS: + // 'label' => "Category", + // 'attribute' => "title", // attribute on model that is shown to user + // 'placeholder' => "Select a category", // placeholder for the select2 input + ]); +``` + +More more optional attributes on relationship fields [look here](#optional-attributes-for-fields-containing-related-entries). + +Out of the box, it supports all common relationships: +- ✅ `hasOne` (1-1) - shows subform if you define `subfields` +- ✅ `belongsTo` (n-1) - shows a select2 (single) +- ✅ `hasMany` (1-n) - shows a select2_multiple OR a subform if you define `subfields` +- ✅ `belongsToMany` (n-n) - shows a select2_multiple OR a subform if you define `subfields` for pivot extras +- ✅ `morphOne` (1-1) - shows a subform if you define `subfields` +- ✅ `morphMany` (1-n) - shows a select2_multiple OR a subform if you define `subfields` +- ✅ `morphToMany` (n-n) - shows a select2_multiple OR a subform if you define `subfields` for pivot extras +- ✅ `morphTo` (n-1) - shows the `_type` and `_id` selects for morphTo relations + +It does NOT support the following Eloquent relationships, since they don't make sense in this context: +- ❌ `hasOneThrough` (1-1-1) - it's read-only, no sense having a field for it; +- ❌ `hasManyThrough` (1-1-n) - it's read-only, no sense having a field for it; +- ❌ Has One Of Many (1-n turned into 1-1) - it's read-only, no sense having a field for it; +- ❌ Morph One Of Many (1-n turned into 1-1) - it's read-only, no sense having a field for it; +- ❌ `morphedByMany` (n-n inverse) - never needed, UI would be very difficult to understand & use at this moment. + +The relationship field is a plug-and-play solution, 90% of the time it will cover all you need by just pointing it to a relationship on the model. But it also has a few optional features, that will greatly help you out in more complex use cases: + + +#### Load entries from AJAX calls - using the Fetch Operation + +If your related entry has hundreds, thousands or millions of entries, it's not practical to load the options using an onpage Eloquent query, because the Create/Update page would be very slow to load. In this case, you should instruct the `relationship` field to fetch the entries using AJAX calls. + +**Step 1.** Add `'ajax' => true` to your relationship field definition: + +```php +CRUD::field([ // relationship + 'type' => "relationship", + 'name' => 'category', // the method on your model that defines the relationship + 'ajax' => true, + + // OPTIONALS: + // 'label' => "Category", + // 'attribute' => "name", // foreign key attribute that is shown to user (identifiable attribute) + // 'entity' => 'category', // the method that defines the relationship in your Model + // 'model' => "App\Models\Category", // foreign key Eloquent model + // 'placeholder' => "Select a category", // placeholder for the select2 input + + // AJAX OPTIONALS: + // 'delay' => 500, // the minimum amount of time between ajax requests when searching in the field + // 'data_source' => url("fetch/category"), // url to controller search function (with /{id} should return model) + // 'minimum_input_length' => 2, // minimum characters to type before querying results + // 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'POST', // optional - HTTP method to use for the AJAX call (GET, POST) + // 'include_all_form_fields' => false, // optional - only send the current field through AJAX (for a smaller payload if you're not using multiple chained select2s) + ]); +``` + +**Step 2.** Create the route and method that responds to the AJAX calls. Fortunately, the `FetchOperation` allows you to easily do just that. Inside the same CrudController where you've defined the `relationship` field, use the `FetchOperation` trait, and define a new method that will respond to AJAX queries for that entity: + +```php + use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation; + + public function fetchCategory() + { + return $this->fetch(\App\Models\Category::class); + } +``` + +This will set up a ```/fetch/category``` route, which points to ```fetchCategory()```, which returns the search results. For more on how this operation works (and how you can customize it), see the [FetchOperation docs](/docs/{{version}}/crud-operation-fetch). + + + +#### Create related entries in a modal - using the InlineCreate Operation + +Works for the `BelongsTo`, `BelongsToMany` and `MorphToMany` relationships. + +Searching with AJAX provides a great UX. But what if the user doesn't find what they're looking for? In that case, it would be useful to add a related entry on-the-fly, without leaving the main form: + +![CRUD Field - relationship fetch with inline create](https://backpackforlaravel.com/uploads/docs-4-2/fields/relationship_inlineCreate.gif) + +If you are using the Fetch operation to get the entries, you're already halfway there. In addition to using Fetch as instructed in the section above, you should perform two additional steps: + +**Step 1.** Add `inline_create` to your field definition in **the current CrudController**: + +```php +// for 1-n relationships (ex: category) +CRUD::field([ + 'type' => "relationship", + 'name' => 'category', + 'ajax' => true, + 'inline_create' => true, // <--- THIS +]); +// for n-n relationships (ex: tags) +CRUD::field([ + 'type' => "relationship", + 'name' => 'tags', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => [ 'entity' => 'tag' ] // <--- OR THIS +]); +// in this second example, the relation is called `tags` (plural), +// but we need to define the entity as "tag" (singural) +``` + +**Step 2.** On the CrudController of that secondary entity (that the user will be able to create on-the-fly, eg. `CategoryCrudController` or `TagCrudController`), you'll need to enable the InlineCreate operation: +```php +class CategoryCrudController extends CrudController +{ + use \Backpack\CRUD\app\Http\Controllers\Operations\InlineCreateOperation; + + // ... +} +``` + +This ```InlineCreateOperation``` will allow us to show _the same fields that are inside the Create operation_, inside a new operation _InlineCreate_, that is available in a modal. For more information, check out the [InlineCreate Operation docs](/docs/{{version}}/crud-operation-inline-create). + +Remember, ```FetchOperation``` is still needed on the main crud (ex: ```ArticleCrudController```) so that the entries are fetched by ```select2``` using AJAX. + +#### Save additional data to pivot table + +For relationships with a pivot table (n-n relationships: `BelongsToMany`, `MorphToMany`), that contain other columns other than the foreign keys themselves, the `relationship` field provides a quick way for your admin to edit those "extra attributes on the pivot table". For example, if you have these database tables: + +```php +// - companies: id, name +// - company_person: company_id, person_id, job_title, job_description +// - persons: id, first_name, last_name +``` + +You might want the admin to define the `job_title` and `job_description` of a person, when creating/editing a company. So instead of this: + + +![CRUD Field - belongsToMany relationship without custom pivot table attributes](https://backpackforlaravel.com/uploads/docs-4-2/fields/relationship_belongsToMany_noPivot.png) + +you might your admin to see this: + +![CRUD Field - belongsToMany relationship with custom pivot table attributes](https://backpackforlaravel.com/uploads/docs-4-2/fields/relationship_belongsToMany_withPivot.png) + + + +The `relationship` field allows you to easily do that. Here's how: + +**Step 1.** On your models, make sure the extra database columns are defined, using `withPivot()`: + +```php +// inside the Company model +public function people() +{ + return $this->belongsToMany(\App\Models\Person::class) + ->withPivot('job_title', 'job_description'); +} +// inside the Person model +public function companies() +{ + return $this->belongsToMany(\App\Models\Company::class) + ->withPivot('job_title', 'job_description'); +} +``` + +**Step 2.** Inside your `relationship` field definition, add `subfields` for those two db columns: + +```php +// Inside PersonCrudController +CRUD::field([ + 'name' => 'companies', + 'type' => "relationship", + // .. + 'subfields' => [ + [ + 'name' => 'job_title', + 'type' => 'text', + 'wrapper' => [ + 'class' => 'form-group col-md-3', + ], + ], + [ + 'name' => 'job_description', + 'type' => 'text', + 'wrapper' => [ + 'class' => 'form-group col-md-9', + ], + ], + ], +]); +``` + +**That's it.** Backpack now will save those additional inputs on the pivot table. + +#### Allow user to select multiple times the same pivot + +By default Backpack does not allow you to select the same pivot twice. If you want to allow the selection of the same pivot more than once you should take some setup steps before. Follow along with the steps below: + +**1)** Make sure your pivot table has a unique key, usually an auto-increment id. If you don't have one, you can add it with a migration. + +**2)** Add the `id` to your `->withPivot()` fields on your relation. Eg: +```php + +$this->belongsToMany(\App\Models\Company::class) + ->withPivot('job_title', 'job_description', 'id'); +``` + +If you unique identifier is not called `id`, you can tell Backpack the appropriate name using `pivot_key_name` in your field definition. Eg: +```php +CRUD::field([ + 'name' => 'companies', + 'type' => 'relationship', + 'pivot_key_name' => 'uuid', + // ... +]); +``` + +**3)** Add the `allow_duplicate_pivots` attribute to your relationship field definition. Eg: +```php +CRUD::field([ + 'name' => 'companies', + 'type' => 'relationship', + 'allow_duplicate_pivots' => true, + 'subfields' => // your subfields (do not add `id` as a subfield. That's done automatically by Backpack). +]); +``` + +#### Configuring the Pivot Select field +If you want to change something about the primary select (the pivot select field created by Backpack), you can do that using the `pivotSelect` attribute: + +```php +CRUD::field([ + 'name' => 'companies', + 'type' => "relationship", + 'subfields' => [ ... ], + // you can use the same configurations you use in any relationship select + // like making it an ajax, constraining the options etc. + 'pivotSelect'=> [ + // 'attribute' => "title", // attribute on model that is shown to user + 'placeholder' => 'Pick a company', + 'wrapper' => [ + 'class' => 'col-md-6', + ], + // by default we call a $model->all(). you can configure it like in any other select + 'options' => function($model) { + return $model->where('type', 'primary'); + }, + // note that just like any other select, enabling ajax will require you to provide an url for the field + // to fetch available options. You can use the FetchOperation or manually create the enpoint. + 'ajax' => true, + 'data_source' => backpack_url('fetch'), + ], +]); +``` + +#### Manage related entries in the same form (create, update, delete) + +Sometimes for `hasMany` and `morphMany` relationships, the secondary entry cannot stand on its own. It's so dependent on the primary entry, that you don't want it to have a Create/Update form of its own - you just want it to be managed inside its parent. Let's take `Invoice` and `InvoiceItem` as an example, with the following database structure: + +```php +// - invoices: id, number, due_date, payment_status +// - invoice_items: id, order, description, unit, quantity, unit_price +``` + +Most likely, what you _really_ want is to be able to create/update/delete `InvoiceItems` right inside the `Invoice` form: + +![CRUD Field - hasMany relationship editing the items on-the-fly](https://backpackforlaravel.com/uploads/docs-4-2/fields/repeatable_hasMany_entries.png) + +To achieve the above, you just need to add a `relationship` field for your `hasMany` or `morphMany` relationship and define the `subfields` you want for that secondary Model: + +```php +CRUD::field([ + 'name' => 'items', + 'type' => "relationship", + 'subfields' => [ + [ + 'name' => 'order', + 'type' => 'number', + 'wrapper' => [ + 'class' => 'form-group col-md-1', + ], + ], + [ + 'name' => 'description', + 'type' => 'text', + 'wrapper' => [ + 'class' => 'form-group col-md-6', + ], + ], + [ + 'name' => 'unit', + 'label' => 'U.M.', + 'type' => 'text', + 'wrapper' => [ + 'class' => 'form-group col-md-1', + ], + ], + [ + 'name' => 'quantity', + 'type' => 'number', + 'attributes' => ["step" => "any"], + 'wrapper' => [ + 'class' => 'form-group col-md-2', + ], + ], + [ + 'name' => 'unit_price', + 'type' => 'number', + 'attributes' => ["step" => "any"], + 'wrapper' => [ + 'class' => 'form-group col-md-2', + ], + ], + ], +]); +``` + +Backpack will then take care of the creating/updating/deleting of the secondary model, after the "Save" button is clicked on the form. + + + +#### Delete related entries or fall back to default + +Normally, when the admin removes a relationship from the "select", only the relationship gets deleted, _not_ the related entry. But for the `hasMany` and `morphMany` relationships, it can also make sense to want to remove the related entry entirely. That's why for those relationships, you can also instruct Backpack to remove the _related entry_ upon saving OR change the foreign key to a default value (fallback). + +```php +// Inside ArticleCrudController +CRUD::field([ + 'type' => "relationship", + 'name' => 'comments', + // when removed, use fallback_id + 'fallback_id' => 3, // will relate to the comment with ID "3" + // when removed, delete the entry + 'force_delete' => true, // will delete that comment +]); +``` + +
+ + +### repeatable PRO + +Shows a group of inputs to the user, and allows the user to add or remove groups of that kind: + +![CRUD Field - repeatable](https://backpackforlaravel.com/uploads/docs-4-2/fields/repeatable.png) + +**Since v5**: repeatable returns an array when the form is submitted instead of the already parsed json. **You must cast** the repeatable field to **ARRAY** or **JSON** in your model. + +Clicking on the "New Item" button will add another group with the same subfields (in the example, another Testimonial). + +You can use most field types inside the field groups, add as many subfields you need, and change their width using ```wrapper``` like you would do outside the repeatable field. But please note that: +- **all subfields defined inside a field group need to have their definition valid and complete**; you can't use shorthands, you shouldn't assume fields will guess attributes for you; +- some field types do not make sense as subfields inside repeatable (for example, relationship fields might not make sense; they will work if the relationship is defined on the main model, but upon save the selected entries will NOT be saved as relationships, they will be saved as JSON; you can intercept the saving if you want and do whatever you want); +- a few fields _make sense_, but _cannot_ work inside repeatable (ex: upload, upload_multiple); [see the notes inside the PR](https://github.com/Laravel-Backpack/CRUD/pull/2266#issuecomment-559436214) for more details, and a complete list of the fields; the few fields that do not work inside repeatable have sensible alternatives; +- **VALIDATION**: you can validate subfields the same way you validate [nested arrays in Laravel](https://laravel.com/docs/8.x/validation#validating-nested-array-input) Eg: `testimonial.*.name => 'required'` +- **FIELD USAGE AND RELATIONSHIPS**: note that it's not possible to use a repeatable field inside other repeatable field. Relationships that use `subfields` are under the hood repeatable fields, so the relationship subfields cannot include other repeatable field. + +```php +CRUD::field([ // repeatable + 'name' => 'testimonials', + 'label' => 'Testimonials', + 'type' => 'repeatable', + 'subfields' => [ // also works as: "fields" + [ + 'name' => 'name', + 'type' => 'text', + 'label' => 'Name', + 'wrapper' => ['class' => 'form-group col-md-4'], + ], + [ + 'name' => 'position', + 'type' => 'text', + 'label' => 'Position', + 'wrapper' => ['class' => 'form-group col-md-4'], + ], + [ + 'name' => 'company', + 'type' => 'text', + 'label' => 'Company', + 'wrapper' => ['class' => 'form-group col-md-4'], + ], + [ + 'name' => 'quote', + 'type' => 'ckeditor', + 'label' => 'Quote', + ], + ], + + // optional + 'new_item_label' => 'Add Group', // customize the text of the button + 'init_rows' => 2, // number of empty rows to be initialized, by default 1 + 'min_rows' => 2, // minimum rows allowed, when reached the "delete" buttons will be hidden + 'max_rows' => 2, // maximum rows allowed, when reached the "new item" button will be hidden + // allow reordering? + 'reorder' => false, // hide up&down arrows next to each row (no reordering) + 'reorder' => true, // show up&down arrows next to each row + 'reorder' => 'order', // show arrows AND add a hidden subfield with that name (value gets updated when rows move) + 'reorder' => ['name' => 'order', 'type' => 'number', 'attributes' => ['data-reorder-input' => true]], // show arrows AND add a visible number subfield +]); +``` + +
+ + +### select2 (1-n relationship) PRO + +Works just like the SELECT field, but prettier. Shows a Select2 with the names of the connected entity and let the user select one of them. +Your relationships should already be defined on your models as hasOne() or belongsTo(). + +```php +CRUD::field([ // Select2 + 'label' => "Category", + 'type' => 'select2', + 'name' => 'category_id', // the db column for the foreign key + + // optional + 'entity' => 'category', // the method that defines the relationship in your Model + 'model' => "App\Models\Category", // foreign key model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'default' => 2, // set the default value of the select2 + + // also optional + 'options' => (function ($query) { + return $query->orderBy('name', 'ASC')->where('depth', 1)->get(); + }), // force the related options to be a custom query, instead of all(); you can use this to filter the results show in the select +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select2](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_nested.png) + + +
+ + +### select2_multiple (n-n relationship) PRO + +[Works just like the SELECT field, but prettier] + +Shows a Select2 with the names of the connected entity and let the user select any number of them. +Your relationship should already be defined on your models as belongsToMany(). + +```php +CRUD::field([ // Select2Multiple = n-n relationship (with pivot table) + 'label' => "Tags", + 'type' => 'select2_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + + // optional + 'entity' => 'tags', // the method that defines the relationship in your Model + 'model' => "App\Models\Tag", // foreign key model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + // 'select_all' => true, // show Select All and Clear buttons? + + // optional + 'options' => (function ($query) { + return $query->orderBy('name', 'ASC')->where('depth', 1)->get(); + }), // force the related options to be a custom query, instead of all(); you can use this to filter the results show in the select +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select2_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_multiple.png) + +
+ + +### select2_nested PRO + +Display a select2 with the values ordered hierarchically and indented, for an entity where you use Reorder. Please mind that the connected model needs: +- a ```children()``` relationship pointing to itself; +- the usual ```lft```, ```rgt```, ```depth``` attributes; + +```php +CRUD::field([ // select2_nested + 'name' => 'category_id', + 'label' => "Category", + 'type' => 'select2_nested', + 'entity' => 'category', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + + // optional + 'model' => "App\Models\Category", // force foreign key model +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select2_nested](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_nested.png) + +
+ + +### select2_grouped PRO + +Display a select2 where the options are grouped by a second entity (like Categories). + +```php +CRUD::field([ // select2_grouped + 'label' => 'Articles grouped by categories', + 'type' => 'select2_grouped', //https://github.com/Laravel-Backpack/CRUD/issues/502 + 'name' => 'article_id', + 'entity' => 'article', // the method that defines the relationship in your Model + 'attribute' => 'title', + 'group_by' => 'category', // the relationship to entity you want to use for grouping + 'group_by_attribute' => 'name', // the attribute on related model, that you want shown + 'group_by_relationship_back' => 'articles', // relationship from related model back to this model +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Input preview: + +![CRUD Field - select2_grouped](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_grouped.png) + + +
+ + +### select_and_order PRO + +Display items on two columns and let the user drag&drop between them to choose which items are selected and which are not, and reorder the selected items with drag&drop. + +Its definition is exactly as ```select_from_array```, but the value will be stored as JSON in the database: ```["3","5","7","6"]```, so it needs the attribute to be cast to array on the Model: + +```php +protected $casts = [ + 'featured' => 'array' +]; +``` + +Definition: + +```php +CRUD::field([ // select_and_order + 'name' => 'featured', + 'label' => "Featured", + 'type' => 'select_and_order', + 'options' => [ + 1 => "Option 1", + 2 => "Option 2" + ] +]); +``` + +Also possible: + +```php +CRUD::field([ // select_and_order + 'name' => 'featured', + 'label' => 'Featured', + 'type' => 'select_and_order', + 'options' => Product::get()->pluck('title','id')->toArray(), +]); +``` + +Input preview: + +![CRUD Field - select_and_order](https://backpackforlaravel.com/uploads/docs-4-2/fields/select_and_order.png) + + +
+ + +### select2_from_array PRO + +Display a select2 with the values you want: + +```php +CRUD::field([ // select2_from_array + 'name' => 'template', + 'label' => "Template", + 'type' => 'select2_from_array', + 'options' => ['one' => 'One', 'two' => 'Two'], + 'allows_null' => false, + 'default' => 'one', + // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; +]); +``` + +Input preview: + +![CRUD Field - select2_from_array](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_from_array.png) + +
+ + +### select2_from_ajax PRO + +Display a select2 that takes its values from an AJAX call. + +```php +CRUD::field([ // 1-n relationship + 'label' => "End", // Table column heading + 'type' => "select2_from_ajax", + 'name' => 'category_id', // the column that contains the ID of that connected entity + 'entity' => 'category', // the method that defines the relationship in your Model + 'attribute' => "name", // foreign key attribute that is shown to user + 'data_source' => url("api/category"), // url to controller search function (with /{id} should return model) + + // OPTIONAL + // 'delay' => 500, // the minimum amount of time between ajax requests when searching in the field + // 'placeholder' => "Select a category", // placeholder for the select + // 'minimum_input_length' => 2, // minimum characters to type before querying results + // 'model' => "App\Models\Category", // foreign key model + // 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'POST', // optional - HTTP method to use for the AJAX call (GET, POST) + // 'include_all_form_fields' => false, // optional - only send the current field through AJAX (for a smaller payload if you're not using multiple chained select2s) +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Of course, you also need to create make the data_source above respond to AJAX calls. You can use the [FetchOperation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-fetch) to quickly do that in your current CrudController, or you can set up your custom API by creating a custom Route and Controller. Here's an example: + +```php +Route::post('/api/category', 'Api\CategoryController@index'); +``` + +```php +input('q'); + + if ($search_term) + { + $results = Category::where('name', 'LIKE', '%'.$search_term.'%')->paginate(10); + } + else + { + $results = Category::paginate(10); + } + + return $results; + } +} +``` + +**Note:** If you want to also make this field work inside `repeatable` too, your API endpoint will also need to respond to the `keys` parameter, with the actual items that have those keys. For example: + +```php + if ($request->has('keys')) { + return Category::findMany($request->input('keys')); + } +``` + +Input preview: + +![CRUD Field - select2_from_array](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_from_array.png) + +
+ + +### select2_from_ajax_multiple PRO + +Display a select2 that takes its values from an AJAX call. Same as [select2_from_ajax](#section-select2_from_ajax) above, but allows for multiple items to be selected. The only difference in the field definition is the "pivot" attribute. + +```php +CRUD::field([ // n-n relationship + 'label' => "Cities", // Table column heading + 'type' => "select2_from_ajax_multiple", + 'name' => 'cities', // a unique identifier (usually the method that defines the relationship in your Model) + 'entity' => 'cities', // the method that defines the relationship in your Model + 'attribute' => "name", // foreign key attribute that is shown to user + 'data_source' => url("api/city"), // url to controller search function (with /{id} should return model) + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + + // OPTIONAL + 'delay' => 500, // the minimum amount of time between ajax requests when searching in the field + 'model' => "App\Models\City", // foreign key model + 'placeholder' => "Select a city", // placeholder for the select + 'minimum_input_length' => 2, // minimum characters to type before querying results + // 'method' => 'POST', // optional - HTTP method to use for the AJAX call (GET, POST) + // 'include_all_form_fields' => false, // optional - only send the current field through AJAX (for a smaller payload if you're not using multiple chained select2s) +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +Of course, you also need to create a controller and routes for the data_source above. Here's an example: + +```php +Route::post('/api/city', 'Api\CityController@index'); +Route::post('/api/city/{id}', 'Api\CityController@show'); +``` + +```php +input('q'); + $page = $request->input('page'); + + if ($search_term) + { + $results = City::where('name', 'LIKE', '%'.$search_term.'%')->paginate(10); + } + else + { + $results = City::paginate(10); + } + + return $results; + } + + public function show($id) + { + return City::find($id); + } +} +``` + +Input preview: + +![CRUD Field - select2_from_ajax_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/select2_from_ajax_multiple.png) + +**Note:** If you want to also make this field work inside `repeatable` too, your API endpoint will also need to respond to the `keys` parameter, with the actual items that have those keys. For example: + +```php + if ($request->has('keys')) { + return City::findMany($request->input('keys')); + } +``` + +
+ +### select2_json_from_api PRO + +Display a select2 that takes its values from an AJAX call. +Similar to [select2_from_ajax](#section-select2_from_ajax) above, but this one is not limited to a single entity. It can be used to select any JSON object from an API. + +```php +CRUD::field([ + 'label' => 'Airports', // Displayed column label + 'type' => 'select2_json_from_api', + 'name' => 'airports', // the column where this field will be stored + 'data_source' => url('airports/fetch/list'), // the endpoint used by this field + + // OPTIONAL + 'delay' => 500, // the minimum amount of time between ajax requests when searching in the field + 'method' => 'POST', // route method, either GET or POST + 'placeholder' => 'Select an airport', // placeholder for the select + 'minimum_input_length' => 2, // minimum characters to type before querying results + 'multiple' => true, // allow multiple selections + 'include_all_form_fields' => false, // only send the current field through AJAX (for a smaller payload if you're not using multiple chained select2s) + + // OPTIONAL - if the response is a list of objects (and not a simple array) + 'attribute' => 'title', // attribute to show in the select2 + 'attributes_to_store' => ['id', 'title'], // attributes to store in the database +]); +``` + +For more information about the optional attributes that fields use when they interact with related entries - [look here](#optional-attributes-for-fields-containing-related-entries). + +You may create a controller and routes for the data_source above. Here's an example using the FetchOperation, including a search term: +Note that this example is for a non paginated response, but `select2_json_from_api` also accepts a paginated response. + +```php +// use the FetchOperation to quickly create an endpoint +use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation; +``` + +```php +public function fetchAirports() +{ + $types = [ + ['id' => 'OPO', 'city' => 'Porto', 'title' => 'Francisco Sá Carneiro Airport'], + ['id' => 'LIS', 'city' => 'Lisbon', 'title' => 'Humberto Delgado Airport'], + ['id' => 'FAO', 'city' => 'Faro', 'title' => 'Faro Airport'], + ['id' => 'FNC', 'city' => 'Funchal', 'title' => 'Cristiano Ronaldo Airport'], + ['id' => 'PDL', 'city' => 'Ponta Delgada', 'title' => 'João Paulo II Airport'], + ]; + + return collect($types)->filter(fn(array $value): bool => str_contains(strtolower($value['title']), strtolower(request('q')))); +} +``` + +A simple array with a key value pair will also work: + +```php +public function fetchAirports() +{ + $types = [ + 'OPO' => 'Francisco Sá Carneiro Airport', + 'LIS' => 'Humberto Delgado Airport', + 'FAO' => 'Faro Airport', + 'FNC' => 'Cristiano Ronaldo Airport', + 'PDL' => 'João Paulo II Airport', + ]; + + return collect($types)->filter(fn(string $value): bool => str_contains(strtolower($value), strtolower(request('q')))); +} +``` + +#### Storing only one the id in the database + +A very common use case you may have is to store only the id of the selected item in the database instead of a `json` string. For those cases you can achieve that by setting the `attributes_to_store` attribute to an array with only one item, the id of the selected item and do a little trick with the model events to store the id you want, and to give the field that id in a way it understands. + +```php + +CRUD::field([ + 'label' => 'Airports', + 'type' => 'select2_json_from_api', + 'name' => 'airport_id', // dont make your column json if not storing json on it! + // .... the rest your field configuration + 'attribute' => 'id', + 'attributes_to_store' => ['id'], + 'events' => [ + 'saving' => function($entry) { + $entry->airport_id = json_decode($entry->airport_id ?? [], true)['id'] ?? null; + 'retrieved' => function($entry) { + $entry->airport_id = json_encode(['id' => $entry->airport_id]); + } + ] +]); + +``` + +
+ +### slug PRO + +Track the value of a different text input and turn it into a valid URL segment (aka. slug), as you type, using Javascript. Under the hood it uses [slugify](https://github.com/simov/slugify/blob/master/README.md) to generate the slug with some sensible defaults. + +```php +CRUD::field([ // Text + 'name' => 'slug', + 'target' => 'title', // will turn the title input into a slug + 'label' => "Slug", + 'type' => 'slug', + + // optional + 'locale' => 'pt', // locale to use, defaults to app()->getLocale() + 'separator' => '', // separator to use + 'trim' => true, // trim whitespace + 'lower' => true, // convert to lowercase + 'strict' => true, // strip special characters except replacement + 'remove' => '/[*+~.()!:@]/g', // remove characters to match regex, defaults to null + ]); +``` + +Input preview: +![CleanShot 2022-06-04 at 13 13 40](https://user-images.githubusercontent.com/1032474/171994919-cbdd8b9d-6823-4b26-82ed-7c2868c0cee8.gif) + + +By default, it will also slugify when the target input is edited. If you want to stop that behaviour, you can do that by removing the `target` on your edit operation. For example: + +```php + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); + + // disable editing the slug when editing + CRUD::field('slug')->target('')->attributes(['readonly' => 'readonly']); + } +``` + +
+ + +### table PRO + +Show a table with multiple inputs per row and store the values as JSON array of objects in the database. The user can add more rows and reorder the rows as they please. + +```php +CRUD::field([ // Table + 'name' => 'options', + 'label' => 'Options', + 'type' => 'table', + 'entity_singular' => 'option', // used on the "Add X" button + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price' + ], + 'max' => 5, // maximum rows allowed in the table + 'min' => 0, // minimum rows allowed in the table +]); +``` + +>It's highly recommended that you use [attribute casting](https://mattstauffer.com/blog/laravel-5.0-eloquent-attribute-casting) on your model when working with JSON arrays stored in database columns, and cast this attribute to either ```object``` or ```array``` in your Model. + +##### Using the table in a repeatable field + +When using this field in a [repeatable field](#repeatable) as subfield, you need to take ensure this field is not double encoded. For that you can overwrite the store and update methods in your CrudController. Here's an example: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { + store as traitStore; +} +use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation { + update as traitUpdate; +} + +public function update($id) +{ + $this->decodeTableFieldsFromRequest(); + return $this->traitUpdate($id); +} + +public function store() +{ + $this->decodeTableFieldsFromRequest(); + return $this->traitStore(); +} + +private function decodeTableFieldsFromRequest() +{ + $request = $this->crud->getRequest(); + $repeatable = $request->get('repeatable'); // change to your repeatable field name + + if(is_array($repeatable)) { + array_map(function($item) { + $item['table_field_name'] = json_decode($item['table_field_name'] ?? '', true); // change to your table field name + return $item; + }, $repeatable); + } + $request->request->set('repeatable', $repeatable); // change to your repeatable field name + $this->crud->setRequest($request); +} + +``` + +Input preview: + +![CRUD Field - table](https://backpackforlaravel.com/uploads/docs-4-2/fields/table.png) + + +
+ + +### tinymce PRO + +Show a wysiwyg (TinyMCE) to the user. + +```php +CRUD::field([ // TinyMCE + 'name' => 'description', + 'label' => 'Description', + 'type' => 'tinymce', + // optional overwrite of the configuration array + // 'options' => [ + //'selector' => 'textarea.tinymce', + //'skin' => 'dick-light', + //'plugins' => 'image link media anchor' + // ], +]); +``` + +Input preview: + +![CRUD Field - tinymce](https://backpackforlaravel.com/uploads/docs-4-2/fields/tinymce.png) + +**NOTE**: if you want to modify the toolbar buttons (add or remove), here is the default configured toolbar so you can modify it: + +```php +'options' => ['toolbar' => 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent'], +``` + +Some buttons are related to specific plugins and need them to work, please read more about it here: [tiny mce available toolbar buttons](https://www.tiny.cloud/docs/advanced/available-toolbar-buttons/) + +
+ + +### video PRO + +Allow the user to paste a YouTube/Vimeo link. That will get the video information with JavaScript and store it as a JSON in the database. + +Field definition: +```php +CRUD::field([ // URL + 'name' => 'video', + 'label' => 'Link to video file on YouTube or Vimeo', + 'type' => 'video', + 'youtube_api_key' => 'AIzaSycLRoVwovRmbIf_BH3X12IcTCudAErRlCE', +]); +``` + +An entry stored in the database will look like this: +``` +$video = { + id: 234324, + title: 'my video title', + image: 'https://provider.com/image.jpg', + url: 'http://provider.com/video', + provider: 'youtube' +} +``` + +So you should use [attribute casting](https://mattstauffer.com/blog/laravel-5.0-eloquent-attribute-casting) in your model, to cast the video as ```array``` or ```object```. + +Vimeo does not require an API key in order to query their DB, but YouTube does, even though their free quota is generous. You can get a free YouTube API Key inside [Google Developers Console](https://console.developers.google.com/) ([video tutorial here](https://www.youtube.com/watch?v=pP4zvduVAqo)). Please DO NOT use our API Key - create your own. The key above is there just for your convenience, to easily try out the field. As soon as you decide to use this field type, create an API Key and use _your_ API Key. Our key hits its ceiling every month, so if you use our key most of the time it won't work. + + +
+ + +### wysiwyg PRO + +Show a wysiwyg (CKEditor) to the user. + +```php +CRUD::field([ // WYSIWYG Editor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'wysiwyg', + + // optional configuration + 'options' => [], // ckeditor configuration options + + // elfinder configuration options when using [the file manager package](https://github.com/Laravel-Backpack/FileManager) + // to use this feature you need to be running backpack/pro:2.2.1 or higher and backpack/filemanager:3.0.8 or higher + // for `elfinderOptions` passing an empty array or `true` will enable the file manager with default options + 'elfinderOptions' => [], +]); +``` + + +## Overwriting Default Field Types + +The actual field types are stored in the Backpack/CRUD package in ```/resources/views/fields```. If you need to change an existing field, you don't need to modify the package, you just need to add a blade file in your application in ```/resources/views/vendor/backpack/crud/fields```, with the same name. The package checks there first, and only if there's no file there, will it load it from the package. + +To quickly publish a field blade file in your project, you can use ```php artisan backpack:field --from=field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:field --from=number``` + +>Please keep in mind that if you're using _your_ file for a field type, you're not using the _package file_. So any updates we push to that file, you're not getting them. In most cases, it's recommended you create a custom field type for your use case, instead of overwriting default field types. + + +## Creating a Custom Field Type + +If you need to extend the CRUD with a new field type, you create a new file in your application in ```/resources/views/vendor/backpack/crud/fields```. Use a name that's different from all default field types. + +```bash +// to create one using Backpack\Generators, run: +php artisan backpack:field new_field_name + +// alternatively, to create a new field similar an existing field, run: +php artisan backpack:field new_field_name --from=old_field_name +``` + + +That's it, you'll now be able to use it just like a default field type. + +Your field definition will be something like: + +```php +CRUD::field([ // Custom Field + 'name' => 'address', + 'label' => 'Home address', + 'type' => 'address' + /// 'view_namespace' => 'yourpackage' // use a custom namespace of your package to load views within a custom view folder. +]); +``` + +And your blade file something like: +```php + +@include('crud::fields.inc.wrapper_start') + + + + {{-- HINT --}} + @if (isset($field['hint'])) +

{!! $field['hint'] !!}

+ @endif +@include('crud::fields.inc.wrapper_end') + +{{-- FIELD EXTRA CSS --}} +{{-- push things in the after_styles section --}} +@push('crud_fields_styles') + + +@endpush + + +{{-- FIELD EXTRA JS --}} +{{-- push things in the after_scripts section --}} +@push('crud_fields_scripts') + + +@endpush +``` + +Inside your custom field type, you can use these variables: +- ```$crud``` - all the CRUD Panel settings, options and variables; +- ```$entry``` - in the Update operation, the current entry being modified (the actual values); +- ```$field``` - all attributes that have been passed for this field; + +If your field type uses JavaScript, we recommend you: +- put a ```data-init-function="bpFieldInitMyCustomField"``` attribute on your input; +- place your logic inside the scripts section mentioned above, inside ```function bpFieldInitMyCustomField(element) {}```; of course, you choose the name of the function but it has to match whatever you specified as data attribute on the input, and it has to be pretty unique; inside this method, you'll find that ```element``` is jQuery-wrapped object of the element where you specified ```data-init-function```; this should be enough for you to not have to use IDs, or any other tricks, to determine other elements inside the DOM - determine them in relation to the main element; if you want, you can choose to put the ```data-init-function``` attribute on a different element, like the wrapping div; + +
+ + +## Advanced Fields Use + + +### Manipulating Fields with JavaScript + +When you need to add custom interactions (if field is X then do Y), we have just the thing for you. You can easily add custom interactions, using our **CrudField JavaScript API**. It's already loaded on our Create / Update pages, in the global `crud` object, and it makes it dead-simple to select a field - `crud.field('title')` - using a syntax that's very familiar to our PHP syntax, then do the most common things on it. + +For more information, please see the dedicated page about our [CrudField Javascript API](/docs/{{version}}/crud-fields-javascript-api). + + + +### Adding new methods to the CrudField class + +You can add your own methods Backpack CRUD fields, so that you can do `CRUD::field('name')->customThing()`. You can easily do that, because the `CrudField` class is Macroable. It's as easy as this: + +```php +use Backpack\CRUD\app\Library\CrudPanel\CrudField; + +// register media upload macros on CRUD fields +if (! CrudField::hasMacro('customThing')) { + CrudField::macro('customThing', function ($firstParamExample = [], $secondParamExample = null) { + /** @var CrudField $this */ + + // TODO: do anything you want to $this + + return $this; + }); +} +``` + +A good place to do this would be in your AppServiceProvider, in a custom service provider. That way you have it across all your CRUD panels. diff --git a/7.x-dev/crud-filters.md b/7.x-dev/crud-filters.md new file mode 100644 index 00000000..6c5d370b --- /dev/null +++ b/7.x-dev/crud-filters.md @@ -0,0 +1,651 @@ +# Filters PRO + +--- + + +## About + +Backpack allows you to show a filters bar right above the entries table. When selected or modified, they reload the DataTable. The search bar will also take filters into account, only looking within filtered results. + +![Backpack CRUD Filters](https://backpackforlaravel.com/uploads/docs-4-0/filters/filters.png) + +Just like with fields, columns or buttons, you can add existing filters or create a custom filter that fits to your particular needs. Everything's done inside your ```EntityCrudController::setupListOperation()```. + +> **Note:** This is a PRO feature. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/pricing). + + +### Filters API + +In order to manipulate filters, you can use: + +```php +// on one filter +CRUD::filter($name) + ->type($type) + ->whenActive($closure) + ->whenInactive($closure) + ->apply(); + +CRUD::filter($name)->remove(); +CRUD::filter($name)->makeFirst(); +CRUD::filter($name)->makeLast(); +CRUD::filter($name)->before($different_filter_name); +CRUD::filter($name)->after($different_filter_name); + +// on all filters +CRUD::removeAllFilters(); // removes all the filters +CRUD::filters(); // gets all the filters +``` + + +### Adding and configuring a filter + +Inside your `setupListOperation()` you can add or select a filter using `CRUD::filter('name')`, then chain methods to completely configure it: + +```php +CRUD::filter('name') + ->type('text') + ->label('The name') + ->whenActive(function($value) { + CRUD::addClause('where', 'name', 'LIKE', '%'.$value.'%'); + })->else(function() { + // nada + }); +``` + +Anything you chain to the ```filter()``` method gets turned into an attribute on that filter. Except for the methods listed below. Keep in mind **in most cases you WILL need to chain ```type()```, ```whenActive()```** and maybe even ```whenInactive()``` or ```apply()```. Details below. + + + +#### Main Chained Methods + +- ```->type('date')``` - By chaining **type()** on a filter you make sure you use that filter type; it accepts a string that represents the type of filter you want to use; + +```php +CRUD::filter('birthday')->type('date'); +``` + +- ```->label('Name')``` - By chaining **label()** on a filter you define what is shown to the user as the filter label; it accepts a string; + +```php +CRUD::filter('name')->label('Name'); +``` + +- ```->whenActive(function($value) {})``` - By chaining **whenActive()** on a filter you define what should be done when that filter is active; it accepts a closure that is called when the filter is active - either immediately by further chaining ```apply()``` on the filter, or automatically after all filters are defined, by the List operation itself; you can also use ```->logic()``` or ```->ifActive()``` which are its aliases: + +```php +// whenActive method, applied immediately +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + CRUD::addClause('where', 'active', '1'); + })->apply(); + +// whenActive, left to be applied by the operation itself +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + CRUD::addClause('where', 'active', '1'); + }); +``` + +- ```->whenInactive(function($value) {})``` - By chaining **whenInactive()** on a filter you define what should be done when that filter is NOT active; it accepts a closure that is called when the filter is NOT active - either immediately by further chaining ```apply()``` on the filter, or automatically after all filters are defined, by the List operation itself; you can also use ```->else()```, ```->fallbackLogic()```, ```->whenNotActive()```, ```->ifInactive()``` and ```->ifNotActive()``` which are its aliases: + +```php +// fallbackLogic method, applied immediately +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + CRUD::addClause('where', 'active', '1'); + })->whenInactive(function ($value) { + CRUD::addClause('where', 'active', '0'); + })->apply(); + +// else alias, left to be applied by the operation itself +CRUD::filter('active') + ->type('simple') + ->label('Simple') + ->whenActive(function ($value) { + CRUD::addClause('where', 'active', '1'); + })->else(function ($value) { + CRUD::addClause('where', 'active', '0'); + }); +``` + +- ```->apply()``` - By chaining **apply()** on a filter after you've specified the filtering logic using `whenActive()` or `whenInactive()`, you immediately call the appropriate closure; in most cases this isn't necessary, because the List operation automatically performs an `apply()` on all filters; but in some cases, where the filtering closures want to stop or change execution, you can use `apply()` to have that logic applied before the next bits of code get executed; + + +#### Other Chained Methods + +If you chain the following methods to a ```CRUD::filter('name')```, they will do something very specific instead of adding that attribute to the filter: + +- ```->remove()``` - By chaining **remove()** on a filter you remove it from the current operation; + +```php +CRUD::filter('name')->remove(); +``` + +- ```->forget('attribute_name')``` - By chaining **forget('attribute_name')** to a filter you remove that attribute from the filter; + +```php +CRUD::filter('price')->prefix('$'); // will have "$" as prefix +CRUD::filter('price')->forget('prefix'); // will no longer have "$" as prefix + +// Note: +// You can only call "forget" on filter attributes. Calling "forget" on "before", +// "after", "whenActive", "whenInactive" etc. will do nothing, because those +// are not attributes, they are methods. You can, however, forget filter logic +// or fallback logic by using their attributes: +CRUD::filter('price')->forget('logic'); +CRUD::filter('price')->forget('fallbackLogic'); +``` + +- ```->after('destination')``` - By chaining **after('destination_filter_name')** you will move the current filter after the given filter; + +```php +CRUD::filter('name')->after('description'); +``` + +- ```->before('destination')``` - By chaining **before('destination_filter_name')** you will move the current filter before the given filter; + +```php +CRUD::filter('name')->before('description'); +``` + +- ```->makeFirst()``` - By chaining **makeFirst()** you will make the current filter the first one for the current operation; + +```php +CRUD::filter('name')->makeFirst(); +``` + +- ```->makeLast()``` - By chaining **makeLast()** you will make the current filter the last one for the current operation; + +```php +CRUD::filter('name')->makeLast(); +``` + + +#### Filter logic closures + +Backpack filters do not contain any default filtering _logic_, because it cannot infer that. If you use a filter and don't specify a filter logic, no filtering of entries will actually happen. You have to define that, inside a _closure_. + +Filter logic closures are just an anonymous functions that gets executed when the filter is active. You can use any Laravel or Backpack functionality you want inside them. For example, a valid closure would be: + +```php +CRUD::filter('draft') ->whenActive(function($value) { + CRUD::addClause('where', 'draft', 1); +}); +``` + +Notes about the filter logic closure: +- the code will only be run on the controller's ```index()``` or ```search()``` methods; +- you can get the filter value by specifying a parameter to the function (ex: ```$value```); +- you have access to other request variables using ```$this->crud->getRequest()```; +- you also have read/write access to public properties using ```$this->crud```; +- when building complicated "OR" logic, make sure the first "where" in your closure is a "where" and only the subsequent are "orWhere"; Laravel 5.3+ no longer converts the first "orWhere" into a "where"; + + + +## Filter Types + + +### Simple + +Only shows a label and can be toggled on/off. Useful for things like active/inactive and easily paired with [Eloquent Scopes](https://laravel.com/docs/5.3/eloquent#local-scopes). The "Draft" and "Has Video" filters in the screenshot below are simple filters. + +![Backpack CRUD Simple Filter](https://user-images.githubusercontent.com/1838187/159197347-e38fc63b-ceb8-4806-98dc-1e10773a57cd.png) + +```php +CRUD::filter('active') + ->type('simple') + ->whenActive(function() { + // CRUD::addClause('active'); // apply the "active" eloquent scope + }); +``` + +
+ + +### Text + +Shows a text input. Most useful for letting the user filter through information that's not shown as a column in the CRUD table - otherwise they could just use the DataTables search field. + +![Backpack CRUD Text Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/text.png) + +```php +CRUD::filter('description') + ->type('text') + ->whenActive(function($value) { + // CRUD::addClause('where', 'description', 'LIKE', "%$value%"); + }); +``` + +
+ + + +### Date + +Show a datepicker. The user can select one day. + +![Backpack CRUD Date Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/date.png) + +```php +CRUD::filter('birthday') + ->type('date') + ->whenActive(function($value) { + // CRUD::addClause('where', 'date', $value); + }); +``` + +
+ + +### Date range + +Show a daterange picker. The user can select a start date and an end date. + +![Backpack CRUD Date Range Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/date_range.png) + +```php +CRUD::filter('from_to') + ->type('date_range') + // set options to customize, www.daterangepicker.com/#options + ->date_range_options([ + 'timePicker' => true // example: enable/disable time picker + ]) + ->whenActive(function($value) { + // $dates = json_decode($value); + // CRUD::addClause('where', 'date', '>=', $dates->from); + // CRUD::addClause('where', 'date', '<=', $dates->to); + }); +``` + +
+ + +### Dropdown + +Shows a list of elements (that you provide) in a dropdown. The user can only select one of these elements. + +![Backpack CRUD Dropdown Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/dropdown.png) + +```php +CRUD::filter('status') + ->type('dropdown') + ->values([ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]) + ->whenActive(function($value) { + // CRUD::addClause('where', 'status', $value); + }); +``` + +
+ + + +### Select2 + +Shows a select2 and allows the user to select one item from the list or search for an item. Useful when the values list is long (over 10 elements). + +![Backpack CRUD Select2 Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2.png) + +```php +CRUD::filter('status') + ->type('select2') + ->values(function () { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; + }) + ->whenActive(function($value) { + // CRUD::addClause('where', 'status', $value); + }); +``` + +**Note:** If you want to pass all entries of a Laravel model to your filter, you can do it in the closure with something like `return \App\Models\Category::all()->keyBy('id')->pluck('name', 'id')->toArray();` + +
+ + +### Select2_multiple + +Shows a select2 and allows the user to select one or more items from the list or search for an item. Useful when the values list is long (over 10 elements) and your user should be able to select multiple elements. + +![Backpack CRUD Select2_multiple Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2_multiple.png) + +```php +CRUD::filter('status') + ->type('select2_multiple') + ->values(function () { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; + }) + ->whenActive(function($values) { + // CRUD::addClause('whereIn', 'status', json_decode($values)); + }); +``` + +**Note:** If you want to pass all entries of a Laravel model to your filter, you can do it in the closure with something like `return \App\Models\Category::all()->keyBy('id')->pluck('name', 'id')->toArray();` + +
+ + +### Select2_ajax + +Shows a select2 and allows the user to select one item from the list or search for an item. This list is fetched through an AJAX call by the select2. Useful when the values list is long (over 1000 elements). + +![Backpack CRUD Select2_ajax Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2_ajax.png) + +**Option (A) Use the FetchOperation to return the results** + +Since Backpack already provides an operation that returns results from the DB, to be shown in Select2 fields, we can use that to populate the select2_ajax filter: + +Step 1. In your CrudController, set up the [FetchOperation](/docs/{{version}}/crud-operation-fetch) to return the entity you want: + +```php + use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation; + + protected function fetchCategory() + { + return $this->fetch(\App\Models\Category::class); + } +``` + +Step 2. Use this filter and make sure you specify the method as "POST": +```php +CRUD::filter('category_id') + ->type('select2_ajax') + ->values(backpack_url('product/fetch/category')) + ->method('POST') + ->whenActive(function($value) { + // CRUD::addClause('where', 'category_id', $value); + }); + + // other methods + // ->placeholder('Pick a category') + // ->select_attribute('name') // the attribute that will be shown to the user by default 'name' + // ->select_key('id') // by default is ID, change it if your model uses some other key +``` + +**Option (B) Use a custom controller to return the results** + +Alternatively, you can use a completely custom endpoint, that returns the options for the select2: + +Step 1. Add a route for the ajax search, right above your usual ```CRUD::resource()``` route. Example: + +```php +Route::get('test/ajax-category-options', 'TestCrudController@categoryOptions'); +CRUD::resource('test', 'TestCrudController'); +``` + +Step 2. Add a method to your EntityCrudController that responds to a search term. The result should be an array with ```id => value```. Example for a 1-n relationship: + +```php +public function categoryOptions(Request $request) { + $term = $request->input('term'); + $options = App\Models\Category::where('name', 'like', '%'.$term.'%')->get()->pluck('name', 'id'); + // optionally you can return a paginated instance: App\Models\Category::where('name', 'like', '%'.$term.'%')::paginate(10) + return $options; +} +``` + +Step 3. Add the select2_ajax filter and for the second parameter ("values") specify the exact route. + +```php +CRUD::filter('category_id') + ->type('select2_ajax') + ->values(url('admin/test/ajax-category-options')) + ->whenActive(function($value) { + // CRUD::addClause('where', 'category_id', $value); + }); + + // other methods: + // ->placeholder('Pick a category') + // ->method('POST') // by default it's GET + + // when returning a paginated instance you can specify the attribute and the key to be used: + // ->select_attribute('title') // by default it's name + // ->select_key('custom_key') // by default it's id +``` + +
+ + +### Range + +Shows two number inputs, for min and max. + +![Backpack CRUD Range Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/range.png) + +```php +CRUD::filter('number') + ->type('range') + ->whenActive(function($value) { + $range = json_decode($value); + // if ($range->from) { + // CRUD::addClause('where', 'number', '>=', (float) $range->from); + // } + // if ($range->to) { + // CRUD::addClause('where', 'number', '<=', (float) $range->to); + // } + }); + + // other methods + // label_from('min value') + // label_to('max value) +``` + +
+ + +### View + +Display any custom column filter you want. Usually used by Backpack package developers, to use views from within their packages, instead of having to publish them. + +```php +CRUD::filter('category_id') + ->type('view') + ->view('package::columns.column_type_name') // or path to blade file + ->whenActive(function($value) { + // CRUD::addClause('where', 'category_id', $value); + }); +``` + + + +## Creating custom filters + +Creating a new filter type is as easy as using the template below and placing a new view in your ```resources/views/vendor/backpack/crud/filters``` folder. You can then call this new filter by its view's name (ex: ```custom_select.blade.php``` will mean your filter type is called ```custom_select```). + +The filters bar is actually a [bootstrap navbar](http://getbootstrap.com/components/#navbar) at its core, but slimmer. So adding a new filter will be just like adding a menu item (for the HTML). Start from the ```text``` filter below and build your functionality. + +Inside this file, you'll have: +- ```$filter``` object - includes everything you've defined on the current field; +- ```$crud``` - the CrudPanel object; + +```php +{{-- Text Backpack CRUD filter --}} + + + +{{-- ########################################### --}} +{{-- Extra CSS and JS for this particular filter --}} + + +{{-- FILTERS EXTRA JS --}} +{{-- push things in the after_scripts section --}} + +@push('crud_list_scripts') + + +@endpush +{{-- End of Extra CSS and JS --}} +{{-- ########################################## --}} +``` + +
+ + +## Examples + +Use a dropdown to filter by the values of a MySQL ENUM column: + +```php +CRUD::filter('published') + ->type('select2') + ->values(function() { + return \App\Models\Test::getEnumValuesAsAssociativeArray('published'); + }) + ->whenActive(function($value) { + CRUD::addClause('where', 'published', $value); + }); +``` + +Use a select2 to filter by a 1-n relationship: +```php +CRUD::filter('category_id') + ->type('select2') + ->values(function() { + return \App\Models\Category::all()->pluck('name', 'id')->toArray(); + }) + ->whenActive(function($value) { + CRUD::addClause('where', 'category_id', $value); + }); +``` + +Use a select2_multiple to filter Products by an n-n relationship: +```php +CRUD::filter('tags') + ->type('select2_multiple') + ->values(function() { // the options that show up in the select2 + return Product::all()->pluck('name', 'id')->toArray(); + }) + ->whenActive(function($values) { + foreach (json_decode($values) as $key => $value) { + $this->crud->query = $this->crud->query->whereHas('tags', function ($query) use ($value) { + $query->where('tag_id', $value); + }); + } + }); +``` + +Use a simple filter to add a scope if the filter is active: +```php +// add a "simple" filter called Published +CRUD::filter('published') + ->type('simple') + ->whenActive(function() { // if the filter is active (the GET parameter "published" exits) + CRUD::addClause('published'); + }); +``` + +Use a simple filter to show the softDeleted items (trashed): +```php +CRUD::filter('trashed') + ->type('simple') + ->whenActive(function($values) { + $this->crud->query = $this->crud->query->onlyTrashed(); + }); +``` + + +## Tips and Tricks + + +### Adding a filter using array syntax + +In Backpack v4-v5 we used an "array syntax" to add and manipulate filters. That syntax is still supported for backwards-compatiblity. But it most cases it's easier to use the fluent syntax. + +When adding a filter using the array syntax you need to specify the 3 parameters of the ```addFilter()``` method: +- `$options` - an array of options (name, type, label are most important) +- `$values` - filter values - can be an array or a closure +- `$filter_logic` - what should happen if the filter is applied (usually add a clause to the query) - can be a closure, a string for a simple operation or false for a simple "where"; + +Here's a simple example, with comments that explain what we're doing: +```php +// add a "simple" filter called Draft +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'draft', + 'label' => 'Draft' +], +false, // the simple filter has no values, just the "Draft" label specified above +function() { // if the filter is active (the GET parameter "draft" exits) + CRUD::addClause('where', 'draft', '1'); + // we've added a clause to the CRUD so that only elements with draft=1 are shown in the table + // an alternative syntax to this would have been + // $this->crud->query = $this->crud->query->where('draft', '1'); + // another alternative syntax, in case you had a scopeDraft() on your model: + // CRUD::addClause('draft'); +}); +``` diff --git a/7.x-dev/crud-fluent-syntax.md b/7.x-dev/crud-fluent-syntax.md new file mode 100644 index 00000000..f087704f --- /dev/null +++ b/7.x-dev/crud-fluent-syntax.md @@ -0,0 +1,722 @@ +# CRUD Fluent API + +--- + + + +## About + +Starting with Backpack 4.1, working with Fields, Columns, Filters, Buttons and Widgets **inside your EntityCrudController** can also be done using a fluent syntax. For example, instead of doing: + +```php +$this->crud->addField([ // Number + 'name' => 'price', + 'label' => 'Price', + 'type' => 'number', + 'prefix' => "$", + 'suffix' => ".00", +]); +``` + +You can now do: +```php +$this->crud->field('price') + ->type('number') + ->label('Price') + ->prefix('$') + ->suffix('.00'); +``` + +But you can go a little further, by using the CrudPanel class at the top of your controller with an alias: + +```php +use Backpack\CRUD\app\Library\CrudPanel\CrudPanel as CRUD; + +CRUD::field('price') + ->type('number') + ->label('Price') + ->prefix('$') + ->suffix('.00'); +``` + +Or maybe even condense it on just one line: +```php +CRUD::field('price')->type('number')->label('Price')->prefix('$')->suffix('.00'); +``` + +Those who prefer this new fluent syntax do so because: +- method chains have better highlighting and suggestions in most IDEs; +- method chains take up slightly fewer lines of code than arrays; +- method chains are faster to write & modify than arrays; +- you no longer have to decide if you're adding or modifying a field, since ```CRUD::field()``` basically functions as a ```CRUD::addOrModifyField()```; +- it allows us to add methods that are exclusive to the fluent syntax, that will make our lives easier; for example, to make a field take up only 6 bootstrap columns, using the non-fluent syntax you'd have to write ```'wrapper' => ['class' => 'form-group col-md-6'],``` - but using the fluent syntax you can just do ```size(6)```; + +But keep in mind that it does have downsides: it's more difficult to debug and arguably makes it more difficult to understand how the admin panel works. Developers who are not already comfortable with Backpack might not understand that: +- referencing ```$this->crud``` is the same thing as ```CRUD``` because it's actually a ```singleton```, a "global" instance of the ```CrudPanel``` object, which gets defined in the Controller and is then read inside the views; +- the fluent syntax merely turns those chained methods into an array, which gets stored inside ```$this->crud``` like it does with ```addField()``` or ```modifyField()```; + + +## Fluent Fields + +These methods should be used inside your CrudController for operations that use Fields, most likely inside the ```setupCreateOperation()``` or ```setupUpdateOperation()``` methods. + + +### General + +```field('name')``` - By specifying **field('name')** you add a field with that name to the current operation, at the end of the stack, or modify the field that already exists with that name; it accepts a single parameter, a string, that will become that field's ```name```; needs to be called directly, not chained; +```php +CRUD::field('name'); +``` + +Anything you chain to the ```field()``` method gets turned into an attribute on that field. Except for the methods below. + + +### Chained Methods + +If you chain the following methods to a ```CRUD::field('name')```, they will do something very specific instead of adding that attribute to the field: + +- ```->remove()``` - By chaining **remove()** on a field you remove it from the current operation; + +```php +CRUD::field('name')->remove(); +``` + +- ```->forget('attribute_name')``` - By chaining **forget('attribute_name')** to a field you remove that attribute from the field definition array; + +```php +CRUD::field('name')->forget('suffix'); +``` + +- ```->after('destination')``` - By chaining **after('destination_field_name')** you will move the current field after the given field; + +```php +CRUD::field('name')->after('description'); +``` + +- ```->before('destination')``` - By chaining **before('destination_field_name')** you will move the current field before the given field; + +```php +CRUD::field('name')->before('description'); +``` + +- ```->makeFirst()``` - By chaining **makeFirst()** you will make the current field the first one for the current operation; + +```php +CRUD::field('name')->makeFirst(); +``` + +- ```->makeLast()``` - By chaining **makeLast()** you will make the current field the last one for the current operation; + +```php +CRUD::field('name')->makeLast(); +``` + +- ```->size(6)``` - By chaining **size(4)** you will make the field span across this many bootstrap columns (instead of the default 12 columns which is a full row); it accepts a single parameter, an integer from 1 to 12; for more information and to see how you can create convenience methods like this one, see [the PR](https://github.com/Laravel-Backpack/CRUD/pull/2638); + +```php +CRUD::field('name')->size(6); + +// alternative to +CRUD::addField([ + 'name' => 'name', + 'wrapper' => ['class' => 'form-group col-md-6'], +]); +``` + +- Do you have an idea for a new chained method aka. convenience method? [Let us know](https://github.com/laravel-backpack/crud/issues). + + +### Examples + +```php +// a text field +CRUD::field('last_name'); + +// an email field, put inside a tab and resized to half the width +CRUD::field('email')->type('email')->size(6)->tab('Simple'); + +// a number field with prefix and suffix (stored as fake in extras) +CRUD::field('price')->type('number')->prefix('$')->suffix(".00")->fake(true); + +// a date picker field with custom options +CRUD::field('birthday') + ->type('date_picker') + ->label('Birthday') + ->date_picker_options([ + 'todayBtn' => true, + 'format' => 'dd-mm-yyyy', + 'language' => 'en', + ]) + ->size(6); + +// a select field, half the width +CRUD::field('category_id') + ->type('select') + ->label('Category') + ->entity('category') + ->attribute('name') + // ->model('Backpack\NewsCRUD\app\Models\Category') // optional; guessed from entity; + // ->wrapper(['class' => 'form-group col-md-6']) // possible, but easier with size below; + ->size(6); + +// a select2_from_ajax field +CRUD::field('article') + ->type('select2_from_ajax') + ->label("Article") + ->entity('article') + // ->attribute('title') // starting with Backpack 4.1 this is optional & guessed + // ->model('Backpack\NewsCRUD\app\Models\Article') // optional; guessed; + ->data_source(url('api/article')) + ->placeholder('Select an article') + ->minimum_input_length(2); + +// a relationship field for an n-n relationship +// also uses the Fetch and InlineCreate operations +CRUD::field('products') + ->type('relationship') + ->label('Products') + // ->entity('products') // optional + // ->attribute('name') // optional + ->ajax(true) + ->data_source(backpack_url('monster/fetch/product')) + ->inline_create(['entity' => 'product']) + // ->wrapper(['class' => 'form-group col-md-6']) + ->tab('Others'); +``` + + +## Fluent Columns + +These methods should be used inside your CrudController for operations that use Columns, most likely inside the ```setupListOperation()``` or ```setupShowOperation()``` methods. + + + +### General + +- ```column('name')``` - By specifying **column('name')** you add a column with that name to the current operation, at the end of the stack, or modify the column that already exists with that name; takes a single parameter, a string, that will become that column's ```name``` and ```key```; needs to be called directly, not chained; +```php +CRUD::column('name'); +``` + +Anything you chain to the ```column()``` method gets turned into an attribute on that column. Except for the methods below: + + + +### Chained Methods + +If you chain the following methods to a ```CRUD::column('name')```, they will do something very specific instead of adding that attribute to the column: + +- ```->remove()``` - By chaining **remove()** on a column you remove it from the current operation; + +```php +CRUD::column('name')->remove(); +``` + +- ```->forget('attribute_name')``` - By chaining **forget('attribute_name')** to a column you remove that attribute from the column definition array; + +```php +CRUD::column('name')->forget('suffix'); +``` + +- ```->after('destination')``` - By chaining **after('destination_column_name')** you will move the current column after the given column; + +```php +CRUD::column('name')->after('description'); +``` + +- ```->before('destination')``` - By chaining **before('destination_column_name')** you will move the current column before the given column; + +```php +CRUD::column('name')->before('description'); +``` + +- ```->makeFirst()``` - By chaining **makeFirst()** you will make the current column the first one for the current operation; + +```php +CRUD::column('name')->makeFirst(); +``` + +- ```->makeLast()``` - By chaining **makeLast()** you will make the current column the last one for the current operation; + +```php +CRUD::column('name')->makeLast(); +``` + + + +### Examples + +```php +// a text column +CRUD::column('last_name'); + +// a textarea column +CRUD::column('description')->type('textarea'); + +// an image column +CRUD::column('profile_photo')->type('image'); + +// a select column with links +CRUD::column('select') + ->type('select') + ->entity('category') + ->attribute('name') + ->model("Backpack\NewsCRUD\app\Models\Category") + ->wrapper([ + 'href' => function ($crud, $column, $entry, $related_key) { + return backpack_url('category/'.$related_key.'/show'); + }, + ]); + +// a select_multiple column +CRUD::column('tags')->type('select_multiple')->entity('tags'); + +// a select_multiple column with everything explicitly defined, plus links +CRUD::column('tags') + ->type('select_multiple') + ->label('Select_multiple') + ->entity('tags') + ->attribute('name') + ->model('Backpack\NewsCRUD\app\Models\Tag') + ->wrapper([ + 'href' => function ($crud, $column, $entry, $related_key) { + return backpack_url('tag/'.$related_key.'/show'); + }, + ]); + +// a checkbox column that turns a boolean into green labels if true +CRUD::column('active') + ->type('boolean') + ->label('Active') + ->options([0 => 'Yes', 1 => 'No']) + ->wrapper([ + 'element' => 'span', + 'class' => function ($crud, $column, $entry, $related_key) { + if ($column['text'] == 'Yes') { + return 'badge badge-success'; + } + + return 'badge badge-default'; + }, + ]); + +// a select_from_array column +CRUD::column('status') + ->type('select_from_array') + ->label('Status') + ->options(['1' => 'New', '2' => 'Processing', '3' => 'Delivered']); + +// a model function column, with custom search logic +CRUD::column('text_and_email') + ->type('model_function') + ->label('Text and Email') + ->function_name('getTextAndEmailAttribute') + ->searchLogic(function ($query, $column, $searchTerm) { + $query->orWhere('email', 'like', '%'.$searchTerm.'%'); + $query->orWhere('text', 'like', '%'.$searchTerm.'%'); + }); +``` + + +## Fluent Buttons + +These methods should be used inside your CrudController for operations that use Buttons, most likely inside the ```setupListOperation()``` or ```setupShowOperation()``` methods. + + + +### General + +- ```button('name')``` - By specifying **button('name')** you add a button with that name to the current operation, at the end of the stack, or modify the button that already exists with that name; takes a single parameter, a string, that will become that button's ```name```; needs to be called directly, not chained; +```php +CRUD::button('name'); +``` + +Anything you chain to the ```button()``` method gets turned into an attribute on that button. Except for the methods listed below. Keep in mind in most cases you will still need to chain ```stack```, ```view``` and maybe ```type``` to this method, to define those attributes. Details in the examples section below. + + + +### Chained Methods + +If you chain the following methods to a ```CRUD::button('name')```, they will do something very specific instead of adding that attribute to the button: + +- ```->remove()``` - By chaining **remove()** on a button you remove it from the current operation; + +```php +CRUD::button('name')->remove(); +``` + +- ```->forget('attribute_name')``` - By chaining **forget('attribute_name')** to a button you remove that attribute from the button; + +```php +CRUD::button('name')->forget('suffix'); +``` + +- ```->after('destination')``` - By chaining **after('destination_button_name')** you will move the current button after the given button; + +```php +CRUD::button('name')->after('description'); +``` + +- ```->before('destination')``` - By chaining **before('destination_button_name')** you will move the current button before the given button; + +```php +CRUD::button('name')->before('description'); +``` + +- ```->makeFirst()``` - By chaining **makeFirst()** you will make the current button the first one for the current operation; + +```php +CRUD::button('name')->makeFirst(); +``` + +- ```->makeLast()``` - By chaining **makeLast()** you will make the current button the last one for the current operation; + +```php +CRUD::button('name')->makeLast(); +``` + + + +### Examples + +```php +// --------- +// Example 1 +// --------- +// instead of +$this->crud->addButton('top', 'create', 'view', 'crud::buttons.create'); +// you can now do +CRUD::button('create')->stack('top')->view('crud::buttons.create'); + +// --------- +// Example 2 +// --------- +// instead of +$this->crud->addButton('line', 'update', 'view', 'crud::buttons.update', 'end'); +// you can now do +CRUD::button('edit')->stack('line')->view('crud::buttons.edit'); + +// --------- +// Example 3 +// --------- +// instead of +$this->crud->addButtonFromModelFunction('line', 'open_google', 'openGoogle', 'beginning'); +// you can now do +CRUD::button('open_google') + ->stack('line') + ->type('model_function') + ->content('openGoogle') + ->makeFirst(); + +// --------- +// Example 4 +// --------- +// instead of +$this->crud->addButtonFromView('top', 'create', 'crud::buttons.create', 'beginning'); +// you can now do +CRUD::button('create')->stack('top')->view('crud::buttons.create'); + +// --------- +// Example 5 +// --------- +// instead of +$this->crud->removeButton('create'); +// you can now do +CRUD::button('create')->remove(); + +// ------ +// Extras +// ------ +// but you don't have to give it a name, so you can also do +CRUD::button()->stack('line')->type('model_function')->content('openGoogle')->makeFirst(); +// and we also have helpers for setting both the type to view/model_function and its content +CRUD::button()->stack('line')->modelFunction('openGoogle')->makeFirst(); + +// ------- +// Aliases +// ------- +// the "stack" attribute can also be set using the "group", "section" and "to" aliases +// all of the calls below do the exact same thing +CRUD::buton('create')->stack('top')->view('crud::butons.create'); +CRUD::buton('create')->to('top')->view('crud::butons.create'); +CRUD::buton('create')->group('top')->view('crud::butons.create'); +CRUD::buton('create')->section('top')->view('crud::butons.create'); +``` + + + +## Fluent Filters + + +These methods should be used inside your CrudController for operations that use Filters, most likely inside ```setupListOperation()```. + + + +### General + +```filter('name')``` - By specifying **filter('name')** you add a filter with that name to the current operation, at the end of the stack, or modify the filter that already exists with that name; takes a single parameter, a string, that will become that filter's ```name```; needs to be called directly, not chained; +```php +CRUD::filter('name'); +``` + +Anything you chain to the ```filter()``` method gets turned into an attribute on that filter. Except for the methods listed below. Keep in mind in most cases you will still need to chain ```type```, ```logic```, ```fallbackLogic``` and maybe ```apply``` to this method, to define those attributes and apply the appropriate logic. Details in the examples section below. + + +### Main Chained Methods + +- ```->type('date')``` - By chaining **type()** on a filter you make sure you use that filter type; it accepts a string that represents the type of filter you want to use; + +```php +CRUD::filter('birthday')->type('date'); +``` + +- ```->label('Name')``` - By chaining **label()** on a filter you define what is shown to the user as the filter label; it accepts a string; + +```php +CRUD::filter('name')->label('Name'); +``` + +- ```->logic(function($value) {})``` - By chaining **logic()** on a filter you define should be done when that filter is active; it accepts a closure that is called when the filter is active - either immediately by further chaining ```apply()``` on the filter, or automatically after all filters are defined, by the List operation itself; you can also use ```->whenActive()``` or ```->ifActive()``` which are its aliases: + +```php +// logic method +CRUD::filter('active') + ->type('simple') + ->logic(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->apply(); + +// whenActive alias +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->apply(); + +// ifActive alias, left to be applied by the operation itself +CRUD::filter('active') + ->type('simple') + ->whenActive(function ($value) { + $this->crud->addClause('where', 'active', '1'); + }); +``` + +- ```->fallbackLogic(function($value) {})``` - By chaining **fallbackLogic()** on a filter you define should be done when that filter is NOT active; it accepts a closure that is called when the filter is NOT active - either immediately by further chaining ```apply()``` on the filter, or automatically after all filters are defined, by the List operation itself; you can also use ```->else()```, ```->whenInactive()```, ```->whenNotActive()```, ```->ifInactive()``` and ```->ifNotActive()``` which are its aliases: + +```php +// fallbackLogic method, applied immediately +CRUD::filter('active') + ->type('simple') + ->logic(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->fallbackLogic(function ($value) { + $this->crud->addClause('where', 'active', '0'); + })->apply(); + +// else alias, left to be applied by the operation itself +CRUD::filter('active') + ->type('simple') + ->label('Simple') + ->ifActive(function ($value) { + $this->crud->addClause('where', 'active', '1'); + })->else(function ($value) { + $this->crud->addClause('where', 'active', '0'); + }); +``` + + +### Other Chained Methods + +If you chain the following methods to a ```CRUD::filter('name')```, they will do something very specific instead of adding that attribute to the filter: + +- ```->remove()``` - By chaining **remove()** on a filter you remove it from the current operation; + +```php +CRUD::filter('name')->remove(); +``` + +- ```->forget('attribute_name')``` - By chaining **forget('attribute_name')** to a filter you remove that attribute from the filter; + +```php +CRUD::filter('name')->forget('suffix'); +``` + +- ```->after('destination')``` - By chaining **after('destination_filter_name')** you will move the current filter after the given filter; + +```php +CRUD::filter('name')->after('description'); +``` + +- ```->before('destination')``` - By chaining **before('destination_filter_name')** you will move the current filter before the given filter; + +```php +CRUD::filter('name')->before('description'); +``` + +- ```->makeFirst()``` - By chaining **makeFirst()** you will make the current filter the first one for the current operation; + +```php +CRUD::filter('name')->makeFirst(); +``` + +- ```->makeLast()``` - By chaining **makeLast()** you will make the current filter the last one for the current operation; + +```php +CRUD::filter('name')->makeLast(); +``` + + + +### Examples + +```php +// instead of +CRUD::addFilter([ + 'type' => 'simple', + 'name' => 'checkbox', + 'label' => 'Simple', +], +false, +function () { + $this->crud->addClause('where', 'checkbox', '1'); +}); + +// you can do +CRUD::filter('checkbox') + ->type('simple') + ->label('Simple') + ->logic(function($value) { + $this->crud->addClause('where', 'checkbox', '1'); + })->apply(); + +// or +CRUD::filter('checkbox') + ->type('simple') + ->label('Simple') + ->whenActive(function($value) { // filter logic + $this->crud->addClause('where', 'checkbox', '1'); + })->whenInactive(function($value) { // fallback logic + $this->crud->addClause('inactive'); + })->apply(); + +// or +CRUD::filter('checkbox') + ->type('simple') + ->label('Simple') + ->ifActive(function($value) { // filter logic + $this->crud->addClause('where', 'checkbox', '1'); + })->else(function($value) { // fallback logic + $this->crud->addClause('inactive'); + })->apply(); + +// ------------------- +// you can also now do +// ------------------- +CRUD::filter('select_from_array')->label('Modified Dropdown'); +CRUD::filter('select_from_array')->whenActive(function($value) { + dd('select_from_array filter logic got modified'); +})->apply(); +CRUD::filter('select_from_array')->remove(); +CRUD::filter('select_from_array')->forget('label'); +CRUD::filter('select_from_array')->after('text'); +CRUD::filter('select_from_array')->before('text'); +CRUD::filter('select_from_array')->makeFirst(); +CRUD::filter('select_from_array')->makeLast(); + +// -------------- +// other examples +// -------------- +// checkbox filter +CRUD::filter('checkbox') + ->type('simple') + ->label('Simple') + ->logic(function($value) { + $this->crud->addClause('where', 'checkbox', '1'); + })->apply(); + +// select_from_array filter +CRUD::filter('select_from_array') + ->type('dropdown') + ->label('DropDOWN') + ->values([ + 'one' => 'One', + 'two' => 'Two', + 'three' => 'Three' + ]) + ->whenActive(function($value) { + $this->crud->addClause('where', 'select_from_array', $value); + }) + ->apply(); + +// text filter +CRUD::filter('text') + ->type('text') + ->label('Text') + ->whenActive(function($value) { + $this->crud->addClause('where', 'text', 'LIKE', "%$value%"); + })->apply(); + +// number filter +CRUD::filter('number') + ->type('range') + ->label('Range')->label_from('min value')->label_to('max value') + ->whenActive(function($value) { + $range = json_decode($value); + if ($range->from && $range->to) { + $this->crud->addClause('where', 'number', '>=', (float) $range->from); + $this->crud->addClause('where', 'number', '<=', (float) $range->to); + } + })->apply(); + +// date filter +CRUD::filter('date') + ->type('date') + ->label('Date') + ->whenActive(function($value) { + $this->crud->addClause('where', 'date', '=', $value); + })->apply(); + +// date_range filter +CRUD::filter('date_range') + ->type('date_range') + ->label('Date range') + ->whenActive(function($value) { + $dates = json_decode($value); + $this->crud->addClause('where', 'date', '>=', $dates->from); + $this->crud->addClause('where', 'date', '<=', $dates->to); + })->apply(); + +// select2 filter +CRUD::filter('select2') + ->type('select2') + ->label('Select2') + ->values(function() { + return \Backpack\NewsCRUD\app\Models\Category::all()->keyBy('id')->pluck('name', 'id')->toArray(); + }) + ->whenActive(function($value) { + $this->crud->addClause('where', 'select2', $value); + })->apply(); + +// select2_multiple filter +CRUD::filter('select2_multiple') + ->type('select2_multiple') + ->label('S2 multiple') + ->values(function() { + return \Backpack\NewsCRUD\app\Models\Category::all()->keyBy('id')->pluck('name', 'id')->toArray(); + }) + ->whenActive(function($value) { + foreach (json_decode($values) as $key => $value) { + $this->crud->addClause('orWhere', 'select2', $value); + } + })->apply(); + +// select2_from_ajax filter +CRUD::filter('select2_from_ajax') + ->type('select2_ajax') + ->label('S2 Ajax') + ->placeholder('Pick an article') + ->values('api/article-search') + ->whenActive(function($value) { + $this->crud->addClause('where', 'select2_from_ajax', $value); + })->apply(); +``` diff --git a/7.x-dev/crud-how-to.md b/7.x-dev/crud-how-to.md new file mode 100644 index 00000000..ddf976ee --- /dev/null +++ b/7.x-dev/crud-how-to.md @@ -0,0 +1,1007 @@ +# FAQs for CRUDs + +--- + +In addition the usual CRUD functionality, Backpack also allows you to do a few more complicated things: + + + +## Routes + + +### How to add extra CRUD routes + +Starting with Backpack\CRUD 4.0, routes are defined inside the Controller, in methods that look like ```setupOperationNameRoutes()```; you can use this naming convention to setup extra routes, for your custom operations: + +```php +protected function setupModerateRoutes($segment, $routeName, $controller) { + Route::get($segment.'/{id}/moderate', [ + 'as' => $routeName.'.moderate', + 'uses' => $controller.'@moderate', + 'operation' => 'moderate', + ]); + + Route::post($segment.'/{id}/moderate', [ + 'as' => $routeName.'.saveModeration', + 'uses' => $controller.'@saveModeration', + 'operation' => 'moderate', + ]); +} +``` + +If you want the route to point to a different controller, you can add the route in ```routes/backpack/custom.php``` instead. + + +## Views + + +### How to customize views for each CRUD panel + +Backpack loads its views through a double-fallback mechanism: +- by default, it will load the views in the vendor folder (the package views); +- if you've included views with the exact same name in your ```resources/views/vendor/backpack/*``` folder, it will pick up those instead; you can use this method to overwrite a blade file for the whole application. +- alternatively, if you only want to change a blade file for one CRUD, you can use the methods below in your ```setup()``` method, to change a particular view: +```php +CRUD::setShowView('your-view'); +CRUD::setEditView('your-view'); +CRUD::setCreateView('your-view'); +CRUD::setListView('your-view'); +CRUD::setReorderView('your-view'); +CRUD::setDetailsRowView('your-view'); +``` + + +### How to add CSS or JS to a page or operation + +If you want to add extra CSS or JS to a certain page, use the `script` and `style` widgets to add a new file of that type onpage, either from your CrudController or a custom blade file: + +```php +use Backpack\CRUD\app\Library\Widget; + +// script widget - works the same for both local paths and CDN +Widget::add()->type('script')->content('assets/js/custom-script.js'); + +Widget::add()->type('script')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + +Widget::add()->type('script') + ->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js') + ->integrity('sha256-0YPKAwZP7Mp3ALMRVB2i8GXeEndvCq3eSl/WsAl1Ryk=') + ->crossorigin('anonymous'); + +// style widget - works the same for both local paths and CDN +Widget::add()->type('style')->content('assets/css/custom-style.css'); + +Widget::add()->type('style')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); + +Widget::add()->type('style') + ->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css') + ->integrity('sha256-0YPKAwZP7Mp3ALMRVB2i8GXeEndvCq3eSl/WsAl1Ryk=') + ->crossorigin('anonymous'); +``` + +For more details please see the `script` and `style` sections in the [Widgets](/docs/{{version}}/base-widgets) page. + +You can limit where that CSS/JS is added by making the `Widget::add()` call in the right place in your CrudController: +- if you do `Widget::add()` inside the `setupListOperation()` method, it will only be loaded there; +- if you do `Widget::add()` inside the `setup()` method, it will be loaded on all pages for that CRUD; +- if you want it to be loaded on all pages for all CRUDs, you can create a CustomCrudController that extends our CrudController, do it there and then make sure all your CRUDs extend `CustomCrudController`; +- if you want it to be loaded on all pages (even non-CRUD like dashboards) you can add the CSS/JS file on all pages by adding it in your `config/backpack/base.php`, under `scripts` and `styles`; + + +## Design + + +### How to customize CRUD Panel design (CSS hooks) + +Our CRUD panel design is the result of 7+ years of feedback, from both admins and developers. Each iteration has made it better and better, to the point where admins find it very intuitive to do everything they need, and Backpack is lauded for how intuitive its design is. So we do _not_ recommend moving components around. + +However, you might want to change the styling - colors, border, padding etc. Especially if you're creating a new theme. For that purpose, we have made sure all CRUD operations have the `bp-section` attributes in key elements, so you can easily and reliably target them. For example: + +- List operation + - `bp-section=page-header` for the page header (between breadcrumbs and content) + - `bp-section=crud-operation-reorder` for the content +- Create operation + - `bp-section=page-header` for the page header (between breadcrumbs and content) + - `bp-section=crud-operation-create` for the content +- Update operation + - `bp-section=page-header` for the page header (between breadcrumbs and content) + - `bp-section=crud-operation-update` for the content +- Show operation + - `bp-section=page-header` for the page header (between breadcrumbs and content) + - `bp-section=crud-operation-show` for the content +- Reorder operation + - `bp-section=page-header` for the page header (between breadcrumbs and content) + - `bp-section=crud-operation-reorder` for the content + +This is a very simple yet effective solution for your custom CSS or JS to target the header or target specific operations, since each operation has its content wrapped around an element with `bp-section=crud-operation-xxx`. + + +## Columns + + +### How to use the same column name multiple times in a CRUD + +If you try to add multiple columns with the same ```name```, by default Backpack will only show the last one. That's because ```name``` is also used as a key in the ```$column``` array. So when you ```addColumn()``` with the same name twice, it just overwrites the previous one. + +In order to insert two columns with the same name, use the ```key``` attribute on the second column (or both columns). If this attribute is present for a column, Backpack will use ```key``` instead of ```name```. Example: + +```diff + CRUD::column([ + 'label' => "Location", + 'type' => 'select', + 'name' => 'location_id', + 'entity' => 'location', + 'attribute' => 'title', + 'model' => "App\Models\Location" + ]); + + CRUD::column([ + 'label' => "Location Type", + 'type' => 'radio_location_type', + 'options' => [ + 1 => "Country", + 2 => "Region" + ], + 'name' => 'location_id', ++ 'key' => 'location_type', + 'entity' => 'location', + 'attribute' => 'type', + 'model' => "App\Models\Location" + ]); +``` + + +## Fields + + +### How to publish a column / field / filter / button and modify it + +All Backpack packages allow you to easily overwrite and customize the views. If you want Backpack to pick up _your_ file, instead of the one in the package, you can do that by just placing a file with the same name in your views. So if you want to overwrite the select2 field (```vendor/backpack/crud/src/resources/views/fields/select2.blade.php```) to change some functionality, you need to create ```resources/views/vendor/backpack/crud/fields/select2.blade.php```. You can do that manually, or use this command: +```shell +php artisan backpack:field --from=select2 +``` +This will look for the blade file and copy it inside the folder, so you can edit it. + +>**Please note:** Once you place a file with the same name inside your project, Backpack will pick that one up instead of the one in the package. That means that even though the file in the package is updated, you won't be getting those updates, since you're not using that file. Blade modifications are almost never breaking changes, but it's a good thing to receive updates with zero effort. Even small ones. So please overwrite the files as little as possible. Best to create your own custom fields/column/filters/buttons, whenever you can. + + +### How to filter the options in a select field + +This also applies to: select2, select2_multiple, select2_from_ajax, select2_from_ajax_multiple. + +There are 3 possible solutions: +1. If there will be few options (dozens), the easiest way would be to use ```select_from_array``` or ```select2_from_array``` instead. Since you tell it what the options are, you can do any filtering you want there. +2. If there are a lot of options (100+), best to use ```select2_from_ajax``` instead. You'll be able to filter the results any way you want in the controller method (the one that responds with the results to the AJAX call). +3. If you can't use ```select2_from_array``` for ```select2_from_ajax```, you can create another model and add a global scope to it. For example, say you only want to show the users that belong to the user's company. You can create ```App\Models\CompanyUser``` and use that in your ```select2``` field instead of ```App\Models\User```: + +```php +company_id) { + $companyId = Auth::user()->company->id; + + static::addGlobalScope('company_id', function (Builder $builder) use ($companyId) { + $builder->where('company_id', $companyId); + }); + } + } +} +``` + + + +### How to load fields from a different folder + +If you're developing a package, you might need Backpack to pick up fields from your package folder, instead of having to publish them upon installation. + +Fields, Columns and Filters all have a ```view_namespace``` parameter you can use. Type your folder there, and Backpack will check that folder first, then where the views are published, then Backpack's package folder. Example: + +```php +CRUD::addFilter([ // add a "simple" filter called Draft + 'type' => 'complex', + 'name' => 'checkbox', + 'label' => 'Checked', + 'view_namespace' => 'custom_filters' +], +false, // the simple filter has no values, just the "Draft" label specified above +function () { // if the filter is active (the GET parameter "draft" exits) + CRUD::addClause('where', 'checkbox', '1'); +}); +``` +This will make Backpack look for the ```resources/views/custom_filters/complex.blade.php```, and pick that up before anything else. + + +### How to add a relationship field that depends on another field + +The `relationship`, `select2_from_ajax` and `select2_from_ajax_multiple` fields allow you to filter the results depending on what has already been written or selected in a form. Say you have two `select2` fields, when the AJAX call is made to the second field, all other variables in the page also get passed - that means you can filter the results of the second `select2`. + +Say you want to show two selects: +- the first one shows Categories +- the second one shows Articles, but only from the category above + +1. In your CrudController you would do: + +```php +// select2 +CRUD::addField([ + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category', + 'entity' => 'category', + 'attribute' => 'name', +]); + +// select2_from_ajax: 1-n relationship +CRUD::addField([ + 'label' => "Article", // Table column heading + 'type' => 'select2_from_ajax_multiple', + 'name' => 'articles', // the column that contains the ID of that connected entity; + 'entity' => 'article', // the method that defines the relationship in your Model + 'attribute' => 'title', // foreign key attribute that is shown to user + 'data_source' => url('api/article'), // url to controller search function (with /{id} should return model) + 'placeholder' => 'Select an article', // placeholder for the select + 'include_all_form_fields' => true, //sends the other form fields along with the request so it can be filtered. + 'minimum_input_length' => 0, // minimum characters to type before querying results + 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'POST', // optional - HTTP method to use for the AJAX call (GET, POST) +]); +``` + +**DIFFERENT HERE**: ```minimum_input_length```, ```dependencies``` and ```include_all_form_fields```. + +Note: if you are going to use `include_all_form_fields` we recommend you to set the method to `POST`, and to properly setup that in your routes. Since all the fields in the form are going to be sent in the request, `POST` support more data. + +2. That second select points to routes that need to be registered: + +```php +Route::post('api/article', 'App\Http\Controllers\Api\ArticleController@index'); +Route::post('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show'); +``` + +**DIFFERENT HERE**: Nothing. + +3. Then that controller would look something like this: + +```php +input('q'); // the search term in the select2 input + + // if you are inside a repeatable we will send some aditional data to help you + $triggeredBy = $request->input('triggeredBy'); // you will have the `fieldName` and the `rowNumber` of the element that triggered the ajax + + // NOTE: this is a Backpack helper that parses your form input into an usable array. + // you still have the original request as `request('form')` + $form = backpack_form_input(); + + $options = Article::query(); + + // if no category has been selected, show no options + if (! $form['category']) { + return []; + } + + // if a category has been selected, only show articles in that category + if ($form['category']) { + $options = $options->where('category_id', $form['category']); + } + + if ($search_term) { + $results = $options->where('title', 'LIKE', '%'.$search_term.'%')->paginate(10); + } else { + $results = $options->paginate(10); + } + + return $results; + } + + public function show($id) + { + return Article::find($id); + } +} +``` + +**DIFFERENT HERE**: We use ```$form``` to determine what other variables have been selected in the form, and modify the result accordingly. + + + +### What field should I use for a relationship? + +With so many field types, it can be a little overwhelming to understand what field type to use for a _particular_ Eloquent relationship. Here's a quick summary of all possible relationships, and the interface you might want for them. Click on the relationship you're interested in, for more details and an example: + + +- **[hasOne (1-1)](#hasone-1-1-relationship)** ✅ + - (A) show a subform - add a `relationship` field with `subfields` + - (B) show a separate field for each attribute on the related entry - add any field type, with dot notation for the field name (`passport.title`) +- **[belongsTo (n-1)](#belongsto-n-1-relationship)** ✅ + - show a select2 (single) - add a `relationship` field +- **[hasMany (1-n)](#hasmany-1-n-relationship)** ✅ + - (A) show a select2_multiple - add a `relationship` field + - (B) show a subform - add a `relationship` field with `subfields` +- **[belongsToMany (n-n)](#belongstomany-n-n-relationship)** ✅ + - (A) show a select2_multiple - add a `relationship` field + - (B) show a subform - add a `relationship` field and define `subfields` +- **[morphOne (1-1)](#morphone-1-1-polymorphic-relationship)** ✅ + - (A) show a subform - add a `relationship` field with `subfields` + - (B) show a separate field for each attribute on the related entry - add any field type, with dot notation for the field name (`passport.title`) +- **[morphMany (1-n)](#morphmany-1-n-polymorphic-relationship)** ✅ + - (A) show a select2_multiple - add a `relationship` field + - (B) show a subform - add a `relationship` field with `subfields` +- **[morphToMany (n-n)](#morphtomany-n-n-polymorphic-relationship)** ✅ + - (A) show a select2_multiple - add a `relationship` field + - (B) show a subform - add a `relationship` field and define `subfields` +- **[morphTo (n-1)](#morphto-n-1-relationship)** ✅ + - Manage both `_type` and `_id` of the morphTo relation; +- **[hasOneThrough (1-1-1)](#hasonethrough-1-1-1-relationship)** ❌ + - it's read-only, no sense having a field for it; +- **[hasManyThrough (1-1-n)](#hasmanythrough-1-1-n-relationship)** ❌ + - it's read-only, no sense having a field for it; +- **[Has One Of Many (1-n turned into 1-1)](#has-one-of-many-1-1-relationship-out-of-1-n-relationship)** ❌ + - it's read-only, no sense having a field for it; +- **[Morph One Of Many (1-n turned into 1-1)](#morph-one-of-many-1-1-relationship-out-of-1-n-relationship)** ❌ + - it's read-only, no sense having a field for it; +- **[morphedByMany (n-n inverse)](#morphedbymany-n-n-inverse-relationship)** ❌ + - never needed, UI would be very difficult to understand & use; + + + +#### hasOne (1-1 relationship) + +- example: + - `User -> hasOne -> Phone` + - the foreign key is stored on the Phone (`user_id` on `phones` table) +- what to use: + - the `relationship` field with `subfields` defined for each column on the related entry +- how to use: + - [the `hasOne` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-one) in the User model; + +```php +// inside UserCrudController::setupCreateOperation() +CRUD::field('phone')->type('relationship')->subfields([ + 'prefix', + 'number', + [ + 'name' => 'type', + 'type' => 'select_from_array', + 'options' => ['mobile' => 'Mobile Phone', 'landline' => 'Landline', 'fax' => 'Fax'], + ] +]); +``` + + +#### hasOne (1-1 relationship) - one field for each attribute of the related entry + +- example: + - `User -> hasOne -> Phone` + - the foreign key is stored on the Phone (`user_id` on `phones` table) +- what to use: + - any field (eg. `text`, `number`, `textarea`), with the field name prefixed by the relationship name (dot notation); +- how to use: + - [the `hasOne` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-one) in the User model; + - you can easily add fields for each individual attribute on the related entry; you just need to specify in the field name that the value should not be stored on the _main model_, but on _a related model_; you can do that using dot notation (`relationship_name.column_name`); note that the prefix (before the dot) is the **Relation** name, not the table name; + - all fields types should work fine - depending on your needs you could choose to add a [`text`](#text) field, [`number`](#number) field, [`textarea`](#textarea) field, [`select`](#select) field etc.; + +```php +// inside UserCrudController::setupCreateOperation() +CRUD::field('phone.number')->type('number'); +CRUD::field('phone.prefix')->type('text'); +CRUD::field('phone.type')->type('select_from_array')->options(['mobile' => 'Mobile Phone', 'landline' => 'Landline', 'fax' => 'Fax']); +``` + + +#### belongsTo (n-1 relationship) + +- example: + - `Phone -> User` + - a Phone belongs to one User; a Phone can only belong to one User + - the foreign key is stored on the Phone (`user_id` on `phones` table) +- how to use: + - [the `belongsTo` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-many-inverse) in the Phone model; + - you can easily add a dropdown to let the admin pick which User the Phone belongs; you can use any of the dropdown fields, but for convenience we've made a list here, and broken them down depending on approximately how many entries the dropdown will have: + - for 0-10 dropdown items - we recommend you use the [`relationship`](#relationship) or [`select`](#select) field; + - for 0-500 dropdown items - we recommend you use the [`relationship`](#relationship) or [`select2`](#select2) field; + - for 500-1.000.000+ dropdown items - we recommend you load the dropdown items using AJAX, by using the [`relationship`](#relationship) field and Fetch operation (alternatively, use the [`select2_from_ajax`](#select2-from-ajax) field); + +```php +// inside PhoneCrudController::setupCreateOperation() +CRUD::field('user'); // notice the name is the relationship name and backpack will auto-infer the field type as [`relationship`](#relationship) +CRUD::field('user_id')->type('select')->model('App\Models\User')->attribute('name')->entity('user'); // notice the name is the foreign key attribute +CRUD::field('user_id')->type('select2')->model('App\Models\User')->attribute('name')->entity('user'); // notice the name is the foreign key attribute +``` + +- notes: + - if you choose to use the [`relationship`](#relationship) field, you could also use [the InlineCreate operation](/docs/{{version}}/crud-operation-inline-create), which will add a [+ Add Item] button next to the dropdown, to let the admin create a User in a modal, without leaving the current Create Phone form; + + +#### hasMany (1-n relationship) + +- example: + - `Post -> HasMany -> Comment` + - the foreign key is stored on the Comment (`post_id` on `comments` table) +- what to use: + - use the `relationship` field; +- how to use: + - [the `hasMany` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-many) in the Post model; + +```php +// inside PostCrudController::setupCreateOperation() +CRUD::field('comments'); // when unselected, will set foreign key to null +CRUD::field('comments')->fallback_id(3); // when unselected, will set foreign key to 3 +CRUD::field('comments')->force_delete(true); // when unselected, will delete the related entry +``` + +- notes: + - when a related entry is unselected (removed), Backpack will: + - set the foreign key to `null`, if that db column is nullable (eg. `post_id`); + - set the foreign key to a default value, if you define a `fallback_id` on the field; + - delete related entry entirely, if you define `'force_delete' => false` on the field; + - you can use [the InlineCreate operation](/docs/{{version}}/crud-operation-inline-create) to show a [+ Add Item] button next to the dropdown; for it to work `post_id` on comments table need to nullable or have a default setup in database; + + + +#### hasMany (1-n relationship) with subform to create, update and delete related entries + +If you want the admin to not only _select_ an entry, but also create them, edit their attributes or delete related entries. + +- example: + - `Post -> HasMany -> Comment` + - the foreign key is stored on the Comment (`post_id` on `comments` table) +- what to use: + - use the `relationship` field; +- how to use: + - [the `hasMany` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-many) in the Post model; + +```php +// inside PostCrudController::setupCreateOperation() +CRUD::field('comments')->subfields([['name' => 'body']]); +// where body is a text field in the comment table. +``` + + +#### belongsToMany (n-n relationship) + +Note: Starting with v5, the `BelongsToMany` relation had been improved to simplify the scenario where your pivot table has extra database columns (in addition to the foreign keys). + +- example: + - `User -> BelongsToMany -> Role` + - the foreign keys are stored on a pivot table (usually the `user_roles` table has both `user_id` and `role_id`) +- how to use: + - [the `belongsToMany` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#many-to-many) in both the User and Role models; + - you can add a dropdown on your User to pick the Roles that are connected to it; for that, use the [`relationship`](#relationship), [`select_multiple`](#select-multiple), [`select2_multiple`](#select2-multiple) or [`select2_from_ajax_multiple`](#select2-from-ajax-multiple) fields; + +```php +// inside UserCrudController::setupCreateOperation() +CRUD::field('roles'); + +// inside RoleCrudController::setupCreateOperation() +CRUD::field('users'); +``` + +- notes: + - if you choose to use the [`relationship`](#relationship) field type, you can also use [the InlineCreate operation](/docs/{{version}}/crud-operation-inline-create), which will add a `[+ Add Item]` button next to the dropdown, to let the admin create a User/Role in a modal, without leaving the current Create User/Role form; + +##### EXTRA: Saving additional attributes to the pivot table + +If your pivot table has additional database columns (eg. not only `user_id` and `role_id` but also `notes` or `supervisor`, you can use the `relationship` field to show a subform, instead of a `select2`, and show `subfields` for each of those attributes you want edited on the pivot table. For the example above (`User -> BelongsToMany -> Roles`) you should do the following: + + +**Step 1.** Setup the pivot fields in your relation definition: + +```php +// inside App\Models\User +public function roles() { + return $this->belongsToMany('App\Models\Role')->withPivot('notes', 'some_other_field'); // `notes` and `some_other_field` are aditional fields in the pivot table that you plan to show in the form. +} +``` + +**Step 2.** Setup the pivot fields in your relation definition: + +```php +// inside UserCrudController::setupCreateOperation() +CRUD::field('roles')->subfields([ + ['name' => 'notes', 'type' => 'textarea'], + ['name' => 'some_other_field'] +]); +``` + +And you are done: a subform will shown, with a select for the pivot connected entity field and the defined fields and Backpack will take care of the saving process. + +**Need to change the pivot `select` field?** You can add any configuration to the pivot field as you would do in a [relationship](#relationship) select field, the only difference is is that it should go inside the `pivotSelect` key: +```php +CRUD::field('users')->subfields([ ['name' => 'notes'] ]) + ->pivotSelect([ + 'ajax' => true, + 'data_source' => backpack_url('role/fetch/user'), + 'placeholder' => 'some placeholder', + 'wrapper' => [ + 'class' => 'col-md-6' + ] + ]); +``` + + +#### morphOne (1-1 polymorphic relationship) + +- example: + - Post/User -> morphOne -> Video. + - The User model and the Post model have 1 Video each. + - [the `morphOne` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-one-polymorphic-relations) in both the Post/User and Video models; + +You can add a subform for the related entry to be created/edited/deleted from the main form: + +```php +CRUD::field('video')->type('relationship')->subfields([ + 'url', + [ + 'name' => 'description', + 'type' => 'ckeditor', + ] +]); +``` + +Backpack will take care of the saving process and deal with the morphable relation. + + + +#### morphOne (1-1 polymorphic relationship) one field for each related entry attribute + +- example: + - Post/User -> morphOne -> Video. + - The User model and the Post model have 1 Video each. + - [the `morphOne` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-one-polymorphic-relations) in both the Post/User and Video models; + +You can add any type of field to change the attribute on the related entry, but make sure to prefix the field name with the name of the relationship: + +```php +CRUD::field('video.description')->type('ckeditor'); +CRUD::field('video.url'); +``` + +Backpack will take care of the saving process and deal with the morphable relation. + + + +#### morphMany (1-n polymorphic relationship) + +This is in all aspects similar to [HasMany](#hasmany) relation, the difference is that it's stored in a pivot table. +- example: + - Video/Post -> morphMany -> Comment. + - The Video model and the Post model can have multiple Comment model but the comment belongs to only one of them. + - [the `morphMany` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#one-to-many-polymorphic-relations) in both the Post/Video and Comment models; + +There is no sense in using this a `select` when using a polymorphic relation because the items that could/would be select might belong to different entities. So you should setup this relation as you would setup a [HasMany creatable](#hasmany-creatable). + +```php +// inside PostCrudController::setupCreateOperation() and inside VideoCrudController::setupCreateOperation() +CRUD::field('comments')->subfields([['name' => 'comment_text']]); //note comment_text is a text field in the comment table. +``` + + + +#### morphToMany (n-n polymorphic relationship) + +This is in all aspects similar to [BelongsToMany](#belongstomany) relation, the difference is that it stores the `morphable` entity in the pivot table: + - Video/Post -> belongsToMany -> Tag. + - The Video model and the Post model can have multiple Tag model and each Tag model can belong to one or more of them. + - [the `morphToMany` relationship should be properly defined](https://laravel.com/docs/eloquent-relationships#many-to-many-polymorphic-relations) in both the Post/Video and Tag models; + +Please read the relationship [BelongsToMany](#belongstomany) documentation, everything is the same in regards to fields definition and Backpack will take care of the morphable relation saving. + + +#### MorphTo (n-1 relationship) + +Using this relation type Backpack will automatically manage for you both `_type` and `_id` fields of this relation. +Let's say we have `comments`, that can be either for `videos` or `posts`. +- Your `Comment` Model should have its `morphTo` relation set up. +- Your db table should have the `commentable_type` and `commentable_id` columns. +```php +// in CommentCrudController you can add the morphTo fields by naming the field the morphTo relation name +CRUD::field('commentable') + ->addMorphOption('App\Models\Video') + ->addMorphOption('App\Models\Post'); +``` +This will generate two inputs: +1 - A select with two options `Video` and `Post` as the `morph type field`. +2 - A second select that will have the options for both `Video` and `Post` models. + +In a real world scenario, you might have other needs, like using AJAX to select the actual entries or changing the inputs size etc. For that, check out the available attributes: + +```php +// ->addMorphOption(string $model/$morphMapName, string $labelInSelect, array $options) +CRUD::field('commentable') + ->addMorphOption('App\Models\Video') + ->addMorphOption('App\Models\Post', 'Posts', [ + [ + 'data_source' => backpack_url('comment/fetch/post'), + 'minimum_input_length' => 2, + 'placeholder' => 'select an amazing post', + 'method' => 'POST', + 'attribute' => 'title', + ] + ]); + +// by defining `data_source` you are telling Backpack that the `Posts` select should be an ajax select. +``` +In this scenario the same two selects would be generated, but for the Post, your admin see an AJAX field, instead of a static one, use POST instead of GET etc. + +To further customize the fields you can use `morphTypeField` and `morphIdField` to configure the select sizes etc. + +```php +CRUD::field('commentable') + ->addMorphOption('App\Models\Video') + ->addMorphOption('App\Models\Post', 'Posts') + ->morphTypeField(['wrapper' => ['class' => 'form-group col-sm-4']]) + ->morphIdField(['wrapper' => [ + 'class' => 'form-group col-sm-8'], + 'attributes' => ['my_custom_attribute' => 'custom_value'] + ]); +``` + +Here is an example using array field definition: + +```php +CRUD::field([ + 'name' => 'commentable', + 'morphOptions' => [ + ['App\Models\PetShop\Owner', 'Owners'], + ['monster', 'Monsters', [ + 'placeholder' => 'Select a little monster' + ]], + ['App\Models\PetShop\Pet', 'Pets', [ + 'data_source' => backpack_url('pet-shop/comment/fetch/pets'), + 'minimum_input_length' => 2, + 'placeholder' => 'select a fluffy pet' + ]], + ], + 'morphTypeField' => [ + 'wrapper' => ['class' => 'form-group col-md-6'] + ], + 'morphIdField' => [ + 'wrapper' => ['class' => 'form-group col-md-6'] + ] +]); +``` + +#### hasOneThrough (1-1-1 relationship) + +- This is a "read-only" relationship. It does not make sense to add a field for it. + + +#### hasManyThrough (1-1-n relationship) + +- This is a "read-only" relationship. It does not make sense to add a field for it. + + +#### Has One of Many (1-1 relationship out of 1-n relationship) + +- This is a "read-only" relationship. It does not make sense to add a field for it. Please use the general-purpose relationship towards this entity (the 1-n relationship, without `latestOfMany()` or `oldestOfMany()`). + + +#### Morph One of Many (1-1 relationship out of 1-n relationship) + +- This is a "read-only" relationship. It does not make sense to add a field for it. Please use the general-purpose relationship towards this entity (the 1-n relationship, without `latestOfMany()` or `oldestOfMany()`). + +#### MorphedByMany (n-n inverse relationship) + +- We do not provide an interface to edit this relationship. We never needed it, nobody ever asked for it and it would be very difficult to create an interface that is easy-to-use and easy-to-understand for the admin. If you find yourself needing this, please let us know by opening an issue on GitHub. + + + +## Operations + + + +### Add an Uneditable Input inside Create or Update Operation - Stripped Request + +You might want to add a new attribute to the Model that gets saved. Let's say you want to add an `updated_by` indicator to the Update operation, containing the ID of the user currently logged in (`backpack_user()->id`). + +**By default, Backpack it will only save inputs that have a corresponding CRUD field defined.** But you can override this behaviour, by using the setting called `strippedRequest`, which determine the which fields should actually be saved, and which fields should be "stripped" from the request. + +Here's how you can use `strippedRequest` to add an `updated_by` item to be saved (but this will work for any changes you want to make to the request, really). You can change the request at various points in the request: +- (a) in your CrudController (eg. `CRUD::setOperationSetting('strippedRequest', StripBackpackRequest::class);` in your `setup()`); +- (b) in your Request (eg. same as above, inside `prepareForValidation()`); +- (c) in your config, if you want it to apply for all CRUDs (eg. inside `config/backpack/operations/update.php`); + +Let's demonstrate each one of the above: + +**Option 1.** In the controller. You can change the `strippedRequest` closure inside your `ProductCrudController::setup()`: +```php +public function setupUpdateOperation() +{ + CRUD::setOperationSetting('strippedRequest', function ($request) { + // keep the recommended Backpack stripping (remove anything that doesn't have a field) + // but add 'updated_by' too + $input = $request->only(CRUD::getAllFieldNames()); + $input['updated_by'] = backpack_user()->id; + + return $input; + }); +} +``` + +**Option 2.** In the request. You can change the same `strippedRequest` closure inside the `ProductFormRequest` that contains your validation: +```php + protected function prepareForValidation() + { + \CRUD::set('update.strippedRequest', function ($request) { //notice here that update is refering to update operation, change accordingly + // keep the recommended Backpack stripping (remove anything that doesn't have a field) + // but add 'updated_by' too + $input = $request->only(\CRUD::getAllFieldNames()); + $input['updated_by'] = backpack_user()->id; + + return $input; + }); + } +``` + +**Option 3.** In the config file. You cannot use a closure (because closures don't get cached). But you can create an invokable class, and use that as your `strippedRequest`, in your `config/backpack/operations/update.php` (for example). Then it will apply to ALL update operations, on all entities: + +```php +only(\CRUD::getAllFieldNames()); + $input['updated_by'] = backpack_user()->id; + return $input; + } +} +``` + + + +### How to make the form smaller or bigger + +In practice, what you want is to change the class on the main `
` of the Create/Update operation. To learn how to do that, please take a look at the next section - how to make an operation wider or narrower. + + +### How to make an operation wider or narrower + +If you want to make the contents of an operation take more / less space from the window, you can easily do that. You just need to change the class on the main `
` of that operation, what we call the "content class". Depending on the scope of your change (for one or all CRUDs) here's how you can do that: + +(A) for all CRUDs, by specifying the custom content class in your ```config/backpack/crud.php```: + +```php + // Here you may override the css-classes for the content section of the create view globally + // To override per view use $this->crud->setCreateContentClass('class-string') + 'create_content_class' => 'col-md-8 col-md-offset-2', + + // Here you may override the css-classes for the content section of the edit view globally + // To override per view use $this->crud->setEditContentClass('class-string') + 'edit_content_class' => 'col-md-8 col-md-offset-2', + + // Here you may override the css-classes for the content section of the revisions timeline view globally + // To override per view use $this->crud->setRevisionsTimelineContentClass('class-string') + 'revisions_timeline_content_class' => 'col-md-10 col-md-offset-1', + + // Here you may override the css-class for the content section of the list view globally + // To override per view use $this->crud->setListContentClass('class-string') + 'list_content_class' => 'col-md-12', + + // Here you may override the css-classes for the content section of the show view globally + // To override per view use $this->crud->setShowContentClass('class-string') + 'show_content_class' => 'col-md-8 col-md-offset-2', + + // Here you may override the css-classes for the content section of the reorder view globally + // To override per view use $this->crud->setReorderContentClass('class-string') + 'reorder_content_class' => 'col-md-8 col-md-offset-2', +``` + +(B) for a single CRUD, by using: + +```php +CRUD::setCreateContentClass('col-md-8 col-md-offset-2'); +CRUD::setUpdateContentClass('col-md-8 col-md-offset-2'); +CRUD::setListContentClass('col-md-8 col-md-offset-2'); +CRUD::setShowContentClass('col-md-8 col-md-offset-2'); +CRUD::setReorderContentClass('col-md-8 col-md-offset-2'); +CRUD::setRevisionsTimelineContentClass('col-md-8 col-md-offset-2'); +``` + + + +## Miscellaneous + + +### Use the Media Library (File Manager) + +The default Backpack installation doesn't come with a file management component. Because most projects don't need it. But we've created a first-party add-on that brings the power of [elFinder](http://elfinder.org/) to your Laravel projects. To install it, [follow the instructions on the add-ons page](https://github.com/Laravel-Backpack/FileManager). It's as easy as running: + +```bash +# require the package +composer require backpack/filemanager + +# then run the installation process +php artisan backpack:filemanager:install +``` + +If you've chosen to install [backpack/filemanager](https://github.com/Laravel-Backpack/FileManager), you'll have elFinder integrated into: +- TinyMCE (as "tinymce" field type) +- CKEditor (as "ckeditor" field type) +- CRUD (as "browse" and "browse_multiple" field types) +- stand-alone, at the */admin/elfinder* route; + +For the integration, we use [barryvdh/laravel-elfinder](https://github.com/barryvdh/laravel-elfinder). + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs-4-0/media_library.png) + + +### How to manually install Backpack + +If the automatic installation doesn't work for you and you need to manually install CRUD, here are all the commands it is running: + +1) In your terminal: + +``` bash +composer require backpack/crud +``` + +2) Instead of running ```php artisan backpack:install``` you can run: +```bash +php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag="minimum" +php artisan migrate +php artisan backpack:publish-middleware +composer require --dev backpack/generators +php artisan basset:install --no-check --no-interaction + +# then install ONE of the first-party themes: +php artisan backpack:require:theme-tabler +php artisan backpack:require:theme-coreuiv4 +php artisan backpack:require:theme-coreuiv2 + +# then check assets can be correctly used +php artisan basset:check +``` + + + +### Overwrite a Method on the CrudPanel Object + +Starting with Backpack v4, you can use a custom CrudPanel object instead of the one in the package. In your custom CrudPanel object, you can overwrite any method you want, but please note that this means that you're overwriting core components, and will be making it more difficult to upgrade to newer versions of Backpack. + +You can do this in any of your service providers (ex: ```app/Providers/AppServiceProvider.php```) to load your class instead of the one in the package: + +```php +$this->app->extend('crud', function () { + return new \App\MyExtendedCrudPanel; +}); +``` + +Details and implementation [here](https://github.com/Laravel-Backpack/CRUD/pull/1990). + + + + +### Error: Failed to Download Backpack PRO + +When trying to install Backpack\PRO (or any of our closed-source add-ons, really), you might run into the following error message: + +```bash +Downloading backpack/pro (1.1.1) +Failed to download backpack/pro from dist: The "https://backpackforlaravel.com/satis/download/dist/backpack/pro/backpack-pro-xxx-zip-zzz.zip" file could not be downloaded (HTTP/2 402 ) +``` + +Or maybe: + +```bash +Syncing backpack/pro (1.1.1) into cache +Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos +Head to https://github.com/settings/tokens/new?scopes=repo&description=Composer+on+DESKTOP-BLABLA+2022-07-14+1559 +to retrieve a token. +``` + +What's happening there? That is a general Composer error - "file could not be downloaded". The error itself doesn't give too much information, but we can make an educated guess. + +**99% of the people who report this error have the same problem - they do not have access to that package version.** They bought updates until 1.0.13 (for example), so they DO NOT have access to the latest version (1.1.1 in this example). What you can do, in that case, is **lock the installation to the latest you have access to**, for example + +```bash +composer require backpack/pro:"1.0.13" +``` + +Alternatively, you can purchase more access on the [Backpack website](https://backpackforlaravel.com/pricing). Or contact the team if there's a mistake. + +-- + +How do you find out what's the last version you have access to? + +(1) **Whenever the error above happens, Backpack will send you an email**, with details and instructions. **Check your email**, it will also include the latest version you have access to. + +(2) [Your Tokens page](https://backpackforlaravel.com/user/tokens) will show more details. For each token you have, it will say when it stops giving you access to updates. If it doesn't say the last version directly, you can corroborate that last day with [the changelog](https://backpackforlaravel.com/products/pro-for-unlimited-projects/CHANGELOG.md ), to determine what's the last version that _you_ have access to. + +-- + +Why the ugly, general error? Because Composer doesn't allow vendors to customize the error, unfortunately. Backpack's server returns a better error message, but Composer doesn't show it. + + + +### Configuring the Temporary Directory + +The [dropzone field](/docs/{{version}}/crud-fields#dropzone-pro) and DropzoneOperation will upload the files to a temporary directory using AJAX. When an entry is saved, they move that file to the final directory. But if the user doesn't finish the saving process, the temp directory can still hold files that are not used anywhere. + +**Configure Temp Directory** + +To configure that temporary directory for ALL dropzone operations, call `php artisan vendor:publish --provider="Backpack\Pro\AddonServiceProvider" --tag="dropzone-config"` and then edit your `config/backpack/operations/dropzone.php` to fit your needs. Here are the most important values you'll find there: + +```php + 'temporary_disk' => 'local', // disk in config/filesystems.php that will be used + 'temporary_folder' => 'backpack/temp', // the directory inside the disk above + 'purge_temporary_files_older_than' => 72 // automatically delete files older than 72 hours +``` + +Alternatively, you can also configure the temp directory for the current CRUD only using: + +```php +public function setupDropzoneOperation() +{ + CRUD::setOperationSetting('temporary_disk', 'public'); + CRUD::setOperationSetting('temporary_folder', 'backpack/temp'); + CRUD::setOperationSetting('purge_temporary_files_older_than', 72); +} +``` + +**Delete Old Temp Files** + +Whenever new files are uploaded using the Dropzone operation, the operation deletes old files from the temp directory. But you can also run the `backpack:purge-temporary-files` command, to clean the temp directory. + + +```bash +php artisan backpack:purge-temporary-files --older-than=24 --disk=public --path="backpack/temp" +``` + +It accepts the following optional parameters: +- `--older-than=24`: the number of hours after which temporary files are deleted. +- `--disk=public`: the disk used by the temporary files. +- `--path="backpack/temp"`: the folder inside the disk where files will be stored. + + +You can use any strategy to run this command periodically - a cron job, a scheduled task or hooking into application termination hooks. Laravel provides a very easy way to setup your scheduled tasks. You can read more about it [here](https://laravel.com/docs/10.x/scheduling). For example, you can run the command every hour by adding the following line to your `app/Console/Kernel.php` in the `schedule()` method: +```php +// app/Console/Kernel.php +$schedule->command('backpack:purge-temporary-files')->hourly(); +``` + +After adding this, you need to setup a cron job that will process the Laravel scheduler. You can manually run it in development with `php artisan schedule:run`. For production, you can setup a cron job take care of it for you. You can read more about it [here](https://laravel.com/docs/10.x/scheduling#running-the-scheduler). + + +### Enable database transactions for create and update + +In v6.6 we introduced the ability to enable database transactions for create and update operations. This is useful if you have a lot of relationships and you want to make sure that all of them are saved or none of them are saved. +You can enable this feature globaly at `config/backpack/base.php` by enabling `useDatabaseTransactions`. + +> **Note:** This feature will be enable by default starting `v7` diff --git a/7.x-dev/crud-operation-clone.md b/7.x-dev/crud-operation-clone.md new file mode 100644 index 00000000..23a24f5f --- /dev/null +++ b/7.x-dev/crud-operation-clone.md @@ -0,0 +1,137 @@ +# Clone Operation PRO + +-- + + +## About + +This CRUD operation allows your admins to duplicate one or more entries from the database. + +>**IMPORTANT:** The clone operation does NOT duplicate related entries. So n-n relationships will be unaffected. However, this also means that n-n relationships are ignored. So when you clone an entry, the new entry: +>- will NOT have the same 1-1 relationships +>- will have the same 1-n relationships +>- will NOT have the same n-1 relationships +>- will NOT have the same n-n relationships +> +>This might be somewhat counterintuitive for end users - though it should make perfect sense for us developers. This is why the Clone operation is NOT enabled by default. + + + +## Requirements + +This is a PRO operation. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/products/pro-for-unlimited-projects). + + +## Clone a Single Item + + +### How it Works + +Using AJAX, a POST request is performed towards ```/entity-name/{id}/clone```, which points to the ```clone()``` method in your EntityCrudController. + + +### How to Use + +To enable it, you need to ```use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;``` on your EntityCrudController. For example: + +```php + +### How to Overwrite + +In case you need to change how this operation works, overwrite the ```clone()``` trait method in your EntityCrudController; make sure you give the method in the trait a different name, so that there are no conflicts: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation { clone as traitClone; } + +public function clone($id) +{ + CRUD::hasAccessOrFail('clone'); + CRUD::setOperation('clone'); + + // whatever you want + + // if you still want to call the old clone method + $this->traitClone($id); +} +``` + +You can also overwrite the clone button by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the clone button there to make changes using: + +```zsh +php artisan backpack:button --from=clone +``` + + +## Clone Multiple Items (Bulk Clone) + +In addition to the button for each entry, you can show checkboxes next to each element, and allow your admin to clone multiple entries at once. + + + +### How it Works + +Using AJAX, a POST request is performed towards ```/entity-name/bulk-clone```, which points to the ```bulkClone()``` method in your EntityCrudController. + +**`NOTES:`** +- The bulk checkbox is added inside the first column defined in the table. For that reason the first column should be visible on table to display the bulk actions checkbox next to it. +- `Bulk Actions` also disable all click events for the first column, so make sure the first column **doesn't** contain an anchor tag (``), as it won't work. + + +### How to Use + +To enable it, you need to ```use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;``` on your EntityCrudController. + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```bulkClone()``` method in your EntityCrudController: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation { bulkClone as traitBulkClone; } + +public function bulkClone($id) +{ + // your custom code here + // + // then you can call the old bulk clone if you want + $this->traitBulkClone($id); +} +``` + +You can also overwrite the bulk clone button by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the clone button there to make changes using: + +```zsh +php artisan backpack:button --from=bulk_clone +``` + + +## Exempt attributes when cloning +If you have attributes that should not be cloned (eg. a SKU with an unique constraint), you can overwrite the replicate method on your model: + +```php + public function replicate(array $except = null) { + + return parent::replicate(['sku']); + } +``` diff --git a/7.x-dev/crud-operation-create.md b/7.x-dev/crud-operation-create.md new file mode 100644 index 00000000..32b3bc8a --- /dev/null +++ b/7.x-dev/crud-operation-create.md @@ -0,0 +1,328 @@ +# Create Operation + +--- + + +## About + +This operation allows your admins to add new entries to a database table. + +![CRUD Create Operation](https://backpackforlaravel.com/uploads/docs-4-2/operations/create.png) + + +## Requirements + +All editable attributes should be ```$fillable``` on your Model. + + +## How to Use + +To use the Create operation, you must: + +**Step 0. Use the operation trait on your EntityCrudController**. This should be as simple as this: + +```php +crud->setValidation(StoreRequest::class); + // $this->crud->addField($field_definition_array); + } +} +``` + +**Step 1. Specify what field types** you'd like to show for each editable attribute, in your EntityCrudController's ```setupCreateOperation()``` method. You can do that using the [Fields API](/docs/{{version}}/crud-fields#fields-api). In short you can: + +```php +protected function setupCreateOperation() +{ + $this->crud->addField($field_definition_array); +} +``` + +**Step 2. Specify validation rules, in your the EntityCrudRequest file**. Then make sure that file is used for validation, by telling the CRUD to validate that request file in ```setupCreateOperation()```: +```php +$this->crud->setValidation(StoreRequest::class); +``` + +For more on how to manipulate fields, please read the [Fields documentation page](/docs/{{version}}/crud-fields). For more on validation rules, check out [Laravel's validation docs](https://laravel.com/docs/master/validation#available-validation-rules). + + +## How It Works + +CrudController is a RESTful controller, so the ```Create``` operation uses two routes: +- GET to ```/entity-name/create``` - points to ```create()``` which shows the Add New Entry form (```create.blade.php```); +- POST to ```/entity-name``` - points to ```store()``` which does the actual storing operation; + +The ```create()``` method will show all the fields you've defined for this operation using the [Fields API](/docs/{{version}}/crud-fields#fields-api), then upon Save the ```store()``` method will first check the validation from the FormRequest you've specified, then create the entry using the Eloquent model. Only attributes that are specified as fields, and are ```$fillable``` on the model will actually be stored in the database. + + +## How to add custom sections(aka. Widgets) + +[Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. You can use the [default widget types](https://backpackforlaravel.com/docs/{{version}}/base-widgets#default-widget-types) or [create your own custom widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets#creating-a-custom-widget-type). + +Backpack default template includes two [sections](https://backpackforlaravel.com/docs/{{version}}/base-widgets#requirements-1) where you can push widgets: + +* `before_content` +* `after_content` + +To use widgets on create operation, define them inside `setupCreateOperation()` function. + +```php +public function setupCreateOperation() +{ + // dynamic data to render in the following widget + $userCount = \App\Models\User::count(); + + //add div row using 'div' widget and make other widgets inside it to be in a row + Widget::add()->to('before_content')->type('div')->class('row')->content([ + + //widget made using fluent syntax + Widget::make() + ->type('progress') + ->class('card border-0 text-white bg-primary') + ->progressClass('progress-bar') + ->value($userCount) + ->description('Registered users.') + ->progress(100 * (int)$userCount / 1000) + ->hint(1000 - $userCount . ' more until next milestone.'), + + //widget made using the array definition + Widget::make( + [ + 'type' => 'card', + 'class' => 'card bg-dark text-white', + 'wrapper' => ['class' => 'col-sm-3 col-md-3'], + 'content' => [ + 'header' => 'Example Widget', + 'body' => 'Widget placed at "before_content" secion in same row', + ] + ] + ), + ]); + + //you can also add Script & CSS to your page using 'script' & 'style' widget + Widget::add()->type('script')->stack('after_scripts')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + Widget::add()->type('style')->stack('after_styles')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); +} +``` + +#### Output: +* Using `before_content`: + +![](https://i.imgur.com/MF9ePIM.png) +* Using `after_content` + +![](https://i.imgur.com/AxC3lAZ.png) + + +## Advanced Features and Techniques + + +### Validation + +There are three ways you can define the [validation rules](https://laravel.com/docs/validation#available-validation-rules) for your fields: + +#### (A) Validating fields using FormRequests + +When you generate a CrudController, you'll notice a [Laravel FormRequest](https://laravel.com/docs/validation#form-request-validation) has also been generated, and that FormRequest is mentioned as the source of your validation rules: +```php +protected function setupCreateOperation() +{ + $this->crud->setValidation(StoreRequest::class); +} +``` + +This works particularly well for bigger models, because you can mention a lot of rules, messages and attributes in your `FormRequest` and it will not increase the size of your `CrudController`. + +**Differences between the Create and Update validations?** Then create a separate request file for each operation and instruct your EntityCrudController to use those files: + +```php +use App\Http\Requests\CreateTagRequest as StoreRequest; +use App\Http\Requests\UpdateTagRequest as UpdateRequest; + +// ... + +public function setupCreateOperation() +{ + $this->crud->setValidation(CreateRequest::class); +} + +public function setupUpdateOperation() +{ + $this->crud->setValidation(UpdateRequest::class); +} +``` + +#### (B) Validating fields using a rules array + +For smaller models (with just a few validation rules), creating an entire FormRequest file to hold them might be overkill. If you prefer, you can pass an array of [validation rules](https://laravel.com/docs/validation#available-validation-rules) to the same `setValidation()` method (with an optional second parameter for the validation messages): + +```php +protected function setupCreateOperation() +{ + $this->crud->setValidation([ + 'name' => 'required|min:2', + ]); + + // or maybe + + $rules = ['name' => 'required|min:2']; + $messages = [ + 'name.required' => 'You gotta give it a name, man.', + 'name.min' => 'You came up short. Try more than 2 characters.', + ]; + $this->crud->setValidation($rules, $messages); +} +``` + +This is more convenient for small and medium models. Plus, it's very easy to read. + +#### (C) Validating fields using field attributes + +Another good option for small & medium models is to define the [validation rules](https://laravel.com/docs/validation#available-validation-rules) directly on your fields: + +```php +protected function setupCreateOperation() +{ + $this->crud->addField([ + 'name' => 'content', + 'label' => 'Content', + 'type' => 'ckeditor', + 'placeholder' => 'Your textarea text here', + 'validationRules' => 'required|min:10', + 'validationMessages' => [ + 'required' => 'You gotta write smth man.', + 'min' => 'More than 10 characters, bro. Wtf... You can do this!', + ] + ]); + + // or using the fluent syntax + + CRUD::field('email_address')->validationRules('required|email|unique:users.email_address'); +} +``` + + +### Callbacks + +If you're coming from other CRUD systems (like GroceryCRUD) you might be looking for callbacks to run "before_insert", "before_update", "after_insert", "after_update". **There are no callbacks in Backpack**, because... they're not needed. There are plenty of other ways to do things before/after an entry is created. + +#### Use Events in your `setup()` method + +Laravel already triggers [multiple events](https://laravel.com/docs/master/eloquent#events) in an entry's lifecycle. Those also include: +- `creating` and `created`, which are triggered by the Create operation; +- `saving` and `saved`, which are triggered by both the Create and the Update operations; + +So if you want to do something to a `Product` entry _before_ it's created, you can easily do that: +```php +public function setupCreateOperation() +{ + + // ... + + Product::creating(function($entry) { + $entry->author_id = backpack_user()->id; + }); +} +``` + +Take a closer look at [Eloquent events](https://laravel.com/docs/master/eloquent#events) if you're not familiar with them, they're really _really_ powerful once you understand them. Please note that **these events will only get registered when the function gets called**, so if you define them in your `CrudController`, then: +- they will NOT run when an entry is changed outside that CrudController; +- if you want to expand the scope to cover both the `Create` and `Update` operations, you can easily do that, for example by using the `saving` and `saved` events, and moving the event-calling to your main `setup()` method; + +#### Use events in your field definition + +You can tell a field to do something to the entry when that field gets saved to the database. Rephrased, you can define standard [Eloquent events](https://laravel.com/docs/master/eloquent#events) directly on fields. For example: + +```php +// FLUENT syntax - use the convenience method "on" to define just ONE event +CRUD::field('name')->on('saving', function ($entry) { + $entry->author_id = backpack_user()->id; +}); + +// FLUENT SYNTAX - you can define multiple events in one go +CRUD::field('name')->events([ + 'saving' => function ($entry) { + $entry->author_id = backpack_user()->id; + }, + 'saved' => function ($entry) { + // TODO: upload some file + }, +]); + +// using the ARRAY SYNTAX, define an array of events and closures +CRUD::addField([ + 'name' => 'name', + 'events' => [ + 'saving' => function ($entry) { + $entry->author_id = backpack_user()->id; + }, + ], +]); +``` + +> An important thing to notice when using model events in the fields is that those events will only be registered **in the same operations (create, update, etc)** where your fields are defined. +> Take for example the `DeleteOperation`, which is ran when you delete an entry. If you define a field with a `deleting` event, that event will not be registered when you delete an entry, because the field is not defined in the `DeleteOperation`. If you want to use model events in the `DeleteOperation`, you can do that by using the `setupDeleteOperation()` method and defining the fields with the events there too, similar to how you do for create and update operations. + +#### Override the `store()` method + +The store code is inside a trait, so you can easily override it, if you want: + +```php +crud->addField(['type' => 'hidden', 'name' => 'author_id']); + // $this->crud->removeField('password_confirmation'); + + // Note: By default Backpack ONLY saves the inputs that were added on page using Backpack fields. + // This is done by stripping the request of all inputs that do NOT match Backpack fields for this + // particular operation. This is an added security layer, to protect your database from malicious + // users who could theoretically add inputs using DeveloperTools or JavaScript. If you're not properly + // using $guarded or $fillable on your model, malicious inputs could get you into trouble. + + // However, if you know you have proper $guarded or $fillable on your model, and you want to manipulate + // the request directly to add or remove request parameters, you can also do that. + // We have a config value you can set, either inside your operation in `config/backpack/crud.php` if + // you want it to apply to all CRUDs, or inside a particular CrudController: + // $this->crud->setOperationSetting('saveAllInputsExcept', ['_token', '_method', 'http_referrer', 'current_tab', 'save_action']); + // The above will make Backpack store all inputs EXCEPT for the ones it uses for various features. + // So you can manipulate the request and add any request variable you'd like. + // $this->crud->getRequest()->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->getRequest()->request->remove('password_confirmation'); + + $response = $this->traitStore(); + // do something after save + return $response; + } +} +``` + +>But before you do that, ask yourself - **_is this something that should be done when an entry is added/updated/deleted from the application, too_**? Not just the admin panel? If so, a better place for it would be the Model. Remember your Model is a pure Eloquent Model, so the cleanest way might be to use [Eloquent Event Observers](https://laravel.com/docs/master/eloquent#events) or [accessors and mutators](https://laravel.com/docs/master/eloquent-mutators#accessors-and-mutators). + + +### Translatable models and multi-language CRUDs + +For UX purposes, when creating multi-language entries, the Create form will only allow the admin to add an entry in one language, the default one. The admin can then edit that entry in all available languages, to translate it. Check out [this same section in the Update operation](/docs/{{version}}/crud-operation-update#translatable-models) for how to enable multi-language functionality. diff --git a/7.x-dev/crud-operation-delete.md b/7.x-dev/crud-operation-delete.md new file mode 100644 index 00000000..36fe3c89 --- /dev/null +++ b/7.x-dev/crud-operation-delete.md @@ -0,0 +1,101 @@ +# Delete Operation + +-- + + +## About + +This CRUD operation allows your admins to remove one or more entries from the table. + +>In case your entity has SoftDeletes, it will perform a soft delete. The admin will _not_ know that his entry has been hard or soft deleted, since it will no longer show up in the ListEntries view. + + +## Delete a Single Item + + +### How it Works + +Using AJAX, a DELETE request is performed towards ```/entity-name/{id}```, which points to the ```destroy()``` method in your EntityCrudController. + + +### How to Use + +To enable it, you need to ```use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;``` on your EntityCrudController. For example: + +```php + +### How to Overwrite + +In case you need to change how this operation works, just create a ```destroy()``` method in your EntityCrudController: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation { destroy as traitDestroy; } + +public function destroy($id) +{ + CRUD::hasAccessOrFail('delete'); + + return CRUD::delete($id); +} +``` + +You can also overwrite the delete button by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the delete button there to make changes using: + +```zsh +php artisan backpack:button --from=delete +``` + + +## Delete Multiple Items (Bulk Delete) PRO + +In addition to the button for each entry, PRO developers can show checkboxes next to each element, to allow their admin to delete multiple entries at once. + + + +### How it Works + +Using AJAX, a DELETE request is performed towards ```/entity-name/bulk-delete```, which points to the ```bulkDelete()``` method in your EntityCrudController. + +**`NOTES:`** +- The bulk checkbox is added inside the first column defined in the table. For that reason the first column should be visible on table to display the bulk actions checkbox next to it. +- `Bulk Actions` also disable all click events for the first column, so make sure the first column **doesn't** contain an anchor tag (``), as it won't work. + + + +### How to Use + +You need to ```use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;``` on your EntityCrudController. + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```bulkDelete()``` method in your EntityCrudController: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation { bulkDelete as traitBulkDelete; } + +public function bulkDelete() +{ + // your custom code here +} +``` + +You can also overwrite the bulk delete button by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the delete button there to make changes using: + +```zsh +php artisan backpack:button --from=bulk_delete +``` diff --git a/7.x-dev/crud-operation-fetch.md b/7.x-dev/crud-operation-fetch.md new file mode 100644 index 00000000..2685f7b3 --- /dev/null +++ b/7.x-dev/crud-operation-fetch.md @@ -0,0 +1,172 @@ +# Fetch Operation PRO + +--- + + +## About + +This operation allows an EntityCrudController to respond to AJAX requests with entries in the database for _a different entity_, in a format that can be used by the ```relationship```, ```select2_from_ajax``` and ```select2_from_ajax_multiple``` fields. + + + +## Requirements + +This is a PRO operation. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/products/pro-for-unlimited-projects). + + +## How to Use + +In order to enable this operation, in your CrudController you need to **use the ```FetchOperation``` trait and add a new method** that responds to the AJAX requests (following the naming convention ```fetchEntityName()```). For example, for a `Tag` model you'd do: + +```php + use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation; + + protected function fetchTag() + { + return $this->fetch(\App\Models\Tag::class); + } +``` + +To customize the FetchOperation, pass an array to the ```fetch()``` call, rather than a class name. For example: + +```php +fetch([ + 'model' => \App\Models\Tag::class, // required + 'searchable_attributes' => ['name', 'description'], + 'paginate' => 10, // items to show per page + 'searchOperator' => 'LIKE', + 'query' => function($model) { + return $model->active(); + } // to filter the results that are returned + ]); + } +} +``` + +You can now point your AJAX select to this route, which will be ```backpack_url('your-main-entity/fetch/tag')``` . + + + +## How It Works + +Based on the fact that the ```fetchTag()``` method exists, the Fetch operation will create a ```/product/fetch/tag``` POST route, which points to ```fetchTag()```. Inside ```fetchTag()``` we call ```fetch()```, that responds with entries in the format ```select2``` needs. + +**Preventing FetchOperation from guessing the searchable attributes** + +If not specified `searchable_attributes` will be automatically inferred from model database columns. To prevent this behaviour you can setup an empty `searchable_attributes` array. For example: + +```php +public function fetchUser() { + return $this->fetch([ + 'model' => User::class, + 'query' => function($model) { + $search = request()->input('q') ?? false; + if ($search) { + return $model->whereRaw('CONCAT(`first_name`," ",`last_name`) LIKE "%' . $search . '%"'); + }else{ + return $model; + } + }, + 'searchable_attributes' => [] + ]); + } +``` + + +## Using FetchOperation with `select2_ajax` filter + +The FetchOperation can also be used as the source URL for the `select2_ajax` filter. To do that, we need to: +- change the `select2_ajax` filter method from `GET` (its default) to `POST` (what FetchOperation uses); +- tell the filter what attribute we want to show to the user; + +``` +CRUD::addFilter([ + 'name' => 'category_id', + 'type' => 'select2_ajax', + 'label' => 'Category', + 'placeholder' => 'Pick a category', + 'method' => 'POST', // mandatory change + // 'select_attribute' => 'name' // the attribute that will be shown to the user by default 'name' + // 'select_key' => 'id' // by default is ID, change it if your model uses some other key +], +backpack_url('product/fetch/category'), // the fetch route on the ProductCrudController +function($value) { // if the filter is active + // CRUD::addClause('where', 'category_id', $value); +}); + +``` + + + +## How to Overwrite + +In case you need to change how this operation works, it's best to take a look at the ```FetchOperation.php``` trait to understand how it works. It's a pretty simple operation. Most common ways to overwrite the Fetch operation are documented below: + +**Change the fetch database search operator** + +You can customize the search operator for `FetchOperation` just like you can in ListOperation. By default it's `LIKE`, but you can: +- change the operator individually for each `fetchEntity` using `searchOperator => 'ILIKE'` in the fetch configuration; +- change the operator for all FetchOperations inside that CrudPanel by doing: +```php +public function setupFetchOperationOperation() { + CRUD::setOperationSetting('searchOperator', 'ILIKE'); + } +``` +- change the operator globally in your project, by creating a config file in `config/backpack/operations/fetch.php` and add the following: +```php + 'ILIKE', +]; +``` + +**Custom behaviour for one fetch method** + +To make a ```fetchCategory()``` method behave differently, you can copy-paste the logic inside the ```FetchOperation::fetch()``` and change it to do whatever you need. Instead of returning ```$this->fetch()``` you can return your own results, in this case fetch will only setup the ajax route for you. + +**Custom behaviour for multiple fetch methods inside a Controller** + +To make all calls to ```fetch()``` inside an EntityCrudController behave differently, you can easily overwrite the ```fetch()``` method in that controller: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation; + +public function fetch($arg) +{ + // your custom code here +} +``` + +Then all ```$this->fetch()``` calls from that Controller will be using your custom code. + +In case you need to call the original ```fetch()``` method (from the trait) inside your custom ```fetch()``` method (inside the controller), you can do: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation { fetch as traitFetch; } + +public function fetch($arg) +{ + // your custom code here + + // call the method in the trait + return $this->traitFetch(); +} +``` + +**Custom behaviour for all fetch calls, in all Controllers** + +If you want all your ```fetch()``` calls to behave differently, no matter what Controller they are in, you can: +- duplicate the ```FetchOperation``` trait inside your application; +- instead of using ```\Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation``` inside your controllers, use your custom operation trait; diff --git a/7.x-dev/crud-operation-inline-create.md b/7.x-dev/crud-operation-inline-create.md new file mode 100644 index 00000000..ad2447e5 --- /dev/null +++ b/7.x-dev/crud-operation-inline-create.md @@ -0,0 +1,128 @@ +# InlineCreate Operation PRO + +--- + + +## About + +This operation allows your admins to add new entries to a database table on-the-fly, from a modal. + +For example: +- if you have an ```ArticleCrudController``` where your user can also select ```Categories```; +- this operation adds the ability to create ```Categories``` right inside the ```ArticleCrudController```'s Create form; + - the admin needs to click an Add button + - a modal will show the form from ```CategoryCrudController```'s Create operation; + +![Backpack InlineCreate Operation](https://backpackforlaravel.com/uploads/docs-4-2/release_notes/inline_create_small.gif) + + + +## Requirements + + +This is a PRO operation. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/products/pro-for-unlimited-projects). + +In addition, it needs: +- a working Create operation; +- correctly defined Eloquent relationships on both the primary Model, and the secondary Model; +- a working Fetch operation to retrieve the secondary Model from the primary Model; +- an understanding of what we call "_main_" and "_secondary_" in this case; using the same example as above, where you want to be able to add ```Categories``` in a modal, inside ```ArticleCrudController```'s create&update forms: + - the _main entity_ would be Article (big form); + - the _secondary entity_ would be Category (small form, in a modal); + + +## How to Use + +> If your field name is comprised of multiple words (eg. `contact_number` or `contactNumber`) you will need to also define the `data_source` attribute for this field; keep in mind that by to generate a route, your field name will be parsed run through `Str::kebab()` - that means `_` (underscore) or `camelCase` will be converted to `-` (hyphens), so in `fetch` your route will be `contact-number` instead of the expected `contactNumber`. To fix this, you need to define: `data_source => backpack_url('monster/fetch/contact-number')` (replace with your strings) + +To use the Create operation, you must: + +**Step 1. Use the operation trait on your secondary entity's CrudController** (aka. the entity that will gain the ability to be created inline, in our example CategoryCrudController). Make sure you use `InlineCreateOperation` *after* `CreateOperation`: + +```php +crud->setValidation(StoreRequest::class); + // $this->crud->addField($field_definition_array); + // } +} +``` + +**Step 2. Use [the relationship field](/docs/{{version}}/crud-fields#relationship) inside the ```setupCreateOperation()``` or ```setupUpdateOperation()``` of the main entity** (where you'd like to be able to click a button and a modal shows up, in our example ArticleCrudController), and define ```inline_create``` on it: + +```php +// for 1-n relationships (ex: category) - inside ArticleCrudController +[ + 'type' => "relationship", + 'name' => 'category', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => true, // assumes the URL will be "/admin/category/inline/create" +] + +// for n-n relationships (ex: tags) - inside ArticleCrudController +[ + 'type' => "relationship", + 'name' => 'tags', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => [ 'entity' => 'tag' ] // specify the entity in singular + // that way the assumed URL will be "/admin/tag/inline/create" +] + +// OPTIONALS - to customize behaviour +[ + 'type' => "relationship", + 'name' => 'tags', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => [ // specify the entity in singular + 'entity' => 'tag', // the entity in singular + // OPTIONALS + 'force_select' => true, // should the inline-created entry be immediately selected? + 'modal_class' => 'modal-dialog modal-xl', // use modal-sm, modal-lg to change width + 'modal_route' => route('tag-inline-create'), // InlineCreate::getInlineCreateModal() + 'create_route' => route('tag-inline-create-save'), // InlineCreate::storeInlineCreate() + 'add_button_label' => 'New tag', // configure the text for the `+ Add` inline button + 'include_main_form_fields' => ['field1', 'field2'], // pass certain fields from the main form to the modal, get them with: request('main_form_fields') + ] +``` + + +**Step 3. OPTIONAL - You can create a ```setupInlineCreateOperation()``` method in the EntityCrudController**, to make the InlineCreateOperation different to the CreateOperation, for example have more/less fields, or different fields. Check out the [Fields API](/docs/{{version}}/crud-fields#fields-api) for a reference of all you can do with them. + + +## How It Works + +The ```CreateInline``` operation uses two routes: +- POST to ```/entity-name/inline/create/modal``` - ```getInlineCreateModal()``` which returns the contents of the Create form, according to how it's been defined by the CreateOperation (in ```setupCreateOperation()```, then overwritten by the InlineCreateOperation (in ```setupInlineCreateOperation()```); +- POST to ```/entity-name/inline/create``` - points to ```storeInlineCreate()``` which does the actual saving in the database by calling the ```store()``` method from the CreateOperation; + +Since this operation is just a way to allow access to the Create operation from a modal, the ```getInlineCreateModal()``` method will show all the fields you've defined for this operation using the [Fields API](/docs/{{version}}/crud-fields#fields-api), then upon Save the ```store()``` method will first check the validation from the FormRequest you've specified, then create the entry using the Eloquent model. Only attributes that are specified as fields, and are ```$fillable``` on the model will actually be stored in the database. + + +## Using Widgets with Inline Create + +When you have Widgets in your "related entry create form", for example a script widget with some javascript, you need to tell Backpack that you want to load that Widget inline too when the form is loaded in the modal. You can do that by adding the to the widget definition `inline()`: + +```diff +- Widget::add()->type('script')->content('assets/my-javascript.js'); ++ Widget::add()->type('script')->inline()->content('assets/my-javascript.js'); +``` +This will load the Widget in both instances, on the create form, and in the inline create form. \ No newline at end of file diff --git a/7.x-dev/crud-operation-list-entries.md b/7.x-dev/crud-operation-list-entries.md new file mode 100644 index 00000000..7b5b74a9 --- /dev/null +++ b/7.x-dev/crud-operation-list-entries.md @@ -0,0 +1,464 @@ +# List Operation + +--- + + +## About + +This operation shows a table with all database entries. It's the first page the admin lands on (for an entity), and it's usually the gateway to all other operations, because it holds all the buttons. + +A simple List view might look like this: + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs-4-2/operations/listEntries.png) + +But a complex implementation of the ListEntries operation, using Columns, Filters, custom Buttons, custom Operations, responsive table, Details Row, Export Buttons will still look pretty good: + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs-4-2/operations/listEntries_full.png) + +You can easily customize [columns](#columns), [buttons](#buttons), [filters](#filters), [enable/disable additional features we've built](#other-features), or [overwrite the view and build your own features](#how-to-overwrite). + + +## How It Works + +The main route leads to ```EntityCrudController::index()```, which shows the table view (```list.blade.php```. Inside that table view, we're using AJAX to fetch the entries and place them inside DataTables. That AJAX points to the same controller, ```EntityCrudController::search()```. + +Actions: +- ```index()``` +- ```search()``` + +For views, it uses: +- ```list.blade.php``` +- ```columns/``` +- ```buttons/``` + + +## How to Use + +Use the operation trait on your controller: +```php + +### Columns + +The List operation uses "columns" to determine how to show the attributes of an entry to the user. All column types must have their ```name```, ```label``` and ```type``` specified, but some could require some additional attributes. + +```php +CRUD::column([ + 'name' => 'name', // The db column name + 'label' => "Tag Name", // Table column heading + 'type' => 'Text' +]); +``` + +Backpack has 22+ [column types](/docs/{{version}}/crud-columns) you can use. Plus, you can easily [create your own type of column](/docs/{{version}}/crud-columns##creating-a-custom-column-type). **Check out the [Columns](/docs/{{version}}/crud-columns##creating-a-custom-column-type) documentation page** for a detailed look at column types, API and usage. + + +### Buttons + +Buttons are used to trigger other operations. Some point to entirely new routes (```create```, ```update```, ```show```), others perform the operation on the current page using AJAX (```delete```). + +The List/Show operations have 3 places where buttons can be placed: + - ```top``` (where the Add button is) + - ```line``` (where the Edit and Delete buttons are) + - ```bottom``` (after the table) + +Backpack adds a few buttons by default: +- ```add``` to the ```top``` stack; +- ```edit``` and ```delete``` to the ```line``` stack; + +#### Merging line buttons into a dropdown + +**NOTE**: The `line` stack buttons can be converted into a dropdown to improve the available table space. +![Backpack List Operation Dropdown ](https://user-images.githubusercontent.com/33960976/228809544-0d5a0d94-9195-4f45-9e20-e9ea32932f49.png) + +This is done by setting the `lineButtonsAsDropdown` setting in list operation to `true`. + +a) For all CrudController (globally) in the `config/backpack/operations/list.php` file. + +b) For a specific CrudController, in its `setupListOperation()` define `CRUD::setOperationSetting('lineButtonsAsDropdown', true);` + +To learn more about buttons, **check out the [Buttons](/docs/{{version}}/crud-buttons) documentation page**. + + +### Filters PRO + +Filters show up right before the actual table, and provide a way for the admin to filter the results in the ListEntries table. To learn more about filters, **check out the [Filters](/docs/{{version}}/crud-filters) documentation page**. Please note that filters are a PRO feature. Check out more differences in [FREE vs PRO](/docs/{{version}}/features-free-vs-paid#features). + + +### Other Features + + +#### Details Row + +The details row functionality allows you to present more information in the table view of a CRUD. When enabled, a "+" button will show up next to every row, which on click will expand a "details row" below it, showing additional information. + +![Backpack CRUD ListEntries Details Row](https://github.com/Laravel-Backpack/docs/assets/7188159/f71ef8e5-f198-4b87-9f11-67fb9f0e972d) + +On click, an AJAX request is sent to the `entity/{id}/details` route, which calls the `showDetailsRow()` method on your EntityCrudController. Everything returned by that method is then shown in the details row (usually a blade view). + +To use, inside your `EntityCrudController` you must first enable the functionality in your `setupListOperation` with: `CRUD::enableDetailsRow();` + +The `details_row` provided by Backpack display widgets from the `details_row` section by default. In your `setupListOperation` you can add widgets to the `details_row` section to display them in the details row. Inside those widgets you have access to `$entry` and `$crud` variables. + +```php +public function setupListOperation() +{ + // ... + Widget::add()->to('details_row')->type('progress')->value(135)->description('Progress')->progress(50); + // ... +} +``` + +Alternatively, if you don't want to use widgets and want to build your own details row, you can: +1. Create a file in your resources folder, with the details row template for that entity. For example, `resources/views/admin/articles_details_row.blade.php`. You can use the `$entry` and `$crud` variables inside that view, to show information about the current entry. +2. Tell Backpack what view to load with: `CRUD::setDetailsRowView('admin.articles_details_row')` in your `setupListOperation()` method. + +**NOTE:** Even when you don't `enableDetailsRow()`, Backpack register the necessary routes for it when using the ListOperation. If you are sure **you don't want to use details row** in that CrudController you can set `protected $setupDetailsRowRoute = false;` in your CrudController. + +##### Overwrite default details row functionality + + +Backpack ships with a default details row template. If you want to use the same template across all your cruds you can overwrite it by creating a `resources/views/vendor/backpack/crud/inc/details_row.blade.php` file. When doing `CRUD::enableDetailsRow()` this template will be used by default. + +You can also create a `showDetailsRow($id)` method in your CrudController to overwrite the default behaviour. + + +#### Export Buttons PRO + +Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```CRUD::enableExportButtons();``` in your constructor. + +![Backpack CRUD ListEntries Details Row](https://backpackforlaravel.com/uploads/docs-4-0/operations/listEntries_export_buttons.png) + +**Please note that when clicked, the button will export** +- **the _currently visible_ table columns** (except columns marked as ```visibleInExport => false```); +- **the columns that are forced to export** (with ```visibleInExport => true``` or ```exportOnlyField => true```); + +**In the UI, the admin can use the "Visibility" button, and the "Items per page" dropdown to manipulate what is visible in the table - and consequently what will be exported.** + +**Export Buttons Rules** + +Available customization: +``` +'visibleInExport' => true/false +'visibleInTable' => true/false +'exportOnlyField' => true +``` + +By default, the field will start visible in the table. Users can hide it toggling visibility. Will be exported if visible in the table. + +If you force `visibleInExport => true` you are saying that independent of field visibility in table it will **always** be exported. + +Contrary is `visibleInExport => false`, even if visible in table, field will not be exported as per developer instructions. + +Setting `visibleInTable => true` will force the field to stay in the table no matter what. User can't hide it. (By default all fields visible in the table will be exported. If you don't want to export this field use with combination with `visibleInExport => false`) + +Using `'visibleInTable' => false` will make the field start hidden in the table. But users can toggle it's visibility. + +If you want a field that is not on table, user can't show it, but will **ALWAYS** be exported use the `exportOnlyField => true`. If used will ignore any other custom visibility you defined. + +#### How to use different separator in DataTables (eg. semicolon instead of comma) + + + +If you want to change the separator in dataTable export to use semicolon (;) instead of comma (,) : + +**Step 1.** Copy vendor/backpack/crud/src/resources/views/crud/inc/export_buttons.blade.php to resources/views/vendor/backpack/crud/inc/export_buttons.blade.php + +**Step 2.** Change it in your `dataTableConfiguration`: +```php +{ + name: 'csvHtml5', + extend: 'csvHtml5', + fieldSeparator: ';', + exportOptions: { + columns: function ( idx, data, node ) { + var $column = crud.table.column( idx ); + return ($column.visible() && $(node).attr('data-visible-in-export') != 'false') || $(node).attr('data-force-export') == 'true'; + } + }, + action: function(e, dt, button, config) { + crud.responsiveToggle(dt); + $.fn.DataTable.ext.buttons.csvHtml5.action.call(this, e, dt, button, config); + crud.responsiveToggle(dt); + } +}, + +``` + +#### Custom Query + + + + +By default, all entries are shown in the ListEntries table, before filtering. If you want to restrict the entries to a subset, you can use the methods below in your EntityCrudController's ```setupListOperation()``` method: + +```php +// Change what entries are shown in the table view. +// This changes all queries on the table view, +// as opposed to filters, who only change it when that filter is applied. +CRUD::addClause('active'); // apply a local scope +CRUD::addClause('type', 'car'); // apply local dynamic scope +CRUD::addClause('where', 'name', '=', 'car'); +CRUD::addClause('whereName', 'car'); +CRUD::addClause('whereHas', 'posts', function($query) { + $query->activePosts(); + }); +CRUD::groupBy(); +CRUD::limit(); +CRUD::orderBy(); // please note it's generally a good idea to use crud->orderBy() inside "if (!CRUD::getRequest()->has('order')) {}"; that way, your custom order is applied ONLY IF the user hasn't forced another order (by clicking a column heading) + +// The above will change the used query, so the ListOperation will say +// "Showing 140 entries, filtered from 1.000 entries". If you want to +// that, and make it look like only those entries are in the databse, +// you can change the baseQuery instead, by using: +CRUD::addBaseClause('where', 'name', '=', 'car'); +``` +**NOTE:** The query constraints added in the `setup()` method operation _cannot_ be reset by `Reset Button`. They are permanent for that CRUD, for all operation. + +#### Custom Order + + + +By default, the List operation gets sorted by the primary key (usually `id`), descending. You can modify this behaviour by defining your own ordering: +```php +protected function setupListOperation() +{ + //change default order key + if (! $this->crud->getRequest()->has('order')){ + $this->crud->orderBy('updated_at', 'desc'); + } +} +``` +**NOTE**: We only apply the `orderBy` when the request don't have an `order` key. +This is because we need to keep the ability to order in the Datatable Columns. +If we didn't conditionally add the `orderBy`, it would become a __permanent order__ that can't be cleared by the Datatables `Reset` button and applied to every request. + + +#### Responsive Table + +If your CRUD table has more columns than can fit inside the viewport (on mobile / tablet or smaller desktop screens), unimportant columns will start hiding and an expansion icon (three dots) will appear to the left of each row. We call this behaviour "_responsive table_", and consider this to be the best UX. By behaviour we consider the 1st column the most important, then 2nd, then 3rd, etc; the "actions" column is considered as important as the 1st column. You can of course [change the importance of columns](/docs/{{version}}/crud-columns#define-which-columns-to-hide-in-responsive-table). + +If you do not like this, you can **toggle off the responsive behaviour for all CRUD tables** by changing this config value in your ```config/backpack/crud.php``` to ```false```: +```php + // enable the datatables-responsive plugin, which hides columns if they don't fit? + // if not, a horizontal scrollbar will be shown instead + 'responsive_table' => true +``` + +To turn off the responsive table behaviour for _just one CRUD panel_, you can use ```CRUD::disableResponsiveTable()``` in your ```setupListOperation()``` method. + + +#### Persistent Table + +By default, ListEntries will NOT remember your filtering, search and pagination when you leave the page. If you want ListEntries to do that, you can enable a ListEntries feature we call ```persistent_table```. + +**This will take the user back to the _filtered table_ after adding an item, previewing an item, creating an item or just browsing around**, preserving the table just like he/she left it - with the same filtering, pagination and search applied. It does so by saving the pagination, search and filtering for an arbitrary amount of time (by default: forever). + +To use ```persistent_table``` you can: +- enable it for all CRUDs with the config option ```'persistent_table' => true``` in your ```config/backpack/crud.php```; +- enable it inside a particular crud controller with ```CRUD::enablePersistentTable();``` +- disable it inside a particular crud controller with ```CRUD::disablePersistentTable();``` + +> You can configure the persistent table duration in ``` config/backpack/crud.php ``` under `operations > list > persistentTableDuration`. False is forever. Set any amount of time you want in minutes. Note: you can configure it's expiring time on a per-crud basis using `CRUD::setOperationSetting('persistentTableDuration', 120); in your setupListOperation()` for 2 hours persistency. The default is `false` which means forever. + + +#### Large Tables (millions of entries) + +By default, ListEntries uses a few features that are not appropriate for Eloquent models with millions (or billions) of records: +- it shows the total number of entries (which can be a very slow query for big tables); +- it paginates using 1/2/3 page buttons, instead of just previous & next; + +Starting with Backpack v5.4 we have an easy way to disable both of those, in order to make the ListOperation super-fast on big database tables. You just need to do: + +```php +protected function setupListOperation() +{ + // ... + CRUD::setOperationSetting('showEntryCount', false); + // ... +} +``` + + +#### Custom Views (for ListOperation) PRO + +You might need different "views" or "pages" for your ListOperation, where each view has some filters/columns. For example: +- default `Product` list view - show all products; +- different `Best Sold Products` list view; +- different `Products for accounting` list view + +The `CustomViews` operation helps you do exactly that - create alternative "views" for your ListOperation. Your admin will get a new dropdown right next to the "Add" button, to toggle between the different list views: + +![Backpack PRO CustomViewsOperation](https://backpackforlaravel.com/uploads/news_images/286036856-edb3a77e-4a65-454c-a6dc-eae05837fe3b.png) + + +To do that: + +1) **Use the `CustomViewOperation` trait in your CrudController**: + +```php +class YourCrudController extends CrudController +{ + ... + use \Backpack\Pro\Http\Controllers\Operations\CustomViewOperation; +``` + +2) **Add `$this->runCustomViews()` at the end of your `setupListOperation()` method.** That will look for all the views you have defined. If you want to costumize the the title of your views, you can pass an array with the key being the name of the method and the value being the title of the view: + +```php +public function setupListOperation() +{ + // ... + + $this->runCustomViews(); + // or + $this->runCustomViews([ + 'setupLast12MonthsView' => __('Last 12 months'), + 'setupLast6MonthsView' => __('Last 6 months'), + ]); +} +``` +3) **Add the view logic you want to use in your CrudController.** This is meant to be run after all the the `setupListOperation()` columns, filters, buttons, etc. have been defined, so it should perform operations over the current state, like add or remove, columns, filters, buttons, etc, depending on your needs for that view. + +```php +public function setupLast6MonthsView() +{ + // ... +} + +public function setupLast12MonthsView() +{ + // ... +} +``` + +**NOTE:** The `CustomView` will apply the query "on top" of the current `$crud->query`. If you would like to use a "fresh query" for your custom view you can use the `CRUD::setQuery()` method that will overwrite the previous set query. + + +## How to add custom sections (aka. Widgets) + +[Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. You can use the [default widget types](https://backpackforlaravel.com/docs/{{version}}/base-widgets#default-widget-types) or [create your own custom widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets#creating-a-custom-widget-type). + +Backpack's default template includes two [sections](https://backpackforlaravel.com/docs/{{version}}/base-widgets#requirements-1) where you can push widgets: + +* `before_content` +* `after_content` + +To use widgets on list operation, define them inside `setupListOperation()` function. + +```php +public function setupListOperation() +{ + // dynamic data to render in the following widget + $userCount = \App\Models\User::count(); + + //add div row using 'div' widget and make other widgets inside it to be in a row + Widget::add()->to('before_content')->type('div')->class('row')->content([ + + //widget made using fluent syntax + Widget::make() + ->type('progress') + ->class('card border-0 text-white bg-primary') + ->progressClass('progress-bar') + ->value($userCount) + ->description('Registered users.') + ->progress(100 * (int)$userCount / 1000) + ->hint(1000 - $userCount . ' more until next milestone.'), + + //widget made using the array definition + Widget::make( + [ + 'type' => 'card', + 'class' => 'card bg-dark text-white', + 'wrapper' => ['class' => 'col-sm-3 col-md-3'], + 'content' => [ + 'header' => 'Example Widget', + 'body' => 'Widget placed at "before_content" secion in same row', + ] + ] + ), + ]); + + //you can also add Script & CSS to your page using 'script' & 'style' widget + Widget::add()->type('script')->stack('after_scripts')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + Widget::add()->type('style')->stack('after_styles')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); +} +``` + +**Output:** +* Using `before_content`: + +![](https://i.imgur.com/MF9ePIM.png) +* Using `after_content` + +![](https://i.imgur.com/AxC3lAZ.png) + + +## How to Overwrite + +The main route leads to ```EntityCrudController::index()```, which loads ```list.blade.php```. Inside that table view, we're using AJAX to fetch the entries and place them inside a DataTables. The AJAX points to the same controller, ```EntityCrudController::search()```. + + +### The View + +You can change how the ```list.blade.php``` file looks and works, by just placing a file with the same name in your ```resources/views/vendor/backpack/crud/list.blade.php```. To quickly do that, run: + +```zsh +php artisan backpack:publish crud/list +``` + +Keep in mind that by publishing this file, you won't be getting any updates we'll be pushing to it. + + +### The Operation Logic + +Getting and showing the information is done inside the ```index()``` method. Take a look at the ```CrudController::index()``` method (your EntityCrudController is extending this CrudController) to see how it works. + +To overwrite it, just create an ```index()``` method in your ```EntityCrudController```. + + +### The Search Logic + +An AJAX call is made to the ```search()``` method: +- when entries are shown in the table; +- when entries are filtered in the table; +- when search is performed on the table; +- when pagination is performed on the table; + +You can of course overwrite this ```search()``` method by just creating one with the same name in your ```EntityCrudController```. In addition, you can overwrite what a specific column is searching through (and how), by [using the searchLogic attribute](/docs/{{version}}/crud-columns#custom-search-logic) on columns. + + + +## How to Debug + +Because the entries are fetched using AJAX requests, debugging the ListOperation can be a little difficult. Fortunately, we've thought of that. + + +### Errors in AJAX requests + +If an error is thrown during the AJAX request, Backpack will show that error in a modal. Easy-peasy. + + +### See query, models, views, exceptions in AJAX requests + +If you want to see or optimize database queries, you can do that using any Laravel tool that analyzes AJAX request. For example, here's how to analyze AJAX requests using the excellent [barryvdh/laravel-debugbar](https://github.com/barryvdh/laravel-debugbar). You just click the Folder icon to the right, and you select the latest request. Debugbar will then show you all info for that last AJAX request: + +![How to use DebugBar with Backpack's ListOperation](https://user-images.githubusercontent.com/1032474/227514264-0a95ac8f-1bfa-4009-86c4-3c8313ca3399.gif) diff --git a/7.x-dev/crud-operation-reorder.md b/7.x-dev/crud-operation-reorder.md new file mode 100644 index 00000000..5cd6ac55 --- /dev/null +++ b/7.x-dev/crud-operation-reorder.md @@ -0,0 +1,145 @@ +# Reorder Operation + +--- + + +## About + +This operation allows your admins to reorder & nest entries. + +![CRUD Reorder Operation](https://backpackforlaravel.com/uploads/docs-4-0/operations/reorder.png) + + +## Requirements + +Your model should have the following integer fields, with a default value of 0: ```parent_id```, ```lft```, ```rgt```, ```depth```. + +Additionally, the `parent_id` field has to be nullable. + + +## How to Use + +In order to enable this operation in your CrudController, you need to use the ```ReorderOperation``` trait, and have a ```setupReorderOperation()``` method that defines the ```label``` and ```max_level``` of allowed depth. + +```php + +## How It Works + +The ```/reorder``` route points to a ```reorder()``` method in your ```EntityCrudController```. + + +## How to add custom sections(aka. Widgets) + +[Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. You can use the [default widget types](https://backpackforlaravel.com/docs/{{version}}/base-widgets#default-widget-types) or [create your own custom widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets#creating-a-custom-widget-type). + +Backpack's default template includes two [sections](https://backpackforlaravel.com/docs/{{version}}/base-widgets#requirements-1) where you can push widgets: + +* `before_content` +* `after_content` + +To use widgets on reorder operation, define them inside `setupReorderOperation()` function. + +```php +public function setupReorderOperation() +{ + // dynamic data to render in the following widget + $userCount = \App\Models\User::count(); + + //add div row using 'div' widget and make other widgets inside it to be in a row + Widget::add()->to('before_content')->type('div')->class('row')->content([ + + //widget made using fluent syntax + Widget::make() + ->type('progress') + ->class('card border-0 text-white bg-primary') + ->progressClass('progress-bar') + ->value($userCount) + ->description('Registered users.') + ->progress(100 * (int)$userCount / 1000) + ->hint(1000 - $userCount . ' more until next milestone.'), + + //widget made using the array definition + Widget::make( + [ + 'type' => 'card', + 'class' => 'card bg-dark text-white', + 'wrapper' => ['class' => 'col-sm-3 col-md-3'], + 'content' => [ + 'header' => 'Example Widget', + 'body' => 'Widget placed at "before_content" secion in same row', + ] + ] + ), + ]); + + //you can also add Script & CSS to your page using 'script' & 'style' widget + Widget::add()->type('script')->stack('after_scripts')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + Widget::add()->type('style')->stack('after_styles')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); +} +``` + +#### Output: +* Using `before_content`: + +![](https://i.imgur.com/MF9ePIM.png) +* Using `after_content` + +![](https://i.imgur.com/AxC3lAZ.png) + + +## How to Overwrite + +In case you need to change how this operation works, take a look at the ```ReorderOperation.php``` trait to understand how it works. You can easily overwrite the ```reorder()``` or the ```saveReorder()``` methods: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\ReorderOperation { reorder as traitReorder; } + +public function reorder() +{ + // your custom code here + + // call the method in the trait + return $this->traitReorder(); +} +``` + +You can also overwrite the reorder button by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the reorder button there to make changes using: + +```zsh +php artisan backpack:button --from=reorder +``` diff --git a/7.x-dev/crud-operation-revisions.md b/7.x-dev/crud-operation-revisions.md new file mode 100644 index 00000000..8f841408 --- /dev/null +++ b/7.x-dev/crud-operation-revisions.md @@ -0,0 +1,71 @@ +# Revise Operation + +--- + + +## About + +Revise allows your admins to store, see and undo changes to entries on an Eloquent model. + +The operation provides you with a simple interface to work with [venturecraft/revisionable](https://github.com/VentureCraft/revisionable#implementation), which is a great package that stores all changes in a separate table. It can work as an audit trail, a backup system and an accountability system for the admins. + +When enabled, ```Revise``` will show another button in the table view, between _Edit_ and _Delete_. On click, that button opens another page which will allow an admin to see all changes and who made them: + + +![CRUD Revision Operation](https://backpackforlaravel.com/uploads/docs-4-0/operations/revisions.png) + + + +## How to Use + +In order to enable this operation for a CrudController, you need to: + +**Step 1.** Install [the package](https://github.com/laravel-backpack/revise-operation) that provides this operation. This will also install venturecraft/revisionable if it's not already installed in your project. + +```bash +composer require backpack/revise-operation +``` + +**Step 2.** Create the revisions table: + +```bash +cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php database/migrations/ && php artisan migrate +``` + +**Step 3.** Use [venturecraft/revisionable](https://github.com/VentureCraft/revisionable#implementation)'s trait on your model, and an ```identifiableName()``` method that returns an attribute on the model that the admin can use to distinguish between entries (ex: name, title, etc). If you are using another bootable trait be sure to override the boot method in your model. + +```php +namespace App\Models; + +class Article extends Eloquent { + use \Backpack\CRUD\app\Models\Traits\CrudTrait, \Venturecraft\Revisionable\RevisionableTrait; + + public function identifiableName() + { + return $this->name; + } + + // If you are using another bootable trait + // be sure to override the boot method in your model + public static function boot() + { + parent::boot(); + } +} +``` + +**Step 4.** In your CrudController, use the operation trait: + +```php + +## About + +This CRUD operation allows your admins to preview an entry. When enabled, it will add a "Preview" button in the ListEntries view, that points to a show page. By default, it will show all attributes for that model: + +![Backpack CRUD Show Operation](https://backpackforlaravel.com/uploads/docs-4-0/operations/show.png) + +In case your entity is translatable, it will show a multi-language dropdown, just like Edit. + + +## How it Works + +The ```/entity-name/{id}/show``` route points to the ```show()``` method in your EntityCrudController, which shows all columns that have been set up using [column types](/docs/{{version}}/crud-columns), by showing a ```show.blade.php``` blade file. + + +## How to Use + +To enable this operation, you need to use the ```ShowOperation``` trait on your CrudController: + +```php + +## How to Configure + +### setupShowOperation() + +You can manually define columns inside the ```setupShowOperation()``` method - thereby stopping the default "guessing" and "removing" of columns - you'll start from a blank slate and be in complete control of what columns are shown. For example: + +```php + // if you just want to show the same columns as inside ListOperation + protected function setupShowOperation() + { + $this->setupListOperation(); + } +``` + +But you can also do both - let Backpack guess columns, and do stuff before or after that guessing, by calling the `autoSetupShowOperation()` method wherever you want inside your `setupShowOperation()`: + +```php + // show whatever you want + protected function setupShowOperation() + { + // MAYBE: do stuff before the autosetup + + // automatically add the columns + $this->autoSetupShowOperation(); + + // MAYBE: do stuff after the autosetup + + // for example, let's add some new columns + CRUD::column([ + 'name' => 'my_custom_html', + 'label' => 'Custom HTML', + 'type' => 'custom_html', + 'value' => 'Something', + ]); + // in the following examples, please note that the table type is a PRO feature + CRUD::column([ + 'name' => 'table', + 'label' => 'Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ] + ]); + CRUD::column([ + 'name' => 'fake_table', + 'label' => 'Fake Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ], + ]); + + // or maybe remove a column + CRUD::column('text')->remove(); + } +``` +### Tabs - display columns in tabs + +Adding the `tab` attribute to a column, will make the operation display the columns in tabs if `show.tabsEnabled` operation setting is not set to `false`. + +```php +public function setupShowOperation() +{ + // using the array syntax + CRUD::column([ + 'name' => 'name', + 'tab' => 'General', + ]); + // or using the fluent syntax + CRUD::column('description')->tab('Another tab'); +} +``` + +By default `horizontal` tabs are displayed. You can change them to `vertical` by adding in the setup function: +`$this->crud->setOperationSetting('tabsType', 'vertical')` + +As like any other operation settings, those can be changed globaly for all CRUDs in the `config/backpack/operations/show.php` file. + + +## How to add custom sections(aka. Widgets) + +[Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. You can use the [default widget types](https://backpackforlaravel.com/docs/{{version}}/base-widgets#default-widget-types) or [create your own custom widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets#creating-a-custom-widget-type). + +Backpack's default template includes two [sections](https://backpackforlaravel.com/docs/{{version}}/base-widgets#requirements-1) where you can push widgets: + +* `before_content` +* `after_content` + +To use widgets on show operation, define them inside `setupShowOperation()` function. + +```php +public function setupShowOperation() +{ + // dynamic data to render in the following widget + $userCount = \App\Models\User::count(); + + //add div row using 'div' widget and make other widgets inside it to be in a row + Widget::add()->to('before_content')->type('div')->class('row')->content([ + + //widget made using fluent syntax + Widget::make() + ->type('progress') + ->class('card border-0 text-white bg-primary') + ->progressClass('progress-bar') + ->value($userCount) + ->description('Registered users.') + ->progress(100 * (int)$userCount / 1000) + ->hint(1000 - $userCount . ' more until next milestone.'), + + //widget made using the array definition + Widget::make( + [ + 'type' => 'card', + 'class' => 'card bg-dark text-white', + 'wrapper' => ['class' => 'col-sm-3 col-md-3'], + 'content' => [ + 'header' => 'Example Widget', + 'body' => 'Widget placed at "before_content" secion in same row', + ] + ] + ), + ]); + + //you can also add Script & CSS to your page using 'script' & 'style' widget + Widget::add()->type('script')->stack('after_scripts')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + Widget::add()->type('style')->stack('after_styles')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); +} +``` + +#### Output: +* Using `before_content`: + +![](https://i.imgur.com/MF9ePIM.png) +* Using `after_content` + +![](https://i.imgur.com/AxC3lAZ.png) + + +## How to Overwrite + +In case you need to modify the show logic in a meaningful way, you can create a ```show()``` method in your EntityCrudController. The route will then point to your method, instead of the one in the trait. For example: + +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\ShowOperation { show as traitShow; } + +public function show($id) +{ + // custom logic before + $content = $this->traitShow($id); + // custom logic after + return $content; +} +``` diff --git a/7.x-dev/crud-operation-trash.md b/7.x-dev/crud-operation-trash.md new file mode 100644 index 00000000..e1b56b60 --- /dev/null +++ b/7.x-dev/crud-operation-trash.md @@ -0,0 +1,166 @@ +# Trash Operation PRO + +-- + + +## About + +This CRUD operation allows your admins to soft delete, restore and permanently delete entries from the table. In other words, admins can send entries to the trash, recover them from trash or completely destroy them. + + +## Requirements + +1. This is a PRO operation. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/products/pro-for-unlimited-projects). + +2. In addition, it needs that your Model uses Laravel's `SoftDeletes` trait. For that, you should: +- generate a migration to add the `deleted_at` column to your table, eg. `php artisan make:migration add_soft_deletes_to_products --table=products`; +- inside that file's `Schema::table()` closure, add `$table->softDeletes();` +- run `php artisan migrate` +- add `use SoftDeletes` on the corresponding model (and import that class namespace); + + +## Trash a Single Item PRO + + +### How it Works +- **Trash**: +Using AJAX, a DELETE request is performed towards ```/entity-name/{id}/trash```, which points to the ```trash()``` method in your EntityCrudController. + +- **Restore**: +Using AJAX, a PUT request is performed towards ```/entity-name/{id}/restore```, which points to the ```restore()``` method in your EntityCrudController. + +- **Destroy**: +Using AJAX, a DELETE request is performed towards ```/entity-name/{id}/destroy```, which points to the ```destroy()``` method in your EntityCrudController. + + +### How to Use + +**Step 1.** If your EntityCrudController uses the `DeleteOperation`, remove it. `TrashOperation` is a complete alternative to `DeleteOperation`. + +**Step 2.** You need to ```use \Backpack\Pro\Http\Controllers\Operations\TrashOperation;``` inside your EntityCrudController. Ideally the TrashOperation should be the last one that gets used. For example: + +```php +class ProductCrudController extends CrudController +{ + // ... other operations + use \Backpack\Pro\Http\Controllers\Operations\TrashOperation; +} +``` +This will make a Trash button and Trashed filter show up in the list view, and will enable the routes and functionality needed for the operation. If you're getting a "Trait not found" exception, make sure in the namespace you have typed `Backpack\Pro`, not `Backpack\PRO`. + + + +### How to configure + +You can easily disable the default trash filter: +```php +public function setupTrashOperation() +{ + CRUD::setOperationSetting('withTrashFilter', false); + + // one of the things the filter does is preventing the update/access to trashed items. + // if you disable it and use Update/Show operations, you should manually disable the + // access to those operations or they will throw 404: + CRUD::setAccessCondition(['update', 'show'], function($entry) { + return ! $entry->trashed(); + }); +} +``` + +Disabling the filter also will make the trashed items show in your List view. Note that, by default, the `Destroy` button is only shown in _trashed items_. If you want to allow your admins to _permanently delete_ without sending first to trash... you can achieve that by defining in your operation setup: + +```php +// in the setupTrashOperation method +CRUD::setOperationSetting('canDestroyNonTrashedItems', true); +``` + + +### How to control access to operation actions + +When used, `TrashOperation` each action inside this operation (`trash`, `restore` and `destroy`) checks for access, before being performed. Likewise, `BulkTrashOperation` checks for access to `bulkTrash`, `bulkRestore` and `bulkDestroy`. + +That means you can revoke access to some operations, depending on user roles or anything else you want: +```php +// if user is not superadmin, don't allow permanently delete items +public function setupTrashOperation() +{ + if(! backpack_user()->hasRole('superadmin')) { + CRUD::denyAccess('destroy'); + } +} +``` + + +### How to Override + +In case you need to change how this operation works, just create ```trash()```, ```restore()```,```destroy()``` methods in your EntityCrudController, and they will be used instead of the default ones. For example for `trash()`: + +```php +use \Backpack\Pro\Http\Controllers\Operations\TrashOperation { trash as traitTrash; } + +public function trash($id) +{ + CRUD::hasAccessOrFail('trash'); + + // your custom code here +} +``` + +You can also override the buttons by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the buttons there to make changes using: + +```zsh +php artisan backpack:button --from=trash + +php artisan backpack:button --from=restore + +php artisan backpack:button --from=destroy +``` + + +## BulkTrash (Trash Multiple Items) PRO + +In addition to the button for each entry, PRO developers can show checkboxes next to each element, to allow their admin to trash, restore & delete multiple entries at once. + + + +### How it Works + +- **BulkTrash**: +Using AJAX, a DELETE request is performed towards ```/entity-name/{id}/bulk-trash```, which points to the ```bulkTrash()``` method in your EntityCrudController. + +- **BulkRestore**: +Using AJAX, a PUT request is performed towards ```/entity-name/{id}/bulk-restore```, which points to the ```bulkRestore()``` method in your EntityCrudController. + +- **BulkDestroy**: +Using AJAX, a DELETE request is performed towards ```/entity-name/{id}/bulk-destroy```, which points to the ```bulkDestroy()``` method in your EntityCrudController. + + +### How to Use + +Assuming your Model already uses Larave's `SoftDeletes`, you just need to ```use \Backpack\Pro\Http\Controllers\Operations\BulkTrashOperation;``` on your EntityCrudController. + + +### How to Override + +In case you need to change how this operation works, just create a ```bulkTrash()```, `bulkRestore()` or `bulkDestroy()` methods in your EntityCrudController: + +```php +use \Backpack\Pro\Http\Controllers\Operations\BulkTrashOperation { bulkTrash as traitBulkTrash; } + +public function bulkTrash() +{ + CRUD::hasAccessOrFail('bulkTrash'); + + // your custom code here +} +``` + +You can also override the buttons by creating a file with the same name inside your ```resources/views/vendor/backpack/crud/buttons/```. You can easily publish the buttons there to make changes using: + +```zsh +php artisan backpack:button --from=bulk_trash + +php artisan backpack:button --from=bulk_restore + +php artisan backpack:button --from=bulk_destroy +``` diff --git a/7.x-dev/crud-operation-update.md b/7.x-dev/crud-operation-update.md new file mode 100644 index 00000000..e6e7e92f --- /dev/null +++ b/7.x-dev/crud-operation-update.md @@ -0,0 +1,456 @@ +# Update Operation + +--- + + +## About + +This operation allows your admins to edit entries from the database. + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/docs-4-2/operations/update.png) + + +## Requirements + +All editable attributes should be ```$fillable``` on your Model. + + +## How to Use + +**Step 0. Use the operation trait on your controller**: + +```php +crud->setValidation(StoreRequest::class); + // $this->crud->addField() + + // or just do everything you've done for the Create Operation + // $this->crud->setupCreateOperation(); + + // You can also do things depending on the current entry + // (the database item being edited or updated) + // if ($this->crud->getCurrentEntry()->smth == true) {} + } +} +``` + +This will: +- allow access to this operation; +- make an Edit button appear in the ```line``` stack, next to each entry, in the List operation view; + +To use the Update operation, you must: + +**Step 1. Specify what field types** you'd like to show for each attribute, in your controller's ```setupUpdateOperation()``` method. You can do that using the [Fields API](/docs/{{version}}/crud-fields#fields-api). In short you can: + +```php +// add a field only to the Update operation +$this->crud->addField($field_definition_array); +``` + +**Step 2. Specify which FormRequest file to use for validation and authorization**, inside your ```setupUpdateOperation()``` method. You can pass the same FormRequest as you did for the Create operation if there are no differences. If you need separate validation for Create and Update [look here](#separate-validation). +```php +$this->crud->setValidation(StoreRequest::class); +``` + +For more on how to manipulate fields, please read the [Fields documentation page](/docs/{{version}}/crud-fields). For more on validation rules, check out [Laravel's validation docs](https://laravel.com/docs/master/validation#available-validation-rules). + +**Step 3. (optional recommended) eager load relationships**. If you're displaying relationships in your fields, you might want to eager load them to avoid the N+1 problem. You can do that in the `setupUpdateOperation()` method: + +```php +$this->crud->setOperationSetting('eagerLoadRelationships', true); +``` +Note: You can enable this setting globally for all your cruds in the `config/backpack/operations/update.php` file. + + +## How It Works + +CrudController is a RESTful controller, so the ```Update``` operation uses two routes: +- GET to ```/entity-name/{id}/edit``` - points to ```edit()``` which shows the Edit form (```edit.blade.php```); +- POST to ```/entity-name/{id}/edit``` - points to ```update()``` which uses Eloquent to update the entry in the database; + +The ```edit()``` method will show all the fields you've defined for this operation using the [Fields API](/docs/{{version}}/crud-fields#fields-api), then upon Save the ```update()``` method will first check the validation from the type-hinted FormRequest, then create the entry using the Eloquent model. Only attributes that have a field type added and are ```$fillable``` on the model will actually be updated in the database. + + +## How to add custom sections(aka. Widgets) + +[Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) (aka cards, aka charts, aka graphs) provide a simple way to insert blade files into admin panel pages. You can use them to insert cards, charts, notices or custom content into pages. You can use the [default widget types](https://backpackforlaravel.com/docs/{{version}}/base-widgets#default-widget-types) or [create your own custom widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets#creating-a-custom-widget-type). + +Backpack's default template includes two [sections](https://backpackforlaravel.com/docs/{{version}}/base-widgets#requirements-1) where you can push widgets: + +* `before_content` +* `after_content` + +To use widgets on update operation, define them inside `setupUpdateOperation()` function. + +```php +public function setupUpdateOperation() +{ + // dynamic data to render in the following widget + $userCount = \App\Models\User::count(); + + //add div row using 'div' widget and make other widgets inside it to be in a row + Widget::add()->to('before_content')->type('div')->class('row')->content([ + + //widget made using fluent syntax + Widget::make() + ->type('progress') + ->class('card border-0 text-white bg-primary') + ->progressClass('progress-bar') + ->value($userCount) + ->description('Registered users.') + ->progress(100 * (int)$userCount / 1000) + ->hint(1000 - $userCount . ' more until next milestone.'), + + //widget made using the array definition + Widget::make( + [ + 'type' => 'card', + 'class' => 'card bg-dark text-white', + 'wrapper' => ['class' => 'col-sm-3 col-md-3'], + 'content' => [ + 'header' => 'Example Widget', + 'body' => 'Widget placed at "before_content" secion in same row', + ] + ] + ), + ]); + + //you can also add Script & CSS to your page using 'script' & 'style' widget + Widget::add()->type('script')->stack('after_scripts')->content('https://code.jquery.com/ui/1.12.0/jquery-ui.min.js'); + Widget::add()->type('style')->stack('after_styles')->content('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.58/dist/themes/light.css'); +} +``` + +#### Output: +* Using `before_content`: + +![](https://i.imgur.com/MF9ePIM.png) +* Using `after_content` + +![](https://i.imgur.com/AxC3lAZ.png) + + +## Advanced Features and Techniques + + +### Validation + +There are three ways you can define the [validation rules](https://laravel.com/docs/validation#available-validation-rules) for your fields: + +#### Validating fields using FormRequests + +When you generate a CrudController, you'll notice a [Laravel FormRequest](https://laravel.com/docs/validation#form-request-validation) has also been generated, and that FormRequest is mentioned as the source of your validation rules: +```php +protected function setupUpdateOperation() +{ + $this->crud->setValidation(StoreRequest::class); +} +``` + +This works particularly well for bigger models, because you can mention a lot of rules, messages and attributes in your `FormRequest` and it will not increase the size of your `CrudController`. + +**Do you need different Create and Update validations?** Then create a separate request file for each operation and instruct your EntityCrudController to use those files: + +```php +use App\Http\Requests\CreateTagRequest as StoreRequest; +use App\Http\Requests\UpdateTagRequest as UpdateRequest; + +// ... + +public function setupCreateOperation() +{ + $this->crud->setValidation(CreateRequest::class); +} + +public function setupUpdateOperation() +{ + $this->crud->setValidation(UpdateRequest::class); +} +``` + +#### Validating fields using a rules array + +For smaller models (with just a few validation rules), creating an entire FormRequest file to hold them might be overkill. If you prefer, you can pass an array of [validation rules](https://laravel.com/docs/validation#available-validation-rules) to the same `setValidation()` method (with an optional second parameter for the validation messages): + +```php +protected function setupUpdateOperation() +{ + $this->crud->setValidation([ + 'name' => 'required|min:2', + ]); + + // or maybe + $rules = ['name' => 'required|min:2']; + $messages = [ + 'name.required' => 'You gotta give it a name, man.', + 'name.min' => 'You came up short. Try more than 2 characters.', + ]; + $this->crud->setValidation($rules, $messages); +} +``` + +This is more convenient for small and medium models. Plus, it's very easy to read. + +#### Validating fields using field attributes + +Another good option for small & medium models is to define the [validation rules](https://laravel.com/docs/validation#available-validation-rules) directly on your fields: + +```php +protected function setupUpdateOperation() +{ + $this->crud->addField([ + 'name' => 'content', + 'label' => 'Content', + 'type' => 'ckeditor', + 'placeholder' => 'Your textarea text here', + 'validationRules' => 'required|min:10', + 'validationMessages' => [ + 'required' => 'You gotta write smth man.', + 'min' => 'More than 10 characters, bro. Wtf... You can do this!', + ] + ]); + // CAREFUL! This MUST be called AFTER the fields are defined, NEVER BEFORE + $this->crud->setValidation(); +} +``` + +You must then call `setValidation()` without a parameter, and Backpack will go through all defined fields, get their `validationRules` and validate them. It is VERY IMPORTANT to call `setValidation()` _after_ you've defined the fields! Otherwise Backpack won't find any `validationRules`. + + +### Callbacks + +Developers coming other CRUD systems (like GroceryCRUD) will be looking for callbacks to run "before_insert", "before_update", "after_insert", "after_update". **There are no callbacks in Backpack**, because... they're not needed. There are plenty of other ways to do things before/after an entry is updated. + + +#### Use Events in your `setup()` method + +Laravel already triggers [multiple events](https://laravel.com/docs/master/eloquent#events) in an entry's lifecycle. Those also include: +- `updating` and `updated`, which are triggered by the Update operation; +- `saving` and `saved`, which are triggered by both the Create and the Update operations; + +So if you want to do something to a `Product` entry _before_ it's updated, you can easily do that: +```php +public function setupUpdateOperation() +{ + + // ... + + Product::updating(function($entry) { + $entry->last_edited_by = backpack_user()->id; + }); +} +``` + +Take a closer look at [Eloquent events](https://laravel.com/docs/master/eloquent#events) if you're not familiar with them, they're really _really_ powerful once you understand them. Please note that **these events will only get registered when the function gets called**, so if you define them in your `CrudController`, then: +- they will NOT run when an entry is changed outside that CrudController; +- if you want to expand the scope to cover both the `Create` and `Update` operations, you can easily do that, for example by using the `saving` and `saved` events, and moving the event-calling to your main `setup()` method; + +#### Use events in your field definition + +You can tell a field to do something to the entry when that field gets saved to the database. Rephrased, you can define standard [Eloquent events](https://laravel.com/docs/master/eloquent#events) directly on fields. For example: + +```php +// FLUENT syntax - use the convenience method "on" to define just ONE event +CRUD::field('name')->on('updating', function ($entry) { + $entry->last_edited_by = backpack_user()->id; +}); + +// FLUENT SYNTAX - you can define multiple events in one go +CRUD::field('name')->events([ + 'updating' => function ($entry) { + $entry->last_edited_by = backpack_user()->id; + }, + 'saved' => function ($entry) { + // TODO: upload some file + }, +]); + +// using the ARRAY SYNTAX, define an array of events and closures +CRUD::addField([ + 'name' => 'name', + 'events' => [ + 'updating' => function ($entry) { + $entry->author_id = backpack_user()->id; + }, + ], +]); +``` + +#### Override the `update()` method + +The store code is inside a trait, so you can easily overwrite it: + +```php +crud->addField(['type' => 'hidden', 'name' => 'author_id']); + // $this->crud->removeField('password_confirmation'); + + // Note: By default Backpack ONLY saves the inputs that were added on page using Backpack fields. + // This is done by stripping the request of all inputs that do NOT match Backpack fields for this + // particular operation. This is an added security layer, to protect your database from malicious + // users who could theoretically add inputs using DeveloperTools or JavaScript. If you're not properly + // using $guarded or $fillable on your model, malicious inputs could get you into trouble. + + // However, if you know you have proper $guarded or $fillable on your model, and you want to manipulate + // the request directly to add or remove request parameters, you can also do that. + // We have a config value you can set, either inside your operation in `config/backpack/crud.php` if + // you want it to apply to all CRUDs, or inside a particular CrudController: + // $this->crud->setOperationSetting('saveAllInputsExcept', ['_token', '_method', 'http_referrer', 'current_tab', 'save_action']); + // The above will make Backpack store all inputs EXCEPT for the ones it uses for various features. + // So you can manipulate the request and add any request variable you'd like. + // $this->crud->getRequest()->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->getRequest()->request->remove('password_confirmation'); + // $this->crud->getRequest()->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->getRequest()->request->remove('password_confirmation'); + + $response = $this->traitUpdate(); + // do something after save + return $response; + } +} +``` + +>But before you do that, ask yourself - **_is this something that should be done when an entry is added/updated/deleted from the application, too_**? Not just the admin admin? If so, a better place for it would be the Model. Remember your Model is a pure Eloquent Model, so the cleanest way might be to use [Eloquent Event Observers](https://laravel.com/docs/5.5/eloquent#events) or [accessors and mutators](https://laravel.com/docs/master/eloquent-mutators#accessors-and-mutators). + + +### Translatable models and multi-language CRUDs + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/docs-4-0/operations/update_translatable.png) + +For localized apps, you can let your admins edit multi-lingual entries. Translations are stored using [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable). + +In order to make one of your Models translatable (localization), you need to: +0. Be running MySQL 5.7+ (or a PostgreSQL with JSON column support); +1. [Install spatie/laravel-translatable](https://github.com/spatie/laravel-translatable#installation); +2. In your database, make all translatable columns either JSON or TEXT. +3. Use Backpack's ```HasTranslations``` trait on your model (instead of using spatie's ```HasTranslations```) and define what fields are translatable, inside the ```$translatable``` property. For example: + +```php + You DO NOT need to cast translatable string columns as array/json/object in the Eloquent model. From Eloquent's perspective they're strings. So: +> - you _should NOT_ cast ```name```; it's a string in Eloquent, even though it's stored as JSON in the db by SpatieTranslatable; +> - you _should_ cast ```extras``` to ```array```, if each translation stores an array of some sort; + +Change the languages available to translate to/from, in your crud config file (```config/backpack/crud.php```). By default there are quite a few enabled (English, French, German, Italian, Romanian). + +Additionally, if you have slugs (but only if you need translatable slugs), you'll need to use backpack's classes instead of the ones provided by `cviebrock/eloquent-sluggable`. +Make sure you have `cviebrock/eloquent-sluggable` installed as well, if not, please do it with `composer require cviebrock/eloquent-sluggable`: + +```php + [ + 'source' => 'slug_or_name', + ], + ]; + } +} +``` +> If your slugs are not translatable, use the ```cviebrock/eloquent-sluggable``` traits. The Backpack's ```Sluggable``` trait saves your slug as a JSON object, regardless of the ```slug``` field being defined inside the ```$translatable``` property. + + +### Delete button on Update operation + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/docs-5-0/operations/delete_from_form.gif) + +If you want to display a **Delete** button right on the **Update** operation, you simply need to add a line to the `setupUpdateOperation()` method: + +```php + protected function setupUpdateOperation() + { + // your code... + + $this->crud->setOperationSetting('showDeleteButton', true); // <--- add this! + + // alternatively you can pass an URL to where user should be redirected after entry is deleted: + // $this->crud->setOperationSetting('showDeleteButton', 'https://someurl.com'); + } +``` + +This will allow admins to remove entries right from the **Update Operation**, and it will redirect them back ot the **List Operation** afterwards. diff --git a/7.x-dev/crud-operations.md b/7.x-dev/crud-operations.md new file mode 100644 index 00000000..459b393c --- /dev/null +++ b/7.x-dev/crud-operations.md @@ -0,0 +1,1150 @@ +# Operations + +--- + +When creating a CRUD Panel, your ```EntityCrudController``` (where Entity = your model name) is extending ```CrudController```. **By default, no operations are enabled.** No routes are registered. + +To use an operation, you need to use the operation trait on your controller. For example, to enable the List operation: + +```php + +## Standard Operations + +No operations are enabled by default. + +But Backpack does provide the logic for the most common operations admins perform on Eloquent model. You just need to use it (and maybe configure it) in your controller. + +Operations provided by Backpack: +- [List](/docs/{{version}}/crud-operation-list-entries) - allows the admin to see all entries for a model, with pagination, search FREE and filters PRO +- [Create](/docs/{{version}}/crud-operation-create) - allows the admin to add a new entry; FREE +- [Update](/docs/{{version}}/crud-operation-update) - allows the admin to edit an existing entry; FREE +- [Show](/docs/{{version}}/crud-operation-show) - allows the admin to preview an entry; FREE +- [Delete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove and entry; FREE +- [BulkDelete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove multiple entries in one go; PRO +- [Clone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of a database entry; PRO +- [BulkClone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of multiple database entries in one go; PRO +- [Reorder](/docs/{{version}}/crud-operation-reorder) - allows the admin to reorder & nest all entries of a model, in a hierarchy tree; FREE +- [Revisions](/docs/{{version}}/crud-operation-revisions) - shows an audit log of all changes to an entry, and allows you to undo modifications; FREE + + + +### Operation Actions + +Each Operation is actually _a trait_, which can be used on CrudControllers. This trait can contain one or more methods (or functions). Since Laravel calls each Controller method an _action_, that means each _Operation_ can have one or many _actions_. For example, we have the ```create``` operation and two actions: ```create()``` and ```store()```. + +```php +trait CreateOperation +{ + public function create() + { + // ... + } + + public function store() + { + // ... + } +} +``` + +An action can do something with AJAX and return true/false, it can return a view, or whatever else you can do inside a controller method. Notice that it's a ```public``` method - which is a Laravel requirement, in order to point a route to it. + +You can check which action is currently being performed using the [standard Laravel Route API](https://laravel.com/api/8.x/Illuminate/Routing/Route.html): + +- ```\Route::getCurrentRoute()->getAction()``` or ```$this->crud->getRequest()->route()->getAction()```: +``` +array:8 [▼ + "middleware" => array:2 [▼ + 0 => "web" + 1 => "admin" + ] + "as" => "crud.monster.index" + "uses" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "operation" => "list" + "controller" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "namespace" => "App\Http\Controllers\Admin" + "prefix" => "admin" + "where" => [] +] +``` +- ```\Route::getCurrentRoute()->getActionName()``` or ```$this->crud->getRequest()->route()->getActionName()```: +``` +App\Http\Controllers\Admin\MonsterCrudController@index +``` +- ```\Route::getCurrentRoute()->getActionMethod()``` or ```$this->crud->getRequest()->route()->getActionMethod()```: +``` +index +``` + +You can also use the shortcuts on the CrudPanel object: +```php +$this->crud->getActionMethod(); // returns the method on the controller that was called by the route; ex: create(), update(), edit() etc; +$this->crud->actionIs('create'); // checks if the controller method given is the one called by the route +``` + + +### Titles, Headings and Subheadings + +For standard CRUD operations, each _action_ that shows an interface uses some texts to show the user what page, operation or action he is currently performing: +- **Title** - page title, shown in the browser's title bar; +- **Heading** - biggest heading on page; +- **Subheading** - short description of the current page, sits beside the heading; + +![CRUD Operation Headings](https://backpackforlaravel.com/uploads/docs-4-0/operations/headings.png) + +You can get and set the above using: +```php +// Getters +$this->crud->getTitle('create'); // get the Title for the create action +$this->crud->getHeading('create'); // get the Heading for the create action +$this->crud->getSubheading('create'); // get the Subheading for the create action + +// Setters +$this->crud->setTitle('some string', 'create'); // set the Title for the create action +$this->crud->setHeading('some string', 'create'); // set the Heading for the create action +$this->crud->setSubheading('some string', 'create'); // set the Subheading for the create action +``` + +These methods are usually useful inside actions, not in ```setup()```. Since action methods are called _after_ ```setup()```, any call to these getters and setters in ```setup()``` would get overwritten by the call in the action. + + +### Handling Access to Operations + +Admins are allowed to do an operation or not using a very simple system: ```$crud->settings['operation_name']['access']``` will either be ```true``` or ```false```. When you enable a stock Backpack operation by doing ```use SomeOperation;``` on your controller, all operations will run ```$this->crud->allowAccess('operation_name');```, which will toggle that variable to ```true```. + +You can easily add or remove elements to this access array in your ```setup()``` method, or your custom methods, using: +```php +$this->crud->allowAccess('operation_name'); +$this->crud->allowAccess(['list', 'update', 'delete']); +$this->crud->denyAccess('operation'); +$this->crud->denyAccess(['update', 'create', 'delete']); + +// allow or deny access depending on the entry in the table +$this->crud->operation('list', function() { + $this->crud->setAccessCondition(['update', 'delete'], function ($entry) { + return $entry->id===1 ? true : false; + }); +}); + +$this->crud->hasAccess('operation_name'); // returns true/false +$this->crud->hasAccessOrFail('create'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false +``` + + +### Operation Routes + +Starting with Backpack 4.0, routes can be defined in the CrudController. Your ```routes/backpack/custom.php``` file will have calls like ```Route::crud('product', 'ProductCrudController');```. This ```Route::crud()``` is a macro that will go to that controller and run all the methods that look like ```setupXxxRoutes()```. That means each operation can have its own method to define the routes it needs. And they do - if you check out the code of any operation, you'll see every one of them has a ```setupOperationNameRoutes()``` method. + +If you want to add a new route to your controller, there are two ways to do it: +1. Add a route in your ```routes/backpack/custom.php```; +2. Add a method following the ```setupXxxRoutes()``` convention to your controller; + +Inside a ```setupOperationNameRoutes()```, you'll notice that's also where we define the operation name: + +```php + protected function setupShowRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}/show', [ + 'as' => $routeName.'.show', + 'uses' => $controller.'@show', + 'operation' => 'show', + ]); + } +``` + + +### Getting an Operation Name + +Once an operation name has been set using that route, you can do ```$crud->getOperation()``` inside your views and do things according to this. + + + +## Creating a Custom Operation + + +### Command-line Tool + +If you've installed ```backpack/generators```, you can do ```php artisan backpack:crud-operation {OperationName}``` to generate an empty operation trait, that you can edit and use on your CrudControllers. For example: + +```bash +php artisan backpack:crud-operation Comment +``` + +Will generate ```app/Http/Controllers/Admin/Operations/CommentOperation``` with the following contents: + +```php + $routeName.'.comment', + 'uses' => $controller.'@comment', + 'operation' => 'comment', + ]); + } + + /** + * Add the default settings, buttons, etc that this operation needs. + */ + protected function setupCommentDefaults() + { + $this->crud->allowAccess('comment'); + + $this->crud->operation('comment', function () { + $this->crud->loadDefaultOperationSettingsFromConfig(); + }); + + $this->crud->operation('list', function () { + // $this->crud->addButton('top', 'comment', 'view', 'crud::buttons.comment'); + // $this->crud->addButton('line', 'comment', 'view', 'crud::buttons.comment'); + }); + } + + /** + * Show the view for performing the operation. + * + * @return Response + */ + public function comment() + { + $this->crud->hasAccessOrFail('comment'); + + // prepare the fields you need to show + $this->data['crud'] = $this->crud; + $this->data['title'] = $this->crud->getTitle() ?? 'comment '.$this->crud->entity_name; + + // load the view + return view("crud::operations.comment", $this->data); + } +} +``` + +You'll notice the generated operation has: +- a GET route (inside ```setupCommentRoutes()```); +- a method that sets the defaults for this operation (```setupCommentDefaults()```); +- a method to perform the operation, or show an interface (```comment()```); + +You can customize these to fit the operation you have in mind, then ```use \App\Http\Controllers\Admin\Operations\CommentOperation;``` inside the CrudControllers where you want the operation. + + +### Contents of a Custom Operation + +Thanks to [Backpack's simple architecture](/docs/{{version}}/crud-basics#architecture), each CRUD panel uses a controller and a route, that are placed inside your project. That means you hold the keys to how this controller works. + +To add an operation to an ```EntityCrudController```, you can: +- decide on your operation name; for example... "publish"; +- create a method that loads the routes inside your controller: +```php + protected function setupPublishRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}/publish', [ + 'as' => $routeName.'.publish', + 'uses' => $controller.'@publish', + 'operation' => 'publish', + ]); + } +``` +- create a method that performs the operation you want: +```php + public function publish() + { + // do something + // return something + } +``` +- [add a new button for this operation to the List view](/docs/{{version}}/crud-buttons#creating-a-custom-button), or enable access to it, inside a ```setupPublishDefaults()``` method: +```php + protected function setupPublishDefaults() + { + $this->crud->allowAccess('publish'); + + $this->crud->operation('list', function () { + $this->crud->addButton('line', 'publish', 'view', 'buttons.publish', 'beginning'); + }); + } +``` + +Take a look at the examples below for a better picture and code examples. + +If you intend to reuse this operation across multiple controllers, you can group all the methods above in a trait, say ```PublishOperation.php``` and then just use that trait on the controllers where you need the operation: + +```php + $routeName.'.publish', + 'uses' => $controller.'@publish', + 'operation' => 'publish', + ]); + } + + protected function setupPublishDefaults() + { + $this->crud->allowAccess('publish'); + + $this->crud->operation('list', function () { + $this->crud->addButton('line', 'publish', 'view', 'buttons.publish', 'beginning'); + }); + } + + public function publish() + { + // do something + // return something + } +} +``` + +In the example above, you could just do ```use \App\Http\Controllers\Admin\Operations\PublishOperation;``` on any EntityCrudController, and your operation will be added - complete with routes, buttons, access, actions, everything. + + +### Access to Custom Operations + +Since you're creating a new operation, in terms of restricting access you can: +1. allow access to this new operation depending on access to a default operation (usually if the admin can ```update```, he's OK to perform custom operations); +2. customize access to this particular operation, by just using a different key than the default ones; for example, you can allow access by using ```$this->crud->allowAccess('publish')``` in your ```setup()``` method, then check for access to that operation using ```$this->crud->hasAccess('publish')```; + + +### Adding Settings to the CrudPanel object + +#### Using the Settings API + +Anything an operation does to configure itself, or process information, should be stored inside ```$this->crud->settings``` . It's an associative array, and you can add/change things using the Settings API: + +```php +// for the operation that is currently being performed +$this->crud->setOperationSetting('show_title', true); +$this->crud->getOperationSetting('show_title'); +$this->crud->hasOperationSetting('show_title'); + +// for a particular operation, pass the operation name as a last parameter +$this->crud->setOperationSetting('show_title', true, 'create'); +$this->crud->getOperationSetting('show_title', 'create'); +$this->crud->hasOperationSetting('show_title', 'create'); + +// alternatively, you could use the direct methods with no fallback to the current operation +$this->crud->set('create.show_title', false); +$this->crud->get('create.show_title'); +$this->crud->has('create.show_title'); +``` + +#### Using the crud config file + +Additionally, operations can load default settings from the config file. You'll notice the ```config/backpack/crud.php``` file contains an array of operations, each with various settings. Those settings there are loaded by the operation as defaults, to allow users to change one setting in the config, and have that default changed across ALL of their CRUDs. If you take a look at the List operation you'll notice this: + +```php + /** + * Add the default settings, buttons, etc that this operation needs. + */ + protected function setupListDefaults() + { + $this->crud->allowAccess('list'); + + $this->crud->operation('list', function () { + $this->crud->loadDefaultOperationSettingsFromConfig(); + }); + } +``` + +You can do the same in custom operations. Because this call happens in setupListDefaults(), inside an operation closure, the settings will only be added when that operation is being performed. + + + +### Adding Methods to the CrudPanel Object + +You can add static methods to this ```$this->crud``` (which is a CrudPanel object) object with ```$this->crud->macro()```, because the object is [macroable](https://unnikked.ga/understanding-the-laravel-macroable-trait-dab051f09172). So you can do: + +```php +class MonsterCrudController extends CrudController +{ + public function setup() + { + $this->crud->macro('doStuff', function($something) { + echo '
'; var_dump($something); echo '
'; + dd($this); + }); + $this->crud->macro('getColumnsInTheFormatIWant', function() { + $columns = $this->columns(); + // ... do something to $columns; + return $columns; + }); + + // bla-bla-bla the actual setup code + } + public function sendEmail() + { + // ... + $this->crud->doStuff(); + dd($this->crud->getColumnsInTheFormatIWant()); + // ... + } + public function markPending() + { + // ... + $this->crud->doStuff(); + dd($this->crud->getColumnsInTheFormatIWant()); + // ... + } +} +``` + +So if you define a custom operation that needs some static methods added to the ```CrudPanel``` object, you can add them. The best place to register your macros in a custom operation would probably be inside your ```setupXxxDefaults()``` method, inside an operation closure. That way, the static methods you add are only added when that operation is being performed. For example: + +```php +protected function setupPrintDefaults() +{ + $this->crud->allowAccess('print'); + + $this->crud->operation('print', function() { + $this->crud->macro('getColumnsInTheFormatIWant', function() { + $columns = $this->columns(); + // ... do something to $columns; + return $columns; + }); + }); +} +``` + +With the example above, you'll be able to use ```$this->crud->getColumnsInTheFormatIWant()``` inside your operation actions. + + +### Using a feature from another operation + +Anything an operation does to configure itself, or process information, is stored on the ```$this->crud->settings``` property. Operation features (ex: fields, columns, buttons, filters, etc) are created in such a way that all they do is add an entry in settings, for an operation, and manipulate it. That means there is nothing stopping you from using a feature from one operation in a different operation. + +If you create a Print operation, and want to use the ```columns``` feature that List and Show use, you can just go ahead and do ```$this->crud->addColumn()``` calls inside your operation. You'll notice the columns are stored inside ```$this->crud->settings['print.columns']```, so they're completely different from the ones in the List or Show operation. You'll need to actually do something with the columns you added, inside your operation methods or views - of course. + + + +### Examples + + +#### Creating a New Operation With No Interface + +Let's say we have a ```UserCrudController``` and we want to create a simple ```Clone``` operation, which would create another entry with the same info. So very similar to ```Delete```. What we need to do is: + +1. Create a route for this operation - as we've learned above we can do that in a ```setupXxxRoutes()``` method: + +```php + protected function setupCloneRoutes($segment, $routeName, $controller) + { + Route::post($segment.'/{id}/clone', [ + 'as' => $routeName.'.clone', + 'uses' => $controller.'@clone', + 'operation' => 'clone', + ]); + } +``` + +2. Add the method inside ```UserCrudController```: + +```php +public function clone($id) +{ + $this->crud->hasAccessOrFail('create'); + $this->crud->setOperation('Clone'); + + $clonedEntry = $this->crud->model->findOrFail($id)->replicate(); + + return (string) $clonedEntry->push(); +} +``` + +3. Create a button for this method. Since our operation is similar to "Delete", lets start from that one and customize what we need. The button should clone the entry using an AJAX call. No need to load another page for an operation this simple. We'll create a ```resources\views\vendor\backpack\crud\buttons\clone.blade.php``` file: + +```php +@if ($crud->hasAccess('create')) + Clone +@endif + +{{-- Button Javascript --}} +{{-- - used right away in AJAX operations (ex: List) --}} +{{-- - pushed to the end of the page, after jQuery is loaded, for non-AJAX operations (ex: Show) --}} +@push('after_scripts') @if (request()->ajax()) @endpush @endif + +@if (!request()->ajax()) @endpush @endif +``` + +4. We can now actually add this button to our ```UserCrudController::setupCloneOperation()``` method, or our ```setupCloneDefaults()``` method: + +```php +protected function setupCloneDefaults() { + $this->crud->allowAccess('clone'); + + $this->crud->operation(['list', 'show'], function () { + $this->crud->addButtonFromView('line', 'clone', 'clone', 'beginning'); + }); +} +``` + +>Of course, **if you plan to re-use this operation on another EntityCrudController**, it's a good idea to isolate the method inside a trait, then use that trait on each EntityCrudController where you want the operation to work. + + +#### Creating a New Operation With An Interface + +Let's say we have a ```UserCrudController``` and we want to create a simple ```Moderate``` operation, where we show a form where the admin can add his observations and what not. In this respect, it should be similar to ```Update``` - the button should lead to a separate form, then that form will probably have a Save button. So when creating the methods, we should look at ```CrudController::edit()``` and ```CrudController::updateCrud()``` for working examples. + +What we need to do is: + +1. Create routes for this operation - we can do that using the ```setupOperationNameRoutes()``` convention inside a ```UserCrudController```: + +```php + protected function setupModerateRoutes($segment, $routeName, $controller) + { + Route::get($segment.'/{id}/moderate', [ + 'as' => $routeName.'.getModerate', + 'uses' => $controller.'@getModerateForm', + 'operation' => 'moderate', + ]); + Route::post($segment.'/{id}/moderate', [ + 'as' => $routeName.'.postModerate', + 'uses' => $controller.'@postModerateForm', + 'operation' => 'moderate', + ]); + } +``` + +2. Add the methods inside ```UserCrudController```: + +```php +public function getModerateForm($id) +{ + $this->crud->hasAccessOrFail('update'); + $this->crud->setOperation('Moderate'); + + // get the info for that entry + $this->data['entry'] = $this->crud->getEntry($id); + $this->data['crud'] = $this->crud; + $this->data['title'] = 'Moderate '.$this->crud->entity_name; + + return view('vendor.backpack.crud.moderate', $this->data); +} + +public function postModerateForm(Request $request = null) +{ + $this->crud->hasAccessOrFail('update'); + + // TODO: do whatever logic you need here + // ... + // You can use + // - $this->crud + // - $this->crud->getEntry($id) + // - $request + // ... + + // show a success message + \Alert::success('Moderation saved for this entry.')->flash(); + + return \Redirect::to($this->crud->route); +} +``` + +3. Create the ```/resources/views/vendor/backpack/crud/moderate.php``` blade file, which shows the moderate form and what not. Best to start from the ```edit.blade.php``` file and customize: + +```html +@extends(backpack_view('layouts.top_left')) + +@php + $defaultBreadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('dashboard'), + $crud->entity_name_plural => url($crud->route), + 'Moderate' => false, + ]; + + // if breadcrumbs aren't defined in the CrudController, use the default breadcrumbs + $breadcrumbs = $breadcrumbs ?? $defaultBreadcrumbs; +@endphp + +@section('header') +
+

+ {!! $crud->getHeading() ?? $crud->entity_name_plural !!} + {!! $crud->getSubheading() ?? 'Moderate '.$crud->entity_name !!}. + + @if ($crud->hasAccess('list')) + {{ trans('backpack::crud.back_to_all') }} {{ $crud->entity_name_plural }} + @endif +

+
+@endsection + +@section('content') +
+
+
+
+

Moderate

+
+
+ Something in the card body +
+ + +
+ +
+
+@endsection + +``` + +4. Create a button for this operation. Since our operation is similar to "Update", lets start from that one and customize what we need. The button should just take the admin to the route that shows the Moderate form. Nothing fancy. We'll create a ```resources\views\vendor\backpack\crud\buttons\moderate.blade.php``` file: + +```php +@if ($crud->hasAccess('moderate')) + Moderate +@endif +``` + +4. We can now actually add this button to our ```UserCrudController::setup()```, to register that button inside the List operation: + +```php +$this->crud->operation('list', function() { + $this->crud->addButtonFromView('line', 'moderate', 'moderate', 'beginning'); +}); +``` + +Or better yet, we can do this inside a ```setupModerateDefaults()``` method, which gets called automatically by CrudController when the ```moderate``` operation is being performed (thanks to the operation name set on the routes): + +```php +protected function setupModerateDefaults() +{ + $this->crud->allowAccess('moderate'); + + $this->crud->operation('list', function() { + $this->crud->addButtonFromView('line', 'moderate', 'moderate', 'beginning'); + }); +} +``` + +>Of course, **if you plan to re-use this operation on another EntityCrudController**, it's a good idea to isolate the method inside a trait, then use that trait on each EntityCrudController where you want the operation to be enabled. + +```php + $routeName.'.getModerate', + 'uses' => $controller.'@getModerateForm', + 'operation' => 'moderate', + ]); + Route::post($segment.'/{id}/moderate', [ + 'as' => $routeName.'.postModerate', + 'uses' => $controller.'@postModerateForm', + 'operation' => 'moderate', + ]); + } + + protected function setupmoderateDefaults() + { + $this->crud->allowAccess('moderate'); + + $this->crud->operation('list', function() { + $this->crud->addButtonFromView('line', 'moderate', 'moderate', 'beginning'); + }); + } + + public function getModerateForm($id) + { + $this->crud->hasAccessOrFail('update'); + $this->crud->setOperation('Moderate'); + + // get the info for that entry + $this->data['entry'] = $this->crud->getEntry($id); + $this->data['crud'] = $this->crud; + $this->data['title'] = 'Moderate '.$this->crud->entity_name; + + return view('vendor.backpack.crud.moderate', $this->data); + } + + public function postModerateForm(Request $request = null) + { + $this->crud->hasAccessOrFail('update'); + + // TODO: do whatever logic you need here + // ... + // You can use + // - $this->crud + // - $this->crud->getEntry($id) + // - $request + // ... + + // show a success message + \Alert::success('Moderation saved for this entry.')->flash(); + + return \Redirect::to($this->crud->route); + } +} +``` + + +#### Creating a New Operation With a Bulk Action (No Interface) + +Say we want to create a ```BulkClone``` operation, with a button which clones multiple entries at the same time. So very similar to our ```BulkDelete```. What we need to do is: + +1. Create a new button: + +```html +@if ($crud->hasAccess('bulkClone') && $crud->get('list.bulkActions')) + Clone +@endif + +@push('after_scripts') + +@endpush +``` + +2. Create a method in your EntityCrudController (or in a trait, if you want to re-use it for multiple CRUDs): + +```php + public function bulkClone() + { + $this->crud->hasAccessOrFail('create'); + + $entries = $this->crud->getRequest()->input('entries'); + $clonedEntries = []; + + foreach ($entries as $key => $id) { + if ($entry = $this->crud->model->find($id)) { + $clonedEntries[] = $entry->replicate()->push(); + } + } + + return $clonedEntries; + } +``` + +3. Add a route to point to this new method: + +```php +protected function setupBulkCloneRoutes($segment, $routeName, $controller) +{ + Route::post($segment.'/bulk-clone', [ + 'as' => $routeName.'.bulkClone', + 'uses' => $controller.'@bulkClone', + 'operation' => 'bulkClone', + ]); +} +``` + +4. Setup the default features we need for the operation to work: + +```php +protected function setupBulkCloneDefaults() +{ + $this->crud->allowAccess('bulkClone'); + + $this->crud->operation('list', function () { + $this->crud->enableBulkActions(); + $this->crud->addButton('bottom', 'bulk_clone', 'view', 'bulk_clone', 'beginning'); + }); +} +``` + + +Now there's a Clone button on our List bottom stack, that works as expected for multiple entries. + +The button makes one call for all entries, and only triggers one notification. If you would rather make a call for each entry, you can use something like below: + +```html +@if ($crud->hasAccess('create') && $crud->bulk_actions) + Clone +@endif + +@push('after_scripts') + +@endpush +``` + + + +#### Creating a New Operation With a Form + +Say we want to create a ```Comment``` operation. Click the Comment button on an entry, and it brings up a form with a textarea. Submit the form and you're back to the list view. Let's get started. What we need to do is: + +**Step 0.** Install ```backpack/generators``` if you haven't yet. [https://github.com/Laravel-Backpack/Generators](https://github.com/Laravel-Backpack/Generators). We have built a set of commands to help you create a new form operation easy peasy. You can use it like this: + +```bash +php artisan backpack:crud-operation Comment # will create a form for the entries in your list view, with the id in the URL + +php artisan backpack:crud-operation Comment --no-id # will create a form, without the id in the URL (generators v4.0.4+) +``` + + +**Step 1.** Back to our goal, lets generate the operation trait, by running `php artisan backpack:crud-form-operation Comment`. This will create a new trait, `CommentOperation` that should look very similar to this: + +```php +formRoutes( + operationName: 'comment', + routesHaveIdSegment: true, + segment: $segment, + routeName: $routeName, + controller: $controller + ); + } + + /** + * Add the default settings, buttons, etc that this operation needs. + */ + protected function setupCommentDefaults(): void + { + $this->formDefaults( + operationName: 'comment', + buttonStack: 'line', // alternatives: top, bottom + // buttonMeta: [ + // 'icon' => 'la la-home', + // 'label' => 'Comment', + // 'wrapper' => [ + // 'target' => '_blank', + // ], + // ], + ); + } + + /** + * Method to handle the GET request and display the View with a Backpack form + * + * @param int $id + * @return \Illuminate\Contracts\View\View + */ + public function getCommentForm(int $id) + { + $this->crud->hasAccessOrFail('comment'); + + return $this->formView($id); + } + + /** + * Method to handle the POST request and perform the operation + * + * @param int $id + * @return array|\Illuminate\Http\RedirectResponse + */ + public function postCommentForm(int $id) + { + $this->crud->hasAccessOrFail('comment'); + + return $this->formAction(id: $id, formLogic: function ($inputs, $entry) { + // You logic goes here... + // dd('got to ' . __METHOD__, $inputs, $entry); + + // show a success message + \Alert::success('Something was done!')->flash(); + }); + } +} + +``` +> Notes : Please Keep in mind, when you set the **buttonStack** to *top* or *bottom*, don't forget to set the **routesHaveIdSegment** to *false*, otherwise it won't show your form. + +**Step 2.** Now let's use this operation trait on our CrudController. For example in our UserCrudController we'd have to do this next to all other operations: +```php + use \App\Http\Controllers\Admin\Operations\CommentOperation; +``` + +**Step 3.** Now let's add the fields. We have a decision to make... who adds the fields? Does it make more sense for: +- **(a)** the developer to add the fields, because they vary from CrudController to CrudController; +- **(b)** the operation itself to add the fields, because the fields never change when you add the operation to multiple CrudControllers; + +If **(a)** made more sense, we'd just create a new function in our CrudController, called `setupCommentOperation()`, and define the fields there. + +In case **(b)** makes more sense, we will define the fields at the operation itself in `setupCommentDefaults()`. + +```php +// a) whenever we use this operation, we want to always setup the same fields + +// inside `ComentOperation.php` +public function setupCommentDefaults(): void +{ + // ... + + $this->crud->operation('comment', function () { + $this->crud->field('message')->type('textarea'); + }); +} + +// b) when the operation can accept different fields for each crud controller, eg: UserCrudController may have some fields, while in PostCrudController we may have others + +// inside `UserCrudController.php` +public function setupCommentOperation(): void +{ + $this->crud->field('message')->type('textarea'); +} + +// inside `PostCrudController.php` +public function setupCommentOperation(): void +{ + $this->crud->field('message')->type('textarea'); + $this->crud->field('rating')->type('number'); + + // if you want to add a FormRequest to validate the fields you do it here. + // later when you handle the form submission, the request will be automatically validated + $this->crud->setValidation(CommentRequest::class); // this file is not automatically created. You have to create it yourself. +} + +``` + +**Step 4.** Let's actually add the comment to the database. Inside the `CommentOperation` trait, if we go to `postCommentForm()` well see we have a placeholder for our logic there: + +```php + public function postCommentForm(int $id) + { + $this->crud->hasAccessOrFail('comment'); + + return $this->formAction(id: $id, formLogic: function ($inputs, $entry) { + // You logic goes here... + + // You can validate the inputs using the Laravel Validator, eg: + // $valid = Validator::make($inputs, ['message' => 'required'])->validated(); + + // alternatively if you set a FormRequest in the setupCommentOperation() method, + // the request will be validated here already + + // and then save it to database + // $entry->comments()->create($valid); + + // show a success message + \Alert::success('Something was done!')->flash(); + }); + } +``` + +That's it. This all you needed to do to achieve a working operation with a Backpack form, that looks a lot like this: + + +![Custom form operation in Backpack v6](https://github.com/Laravel-Backpack/CRUD/assets/1032474/0bc88660-70d5-41c8-a298-b8a971fd9539) diff --git a/7.x-dev/crud-save-actions.md b/7.x-dev/crud-save-actions.md new file mode 100644 index 00000000..4fb61c85 --- /dev/null +++ b/7.x-dev/crud-save-actions.md @@ -0,0 +1,141 @@ +# Save Actions + +--- + + +## About + +`Create` and `Update` forms end in a Save button with a drop menu. Every option in that dropdown is a SaveAction - they determine where the user is redirected after the saving is complete. + + +## Default Save Actions + +There are four save actions registered by Backpack by default. They are: + - ```save_and_back``` (Save your entity and go back to previous URL) + - ```save_and_edit``` (Save and edit the current entry) + - ```save_and_new``` (Save and go to create new entity page) + - ```save_and_preview``` (Save and go to show the current entity) + + +## Save Actions API + +Inside your CrudController, inside your ```setupCreateOperation()``` or ```setupUpdateOperation()``` methods, you can change what save buttons are shown for each operation by using the methods below: + +#### addSaveAction(array $saveAction) + +Adds a new SaveAction to the "Save" button/dropdown. + +```php +CRUD::addSaveAction([ + 'name' => 'save_action_one', + 'redirect' => function($crud, $request, $itemId) { + return $crud->route; + }, // what's the redirect URL, where the user will be taken after saving? + + // OPTIONAL: + 'button_text' => 'Custom save message', // override text appearing on the button + // You can also provide translatable texts, for example: + // 'button_text' => trans('backpack::crud.save_action_one'), + 'visible' => function($crud) { + return true; + }, // customize when this save action is visible for the current operation + 'referrer_url' => function($crud, $request, $itemId) { + return $crud->route; + }, // override http_referrer_url + 'order' => 1, // change the order save actions are in +]); +``` + +#### addSaveActions(array $saveActions) + +The same principle of `addSaveAction([])` but for adding multiple actions with only one crud call. + +```php +CRUD::addSaveActions([ + [ + 'name' => 'save_action_one', + 'visible' => function($crud) { + return true; + }, + 'redirect' => function($crud, $request, $itemId) { + return $crud->route; + }, + ], + [ + 'name' => 'save_action_two', + 'visible' => function($crud) { + return true; + }, + 'redirect' => function($crud, $request, $itemId) { + return $crud->route; + }, + ], +]); +``` + +#### replaceSaveActions(array $saveActions) + +This allows you to replace the current save actions with the ones provided in an array. + +```php +CRUD::replaceSaveActions( + [ + 'name' => 'save_action_one', + 'visible' => function($crud) { + return true; + }, + 'redirect' => function($crud, $request, $itemId) { + return $crud->route; + }, + ], +); +``` + + +#### removeSaveAction(string $saveAction) + +This allows you to remove a specific save action from the save actions array. Provide the name of the save action that you would like to remove. +```php +CRUD::removeSaveAction('save_action_one'); +``` + +#### removeSaveActions(array $saveActions) + +The same principle as `removeSaveAction()` but to remove multiple actions at same time. You should provide an array with save action names. +```php +CRUD::removeSaveActions(['save_action_one','save_action_two']); +``` + +#### orderSaveAction(string $saveAction, int $wantedOrder) + +You can specify a certain order for a certain save action. + +```php +CRUD::orderSaveAction('save_action_one', 1); +``` + +We will setup the save action in the desired order and try to re-order the other save actions accordingly. If you want more granular control over all save actions order, you can define ```order``` when creating the save action, or use ```orderSaveActions()``` + +#### orderSaveActions(array $saveActions) + +Allows you to reorder multiple save actions at same time. You can use it by either specifying only the names of the save actions, in the order you want, or by specifying their order number too: + +```php +// make save actions show up in this order +CRUD::orderSaveActions(['save_action_one','save_action_two']); +// or +CRUD::orderSaveActions(['save_action_one' => 3,'save_action_two' => 2]); +``` + +#### setSaveActions(array $saveActions) + +Alias for ```replaceSaveActions(array $saveActions)```. + +## Action change notification + +By default, a change of the save action is shown to the user with a notification. If you want to disable the notification +you can configure this in the setup of you CRUD controller: + +```php +CRUD::setOperationSetting('showSaveActionChange', false); +``` diff --git a/7.x-dev/crud-tutorial.md b/7.x-dev/crud-tutorial.md new file mode 100644 index 00000000..dca4eccd --- /dev/null +++ b/7.x-dev/crud-tutorial.md @@ -0,0 +1,356 @@ +# CRUD Crash Course + +--- + +What's the simplest entity you can think of? It will probably be something like ```Tag```, which only holds an ```id``` and a ```name```. Let's create this new entity in the database, the model for it, then create a CRUD Panel to let admins manage entries for this entity. + +We assume: +- you've [installed Backpack](/docs/{{version}}/installation); +- you don't already have a ```Tag``` model in your project; + + +## Generate Files + +In order to build a CRUD, we need an Eloquent model. So let's create a migration and model, using [Jeffrey Way's Generators](https://github.com/laracasts/Laravel-5-Generators-Extended): + +```zsh +# install a 3rd party tool to generate migrations from the command line +composer require --dev laracasts/generators + +# generate a migration and run it +php artisan make:migration:schema create_tags_table --schema="name:string:unique" +php artisan migrate +``` + +> **Note:** If you have a lot of database tables to generate, we heavily recommend **our paid addon - [Backpack DevTools](https://backpackforlaravel.com/products/devtools).** It's a GUI that helps you generate Migrations, Models (complete with relationships) and CRUDs from the browser. It does cost extra, but it's well worth the price if you use Backpack regularly or your models are not dead-simple. + +Now that we have the ```tags``` table in the database, let's generate the actual files we'll be using: + +```zsh +php artisan backpack:crud tag #use singular, not plural +``` + +The code above will have generated: +- a migration (```database/migrations/yyyy_mm_dd_xyz_create_tags_table.php```); +- a database table (```tags``` with just two columns: ```id``` and ```name```); +- a model (```app/Models/Tag.php```); +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); +- a resource route, as a line inside ```routes/backpack/custom.php```; +- a new menu item in ```resources/views/vendor/backpack/ui/inc/menu_items.blade.php```; + +**Next up:** we'll need to go through the generated files, and customize for our needs. + + +## Customize Generated Files + +We'll skip the migration and database table, since there's nothing there specific to Backpack, nothing to customize, and we've already run the migration. + + +### The Model + +Let's take a look at the generated model: + +```php + +### The Controller + +Let's take a look at ```app/Http/Controllers/Admin/TagCrudController.php```. It should look something like this: + +```php +setupCreateOperation(); + } +} + +``` + +What we should notice inside this TagCrudController is that: +- ```TagCrudController extends CrudController```; +- ```TagCrudController``` has a ```setup()``` method, where we must define the basics of our CRUD panel; everything we write here is applied on ALL operations; +- All operations are enabled by using that operation's trait on the controller; +- Each operation is set up inside a ```setupXxxOperation()``` method; + +#### Operation Setup Methods + +The best way to configure operations is to define each operation inside its ```setupXxxOperation()``` method, like the generated file above does. Over there the `setupXxxOperation()` methods: +- add a simple ```text``` column for our ```name``` attribute for the List operation (the table view); +- add a simple ```text``` field for our ```name``` attribute to the Create and Update forms; + +#### Operation Setup Closures + +An alternative to defining operations inside ```setupXxxOperation()``` methods is to do everything inside the ```setup()``` method. However, as we've mentioned before, everything you run in your ```setup()``` method is run for ALL operations. So you can easily end up bloating your operation with unnecessary operations. If you don't like having a method to configure each operation, and want to define everything in ```setup()```, you should do so inside an ```operation()``` closure. Whatever's inside that closure will only be run for that operation. For example, everything we've done above would look like this if done inside operation closures: + +```php + public function setup() + { + CRUD::setModel('App\Models\Tag'); + CRUD::setRoute(config('backpack.base.route_prefix') . '/tag'); + CRUD::setEntityNameStrings('tag', 'tags'); + + CRUD::operation('list', function() { + CRUD::column('name'); + }); + + CRUD::operation(['create', 'update'], function() { + CRUD::addValidation(TagCrudRequest::class); + CRUD::field('name'); + }); + } + + +} +``` + +#### Other Calls + +Here, inside your ```setup()``` or ```setupXxxOperation``` methods, you can also do a lot of other things, like adding buttons, adding filters, customizing your query, etc. For a full list of the things you can do inside ```setup()``` check out our [cheat sheet](/docs/{{version}}/crud-cheat-sheet). + +Next, let's continue to another generated file. + + +### The Request + +Backpack can also generate a [standard FormRequest file](https://laravel.com/docs/master/validation#form-request-validation), that you can use for validation of the Create and Update forms. There is nothing Backpack-specific in here, but let's take a look at the generated ```app/Http/Requests/TagRequest.php``` file: + +```php +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // 'name' => 'required|min:5|max:255' + ]; + } + + /** + * Get the validation attributes that apply to the request. + * + * @return array + */ + public function attributes() + { + return [ + // + ]; + } + + /** + * Get the validation messages that apply to the request. + * + * @return array + */ + public function messages() + { + return [ + // + ]; + } +} + +``` + +This file is a 100% pure FormRequest file - all Laravel, nothing particular to Backpack. In generated FormRequest files, no validation rules are imposed by default - unless you've generated requests using [Backpack DevTools](https://backpackforlaravel.com/products/devtools), which does its best to populate the validation rules from the database schema - just saying, it will save you time here too 😉. But we do want ```name``` to be ```required``` and ```unique```, so let's do that, using the [standard Laravel validation rules](https://laravel.com/docs/master/validation#available-validation-rules): + +```diff + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ +- // 'name' => 'required|min:5|max:255' ++ 'name' => 'required|min:5|max:255|unique:tags,name' + ]; + } +``` + +> If your validation needs to be different between the Create and Update operations, [you can easily do that too](/docs/{{version}}/crud-operation-create#separate-requests-for-create-and-update), by specifying different FormRequest files for each operation. + + +### The Route + +We have already generated our CRUD route, and we don't need to do anything about it, but let's check our ```routes/backpack/custom.php```. It should look like this: + +```php + config('backpack.base.route_prefix', 'admin'), + 'middleware' => ['web', config('backpack.base.middleware_key', 'admin')], + 'namespace' => 'App\Http\Controllers\Admin', +], function () { // custom admin routes + // CRUD resources and other admin routes + Route::crud('tag', 'TagCrudController'); +}); // this should be the absolute last line of this file +``` + +Here, we can see that our routes have been placed: +- under a prefix that we can change in ```config/backpack/base.php```; +- under a middleware we can change in ```config/backpack/base.php```; +- inside the ```App\Http\Controllers\Admin``` namespace, because that's where our custom CrudControllers will be generated; + +**It's generally a good idea to have the all admin routes in this separate file.** If you edit this file in the future, make sure you leave the last line intact, so that other routes can be automatically generated inside this file. And of course, add your routes inside this route group, so that: +- you have a single prefix for your admin routes (ex: ```admin/tag```, ```admin/product```, ```admin/dashboard```); +- all your admin panel functionality is protected by the same middleware; +- all your admin panel controllers live in one place (```App\Http\Controllers\Admin```); + + +### The Menu Item + +We've previously generated a menu item in the ```resources/views/vendor/backpack/ui/inc/menu_items.blade.php``` file. That is using our menu item Blade components. The "active" state of the menu is done with JavaScript, based on the ```href``` attribute. + +This is the bit that has been generated for you: + +```php + +``` + +You can of course change anything here, if you want. For example, change `la la-question` to `la la-tag`. + + +## The result + +You are now ready to go to ```your-app-name.domain/admin/tag``` and see your fully functional admin panel for ```Tags```. + +**Congratulations, you should now have a good understanding of how Backpack\CRUD works!** This is a very very basic example, but the process will be identical for Models with 50+ attributes, complicated logic, etc. + +If you do have complex models, we _heavily_ recommend you go purchase [Backpack DevTools](https://backpackforlaravel.com/products/devtools) right now. It's the official paid GUI for generating all of the above. And while you're at it, you can [purchase a Backpack license](https://backpackforlaravel.com/pricing) too 😉 diff --git a/7.x-dev/crud-uploaders.md b/7.x-dev/crud-uploaders.md new file mode 100644 index 00000000..2b49ef32 --- /dev/null +++ b/7.x-dev/crud-uploaders.md @@ -0,0 +1,212 @@ +# Uploaders + +--- + + +## About + +Uploading and managing files is a common task in Admin Panels. Starting with Backpack v6, you can fully setup your upload fields in your field definition, using purpose-built classes we call Uploaders. No more need to create mutators, manual validation of input or custom code to handle the files - though you can still do that, if you want. + + +## How it works + +When adding an upload field (`upload`, `upload_multiple`, `image` or `dropzone`) to your operation, tell Backpack that you want to use the appropriate Uploader, by using `withFiles()`: + +```php +CRUD::field('avatar')->type('upload')->withFiles(); +``` + +That's it. Backpack will now handle the upload, storage and deletion of the files for you. By default it will use `public` disk, and will delete the files when the entry is deleted(*). + +> **IMPORTANT**: +> - Make sure you've linked the `storage` folder to your `public` folder. You can do that by running `php artisan storage:link` in your terminal. +> - (*) If you want your files to be deleted when the entry is deleted, please [Configure File Deletion](#deleting-files-when-entry-is-deleted) + + + +## Configuring the Uploaders + +The `withFiles()` method accepts an array of options that you can use to customize the upload. + +```php +CRUD::field('avatar') + ->type('upload') + ->withFiles([ + 'disk' => 'public', // the disk where file will be stored + 'path' => 'uploads', // the path inside the disk where file will be stored +]); +``` +**Note**: If you've defined `disk` or `prefix` on the field, you no longer need to define `disk` or `path` within `withFiles()` - it will pick those up. Make sure you are not defining both. + + +**Configuration options:** + +- **`disk`** - default: **`public`** +The disk where the file will be stored. You can use any disk defined in your `config/filesystems.php` file. +- **`path`** - default: **`/`** +The path inside the disk where the file will be stored. It maps to `prefix` in field definition. +- **`deleteWhenEntryIsDeleted`** - default: **`true`** (**NEED ADDITIONAL CONFIGURATION**!! See: [Configure File Deletion](#deleting-files-when-entry-is-deleted)) +The files will be deleted when the entry is deleted. Please take into consideration that `soft deleted models` don't delete the files. +- **`temporaryUrl`** - default: **`false`** +Some cloud disks like `s3` support the usage of temporary urls for display. Set this option to true if you want to use them. +- **`temporaryUrlExpirationTime`** - default: **`1`** +When `temporaryUrl` is set to `true`, this configures the amount of time in minutes the temporary url will be valid for. +- **`uploader`** - default: **null** +This allows you to overwrite or set the uploader class for this field. You can use any class that implements `UploaderInterface`. +- **`fileNamer`** - default: **null** +It accepts a `FileNameGeneratorInterface` instance or a closure. As the name implies, this will be used to generate the file name. Read more about in the [Naming uploaded files](#upload-name-files) section. + + +### Handling uploads in relationship fields + +**IMPORTANT**: Please make sure you are **NOT** casting the uploaders attributes in your model. If you need a casted attribute to work with the values somewhere else, please create a different attribute that copies the uploader attribute value and manually cast it how you need it. + +Some relationships require additional configuration to properly work with the Uploaders, here are some examples: + +- **`BelongsToMany`** + +In this relationships, you should add the upload fields to the `withPivot()` method and create a Pivot model where Uploaders register their events. [Laravel Docs - Pivot Models](https://laravel.com/docs/10.x/eloquent-relationships#defining-custom-intermediate-table-models) + +Take for example an `Article` model has a `BelongsToMany` relationship defined with `Categories` model: + +```php +// Article model +public function categories() { + $this->belongsToMany(Category::class); +} +``` + +To use an Uploader in this relation, you should create the `ArticleCategory` pivot model, and tell Laravel to use it. + +```php +use Illuminate\Database\Eloquent\Relations\Pivot; + +class ArticleCategory extends Pivot +{ + +} + + +// and in your article/category models, update the relationship to: +public function categories() { + $this->belongsToMany(Category::class)->withPivot('picture')->using(ArticleCategory::class); //assuming picture is the pivot field where you store the uploaded file path. +} +``` + +- **`MorphToMany`** + +Everything like the previous `belongsToMany`, but the pivot model needs to extend `MorphPivot`. + +```php +use Illuminate\Database\Eloquent\Relations\MorphPivot; + +class ArticleCategory extends MorphPivot +{ + +} + + +//in your model +public function categories() { + $this->morphToMany(Category::class)->withPivot('picture')->using(ArticleCategory::class); //assuming picture is the pivot field where you store the uploaded file path. +} +``` + + +### Naming files when using Uploaders + +Backpack provides a naming strategy for uploaded files that works well for most scenarios: +- For `upload`, `upload_multiple` and `dropzone` fields, the file name will be the original file name slugged and with a random 4 character string appended to it, to avoid name collisions. Eg: `my file.pdf` becomes `my-file-aY5x.pdf`. +- For `image` it will generate a unique name for the file, and will keep the original extension. Eg: `my file.jpg` becomes `5f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c.jpg`. + +You can customize the naming strategy by creating a class that implements `FileNameGeneratorInterface` and pass it to the upload configuration (the default used by Backpack). + +```php +CRUD::field('avatar')->type('upload')->withFiles([ + 'fileNamer' => \Backpack\CRUD\app\Library\Uploaders\Support\FileNameGenerator::class, +]); + +// alternativelly you can pass a closure: +->withFiles([ + 'fileNamer' => function($file, $uploader) { return 'the_file_name.png'; }, +]) +``` + +### Subfields in Uploaders + +You can also use uploaders in subfields. The configuration is the same as for regular fields, just use the same `withFiles` key and pass it `true` if no further configuration is required. + +```php +// subfields array +[ + [ + 'name' => 'avatar', + 'type' => 'upload', + 'withFiles' => true + ], + [ + 'name' => 'attachments', + 'type' => 'upload_multiple', + 'withFiles' => [ + 'path' => 'attachments', + ], + ], +] +``` + + +### Configure uploaded files to be automatically deteled + +To automatically delete the uploaded files when the entry is deleted _in the admin panel_, we need to setup the upload fields in the `DeleteOperation` too: + +```php +protected function setupDeleteOperation() +{ + CRUD::field('photo')->type('upload')->withFiles(); + + // Alternatively, if you are not doing much more than defining fields in your create operation: + // $this->setupCreateOperation(); +} +``` + +Alternatively, you can manually delete the file in your Model, using the `deleted` Eloquent model event. That would ensure the file gets deleted _even if_ the entry was deleted from outside the admin panel. + +```php +class SomeModel extends Model +{ + protected static function booted() + { + static::deleted(function ($model) { + // delete the file + Storage::disk('my_disk')->delete($model->photo); + }); + } +} +``` + + +### Configuring uploaders in custom fields + +When using uploads in custom fields, you need to tell Backpack what Uploader to use for that custom field type. + +Imagine that you created a custom upload field starting from backpack `upload` field type with: `php artisan backpack:field custom_upload --from=upload`. + +You can tell Backpack what Uploader to use in 2 ways: + +- In the custom field defininiton inside the uploader configuration: +```php +CRUD::field('custom_upload')->withFiles([ + 'uploader' => \Backpack\CRUD\app\Library\Uploaders\SingleFile::class, +]); +``` +- Or you can add it globally for that field type by adding in your Service Provider `boot()` method: +```php +app('UploadersRepository')->addUploaderClasses(['custom_upload' => \Backpack\CRUD\app\Library\Uploaders\SingleFile::class], 'withFiles'); +``` + + +### Uploaders for Spatie MediaLibrary + +The 3rd party package [`spatie/laravel-medialibrary`](https://spatie.be/docs/laravel-medialibrary/) gives you the power to easily associate files with Eloquent models. The package is incredibly popular, time-tested and well maintained. + +To have Backpack upload and retrieve files using this package, we've created special Uploaders. Then it will be as easy as doing `CRUD::field('avatar')->type('image')->withMedia();`. For more information and installation instructions please see the docs on Github for [`backpack/medialibrary-uploaders`](https://github.com/Laravel-Backpack/medialibrary-uploaders). diff --git a/7.x-dev/custom-validation-rules.md b/7.x-dev/custom-validation-rules.md new file mode 100644 index 00000000..86cfd6b9 --- /dev/null +++ b/7.x-dev/custom-validation-rules.md @@ -0,0 +1,64 @@ +# Custom Validation Rules + +--- + + +## About + +Some Backpack fields are more difficult to validate using standard Laravel validation rules. So we've created a few custom validation rules, that will make validation them dead-simple. + + +## `ValidUpload` for `upload` field type + +The `ValidUpload` rule is used to validate the `upload` field type. Using the custom rule helps developer avoid to setting different validation rules for different operations, (create/update). + +```php +// for the field +CRUD::field('avatar')->type('upload'); + +// you can write the validation rule as + +use Backpack\CRUD\app\Library\Validation\Rules\ValidUpload; + +'avatar' => ValidUpload::field('required')->file('mimes:jpg,png|max:2048'), +``` + +The `::field()` constructor accepts the rules for the field, while `->file()` accepts the specific rules for files sent in field. The validation rule handles the `sometimes` case for you. + + +## `ValidUploadMultiple` for `upload_multiple` field type + +You can use this validation rule to handle validation for your `upload_multiple` field - both for the Create and the Update operation in one go: +- use the `::field()` constructor to define the rules for the field; +- use the `->file()` method for rules specific to the files sent in field; + +```php +// for the field +CRUD::field('attachments')->type('upload_multiple'); + +// you can write the validation rule as + +use Backpack\CRUD\app\Library\Validation\Rules\ValidUploadMultiple; + +'attachments' => ValidUploadMultiple::field(['min:2', 'max:5'])->file('mimes:pdf|max:10000'), + +``` + + +## `ValidDropzone` for `dropzone` field type + +You can use this validation rule to handle validation for your `dropzone` field - both for the Create and the Update operation in one go: +- use the `::field()` constructor to define the rules for the field; +- use the `->file()` method for rules specific to the files sent in field; + +```php +// for the field +CRUD::field('photos')->type('dropzone'); + +// you can write the validation rule as + +use Backpack\Pro\Uploads\Validation\ValidDropzone; + +'attachments' => ValidDropzone::field('min:2|max:5')->file('file|mimes:jpg,png,gif|max:10000'), + +``` \ No newline at end of file diff --git a/7.x-dev/demo.md b/7.x-dev/demo.md new file mode 100644 index 00000000..72394941 --- /dev/null +++ b/7.x-dev/demo.md @@ -0,0 +1,84 @@ +# Demo + +--- + +We've put together a working Laravel app (backend-only). This should make it easier to: +- see how it looks & feels; +- see how it works; +- change stuff in code, to see how easy it is to customize Backpack; + +In this [Demo repository](https://github.com/laravel-backpack/demo), we've: +- installed Laravel 11; +- installed Backpack\CRUD; FREE +- installed Backpack\PRO; PRO +- installed Backpack\Editable-Columns; PREMIUM +- created a few demo models and admin panels for them, using dozens of field types, column types, filters, etc - to show off most of Backpack CRUD + PRO features; +- installed a few Backpack extensions: PermissionManager, PageManager, LogManager, BackupManager, Settings, MenuCRUD, NewsCRUD; + + +>**Don't use this demo to start your real projects.** Please use [the recommended installation procedure](/docs/{{version}}/installation). You don't want all the bogus entities we've created. You don't want all the packages we've used. And you _definitely_ don't want the default admin user. Start from scratch! + + +## Demo Preview + +![https://user-images.githubusercontent.com/1032474/86720524-c5a1d480-c02d-11ea-87ed-d03b0197eb25.gif](https://user-images.githubusercontent.com/1032474/86720524-c5a1d480-c02d-11ea-87ed-d03b0197eb25.gif) + +If you just want to take a look at the Backpack interface and click around, you don't have to install anything. **Take a look at [demo.backpackforlaravel.com](https://demo.backpackforlaravel.com/admin) - we've installed for you.** The online demo is wiped and reinstalled every hour, on the hour. + + +## Demo Installation + +If you _do_ want to install the Demo and play around, it's easy to do so. But because the demo uses [Backpack/PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) and [Backpack/Editable-Columns](https://backpackforlaravel.com/products/editable-columns), you need to [purchase "Everything" first](https://backpackforlaravel.com/pricing), or those addons individually. If you don't like it, we'll happily give you a refund. + +1) In your ```Projects``` or ```www``` directory, wherever you host your apps: + +```zsh +git clone https://github.com/Laravel-Backpack/demo.git backpack-demo +``` + +2) Set your database information in your ```.env``` file (use ```.env.example``` as an example); + +3) Authenticate and install the requirements: +``` zsh +cd backpack-demo + +# Tell Composer how to connet to the private Backpack repo. +# You'll need to replace these with your real token and password: +composer config http-basic.backpackforlaravel.com [your-token-username] [your-token-password] + +# Install all dependencies +composer install +``` + +4) Populate the database: +```zsh +php artisan key:generate +php artisan migrate +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +php artisan db:seed +``` + +5) Cache the CSS and JS assets using Basset: +```zsh +php artisan storage:link +php artisan basset:cache +``` + + +## Demo Usage + +Once everything's installed, and your database has been set up: + +- Your admin panel is available at `{APP_URL}`/admin +- Login with email ```admin@example.com```, password ```admin``` +- You can register a different account, to check out the process and see your Gravatar inside the admin panel. +- By default, registration is open only in your local environment. Check out ```config/backpack/base.php``` to change this and other preferences. +- Check out the Monsters admin panel - it features over 50 field types. +- Devs love Backpack not just for its standard functionality, but also for how easy it is to code your own, or customize every little bit of it. Our recommendation: + - Go through the [CRUD Tutorial](/docs/{{version}}/crud-tutorial) to understand it; + - Create a new CRUD panel for an entity, using the faster procedure outlined at the end of that page; say... ```car```; + + +>**vhost configurations** +> +>Depending on your vhost configuration you might need to access the application via a different url, for example if you're using ```artisan serve``` you can access it on http://127.0.0.1:8000/admin - if you're using Laravel Valet, then it may look like http://backpack-demo.test/admin - you will need to access the url which matches your systems configuration. If you do not understand how to configure your virtual hosts, we suggest [watching Laracasts Episode #1](https://laracasts.com/series/laravel-from-scratch/episodes/1) to quickly get started. diff --git a/7.x-dev/faq.md b/7.x-dev/faq.md new file mode 100644 index 00000000..2ae09bd4 --- /dev/null +++ b/7.x-dev/faq.md @@ -0,0 +1,112 @@ +# Frequently Asked Questions + +--- + + + +## Licensing + + +### Do I need a license to test Backpack? + +You don't need a license code AT ALL. Go ahead and install Backpack CRUD on your machine - it's free and open-source, released under the MIT License. + +You only need to pay if you want the extra features provided by our premium add-ons (e.g. [Backpack PRO](https://backpackforlaravel.com/pricing) and [Backpack DevTools](https://backpackforlaravel.com/products/devtools)). That's it. + + + +### Do I need a license to put a PRO project on a testing domain? + +No: +- when you purchase [Backpack PRO for Unlimited Projects](https://backpackforlaravel.com/products/pro-for-unlimited-projects), you can use it on any number of domains, subdomains and IPs (that's why it's called unlimited); +- when you purchase [Backpack PRO for One Project](https://backpackforlaravel.com/products/pro-for-one-project), you get the right to use it on one MAIN domain, but also on as many staging/test/beta domains or subdomains as you need; if someone from our team contacts you, then you can explain, it's perfectly reasonable to have test instances - we know how it goes; + + + +### Can I use Backpack to create an open-source project? + +Yes you can! Use [Backpack CRUD v6](https://github.com/laravel-backpack/crud), which is free and open-source, released under the MIT License. + + + +### Can I use Backpack PRO in an open-source project? + +In short - no, you cannot. Please use [Backpack CRUD v6](https://github.com/laravel-backpack/crud) instead, which is free and open-source, released under the MIT License. + +Backpack PRO is a closed-source add-on, which requires payment in order to receive an access token. If you did include `backpack/pro` as a dependency in your open-source software, then your software would no longer be open-source. Everybody who installed your project/package would need to pay for Backpack PRO to get access to it. + + + +### Can I get Backpack PRO for free to use in a non-commercial project? + +No - we're no longer giving away free licenses. But we _have_ released Backpack CRUD v5 and v6 under the MIT License, which means it's free and open-source. It has fewer features, but you can do absolutely do anything you want with it. + + +## Installation + + + +### How do I update Backpack to the latest non-breaking version? + +Run **`composer update`** on your project to update the dependencies given your version constrains in `composer.json`. If you want to update a specific package, you can run **`composer update backpack/crud`** for example to only update `backpack/crud` and it's dependencies. + +If you have your assets cached you can run `php artisan basset:clear` to clear the cache too. You can manually rebuild the asset cache if necessary with `php artisan basset:cache`. We do recommend you keep basset disabled on localhost while developing. + +Some packages may require you to run additional commands to update the database schema or publish new assets. Please refer to the package documentation or upgrade guide for more information. + + +### How do I uninstall Backpack from my project? + +You can remove Backpack from your project pretty easily, if you decide to stop using it. You just have to do the opposite of the installation process: + +```bash +# delete the files Backpack has placed inside your application +rm -rf app/Http/Middleware/CheckIfAdmin.php +rm -rf config/backpack +rm -rf config/gravatar.php +rm -rf resources/views/vendor/backpack +rm -rf routes/backpack + +# delete any CrudControllers you've created, so MAYBE: +rm -rf app/Http/Controllers/Admin + +# delete any Requests you've created for your CrudControllers. +# MAKE SURE YOU DON'T NEED ANYTHING IN THIS DIRECTORY ANYMORE. +# You might have OTHER requests that are not Backpack-related. +rm -rf app/Http/Requests + +# (MUST) remove other Backpack packages that you are using, like PRO, Editable Columns, DevTools etc: +composer remove --dev backpack/devtools +composer remove backpack/pro +composer remove backpack/editable-columns + +etc... + +# After everything related to Backpack is deleted, just need to delete the crud! +composer remove backpack/crud + +``` + +That's it! If you've decided NOT to use Backpack, we'd be super grateful if you could send us an email telling us WHY you decided not to use Backpack, or why it didn't fit your project. It might help us take Backpack in a different direction, a direction where you might want to use it again. Thank you 🙏 + + + +### Errors when installing paid add-ons + +When installing our [paid add-ons](https://backpackforlaravel.com/addons): +- Composer will add our private repository (`repo.backpackforlaravel.com`) to your `composer.json` file; +- Composer will try to download the `dist` version of the package from there; + - if successful, you're good; + - if the `dist` version fails to download, Composer will throw an error (with an HTTP code like 402); then Composer will try to download the `source` version of the package straight from our Github repo; that will 100% fail, because you do NOT have access to our private Github repo; to rephrase, you don't have access to the `source`, only to the `dist` version; + +Unfortunately, we cannot customize the errors that Composer throws, so the error text might be confusing. Please take a look at the HTTP error code shown in the error to understand what happened: +- 400 Error - Bad Request - user and password do not match; please check your auth credentials; +- 401 Error - Unauthorized - no token username or password; please check your auth credentials; +- 402 Error - Payment Required - you are trying to download a version newer than you have access to; our system will send you an email with clear instructions on what to do to require the latest version you have access to; you can also check the latest version you have access to in your Backpack account, and require that version specifically; alternatively, please purchase the same product again to gain access to more updates, then it will work again; +- 404 Error - Not Found - the package that you are trying to download does not exist; +- 429 Error - Too Many Requests - our server has received too many requests from your IP address; please wait one minute and try again; + +If you still can't figure it out, please [open a new discussion in our Community Forum](https://github.com/Laravel-Backpack/community-forum/discussions/categories/q-a-help). Please make sure to: +- mention the steps you have followed to get there (e.g. `composer require backpack/pro`, `php artisan backpack:require:pro` etc.); +- include a screenshot of the console output, so we can understand what happened; +- cross out any personal data (e.g. token username or password); diff --git a/7.x-dev/features-free-vs-paid.md b/7.x-dev/features-free-vs-paid.md new file mode 100644 index 00000000..997825e8 --- /dev/null +++ b/7.x-dev/features-free-vs-paid.md @@ -0,0 +1,214 @@ +# Features (Free vs Paid) + +--- + +Our software is open-core. That means there are features that you can use for free, and features you can only access by purchasing. Our goal with this split was to have: +- a simplified version, that includes what most admin panels absolutely need, in `backpack\crud`; FREE +- a plug-and-play add-on, that adds features for more complex use cases, in `backpack\pro`; PRO +- add-ons to help in corner cases (both FREE and PAID); + +You do not _need to_ purchase anything from us. But we hope that: +- if you're making money from your project, as soon as you need _one_ paid feature, you can justify its cost, to save the time it takes to build that yourself; +- if you're _not_ making money from your project yet, as the project grows and starts making a profit, you'll _want to_ purchase, to get access to paid features and support its maintenance; + + +## Features + +Everywhere in our docs, you'll see the PRO label if it needs the `backpack\pro` add-on. Everything else is free, as part of `backpack\crud`. Here's a comparison table of all features, so you can easily understand what will be a good fit for you: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Backpack\CRUDBackpack\CRUD +
Backpack\PRO
Admin UI
  - Alerts   FREEFREE
  - Authentication   FREEFREE
  - Custom Pages   FREEFREE
  - Breadcrumbs   FREEFREE
  - HTML Components   180+ components180+ components
  - Widgets   9 widgets9 widgets + chart
CRUD Panels
  - List OperationFREEFREE
      - Columns   28 columns types + 28 free + + 29 pro columns +
      - Buttons   FREEFREE
      - Search   FREEFREE
      - Filters   -10+ filter types
      - Export Buttons   -PRO
      - Details Row   -PRO
  - Create & Update OperationsFREEFREE
      - Fields   28 field types + 28 free + + 29 pro fields +
      - Validation   3 ways3 ways
      - Multiple fields per line   FREEFREE
      - Split fields into tabs   FREEFREE
      - Translatable Models   FREEFREE
      - Save Actions   FREEFREE
  - Show OperationFREEFREE
      - Columns   28 column types + 28 free + + 29 pro columns +
  - Delete OperationFREEFREE
  - Reorder OperationFREEFREE
  - Revise Operation   FREEFREE
  - BulkDelete Operation   -PRO
  - Clone Operation   -PRO
  - BulkClone Operation   -PRO
  - Fetch Operation   -PRO
  - InlineCreate Operation   -PRO
+ +

+Both `backpack/crud` and `backpack/pro` will keep receiving active attention, maintenance and care from us, for many years going forward - this is our job. If you have suggestions, please [tell us](https://github.com/laravel-backpack/ideas). + + +## Add-ons + +In addition to our main packages (`backpack\crud` and `backpack\pro`), whose features we've detailed above, we've also developed a series of single-purpose Backpack add-ons. Most developers won't need these, but those who do will be grateful that we took the time: + - some free: `backpack\permissionmanager`, `backpack\settings`, `backpack\pagemanager`, `backpack\newscrud`, `backpack\menucrud`, `backpack\filemanager`, `backpack\logmanager`, `backpack\backupmanager`, `backpack\revise-operation` etc. FREE + - some paid: `backpack\devtools`, `backpack\figma-template` PAID EXTRA + +We also encourage our community to build third-party add-ons (we've made it [super easy](/docs/{{version}}/add-ons-tutorial-using-the-addon-skeleton)). Our plan is to create more and more add-ons, as we discover more recurring problems, that we can solve for Laravel freelancers and development teams. + +For more information, see: +- [our add-ons page](https://backpackforlaravel.com/addons) for more add-ons (including third-party add-ons); +- [our pricing page](https://backpackforlaravel.com/pricing) for the first-party add-ons that we think are _so good_, that they're worth paying for; diff --git a/7.x-dev/generating-code.md b/7.x-dev/generating-code.md new file mode 100644 index 00000000..a73cc55c --- /dev/null +++ b/7.x-dev/generating-code.md @@ -0,0 +1,222 @@ +# Generating Code + +--- + +Backpack also provides ways to quickly write code inside your admin panels, for you to customize to your needs. There are two officials tools, both will help you publish, override or generate files, that you can customize to your liking: +- [Backpack Generators](https://github.com/laravel-backpack/generators/) (FREE) - a command-line interface that has already been installed in your project; +- [Backpack DevTools](/products/devtools) (PAID) - a web interface that helps do all of the above and more, from a browser; + + +## Command-Line Interface (CLI) - FREE + +If you've installed Backpack, you already have access to Backpack's command line interface. You can run `php artisan backpack` to get a quick list of everything you can do using our CLI. Here's that same list, a bit more organized: + + +#### Generate Full CRUDs + + + + + + + + + + +
php artisan backpack:buildCreate CRUDs for all Models that do not already have one.
php artisan backpack:crudCreate a CRUD interface: Controller, Model, Request
+ + +#### Generate CRUD files + + + + + + + + + + + + + + + + + + + + + + +
php artisan backpack:crud-controllerGenerate a Backpack CRUD controller.
php artisan backpack:crud-modelGenerate a Backpack CRUD model
php artisan backpack:crud-requestGenerate a Backpack CRUD request
php artisan backpack:crud-operationGenerate a custom Backpack CRUD operation trait
php artisan backpack:crud-form-operationGenerate a custom Backpack CRUD operation trait with Backpack Form
+ + +#### Generate CRUD operation components + + + + + + + + + + + + + + + + + + +
php artisan backpack:buttonGenerate a custom Backpack button
php artisan backpack:columnGenerate a custom Backpack column
php artisan backpack:fieldGenerate a custom Backpack field
php artisan backpack:filterGenerate a custom Backpack filter
+ + +#### Generate general admin panel files (non-CRUD) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
php artisan backpack:pageGenerate a Backpack Page
php artisan backpack:page-controllerGenerate a Backpack PageController
php artisan backpack:chartCreate a ChartController and route
php artisan backpack:chart-controllerGenerate a Backpack ChartController
php artisan backpack:widgetGenerate a Backpack widget
php artisan backpack:add-custom-routeAdd code to the routes/backpack/custom.php file
php artisan backpack:add-sidebar-contentAdd code to the Backpack sidebar_content file
php artisan backpack:publishPublishes a view to make changes to it, for your project +
+ + +#### Installation & Debugging + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
php artisan backpack:installInstall Backpack requirements on dev, publish CSS and JS assets and create uploads directory.
php artisan backpack:require:proRequire Backpack PRO
php artisan backpack:require:devtoolsRequire Backpack DevTools on dev
php artisan backpack:require:editablecolumnsRequire Backpack Editable Columns
php artisan backpack:publish-middlewarePublish the CheckIfAdmin middleware
php artisan backpack:fixFix known Backpack issues.
php artisan backpack:userCreate a new user
php artisan backpack:versionShow the version of PHP and Backpack packages.
+ + +## Web Interface (DevTools) - PREMIUM + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/list-models.jpg) + +For the people who want to step up their code-generation game, we've created [Backpack DevTools](/products/devtools). It empowers devs to do most of the things our CLI does, but: +- in a more intuitive and easy-to-use environment (web browser); +- in a more complete and correct way (eg. fills in validation rules, relationships etc.); +- can also do things that the CLI can't (eg. generate Seeders, Factories); + +Here are a few things DevTools makes easy, and how: + + +#### Generate Migration & Model + +As opposed to the CLI who can only generate an _empty_ model, DevTools can create near-perfect Eloquent Models, that include fillable and relationships. Just fill in one web form: + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/new-model.jpg) + +While the code might not be perfect for complex models (will need a double-check and possibly customizations), it _will_ generate working code, and it _will_ save you the bulk of your work. + + +#### Generate Factory & Seeder + +This is something the CLI doesn't offer at all. + +When generating a Model, you can also choose to generate a Factory and a Seeder for it. While the generated code isn't 100% perfect for complex models, it's a great starting point - and super-easy to customize after it's generated. + +One other benefit of having Factories and Seeders generated is that you'll then be able to Seed your tables (aka. add dummy entries) right from your admin panel, inside DevTools: + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/seed-model.jpg) + + + +#### Generate CRUDs + +The functionality here isn't much different from the CLI. DevTools will allow you to create standard CRUDs for your Eloquent models, from the comfort of your web browser. Hell, it will even show you a list of Models, to see which ones have CRUDs and which ones do not. + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/list-models.jpg) + +*Note:* DevTools will _not_ allow you too choose operations, columns, fields etc for those CRUDs. The CRUDs that are generated are standard, just like the ones provided by our CLI. You can then customize operations, columns, fields etc in code, directly. + + + +#### Generate CRUD Operations + +While the CLI allows you to create blank operations, DevTools takes that to a whole different level. It allows you to quickly create fully-working Operations, where you just need to customize the logic. Using various combinations, DevTools allows you to create up to 16 types of operations + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/new-operation.jpg) + + +#### Generate or Publish Operations Files + +Just like the CLI, DevTools will help generate custom buttons, columns, fields or filters... or alternatively... publish the _existing_ blade files, to customize them to your liking. + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/new-operation-file.jpg) + + + +#### Generate Custom Page + +Just like the CLI, DevTools will also help you generate completely custom admin panel pages, that DO NOT have CRUDs. This is very useful to generate pages for your Dashboards and other types of pages that do not depend on CRUD. Keep in mind you can copy-paste any HTML from https://backstrap.net into the views and it'll look identical. + + +![](https://backpackforlaravel.com/uploads/docs-5-0/devtools/new-page.jpg) + + +--- + + +Needless to say, we highlighy recommend purchasing [Backpack DevTools](/products/devtools). It saved us so many hours in development time, we can't even count. It's not a magic bullet - it's NOT a no-code solution. But it will help you generate so much code, and keep you working on the important bits, the actual logic. diff --git a/7.x-dev/getting-started-advanced-features.md b/7.x-dev/getting-started-advanced-features.md new file mode 100644 index 00000000..f1c3ff57 --- /dev/null +++ b/7.x-dev/getting-started-advanced-features.md @@ -0,0 +1,49 @@ +# 3. Advanced Features + +--- + +**Duration:** 5 minutes + +Here are some other cool things Backpack makes easy for you. We recommend going through them one by one, just browsing. You might not need the feature _right now_, but when _you do_, you'll know where to find it. + +--- + + +## Other Operations +- [Show](/docs/{{version}}/crud-operation-show) Operation - helps admins preview an entry FREE +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - helps reorder and nest entries (hierarchy tree) FREE +- [Revise](/docs/{{version}}/crud-operation-revisions) Operation - helps record all changes to an entry and undo them FREE +- [Clone](/docs/{{version}}/crud-operation-clone) Operation - helps make a copy of an entry PRO +- [BulkDelete](/docs/{{version}}/crud-operation-delete) Operation - helps delete multiple items in one go PRO +- [BulkClone](/docs/{{version}}/crud-operation-clone) Operation - helps clone multiple items in one go PRO + +--- + + +## Other Features +- **Create & Update** + - [Manage files on disk](/docs/{{version}}/crud-how-to#use-the-media-library-file-manager) (media library) FREE + - [Fake fields](/docs/{{version}}/crud-fields#optional-fake-field-attributes-stores-fake-attributes-as-json-in) FREE + - Translatable models and [multi-language CRUDs](/docs/{{version}}/crud-operation-update#translatable-models) FREE + - [Tabs in create/update forms](/docs/{{version}}/crud-fields#split-fields-into-tabs) FREE + +-- + +- **ListEntries** + - you can add a "+" button next to each entry to allow an admin to easily preview some quick information that was too big to fit inside a column - we call this [details row](/docs/{{version}}/crud-operation-list-entries#details-row) PRO + - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) PRO + - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in a list view FREE + + +Additionally, here are a few more ways you can customize your CRUDs: +- [Custom Views for each CRUD](/docs/{{version}}/crud-how-to#customize-views-for-each-crud-panel) +- [Custom Content Class for each CRUD](/docs/{{version}}/crud-how-to#resize-the-content-wrapper-for-an-operation) +- [Custom CSS or JS](/docs/{{version}}/crud-how-to#customize-css-and-js-for-default-crud-operations) for each CRUD Operation + +**That's it!** We told you we're done with long lessons. Hopefully, some of the above has peaked your interest and you've clicked through to see what Backpack can do. In the [next lesson](/docs/{{version}}/getting-started-license-and-support) we'll go through a few other Backpack packages that cover some recurring use cases. + + +
+ + Next → + diff --git a/7.x-dev/getting-started-basics.md b/7.x-dev/getting-started-basics.md new file mode 100644 index 00000000..3f2e947f --- /dev/null +++ b/7.x-dev/getting-started-basics.md @@ -0,0 +1,160 @@ +# 1. Basics + +--- + +**Duration:** 5 minutes + +> **Are you already comfortable with Laravel?** In order to understand this series and make use of Backpack, you'll need to have a decent understanding of the Laravel framework. If you don't, please [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-8-from-scratch) and familiarize yourself with Laravel first. + + + +## What is Backpack? +A software package that helps Laravel professionals build administration panels - secure areas where administrators login and create, read, update, and delete application information. It is *not* a CMS, it is more a framework that lets you *build your own* CMS. You can install it in your existing project or in a totally new project. + +It's designed to be flexible enough to allow you to **build admin panels for everything from simple presentation websites to CRMs, ERPs, eCommerce, eLearning, etc**. We can vouch for that, because we have built all that stuff using Backpack already. + + +## What's a CRUD? + +A **CRUD** is what we call a section of your admin panel that lets the admin _Create, Read, Update, or Delete_ entries of a certain entity (or Model). So you can have a CRUD for Products, a CRUD for Articles, a CRUD for Categories, or whatever else you might want to create, read, update, or delete. + +For the purpose of this series, we'll show examples on the ```Tag``` entity. This is how a Tag CRUD might look like: + +![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/tag_crud_list_entries.png) + +But Backpack is prepared for feature-packed CRUDs - since it's a good tool for very complex projects too. This is how a CRUD that uses all of Backpack's features might look like: + +![Monsters CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/monster_crud_list_entries.png) + +However, you will _almost never_ use all of Backpack's features in one CRUD. But if you do, it will still look good, and it will be intuitive to use. + + +## Main Features + + +### Front-End Design + +New Backpack installs come with an HTML theme installed - you choose which theme. All themes use Bootstrap, and have many HTML blocks ready for you to use. When you're building a custom page in your admin panel, it's easy to just copy-paste the HTML from the the theme's demo or from its documentation. And the page will look good, without you having to design anything. Currently, we have three first-party themes: +- [Tabler](https://github.com/Laravel-Backpack/theme-tabler) +- [CoreUI v4](https://github.com/Laravel-Backpack/theme-coreuiv4) +- [CoreUI v2](https://github.com/Laravel-Backpack/theme-tabler) (which still provides IE support) + +You can also create your own custom theme. In fact, we've built our theming system in such a way that if you buy a Bootstrap-based HTML template from Envato / GetBootstrap / WrapBootstrap, it should take you no more than 5 hours to create a Backpack theme that uses that HTML template. + +All themes also install Noty for triggering JS notification bubbles, and SweetAlerts. So you can easily use these across your admin panel. You can [trigger notification bubbles in PHP](/docs/{{version}}/base-about#triggering-notification-bubbles-in-php) or [trigger notification bubbles in JavaScript](/docs/{{version}}/base-about#triggering-notification-bubbles-in-javascript). + + +### Authentication + +Backpack comes with an authentication system that's separate from Laravel's. This way, you can have different login screens for users and admins, if you need to. If not, you can choose to use only one authentication - either Laravel's or Backpack's. + +![Backpack Authentication Screens](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/auth_screens.png) + +After you [install Backpack](/docs/{{version}}/installation) (don't do it now), you'll be able to log into your admin panel at ```http://yourapp/admin```. You can change the URL prefix from ```admin``` to something else in your ```config/backpack/base.php``` file, along with a bunch of other configuration options. [Click here](https://github.com/Laravel-Backpack/CRUD/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. + + + +### CRUDs + +This is where it gets interesting. As soon as you [install Backpack](/docs/{{version}}/installation) in your project, you can create **CRUDs** for your admins to easily manipulate database entries. Let's browse through a simple example of creating a CRUD administration panel for a Tag entity. + +You can generate everything a CRUD needs using one of the methods below: + +--- + +**Option A) PRO - using our GUI, [Backpack DevTools](https://backpackforlaravel.com/products/devtools)** + +Just install DevTools, fill in a web form with the columns for your entity, and it'll generate all the needed files. It's that simple. Check out [the images here](https://backpackforlaravel.com/products/devtools) to see how it works. It's especially useful for more complex entities. It is a paid tool though, so if you are not yet ready to purchase, let's explore the free option too. + +**Option B) FREE - using the command-line interface** + +You can use anything you want to generate the Migration and Model, so in this case we're going to use [laracasts/generators](https://github.com/laracasts/Laravel-5-Generators-Extended): + +```zsh +# STEP 0. install a 3d party tool to generate migrations +composer require --dev laracasts/generators + +# STEP 1. create a migration +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique,slug:string:unique" +php artisan migrate + +# STEP 2. create a CRUD for it +php artisan backpack:crud tag #use singular, not plural +``` + +--- + +In both cases, what we're getting is a simple CRUD panel, which you should now be able to see in the Sidebar. + +For a simple entry like this, the generated CRUD panel will even work "as is", with no need for customisations. But don't expect this for more complex entities. They will usually have specific requirements and need customization. That's where Backpack shines - modifying anything in the CRUD panel is easy and intuitive, once you understand how it works. + +The methods above will generate: +- a **migration** file +- a **model** (```app\Models\Tag.php```) +- a **request** file, for form validation (```app\Http\Requests\TagCrudRequest.php```) +- a **controller** file, where you can customize how the CRUD panel looks and feels (```app\Http\Controllers\Admin\TagCrudController.php```) +- a **route**, as a line inside ```routes/backpack/custom.php``` + +It will also add: +- a route inside ```routes/backpack/custom.php```, pointing to that controller +- a menu item inside ```resources/views/vendor/backpack/ui/inc/menu_items.blade.php``` + +You might have noticed that **no views** are generated. That's because in most cases you _don't need_ custom views with Backpack. All your custom code is in the controller, model, or request, so the default views are loaded from the package. If you do, however, need to customize a view, it is [really easy](/docs/{{version}}/crud-how-to#customize-views-for-each-crud-panel). + +Also, we won't be covering the **migration**, **model**, and **request** files here, as they are in no way custom. The only thing you need to make sure is that the Model is properly configured (database table, relationships, ```$fillable``` or ```$guarded``` properties, etc.) and that it uses our ```CrudTrait```. What we _will_ be covering is ```TagCrudController``` - which is where most of your logic will reside. Here's a copy of a simple one that you might use to achieve the above: + +```php +type('text'); + CRUD::field('slug')->type('text')->label('URL Segment (slug)'); + } + + public function setupUpdateOperation() + { + $this->setupCreateOperation(); + } +} +``` + +You should notice: +- It uses basic inheritance (```TagCrudController extends CrudController```); so if you want to modify a behaviour (save, update, reorder, etc.), you can easily do that by overwriting the corresponding method in your ```TagCrudController``` +- All operations are enabled by using that operation's trait on the controller +- The ```setup()``` method defines the basics of the CRUD panel +- Each operation is set up inside a ```setupXxxOperation()``` method + +**That's it!** If you want to learn more, please [read the next lesson](/docs/{{version}}/getting-started-crud-operations) of this series. + + +
+ + Next → + diff --git a/7.x-dev/getting-started-crud-operations.md b/7.x-dev/getting-started-crud-operations.md new file mode 100644 index 00000000..0c79813c --- /dev/null +++ b/7.x-dev/getting-started-crud-operations.md @@ -0,0 +1,268 @@ +# 2. CRUD Operations + +--- + +**Duration:** 10 minutes + +Let's bring back the example from our first lesson. The Tags CRUD: + +![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/tag_crud_list_entries.png) + +With its ```TagCrudController```: + +```php +type('text')->label('URL Segment (slug)'); + } + + public function setupUpdateOperation() + { + $this->setupCreateOperation(); + } +} +``` + +In the example above, we've enabled the most common operations: +- **Create** - using a create form (aka "*add form*") +- **List** - using AJAX DataTables (aka "*list view*", aka "*table view*") +- **Update** - using an update form (aka "*edit form*") +- **Delete** - using a *button* in the *list view* +- **Show** - using a *button* in the *list view* + +These are the basic operations an admin can execute on an Eloquent model, thanks to Backpack. We do have additional operations (Reorder, Revisions, Clone, BulkDelete, and BulkClone), and you can easily _create a custom operation_, but let's not get ahead of ourselves. **Let's go through the most important features of the operations you'll be using _all the time_: List, Create, and Update**. + + +## Create and Update Operations + +![Tag CRUD - Edit Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/tag_crud_edit.png) + + +### Fields + +Inside your Controller's ```setupCreateOperation()``` or ```setupUpdateOperation()``` method, you'll be able to define which fields you want the admin to see, when creating/updating entries. In the example above, we only have two fields, both using the ```text``` field type. So that's what's shown to the admin. When the admin presses _Save_, assuming your model has those two attributes as ```$fillable```, Backpack will save the entry and take you back to the List view. Keep in mind we're using a _pure_ Eloquent model. So of course, inside the model you could use accessors, mutators, events, etc. + + +But often, you won't just need text inputs. You'll need datepickers, upload buttons, 1-n relationships, n-n relationships, textareas, etc. For each field, you only need to define it properly in the Controller. Here are the most often used methods to manipulate fields: + +```php +CRUD::field('price'); +CRUD::field('price')->prefix('$'); // set the "prefix" attribute to "$" +CRUD::field('price')->remove(); // delete a field from the form +CRUD::field('price')->after('name'); // move a field after a different field + +// you can of course chain these calls: +CRUD::field('price')->label('Product price')->prefix('$')->after('name'); + +// you can also add multiple attributes in one go, by creating +// a field using an array, instead of just its name (a string); +// we call this the array syntax: +CRUD::field([ + 'name' => 'price', + 'type' => 'number', + 'label' => 'Product price', + 'prefix' => '$', +]); +``` + +A typical *field definition* will need at least three things: +- ```name``` - the attribute (column in the database), which will also become the name of the input +- ```type``` - the kind of field we want to use (text, number, select2, etc.) +- ```label``` - the human-readable label for the input (will be generated from ```name``` if not given) + + +You can use [one of the 44+ field types we've provided](/docs/{{version}}/crud-fields#default-field-types), or easily [create a custom field type](/docs/{{version}}/crud-fields#creating-a-custom-field-type) if you have a specific need that we haven't covered yet, or even [overwrite how a field type works](#overwriting-default-field-types). Take a few minutes and [browse the 44+ field types](/docs/{{version}}/crud-fields#default-field-types) to understand how the definition array differs from one to another and how many use cases you have already covered. + +Let's take another example, slightly more complicated than the ```text``` fields we used above. Something you'll encounter all the time are relationship fields. So let's say the ```Tag``` model has an **n-n relationship** with an Article model: + +```php + public function articles() + { + return $this->belongsToMany('App\Models\Article', 'article_tag'); + } +``` + +We could use the code below to add a ```select2_multiple``` field to the Tag update forms: + +```php +CRUD::field([ + 'type' => 'select2_multiple', + 'name' => 'articles', // the relationship name in your Model + 'entity' => 'articles', // the relationship name in your Model + 'attribute' => 'title', // attribute on Article that is shown to admin + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? +]); +``` + +**Notes:** +- If we call this inside ```setupUpdateOperation()```, then it will only be added on that operation +- Because we haven't specified a ```label```, Backpack will take the "_articles_" name and turn it into a label, "_Articles_" + +If we had an Articles CRUD, and the reverse relationship defined on the ```Article``` model too, then we could also add a ```select2_multiple``` field in the Article CRUD to allow the admin to choose which tags apply to each article. This actually makes more sense than the above: + +```php +CRUD::field([ + 'label' => "Tags", + 'type' => 'select2_multiple', + 'name' => 'tags', // the method that defines the relationship in your Model + 'entity' => 'tags', // the method that defines the relationship in your Model + 'attribute' => 'name', // foreign key attribute that is shown to user + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? +]); +``` + + +### Callbacks + +Developers coming from other CRUD systems will be looking for callbacks to run ```before_insert```, ```before_update```, ```after_insert```, and ```after_update```. **There are no callbacks in Backpack**. + +Remember that Backpack is using Eloquent models. That means you can do X when Y is happening, by using the [model events](https://laravel.com/docs/10.x/eloquent#events). For example, in your MonsterCrudController you can do: + +```php +public function setup() { + // this will get run for all operations that trigger the "deleting" model event + // by default, that's the Delete operation + Monster::deleting(function ($entry) { + // TODO: delete that entry's files from the disk + }); + + // this will get run on all operations that trigger the "saving" model event + // by default, that's the Create and Update operations + Monster::saving(function ($entry) { + // TODO: change one value to another or something + }); +} +``` + +Alternatively, if you need to change how an operation does something, then that's simple too. The ```store()``` and ```update()``` code is inside a trait, so you can easily override that method and call it inside your new method. For example, here's how we can do things before/after an item is saved in the Create operation: + +```php +traitStore(); + // do something after save + return $response; + } +} +``` + +>But before you do that, ask yourself - **_is this something that should be done when an entry is added/updated/deleted from the application too_**? Not just from the admin panel? If so, a better place for it would be the Model. Remember your Model is a pure Eloquent Model, so the cleanest way might be to use [Eloquent Event Observers](https://laravel.com/docs/6.0/eloquent#events) or [accessors and mutators](https://laravel.com/docs/master/eloquent-mutators#accessors-and-mutators). + + +## List Operation + +List shows the admin a table with all entries. On the front-end, the information is pulled using AJAX calls, and shown using DataTables. It's the most feature-packed operation in Backpack, but right now we're just going through the most important features you need to know about: columns, filters, and buttons. + +You should configure the List operation inside the ```setupListOperation()``` method. + + +### Columns + +Columns help you specify *which* attributes are shown in the table and *in which order*. Their syntax is almost identical to fields. In fact, you'll find each Backpack field has a corresponding column, with the same name and syntax: + +```php +CRUD::column($column_definition_array); // add a single column, at the end of the table +CRUD::column('price')->type('number'); +CRUD::column('price')->type('number')->prefix('$'); +CRUD::column('price')->after('name'); // move column after another column +CRUD::column('price')->remove(); // move column after another column + +// bulk actions +CRUD::addColumns([$column_definition_array_1, $column_definition_array_2]); // add multiple columns, at the end of the table +CRUD::setColumns([$column_definition_array_1, $column_definition_array_2]); // make these the only columns in the table +CRUD::removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the table +CRUD::setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); +``` + +You can use one of the [44+ column types](/docs/{{version}}/crud-columns#default-column-types) to show information to the user in the table, or easily [create a custom column type](/docs/{{version}}/crud-columns#creating-a-custom-column-type) if you have a specific need. Here's an example of using the methods above: + +```php +CRUD::column([ + 'name' => 'published_at', + 'type' => 'date', + 'label' => 'Publish_date', +]); + +// PRO TIP: to quickly add a text column, just pass the name string instead of an array +CRUD::column('text'); // adds a text column, at the end of the stack +``` + + +### Filters + +![Monster CRUD - List Entries Filters](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/backpack_filters.png) + +Filters provide an easy way for the admin to _filter_ the List table. The syntax is very similar to Fields and Columns and you can use one of the [existing 8 filter types](/docs/{{version}}/crud-filters) or easily [create a custom filter](/docs/{{version}}/crud-filters#creating-custom-filters). Note that this is a PRO feature. + +```php +CRUD::addFilter($options, $values, $filter_logic); +CRUD::removeFilter($name); +CRUD::removeAllFilters(); +``` + +For more on filters, check out the [filters documentation page](/docs/{{version}}/crud-filters). + + +### Buttons + +![Tag CRUD - List Entries Buttons](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/backpack_buttons.png) + +If you want to add a custom button to an entry, you can do that. If you want to remove a button, you can also do that. Check out the [buttons documentation](/docs/{{version}}/crud-buttons). + +```php +// positions: 'beginning' and 'end' +// stacks: 'line', 'top', 'bottom' +// types: view, model_function +CRUD::addButton($stack, $name, $type, $content, $position); +CRUD::removeButton($name); +CRUD::removeButtonFromStack($name, $stack); +``` + +**That's it!** Thanks for sticking with us this long. This has been the most important and longest lesson. You can go ahead and [install Backpack](/docs/{{version}}/installation) now as you've already gone through the most important features. Or [read the next lesson](/docs/{{version}}/getting-started-advanced-features) about advanced features. + + +
+ + Next → + diff --git a/7.x-dev/getting-started-license-and-support.md b/7.x-dev/getting-started-license-and-support.md new file mode 100644 index 00000000..fed5a439 --- /dev/null +++ b/7.x-dev/getting-started-license-and-support.md @@ -0,0 +1,55 @@ +# 4. Add-ons, License, and Support + +--- + +**Duration:** 3 minutes + + +## Add-ons + +In addition to our core package (CRUD), we have quite a few packages you can install or download that deal with common use cases. Some have been developed by our core team, some by our wonderful community. For example, we have plug&play interfaces to manage [site-wide settings](https://github.com/Laravel-Backpack/Settings), [the default Laravel users table](https://github.com/eduardoarandah/UserManager), [users, groups & permissions](https://github.com/Laravel-Backpack/PermissionManager), [content for custom pages, using page templates](https://github.com/Laravel-Backpack/PageManager), [news articles, categories and tags](https://github.com/Laravel-Backpack/NewsCRUD), etc. + +Take a look at: +- [all official add-ons](/docs/{{version}}/add-ons-official) +- [all community add-ons](/docs/{{version}}/add-ons-community) + + +## License + + +Backpack is open-core. The features you'll find in our docs are split into two packages: + +- [Backpack\CRUD](https://github.com/laravel-backpack/crud) is the core, released under the [MIT License](https://github.com/Laravel-Backpack/CRUD/blob/master/LICENSE.md) (free, open-source) FREE +- [Backpack\PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) is a Backpack add-on, released under our [EULA](https://backpackforlaravel.com/eula) (paid, closed-source) PRO + +Backpack\CRUD is perfect if you're building a simple admin panel - it's packed with features! It's also perfect if you're building an open-source project, the permissive license allows you to do whatever you want. + +When your admin panel grows and your needs become more complex, you can purchase our [Backpack\PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) add-on, which adds A LOT of features for complex use cases (see [list here](https://backpackforlaravel.com/products/pro-for-unlimited-projects)). Our documentation includes instructions on how to use both Backpack\CRUD and Backpack\PRO, with all the PRO features clearly labeled PRO + + + +## Support + +We offer free support for all our packages. If you report a bug, we will do our best to fix it in a timely manner. We publish a new patch version weekly, launch new features every month and major versions yearly. But please note that support does _not_ mean we can help with debugging, implementation issues specific to your project, brainstorming on solutions for your project, jumping on Zoom/Meet calls etc. We'd love to, but we just cannot do that, with thousands of developers using Backpack and such a small price. But. We do have good documentation and have been blessed with a **great community**, where people help each other out. If Backpack becomes your tool of choice, I highly recommend you join our gang. Help others with your experience, report bugs, create cool stuff and even influence the direction of Backpack. Use: + +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for help requests**. If you need help creating something using Backpack, post your question on StackOverflow using the ```backpack-for-laravel``` tag. We've been blessed with a great community that is happy to help. Who doesn't like getting StackOverflow points? +- **[Community Forum Issues](https://github.com/laravel-backpack/community-forum) for bugs**. Found a bug? Great! Please search for it on GitHub first - someone might have already found it. If not, open an issue, we're happy to make Backpack better. +- **[Community Forum Discussions](https://github.com/Laravel-Backpack/community-forum/discussions) for showing off your work, asking for opinions on implementation, sharing tips, packages, etc.** + +Thank you for sticking with us for so long. This is the last Backpack lesson in this series. **Now you have absolutely no excuse - time to start your first Backpack project!** But first, here are a few more links, if you are still not sure whether you are ready: + +- [Go through the demo](/docs/{{version}}/demo) and play around +- Read this [CRUD Crash Course](/docs/{{version}}/crud-tutorial) and do the steps yourself +- Take a look at the [CrudController API Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) + + +
+ + CRUD Crash Course + + + Demo + + + Cheat Sheet + diff --git a/7.x-dev/getting-started-videos.md b/7.x-dev/getting-started-videos.md new file mode 100644 index 00000000..ae0f8de0 --- /dev/null +++ b/7.x-dev/getting-started-videos.md @@ -0,0 +1,187 @@ +# Getting Started Videos + +--- + +**Total Duration:** 59 minutes + + + +

1. Intro

+ +Let's get to know Mauro Martinez, one of the Backpack maintainers and your teacher for this series. + +
+ +
+Mentioned in this video: +- [Laravel](https://laravel.com) +- ["Laravel from Scratch" series on Laracasts](http://laravelfromscratch.com/) +- [Backpack's docs repo on Github](https://github.com/laravel-backpack/docs) + +---- + + +

2. Installation and Setup

+ +Let's set up a Laravel project and follow the Backpack installation. + +
+ +
+Mentioned in this video: +- [Laravel docs - create project](https://laravel.com/docs/master/installation#your-first-laravel-project) +- [Backpack docs - installation](/docs/{{version}}/installation) + + +---- + + +

3. Look and Feel - Introduction to Backpack Themes

+A quick intro to Backpack themes - how to customize the look and feel of your admin panel, or use a Bootstrap theme of your choice. +
+ +
+Mentioned in this video: +- [About Themes](https://backpackforlaravel.com/docs/{{version}}/base-themes) +- [Demo](https://demo.backpackforlaravel.com/admin) + + +---- + + +

4. Dashboard

+Let's place some content on the admin dashboard using Backpack widgets - it's super easy. +
+ +
+Mentioned in this video: +- [Widgets](https://backpackforlaravel.com/docs/{{version}}/base-widgets) +- [Customize dashboard](https://backpackforlaravel.com/docs/{{version}}/base-how-to#customize-the-dashboard) + + +---- + + +

5. CRUDs - Intro to Operations

+Operations are a very important part of Backpack - they allow you to do things like [Create](https://backpackforlaravel.com/docs/{{version}}/crud-operation-create), Read ([List](https://backpackforlaravel.com/docs/{{version}}/crud-operation-list-entries) and [Show](https://backpackforlaravel.com/docs/{{version}}/crud-operation-show)), [Update](https://backpackforlaravel.com/docs/{{version}}/crud-operation-update), and [Delete](https://backpackforlaravel.com/docs/{{version}}/crud-operation-delete). Let's understand how they work. +
+ +
+Mentioned in this video: +- [Set up CRUD - basic guide](https://backpackforlaravel.com/docs/{{version}}/getting-started-crud-operations) +- [Operations - in depth](https://backpackforlaravel.com/docs/{{version}}/crud-operations) + + +---- + + +

6. DevTools - Generating CRUDs

+ +Go from an idea to a full CRUD in seconds! Quickly build an admin panel for your Eloquent models using a web interface to generate migrations, models, and CRUDs. + +
+ +
+Mentioned in this video: +- [DevTools - about](https://backpackforlaravel.com/docs/{{version}}/generating-code#web-interface-devtools-premium) +- [DevTools - installation](https://backpackforlaravel.com/products/devtools) + +---- + + +

7. List Operation

+ +Let's take a deeper dive into the List operation and one important feature it is using - Backpack columns. + +
+ +
+Mentioned in this video: +- [List operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-list-entries) +- [50+ Columns](https://backpackforlaravel.com/docs/{{version}}/crud-columns) + +---- + + +

8. Create and Update Operations

+ +Now let's do a deeper dive into Backpack forms - particularly the ones in the Create and Update operations, which use Backpack fields, another important feature. + +
+ +
+Mentioned in this video: +- [Create Operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-create) +- [Update Operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-update) +- [50+ Fields](https://backpackforlaravel.com/docs/{{version}}/crud-fields) + +---- + + +

9. Show and Delete Operations

+Let's also address some easy operations - the Show and Delete operations. You'll see they are very similar to the ones above. +
+ +
+Mentioned in this video: +- [Delete operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-delete) +- [Show operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-show) +- [50+ Columns](https://backpackforlaravel.com/docs/{{version}}/crud-columns) + +---- + + +

10. Reorder and BulkClone Operations

+Here are two other operation you will most likely need - they will help you reorder or duplicate entries. +
+ +
+Mentioned in this video: +- [Reorder Operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-reorder) +- [Clone Operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-clone) +- [Available Operations](https://backpackforlaravel.com/docs/{{version}}/crud-operations#standard-operations) + + +---- + + +

11. Custom Operations using DevTools

+ +This video shows you how to create custom operations, using our premium add-on DevTools. + +
+ +
+Mentioned in this video: +- [DevTools](https://backpackforlaravel.com/products/devtools) + +---- + + +Thank you for dedicating those 59 minutes to learning Backpack. **You should now be able to build your first admin panel.** But first, here are a few more links, if you are still not sure whether you are ready: + +- [Go through the demo](/docs/{{version}}/demo) and play around, browse the features (pay special attention to the Monsters CRUD) +- Read this [CRUD Crash Course](/docs/{{version}}/crud-tutorial) and do the steps yourself +- Take a look at the [CrudController API Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) +- Read our [Getting Started Text Course](/docs/{{version}}/getting-started-basics) +- Purchase and install [Backpack DevTools](https://backpackforlaravel.com/products/devtools) which will generate the minimum stuff for you + + + diff --git a/7.x-dev/index.md b/7.x-dev/index.md new file mode 100644 index 00000000..cb6360d6 --- /dev/null +++ b/7.x-dev/index.md @@ -0,0 +1,66 @@ +#### About + +- [Introduction](/docs/{{version}}/introduction) +- [Demo](/docs/{{version}}/demo) +- [Installation](/docs/{{version}}/installation) +- [Features (Free vs Paid)](/docs/{{version}}/features-free-vs-paid) +- [Release Notes](/docs/{{version}}/release-notes) +- [Upgrade Guide](/docs/{{version}}/upgrade-guide) +- [FAQ](/docs/{{version}}/faq) + +#### Getting Started +- [Video Course](/docs/{{version}}/getting-started-videos) +- [Text Course](/docs/{{version}}/getting-started-basics) + - [1. Basics](/docs/{{version}}/getting-started-basics) + - [2. CRUD Operations](/docs/{{version}}/getting-started-crud-operations) + - [3. Advanced Features](/docs/{{version}}/getting-started-advanced-features) + - [4. Add-ons, License & Support](/docs/{{version}}/getting-started-license-and-support) +- [Tutorials](/docs/{{version}}/tutorials) + +#### Admin UI + +- [About](/docs/{{version}}/base-about) +- [Alerts](/docs/{{version}}/base-alerts) +- [Breadcrumbs](/docs/{{version}}/base-breadcrumbs) +- [Themes](/docs/{{version}}/base-themes) +- [Widgets](/docs/{{version}}/base-widgets) +- [Components](/docs/{{version}}/base-components) +- [FAQs](/docs/{{version}}/base-how-to) + +#### CRUD Panels + +- [Basics](/docs/{{version}}/crud-basics) +- [Crash Course](/docs/{{version}}/crud-tutorial) +- [Operations](/docs/{{version}}/crud-operations) + + [List](/docs/{{version}}/crud-operation-list-entries) + + [Columns](/docs/{{version}}/crud-columns) + + [Buttons](/docs/{{version}}/crud-buttons) + + [Filters](/docs/{{version}}/crud-filters) + + [Create](/docs/{{version}}/crud-operation-create) & [Update](/docs/{{version}}/crud-operation-update) + + [Fields](/docs/{{version}}/crud-fields) + + [Save Actions](/docs/{{version}}/crud-save-actions) + + [Uploaders](/docs/{{version}}/crud-uploaders) + + [CrudField JS Library](/docs/{{version}}/crud-fields-javascript-api) + + [Delete](/docs/{{version}}/crud-operation-delete) + + [Show](/docs/{{version}}/crud-operation-show) + + [Columns](/docs/{{version}}/crud-columns) +- [Additional Operations](/docs/{{version}}/crud-operations) + + [Clone](/docs/{{version}}/crud-operation-clone) + + [Reorder](/docs/{{version}}/crud-operation-reorder) + + [Revise](/docs/{{version}}/crud-operation-revisions) + + [Fetch](/docs/{{version}}/crud-operation-fetch) + + [InlineCreate](/docs/{{version}}/crud-operation-inline-create) + + [Trash](/docs/{{version}}/crud-operation-trash) +- [API](/docs/{{version}}/crud-cheat-sheet) + + [Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) + + [Crud API](/docs/{{version}}/crud-api) + + [Fluent API](/docs/{{version}}/crud-fluent-syntax) +- [Generating Code](/docs/{{version}}/generating-code) +- [FAQ](/docs/{{version}}/crud-how-to) + +#### Add-ons + +- [Official Add-ons](/docs/{{version}}/add-ons-official) +- [Community Add-ons](https://backpackforlaravel.com/addons) +- [How to Create an Add-on](/docs/{{version}}/add-ons-tutorial-using-the-addon-skeleton) +- [How to Create a Theme](/docs/{{version}}/add-ons-tutorial-how-to-create-a-theme) diff --git a/7.x-dev/install-optionals.md b/7.x-dev/install-optionals.md new file mode 100644 index 00000000..e297b5fb --- /dev/null +++ b/7.x-dev/install-optionals.md @@ -0,0 +1,167 @@ +# Install Optional Packages + +--- + +Each Backpack package has its own installation instructions in its readme file. We duplicate them here for easy access. + +Everything else is optional. Your project might use them or it might not. Only do each of the following steps if you need the functionality that package provides. + + +## BackupManager + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/BackupManager) + +1) In your terminal + +```bash +# Install the package +composer require backpack/backupmanager + +# Publish the config file and lang files: +php artisan vendor:publish --provider="Backpack\BackupManager\BackupManagerServiceProvider" + +# [optional] Add a menu item for it in resources/views/vendor/backpack/ui/inc/menu_items.blade.php: +php artisan backpack:add-menu-content "" +``` + +2) Add a new "disk" to config/filesystems.php: + +```php +// used for Backpack/BackupManager +'backups' => [ + 'driver' => 'local', + 'root' => storage_path('backups'), // that's where your backups are stored by default: storage/backups +], +``` +This is where you choose a different driver if you want your backups to be stored somewhere else (S3, Dropbox, Google Drive, Box, etc). + +3) [optional] Modify your backup options in config/backup.php + +4) [optional] Instruct Laravel to run the backups automatically in your console kernel: + +```php +// app/Console/Kernel.php + +protected function schedule(Schedule $schedule) +{ + $schedule->command('backup:clean')->daily()->at('04:00'); + $schedule->command('backup:run')->daily()->at('05:00'); +} +``` + +5) [optional] If you need to change the path to the mysql_dump command, you can do that in your config/database.php file. For MAMP on Mac OS, add these to your mysql connection: +```php +'dump' => [ + 'dump_binary_path' => '/Applications/MAMP/Library/bin/', // only the path, so without `mysqldump` or `pg_dump` + 'use_single_transaction', + 'timeout' => 60 * 5, // 5 minute timeout +] +``` + + +## LogManager + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/logmanager) + + +1) Install via composer: + +```bash +composer require backpack/logmanager +``` + +2) Add a "storage" filesystem disk in config/filesystems.php: + +```php +// used for Backpack/LogManager +'storage' => [ + 'driver' => 'local', + 'root' => storage_path(), +], +``` + +3) Configure Laravel to create a new log file for every day, in your .ENV file, if it's not already. Otherwise there will only be one file at all times. + +``` +APP_LOG=daily +``` + +or directly in your config/app.php file: +```php +'log' => env('APP_LOG', 'daily'), +``` + +4) [optional] Add a menu item for it in resources/views/vendor/backpack/ui/inc/menu_items.blade.php: + +```bash +php artisan backpack:add-menu-content "" +``` + +## Settings + +An interface for the administrator to easily change application settings. Uses Laravel Backpack. + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/settings) + +Installation: + +```bash +# install the package +composer require backpack/settings + +# run the migration +php artisan vendor:publish --provider="Backpack\Settings\SettingsServiceProvider" +php artisan migrate + +# [optional] add a menu item for it in resources/views/vendor/backpack/ui/inc/menu_items.blade.php: +php artisan backpack:add-menu-content "" + +# [optional] insert some example dummy data to the database +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +``` + + +## PageManager + +An admin panel where you, as a developer, can define templates with different fields, and the admin can choose between those templates to create/edit pages with content. + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/pagemanager) + + +## PermissionManager + +An admin panel for user authentication on Laravel 5, using Backpack\CRUD. Add, edit, delete users, roles and permission. + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/PermissionManager) + + +## MenuCrud + +An admin panel for menu items on Laravel 5, using Backpack\CRUD. Add, edit, reorder, nest, rename menu items and link them to Backpack\PageManager pages, external link or custom internal link. + +[>> GitHub](https://github.com/Laravel-Backpack/MenuCRUD) + + +## NewsCrud + +Since NewsCRUD does not provide any extra functionality other than Backpack\CRUD, it is not a package. It's just a tutorial to show you how this can be achieved. In the future, CRUD examples like this one will be easily installed from the command line, from a central repository. Until then, you will need to manually create the files. + +[>> GitHub](https://github.com/Laravel-Backpack/NewsCRUD) + + + +## FileManager + +Backpack admin interface for files and folders, using [barryvdh/laravel-elfinder](https://github.com/barryvdh/laravel-elfinder). + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/FileManager) + +Installation: + +```bash +composer require backpack/filemanager +``` + +```bash +php artisan backpack:filemanager:install +``` diff --git a/7.x-dev/installation.md b/7.x-dev/installation.md new file mode 100644 index 00000000..8fc6859d --- /dev/null +++ b/7.x-dev/installation.md @@ -0,0 +1,85 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 10 or 11, you can install Backpack. Backpack does _not_ have additional requirements. + +For the following process, we assume: + +- you have a [working installation of Laravel](https://laravel.com/docs/11.x#installation) (an existing project is fine, you don't need a *fresh* Laravel install); + +- you have configured your .ENV file with your database and mail information; + +- you can run the ```composer``` command from any directory (you have ```composer``` registered as a global command); if you need to run ```php composer.phar``` or reference another directory, please remember to adapt the commands below to your configuration; + + +## Installation + + +### Install using Composer + +Go to your Laravel project's directory, then in your terminal, run: + +``` bash +composer require backpack/crud +php artisan backpack:install +``` + +Follow the prompts - in the end, the installer will also tell you your admin panel's URL, where you should go and login. + +> **NOTE:** When the installer asks you if you would like to create an admin user, Backpack assumes that you are using the default user structure with `name, email and password` fields. If that's not the case, please reply **NO** to that question and manually create your admin user. + +> **NOTE:** The installation command is interactive - it will ask you questions. You can bypass the questions by adding the `--no-interaction` argument to the install command. + +### Configure + +In most cases, it's a good idea to look at the configuration files and make the admin panel your own: +- You should change the config values in ```config/backpack/base.php``` to make the admin panel your own. +- You can also change ```config/backpack/ui.php``` to change the UI; Backpack is white label, so you can change configs to make it your own: project name, logo, developer name etc. +- By default all users are considered admins; If that's not what you want in your application (you have both users and admins), please: + - Change ```app/Http/Middleware/CheckIfAdmin.php```, particularly ```checkIfUserIsAdmin($user)```, to make sure you only allow admins to access the admin panel; + - Change ```app/Providers/RouteServiceProvider::HOME```, which will send logged in (but not admin) users to `/home`, to something that works for your app; +- If your User model has been moved from the default ```App\Models\User.php```, please change ```config/backpack/base.php``` to use the correct user model under the ```user_model_fqn``` config key; + +### Create your Eloquent Models + +Backpack assumes you already have your Eloquent Models properly set up. If you don't, **consider using something to quickly generate Migrations & Models**. You can use anything you want, but here are the options we recommend: + +- a) Generate from a **web interface** - [Backpack Devtools](https://backpackforlaravel.com/products/devtools) - premium product, paid separately. A simple GUI to quickly generate Migrations, Models, Factories, Seeders and CRUDs, right from your browser. Works well for entities of all sizes. + +- b) Generate from the **command-line** - [Laracasts Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) - free & open-source. Adds a new artisan command so that you can do `php artisan make:migration:schema create_users_table --schema="username:string, email:string:unique"`. Works well for smaller entities. + +- c) Generate from a **YAML file** - [LaravelShift's Blueprint](https://blueprint.laravelshift.com/) - free & open-source. Enables you to create a `draft.yml` file in your repo, where you can specify the column using their custom YAML syntax. Works well for small & medium entities. + +### Create your CRUDs + +For each Eloquent model you want to have an admin panel, run: + +```bash +php artisan backpack:crud {model} # use the model name, in singular +``` + +Alternatively, you can generate CRUDs for all Eloquent models, by running: + +```bash +php artisan backpack:build +``` + +Then go through each CRUD file (Controller, Request, Route Item, Menu Item) and customize as you fit. If you don't know what those are, and how you can customize them... please go through our [Getting Started](https://backpackforlaravel.com/docs/5.x/introduction#how-to-start) section, it's important. At the very least, read our [Crash Course](https://backpackforlaravel.com/docs/5.x/crud-tutorial). + + +### Install Add-ons + +In case you want to add extra functionality that's already been built, check out [the installation steps for the add-ons we've developed](/docs/{{version}}/install-optionals). + + +## Frequently Asked Questions + +- **Error: The process X exceeded the timeout of 60 seconds.** It might mean GitHub or Packagist is unavailable at the moment. This usually doesn't last for more than a few minutes, so you can run ```php artisan backpack:install --timeout=600``` to increase the timeout to 10 minutes. If this doesn't work either, take a look behind the scenes with ```php artisan backpack:install --timeout=600 --debug```; + +- **Error: SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long**. Your MySQL version might be a bit old. Please [apply this quick fix](https://laravel-news.com/laravel-5-4-key-too-long-error), then run ```php artisan migrate:fresh```. + +- **Any other installation error?** If you can't install Backpack because of a different error, you can [try the manual installation process](/docs/{{version}}/crud-how-to#how-to-manually-install-backpack), which you can tweak to your needs. diff --git a/7.x-dev/introduction.md b/7.x-dev/introduction.md new file mode 100644 index 00000000..02ca3170 --- /dev/null +++ b/7.x-dev/introduction.md @@ -0,0 +1,114 @@ +# Introduction + +--- + +Backpack is a collection of Laravel packages that help you **build custom administration panels**, for anything from presentation websites to complex web applications. You can install them on top of existing Laravel installations _or_ fresh projects. + +In a nutshell: + +- UI - Backpack will provide you with a _visual interface_ for the admin panel (the HTML, the CSS, the JS), authentication functionality & global bubble notifications; you can choose from one of the 3 themes we have developed (powered by Tabler, CoreUI v4 or CoreUI v2), a third-party theme or create your own theme; the enormous advantage of using a Bootstrap-based HTML theme is that when you need custom pages, you already have the HTML blocks for the UI, you don't have to design them yourself; +- CRUDs - Backpack will also help you build _sections where your admins can manipulate entries for Eloquent models_; we call them _CRUD Panels_ after the most basic operations: Create, Read, Update & Delete; after [understanding Backpack](/docs/{{version}}/getting-started-basics), you'll be able to create a CRUD panel in a few minutes per model: + +![](https://user-images.githubusercontent.com/1032474/86720524-c5a1d480-c02d-11ea-87ed-d03b0197eb25.gif) + +If you already have your Eloquent models, generating Backpack CRUDs is as simple as: +```bash +# ------------------------------- +# For one specific Eloquent Model +# ------------------------------- +# Create a Model, Request, Controller, Route and sidebar item, so +# that one Eloquent model you specify has an admin panel. + +php artisan backpack:crud tag # use singular, not plural (like the Model name) + +# ----------------------- +# For all Eloquent Models +# ----------------------- +# Create a Model, Request, Controller, Route and sidebar item for +# all Eloquent models that don't already have one. + +php artisan backpack:build +``` + +If you have NOT created your Eloquent models yet, you can use whatever you want for that. We recommend: +- FREE - [`laravel-shift/blueprint`](https://github.com/laravel-shift/blueprint) as the best **YAML-based tool** for this; +- PAID - [`backpack/devtools`](https://backpackforlaravel.com/products/devtools) as the best **web interface** for this; it makes it dead simple to create Eloquent models, from the comfort of your web browser; + +--- + + +## How to Start + +We heavily recommend you spend a little time to understand Backpack, and only afterwards install and use it. Fortunately, it's super simple to get started. Using any of the options below will get you to a point where you can use Backpack in your projects: +- **[Video Course](/docs/{{version}}/getting-started-videos)** - 59 minutes +- **[Text Course](/docs/{{version}}/getting-started-basics)** - 20 minutes + + +
+ Video Course + Text Course + +--- + + +## Need to Know + + +### Requirements + + - Laravel 10.x or 11.x + - MySQL / PostgreSQL / SQLite / SQL Server + + +### How does it look? + +**Take a look at our [live demo](https://demo.backpackforlaravel.com/admin/login).** If you've purchased ["Everything"](https://backpackforlaravel.com/pricing) you can even [install the demo](/docs/{{version}}/demo) and fiddle with the code. Otherwise, you can just start a new Laravel project, [install Backpack\CRUD](/docs/{{version}}/installation) on top, and [follow our text course](/docs/{{version}}/getting-started-basics) to create a few CRUDs. + + +### Security + +Backpack has never had a critical vulnerability/hack. But there _have_ been important security updates for dependencies (including Laravel). Please [register using Github](/auth/github) or [subscribe to our twice-a-year newsletter](https://backpackforlaravel.com/newsletter), so we can reach you in case your admin panel becomes vulnerable in any way. + + +### Maintenance + +Backpack v6 is the current version and is actively maintained by the Backpack team, with the help of a wonderful community of Backpack veterans. [See all contributors](https://github.com/Laravel-Backpack/CRUD/graphs/contributors). + + +### License + +Backpack is open-core: +- **Backpack CRUD is free & open-source, licensed under the [MIT License](https://github.com/Laravel-Backpack/CRUD/blob/main/LICENSE.md)**; it is perfect if you're building a simple admin panel - it's packed with features! it's also perfect if you're building an open-source project, the permissive license allows you to do whatever you want; +- **Backpack PRO is a paid, closed-source add-on, licensed under our [EULA](https://backpackforlaravel.com/eula)**; [PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) adds additional functionality to CRUD, that will be useful when your admin panel grows (see our [FREE vs PRO comparison](https://backpackforlaravel.com/docs/6.x/features-free-vs-paid)); +- Of the other add-ons we've created, some are FREE and some are PAID; please see [our add-ons list](https://backpackforlaravel.test/docs/6.x/add-ons-official) for more info; + +[Our documentation](https://backpackforlaravel.com/docs) covers both CRUD and PRO, with all the PRO features clearly labeled PRO. + + + +### Versioning, Updates and Upgrades + +Starting with the previous version, all our packages follow [semantic versioning](https://semver.org/). Here's what `major.minor.patch` (e.g. `7.0.1`) means for us: +- `major` - breaking changes, major new features, complete rewrites; released **once a year**, in February; it adds features that were previously impossible and upgrades our dependencies; upgrading is done by following our clear and detailed upgrade guides; +- `minor` - new features, released in backwards-compatible ways; **every few months**; update takes seconds; +- `patch` - bug fixes & small non-breaking changes; historically **every week**; update takes seconds; + +When we release a new Backpack\CRUD version, all paid add-ons receive support for it the same day. + +When you buy a premium Backpack add-on, you get access to not only _updates_, but also _upgrades_ (for 12 months), that means that **any time you buy a Backpack add-on, it is very likely that you're not only buying the _current_ version** (`v7` at the moment), **but also the upgrade to the _next version_** (`v8` for example). + + +### Add-ons + +Backpack's core is open-source and free (Backpack\CRUD). FREE + +The reason we've been able to build and maintain Backpack since 2016 is that Laravel professionals have supported us, by buying our paid products. As of 2022, these are all Backpack add-ons, which we highly recommend: +- [Backpack PRO](/products/pro-for-unlimited-projects) - a crazy amount of added features; PAID +- [Backpack DevTools](/products/devtools) - a developer UI for generating migrations, models and CRUDs; PAID +- [Backpack FigmaTemplate](/products/figma-template) - quickly create designs and mockups, using Backpack's design; PAID +- [Backpack EditableColumns](/products/editable-columns) - let your admins do quick edits, right in the table view; PAID + + +In addition to our open-source core and our closed-source add-ons, there are a few other add-ons you might want to take a look at, that treat common use cases. Some have been developed by our core team, some by our wonderful community. You can just install interfaces to manage [site-wide settings](https://github.com/Laravel-Backpack/Settings), [the default Laravel users table](https://github.com/eduardoarandah/UserManager), [users, groups & permissions](https://github.com/Laravel-Backpack/PermissionManager), [content for custom pages, using page templates](https://github.com/Laravel-Backpack/PageManager), [news articles, categories and tags](https://github.com/Laravel-Backpack/NewsCRUD), etc. FREE + +For more information, please see [our add-ons page](/addons). diff --git a/7.x-dev/release-notes.md b/7.x-dev/release-notes.md new file mode 100644 index 00000000..3df11af9 --- /dev/null +++ b/7.x-dev/release-notes.md @@ -0,0 +1,68 @@ +# Release Notes + +--- + +**Planned launch date:** Dec 4th, 2024 + +For the past 2 years, we've done our very best to make all changes to Backpack in a backwards-compatible way. To not push a new version, because we know it's a pain in the bee's hind to upgrade stuff. But... it's time for a new release. Have no fear though, we've made this super easy for you. + +Backpack v7 is a maintenance release. Yes, it does have a few breaking changes, but they will not impact 99.9% of projects. But that doesn't mean you shouldn't follow the upgrade guide! Please do - read it from top to bottom and make sure none of the changes impact you. + +Here are the BIG things Backpack v7 brings to the table and why you should upgrade from [Backpack v6](/docs/6.x) to v7. But first... we should give credit where credit is due. **Big BIG thanks to**: +- **[Pedro Martins](https://github.com/pxpm)** for x; +- **[Jorge Castro](https://github.com/jcastroa87)** for y; +- **[Karan Datwani](https://github.com/karandatwani92)** for z; +- **[Cristian Tabacitu](https://github.com/tabacitu)** for t; +- **our paying customers**, who have made all of this possible by supporting our work 🙏 + +Together, our team has put in an incredible amount of work to make v7 what it is - more than XXX commits, across YYY months, all while still maintaining, bug fixing and improving v6. Again, big thanks to everybody who has helped made this happen 🙏 + + +## Added + +### CRUD Lifecycle Hooks + +// TODO + +### Two-Factor Authentication + +// TODO + +### Re-Usable Filters + +// just like your air purifier +// TODO + +### Browser Tests + +// TODO + + + +## Changed + +### Uploaders + +// TODO + +### Moved TinyMCE and CKEditor fields & columns + +// TODO + +### Basset + +// TODO + +### Parent Theme + +// TODO + + +## Removed + +- Support for Laravel 10?! 👀 +- Support for PHP lower than 8.2? + +--- + +If you like what you see, please help us out - share what you like on social media or tell a friend. To get all of the features above (and a lot more), please [follow the upgrade guide](/docs/{{version}}/upgrade-guide). diff --git a/7.x-dev/theme-tabler.md b/7.x-dev/theme-tabler.md new file mode 100644 index 00000000..f3e9dcab --- /dev/null +++ b/7.x-dev/theme-tabler.md @@ -0,0 +1,38 @@ +# Theme Tabler + + +### About + +For more information about installation and/or configuration steps, see [backpack/theme-tabler](https://github.com/Laravel-Backpack/theme-tabler) on Github. + + +## Components + + +### Menu Dropdown Column + +In addition to regular menu components provided by Backpack [Menu Dropdown and Menu Dropdown Item](https://backpackforlaravel.com/docs/base-components#menu-dropdown-and-menu-dropdown-item), Tabler theme provides this new component which is used to create side by side menus on horizontal layouts. + +#### Requirements +- Require `Backpack/CRUD:6.6.4` or higher +- Require `Backpack/theme-tabler:1.2.1` or higher + +#### Usage +In your parent dropdown item, enable the feature by setting `:withColumns="true"` and then use `x-theme-tabler::menu-dropdown-column` component to wrap each set of menu items. See the example below: + +```html + + + + + + + + + + + + +``` + +![tabler side by side menus](https://github.com/Laravel-Backpack/docs/assets/7188159/2c65e523-a545-486a-b7b0-cbd9f92ee273) diff --git a/7.x-dev/themes/theme-tabler.md b/7.x-dev/themes/theme-tabler.md new file mode 100644 index 00000000..943e68ef --- /dev/null +++ b/7.x-dev/themes/theme-tabler.md @@ -0,0 +1,33 @@ +# Theme Tabler + + +### About + +For more information about installation and/or configuration steps, see [`backpack/theme-tabler`](https://github.com/Laravel-Backpack/theme-tabler) on Github. + + +## Components + + +### View Components + +`MenuDropdownMenu` - In addition to regular menu components provided by backpack [Menu Dropdown and Menu Dropdown Item](https://backpackforlaravel.com/docs/base-components#menu-dropdown-and-menu-dropdown-item), Tabler theme provides a new component `Menu Dropdown Menu` which is used to create side by side menus on horizontal layouts. +> Backpack/CRUD `v6.6.4` or higher. +> Backpack/theme-tabler `v1.2.1` or higher + +![tabler side by side menus](https://github.com/Laravel-Backpack/docs/assets/7188159/2c65e523-a545-486a-b7b0-cbd9f92ee273) + +```html + + + + + + + + + + + + +``` diff --git a/7.x-dev/tutorials.md b/7.x-dev/tutorials.md new file mode 100644 index 00000000..0b144afc --- /dev/null +++ b/7.x-dev/tutorials.md @@ -0,0 +1,52 @@ +# Tutorials for the admin UI + +--- + + +## Tutorials +- [How to create an AJAX Operation with Quick Button.](https://backpackforlaravel.com/articles/tutorials/how-to-create-an-ajax-operation-with-quick-button) +- [Edit Laravel Translations from Your Admin Panel.](https://backpackforlaravel.com/articles/tutorials/new-addon-edit-laravel-translations-from-your-admin-panel) +- [Language switcher UI for your Laravel App](https://backpackforlaravel.com/articles/tutorials/language-switcher-ui-for-your-laravel-app) +- [Set a larger layout for your large set of menu items.](https://backpackforlaravel.com/articles/tutorials/new-in-v6-a-larger-layout-for-your-large-set-of-menu-items) +- [Setup Calendar view on your Laravel projects](https://backpackforlaravel.com/articles/tutorials/new-in-v6-setup-calendar-view-on-your-laravel-projects) +- [How to setup multiple views on List operation](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-setup-multiple-views-on-list-operation) +- [How to access CRUD fields via javascript to manipulate Form](https://backpackforlaravel.com/articles/tutorials/how-to-access-crud-fields-via-javascript-to-manipulate-form) +- [How to configure User Access Control and Permissions in 10 minutes](https://backpackforlaravel.com/articles/tutorials/guide-on-access-control-for-your-admin-panel) +- [Granular User Access, using Custom Closures](https://backpackforlaravel.com/articles/tutorials/new-in-v6-granular-user-access-using-custom-closures) +- [Excel Imports for Backpack!](https://backpackforlaravel.com/articles/tutorials/cheers-to-lewis-and-excel-imports-for-backpack) +- [How to Build an Image Slider CRUD - Backpack Basics](https://backpackforlaravel.com/articles/tutorials/how-to-build-an-image-slider-crud-backpack-basics) +- [How I created a custom “webcam” field for Backpack.](https://backpackforlaravel.com/articles/tutorials/how-i-created-a-custom-webcam-field-for-backpack) +- [How to create a Trash/Deleted section in Backpack CRUD](https://backpackforlaravel.com/articles/tutorials/how-to-create-a-trash-deleted-section-in-backpack-crud) +- [How to build a theme-picker for Backpack admin panel.](https://backpackforlaravel.com/articles/tutorials/how-to-build-a-theme-picker-for-backpack-admin-panel) +- [How to make custom JavaScript work with Backpack's List Operation](https://backpackforlaravel.com/articles/tutorials/how-to-make-custom-javascript-work-with-backpack-s-list-operation) +- [Easy Column Links using linkTo()](https://backpackforlaravel.com/articles/tutorials/easy-column-links-using-linkto) +- [CSS Hooks - Easy CSS Customization for Your Backpack CRUD Panels](https://backpackforlaravel.com/articles/tutorials/css-hooks-easy-css-customization-for-your-backpack-crud-panels) +- [How to use Spatie Media Library in Backpack](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-use-spatie-media-library-in-backpack) +- [How to change layout for panel & auth views](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-change-layout-for-panel-auth-views) +- [How to enable dark-mode on Backpack.](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-enable-dark-mode-on-backpack) +- [How to create your own custom theme to Backpack v6](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-create-your-own-custom-theme-to-backpack-v6) +- [How to change themes in Backpack v6](https://backpackforlaravel.com/articles/tutorials/new-in-v6-how-to-change-themes-in-backpack-v6) +- [How to add custom sections to CRUD tables and forms](https://backpackforlaravel.com/articles/tutorials/how-to-add-custom-sections-to-crud-tables-and-forms) +- [How to create a custom operation that uses Backpack fields](https://backpackforlaravel.com/articles/tutorials/how-to-create-a-custom-operation-that-uses-backpack-fields) +- [How to create a Backpack add-on with a custom Operation](https://backpackforlaravel.com/articles/tutorials/how-to-create-a-backpack-add-on-with-a-custom-operation) +- [How to use a custom operation inside PermissionManager](https://backpackforlaravel.com/articles/tutorials/how-to-use-a-custom-operation-inside-permissionmanager) +- [How to create a custom operation with a form](https://backpackforlaravel.com/articles/tutorials/how-to-create-a-custom-operation-with-a-form) +- [Editable Columns](https://backpackforlaravel.com/articles/tutorials/new-addon-editable-columns) +- [How to create a Print operation](https://backpackforlaravel.com/articles/tutorials/how-to-create-a-print-operation) +- [How to Add Impersonate Functionality to Your Backpack v4 Admin Panel](https://backpackforlaravel.com/articles/tutorials/how-to-add-impersonate-functionality-to-your-admin-panel) +- [Nested resources in Backpack CRUD](https://backpackforlaravel.com/articles/tutorials/nested-resources-in-backpack-crud) + + + +## Submit Your Tutorial + +We are always excited to see new contributions from our community. If you have created a tutorial that you think would benefit others, we'd love to hear about it! + +To submit your tutorial: + +1. Make sure your tutorial is well-documented and easy to follow. +2. It includes clear, practical examples and relevant code snippets. +3. Include screenshots or video to the tutorial if applicable for better understanding. +4. Send your tutorial to hello@backpackforlaravel.com with the subject line "Tutorial Submission". + +We will review your submission and get back to you as soon as possible. Thank you for helping us build a robust resource for the Backpack for Laravel community! \ No newline at end of file diff --git a/7.x-dev/upgrade-guide.md b/7.x-dev/upgrade-guide.md new file mode 100644 index 00000000..00ee27a4 --- /dev/null +++ b/7.x-dev/upgrade-guide.md @@ -0,0 +1,143 @@ +# Upgrade Guide + +--- + +This will guide you to upgrade from Backpack v6 to v7. The steps are color-coded by the probability that you will need it for your application: High, Medium and Low. **At the very least, please read what's in bold**. + + +## Requirements + +Please make sure your project respects the requirements below, before you start the upgrade process. You can check with ```php artisan backpack:version```: + +- PHP 8.1+ +- Laravel 11.x +- Backpack\CRUD 6.x +- 5-10 minutes (for most projects) + +**If you're running Backpack version 3.x-5.x, please follow ALL other upgrade guides first, to incrementally get to use Backpack v6**. Test that your app works well with each version, after each upgrade. Only _afterwards_ can you follow this guide, to upgrade from v5 to v6. Previous upgrade guides: +- [upgrade from 5.x to 6.x](https://backpackforlaravel.com/docs/6.x/upgrade-guide); +- [upgrade from 4.1 to 5.x](https://backpackforlaravel.com/docs/5.x/upgrade-guide); +- [upgrade from 4.0 to 4.1](https://backpackforlaravel.com/docs/4.1/upgrade-guide); +- [upgrade from 3.6 to 4.0](https://backpackforlaravel.com/docs/4.0/upgrade-guide); +- [upgrade from 3.5 to 3.6](https://backpackforlaravel.com/docs/3.6/upgrade-guide); +- [upgrade from 3.4 to 3.5](https://backpackforlaravel.com/docs/3.5/upgrade-guide); +- [upgrade from 3.3 to 3.4](https://backpackforlaravel.com/docs/3.4/upgrade-guide); + + +## Upgrade Steps + +Step 0. **[Upgrade to Laravel 11](https://laravel.com/docs/11.x/upgrade) if you don't use it yet, then test to confirm your app is working fine.** + + +### Composer + +Step 1. Update your ```composer.json``` file to require: + +``` + "backpack/crud": "v7-dev", +``` + +Step 2. Bump the version of any first-party Backpack add-ons you have installed (eg. `backpack/pro`, `backpack/editable-columns` etc.) to the versions that support Backpack v6. For 3rd-party add-ons, please check each add-on's Github page. Here's a quick list of 1st party packages and versions: + +```js + "backpack/crud": "v7-dev", + "backpack/pro": "v3-dev", + "backpack/filemanager": "^3.0", + "backpack/theme-coreuiv2": "^1.0", + "backpack/theme-coreuiv4": "^1.0", + "backpack/theme-tabler": "^1.0", + "backpack/logmanager": "^5.0", + "backpack/settings": "^3.1", + "backpack/newscrud": "^5.0", + "backpack/permissionmanager": "^7.0", + "backpack/pagemanager": "^3.2", + "backpack/menucrud": "^4.0", + "backpack/backupmanager": "^5.0", + "backpack/editable-columns": "^3.0", + "backpack/revise-operation": "^2.0", + "backpack/medialibrary-uploaders": "^1.0", + "backpack/devtools": "^2.0", +``` + +Step 3. Let's get the latest Backpack and install it. If you get any conflicts with **Backpack 1st party add-ons**, most of the time you just need to move one version up, eg: from `backpack/menucrud: ^3.0` to `backpack/menucrud: ^4.0`. See the step above again. Please run: + +```bash +composer update + +# before calling the next command make sure you have no pending migrations with `php artisan migrate:status` +# and that your webserver is running and accessible in the URL you defined in .env `APP_URL`. +php artisan backpack:upgrade +``` + +// TODO: actually create an upgrade command that does only the things we need from the `install` command. + + +### Models + +No changes needed. + + +### Form Requests + +No changes needed. + + +### Routes + +No changes needed. + + +### Config + +No changes needed. + + +### CrudControllers + +No changes needed. + + +### CSS & JS Assets + +No changes needed. + + +### Views + +No changes needed. + + +### Security + +No changes needed. + + +### Cache + +Step xx. Clear your app's cache: +``` +php artisan config:clear +php artisan cache:clear +php artisan view:clear +``` + +If the table view still looks wonky (search bar out of place, big + instead of ellipsis), then do a hard-reload in your browser (Cmd+Shift+R or Ctrl+Shift+F5) to purge the browser cache too. + +--- + +Step yy. If your pages are slow to load, that's because Basset caching the assets as you load the pages, so your first pageload will be quite slow. If you find that annoying, run `php artisan basset:cache` to cache all CSS and JS assets. Alternatively, if you want Basset NOT to run because you're making changes to CSS and JS files, you can add `BASSET_DEV_MODE=true` to your `.ENV` file. + +--- + + +### Upgrade Add-ons + +For any addons you might have upgraded, please double-check if they have an upgrade guide. For example: +- Xx package has the upgrade guide here; +- Yy package has the upgrade guide here; + +--- + +**You're done! Good job.** Thank you for taking the time to upgrade. Now you can: +- thoroughly test your application and your admin panel; +- start using the [new features in Backpack v7](/docs/{{version}}/release-notes);