diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml new file mode 100644 index 00000000..09987553 --- /dev/null +++ b/.github/workflows/add-to-project.yml @@ -0,0 +1,20 @@ +name: Add new bugs & PRs to This Week project + +on: + issues: + types: + - opened + - transferred + pull_request: + types: + - opened + +jobs: + add-to-project: + name: Add new bugs and PRs to This Week project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/Laravel-Backpack/projects/13 + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/3.3/introduction.md b/3.3/introduction.md index 5dd9f6a6..203a2293 100644 --- a/3.3/introduction.md +++ b/3.3/introduction.md @@ -1,3 +1,5 @@ -### Moved +### Unavailable online -For Backpack v3.3 documentation, please head over to [our old readme.io documentation](https://laravel-backpack.readme.io/v3.3/docs). \ No newline at end of file +For Backpack v3.3 documentation, please [download our old documentation](https://drive.google.com/open?id=1f55_5DEHnyGpiRBbCSZzvMTG-X1AG9EV). + +This is a full archive of our old documentation, hosted on readme.io. We're no longer using Readme.io for our documentation, but [our own github repo](https://github.com/laravel-backpack/docs). We've had to deleted our Readme.io account, and remove the results from Google search, because people got to the old docs (which were inaccurate and incomplete) instead of our current docs. diff --git a/3.4/add-ons-community.md b/3.4/add-ons-community.md index 6178e504..6a4c9b22 100644 --- a/3.4/add-ons-community.md +++ b/3.4/add-ons-community.md @@ -14,4 +14,5 @@ We've been blessed with a wonderful, supportive community, where developers help | [novius/laravel-backpack-translation-manager](https://github.com/novius/laravel-backpack-translation-manager) | manage translations stored in the database | - | | [updivision/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) | | [webfactor/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) | -| [seandowney/laravel-backpack-gallery-crud](https://github.com/seandowney/laravel-backpack-gallery-crud) | manage photo galleries | [MIT](https://github.com/seandowney/laravel-backpack-gallery-crud/blob/master/LICENSE.md) | +| [seandowney/laravel-backpack-gallery-crud](https://gitlab.com/seandowney/laravel-backpack-gallery-crud) | Manage photo galleries | [MIT](https://github.com/seandowney/laravel-backpack-gallery-crud/blob/master/LICENSE.md) | +| [seandowney/laravel-backpack-store-crud](https://gitlab.com/seandowney/laravel-backpack-store-crud) | Manage a shop with the beginnings of a Frontend - Categories, Products, Price Options and Groups, Delivery Options and Groups and Orders. Not tested above Crud 3.4 | [MIT](https://gitlab.com/seandowney/laravel-backpack-store-crud/blob/master/LICENSE.md) | diff --git a/3.4/base-how-to.md b/3.4/base-how-to.md index 11e9efa4..d0dae179 100644 --- a/3.4/base-how-to.md +++ b/3.4/base-how-to.md @@ -5,7 +5,7 @@ ## Customize the menu or sidebar -During installation, Backpack publishes a few files in you ```resources/views/vendor/backpack/base``` folder. In there, you'll also find ```inc/sidebar_content.php``` and ```inc/menu.php```. Change those files as you please. +During installation, Backpack publishes a few files in your ```resources/views/vendor/backpack/base``` folder. In there, you'll also find ```inc/sidebar_content.php``` and ```inc/menu.php```. Change those files as you please. ## Customize the dashboard @@ -78,7 +78,7 @@ In ```config/backpack/base.php``` you'll find these configuration options: 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::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'); diff --git a/3.4/crud-basics.md b/3.4/crud-basics.md index ab869a44..a629169e 100644 --- a/3.4/crud-basics.md +++ b/3.4/crud-basics.md @@ -2,7 +2,7 @@ --- -Backpack\CRUD provides a fast way to build admininistration 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.** +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 @@ -43,4 +43,4 @@ For a ```Tag``` entity, your CRUD Panel would consist of: - a route inside ```routes/backpack/custom.php```; - your existing model (```app/Models/Tag.php```); -To further your understading of how a CRUD Panel works, [read more about this example in the tutorial](/docs/{{version}}/crud-tutorial). +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/3.4/crud-buttons.md b/3.4/crud-buttons.md index e7a48e61..273d6e08 100644 --- a/3.4/crud-buttons.md +++ b/3.4/crud-buttons.md @@ -89,7 +89,7 @@ Let's say we want to create a simple ```moderate.blade.php``` button. This butto Route::get('user/{id}/moderate', 'UserCrudController@ban'); ``` -- We can now create add a ```moderate()``` method to our ```UserCrudController```, which would moderate the user, and redirect back. +- We can now add a ```moderate()``` method to our ```UserCrudController```, which would moderate the user, and redirect back. ```php public function moderate() { diff --git a/3.4/crud-cheat-sheet.md b/3.4/crud-cheat-sheet.md index 3a2c9acb..f9121c59 100644 --- a/3.4/crud-cheat-sheet.md +++ b/3.4/crud-cheat-sheet.md @@ -63,7 +63,7 @@ $this->crud->filters(); // gets all the filters #### Details Row ```php -// Shows a + sign next to each table row, so that the user can expand that row and reveal details. You are responsible for creting the view with those details. +// 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. $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 @@ -107,7 +107,7 @@ $this->crud->setActionsColumnPriority(10000); #### Custom / Advanced Queries ```php -// Change what entries are show in the table view. +// 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. $this->crud->addClause('active'); // apply local scope diff --git a/3.4/crud-columns.md b/3.4/crud-columns.md index 9a58a23c..1171aba8 100644 --- a/3.4/crud-columns.md +++ b/3.4/crud-columns.md @@ -174,7 +174,9 @@ Show custom HTML based on a closure you specify in your EntityCrudController. Pl ### date -The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted as date in the model or not: +The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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 [ @@ -189,7 +191,10 @@ The date column will show a localized date in the default date format (as specif ### datetime -The date column will show a localized datetime in the default datetime format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted as datetime in the model or not: +The date column will show a localized datetime in the default datetime format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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 [ @@ -236,11 +241,11 @@ The model_function column will output a function on your main model. Its definit 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.''; - } + return ''.$this->slug.''; +} ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### model_function_attribute @@ -258,7 +263,7 @@ If the function you're trying to use returns an object, not a string, you can us ], ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### multidimensional_array @@ -349,7 +354,7 @@ The text column will just output the text value of a db column (or model attribu ], ``` -**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 attibute in the ```name```, using dot notation: +**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', @@ -375,7 +380,7 @@ The select column will output its connected entity. Its name and definition is t ], ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### select_from_array @@ -388,7 +393,7 @@ Show a particular text depending on the value of the attribute. 'name' => 'status', 'label' => "Status", 'type' => 'select_from_array', - 'options' => [‘draft’ => ‘Draft (invisible)’, ‘published’ => ‘Published (visible)’], + 'options' => ['draft' => 'Draft (invisible)', 'published' => 'Published (visible)'], ], ``` @@ -409,7 +414,7 @@ The select_multiple column will output a comma separated list of its connected e ], ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### table @@ -616,4 +621,4 @@ You can make the last column be less important (and hide) by giving it an unreas $this->crud->setActionsColumnPriority(10000); ``` ->Note that repsonsive tables adopt special behavior if the table is not able to show all columns. This includes displaying a vertical elipsis 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. +>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. diff --git a/3.4/crud-fields.md b/3.4/crud-fields.md index 50bdf78b..399972a5 100644 --- a/3.4/crud-fields.md +++ b/3.4/crud-fields.md @@ -7,7 +7,7 @@ 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. +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). @@ -96,7 +96,7 @@ $this->crud->addField($field_definition_array)->afterField('name'); #### Fake Fields (all stored as JSON in the database) -In case you want to store insignificant information for an entry, that don't need a database column, you can add any number of Fake Fields, and all their information will be store inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. +In case you want to store insignificant information for an entry that doesn't need a database column, you can add any number of Fake Fields, and all their information will be stored inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. **Step 1.** Use the fake attribute on your field: ```php @@ -104,7 +104,7 @@ In case you want to store insignificant information for an entry, that don't nee '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 + '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 ], ``` @@ -138,7 +138,7 @@ Example: ], ``` -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: +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"} @@ -220,7 +220,7 @@ Onclick preview: ### browse_multiple -Open elFinder and select multiple file from there. +Open elFinder and select multiple files from there. ```php [ // Browse multiple @@ -617,7 +617,7 @@ $this->crud->addField([ // image 'type' => 'image', 'upload' => true, 'crop' => true, // set to true to allow cropping, false to disable - 'aspect_ratio' => 1, // ommit or set to 0 to allow any aspect ratio + 'aspect_ratio' => 1, // omit or set to 0 to allow any aspect ratio // 'disk' => 's3_bucket', // in case you need to show images from a different disk // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; ]); @@ -720,14 +720,14 @@ Input preview: ### page_or_link -Select an existing page from PageManager or an internal or external link. It’s used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: +Select an existing page from PageManager or an internal or external link. It's used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: ```php [ // PageOrLink 'name' => 'type', 'label' => "Type", 'type' => 'page_or_link', 'page_model' => '\Backpack\PageManager\app\Models\Page' -] +], ``` Input preview: @@ -765,7 +765,7 @@ Show radios according to an associative array you give the input and let the use ], // optional //'inline' => false, // show the radios all on the same line? -] +], ``` Input preview: @@ -801,7 +801,7 @@ Your relationships should already be defined on your models. 'entity' => 'category', // 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 -] +], ``` Input preview: @@ -822,7 +822,7 @@ Your relationships should already be defined on your models. 'entity' => 'category', // 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 -] +], ``` Input preview: @@ -844,7 +844,7 @@ Your relationships should already be defined on your models. 'attribute' => 'name', // foreign key attribute that is shown to user 'model' => "App\Models\Tag", // foreign key model 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? -] +], ``` Input preview: @@ -856,7 +856,7 @@ Input preview: [Works just like the SELECT field, but prettier] -Show a Select2 with the names of the connected entity and let the user select any number of them. +Shows a Select2 with the names of the connected entity and let the user select any number of them. Your relationships should already be defined on your models. ```php @@ -869,7 +869,7 @@ Your relationships should already be defined on your models. 'model' => "App\Models\Tag", // foreign key model 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? // 'select_all' => true, // show Select All and Clear buttons? -] +], ``` Input preview: @@ -886,7 +886,7 @@ Display a select with the values you want: 'name' => 'template', 'label' => "Template", 'type' => 'select_from_array', - 'options' => [‘one’ => ‘One’, ‘two’ => ‘Two’], + 'options' => ['one' => 'One', 'two' => 'Two'], 'allows_null' => false, 'default' => 'one', // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; @@ -907,7 +907,7 @@ Display a select2 with the values you want: 'name' => 'template', 'label' => "Template", 'type' => 'select2_from_array', - 'options' => [‘one’ => ‘One’, ‘two’ => ‘Two’], + 'options' => ['one' => 'One', 'two' => 'Two'], 'allows_null' => false, 'default' => 'one', // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; @@ -1209,7 +1209,7 @@ Input preview: 'label' => 'Image', 'type' => 'upload', 'upload' => true, - 'disk' => 'uploads' // if you store files in the /public folder, please ommit this; if you store them in /storage or S3, please specify it; + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; ], ``` @@ -1276,7 +1276,7 @@ Shows a multiple file input to the user and stores the values as a JSON array in 'label' => 'Photos', 'type' => 'upload_multiple', 'upload' => true, - 'disk' => 'uploads' // if you store files in the /public folder, please ommit this; if you store them in /storage or S3, please specify it; + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; ], ``` @@ -1374,10 +1374,10 @@ 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’ + title: 'my video title', + image: 'https://provider.com/image.jpg', + url: 'http://provider.com/video', + provider: 'youtube' } ``` @@ -1430,11 +1430,11 @@ Show a wysiwyg (CKEditor) to the user. ## 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. +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:crud:publish fields/field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:crud:publish fields/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 crate a custom field type for your use case, instead of overwriting default field types. +>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 @@ -1450,7 +1450,7 @@ Your field definition will be something like: '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: diff --git a/3.4/crud-filters.md b/3.4/crud-filters.md index 1feb07b6..21bed0d1 100644 --- a/3.4/crud-filters.md +++ b/3.4/crud-filters.md @@ -55,7 +55,7 @@ function() { // if the filter is active (the GET parameter "draft" exits) > - 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->request```; > - 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 convers the first "orWhere" into a "where"; +> - 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 @@ -80,7 +80,7 @@ function() { // if the filter is active ### Text -Shows a text input. Most useful for letting the user filter through information that not shown as a column in the CRUD table - otherwise they could just use the DataTables search field. +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/filters/text.png) @@ -104,15 +104,15 @@ Show a datepicker. The user can select one day. ![Backpack CRUD Date Filter](https://backpackforlaravel.com/uploads/docs/filters/date.png) ```php - $this->crud->addFilter([ // date filter - 'type' => 'date', - 'name' => 'date', - 'label'=> 'Date' - ], - false, - function($value) { // if the filter is active, apply these constraints - // $this->crud->addClause('where', 'date', $value); - }); +$this->crud->addFilter([ // date filter + 'type' => 'date', + 'name' => 'date', + 'label'=> 'Date' +], +false, +function($value) { // if the filter is active, apply these constraints + // $this->crud->addClause('where', 'date', $value); +}); ``` @@ -123,17 +123,17 @@ 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/filters/date_range.png) ```php - $this->crud->addFilter([ // daterange filter - 'type' => 'date_range', - 'name' => 'from_to', - 'label'=> 'Date range' - ], - false, - function($value) { // if the filter is active, apply these constraints - // $dates = json_decode($value); - // $this->crud->addClause('where', 'date', '>=', $dates->from); - // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); - }); +$this->crud->addFilter([ // daterange filter + 'type' => 'date_range', + 'name' => 'from_to', + 'label'=> 'Date range' +], +false, +function($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); ``` diff --git a/3.4/crud-how-to.md b/3.4/crud-how-to.md index 84a642a4..c5290169 100644 --- a/3.4/crud-how-to.md +++ b/3.4/crud-how-to.md @@ -36,7 +36,7 @@ If you don't find one there, you can create one, and Backpack will pick it up in Starting with Backpack\CRUD 3.2, you can use the ```with()``` method on ```CRUD::resource``` to better organize your routes. Something like this: ```php -CRUD::resource(‘teams’, ‘Admin\TeamCrudController’)->with(function(){ +CRUD::resource('teams', 'Admin\TeamCrudController')->with(function(){ // add extra routes to this resource Route::get('teams/ajax-name-options', 'Admin\TeamCrudController@nameOptions'); Route::get('teams/ajax-category-options', 'Admin\TeamCrudController@categoryOptions'); @@ -102,7 +102,7 @@ class CompanyUser extends User 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 column 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: +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 $this->crud->addColumn([ @@ -132,10 +132,10 @@ In order to insert two column with the same name, use the ```key``` attribute on ## Use the Media Library (File Manager) -If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it’s integrated into: -- TinyMCE (as “tinymce” fieldtype) -- CKEditor (as “ckeditor” fieldtype) -- CRUD “browse” fieldtype +If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it's integrated into: +- TinyMCE (as "tinymce" fieldtype) +- CKEditor (as "ckeditor" fieldtype) +- CRUD "browse" fieldtype - standalone, at the *your-project/admin/elfinder* route; For the integration, barryvdh's [laravel-elfinder](https://github.com/barryvdh/laravel-elfinder) package is used. diff --git a/3.4/crud-operation-list-entries.md b/3.4/crud-operation-list-entries.md index 1db7fa19..3e825e17 100644 --- a/3.4/crud-operation-list-entries.md +++ b/3.4/crud-operation-list-entries.md @@ -110,7 +110,7 @@ Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->crud->en 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 ```setup()``` method: ```php -// Change what entries are show in the table view. +// 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. $this->crud->addClause('active'); // apply a local scope @@ -130,7 +130,7 @@ $this->crud->orderBy(); #### Responsive Table -If you 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, than 2nd, than 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 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 diff --git a/3.4/crud-operation-revisions.md b/3.4/crud-operation-revisions.md index 54ce292b..57dff75c 100644 --- a/3.4/crud-operation-revisions.md +++ b/3.4/crud-operation-revisions.md @@ -7,7 +7,7 @@ Revisions 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 pacakge 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. +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, ```Revisions``` 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: diff --git a/3.4/crud-operation-update.md b/3.4/crud-operation-update.md index b94127a1..ccd23a64 100644 --- a/3.4/crud-operation-update.md +++ b/3.4/crud-operation-update.md @@ -26,7 +26,7 @@ To use the Update operation, you must: ```php // add a field only to the Update operation $this->crud->addField($field_definition_array, 'update'); -// add a field to both the Update and Update operations +// add a field to both the Create and Update operations $this->crud->addField($field_definition_array); ``` @@ -75,7 +75,7 @@ public function update(UpdateRequest $request) You can let your admins edit multi-lingual entries. Only translations stored the [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable) way are supported right now, but more options will be coming soon. In order to make one of your Models translatable, you need to: -0. Be running MySQL 5.7+ (or a PosgreSQL with JSON column support); +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: diff --git a/3.4/crud-operations.md b/3.4/crud-operations.md index a4edc15d..27f37b2c 100644 --- a/3.4/crud-operations.md +++ b/3.4/crud-operations.md @@ -400,7 +400,7 @@ The button makes one call for all entries, and only triggers one notification. I crud.checkedItems.forEach(function(item) { var clone_route = "{{ url(http://github.com/$crud-%3Eroute) }}/"+item+"/clone"; - // submit an AJAX delete call + // submit an AJAX clone call ajax_calls.push($.ajax({ url: clone_route, type: 'POST', @@ -433,4 +433,4 @@ The button makes one call for all entries, and only triggers one notification. I } @endpush -``` \ No newline at end of file +``` diff --git a/3.4/crud-tutorial.md b/3.4/crud-tutorial.md index 2d97174b..c5394877 100644 --- a/3.4/crud-tutorial.md +++ b/3.4/crud-tutorial.md @@ -98,7 +98,7 @@ class Tag extends Model { /* |-------------------------------------------------------------------------- - | ACCESORS + | ACCESSORS |-------------------------------------------------------------------------- */ @@ -205,7 +205,7 @@ What we should notice inside this TagCrudController is that: Let's move our attention to the ```setup()``` method, which is the gateway to configuring our CRUD Panel. -As we can tell from the comments there, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of fields types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. +As we can tell from the comments there, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. Since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys: @@ -244,7 +244,7 @@ This will: - add a simple ```text``` column for our ```name``` attribute to the ListEntries operation (the table view); - add a simple ```text``` field for our ```name``` attribute to the Create and Update forms; -It the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. Here, in the ```setup()``` method, is where you can also do a lot of other things, like enabling other operations, 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). diff --git a/3.4/demo.md b/3.4/demo.md index 8ad6e2aa..280ac8c3 100644 --- a/3.4/demo.md +++ b/3.4/demo.md @@ -2,7 +2,7 @@ --- -We've put toghether a working Laravel backend app, that you can install on your machine. This should make it easier for see how it looks and works. You can even 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: +We've put together a working Laravel backend app, that you can install on your machine. This should make it easier for see how it looks and works. You can even 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 5.6; - installed Backpack\Base and Backpack\CRUD on top; - created a few demo models (Monsters, Icons, Products) and admin panels for them, using dozens of field types, column types, filters, etc - to show off most of Backpack's default features; @@ -47,7 +47,7 @@ Once everything's installed, and your database has been set up: - 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 40 field types. - The magic of Backpack is not in its standard functionality, but in how easy it is to code your own, then customize every little bit of it. Our recommendation: - - Go through the [CRUD Tutorial](/docs/{version}/crud-tutorial) to understand it; + - 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```; diff --git a/3.4/getting-started-advanced-features.md b/3.4/getting-started-advanced-features.md index 5252217a..60148539 100644 --- a/3.4/getting-started-advanced-features.md +++ b/3.4/getting-started-advanced-features.md @@ -4,14 +4,14 @@ **Duration:** 5 min -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 how to find it. +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 how to find it. --- ## Other Operations - [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry -- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarcy tree) +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) - [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes --- @@ -27,7 +27,7 @@ Here are some other cool things Backpack makes easy for you. We recommend going -- - **ListEntries** - - you can add a “+” button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) + - you can add a "+" button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in the list view @@ -36,4 +36,4 @@ 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 CSS or JS](/docs/{{version}}/crud-how-to#customize-css-and-js-for-default-crud-operations) for each CRUD Operation -**That’s it for today!** Told you we’re done with long lessons :-) Hopefully some of the above have peaked your interest and you’ve clicked 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. +**That's it for today!** Told you we're done with long lessons :-) Hopefully some of the above have peaked your interest and you've clicked 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. diff --git a/3.4/getting-started-basics.md b/3.4/getting-started-basics.md index e2f00079..26de284a 100644 --- a/3.4/getting-started-basics.md +++ b/3.4/getting-started-basics.md @@ -4,14 +4,14 @@ **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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2017) and accomodate yourself with Laravel first. +> **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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2017) and accommodate yourself with Laravel first. ## What is Backpack? A software 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. +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. ## How to use? @@ -22,7 +22,7 @@ Backpack consists of two **core packages**: 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 entry. This is what it could look like: +For the purpose of this series, we'll show examples on the Tag entry. This is what it could look like: ![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs/getting_started/tag_crud_list_entries.png) @@ -32,15 +32,15 @@ For the purpose of this series, we’ll show examples on the Tag entry. This is ### Backpack\Base -**Backpack/Base** is the package that **will handle the authentication** and provide you with minimal admin area functionality. **Your admin will be able to login and change his password or email.** And that’s pretty much it. +**Backpack/Base** is the package that **will handle the authentication** and provide you with minimal admin area functionality. **Your admin will be able to login and change his password or email.** And that's pretty much it. -Thanks to Base, 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/Base/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. +Thanks to Base, 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/Base/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. Backpack\Base pulls in the free [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html) theme and enhances the design a little bit. So any front-end block that AdminLTE has, you'll also be able to use in your custom pages. It also includes a system for bubble notifications, which you can use across the admin panel. You can easily [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). ### Backpack\CRUD -This is where it gets interesting. As soon as you [install Backpack](docs/3.4/installation) in your project, you can create **CRUDs** for your admins to easily manipulate DB information. Let’s browse through a simple example, of creating a CRUD administration panel for a Tag entity: +This is where it gets interesting. As soon as you [install Backpack](docs/3.4/installation) in your project, you can create **CRUDs** for your admins to easily manipulate DB information. Let's browse through a simple example, of creating a CRUD administration panel for a Tag entity: ```zsh # STEP 1. create migration @@ -59,7 +59,7 @@ php artisan backpack:base:add-sidebar-content "
  • ## Create & Update Operations @@ -95,7 +95,7 @@ A typical *field definition array* will need at least three things: - ```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 some super-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. +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 some super-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 find encounter all the time is relationship fields. So let's say the ```Tag``` model has an **n-n relationship** with an Article model: @@ -116,7 +116,7 @@ $this->crud->addField([ '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? -], ‘update’); +], 'update'); ``` **Notes:** @@ -139,7 +139,7 @@ $this->crud->addField([ **Note: **Because the last parameter is missing, the field will be added to both Create and Update forms. -> When generating a CrudController, you’ll be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and in your list view, but - as you'd expect - only works for the simple field types. You can: +> When generating a CrudController, you'll be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and in your list view, but - as you'd expect - only works for the simple field types. You can: > > (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields > @@ -184,7 +184,7 @@ ListEntries shows the admin a table with all entries. On the front-end, the info ### Columns -Columns help you specify *which* attributes are shown in the table and *in which order*. **They’re defined in the ```setup()``` method, the same as fields, and their syntax is super-similar to fields too**: +Columns help you specify *which* attributes are shown in the table and *in which order*. **They're defined in the ```setup()``` method, the same as fields, and their syntax is super-similar to fields too**: ```php $this->crud->addColumn($column_definition_array); // add a single column, at the end of the table @@ -241,4 +241,4 @@ $this->crud->removeButton($name); $this->crud->removeButtonFromStack($name, $stack); ``` -**That’s it for today!** 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. \ No newline at end of file +**That's it for today!** 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. \ No newline at end of file diff --git a/3.4/getting-started-license-and-support.md b/3.4/getting-started-license-and-support.md index 191c3714..f3887feb 100644 --- a/3.4/getting-started-license-and-support.md +++ b/3.4/getting-started-license-and-support.md @@ -16,7 +16,7 @@ Take a look at: ## License -Backpack is **free for non-commercial use**, but needs a license code in order to prevent “_unlicensed use_” notification bubbles and interruption of service. You can get a license code for your project: +Backpack is **free for non-commercial use**, but needs a license code in order to prevent "_unlicensed use_" notification bubbles and interruption of service. You can get a license code for your project: - ```free```, if you're using it for non-commercial purposes; - ```free```, if you've contributed to Backpack on Github; - ```$49 EUR/project```, if you're making money using it for a project; @@ -25,14 +25,14 @@ Backpack is **free for non-commercial use**, but needs a license code in order t But **developers** or companies **who make money using it** - for themselves, their employers or their clients, **should [purchase a commercial license here](https://backpackforlaravel.com/pricing)**. We do give away free license codes for personal use, non-commercial purposes, non-profits or Backpack contributors, just [let us know](https://backpackforlaravel.com/contact), happy to help. ->**You don't need a license code on LOCALHOST.** If you’re just trying Backpack on your own machine, you don’t need a license code. You only need a license code when you take your application to production. +>**You don't need a license code on LOCALHOST.** If you're just trying Backpack on your own machine, you don't need a license code. You only need a license code when you take your application to production. ## Support -With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can’t offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: -- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) - **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) - **[Github Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) diff --git a/3.4/install-optionals.md b/3.4/install-optionals.md index 0dfa043c..7a99e6e3 100644 --- a/3.4/install-optionals.md +++ b/3.4/install-optionals.md @@ -4,7 +4,7 @@ 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 following steps if you need the functionality that package provides. +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 diff --git a/3.4/installation.md b/3.4/installation.md index caf3247b..baa221c3 100644 --- a/3.4/installation.md +++ b/3.4/installation.md @@ -5,9 +5,9 @@ ## Requirements -If you can run Laravel 5.6, you can install Backpack. Backpack does _not_ have additional requirements. For the following process, we assume: +If you can run Laravel 5.7, you can install Backpack. Backpack does _not_ have additional requirements. For the following process, we assume: -- you have a working [installation of Laravel 5.6](https://laravel.com/docs/5.6#installing-laravel) (an existing project is fine, you don't need a *fresh* Laravel install); +- you have a working [installation of Laravel 5.7](https://laravel.com/docs/5.7#installing-laravel) (an existing project is fine, you don't need a *fresh* Laravel install); - 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; diff --git a/3.4/introduction.md b/3.4/introduction.md index c2930933..61aa29b5 100644 --- a/3.4/introduction.md +++ b/3.4/introduction.md @@ -34,12 +34,12 @@ php artisan backpack:base:add-sidebar-content "
  • ### Screenshots -Take a look at [our homepage](http://www.backpackforlaravel.com/). +Take a look at [our homepage](https://www.backpackforlaravel.com/). ### Demo @@ -75,5 +75,5 @@ For more, please see: We heavily recommend you spend a little time to understand Backpack, and only afterwards install and use it. Currently your options are: - **[Text Tutorial](/docs/{{version}}/getting-started-basics)** - 23 minutes -- **[Email Tutorial](http://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each +- **[Email Tutorial](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each - **Video Tutorial** - working on it diff --git a/3.5/add-ons-how-to-create-a-backpack-addon.md b/3.5/add-ons-how-to-create-a-backpack-addon.md index a7773621..9c8baa14 100644 --- a/3.5/add-ons-how-to-create-a-backpack-addon.md +++ b/3.5/add-ons-how-to-create-a-backpack-addon.md @@ -33,7 +33,7 @@ Requirements: ### Install Backpack Demo -We're going to use [the Backpack demo project](https://github.com/laravel-backpack/demo) to generate create a new package. Follow the instructions in [the Installation chapter](https://github.com/laravel-backpack/demo#install). +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. @@ -59,7 +59,7 @@ This will create a ```/packages/``` folder in your root directory, where your pa 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 prviders section in your ```composer.json```'s ```extra``` section, like so: +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": { @@ -164,13 +164,13 @@ Tags are the way you will version your package, so it's important you do it. Peo ## Step 3. Put it on Packagist -On [Packagist.org](http://packagist.org), submit a new package. Enter you package's GitHub URL and click Check. If any errors occur, follow the onscreen instructions. +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 dissapear in 5–10 minutes. +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. @@ -209,7 +209,7 @@ git push origin master --tags 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: -- http://laravel.com/docs/packages +- 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 diff --git a/3.5/crud-api.md b/3.5/crud-api.md index 85240dad..1c22bb4a 100644 --- a/3.5/crud-api.md +++ b/3.5/crud-api.md @@ -66,7 +66,7 @@ $this->crud->addColumn()->beforeColumn('name'); $this->crud->addColumn()->afterColumn('name'); ``` -- **Chained - makeFirstColumn()** - make this colum the first one in the list +- **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 @@ -136,7 +136,7 @@ $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 creting the view with those details. +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 diff --git a/3.5/crud-basics.md b/3.5/crud-basics.md index ab869a44..a629169e 100644 --- a/3.5/crud-basics.md +++ b/3.5/crud-basics.md @@ -2,7 +2,7 @@ --- -Backpack\CRUD provides a fast way to build admininistration 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.** +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 @@ -43,4 +43,4 @@ For a ```Tag``` entity, your CRUD Panel would consist of: - a route inside ```routes/backpack/custom.php```; - your existing model (```app/Models/Tag.php```); -To further your understading of how a CRUD Panel works, [read more about this example in the tutorial](/docs/{{version}}/crud-tutorial). +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/3.5/crud-buttons.md b/3.5/crud-buttons.md index c151d5ca..49eab483 100644 --- a/3.5/crud-buttons.md +++ b/3.5/crud-buttons.md @@ -86,10 +86,10 @@ Let's say we want to create a simple ```moderate.blade.php``` button. This butto ``` - Add the new route, next to ```UserCrudController```'s route (most likely inside ```routes/backpack/custom.php```): ```php -Route::get('user/{id}/moderate', 'UserCrudController@ban'); +Route::get('user/{id}/moderate', 'UserCrudController@moderate'); ``` -- We can now create add a ```moderate()``` method to our ```UserCrudController```, which would moderate the user, and redirect back. +- We can now add a ```moderate()``` method to our ```UserCrudController```, which would moderate the user, and redirect back. ```php public function moderate() { diff --git a/3.5/crud-cheat-sheet.md b/3.5/crud-cheat-sheet.md index bc9da204..79ef3c59 100644 --- a/3.5/crud-cheat-sheet.md +++ b/3.5/crud-cheat-sheet.md @@ -63,7 +63,7 @@ $this->crud->filters(); // gets all the filters #### Details Row ```php -// Shows a + sign next to each table row, so that the user can expand that row and reveal details. You are responsible for creting the view with those details. +// 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. $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 @@ -115,7 +115,7 @@ $this->crud->setActionsColumnPriority(10000); #### Custom / Advanced Queries ```php -// Change what entries are show in the table view. +// 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. $this->crud->addClause('active'); // apply local scope diff --git a/3.5/crud-columns.md b/3.5/crud-columns.md index df6d924c..6b089e37 100644 --- a/3.5/crud-columns.md +++ b/3.5/crud-columns.md @@ -181,7 +181,9 @@ Show custom HTML based on a closure you specify in your EntityCrudController. Pl ### date -The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted as date in the model or not: +The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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 [ @@ -196,7 +198,10 @@ The date column will show a localized date in the default date format (as specif ### datetime -The date column will show a localized datetime in the default datetime format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted as datetime in the model or not: +The date column will show a localized datetime in the default datetime format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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 [ @@ -257,11 +262,11 @@ The model_function column will output a function on your main model. Its definit 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.''; - } + return ''.$this->slug.''; +} ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### model_function_attribute @@ -280,7 +285,7 @@ If the function you're trying to use returns an object, not a string, you can us ], ``` -**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent that JS and CSS from reaching your DB in the first place. +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. ### multidimensional_array @@ -371,7 +376,7 @@ The text column will just output the text value of a db column (or model attribu ], ``` -**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 attibute in the ```name```, using dot notation: +**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', @@ -406,7 +411,7 @@ Show a particular text depending on the value of the attribute. 'name' => 'status', 'label' => "Status", 'type' => 'select_from_array', - 'options' => [‘draft’ => ‘Draft (invisible)’, ‘published’ => ‘Published (visible)’], + 'options' => ['draft' => 'Draft (invisible)', 'published' => 'Published (visible)'], ], ``` @@ -599,7 +604,7 @@ If you want a column to not be orderable at all, just pass ```'orderable' => fal ### Choose Where Columns are Visible -Starting with Backpack\CRUD 3.5.0, you can choose to show/hide column 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: +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([ @@ -674,4 +679,4 @@ You can make the last column be less important (and hide) by giving it an unreas $this->crud->setActionsColumnPriority(10000); ``` ->Note that repsonsive tables adopt special behavior if the table is not able to show all columns. This includes displaying a vertical elipsis 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. +>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. diff --git a/3.5/crud-fields.md b/3.5/crud-fields.md index 3cd56d5a..187debc8 100644 --- a/3.5/crud-fields.md +++ b/3.5/crud-fields.md @@ -7,7 +7,7 @@ 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. +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). @@ -105,7 +105,7 @@ $this->crud->addField($field_definition_array)->afterField('name'); #### Fake Fields (all stored as JSON in the database) -In case you want to store insignificant information for an entry, that don't need a database column, you can add any number of Fake Fields, and all their information will be store inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. +In case you want to store insignificant information for an entry that doesn't need a database column, you can add any number of Fake Fields, and all their information will be stored inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. **Step 1.** Use the fake attribute on your field: ```php @@ -113,7 +113,7 @@ In case you want to store insignificant information for an entry, that don't nee '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 + '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 ], ``` @@ -147,7 +147,7 @@ Example: ], ``` -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: +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"} @@ -223,7 +223,7 @@ Using Google Places API is dependant on using an API Key. Please [get an API key ```php 'google_places' => [ - 'key' => ’the-key-you-got-from-google-places' + 'key' => 'the-key-you-got-from-google-places' ], ``` @@ -258,7 +258,7 @@ Onclick preview: ### browse_multiple -Open elFinder and select multiple file from there. +Open elFinder and select multiple files from there. ```php [ // Browse multiple @@ -471,7 +471,7 @@ Show a pretty [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/ 'label' => 'Date', // optional: 'date_picker_options' => [ - 'todayBtn' => true, + 'todayBtn' => 'linked', 'format' => 'dd-mm-yyyy', 'language' => 'fr' ], @@ -655,7 +655,7 @@ $this->crud->addField([ // image 'type' => 'image', 'upload' => true, 'crop' => true, // set to true to allow cropping, false to disable - 'aspect_ratio' => 1, // ommit or set to 0 to allow any aspect ratio + 'aspect_ratio' => 1, // omit or set to 0 to allow any aspect ratio // 'disk' => 's3_bucket', // in case you need to show images from a different disk // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; ]); @@ -758,14 +758,14 @@ Input preview: ### page_or_link -Select an existing page from PageManager or an internal or external link. It’s used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: +Select an existing page from PageManager or an internal or external link. It's used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: ```php [ // PageOrLink 'name' => 'type', 'label' => "Type", 'type' => 'page_or_link', 'page_model' => '\Backpack\PageManager\app\Models\Page' -] +], ``` Input preview: @@ -803,7 +803,7 @@ Show radios according to an associative array you give the input and let the use ], // optional //'inline' => false, // show the radios all on the same line? -] +], ``` Input preview: @@ -844,7 +844,7 @@ Your relationships should already be defined on your models as hasOne() or belon '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 -] +], ``` Input preview: @@ -893,7 +893,7 @@ Your relationships should already be defined on your models as hasOne() or belon '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 -] +], ``` Input preview: @@ -920,7 +920,7 @@ Your relationships should already be defined on your models as hasMany() or belo '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 -] +], ``` Input preview: @@ -934,7 +934,7 @@ Input preview: [Works just like the SELECT field, but prettier] -Show a Select2 with the names of the connected entity and let the user select any number of them. +Shows a Select2 with the names of the connected entity and let the user select any number of them. Your relationships should already be defined on your models as hasMany() or belongsToMany(). ```php @@ -952,7 +952,7 @@ Your relationships should already be defined on your models as hasMany() or belo '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 -] +], ``` Input preview: @@ -1008,7 +1008,7 @@ Input preview: ### select_and_order -Display items on two columns and let the user drag&drop between them to choose which items are selected an which are not, and reorder the selected items with drag&drop. +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: @@ -1040,7 +1040,7 @@ Also possible: 'label' => 'Featured', 'type' => 'select_and_order', 'options' => Product::get()->pluck('title','id')->toArray(), -] +], ``` Input preview: @@ -1058,7 +1058,7 @@ Display a select with the values you want: 'name' => 'template', 'label' => "Template", 'type' => 'select_from_array', - 'options' => [‘one’ => ‘One’, ‘two’ => ‘Two’], + 'options' => ['one' => 'One', 'two' => 'Two'], 'allows_null' => false, 'default' => 'one', // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; @@ -1079,7 +1079,7 @@ Display a select2 with the values you want: 'name' => 'template', 'label' => "Template", 'type' => 'select2_from_array', - 'options' => [‘one’ => ‘One’, ‘two’ => ‘Two’], + 'options' => ['one' => 'One', 'two' => 'Two'], 'allows_null' => false, 'default' => 'one', // 'allows_multiple' => true, // OPTIONAL; needs you to cast this to array in your model; @@ -1107,8 +1107,8 @@ Display a select2 that takes its values from an AJAX call. 'data_source' => url("http://github.com/api/category"), // url to controller search function (with /{id} should return model) 'placeholder' => "Select a category", // placeholder for the select 'minimum_input_length' => 2, // minimum characters to type before querying results - // 'dependencies' => [‘category’], // when a dependency changes, this select2 is reset to null - // ‘method' => ‘GET’, // optional - HTTP method to use for the AJAX call (GET, POST) + // 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST) ] ``` @@ -1383,7 +1383,7 @@ Input preview: 'label' => 'Image', 'type' => 'upload', 'upload' => true, - 'disk' => 'uploads' // if you store files in the /public folder, please ommit this; if you store them in /storage or S3, please specify it; + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; ], ``` @@ -1450,7 +1450,7 @@ Shows a multiple file input to the user and stores the values as a JSON array in 'label' => 'Photos', 'type' => 'upload_multiple', 'upload' => true, - 'disk' => 'uploads' // if you store files in the /public folder, please ommit this; if you store them in /storage or S3, please specify it; + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; ], ``` @@ -1548,10 +1548,10 @@ 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’ + title: 'my video title', + image: 'https://provider.com/image.jpg', + url: 'http://provider.com/video', + provider: 'youtube' } ``` @@ -1604,11 +1604,11 @@ Show a wysiwyg (CKEditor) to the user. ## 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. +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:crud:publish fields/field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:crud:publish fields/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 crate a custom field type for your use case, instead of overwriting default field types. +>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 @@ -1624,7 +1624,7 @@ Your field definition will be something like: '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: diff --git a/3.5/crud-filters.md b/3.5/crud-filters.md index 1feb07b6..21bed0d1 100644 --- a/3.5/crud-filters.md +++ b/3.5/crud-filters.md @@ -55,7 +55,7 @@ function() { // if the filter is active (the GET parameter "draft" exits) > - 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->request```; > - 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 convers the first "orWhere" into a "where"; +> - 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 @@ -80,7 +80,7 @@ function() { // if the filter is active ### Text -Shows a text input. Most useful for letting the user filter through information that not shown as a column in the CRUD table - otherwise they could just use the DataTables search field. +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/filters/text.png) @@ -104,15 +104,15 @@ Show a datepicker. The user can select one day. ![Backpack CRUD Date Filter](https://backpackforlaravel.com/uploads/docs/filters/date.png) ```php - $this->crud->addFilter([ // date filter - 'type' => 'date', - 'name' => 'date', - 'label'=> 'Date' - ], - false, - function($value) { // if the filter is active, apply these constraints - // $this->crud->addClause('where', 'date', $value); - }); +$this->crud->addFilter([ // date filter + 'type' => 'date', + 'name' => 'date', + 'label'=> 'Date' +], +false, +function($value) { // if the filter is active, apply these constraints + // $this->crud->addClause('where', 'date', $value); +}); ``` @@ -123,17 +123,17 @@ 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/filters/date_range.png) ```php - $this->crud->addFilter([ // daterange filter - 'type' => 'date_range', - 'name' => 'from_to', - 'label'=> 'Date range' - ], - false, - function($value) { // if the filter is active, apply these constraints - // $dates = json_decode($value); - // $this->crud->addClause('where', 'date', '>=', $dates->from); - // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); - }); +$this->crud->addFilter([ // daterange filter + 'type' => 'date_range', + 'name' => 'from_to', + 'label'=> 'Date range' +], +false, +function($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); ``` diff --git a/3.5/crud-how-to.md b/3.5/crud-how-to.md index 76869dbb..b85723d0 100644 --- a/3.5/crud-how-to.md +++ b/3.5/crud-how-to.md @@ -37,7 +37,7 @@ If you don't find one there, you can create one, and Backpack will pick it up in Starting with Backpack\CRUD 3.2, you can use the ```with()``` method on ```CRUD::resource``` to better organize your routes. Something like this: ```php -CRUD::resource(‘teams’, ‘Admin\TeamCrudController’)->with(function(){ +CRUD::resource('teams', 'Admin\TeamCrudController')->with(function(){ // add extra routes to this resource Route::get('teams/ajax-name-options', 'Admin\TeamCrudController@nameOptions'); Route::get('teams/ajax-category-options', 'Admin\TeamCrudController@categoryOptions'); @@ -103,7 +103,7 @@ class CompanyUser extends User 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 column 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: +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 $this->crud->addColumn([ @@ -133,10 +133,10 @@ In order to insert two column with the same name, use the ```key``` attribute on ## Use the Media Library (File Manager) -If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it’s integrated into: -- TinyMCE (as “tinymce” fieldtype) -- CKEditor (as “ckeditor” fieldtype) -- CRUD “browse” fieldtype +If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it's integrated into: +- TinyMCE (as "tinymce" fieldtype) +- CKEditor (as "ckeditor" fieldtype) +- CRUD "browse" fieldtype - standalone, at the *your-project/admin/elfinder* route; For the integration, barryvdh's [laravel-elfinder](https://github.com/barryvdh/laravel-elfinder) package is used. @@ -205,9 +205,9 @@ Say you want to show two selects: ```php $this->crud->addField([ // SELECT2 - 'label' => ‘Category', + 'label' => 'Category', 'type' => 'select', - 'name' => ‘category', + 'name' => 'category', 'entity' => 'category', 'attribute' => 'name', ]); @@ -221,8 +221,8 @@ Say you want to show two selects: 'data_source' => url('http://github.com/api/article'), // url to controller search function (with /{id} should return model) 'placeholder' => 'Select an article', // placeholder for the select 'minimum_input_length' => 0, // minimum characters to type before querying results - 'dependencies' => [‘category’], // when a dependency changes, this select2 is reset to null - // ‘method' => ‘GET’, // optional - HTTP method to use for the AJAX call (GET, POST) + 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST) ]); ``` @@ -232,7 +232,7 @@ Say you want to show two selects: ```php Route::get('api/article', 'App\Http\Controllers\Api\ArticleController@index'); -Route::get('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show’); +Route::get('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show'); ``` **DIFFERENT HERE**: Nothing. diff --git a/3.5/crud-operation-list-entries.md b/3.5/crud-operation-list-entries.md index 5255371c..2f22a632 100644 --- a/3.5/crud-operation-list-entries.md +++ b/3.5/crud-operation-list-entries.md @@ -110,7 +110,7 @@ Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->crud->en 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 ```setup()``` method: ```php -// Change what entries are show in the table view. +// 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. $this->crud->addClause('active'); // apply a local scope @@ -130,7 +130,7 @@ $this->crud->orderBy(); #### Responsive Table -If you 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, than 2nd, than 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 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 diff --git a/3.5/crud-operation-revisions.md b/3.5/crud-operation-revisions.md index 6a9210bf..b2833e4f 100644 --- a/3.5/crud-operation-revisions.md +++ b/3.5/crud-operation-revisions.md @@ -7,7 +7,7 @@ Revisions 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 pacakge 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. +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, ```Revisions``` 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: diff --git a/3.5/crud-operation-update.md b/3.5/crud-operation-update.md index e6477313..c7fd915f 100644 --- a/3.5/crud-operation-update.md +++ b/3.5/crud-operation-update.md @@ -75,7 +75,7 @@ public function update(UpdateRequest $request) For localized apps, you can let your admins edit multi-lingual entries. Only translations stored the [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable) way are supported right now, but more options will be coming soon. In order to make one of your Models translatable (localization), you need to: -0. Be running MySQL 5.7+ (or a PosgreSQL with JSON column support); +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: diff --git a/3.5/crud-operations.md b/3.5/crud-operations.md index 4ad5a4ca..bc125218 100644 --- a/3.5/crud-operations.md +++ b/3.5/crud-operations.md @@ -96,7 +96,7 @@ $this->crud->setHeading('some string', 'create'); // set the Heading for the cre $this->crud->setSubheading('some string', 'create'); // set the Subheading for the create action ``` -There 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. +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 diff --git a/3.5/crud-tutorial.md b/3.5/crud-tutorial.md index 827b0696..4fa02c24 100644 --- a/3.5/crud-tutorial.md +++ b/3.5/crud-tutorial.md @@ -98,7 +98,7 @@ class Tag extends Model { /* |-------------------------------------------------------------------------- - | ACCESORS + | ACCESSORS |-------------------------------------------------------------------------- */ @@ -205,7 +205,7 @@ What we should notice inside this TagCrudController is that: Let's move our attention to the ```setup()``` method, which is the gateway to configuring our CRUD Panel. -As we can tell from the comments there, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of fields types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. +As we can tell from the comments there, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. Since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys: @@ -244,7 +244,7 @@ This will: - add a simple ```text``` column for our ```name``` attribute to the ListEntries operation (the table view); - add a simple ```text``` field for our ```name``` attribute to the Create and Update forms; -It the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. Here, in the ```setup()``` method, is where you can also do a lot of other things, like enabling other operations, 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). diff --git a/3.5/demo.md b/3.5/demo.md index 4cec341e..eb531deb 100644 --- a/3.5/demo.md +++ b/3.5/demo.md @@ -2,7 +2,7 @@ --- -We've put toghether a working Laravel app (backend-only), that you can install on your machine. This should make it easier to: +We've put together a working Laravel app (backend-only), that you can install on your machine. 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; diff --git a/3.5/getting-started-advanced-features.md b/3.5/getting-started-advanced-features.md index 5252217a..60148539 100644 --- a/3.5/getting-started-advanced-features.md +++ b/3.5/getting-started-advanced-features.md @@ -4,14 +4,14 @@ **Duration:** 5 min -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 how to find it. +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 how to find it. --- ## Other Operations - [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry -- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarcy tree) +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) - [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes --- @@ -27,7 +27,7 @@ Here are some other cool things Backpack makes easy for you. We recommend going -- - **ListEntries** - - you can add a “+” button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) + - you can add a "+" button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in the list view @@ -36,4 +36,4 @@ 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 CSS or JS](/docs/{{version}}/crud-how-to#customize-css-and-js-for-default-crud-operations) for each CRUD Operation -**That’s it for today!** Told you we’re done with long lessons :-) Hopefully some of the above have peaked your interest and you’ve clicked 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. +**That's it for today!** Told you we're done with long lessons :-) Hopefully some of the above have peaked your interest and you've clicked 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. diff --git a/3.5/getting-started-basics.md b/3.5/getting-started-basics.md index bae7efde..99061791 100644 --- a/3.5/getting-started-basics.md +++ b/3.5/getting-started-basics.md @@ -4,14 +4,14 @@ **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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2017) and accomodate yourself with Laravel first. +> **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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2017) and accommodate yourself with Laravel first. ## What is Backpack? A software 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. +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. ## How to use? @@ -22,7 +22,7 @@ Backpack consists of two **core packages**: 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 what a Tag CRUD could look like: +For the purpose of this series, we'll show examples on the ```Tag``` entity. This is what a Tag CRUD could look like: ![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/tag_crud_list_entries.png) @@ -38,17 +38,17 @@ Mind that you will _almost never_ use all of Backpack's features in one CRUD. Bu ### Backpack\Base -**Backpack/Base** is the package that **will handle the authentication** and provide you with minimal admin area functionality. **Your admin will be able to login and change his password or email.** And that’s pretty much it. +**Backpack/Base** is the package that **will handle the authentication** and provide you with minimal admin area functionality. **Your admin will be able to login and change his password or email.** And that's pretty much it. ![Backpack 3.5 Authentication Screens](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/auth_screens.png) -Thanks to Base, 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/Base/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. +Thanks to Base, 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/Base/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. Backpack\Base pulls in the free [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html) theme and enhances the design a little bit. So any front-end block that AdminLTE has, you'll also be able to use in your custom pages. It also includes a system for bubble notifications, which you can use across the admin panel. You can easily [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). ### Backpack\CRUD -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 DB information. Let’s browse through a simple example, of creating a CRUD administration panel for a Tag entity: +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 DB information. Let's browse through a simple example, of creating a CRUD administration panel for a Tag entity: ```zsh # STEP 1. create migration @@ -67,7 +67,7 @@ php artisan backpack:base:add-sidebar-content "
  • ## Create & Update Operations @@ -95,7 +95,7 @@ A typical *field definition array* will need at least three things: - ```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 some super-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. +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 some super-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 is relationship fields. So let's say the ```Tag``` model has an **n-n relationship** with an Article model: @@ -115,7 +115,7 @@ $this->crud->addField([ '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? -], ‘update’); +], 'update'); ``` **Notes:** @@ -138,7 +138,7 @@ $this->crud->addField([ **Note: **Because the last parameter is missing, the field will be added to both Create and Update forms. -> When generating a CrudController, you’ll be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and in your list view, but - as you'd expect - only works for the simple field types. You can: +> When generating a CrudController, you'll be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and in your list view, but - as you'd expect - only works for the simple field types. You can: > > (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields > @@ -183,7 +183,7 @@ ListEntries shows the admin a table with all entries. On the front-end, the info ### Columns -Columns help you specify *which* attributes are shown in the table and *in which order*. **They’re defined in the ```setup()``` method, the same as fields, and their syntax is super-similar to fields too**: +Columns help you specify *which* attributes are shown in the table and *in which order*. **They're defined in the ```setup()``` method, the same as fields, and their syntax is super-similar to fields too**: ```php $this->crud->addColumn($column_definition_array); // add a single column, at the end of the table @@ -240,4 +240,4 @@ $this->crud->removeButton($name); $this->crud->removeButtonFromStack($name, $stack); ``` -**That’s it for today!** 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. \ No newline at end of file +**That's it for today!** 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. \ No newline at end of file diff --git a/3.5/getting-started-license-and-support.md b/3.5/getting-started-license-and-support.md index 3916ffc9..7dceeb0a 100644 --- a/3.5/getting-started-license-and-support.md +++ b/3.5/getting-started-license-and-support.md @@ -16,7 +16,7 @@ Take a look at: ## License -Backpack is **free for non-commercial use**, but needs a license code in order to prevent “_unlicensed use_” notification bubbles and interruption of service. You can get a license code for your project: +Backpack is **free for non-commercial use**, but needs a license code in order to prevent "_unlicensed use_" notification bubbles and interruption of service. You can get a license code for your project: - ```free```, if you're using it for non-commercial purposes; [apply here](https://backpackforlaravel.com/pricing); - ```free```, if you've contributed to Backpack on Github; [apply here](https://backpackforlaravel.com/pricing); - ```$49 EUR/project```, if you're making money using it for a project; [buy here](https://backpackforlaravel.com/pricing); @@ -25,14 +25,14 @@ Backpack is **free for non-commercial use**, but needs a license code in order t **Freelancers** or companies **who make money using Backpack** - for themselves, their employers or their clients, **should [purchase a commercial license here](https://backpackforlaravel.com/pricing)**. ->**You don't need a license code on LOCALHOST.** If you’re just trying Backpack on your own machine, you don’t need a license code. You only need a license code when you take your application to production. +>**You don't need a license code on LOCALHOST.** If you're just trying Backpack on your own machine, you don't need a license code. You only need a license code when you take your application to production. ## Support -With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can’t offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: -- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) - **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) - **[Github Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) diff --git a/3.5/install-optionals.md b/3.5/install-optionals.md index 83f82fc2..bf58fc0b 100644 --- a/3.5/install-optionals.md +++ b/3.5/install-optionals.md @@ -4,7 +4,7 @@ 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 following steps if you need the functionality that package provides. +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 diff --git a/3.5/installation.md b/3.5/installation.md index e76efa40..c9eaa2c1 100644 --- a/3.5/installation.md +++ b/3.5/installation.md @@ -7,7 +7,7 @@ If you can run Laravel 5.6, you can install Backpack. Backpack does _not_ have additional requirements. For the following process, we assume: -- you have a working installation of [Laravel 5.7](https://laravel.com/docs/5.7#installing-laravel) or [Laravel 5.6](https://laravel.com/docs/5.6#installing-laravel) (an existing project is fine, you don't need a *fresh* Laravel install); +- you have a working installation of [Laravel 5.7](https://laravel.com/docs/5.7#installing-laravel) or [5.6](https://laravel.com/docs/5.6#installing-laravel) (an existing project is fine, you don't need a *fresh* Laravel install); - you have put your database and email credentials in your .ENV file; diff --git a/3.5/introduction.md b/3.5/introduction.md index 44697be4..b639be19 100644 --- a/3.5/introduction.md +++ b/3.5/introduction.md @@ -32,14 +32,14 @@ php artisan backpack:base:add-sidebar-content "
  • ### Requirements - - Laravel 5.6 or 5.7 + - Laravel 5.7 or 5.6 - PHP 7.1.3+ - - MySQL (recommended) / PosgreSQL / SQLite / SQL Server + - MySQL (recommended) / PostgreSQL / SQLite / SQL Server ### Screenshots -Take a look at [our homepage](http://www.backpackforlaravel.com/). +Take a look at [our homepage](https://backpackforlaravel.com/). ### Demo @@ -54,7 +54,7 @@ Backpack has never had a critical vulnerability/hack. But there _have_ been impo ### Maintenance -Backpack 3.5 is the current version, and is being actively maintained by Backpack's creator, [Cristian Tabacitu](http://tabacitu.ro), with the help of a wonderful community of Backpack veterans. [See all contributors](https://github.com/Laravel-Backpack/CRUD/graphs/contributors). +Backpack 3.5 is the current version, and is being actively maintained by Backpack's creator, [Cristian Tabacitu](https://tabacitu.ro), with the help of a wonderful community of Backpack veterans. [See all contributors](https://github.com/Laravel-Backpack/CRUD/graphs/contributors). ### License @@ -75,5 +75,5 @@ For more, please see: We heavily recommend you spend a little time to understand Backpack, and only afterwards install and use it. Currently your options are: - **[Text Tutorial](/docs/{{version}}/getting-started-basics)** - 23 minutes -- **[Email Tutorial](http://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each +- **[Email Tutorial](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each - **Video Tutorial** - working on it diff --git a/3.5/release-notes.md b/3.5/release-notes.md index 9f56399f..0ccf1ed6 100644 --- a/3.5/release-notes.md +++ b/3.5/release-notes.md @@ -18,8 +18,8 @@ Here are the main differences between [Backpack 3.4](https://backpackforlaravel. - added ```php artisan backpack:base:publish-middleware``` command; [details here](https://github.com/Laravel-Backpack/Base/pull/334); - upon installation, ```BackpackUser``` model and ```CheckIfAdmin``` middleware are published by default - so they can EASILY be customized; [details here](https://github.com/Laravel-Backpack/Base/pull/334); - two separate files: ```inc/topbar_left_content.blade.php``` and ```inc/topbar_right_content.blade.php``` where the user can specify additional content for the top menu; [details here](https://github.com/Laravel-Backpack/Base/pull/302); [documentation here](docs/{{version}}/base-how-to#use-separate-login-register-forms-for-users-and-admins); -- error views now use a layout file, so it’s easier to customize how all error pages look; defaut error view design is now consistent with default AdminLTE design; -- split ```layout``` into multiple views (```head```, ```scripts```), so it’s easier to customize just one part of it; +- error views now use a layout file, so it's easier to customize how all error pages look; defaut error view design is now consistent with default AdminLTE design; +- split ```layout``` into multiple views (```head```, ```scripts```), so it's easier to customize just one part of it; - ```backpack_url()``` can now take parameters, just like ```url()```; - password reset page is a clear two-step process, and pre-populates the email field; big UX improvement for something that is often used by inexperienced users (they're the ones losing their password); @@ -29,7 +29,7 @@ Here are the main differences between [Backpack 3.4](https://backpackforlaravel. - default CSS file now uses ```body.skin-purple``` as a selector, to fix the paint glitch, where buttons and other things were shown blue, then changed to purple, when using the purple skin; - now using jquery and font-awesome from adminlte package instead of CDN; - language folders like ```da_DK```, ```fr_CA``` and ```pt_br``` have been duplicated into their standardized form (```da-DK```, ```fr-CA``` and ```pt-BR```); introduced notice that the old folders will be deprecated in the next release; -- the footer is now transparent; it is not a primary piece of content, it shouldn’t stand out; +- the footer is now transparent; it is not a primary piece of content, it shouldn't stand out; ### Removed - removed Laravel 5.5 support; diff --git a/3.5/upgrade-guide.md b/3.5/upgrade-guide.md index 01206bcc..9c1d10e9 100644 --- a/3.5/upgrade-guide.md +++ b/3.5/upgrade-guide.md @@ -107,7 +107,7 @@ Search any custom files you use in your admin panels for ```Auth::```. You _migh #### Step 5 -If you haven’t created any custom **error views**, re-publish the ```resources/views/errors``` folder. Please note that this will delete your existing folder. +If you haven't created any custom **error views**, re-publish the ```resources/views/errors``` folder. Please note that this will delete your existing folder. ```bash php artisan vendor:publish --provider="Backpack\Base\BaseServiceProvider" --tag=errors --force @@ -146,7 +146,7 @@ And change the overlays path in your ```config/backpack/base.php``` file (```ove #### Step 7 -**ALL Backpack/Base views have suffered some changes**. If you’ve published/customized/overwritten any of the Backpack/Base views, please [take a look at the changes](https://github.com/Laravel-Backpack/Base/pull/324/files) and implement them in your file. To rephrase this: +**ALL Backpack/Base views have suffered some changes**. If you've published/customized/overwritten any of the Backpack/Base views, please [take a look at the changes](https://github.com/Laravel-Backpack/Base/pull/324/files) and implement them in your file. To rephrase this: - if you have files in you ```resources/views/vendor/backpack/base/``` it means you're actually using _that_ file, instead of the new one provided by upgrading to Base 1.0.0; - you need to either (A) delete that file and forfeit any changes you've made (Backpack will pick up the new one) OR (B) apply the changes yourself diff --git a/3.6/add-ons-community.md b/3.6/add-ons-community.md new file mode 100644 index 00000000..6178e504 --- /dev/null +++ b/3.6/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 | +| ------------- |:-------------:| --------:| +| [AbbyJanke/BackpackBlog](https://github.com/AbbyJanke/BackpackBlog) | blog front-end and back-end | - | +| [AbbyJanke/BackpackMeta](https://github.com/AbbyJanke/BackpackMeta) | helps create meta options for extending core functions | - | +| [eduardoarandah/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) | +| [eduardoarandah/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) | +| [novius/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) | +| [novius/laravel-backpack-translation-manager](https://github.com/novius/laravel-backpack-translation-manager) | manage translations stored in the database | - | +| [updivision/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) | +| [webfactor/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) | +| [seandowney/laravel-backpack-gallery-crud](https://github.com/seandowney/laravel-backpack-gallery-crud) | manage photo galleries | [MIT](https://github.com/seandowney/laravel-backpack-gallery-crud/blob/master/LICENSE.md) | diff --git a/3.6/add-ons-how-to-create-a-backpack-addon.md b/3.6/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..9c8baa14 --- /dev/null +++ b/3.6/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 \ No newline at end of file diff --git a/3.6/add-ons-official.md b/3.6/add-ons-official.md new file mode 100644 index 00000000..429a1a02 --- /dev/null +++ b/3.6/add-ons-official.md @@ -0,0 +1,15 @@ +# Official Add-ons + +In addition to our core packages (Base and CRUD), we've developed a few packages you can install or download, that treat common use cases. + + + - [PermissionManager](https://github.com/Laravel-Backpack/PermissionManager) - interface to manage users & permissions, using [spatie/laravel-permission](https://github.com/spatie/laravel-permission); ```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``` + + +>**These 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. \ No newline at end of file diff --git a/3.6/base-about.md b/3.6/base-about.md new file mode 100644 index 00000000..e05c2926 --- /dev/null +++ b/3.6/base-about.md @@ -0,0 +1,174 @@ +# About Backpack\Base + +--- + +Backpack/Base kickstarts your admin panel building by: +- pulling in the AdminLTE HTML theme, based on Bootstrap 3; +- providing different views and functionality for authentication; +- integrating [prologue/alerts](https://github.com/prologuephp/alerts) and and [pnotify](https://github.com/sciactive/pnotify) for showing notification bubbles upon error/success/warning/info; +- providing pretty error pages for most common errors; +- providing a horizontal menu and a side menu you can customize; +- providing a place for your admin to to change his email/name/password; +- providing a few helpers you can use throughout your admin panel; + +For the simplest projects, you will never need to know how it works, never need to customize anything but the ```config/backpack/base.php``` file. But here's how everything works, below. + + +## Layout & Design + + +### General + +Backpack\Base pulls in the free [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html) theme and adds our own CSS file on top, for a few cosmetic improvements. We've chosen AdminLTE because it provides design blocks for all common features of an administration panel. When you decide to build something from scratch, you can just use its HTML blocks - no designer needed. + + +### Published Views + +After installation, you'll notice Backpack has added one or more files to ```resources/views/vendor/backpack/base/```. By default, it only publishes: +- ```inc/sidebar_content.blade.php```; +- ```dashboard.blade.php```; + +Those files are used to show the contents of the menu to the left (sidebar), and the first page the admin sees when logging in (dashboard). They've been published there so that you can easily modify their contents. + + +### 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, then go to ```resources/views/vendor/backpack/base``` and create a file with the exact same name. Backpack\Base will use this new file, instead of the one in the package. + +For example, if you want to add an item to the top menu, you could just create a file called ```resources/views/vendor/backpack/base/inc/menu.php```. Backpack will now use this file's contents, instead of ```vendor/backpack/base/src/resources/views/inc/menu.php``` + + +### Folder Structure + +If you'll take a look inside any Backpack package, you'll notice the ```src``` directory is 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 inside the ```config/backpack/base.php``` config file. + + +> **The ```BackpackUser``` model extends Laravel's default ```App\User``` model**. This assumes you weren't already using this model, or the ```users``` table, for anything else. If you were, please read below. + + +### Using a Different User Model + +If you want to use a different User model than ```App\User``` or you've changed its location, please: +- take a look at [```\Backpack\Base\app\Models\BackpackUser::class```](https://github.com/Laravel-Backpack/Base/blob/master/src/app/Models/BackpackUser.php); +- include the methods there in _your_ user model; they're important for password recovery; +- tell Backpack to use _your_ model in ```config/backpack/base.php```; + + +### 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 creating a middleware that checks that that column is true. You can use ```\Backpack\Base\app\Http\Middleware\CheckIfAdmin::class``` as a starting point; +- (B) using the [PermissionManager](https://github.com/Laravel-Backpack/PermissionManager) extension - this will also add groups and permissions; + +Please remember to tell Backpack to use _this new 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 ```admin``` middleware. You can change that inside ```config/backpack/base.php```. Inside your _custom admin pages or admin features_, please: +- use ```backpack_auth()``` instead of ```auth()```; +- use ```backpack_url()``` instead of ```url()```; + +This will make sure you're using the 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 his/her name to go to his "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\Base\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\Base routes there and change whatever you need. You can then point the route 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/base/auth/account/update_info.blade.php``` that uses code from the same file in the Backpack package, 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\Base\app\Http\Controllers\Auth\MyAccountController```, using the ```getChangePasswordForm()``` and ```postChangePasswordForm()``` methods. If you want to change how this works, we recommend you create a ```routes/backpack/base.php``` file, copy-paste all Backpack\Base routes there and change whatever you need. You can then point the route to your own controller, where you can do whatever you want. + + +## Notification Bubbles + + +### Triggering Notification Bubbles 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. Basic examples: + +```php +public function foo() +{ + \Alert::info('This is a blue bubble.'); + \Alert::warning('This is a yellow/orange bubble.'); + \Alert::error('This is a red bubble.'); + \Alert::success('This is a green bubble.'); + \Alert::success('Got it
    This is an HTML message.'); + + + // the layout will make sure to show your notifications + return view('some_view'); +} + +public function bar() +{ + \Alert::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 Notification Bubbles in JavaScript + +We use [PNotify](https://sciactive.com/pnotify/) to show notifications from JavaScript, on the same page. Check out its page for more detailed use. Basic example: + +```php +new PNotify({ + title: "Operation successful", + text: "You have deleted the internet.", + type: "success" +}); + +// available types: success, info, warning, error +// PLEASE NOTE it's "error" here, not "danger" +``` + + +## 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(http://github.com/$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 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```. diff --git a/3.6/base-how-to.md b/3.6/base-how-to.md new file mode 100644 index 00000000..7516264e --- /dev/null +++ b/3.6/base-how-to.md @@ -0,0 +1,237 @@ +# How To for Backpack\Base + +--- + + +## Customize the menu or sidebar + +During installation, Backpack publishes a few files in you ```resources/views/vendor/backpack/base/inc``` folder. In there, you'll also find: +- ```sidebar_content.php``` +- ```topbar_left_content.php``` +- ```topbar_right_content.php``` + +Change those files as you please. + + +## 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 from the language file, and return a view: ```backpack::dashboard```. + +In order to place something else inside that view, 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/base/dashboard.blade.php``` file: + +```html +@extends('backpack::layout') + +@section('header') +
    +

    + {{ trans('backpack::base.dashboard') }}{{ trans('backpack::base.first_page_you_see') }} +

    + +
    +@endsection + + +@section('content') +
    +
    +
    +
    +
    {{ trans('backpack::base.login_status') }}
    +
    + +
    {{ trans('backpack::base.logged_in') }}
    +
    +
    +
    +@endsection +``` + +To use information from the database, [use view composers](https://laravel.com/docs/5.7/views#view-composers) to push variables inside this view, when it's loaded. Or better yet, load all your dashboard information using AJAX calls, if you're loading charts, reports, etc, and the DB queries might take a long time. + +Take a look at the [AdminLTE dashboards](https://adminlte.io/themes/AdminLTE/index.html) - you can easily use whatever content block you want from there. + +## Customizing the general layout/design + +See [the docs](/docs/{{version}}/base-about#layout-design). + +## 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'); +}); +``` + + +## Customize the look and feel of AdminLTE (using CSS) + +In ```config/app.php``` you should have a config option that looks like this: + +```php + // Overlays - CSS files that change the look and feel of the admin panel + 'overlays' => [ + 'vendor/backpack/base/backpack.bold.css', + // 'vendor/backpack/base/backpack.content.is.king.css', // opinionized borderless alternative + ], +``` + +If you don't (it was added in Base 0.9.9), you can create it. + +This config option allows you to add CSS files that add style _on top_ of AdminLTE, to make it look different. Our ```backpack.bold.css``` file is included by default, which makes AdminLTE look more modern. But if you want your backend to match your front-end, you can create a CSS file anywhere inside your ```public``` folder, and add it here. + +For example, if you're using the [Stack HTML template](https://themeforest.net/item/stack-multipurpose-html-with-page-builder/19337626?ref=medium_rare) on your front-end, you can just [add this overlay](https://gist.github.com/tabacitu/4f7eae0519e37aef46cbb959b8ab01a9) to make AdminLTE look very similar. + + +## Use separate login/register forms for users and admins + +This is a default in Backpack\Base 1.0.0. + +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\Base 1.0.0. + + +## 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 tge 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 BackpackUser + +By default, authentication and everything else inside Backpack is done using the ```Backpack\Base\app\Models\BackpackUser``` model, which extends Laravel's default ```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; +- making sure everything inside ```BackpackUser``` is also inside your new model (this is important for recovering password, etc); + + + +## 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. + + + +## Manually install Base + +If for any reason the Backpack/Base installation process fails for you, you can manually run all the commands in the installer, which are listed below. Failure to install can happens sometimes if the user does not have enough permissions (sudo access is needed) or if the composer command is not registered (and ```php composer``` needs to be run instead). + +```bash +# Install backpack/generators +composer require backpack/generators --dev + +# Install laracasts/generators +composer require laracasts/generators:dev-master --dev + +# Publish configs, langs, views and AdminLTE files +php artisan vendor:publish --provider="Backpack\Base\BaseServiceProvider" --tag="minimum" + +# Publish config for notifications - prologue/alerts +php artisan vendor:publish --provider="Prologue\Alerts\AlertsServiceProvider" + +# Generate users table (using Laravel's default migrations) +php artisan migrate + +# Publish the BackpackUser model inside your app/Models directory +php artisan backpack:base:publish-user-model + +# Publish the CheckIfAdmin middleware inside your app/Http/Middleware directory +php artisan backpack:base:publish-middleware +``` diff --git a/3.6/crud-api.md b/3.6/crud-api.md new file mode 100644 index 00000000..1c22bb4a --- /dev/null +++ b/3.6/crud-api.md @@ -0,0 +1,476 @@ +# API + +--- + +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 + + +### ListEntries + + +#### 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); +``` + +- **removeButton()** - remove a button from whatever stack it's in +```php +$this->crud->removeButton($name); +``` + +- **removeButtonFromStack()** - remove a button from a particular stack +```php +$this->crud->removeButtonFromStack($name, $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(); +``` + + +### Show + +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 last parameter is always the form - ```create``` or ```update```. If missing, it's assumed ```both```. + +- **addField()** - add one field to the create / update or both forms +```php +$this->crud->addField($field_definition_array, 'update/create/both'); +$this->crud->addField('db_column_name', 'update/create/both'); // 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 to the create / update or both forms +```php +$this->crud->addFields($array_of_fields_definition_arrays, 'update/create/both'); +``` + +- **modifyField()** - change the attributes of an existing field +```php +$this->crud->modifyField($name, $modifs_array, 'update/create/both'); +``` + +- **removeField()** - remove a given field from a given operation +```php +$this->crud->removeField('name', 'update/create/both'); +``` + +- **removeFields()** - remove multiple fields from a given operation +```php +$this->crud->removeFields($array_of_names, 'update/create/both'); +``` + +- **removeAllFields()** - remove all registered fields from both create and update operations +```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, 'create'); +$this->crud->setRequiredFields(UpdateRequest::class, 'edit'); +``` + + +### Reorder + +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. + +- **enableReorder()** - enable the Reorder functionality +```php +$this->crud->enableReorder('label_name', 3); +// NOTE: the second parameter is the maximum nesting depth; this example will prevent the user from creating trees deeper than 3 levels; +// NOTE: you also need to do allow access to the right users: +$this->crud->allowAccess('reorder'); +``` + +- **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(); +``` + + +### Revisions + +A.k.a. Audit Trail. Tracks all changes to an entry and provides an interface to revert to a previous state. In order to use this, you also need to ```use \Venturecraft\Revisionable\RevisionableTrait;```. Please check out the [Revision Operation](/docs/{{version}}/crud-operation-revisions) for more info. + +```php +$this->crud->allowAccess('revisions'); +``` + + +## 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']); +``` + +- **denyAccess()** - prevent users from accessing one or multiple operations +```php +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); +``` + +- **hasAccess()** - check if the current user has access to one or multiple operations +```php +$this->crud->hasAccess('add'); // returns true/false +$this->crud->hasAccessOrFail('add'); // 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('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); +``` + +### 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 + +- **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"); +``` \ No newline at end of file diff --git a/3.6/crud-basics.md b/3.6/crud-basics.md new file mode 100644 index 00000000..a629169e --- /dev/null +++ b/3.6/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 **controller** - holds the logic for the all operations an admin can perform on that Eloquent model; will be generated in ```app/Http/Controllers/Admin```; +- a **request** file - used to validate Create and Update forms; will be generated in ```app/Http/Requests```; +- a resource **route** - points to the controller above; will be generated in ```routes/backpack/custom.php```; + +**The only difference** between building it from scratch and using Backpack\CRUD** is that: +- your controller will be extending ```Backpack\CRUD\app\Http\Controllers\CrudController```**, which already has the logic for a few operations: Create, Update, Delete, ListEntries, Show, Reorder, Revisions. +- 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; +- **you can _easily_ overwrite what happens inside each default operation**; +- **you can _easily_ add 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 CrudController; 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; + + +## Files + +For a ```Tag``` entity, your CRUD Panel would consist of: +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); +- a route inside ```routes/backpack/custom.php```; +- your existing model (```app/Models/Tag.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/3.6/crud-buttons.md b/3.6/crud-buttons.md new file mode 100644 index 00000000..090d9bd4 --- /dev/null +++ b/3.6/crud-buttons.md @@ -0,0 +1,201 @@ +# Buttons + +--- + + +## About + +Buttons are used inside the ListEdit operation, to allow the admin to trigger other operations. Some point to entirely new routes (```create```, ```update```, ```show```), others perform the operation on the current page using AJAX (```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) + +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 + +Backpack adds a few buttons by default: +- ```create``` to the ```top``` stack; +- ```update``` and ```delete``` to the ```line``` stack; + +Default buttons are invisible if an operation has been disabled. For example, you can: +- hide the "delete" button using ```$this->crud->denyAccess('delete')```; +- show a "preview" button by using ```$this->crud->allowAccess('show')```; + + +### Buttons API + +Here are a few things you can call in your EntityCrudController's ```setup()``` method, to manipulate buttons: + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; + +// add a button; possible types are: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); + +// add a button whose HTML is returned by a method in the CRUD model +$this->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 +$this->crud->addButtonFromView($stack, $name, $view, $position); + +// remove a button +$this->crud->removeButton($name); + +// remove a button for a certain stack (top, line, bottom) +$this->crud->removeButtonFromStack($name, $stack); +``` + +### Overwriting a Default Button + +Before showing any buttons, Backpack will check your ```resources\views\vendor\backpack\crud\buttons``` directory, to see if you've overwritten any default buttons. If it finds a blade file with the same name there as the default buttons, it will use your blade file, instead of the default. + +That means **you can overwrite an existing button simply by creating a blade file with the same name inside this directory**. + + +### Creating a Custom Button + +To create a custom button: +- create a new blade file in ```resources\views\vendor\backpack\crud\buttons```; +- add that button using the ```addButton()``` syntax above, in the EntityCrudControllers you want, inside the ```setup()``` method; + +In this 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; + + +## 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')) + 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::setup()```: +```php +$this->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::setup()```: +```php +// add a button whose HTML is returned by a method in the CRUD model +$this->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_javascript')```, because the Backpack default layout has an ```after_javascript``` 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::setup()```: +```php +$this->crud->addButtonFromView('top', 'import', 'view', 'crud::buttons.import', 'end'); +``` diff --git a/3.6/crud-cheat-sheet.md b/3.6/crud-cheat-sheet.md new file mode 100644 index 00000000..60174261 --- /dev/null +++ b/3.6/crud-cheat-sheet.md @@ -0,0 +1,307 @@ +# 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 + + +### ListEntries + + +#### Columns + +Methods: addColumn(), addColumns(), modifyColumn(), removeColumn(), removeColumns(), setColumnDetails(), setColumnsDetails(), setColumns(), beforeColumn(), afterColumn(), makeFirstColumn() + +```php +// Manipulate what columns are shown in the table view. +$this->crud->addColumn($column_definition_array); // add a column, at the end of the stack +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); // add multiple columns, at the end of the stack +$this->crud->modifyColumn($name, $modifs_array); +$this->crud->removeColumn('column_name'); // remove a column from the stack +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns(); // set the columns you want in the table view, either as array of column names, or multidimensional array with all columns detailed with their types + +// ------ REORDER COLUMNS +$this->crud->addColumn()->beforeColumn('name'); // will show this before the given column +$this->crud->addColumn()->afterColumn('name'); // will show this after the given column + +$this->crud->addColumn()->makeFirstColumn(); + // will make this column the first one in the list + // you need to also specify priority 1 in your addColumn statement for details_row or responsive expand buttons to show +``` + + +#### Buttons + +Methods: addButton(), addButtonFromModelFunction(), addButtonFromView(), removeButton(), removeButtonFromStack() + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; +$this->crud->addButton($stack, $name, $type, $content, $position); // add a button; possible types are: view, model_function +$this->crud->addButtonFromModelFunction($stack, $name, $model_function_name, $position); // add a button whose HTML is returned by a method in the CRUD model +$this->crud->addButtonFromView($stack, $name, $view, $position); // add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + + +#### Filters + +Methods: addFilter(), modifyFilter(), removeFilter(), removeAllFilters(), filters() + +```php +// Manipulate what filters are shown in the table view. +// +// Note: check out CRUD > Features > Filters in the docs to see examples of $filter_definition_array +$this->crud->addFilter($filter_definition_array, $values, $filter_logic); +$this->crud->modifyFilter($name, $modifs_array); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +$this->crud->filters(); // gets all the filters +``` + + +#### 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. +$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->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. +$this->crud->enableExportButtons(); +``` + + +#### Responsive Table + +Methods: enableResponsiveTable(), disableResponsiveTable() + +```php +$this->crud->disableResponsiveTable(); +$this->crud->enableResponsiveTable(); +``` + + +#### Persistent Table + +Methods: enablePersistenTable(), disablePersistenTable() + +```php +$this->crud->disablePersistentTable(); +$this->crud->enablePersistentTable(); +``` + + +#### Page Length + +Methods: setDetaultPageLength(), setPageLengthMenu() + +```php +$this->crud->setDefaultPageLength(10); // number of rows shown in list view +$this->crud->setPageLengthMenu([100, 200, 300]); // page length menu to show in the list view +``` + + +#### Actions Column + +Methods: setActionColumnPriority() + +```php +// make the actions column (in the table view) hide when not enough space is available, by giving it an unreasonable priority +$this->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. +$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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->request->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: addField(), addFields(), modifyField(), modifyFields(), removeField(), removeFields(), removeAllFields(), beforeField(), afterField() + +```php +// ------ +// FIELDS +// ------ +// Manipulate what fields are shown in the create / update forms. +// +// Note: check out CRUD > Features > Field Types in the docs to see examples of $field_definition_array + +$this->crud->addField($field_definition_array, 'update/create/both'); +$this->crud->addField('db_column_name', 'update/create/both'); // a lazy way to add fields: let the CRUD decide what field type it is and set it automatically, along with the field label +$this->crud->addFields($array_of_fields_definition_arrays, 'update/create/both'); +$this->crud->modifyField($name, $modifs_array, 'update/create/both'); +$this->crud->removeField('name', 'update/create/both'); +$this->crud->removeFields($array_of_names, 'update/create/both'); +$this->crud->removeAllFields(); + +// Note: the last parameter is always the form - create or update; if missing, it's assumed 'both'; + + +// ------ REORDER FIELDS +$this->crud->addField()->beforeField('name'); // will show this before the given field +$this->crud->addField()->afterField('name'); // will show this after the given field +``` + + +### Reorder + +Methods: enableReorder(), disableReorder(), isReorderEnabled() + +```php +// Show a reorder button in the table view, next to Add +// Provide an interface to reorder & nest elements, provided the parent_id, lft, rgt, depth columns are in the database, and fillable on the model. +$this->crud->enableReorder('label_name', 3); +// NOTE: the second parameter is the maximum nesting depth; this example will prevent the user from creating trees deeper than 3 levels; +// NOTE: you also need to do allow access to the right users: $this->crud->allowAccess('reorder'); + +$this->crud->disableReorder(); +$this->crud->isReorderEnabled(); // return true/false + +``` + + +### 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 +$this->crud->allowAccess('revisions'); +``` + + +## All Operations + +Methods: allowAccess(), denyAccess(), hasAccess(), 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. + +$this->crud->allowAccess('list'); +$this->crud->allowAccess(['list', 'create', 'delete']); +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); + +$this->crud->hasAccess('add'); // returns true/false +$this->crud->hasAccessOrFail('add'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false + +// ------------- +// EAGER LOADING +// ------------- + +// eager load a relationship +$this->crud->with('relationship_name'); + +// ------------ +// CUSTOM VIEWS +// ------------ + +// use a custom view for a CRUD operation +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); + +// ------- +// GETTERS +// ------- + +$this->crud->getEntry($entry_id); +$this->crud->getEntries(); + +$this->crud->getFields('create/update/both'); + +// in your update() method, after calling parent::updateCrud() +$this->crud->getCurrentEntry(); + +// ------- +// OPERATIONS +// ------- + +$this->crud->setOperation('ListEntries'); +$this->crud->getOperation(); + +// ------- +// ACTIONS +// ------- + +$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 + +$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 + +$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 + +// --------------------------- +// CrudPanel Basic Information +// --------------------------- +$this->crud->setModel("App\Models\Example"); +$this->crud->setRoute("admin/example"); +// OR $this->crud->setRouteName("admin.example"); +$this->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 +$this->crud->setRequiredFields(StoreRequest::class, 'create'); +$this->crud->setRequiredFields(UpdateRequest::class, 'edit'); +``` diff --git a/3.6/crud-columns.md b/3.6/crud-columns.md new file mode 100644 index 00000000..a835fb3c --- /dev/null +++ b/3.6/crud-columns.md @@ -0,0 +1,701 @@ +# 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) +- [```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) + + +### Columns API + +Inside your ```setup()``` 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]); + +// 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]); +``` + +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(); +``` + + +## Default Column Types + + +### array + +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 + +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" +], +``` + + +### 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(); +``` + + +### closure + + +Show custom HTML based on a closure you specify in your EntityCrudController. Please note this column does not escape HTML before rendering. You need to do that yourself, if you consider it necessary. + +```php +[ + 'name' => 'created_at', + 'label' => 'Created At', + 'type' => 'closure', + 'function' => function($entry) { + return 'Created on '.$entry->created_at; + } +], +``` + + +### date + + +The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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/base.php``` file), whether the attribute is casted 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 +], +``` + + +### image + + +Show a thumbnail image. + +```php +[ + 'name' => 'profile_image', // The db column name + 'label' => "Profile image", // Table column heading + 'type' => 'image', + // 'prefix' => 'folder/subfolder/', + // optional width/height if 25px is not ok with you + // 'height' => '30px', + // 'width' => '30px', +], +``` + + +### markdown + + +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', +], +``` + + +### 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 +], +``` +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.''; +} +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + + +### 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 +], +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + + +### 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 +], +``` + + +### 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; + + +### 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. + +``` +$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; + + +### 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' +], +``` + + +### 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 +], +``` + + +### 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_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 +], +``` + + +### table + + +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' + ] +], +``` + + +### upload_multiple + + +The ```table``` 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 +], +``` + + +### video + + +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', +], +``` + + +### 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 +], +``` + + +## 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:crud:publish columns/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:crud:publish columns/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 blad 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.*'); + } +]); +``` + +If you want a column to not be orderable at all, just pass ```'orderable' => false``` + + +### 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, // sure, why not +]); +``` + +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 +]); +``` + + +### 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. diff --git a/3.6/crud-fields.md b/3.6/crud-fields.md new file mode 100644 index 00000000..e2e889b1 --- /dev/null +++ b/3.6/crud-fields.md @@ -0,0 +1,1686 @@ +# 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). + + +### Mandatory Field Attributes + +For each of them, you only need to define it properly in the Controller. All field types will need at least three things: +- 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, your field definition array should look like: +```php +[ + 'name' => 'description', + 'type' => 'textarea', + 'label' => 'Article Description', +] +``` + + +### Optional Field Attributes + +There are a few optional attributes on all default field types, that you can use to easily achieve a few common cutomizations: + +```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 + 'wrapperAttributes' => [ + '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); +- **wrapperAttributes** - change or add actual HTML attributes to the div that contains the input; + + +### Fields API + +To manipulate fields, you can use the methods below. As a second parameter, you can specify the operation you want that to work for (```create``` or ```update```). If you want it to work for both, don't use the second parameter. + +```php +// add a field to both Create and Update operation +$this->crud->addField($field_definition_array); + +// add a field only to the Update operation +$this->crud->addField($field_definition_array, 'update'); + +// shorthand: add a text field to both Create and Update operations +$this->crud->addField('db_column_name'); + +// add multiple fields +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); + +// change the attributes of a field +$this->crud->modifyField($name, $modifs_array); + +// remove a field from both operations +$this->crud->removeField('name'); + +// remove multiple fields from both operations +$this->crud->removeFields($array_of_names); + +// remove all fields from all operations +$this->crud->removeAllFields(); + +// FIELD ORDER + +// add a field before a given field +$this->crud->addField($field_definition_array)->beforeField('name'); + +// add a field after a given field +$this->crud->addField($field_definition_array)->afterField('name'); +``` + + +### Extra Fields Features + + +#### Fake Fields (all stored as JSON in the database) + +In case you want to store insignificant information for an entry that doesn't need a database column, you can add any number of Fake Fields, and all their information will be stored inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. + +**Step 1.** Use the fake attribute on your field: +```php +[ + '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 casted as array in ```$casts```; + +>If you need your fakes to also be translatable, remember to also place ```extras``` in your model's ```$translatable``` property. + +Example: +```php +[ + 'name' => 'meta_title', + 'label' => "Meta Title", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + 'name' => 'meta_description', + 'label' => "Meta Description", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + '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. + + +#### Split Fields into Tabs + +You can now split your create/edit inputs into multiple tabs. + +![CRUD Fields Tabs](https://backpackforlaravel.com/uploads/docs/operations/create_tabs.png) + +In order to use this feature, you just need to specify the tab name for each of your fields. Example: + +```php +$this->crud->addField([ // select_from_array + 'name' => 'select_from_array', + 'label' => "Select from array", + 'type' => 'select_from_array', + 'options' => ['one' => 'One', 'two' => 'Two', 'three' => 'Three'], + 'allows_null' => false, + 'allows_multiple' => true, + 'tab' => 'Tab name here', +]); +``` + +If you forget to specify a tab name for a field, Backpack will place it above all tabs. + + + +## Default Field Types + + +### address_algolia + +Use [Algolia Places autocomplete](https://community.algolia.com/places/) 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 address string. + +```php +[ // Address + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_algolia', + // optional + 'store_as_json' => true +], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://laravel.com/docs/eloquent-mutators#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. + + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-3-5/fields/address.png) + + +### address_google + +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 +[ // Address + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_google', + // optional + 'store_as_json' => true +], +``` + +Using Google Places API is dependant 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' + ], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://laravel.com/docs/eloquent-mutators#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. + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-3-5/fields/address_google.png) + + +### browse + +If you've chosen to use [elFinder](http://elfinder.org/) upon Backpack installation, this button allows the admin to open [elFinder](http://elfinder.org/) and select a file from there. + +```php +[ // Browse + 'name' => 'image', + 'label' => 'Image', + 'type' => 'browse' +], +``` + + +Input preview: + +![CRUD Field - browse](https://backpackforlaravel.com/uploads/docs-3-5/fields/browse.png) + +Onclick preview: + +![CRUD Field - browse popup](https://backpackforlaravel.com/uploads/docs-3-5/fields/browse_popup.png) + + +### browse_multiple + +Open elFinder and select multiple files from there. + +```php +[ // Browse multiple + 'name' => 'files', + 'label' => 'Files', + 'type' => 'browse_multiple', + // 'multiple' => true, // enable/disable the multiple selection functionality + // 'mime_types' => null, // visible mime prefixes; ex. ['image'] or ['application/pdf'] +], +``` + +We recommend you cast your attribute as ```array``` on your model. That way, when you do ```$entry->files``` you get a nice array. The field will work even if you don't cast. + +Input preview: + +![CRUD Field - browse_multiple](https://backpackforlaravel.com/uploads/docs-3-5/fields/browse_multiple.png) + + +### base64_image + +Upload an image and store it in the database as Base64. Notes: +- make sure the column type is LONGBLOB; +- detailed [instructions and customizations here](https://github.com/Laravel-Backpack/CRUD/pull/56#issue-164712261); + +```php +$this->crud->addField([ // base64_image + '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-3-5/fields/base64_image.png) + + +### checkbox + +Checkbox for true/false. + +```php +[ // Checkbox + 'name' => 'active', + 'label' => 'Active', + 'type' => 'checkbox' +], +``` + +Input preview: + +![CRUD Field - checkbox](https://backpackforlaravel.com/uploads/docs-3-5/fields/checkbox.png) + + +### checklist + +```php +[ + 'label' => 'Roles', + 'type' => 'checklist', + 'name' => 'roles', + 'entity' => 'roles', + 'attribute' => 'name', + 'model' => "Backpack\PermissionManager\app\Models\Role", + 'pivot' => true, +]); +``` + +Input preview: + +![CRUD Field - checklist](https://backpackforlaravel.com/uploads/docs-3-5/fields/checklist.png) + + +### checklist_dependency + +```php + +// two interconnected entities +'label' => 'User Role Permissions', +'field_unique_name' => 'user_role_permission', +'type' => 'checklist_dependency', +'name' => 'roles_and_permissions', // the methods that defines the relationship in your Model +'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 + ], + '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 + ], + ], +], +``` + +Input preview: + +![CRUD Field - checklist_dependency](https://backpackforlaravel.com/uploads/docs-3-5/fields/checklist_dependency.png) + + +### ckeditor + +Show a wysiwyg CKEditor to the user. + +```php +[ // CKEditor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'ckeditor', + // optional: + 'extra_plugins' => ['oembed', 'widget'], + 'options' => [ + 'autoGrow_minHeight' => 200, + 'autoGrow_bottomSpace' => 50, + 'removePlugins' => 'resize,maximize', + ] +], +``` + +Input preview: + +![CRUD Field - ckeditor](https://backpackforlaravel.com/uploads/docs-3-5/fields/ckeditor.png) + + +### color + +```php +[ // Color + 'name' => 'background_color', + 'label' => 'Background Color', + 'type' => 'color' +], +``` + +Input preview: + +![CRUD Field - color](https://backpackforlaravel.com/uploads/docs-3-5/fields/color.png) + + +### color_picker + +Show a pretty colour picker using [Bootstrap Colorpicker](https://itsjavi.com/bootstrap-colorpicker/). + +```php +[ // color_picker + 'label' => 'Background Color', + 'name' => 'background_color', + 'type' => 'color_picker', + 'color_picker_options' => ['customClass' => 'custom-class'] +] +``` + +Input preview: + +![CRUD Field - color_picker](https://backpackforlaravel.com/uploads/docs-3-5/fields/color_picker.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 +[ // CustomHTML + 'name' => 'separator', + 'type' => 'custom_html', + 'value' => '
    ' +], +``` + + +### date + +```php +[ // Date + 'name' => 'birthday', + 'label' => 'Birthday', + 'type' => 'date' +], +``` + +Input preview: + +![CRUD Field - date](https://backpackforlaravel.com/uploads/docs-3-5/fields/date.png) + + +### date_picker + +Show a pretty [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/). + +```php +[ // 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/eloquent-mutators#attribute-casting) on your model (cast to date). + + +Input preview: + +![CRUD Field - date_picker](https://backpackforlaravel.com/uploads/docs-3-5/fields/date_picker.png) + + +### date_range + +Starting with Backpack\CRUD 3.1.59 + +Show a DateRangePicker and let the user choose a start date and end date. + +```php + [ + 'name' => 'event_date_range', // a unique name for this field + 'start_name' => 'start_date', // the db column that holds the start_date + 'end_name' => 'end_date', // the db column that holds the end_date + 'label' => 'Event Date Range', + 'type' => 'date_range', + // OPTIONALS + 'start_default' => '1991-03-28 01:01', // default value for start_date + 'end_default' => '1991-04-05 02:00', // default value for end_date + 'date_range_options' => [ // options sent to daterangepicker.js + 'timePicker' => true, + 'locale' => ['format' => 'DD/MM/YYYY HH:mm'] + ] + ] +``` + +Please note it is recommended that you use [attribute casting](https://laravel.com/docs/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-3-5/fields/date_range.png) + + +### datetime + +```php +[ // DateTime + 'name' => 'start', + 'label' => 'Event start', + 'type' => 'datetime' +], +``` + +**Please note:** if you're using datetime [attribute casting](https://laravel.com/docs/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 formal will cause some errors. + +Input preview: + +![CRUD Field - datetime](https://backpackforlaravel.com/uploads/docs-3-5/fields/datetime.png) + + +### datetime_picker + +Show a [Bootstrap Datetime Picker](https://eonasdan.github.io/bootstrap-datetimepicker/). + +```php +[ // DateTime + 'name' => 'start', + 'label' => 'Event start', + 'type' => 'datetime_picker', + // optional: + 'datetime_picker_options' => [ + 'format' => 'DD/MM/YYYY HH:mm', + 'language' => 'fr' + ], + 'allows_null' => true, + // 'default' => '2017-05-12 11:59:59', +], +``` + +Input preview: + +![CRUD Field - datetime_picker](https://backpackforlaravel.com/uploads/docs-3-5/fields/datetime_picker.png) + + +### email + +```php +[ // Email + 'name' => 'email', + 'label' => 'Email Address', + 'type' => 'email' +], +``` + +Input preview: + +![CRUD Field - email](https://backpackforlaravel.com/uploads/docs-3-5/fields/email.png) + + + +### enum + +Show a select with the values in the database for that ENUM field. Requires that the db column type is "enum". If the db column allows null, the " - " value will also show up in the select. + +```php +[ // Enum + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum' +], +``` + +PLEASE NOTE the enum field only works for MySQL databases. + +Input preview: + +![CRUD Field - enum](https://backpackforlaravel.com/uploads/docs-3-5/fields/enum.png) + + +### hidden + +Include an in the form. + +```php +[ // Hidden + 'name' => 'status', + 'type' => 'hidden' +], +``` + + +### icon_picker + +[/block] +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 +[ + '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-3-5/fields/icon_picker.png) + + +### image + +Upload an image and store it on the disk. + +**Step 1.** Show the field. +```php +$this->crud->addField([ // image + 'label' => "Profile Image", + 'name' => "image", + 'type' => 'image', + 'upload' => true, + 'crop' => true, // set to true to allow cropping, false to disable + 'aspect_ratio' => 1, // omit or set to 0 to allow any aspect ratio + // 'disk' => 's3_bucket', // in case you need to show images from a different disk + // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; +]); +``` + +**Step 2.** Set a mutator on your Model, so that the file can be stored. You can use this boilerplate code and modify it to match your use case: + +```php +// .. + +use Illuminate\Support\Str; + +// .. + +Class Product extends Model +{ + // .. + + public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = config('backpack.base.root_disk_name'); // or use your own disk, defined in config/filesystems.php + $destination_path = "public/uploads/folder_1/folder_2"; // path relative to the disk above + + // if the image was erased + if ($value==null) { + // delete the image from disk + \Storage::disk($disk)->delete($this->{$attribute_name}); + + // set null in the database column + $this->attributes[$attribute_name] = null; + } + + // if a base64 was sent, store it in the db + if (starts_with($value, 'data:image')) + { + // 0. Make the image + $image = \Image::make($value)->encode('jpg', 90); + // 1. Generate a filename. + $filename = md5($value.time()).'.jpg'; + // 2. Store the image on disk. + \Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream()); + // 3. Save the public path to the database + // but first, remove "public/" from the path, since we're pointing to it from the root folder + // that way, what gets saved in the database is the user-accesible URL + $public_destination_path = Str::replaceFirst('public/', '', $destination_path); + $this->attributes[$attribute_name] = $public_destination_path.'/'.$filename; + } + } + +// .. +``` +> **The uploaded images are not deleted for you.** If you delete an entry (using the CRUD or anywhere inside your app), the image file won't be deleted from the disk. +> If you're NOT using soft deletes on that Model and want the image to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +> ```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } + ``` + +**A note about aspect_ratio** +The value for aspect ratio is a float that represents the ratio of the cropping rectangle height and width. By way of example, + +- Square = 1 +- Landscape = 2 +- Portrait = 0.5 + +And you can, of course, use any value for more extreme rectangles. + +Input preview: + +![CRUD Field - image](https://backpackforlaravel.com/uploads/docs-3-5/fields/image.png) + + + +### month + +```php +[ // Month + 'name' => 'month', + 'label' => 'Month', + 'type' => 'month' +], +``` + +Input preview: + +![CRUD Field - month](https://backpackforlaravel.com/uploads/docs-3-5/fields/month.png) + + +### number + +Shows an input type=number to the user, with optional prefix and suffix: + +```php +[ // 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-3-5/fields/number.png) + + +### page_or_link + +Select an existing page from PageManager or an internal or external link. It's used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: +```php +[ // PageOrLink + 'name' => 'type', + 'label' => "Type", + 'type' => 'page_or_link', + 'page_model' => '\Backpack\PageManager\app\Models\Page' +], +``` + +Input preview: + +![CRUD Field - page_or_link](https://backpackforlaravel.com/uploads/docs-3-5/fields/page_or_link.png) + + +### password + +```php +[ // Password + 'name' => 'password', + 'label' => 'Password', + 'type' => 'password' +], +``` + +Input preview: + +![CRUD Field - password](https://backpackforlaravel.com/uploads/docs-3-5/fields/password.png) + + +### 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 +[ + '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-3-5/fields/radio.png) + + +### range + +```php +[ // Range + 'name' => 'range', + 'label' => 'Range', + 'type' => 'range' +], +``` + +Input preview: + +![CRUD Field - range](https://backpackforlaravel.com/uploads/docs-3-5/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 +[ // 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 + 'model' => "App\Models\Tag", + + // 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 +], +``` + +Input preview: + +![CRUD Field - select](https://backpackforlaravel.com/uploads/docs-3-5/fields/select.png) + + + +### select_grouped + +Display a select where the options are grouped by a second entity (like Categories). + +```php +[ // 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 +], +``` + +Input preview: + +![CRUD Field - select_grouped](https://backpackforlaravel.com/uploads/docs-3-5/fields/select_grouped.png) + + +### select2 (1-n relationship) + +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 +[ // Select2 + 'label' => "Category", + 'type' => 'select2', + '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 + 'model' => "App\Models\Tag", // foreign key model + + // optional + 'default' => 2, // set the default value of the select2 + '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 +], +``` + +Input preview: + +![CRUD Field - select2](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_nested.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 relationships should already be defined on your models as hasMany() or belongsToMany(). + +```php +[ // SelectMultiple = n-n relationship (with pivot table) + 'label' => "Tags", + '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 + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + + // 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 +], +``` + +Input preview: + +![CRUD Field - select_multiple](https://backpackforlaravel.com/uploads/docs-3-5/fields/select_multiple.png) + + + + +### select2_multiple (n-n relationship) + +[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 relationships should already be defined on your models as hasMany() or belongsToMany(). + +```php +[ // Select2Multiple = n-n relationship (with pivot table) + '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 + 'model' => "App\Models\Tag", // foreign key model + '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 +], +``` + +Input preview: + +![CRUD Field - select2_multiple](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_multiple.png) + + +### select2_nested + +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 +[ // 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 + // 'model' => "App\Models\Category", // force foreign key model +], +``` + +Input preview: + +![CRUD Field - select2_nested](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_nested.png) + + + +### select2_grouped + +Display a select2 where the options are grouped by a second entity (like Categories). + +```php +[ // select2_grouped + 'label' => 'Articles grouped by categories', + 'type' => 'select2_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 +], +``` + +Input preview: + +![CRUD Field - select2_grouped](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_grouped.png) + + + +### select_and_order + +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 +[ // select_and_order + 'name' => 'featured', + 'label' => "Featured", + 'type' => 'select_and_order', + 'options' => [ + 1 => "Option 1", + 2 => "Option 2" + ] +], +``` + +Also possible: + +```php +[ + '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-3-5/fields/select_and_order.png) + + + +### select_from_array + +Display a select with the values you want: + +```php +[ // 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-3-5/fields/select_from_array.png) + + +### select2_from_array + +Display a select2 with the values you want: + +```php +[ // select_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-3-5/fields/select2_from_array.png) + + +### select2_from_ajax + +Display a select2 that takes its values from an AJAX call. + +``` +[ + // 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' => 'city', // 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 + 'data_source' => url("http://github.com/api/category"), // url to controller search function (with /{id} should return model) + 'placeholder' => "Select a category", // placeholder for the select + 'minimum_input_length' => 2, // minimum characters to type before querying results + // 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'GET', // 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) + ] +``` + +Of course, you also need to create a controller and routes for the data_source above. Here's an example: + +``` +Route::get('/api/category', 'Api\CategoryController@index'); +Route::get('/api/category/{id}', 'Api\CategoryController@show'); +``` + +``` +input('q'); + $page = $request->input('page'); + + if ($search_term) + { + $results = Category::where('name', 'LIKE', '%'.$search_term.'%')->paginate(10); + } + else + { + $results = Category::paginate(10); + } + + return $results; + } + + public function show($id) + { + return Category::find($id); + } +} +``` + +Input preview: + +![CRUD Field - select2_from_array](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_from_array.png) + + +### select2_from_ajax_multiple + +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. + +``` +[ + // n-n relationship + 'label' => "End", // Table column heading + 'type' => "select2_from_ajax_multiple", + 'name' => 'city_id', // the column that contains the ID of that connected entity + 'entity' => 'city', // the method that defines the relationship in your Model + 'attribute' => "name", // foreign key attribute that is shown to user + 'model' => "App\Models\City", // foreign key model + 'data_source' => url("http://github.com/api/cities"), // url to controller search function (with /{id} should return model) + 'placeholder' => "Select a city", // placeholder for the select + 'minimum_input_length' => 2, // minimum characters to type before querying results + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + // '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) + ] +``` + +Of course, you also need to create a controller and routes for the data_source above. Here's an example: + +``` +Route::get('/api/category', 'Api\CategoryController@index'); +Route::get('/api/category/{id}', 'Api\CategoryController@show'); +``` + +``` +input('q'); + $page = $request->input('page'); + + if ($search_term) + { + $results = Category::where('name', 'LIKE', '%'.$search_term.'%')->paginate(10); + } + else + { + $results = Category::paginate(10); + } + + return $results; + } + + public function show($id) + { + return Category::find($id); + } +} +``` + +Input preview: + +![CRUD Field - select2_from_ajax_multiple](https://backpackforlaravel.com/uploads/docs-3-5/fields/select2_from_ajax_multiple.png) + + +### simplemde + +Show a [SimpleMDE markdown editor](https://simplemde.com/) to the user. + +```php +[ // SimpleMDE + 'name' => 'description', + 'label' => 'Description', + 'type' => 'simplemde', + // optional + // 'simplemdeAttributes' => [ + // 'promptURLs' => true, + // 'status' => false, + // 'spellChecker' => false, + // 'forceSync' => true, + // ], + // 'simplemdeAttributesRaw' => $some_json +], +``` + +Input preview: + +![CRUD Field - simplemde](https://backpackforlaravel.com/uploads/docs-3-5/fields/simplemde.png) + + +### summernote + +Show a [Summernote wysiwyg editor](http://summernote.org/) to the user. + +```php +[ // Summernote + 'name' => 'description', + 'label' => 'Description', + 'type' => 'summernote', + // 'options' => [], // easily pass parameters to the summernote JS initialization +], +``` + +Input preview: + +![CRUD Field - summernote](https://backpackforlaravel.com/uploads/docs-3-5/fields/summernote.png) + + +### table + +Show a table with multiple inputs per row and store the values as JSON in the database. The user can add more rows and reorder the rows as they please. + +```php +[ // 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://laravel.com/docs/eloquent-mutators#attribute-casting) on your model when working with JSON stored in database columns, and cast your this attribute to either ```object``` or ```array```. + +Input preview: + +![CRUD Field - table](https://backpackforlaravel.com/uploads/docs-3-5/fields/table.png) + + +### text + +The basic field type, all it needs is the two mandatory parameters: name and label. + +```php +[ // 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' + //], // extra HTML attributes and values your input might need + //'wrapperAttributes' => [ + //'class' => 'form-group col-md-12' + //], // extra HTML attributes for the field wrapper - mostly for resizing fields + //'readonly'=>'readonly', +], +``` + +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-3-5/fields/text.png) + + +### textarea + +Show a textarea to the user. + +```php +[ // Textarea + 'name' => 'description', + 'label' => 'Description', + 'type' => 'textarea' +], +``` + +Input preview: + +![CRUD Field - textarea](https://backpackforlaravel.com/uploads/docs-3-5/fields/textarea.png) + + +### time + +```php +[ // Time + 'name' => 'start', + 'label' => 'Start time', + 'type' => 'time' +], +``` + + +### tinymce + +Show a wysiwyg (TinyMCE) to the user. + +```php +[ // 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-3-5/fields/tinymce.png) + + +### upload + +**Step 1.** Show a file input to the user: +```php +[ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', + 'upload' => true, + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; +], +``` + +**Step 2.** In order to save/update/delete the file from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path); + + // return $this->{$attribute_name}; // uncomment if this is a translatable field + } +``` + +**How it works:** + +The field sends the file, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the file is stored on the disk. + +[The ```uploadFileToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/CrudTrait.php#L108-L129) will take care of everything for most use cases: + +```php +/** + * Handle file upload and DB storage for a file: + * - on CREATE + * - stores the file at the destination path + * - generates a name + * - stores the full path in the DB; + * - on UPDATE + * - if the value is null, deletes the file and sets null in the DB + * - if the value is different, stores the different file and updates DB value + * / +public function uploadFileToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (wether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the file to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } +``` + +Input preview: + +![CRUD Field - upload](https://backpackforlaravel.com/uploads/docs-3-5/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 1.** Show a multiple file input to the user: +```php +[ // Upload + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + 'upload' => true, + 'disk' => 'uploads' // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; +], +``` + +**Step 2.** In order to save/update/delete the files from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setPhotosAttribute($value) + { + $attribute_name = "photos"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path); + } +``` + +**Step 3.** Since the filenames are stored in the database as a JSON array, we're going to use [attribute casting](https://laravel.com/docs/eloquent-mutators#attribute-casting) on your model, so every time we get the filenames array from the database it's converted from a JSON array to a PHP array: +```php + protected $casts = [ + 'photos' => 'array' + ]; +``` + +**How it works:** + +The field sends the files, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the files are stored on the disk. + +[The ```uploadMultipleFilesToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/CrudTrait.php#L154-L189) will take care of everything for most use cases: + +``` +/** + * Handle multiple file upload and DB storage: + * - if files are sent + * - stores the files at the destination path + * - generates random names + * - stores the full path in the DB, as JSON array; + * - if a hidden input is sent to clear one or more files + * - deletes the file + * - removes that file from the DB. + * / +public function uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (wether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the files to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + if (count((array)$obj->photos)) { + foreach ($obj->photos as $file_path) { + \Storage::disk('public_folder')->delete($file_path); + } + } + }); + } +``` + +You might notice the field is using a ```clear_photos``` variable. Don't worry, you don't need it in your db table. That's just used to delete photos upon "update". If you use ```$fillable``` on your model, just don't include it. If you use ```$guarded``` on your model, place it in guarded. + +Input preview: + +![CRUD Field - upload_multiple](https://backpackforlaravel.com/uploads/docs-3-5/fields/upload_multiple.png) + + +### url + +[/block] +```php +[ // URL + 'name' => 'link', + 'label' => 'Link to video file', + 'type' => 'url' +], +``` + + +### video + +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 +[ // URL + 'name' => 'video', + 'label' => 'Link to video file on Youtube or Vimeo', + 'type' => 'video', +], +``` + +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://laravel.com/docs/eloquent-mutators#attribute-casting) in your model, to cast the video as ```array``` or ```object```. + + + +### view + +Load a custom view in the form. + +```php +[ + '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). + + +### week + +```php +[ // Week + 'name' => 'first_week', + 'label' => 'First week', + 'type' => 'week' +], +``` + +Input preview: + +![CRUD Field - week](https://backpackforlaravel.com/uploads/docs-3-5/fields/week.png) + + +### wysiwyg + +Show a wysiwyg (CKEditor) to the user. + +```php +[ // WYSIWYG Editor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'wysiwyg' +], +``` + + +## 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:crud:publish fields/field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:crud:publish fields/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. 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 +[ + // 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 + +
    + + + + {{-- HINT --}} + @if (isset($field['hint'])) +

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

    + @endif +
    + + +@if ($crud->checkIfFieldIsFirstOfItsType($field)) + {{-- 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 +@endif + +// Note: most of the times you'll want to use @if ($crud->checkIfFieldIsFirstOfItsType($field, $fields)) to only load CSS/JS once, even though there are multiple instances of it. +``` + +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; diff --git a/3.6/crud-filters.md b/3.6/crud-filters.md new file mode 100644 index 00000000..21bed0d1 --- /dev/null +++ b/3.6/crud-filters.md @@ -0,0 +1,457 @@ +# Filters + +--- + + +## About + +Backpack CRUD allows you to show a filters bar right above the entries table. When selected or modified, they reload the DataTables results. The search will then search within the filtered elements. + +![Backpack CRUD Filters](https://backpackforlaravel.com/uploads/docs/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::setup()```. + + +### Filters API + +In order to manipulate filters, you can use: + +```php +$this->crud->addFilter($options, $values, $filter_logic); + +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); + +$this->crud->filters(); // gets all the filters +``` + + +### Adding a filter + +When adding a filter 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 +$this->crud->addFilter([ // add a "simple" filter called Draft + '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) + $this->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: + // $this->crud->addClause('draft'); +}); +``` +> 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->request```; +> - 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 above are simple filters. + +```php +$this->crud->addFilter([ // simple filter + 'type' => 'simple', + 'name' => 'active', + 'label'=> 'Active' +], +false, +function() { // if the filter is active + // $this->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/filters/text.png) + +```php +$this->crud->addFilter([ // simple filter + 'type' => 'text', + 'name' => 'description', + 'label'=> 'Description' +], +false, +function($value) { // if the filter is active + // $this->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/filters/date.png) + +```php +$this->crud->addFilter([ // date filter + 'type' => 'date', + 'name' => 'date', + 'label'=> 'Date' +], +false, +function($value) { // if the filter is active, apply these constraints + // $this->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/filters/date_range.png) + +```php +$this->crud->addFilter([ // daterange filter + 'type' => 'date_range', + 'name' => 'from_to', + 'label'=> 'Date range' +], +false, +function($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); +``` + + +### 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/filters/dropdown.png) + +```php +$this->crud->addFilter([ // dropdown filter + 'name' => 'status', + 'type' => 'dropdown', + 'label'=> 'Status' +], [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', +], function($value) { // if the filter is active + // $this->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/filters/select2.png) + +```php +$this->crud->addFilter([ // select2 filter + 'name' => 'status', + 'type' => 'select2', + 'label'=> 'Status' +], function() { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function($value) { // if the filter is active + // $this->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 second parameter (the closure), with something like ```return \Backpack\NewsCRUD\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. You can decide yourself if the query for each element should use 'where' or 'orWhere', in the third parameter of the ```addFilter()``` method. + +![Backpack CRUD Select2_multiple Filter](https://backpackforlaravel.com/uploads/docs/filters/select2_multiple.png) + +```php +$this->crud->addFilter([ // select2_multiple filter + 'name' => 'status', + 'type' => 'select2_multiple', + 'label'=> 'Status' +], function() { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function($values) { // if the filter is active + // foreach (json_decode($values) as $key => $value) { + // $this->crud->addClause('where', 'published', $value); + // } +}); +``` + + +### 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/filters/select2_ajax.png) + +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'); +``` + +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'); + return $options; +} +``` + +3. Add the select2_ajax filter and for the second parameter ("values") specify the exact route. + +```php +$this->crud->addFilter([ // select2_ajax filter + 'name' => 'category_id', + 'type' => 'select2_ajax', + 'label'=> 'Category', + 'placeholder' => 'Pick a category' +], +url('http://github.com/admin/test/ajax-category-options'), // the ajax route +function($value) { // if the filter is active + // $this->crud->addClause('where', 'category_id', $value); +}); +``` + + +### Range + +Shows two number inputs, for min and max. + +![Backpack CRUD Range Filter](https://backpackforlaravel.com/uploads/docs/filters/range.png) + +```php +$this->crud->addFilter([ + 'name' => 'number', + 'type' => 'range', + 'label'=> 'Range', + 'label_from' => 'min value', + 'label_to' => 'max value' +], +false, +function($value) { // if the filter is active + $range = json_decode($value); + if ($range->from) { + $this->crud->addClause('where', 'number', '>=', (float) $range->from); + } + if ($range->to) { + $this->crud->addClause('where', 'number', '<=', (float) $range->to); + } +}); +``` + +### 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 +$this->crud->addFilter([ // custom filter view + 'name' => 'category_id', + 'type' => 'view', + 'view' => 'package::columns.column_type_name', // or path to blade file + 'label'=> 'Category', + 'placeholder' => 'Pick a category', +], +false, +function($value) { // if the filter is active + // $this->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 +$this->crud->addFilter([ // select2 filter + 'name' => 'published', + 'type' => 'select2', + 'label'=> 'Published' +], function() { + return \App\Models\Test::getEnumValuesAsAssociativeArray('published'); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'published', $value); +}); +``` + +Use a select2 to filter by a 1-n relationship: +```php +$this->crud->addFilter([ // select2 filter + 'name' => 'category_id', + 'type' => 'select2', + 'label'=> 'Category' +], function() { + return \App\Models\Category::all()->pluck('name', 'id')->toArray(); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'category_id', $value); +}); +``` + +Use a select2_multiple to filter Products by an n-n relationship: +```php +$this->crud->addFilter([ // select2_multiple filter + 'name' => 'tags', + 'type' => 'select2_multiple', + 'label'=> 'Tags' +], function() { // the options that show up in the select2 + return Product::all()->pluck('name', 'id')->toArray(); +}, function($values) { // if the filter is active + 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 +$this->crud->addFilter([ // add a "simple" filter called Published + 'type' => 'simple', + 'name' => 'published', + 'label'=> 'Published' +], +false, +function() { // if the filter is active (the GET parameter "published" exits) + $this->crud->addClause('published'); +}); +``` + +Use a simple filter to show the softDeleted items (trashed): +```php + $this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'trashed', + 'label'=> 'Trashed' + ], + false, + function($values) { // if the filter is active + $this->crud->query = $this->crud->query->onlyTrashed(); + }); +``` diff --git a/3.6/crud-how-to.md b/3.6/crud-how-to.md new file mode 100644 index 00000000..b85723d0 --- /dev/null +++ b/3.6/crud-how-to.md @@ -0,0 +1,331 @@ +# How To for Backpack\CRUD + +--- + +In addition the usual CRUD functionality, Backpack also allows you to do a few more complicated things: + + +## 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/crud``` folder, it will pick up those instead; you can use this method to overwrite a blade file for all CRUDs; +- 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 +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); +``` + + +## Customize CSS and JS for Default CRUD Operations + +Each default Backpack operation has its own CSS and JS file, in: +- ```public/vendor/backpack/crud/css``` +- ```public/vendor/backpack/crud/js``` + +If you don't find one there, you can create one, and Backpack will pick it up in that operation's view (ex: ```create.css``` or ```list.js```). + + +## Add Extra CRUD Routes + +Starting with Backpack\CRUD 3.2, you can use the ```with()``` method on ```CRUD::resource``` to better organize your routes. Something like this: + +```php +CRUD::resource('teams', 'Admin\TeamCrudController')->with(function(){ + // add extra routes to this resource + Route::get('teams/ajax-name-options', 'Admin\TeamCrudController@nameOptions'); + Route::get('teams/ajax-category-options', 'Admin\TeamCrudController@categoryOptions'); +}); +``` + + +## 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:crud:publish fields/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. + + +## 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); + }); + } + } +} +``` + + +## 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 + $this->crud->addColumn([ + 'label' => "Location", + 'type' => 'select', + 'name' => 'location_id', + 'entity' => 'location', + 'attribute' => 'title', + 'model' => "App\Models\Location" + ]); + + $this->crud->addColumn([ + '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" + ]); +``` + + +## Use the Media Library (File Manager) + +If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it's integrated into: +- TinyMCE (as "tinymce" fieldtype) +- CKEditor (as "ckeditor" fieldtype) +- CRUD "browse" fieldtype +- standalone, at the *your-project/admin/elfinder* route; + +For the integration, barryvdh's [laravel-elfinder](https://github.com/barryvdh/laravel-elfinder) package is used. + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs/media_library.png) + + +## Manually install Backpack/CRUD + +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) If you'd also like a file manager, run: +```bash +composer require barryvdh/laravel-elfinder +mkdir -p public/uploads +php artisan elfinder:publish +php artisan vendor:publish --provider="Backpack\CRUD\CrudServiceProvider" --tag="elfinder" +php artisan backpack:base:add-sidebar-content '
  • File manager
  • ' +``` + +3) Actually install CRUD: +```bash +php artisan vendor:publish --provider="Backpack\CRUD\CrudServiceProvider" --tag="public" +php artisan vendor:publish --provider="Backpack\CRUD\CrudServiceProvider" --tag="lang" +php artisan vendor:publish --provider="Backpack\CRUD\CrudServiceProvider" --tag="config" +``` + + +## 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 +$this->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) + $this->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. + + +## Add a select2 field that depends on another field + +The ```select2_from_ajax``` and ```select2_from_ajax_multiple``` fields allow you to filter the results of a select2, depending on what has already been selected in a form. Say you have to 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 you CrudController you would do: + +```php + + $this->crud->addField([ // SELECT2 + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category', + 'entity' => 'category', + 'attribute' => 'name', + ]); + + $this->crud->addField([ // select2_from_ajax: 1-n relationship + '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('http://github.com/api/article'), // url to controller search function (with /{id} should return model) + 'placeholder' => 'Select an article', // placeholder for the select + 'minimum_input_length' => 0, // minimum characters to type before querying results + 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST) + ]); +``` + +**DIFFERENT HERE**: ```minimum_input_length``` and ```dependencies```. + +2. That second select points to routes that need to be registered: + +```php +Route::get('api/article', 'App\Http\Controllers\Api\ArticleController@index'); +Route::get('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show'); +``` + +**DIFFERENT HERE**: Nothing. + +3. Then that controller would look something like this: + +```php +input('q'); + $form = collect($request->input('form'))->pluck('value', 'name'); + + $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 $options->paginate(10); + } + + 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. + + + +## Change the content class for an operation + +If you want to make the contents of an operation take more / less space from the window, 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 +$this->crud->setCreateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setUpdateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setListContentClass('col-md-8 col-md-offset-2'); +$this->crud->setShowContentClass('col-md-8 col-md-offset-2'); +$this->crud->setReorderContentClass('col-md-8 col-md-offset-2'); +$this->crud->setRevisionsTimelineContentClass('col-md-8 col-md-offset-2'); +``` diff --git a/3.6/crud-operation-clone.md b/3.6/crud-operation-clone.md new file mode 100644 index 00000000..d5f20552 --- /dev/null +++ b/3.6/crud-operation-clone.md @@ -0,0 +1,91 @@ +# Clone + +-- + + +## 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. + + +## Clone a Single Item + + +### How it Works + +Using AJAX, a POST request is performed towards ```/entity-name/{id}```, which points to the ```clone()``` method in your EntityCrudController. + + +### How to Use + +The ```clone()``` action is **disabled by default**. To enable it, you should use ```$this->crud->allowAccess('clone');``` inside your ```setup()``` method. This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```clone()``` method in your EntityCrudController: + +```php +public function clone($id) +{ + $this->crud->hasAccessOrFail('clone'); + $this->crud->setOperation('clone'); + + // whatever you want +} +``` + +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:crud:publish buttons/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. + + +### How to Use + +The ```bulkClone()``` action is **disabled by default**, and there are no buttons using it. To make the buttons show up, inside your ```setup()``` method you should: + +```php +$this->crud->enableBulkActions(); // if you haven't already + +$this->crud->allowAccess('clone'); +$this->crud->addButton('bottom', 'bulk_clone', 'view', 'crud::buttons.bulk_clone', 'beginning'); +``` + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```bulkClone()``` method in your EntityCrudController: + +```php +public function bulkClone($id) +{ + // your custom code here +} +``` + +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:crud:publish buttons/bulk_clone +``` diff --git a/3.6/crud-operation-create.md b/3.6/crud-operation-create.md new file mode 100644 index 00000000..855b7b11 --- /dev/null +++ b/3.6/crud-operation-create.md @@ -0,0 +1,88 @@ +# Create + +--- + + +## About + +This operation allows your admins to add new entries to a database table. + +![CRUD Create Operation](https://backpackforlaravel.com/uploads/screenshots/news_add.png) + + +## Requirements + +All editable attributes should be ```$fillable``` on your Model. + + +## How to Use + +The ```Create``` operation is **enabled by default**. To disable it, you should use ```$this->crud->denyAccess('create');``` inside your ```setup()``` method. This will make the Add button disappear in ListEntries, in the ```top``` button stack. + +To use the Create operation, you must: + +**Step 1. Specify what field types** you'd like to show for each editable attribute, in your EntityCrudController's ```setup()``` 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 Create operation +$this->crud->addField($field_definition_array, 'create'); +// add a field to both the Create and Update operations +$this->crud->addField($field_definition_array); +``` + +**Step 2. Specify validation rules, in your the EntityCrudRequest file** that you are typehinting on your ```EntityCrudController::store()``` method. If you need separate validation for Create and Update [look here](#separate-validation). + +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 typehinted FormRequest, then create the entry using the Eloquent model. Only attributes that are ```$fillable``` on the model will actually be stored in the database. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**, because there's no need. The code for the insert&update operations is out in the open for you to customize. Notice your ```EntityCrudController``` **already** has the following methods, which you can modify as you wish: +```php +public function store(StoreRequest $request) +{ + // <--------- here is where a before_insert callback logic would be + $response = parent::storeCrud(); + // <--------- here is where a after_insert callback logic would be + return $response; +} + +public function update(UpdateRequest $request) +{ + // <--------- here is where a before_update callback logic would be + $response = parent::updateCrud(); + // <--------- here is where a after_update callback logic would be + 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 + +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. + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` \ No newline at end of file diff --git a/3.6/crud-operation-delete.md b/3.6/crud-operation-delete.md new file mode 100644 index 00000000..8a661cd9 --- /dev/null +++ b/3.6/crud-operation-delete.md @@ -0,0 +1,82 @@ +# Delete + +-- + + +## 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 + +The ```Delete``` action is **enabled by default**. To disable it, you should use ```$this->crud->denyAccess('delete');``` inside your ```setup()``` method. This will prevent the Delete button from appearing in the table view, and will deny access to the controller method if manually accessed. + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```destroy()``` method in your EntityCrudController: + +```php +public function destroy($id) +{ + $this->crud->hasAccessOrFail('delete'); + + return $this->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:crud:publish buttons/delete +``` + + +## Delete Multiple Items (Bulk Delete) + +In addition to the button for each entry, you can show checkboxes next to each element, and allow your 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. + + +### How to Use + +The ```bulkDelete()``` action is **enabled by default**, but there are no buttons using it. To make the buttons show up, inside your ```setup()``` method you should: + +```php +$this->crud->enableBulkActions(); +$this->crud->addBulkDeleteButton(); +``` + + +### How to Overwrite + +In case you need to change how this operation works, just create a ```bulkDelete()``` method in your EntityCrudController: + +```php +public function bulkDelete($id) +{ + // 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:crud:publish buttons/bulk_delete +``` \ No newline at end of file diff --git a/3.6/crud-operation-list-entries.md b/3.6/crud-operation-list-entries.md new file mode 100644 index 00000000..2f22a632 --- /dev/null +++ b/3.6/crud-operation-list-entries.md @@ -0,0 +1,189 @@ +# ListEntries + +--- + + +## 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 ListEntries view might look like this: + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs/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/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 + +The ```ListEntries``` operation is **enabled by default**. To disable it, you can use ```$this->crud->denyAccess('list');``` inside your ```setup()``` method. + +Configuration for this operation is usually done inside your ```setup()``` method. That's recommended, because the columns you define here are used by a few actions (```index()```, ```search()```, ```show()```). + +**For a minimum setup, you only need to define the columns you need to show in the table.** + + +### Columns + +Columns represent the way your information is shown in the table view. All column types must have their ```name```, ```label``` and ```type``` specified, but some could require some other attributes too. + +```php +$this->crud->addColumn([ + '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 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) + +Backpack adds a few buttons by default: +- ```add``` to the ```top``` stack; +- ```edit``` and ```delete``` to the ```line``` stack; + +To learn more about buttons, **check out the [Buttons](/docs/{{version}}/crud-buttons) documentation page**. + + +### Filters + +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**. + + +### 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://backpackforlaravel.com/uploads/docs/operations/listEntries_details_row.png) + +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. You'll want to overwrite that method to show anything you'd like in the details row. + +To use, inside your ```EntityCrudController```: +1. Enable the functionality: ```$this->crud->enableDetailsRow();``` +2. Allow access to all admins: ```$this->crud->allowAccess('details_row');```; Wrap an "if" statement around this if you don't want everybody to be able to see it. +3. Overwrite the ```showDetailsRow($id)``` method; + +Alternative for the 3rd step: overwrite ```views/backpack/crud/details_row.blade.php``` which is called by the default ```showDetailsRow($id)``` functionality. + + +#### Export Buttons + +Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->crud->enableExportButtons();``` in your constructor. + +![Backpack CRUD ListEntries Details Row](https://backpackforlaravel.com/uploads/docs/operations/listEntries_export_buttons.png) + +>**Please note that when clicked, each button will export the _currently visible_ table.** You can use the "visibility" button, and the "Items per page" dropdown to manipulate what is inside the export. + + +#### 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 ```setup()``` 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. +$this->crud->addClause('active'); // apply a 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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->request->has('order')) {}"; that way, your custom order is applied ONLY IF the user hasn't forced another order (by clicking a column heading) +``` + + +#### 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 ```$this->crud->disableResponsiveTable()``` in your ```setup()``` 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 2 hours in local storage. + +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 ```$this->crud->enablePersistentTable();``` +- disable it inside a particular crud controller with ```$this->crud->disablePersistentTable();``` + + + +## 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:crud:publish 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. diff --git a/3.6/crud-operation-reorder.md b/3.6/crud-operation-reorder.md new file mode 100644 index 00000000..dd70a0a2 --- /dev/null +++ b/3.6/crud-operation-reorder.md @@ -0,0 +1,34 @@ +# Reorder + +--- + + +## About + +This operation allows your admins to reoder & nest entries. + +![CRUD Reorder Operation](https://backpackforlaravel.com/uploads/docs/operations/reorder.png) + + +## Requirements + +Your model should have the following integer fields, with a default value of 0: ```parent_id```, ```lft```, ```rgt```, ```depth```. + +Additionnaly, the `parent_id` field has to be nullable. + + +## How to Use + +The ```Reorder``` operation is **disabled by default**. To enable it, you should use ```$this->crud->allowAccess('reorder');``` inside your ```setup()``` method. This will make a Reorder button appear in ListEntries, in the ```top``` button stack. Then, in your EntityCrudController's ```setup()``` method: + +```php +$this->crud->enableReorder('attribute_name', ALLOWED_DEPTH); +``` +Where: +- ```attribute_name``` should be the attribute you want shown on the draggable elements (ex: ```name```); +- ```ALLOWED_DEPTH``` should be an integer, how many levels deep would you allow your admin to go when nesting; for infinit levels, you should set it to ```0```; + + +## How It Works + +The ```/reorder``` route points to a ```reorder()``` method in your ```EntityCrudController```. diff --git a/3.6/crud-operation-revisions.md b/3.6/crud-operation-revisions.md new file mode 100644 index 00000000..b2833e4f --- /dev/null +++ b/3.6/crud-operation-revisions.md @@ -0,0 +1,58 @@ +# Revisions + +--- + + +## About + +Revisions 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, ```Revisions``` 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/operations/revisions.png) + + + +## How to Use + +The ```Revision``` operation is **disabled by default**. In order to enable this functionality for a CRUD panel, you need to: + +1. Create the revisions table: + +```bash +cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php database/migrations/ && php artisan migrate +``` + +2. Use [venturecraft/revisionable](https://github.com/VentureCraft/revisionable#implementation) on your model. If you are using another bootable trait be sure to override the boot method in your model. + +```php +namespace MyApp\Models; + +class Article extends Eloquent { + use \Backpack\CRUD\CrudTrait, \Venturecraft\Revisionable\RevisionableTrait; + + // If you are using another bootable trait + // be sure to override the boot method in your model + public static function boot() + { + parent::boot(); + } +} +``` + +3. In your EntityCrudController's ```setup()``` method, enable access to the Revisions operation: + +```php +$this->crud->allowAccess('revisions'); +``` + +4. [optional] If you want the table view to load faster (and why wouldn't you?), you should eager-load the revisions, so there won't be any extra DB queries for the revisions: + +```php +$this->crud->with('revisionHistory'); +``` + +For complex usage, head on over to [VentureCraft/revisionable](https://github.com/VentureCraft/revisionable) to see the full documentation and extra configuration options. diff --git a/3.6/crud-operation-show.md b/3.6/crud-operation-show.md new file mode 100644 index 00000000..724528a2 --- /dev/null +++ b/3.6/crud-operation-show.md @@ -0,0 +1,64 @@ +# Show + +-- + + +## 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: + +![Backpack CRUD Show Operation](https://backpackforlaravel.com/uploads/docs/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}``` route points to the ```show()``` method in your EntityCrudController. Inside this method, it uses ```setFromDb()``` to try to magically figure out all attributes you would like shown for this Model, and shows them using [Column types](/docs/{{version}}/crud-columns) inside ```show.blade.php```. + + +## How to Use + +The ```ListEntries``` operations is **disabled by default**. To enable it, you should use ```$this->crud->allowAccess('show');``` inside your ```setup()``` method.This will make a Preview button appear in the table view, and allow access to the show view. + +This view uses the same column types you've defined in ```setup()``` and adds all other **fillable** attributes on the model. If you need to hide a ListEntries column from the Show operation, you can specify ```'visibleInShow' => false,``` to the column in ```setup()```, and it will be hidden from the preview page. + +If you need more customization (add/change/remove columns), check out [How to Overwrite](#how-to-overwrite). + + +## How to Overwrite + +In case you need to add/change/remove any columns, create a ```show()``` method in your EntityCrudController. Using the ```addColumn()``` you're already familiar with, you can change how those attributes are shown to the admin. For example: + +```php +public function show($id) +{ + $content = parent::show($id); + + $this->crud->addColumn([ + 'name' => 'table', + 'label' => 'Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ] + ]); + $this->crud->addColumn([ + 'name' => 'fake_table', + 'label' => 'Fake Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ], + ]); + $this->crud->addColumn('text'); + $this->crud->removeColumn('date'); + $this->crud->removeColumn('extras'); + + return $content; +} +``` diff --git a/3.6/crud-operation-update.md b/3.6/crud-operation-update.md new file mode 100644 index 00000000..c7fd915f --- /dev/null +++ b/3.6/crud-operation-update.md @@ -0,0 +1,178 @@ +# Update + +--- + + +## About + +This operation allows your admins to add new entries to a database table. + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/screenshots/news_add.png) + + +## Requirements + +All editable attributes should be ```$fillable``` on your Model. + + +## How to Use + +The ```Update``` operation is **enabled by default**. To disable it, you should use ```$this->crud->denyAccess('update');``` inside your ```setup()``` method. This will make the Update button disappear in ListEntries, in the ```line``` stack. + +To use the Update operation, you must: + +**Step 1. Specify what field types** you'd like to show for each attribute, in your EntityCrudController's ```setup()``` 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, 'update'); +// add a field to both the Update and Update operations +$this->crud->addField($field_definition_array); +``` + +**Step 2. Specify validation rules, in your the EntityCrudRequest file** that you are typehinting on your ```EntityCrudController::update()``` method. If you need separate validation for Create and Update [look here](#separate-validation). + +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 ```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 ```store()``` 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 typehinted FormRequest, then create the entry using the Eloquent model. Only attributes that are ```$fillable``` on the model will actually be updated in the database. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**, because there's no need. The code for the insert&update operations is out in the open for you to customize. Notice your ```EntityCrudController``` **already** has the following methods, which you can modify as you wish: +```php +public function store(StoreRequest $request) +{ + // <--------- here is where a before_insert callback logic would be + $response = parent::storeCrud(); + // <--------- here is where a after_insert callback logic would be + return $response; +} + +public function update(UpdateRequest $request) +{ + // <--------- here is where a before_update callback logic would be + $response = parent::updateCrud(); + // <--------- here is where a after_update callback logic would be + 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/operations/update_translatable.png) + +For localized apps, you can let your admins edit multi-lingual entries. Only translations stored the [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable) way are supported right now, but more options will be coming soon. + +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, you'll need to use backpack's classes instead of the ones provided by cviebrock/eloquent-sluggable: + +```php + [ + 'source' => 'slug_or_name', + ], + ]; + } +} +``` + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` diff --git a/3.6/crud-operations.md b/3.6/crud-operations.md new file mode 100644 index 00000000..bc125218 --- /dev/null +++ b/3.6/crud-operations.md @@ -0,0 +1,573 @@ +# Operations + +--- + +When creating a CRUD Panel, your ```EntityCrudController``` (where Entity = your model name) is extending ```CrudController```. Inside it, we've already provided the logic for [the most important operations](/#supported-operations), you just need to enable or configure them. Also, you can easily [add custom operations](/#creating-a-custom-operation). + + +## Standard Operations + +Operations enabled by default: +- [ListEntries](/docs/{{version}}/crud-operation-list-entries) - allows the admin to see all entries for an Eloquent model, with pagination, search, filters; +- [Create](/docs/{{version}}/crud-operation-create) - allows the admin to add a new entry; +- [Update](/docs/{{version}}/crud-operation-update) - allows the admin to edit an existing entry; +- [Delete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove and entry; + +Operations disabled by default: +- [Show](/docs/{{version}}/crud-operation-show) - allows the admin to preview an entry; +- [Reorder](/docs/{{version}}/crud-operation-reorder) - allows the admin to reorder all entries of a model; +- [Revisions](/docs/{{version}}/crud-operation-revisions) - shows an audit log of all changes to an entry, and allows the admin to undo modifications; + + + +### 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 ```clone``` operation and two actions: ```clone``` and ```bulkClone```. + +```php +trait CloneOperation +{ + public function clone($id) + { + // ... + } + + public function bulkClone() + { + // ... + } +} +``` + +An action can do something with AJAX and return true/false, it can return an AJAX, or it can return a view - or whatever else you can do inside a controller method. + +You can check which action is currently being performed using the [standard Laravel Route API](https://laravel.com/api/5.7/Illuminate/Routing/Route.html): + +- ```\Route::getCurrentRoute()->getAction()``` or ```$this->request->route()->getAction()```: +``` +array:7 [▼ + "middleware" => array:2 [▼ + 0 => "web" + 1 => "admin" + ] + "as" => "crud.monster.index" + "uses" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "controller" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "namespace" => "App\Http\Controllers\Admin" + "prefix" => "admin" + "where" => [] +] +``` +- ```\Route::getCurrentRoute()->getActionName()``` or ```$this->request->route()->getActionName()```: +``` +App\Http\Controllers\Admin\MonsterCrudController@index +``` +- ```\Route::getCurrentRoute()->getActionMethod()``` or ```$this->request->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-3-5/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``` holds an array with all operations they can perform. By default it will look like this: + +```php +public $access = [ + 'list', + 'create', + 'update', + 'delete' + /* 'revisions', reorder', 'show', 'clone' */ +]; +``` + +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'); +$this->crud->allowAccess(['list', 'update', 'delete']); +$this->crud->denyAccess('operation'); +$this->crud->denyAccess(['update', 'create', 'delete']); + +$this->crud->hasAccess('operation'); // 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 +``` + + + +### Getting and Setting an Operation Name + +Inside a CrudController method all default operations use ```$this->crud->setOperation('show')``` to define which operation is currently being performed. So you can do ```$crud->getOperation()``` inside your views and do things according to this. + +When you create custom operation, it's recommended that you also do ```$this->crud->setOperation('show')``` in each custom method, so that have the ability to check later on. + + +### Adding Methods to the CrudPanel Object + +Every time you call ```$this->crud```, you're referring to a ```CrudPanel``` object, where we store all information about the current CRUD and perform all computation. + +Starting with CRUD 3.5 you can add static methods to this ```CrudPanel``` 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, you can add them. You can also use the ```$this->crud->settings``` object, to store various settings. Use it as an associative array, with the operation as key: + +```php +$this->crud->settings['moderate']['show_title'] = false; +``` + + +## Creating 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 just: +- create a new route in ```routes/backpack/custom.php``` that points to a new method in that controller; +- create that method inside your ```EntityCrudController```; +- [add a new button for this operation to the ListEntries view](/docs/{{version}}/crud-buttons#creating-a-custom-button); + +Take a look at the examples below for a better picture and code examples. + + +### 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('moderate')``` in your ```setup()``` method, then check for access to that operation using ```$this->crud->hasAccess('moderate')```; + + +### 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 - we can add it anywhere, but it's recommended we keep all admin routes within ```routes/backpack/custom.php```: + +```php +Route::post('user/{id}/clone', 'UserCrudController@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 + + +``` + +4. We can now actually add this button to our ```UserCrudController::setup()```: + +```php +$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 a route for this operation - we can add it anywhere, but it's recommended we keep all admin routes within ```routes/backpack/custom.php```: + +```php +Route::get('user/{id}/moderate', 'UserCrudController@getModerateForm'); +Route::post('user/{id}/moderate', 'UserCrudController@postModerateForm'); +``` + +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::layout') + +@section('header') +
    +

    + {{ $crud->entity_name_plural }} + {{ trans('backpack::crud.edit').' '.$crud->entity_name }}. +

    + +
    +@endsection + +@section('content') +
    +
    + + @if ($crud->hasAccess('list')) + {{ trans('backpack::crud.back_to_all') }} {{ $crud->entity_name_plural }}

    + @endif + +
    +
    +

    Moderate

    +
    +
    + Something in the box 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('update')) + Moderate +@endif +``` + +4. We can now actually add this button to our ```UserCrudController::setup()```: + +```php +$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 work. + + +#### Creating a New Operation With a Bulk Action (No Interface) + +Say we want to create a ```Clone``` button which clones multiple entries at the same time. So very similar to our ```Bulk Delete```. What we need to do is: + +1. ```$this->crud->enableBulkActions()``` to make the checkboxes show up; + +2. Create a new button and add it to our buttom stack: + +```html +@if ($crud->hasAccess('create') && $crud->bulk_actions) + Clone +@endif + +@push('after_scripts') + +@endpush +``` + +3. In our ```setup()``` method, add this button to the bottom stack: + +```php +$this->crud->addButtonFromView('bottom', 'bulk_clone', 'bulk_clone', 'end'); +``` + +4. 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->request->input('entries'); + $clonedEntries = []; + + foreach ($entries as $key => $id) { + if ($entry = $this->crud->model->find($id)) { + $clonedEntries[] = $entry->replicate()->push(); + } + } + + return $clonedEntries; + } +``` + +5. Add a route to point to this new method: + +```php +CRUD::resource('monster', 'MonsterCrudController')->with(function() { + Route::post('monster/bulk-clone', 'MonsterCrudController@bulkClone'); +}); +``` + +Now there's a Clone button on our 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 +``` \ No newline at end of file diff --git a/3.6/crud-tutorial.md b/3.6/crud-tutorial.md new file mode 100644 index 00000000..4fa02c24 --- /dev/null +++ b/3.6/crud-tutorial.md @@ -0,0 +1,389 @@ +# CRUD Tutorial + +--- + +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 + +Since we don't have an Eloquent model for it already, we're going to use [Jeffrey Way's Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) package, which is installed along with Backpack, to generate the migration. + +```zsh +# STEP 0. create migration +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate +``` + +Now that we have the ```tags``` table in the database, let's generate the actual files we'll be using: + +```zsh +# STEP 1. create a model, a request and a controller for the admin panel +php artisan backpack:crud tag #use singular, not plural + +# STEP 2. add a route for the crud panel (under the admin prefix and auth middleware): +php artisan backpack:base:add-custom-route "CRUD::resource('tag', 'TagCrudController');" + +# STEP 3. add an item to the sidebar menu +php artisan backpack:base:add-sidebar-content "
  • Tags
  • " +``` + +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```; + +**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 +crud->setModel('App\Models\Tag'); + $this->crud->setRoute(config('backpack.base.route_prefix') . '/tag'); + $this->crud->setEntityNameStrings('tag', 'tags'); + + /* + |-------------------------------------------------------------------------- + | CrudPanel Configuration + |-------------------------------------------------------------------------- + */ + + // TODO: remove setFromDb() and manually define Fields and Columns + $this->crud->setFromDb(); + } + + public function store(StoreRequest $request) + { + // your additional operations before save here + $redirect_location = parent::storeCrud($request); + // your additional operations after save here + // use $this->data['entry'] or $this->crud->entry + return $redirect_location; + } + + public function update(UpdateRequest $request) + { + // your additional operations before save here + $redirect_location = parent::updateCrud($request); + // your additional operations after save here + // use $this->data['entry'] or $this->crud->entry + return $redirect_location; + } +} + +``` + +What we should notice inside this TagCrudController is that: +- ```TagCrudController extends CrudController```, which, if we drill down, is a RESTful controller that already has a few methods we will be using - ```create()```, ```edit()```, ```show()```, ```destroy()```, etc.; +- ```TagCrudController``` has a ```setup()``` method, where can configure how the CRUD panel works; +- ```TagCrudController``` has its ```store()``` and ```update()``` methods with ```StoreRequest``` and ```UpdateRequest``` typehinted; which means those classes will be used for form validation; if we take a look at the top of the file, those Requests both lead to our newly generated ```app/Http/Requests/TagRequest.php```; + +Let's move our attention to the ```setup()``` method, which is the gateway to configuring our CRUD Panel. + +As we can tell from the comments there, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. + +Since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys: + +```diff + public function setup() + { + /* + |-------------------------------------------------------------------------- + | CrudPanel Basic Information + |-------------------------------------------------------------------------- + */ + $this->crud->setModel('App\Models\Tag'); + $this->crud->setRoute(config('backpack.base.route_prefix') . '/tag'); + $this->crud->setEntityNameStrings('tag', 'tags'); + + /* + |-------------------------------------------------------------------------- + | CrudPanel Configuration + |-------------------------------------------------------------------------- + */ + +- // TODO: remove setFromDb() and manually define Fields and Columns +- $this->crud->setFromDb(); + ++ // Columns ++ $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); ++ ++ // Fields ++ $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } +} +``` + +This will: +- disable the ```setFromDb()``` functionality; +- add a simple ```text``` column for our ```name``` attribute to the ListEntries operation (the table view); +- add a simple ```text``` field for our ```name``` attribute to the Create and Update forms; + +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. + +Here, in the ```setup()``` method, is where you can also do a lot of other things, like enabling other operations, 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). + +But for now, let's continue to our next generated file. + + +### The Request + +Backpack will 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/TagCrudRequest.php``` file: + +```php + '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. 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). + + +### 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 + CRUD::resource('tag', 'Admin\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 ```views/vendor/backpack/base/inc/sidebar_content.php``` file. You'll see this file is pure HTML. This will allow you to customize the menu as much as you want. 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 +
  • Manage Tags
  • +``` + +You can of course change anything here, if you want. + + +## 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. diff --git a/3.6/demo.md b/3.6/demo.md new file mode 100644 index 00000000..eb531deb --- /dev/null +++ b/3.6/demo.md @@ -0,0 +1,61 @@ +# Demo + +--- + +We've put together a working Laravel app (backend-only), that you can install on your machine. 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 5.6; +- installed Backpack\Base and Backpack\CRUD on top; +- created a few demo models (Monsters, Icons, Products) and admin panels for them, using dozens of field types, column types, filters, etc - to show off most of Backpack's default 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 Installation + +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) Install all the requirements: +``` zsh +cd backpack-demo +composer install +``` + +4) Populate the database and stuff: +```zsh +php artisan key:generate +php artisan migrate +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +php artisan db:seed +``` + + +## Usage + +Once everything's installed, and your database has been set up: + +- Your admin panel is available at http://localhost/backpack-demo/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 40 field types. +- The magic of Backpack is not in its standard functionality, but in 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. \ No newline at end of file diff --git a/3.6/getting-started-advanced-features.md b/3.6/getting-started-advanced-features.md new file mode 100644 index 00000000..60148539 --- /dev/null +++ b/3.6/getting-started-advanced-features.md @@ -0,0 +1,39 @@ +# 3. Advanced Features + +--- + +**Duration:** 5 min + +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 how to find it. + +--- + + +## Other Operations +- [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) +- [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes + +--- + + +## Other Features +- **Create & Update** + - [Manage files on disk](/docs/{{version}}/crud-how-to#use-the-media-library-file-manager) (media library) + - [Fake fields](/docs/{{version}}/crud-fields#fake-fields-all-stored-as-json-in-the-database) + - Translatable models and [multi-language CRUDs](/docs/{{version}}/crud-operation-update#translatable-models) + - [Tabs in create/update forms](/docs/{{version}}/crud-fields#split-fields-into-tabs) + +-- + +- **ListEntries** + - you can add a "+" button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) + - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) + - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in the list view + + +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 CSS or JS](/docs/{{version}}/crud-how-to#customize-css-and-js-for-default-crud-operations) for each CRUD Operation + +**That's it for today!** Told you we're done with long lessons :-) Hopefully some of the above have peaked your interest and you've clicked 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. diff --git a/3.6/getting-started-basics.md b/3.6/getting-started-basics.md new file mode 100644 index 00000000..64507b5e --- /dev/null +++ b/3.6/getting-started-basics.md @@ -0,0 +1,128 @@ +# 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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2017) and accommodate yourself with Laravel first. + + + +## What is Backpack? +A software 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. + + +## How to use? + +Backpack consists of two **core packages**: +- **Backpack\Base** - provides the design of the admin area (based on [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html)) +- **Backpack\CRUD** - empowers you to create **CRUDs**, really fast + +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 what a Tag CRUD could look like: + +![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/tag_crud_list_entries.png) + +But Backpack is fully prepared for feature-packed CRUDs - since it's a good tool for very complex projects too. Here's what a CRUD that uses all of Backpack's features could look like: + +![Monsters CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/monster_crud_list_entries.png) + +Mind that you will _almost never_ use all of Backpack's features in one CRUD. But if you do... it still looks good, and it'll be intuitive to use. + + +## Core Packages + + +### Backpack\Base + +**Backpack/Base** is the package that **will handle the authentication** and provide you with minimal admin area functionality. **Your admin will be able to login and change his password or email.** And that's pretty much it. + +![Backpack 3.5 Authentication Screens](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/auth_screens.png) + +Thanks to Base, 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/Base/blob/master/src/config/backpack/base.php) to browse the configuration file and see what it can do for you. + +Backpack\Base pulls in the free [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html) theme and enhances the design a little bit. So any front-end block that AdminLTE has, you'll also be able to use in your custom pages. It also includes a system for bubble notifications, which you can use across the admin panel. You can easily [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). + + +### Backpack\CRUD +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 DB information. Let's browse through a simple example, of creating a CRUD administration panel for a Tag entity: + +```zsh +# STEP 1. create migration +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate + +# STEP 2. create a model, a request and a controller for the admin panel +php artisan backpack:crud tag #use singular, not plural + +# STEP 3. add a route to routes/backpack/custom.php (under the admin prefix and auth middleware): +php artisan backpack:base:add-custom-route "CRUD::resource('tag', 'TagCrudController');" + +# STEP 4. add a sidebar item +php artisan backpack:base:add-sidebar-content "
  • Tags
  • " +``` + +This will create 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", no need for customizations. But don't expect this for more complex entities. They will usually have particularities 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 code above would 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 CrudPanel looks and feels (```app\Http\Controllers\Admin\TagCrudController.php```) +- a **route**, as a line inside ```routes/backpack/custom.php``` + +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 (db 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 you might use to achieve the above: + +```php +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + + $this->crud->setColumns(['name', 'slug']); + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => 'slug', + 'type' => 'text', + 'label' => "URL Segment (slug)" + ]); + } + + public function store(StoreRequest $request) + { + return parent::storeCrud(); + } + + public function update(UpdateRequest $request) + { + return parent::updateCrud(); + } +} +``` + +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```; +- The request file is typehinted in the ```store()``` and ```update()``` methods; Since in case form validation fails, the information won't even reach these methods; +- All the CRUD setup is usually done in the ```setup()``` method; + +**That's all for today! **If you want to learn more, go ahead and [read the next lesson](/docs/{{version}}/getting-started-crud-operations) of this series. diff --git a/3.6/getting-started-crud-operations.md b/3.6/getting-started-crud-operations.md new file mode 100644 index 00000000..42735246 --- /dev/null +++ b/3.6/getting-started-crud-operations.md @@ -0,0 +1,243 @@ +# 2. CRUD Operations + +--- + +**Duration:** 10 minutes + +Let's bring back the example in our first lesson. The Tags CRUD: + +![Tag CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/tag_crud_list_entries.png) + +With its ```TagCrudController```: + +```php +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + + $this->crud->setColumns(['name', 'slug']); + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => 'slug', + 'type' => 'text', + 'label' => "URL Segment (slug)" + ]); + } + + public function store(StoreRequest $request) + { + return parent::storeCrud(); + } + + public function update(UpdateRequest $request) + { + return parent::updateCrud(); + } +} +``` + +By default, CRUDs have these operations already enabled: +- **Create** - using a create form (aka "*add form*") +- **ListEntries** - 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* + +These are the basic operations an admin can execute on an Eloquent model, thanks to Backpack. We do have additional operations (Preview, Reorder, Revisions), and you can easily _create a custom operation_, but let's not get ahead of ourselves. Baby steps. **Let's go through the most important features of the operations you'll be using _all the time_: ListEntries, Create and Update**. + + +## Create & Update Operations + +![Tag CRUD - Edit Operation](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/tag_crud_edit.png) + + +### Fields + +Inside your Controller's ```setup()``` method, you'll be able to define what 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 ListEntries 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 a lot of times, you won't just need text inputs. You'll need datepickers, upload buttons, 1-n relationship, n-n relationships, textareas, etc. For each field, you only need to define it properly in the Controller's ```setup()``` method. Here are the most used methods to manipulate fields: + +```php +// You can pass a second parameter with the operation: +// - "create" for Create +// - "update" for Update +// - nothing (missing) for both Create and Update + +$this->crud->addField($field_definition_array); +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); +$this->crud->removeField('name'); +$this->crud->removeFields(['name_1', 'name_2']); + +// pro tip: +// a quick way to add simple fields: let the CRUD decide what field type it is +$this->crud->addField('db_column_name'); +``` + +A typical *field definition array* 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'd like 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 some super-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 is 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 +$this->crud->addField([ + '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? +], 'update'); +``` + +**Notes:** +- Because of the last parameter ("_update_"), the field will only be added on the Update form (not on the Create); +- 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, 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 +$this->crud->addField([ + '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? +]); +``` + +**Note: **Because the last parameter is missing, the field will be added to both Create and Update forms. + + +> When generating a CrudController, you'll be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and in your list view, but - as you'd expect - only works for the simple field types. You can: +> +> (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields +> +> or +> +> (2) delete ```setFromDb()``` and manually define each field and column; +> +> **Our recommendation**, for anything but the simplest CRUDs, **is to manually define each field** - much easier to understand and customize, for your future self and any other developer that comes after you. + + +### Callbacks + +Developers coming from GroceryCRUD on CodeIgniter or other CRUD systems will be looking for callbacks to run ```before_insert```, ```before_update```, ```after_insert```, ```after_update```. + +**There are no callbacks in Backpack**, because there's no need. The code for the create & update operations is out in the open for you to customize. Notice your EntityCrudController already has the following methods, which you can modify as you wish: + +```php +public function store(StoreRequest $request) +{ + // <--------- here is where a before_insert callback logic would be + $response = parent::storeCrud(); + // <--------- here is where a after_insert callback logic would be + return $response; +} + +public function update(UpdateRequest $request) +{ + // <--------- here is where a before_update callback logic would be + $response = parent::updateCrud(); + // <--------- here is where a after_update callback logic would be + 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). + + +## ListEntries Operation + +ListEntries 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. + + +### Columns + +Columns help you specify *which* attributes are shown in the table and *in which order*. **They're defined in the ```setup()``` method, the same as fields, and their syntax is super-similar to fields too**: + +```php +$this->crud->addColumn($column_definition_array); // add a single column, at the end of the table +$this->crud->addColumns([$column_definition_array_1, $column_definition_array_2]); // add multiple columns, at the end of the table +$this->crud->removeColumn('column_name'); // remove a column from the table +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the table +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns([$column_definition_array_1, $column_definition_array_2]); // make these the only columns in the table +``` + +You can use one of the [14+ 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 super-specific need. Here's an example of using the methods above: + +```php +$this->crud->addColumn([ + '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 +$this->crud->addColumn('text'); // adds a text column, at the end of the stack +``` + + +### Filters + +![Monster CRUD - List Entries Filters](https://backpackforlaravel.com/uploads/docs-3-5/getting_started/backpack_filters.png) + +Filters provide an easy way for the admin to well… _filter_ the ListEntries 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). + +```php +$this->crud->addFilter($options, $values, $filter_logic); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +``` + +For more on this, check out the [filters documentation page](/docs/{{version}}/crud-filters), when you need them. + + +### Buttons + +![Tag CRUD - List Entries Buttons](https://backpackforlaravel.com/uploads/docs-3-5/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. Look for the [buttons documentation](/docs/{{version}}/crud-buttons) when you need it. + +```php +// positions: 'beginning' and 'end' +// stacks: 'line', 'top', 'bottom' +// types: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + +**That's it for today!** 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. \ No newline at end of file diff --git a/3.6/getting-started-license-and-support.md b/3.6/getting-started-license-and-support.md new file mode 100644 index 00000000..7dceeb0a --- /dev/null +++ b/3.6/getting-started-license-and-support.md @@ -0,0 +1,43 @@ +# 4. Add-ons, License & Support + +--- + +**Duration:** 3 minutes + + +## Add-ons + +In addition to our core packages (Base and CRUD), we have quite a few packages you can install or download, that treat 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 **free for non-commercial use**, but needs a license code in order to prevent "_unlicensed use_" notification bubbles and interruption of service. You can get a license code for your project: +- ```free```, if you're using it for non-commercial purposes; [apply here](https://backpackforlaravel.com/pricing); +- ```free```, if you've contributed to Backpack on Github; [apply here](https://backpackforlaravel.com/pricing); +- ```$49 EUR/project```, if you're making money using it for a project; [buy here](https://backpackforlaravel.com/pricing); +- ```$299 EUR for unlimited projects```, if you use Backpack a lot; [buy here](https://backpackforlaravel.com/pricing); + +**Freelancers** or companies **who make money using Backpack** - for themselves, their employers or their clients, **should [purchase a commercial license here](https://backpackforlaravel.com/pricing)**. + + +>**You don't need a license code on LOCALHOST.** If you're just trying Backpack on your own machine, you don't need a license code. You only need a license code when you take your application to production. + + +## Support + +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: + +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) +- **[Github Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) + +Thank you for sticking up with us for so long. This is the last Backpack lesson we can give you. **Now you have absolutely no excuse not to start your first Backpack project :-)** Here are a few links for if you still don't think you're ready: + +- [Go through the demo](/docs/{{version}}/demo) and play around +- Read this [CRUD Tutorial](/docs/{{version}}/crud-tutorial) +- Take a look at the [CrudController API Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) \ No newline at end of file diff --git a/3.6/index.md b/3.6/index.md new file mode 100644 index 00000000..c4b04116 --- /dev/null +++ b/3.6/index.md @@ -0,0 +1,44 @@ +#### About + +- [Getting Started](/docs/{{version}}/introduction) + - [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) +- [Demo](/docs/{{version}}/demo) +- [Installation](/docs/{{version}}/installation) +- [Release Notes](/docs/{{version}}/release-notes) +- [Upgrade Guide](/docs/{{version}}/upgrade-guide) + +#### Backpack\Base 1.1.x + +- [About](/docs/{{version}}/base-about) +- [How To](/docs/{{version}}/base-how-to) + +#### Backpack\CRUD 3.6.x + +- [Basics](/docs/{{version}}/crud-basics) +- [Tutorial](/docs/{{version}}/crud-tutorial) +- [API](/docs/{{version}}/crud-api) +- [Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) +- [Operations](/docs/{{version}}/crud-operations) + + [ListEntries](/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) + + [Delete](/docs/{{version}}/crud-operation-delete) + + [Clone](/docs/{{version}}/crud-operation-clone) + + [Show](/docs/{{version}}/crud-operation-show) + + [Columns](/docs/{{version}}/crud-columns) + + [Reorder](/docs/{{version}}/crud-operation-reorder) + + [Revisions](/docs/{{version}}/crud-operation-revisions) +- [How To](/docs/{{version}}/crud-how-to) + + +#### Add-ons + +- [Official Add-ons](/docs/{{version}}/add-ons-official) +- [Community Add-ons](/docs/{{version}}/add-ons-community) +- [How to Create an Add-on](/docs/{{version}}/add-ons-how-to-create-a-backpack-addon) diff --git a/3.6/install-optionals.md b/3.6/install-optionals.md new file mode 100644 index 00000000..bf58fc0b --- /dev/null +++ b/3.6/install-optionals.md @@ -0,0 +1,165 @@ +# 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 sidebar_content item for it +php artisan backpack:base:add-sidebar-content "
  • Backups
  • " +``` + +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: +``` + 'dump_command_path' => '/Applications/MAMP/Library/bin/', // only the path, so without 'mysqldump' or 'pg_dump' + 'dump_command_timeout' => 60 * 5, // 5 minute timeout + 'dump_using_single_transaction' => true, +``` + + +## 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: + +``` +// 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: +``` + 'log' => env('APP_LOG', 'daily'), +``` + +4) [optional] Add a menu item for it in resources/views/vendor/backpack/base/inc/sidebar_content.blade.php or menu.blade.php: + +```bash +php artisan backpack:base:add-sidebar-content "
  • Logs
  • " +``` + +## 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 to the sidebar_content file +php artisan backpack:base:add-sidebar-content "
  • Settings
  • " + +# [optional] insert some example dummy data to the database +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +``` + + +## PageManager + +[>> See screenshots and installation](https://github.com/Laravel-Backpack/pagemanager) + +1) In your terminal + +``` bash +composer require backpack/pagemanager +``` + +2) Publish the views, migrations and the PageTemplates trait; run the migrations: + +``` +php artisan vendor:publish --provider="Backpack\PageManager\PageManagerServiceProvider" +php artisan migrate +``` + +3) [optional] Add a menu item for it in resources/views/vendor/backpack/base/inc/sidebar_content.blade.php or menu.blade.php: + +```bash +php artisan backpack:base:add-sidebar-content "
  • Pages
  • " +``` + + +## PermissionManager + +An admin panel for user authentication on Laravel 5, using Backpack\CRUD. Add, edit, delete users, roles and permission. + +[>> Installation](https://github.com/Laravel-Backpack/PermissionManager#install) +[>> Github](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) diff --git a/3.6/installation.md b/3.6/installation.md new file mode 100644 index 00000000..48d6c9bc --- /dev/null +++ b/3.6/installation.md @@ -0,0 +1,71 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 6 or 5.8, you can install Backpack. Backpack does _not_ have additional requirements. For the following process, we assume: + +- you have a working installation of [Laravel 6](https://laravel.com/docs/6.0#installing-laravel) or [Laravel 5.8](https://laravel.com/docs/5.8#installing-laravel) (an existing project is fine, you don't need a *fresh* Laravel install); + +- you have put your database and email credentials in your .ENV file; + +- 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 Core Packages + +0) Open your project folder in your terminal: + +```bash +cd your-laravel-project-name +``` + +1) In your project's main directory, install CRUD using composer: + +``` bash +composer require backpack/crud +``` + +2) Now run the installation commands for each of the core packages (Base has also been installed, as a dependency): + +``` bash +php artisan backpack:base:install +php artisan backpack:crud:install +``` + +Note: If you'd also like to enable the [file manager functionality](https://backpackforlaravel.com/uploads/home_slider/4.png), reply "yes" when the installer asks you. By default it lets users manage the ```public/uploads``` directory, but you can change that in the ```elfinder.php``` config file. Most of the times it is _not_ recommended to give your admins power over file structure - not even their uploads alone. So ```elfinder``` does not come installed by default. + + +3) [Optional] You should now: +- Change configuration values in ```config/backpack/base.php``` to make the admin panel your own. Backpack is white label, so you can change everything: menu color, project name, developer name etc. +- If your User model has been moved (it is not ```App\User.php```, please go change ```App\Models\BackpackUser.php``` and make sure it extends the correct user model; +- If you have separate admin panels for Users and Administrators, and already have a way to differentiate between the two, please change ```app/Http/Middleware/CheckIfAdmin.php```, particularly ```checkIfUserIsAdmin($user)```, to make sure all users who get log into the admin panel have a right to do that; +- If your application has only one login screen (for the admins), that means you're not going to use the auth controllers that Laravel provided by default. You're only going to use Backpack's auth controllers. You can keep the Laravel ones in your project, of course. But some people like to delete them to not get confused later on: + +``` bash +# OPTIONAL! Please read the notice above. +rm -rf app/Http/Controllers/Auth #deletes laravel's demo auth controllers +``` + +That's it. If you already know how to use Backpack, next up you'll probably want to [create CRUD Panels](/docs/{{version}}/crud-tutorial#generate-files). + +> If it's your first time installing Backpack, it is **highly recommended** that you go through our [Getting Started series](/docs/{{version}}/getting-started-basics), to understand how Backpack works. That's why we created it - to help you learn how to use this admin panel framework. In ~23 minutes we'll teach you 80% of what you can do, and how. + + +### 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:base: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:base:install --timeout=600 --debug```, and refer to [this thread](https://github.com/Laravel-Backpack/Base/issues/217). + +- **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\\Base because of a different error, you can [try the manual installation process](/docs/{{version}}/base-how-to#manually-install-base), which you can tweak to your needs. diff --git a/3.6/introduction.md b/3.6/introduction.md new file mode 100644 index 00000000..81bd4d44 --- /dev/null +++ b/3.6/introduction.md @@ -0,0 +1,79 @@ +# Getting Started + +--- + +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 drop it on existing Laravel installations _or_ fresh projects. + +In a nutshell: + +- **Backpack\Base** will provide you with a _visual interface_ for the admin panel (the HTML, the CSS, the JS); it pulls in the excellent [AdminLTE](https://adminlte.io/themes/AdminLTE/index2.html) theme, adds authentication functionality & bubble notifications; when you decide to build a custom feature for your admin panel, you already have the HTML blocks for the UI, and it will look good; +- **Backpack\CRUD** will 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 for each entity in about 10 minutes / model: + +```bash +# STEP 0. create migration (in case you're starting from scratch) +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate + +# STEP 1. create a model, a request and a controller for the admin panel +php artisan backpack:crud tag #use singular, not plural + +# STEP 2. add a route for this admin panel to routes/backpack/custom.php +php artisan backpack:base:add-custom-route "CRUD::resource('tag', 'TagCrudController');" + +# STEP 3. add sidebar item to resources/views/vendor/backpack/base/inc/sidebar_content.blade.php +php artisan backpack:base:add-sidebar-content "
  • Tags
  • " + +# STEP 4. go through the generated files, customize according to your needs +``` + + +## Need to Know + + +### Requirements + + - Laravel 5.8 + - PHP 7.1.3+ + - MySQL (recommended) / PostgreSQL / SQLite / SQL Server + + +### Screenshots + +Take a look at [our homepage](https://backpackforlaravel.com/). + + +### Demo + +You can easily [install a demo Laravel project with Backpack installed](/docs/{{version}}/demo) and play around. + + +### Security + +Backpack has never had a critical vulnerability/hack. But there _have_ been important security updates for dependencies (including Laravel). Please [login with Github](/auth/github) or [subscribe to our monthly newsletter](https://backpackforlaravel.com/newsletter), so we can reach you in case anything bad happens. No spam, no marketing emails, we promise. We only send one email per month, with Backpack updates. + + +### Maintenance + +Backpack 3.5 is the current version, and is being actively maintained by Backpack's creator, [Cristian Tabacitu](http://tabacitu.ro), with the help of a wonderful community of Backpack veterans. [See all contributors](https://github.com/Laravel-Backpack/CRUD/graphs/contributors). + + +### License + +Backpack is under a license we call "_You make money, I make money_" (YummY). Backpack's source is public, and you can use it for free for non-commercial purposes (testing, non-profits, personal use, etc), but if you make money using it you need to purchase a commercial license. Please see [the pricing section](https://backpackforlaravel.com/pricing) for more details. In production, you need a license code for both commercial and non-commercial use, to prevent nagging notification bubbles. On localhost, you don't need a license code. + + +### Add-ons + +In addition to our core packages (Base and CRUD), there are a few packages you can install or download, 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. + +For more, please see: +- [all official add-ons](/docs/{{version}}/add-ons-official) +- [all community add-ons](/docs/{{version}}/add-ons-community) + + +## How to Start + +We heavily recommend you spend a little time to understand Backpack, and only afterwards install and use it. Currently your options are: +- **[Text Tutorial](/docs/{{version}}/getting-started-basics)** - 23 minutes +- **[Email Tutorial](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each +- **Video Tutorial** - working on it diff --git a/3.6/release-notes.md b/3.6/release-notes.md new file mode 100644 index 00000000..395ab383 --- /dev/null +++ b/3.6/release-notes.md @@ -0,0 +1,27 @@ +# Release Notes + +--- + +**Launch date:** March 1st 2019 + +**Backpack 3.6 has no new features**. It is just a maintenance upgrade, which adds support for Laravel 5.8 and removes support for Laravel 5.7 and 5.6. + +Here are the main differences between [Backpack 3.5](https://backpackforlaravel.com/docs/3.5) and Backpack 3.6. + + +## Backpack\Base 1.1.x + +### Added +- support for Laravel 5.8; + +### Removed +- removed Laravel 5.6 and 5.7 support; +- ```jenssegers/date``` dependency, since Carbon v2 can now do the same thing well; +- ```Tightenco\Parental``` dependency, since it proved unstable; the trait we were using is now included in Base as ```Backpack\Base\app\Models\Traits\InheritsRelationsFromParentModel;```; + + +## Backpack\CRUD 3.6.x + +### Fixed + +- ```date``` and ```datetime``` column use Carbon localized dates, instead of ```jenssegers/date``` ; diff --git a/3.6/upgrade-guide.md b/3.6/upgrade-guide.md new file mode 100644 index 00000000..f768f2d9 --- /dev/null +++ b/3.6/upgrade-guide.md @@ -0,0 +1,31 @@ +# Upgrade Guide + +--- + +This will guide you through upgrading from Backpack 3.5 to 3.6. For upgrading from 3.4 to 3.5 [check out the previous upgrade guide](https://backpackforlaravel.com/docs/3.5/upgrade-guide). + + +## Requirements + +- Backpack\Base 1.0.x installed +- Backpack\CRUD 3.5.x installed +- Laravel 5.8 installed +- PHP 7.1.3+ +- 5 minutes on top of your normal [Laravel 5.8 upgrade](https://laravel.com/docs/5.8/upgrade) + + +## Upgrade Steps + +1. Update your ```composer.json``` file to require ```"backpack/crud": "3.6.*"``` along with ```"laravel/framework": "5.8.*"```. Remove ```backpack/base``` from your requirements - backpack/crud will take care of that. Then run ```composer update```. + +2. In your ```App\Models\BackpackUser``` instead of ```Tightenco\Parental\HasParent```, please use ```Backpack\Base\app\Models\Traits\InheritsRelationsFromParentModel```; [here's the diff](https://github.com/Laravel-Backpack/Base/pull/362/files#diff-f075b83ebb2b1ef3ba84dec14b395607); + +3. In your ```app/config/backpack/base.php``` please change your ```default_date_format``` and ```default_datetime_format``` to ```Do MMMM YYYY``` and ```Do MMMM YYYY, HH:mm``` respectively; + +4. If you've overwritten ```inc/head.blade.php``` or ```inc/scripts.blade.php```, please make sure you [use the newest version of Bootstrap](https://github.com/Laravel-Backpack/Base/pull/362/files#diff-96ac3ea4d0cb85053acf44e3772eb5f1); they've fixed a security vulnerability (XSS); + +5. Run ```php artisan view:clear``` + +--- + +**That's it**. Thank you for taking the time to upgrade. diff --git a/4.0/add-ons-community.md b/4.0/add-ons-community.md new file mode 100644 index 00000000..6e9bc673 --- /dev/null +++ b/4.0/add-ons-community.md @@ -0,0 +1,16 @@ +# 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 | +| ------------- |:-------------:| --------:| +| [AbbyJanke/Expensed](https://github.com/AbbyJanke/expensed) | income and expense tracker | MIT | +| [eduardoarandah/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) | +| [eduardoarandah/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) | +| [novius/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) | +| [novius/laravel-backpack-translation-manager](https://github.com/novius/laravel-backpack-translation-manager) | manage translations stored in the database | - | +| [updivision/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) | +| [webfactor/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) | +| [seandowney/laravel-backpack-gallery-crud](https://github.com/seandowney/laravel-backpack-gallery-crud) | manage photo galleries | [MIT](https://github.com/seandowney/laravel-backpack-gallery-crud/blob/master/LICENSE.md) | diff --git a/4.0/add-ons-how-to-create-a-backpack-addon.md b/4.0/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..9c8baa14 --- /dev/null +++ b/4.0/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 \ No newline at end of file diff --git a/4.0/add-ons-official.md b/4.0/add-ons-official.md new file mode 100644 index 00000000..429a1a02 --- /dev/null +++ b/4.0/add-ons-official.md @@ -0,0 +1,15 @@ +# Official Add-ons + +In addition to our core packages (Base and CRUD), we've developed a few packages you can install or download, that treat common use cases. + + + - [PermissionManager](https://github.com/Laravel-Backpack/PermissionManager) - interface to manage users & permissions, using [spatie/laravel-permission](https://github.com/spatie/laravel-permission); ```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``` + + +>**These 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. \ No newline at end of file diff --git a/4.0/add-ons-tutorial.md b/4.0/add-ons-tutorial.md new file mode 100644 index 00000000..b488415f --- /dev/null +++ b/4.0/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 sthe keleton 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](https://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 subredddit](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/4.0/base-about.md b/4.0/base-about.md new file mode 100644 index 00000000..cee5c5c9 --- /dev/null +++ b/4.0/base-about.md @@ -0,0 +1,201 @@ +# About Backpack's User Interface + +--- + +Backpack helps you build admin panels faster by: +- installing our custom HTML theme, [Backstrap](https://backstrap.net), based on Bootstrap 4 and [CoreUI](https://coreui.io); +- installing [sweetalert](https://sweetalert.js.org/) for triggering pretty Confirm modals; +- installing [noty](https://ned.im/noty/#/) to show notification bubbles upon error/success/warning/info - triggered from Javascript; +- installing [prologue/alerts](https://github.com/prologuephp/alerts) for triggering notification bubbles from PHP (both on the same page and using flashdata); +- providing a separate authentication system for your admins; +- providing pretty error pages for most common errors; +- providing a horizontal menu and a side menu you can customize; +- providing a place for your admin to to change his email/name/password; +- providing a few helpers you can use throughout your admin panel; + +For the simplest projects, you will never need to know how it works, never need to customize anything but the ```config/backpack/base.php``` file. But here's how everything works, below. + + +## Layout & Design + + +### General + +Backpack pulls in our custom HTML template, [Backstrap](https://www.npmjs.com/package/@digitallyhappy/backstrap), and adds our own CSS file on top, for a few cosmetic improvements. We've chosen to base Backstrap on [CoreUI](https://coreui.io), because it provides design blocks for all common features of an administration panel. When you decide to build custom pages for your Admin Panel, you can just use Backstrap's HTML blocks - no designer needed. You can see all the HTML components Backstrap provides on [backstrap.net](https://backstrap.net), and copy-paste HTML from there, or use [CoreUI](https://coreui.io)'s documentation for details. + + +### New Files in Your App + +After installation, you'll notice Backpack has added a few files: + +**1) Views to ```resources/views/vendor/backpack/base/```** + - ```inc/sidebar_content.blade.php```; + - ```dashboard.blade.php```; + +Those files are used to show the contents of the menu to the left (sidebar), and the first page the admin sees when logging in (dashboard). They've been published there so that you can easily modify their contents, by editing their HTML or adding dynamic content through [widgets](/docs/{{version}}/base-widgets). + +**2) Model to ```app/Models/BackpackUser.php```** + +Notice that ```BackpackUser``` extends your own user model. Backpack will be using ```BackpackUser``` for authentication and management, not your ```User```, so if there's anything that's specific to your admins (accessors, mutators, methods), you can use this model to define them. + +**3) 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. + +**4) 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 few blade files in ```resources/views/vendor/backpack/base/```: + - ```inc/sidebar_content.blade.php```; + - ```dashboard.blade.php```; + +Those files are used to show the contents of the menu to the left (sidebar), and the first page the admin sees when logging in (dashboard). They've been published there so that you can easily modify their contents, by editing their 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, then go to ```resources/views/vendor/backpack/base``` and create a file with the exact same name. Backpack\Base will use this new file, instead of the one in the package. + +For example, if you want to add an item to the top menu, you could just create a file called ```resources/views/vendor/backpack/base/inc/topbar_left_content.php```. Backpack will now use this file's contents, instead of ```vendor/backpack/base/src/resources/views/inc/topbar_left_content.php``` + + +### Folder Structure + +If you'll take a look inside any Backpack package, you'll notice the ```src``` directory is 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. + +> **The ```BackpackUser``` model extends Laravel's default ```App\User``` model**. This assumes you weren't already using this model, or the ```users``` table, for anything else. If you were, please read below. + + +### Using a Different User Model + +If you want to use a different User model than ```App\User``` or you've changed its location, please: +- tell Backpack to use _your_ model in ```config/backpack/base.php``` instead of the ```BackpackUser``` model Backpack has published to your app; +- take a look at [```\Backpack\Base\app\Models\BackpackUser::class```](https://github.com/Laravel-Backpack/Base/blob/master/src/app/Models/BackpackUser.php); +- include the methods there in _your_ user model; they're important for password recovery; + + + +### 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 his/her name to go to his "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\Base\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\Base 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/base/my_account.blade.php``` that uses code from the same file in the Backpack package, 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\Base\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\Base routes there and change whatever you need. You can then point the route to your own controller, where you can do whatever you want. + + +## Notification Bubbles + + +### Triggering Notification Bubbles 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. Basic examples: + +```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 Notification Bubbles in JavaScript + +We use [Noty](https://ned.im/noty/#/) to show notifications from JavaScript, on the same page. Check out its page for more detailed use. Basic example: + +```php +new Noty({ + type: "success", + text: 'Some notification text', +}).show(); + +// available types: success, info, warning/notice, error/danger, primary, secondary, dark, light +``` + + +## 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(http://github.com/$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```. diff --git a/4.0/base-breadcrumbs.md b/4.0/base-breadcrumbs.md new file mode 100644 index 00000000..a3a7d5ad --- /dev/null +++ b/4.0/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/base.php```: + +```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('http://github.com/dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('http://github.com/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('http://github.com/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('http://github.com/dashboard'), + 'Dashboard' => false, + ]; +@endphp + +@section('content') + some other content here +@endsection +``` \ No newline at end of file diff --git a/4.0/base-how-to.md b/4.0/base-how-to.md new file mode 100644 index 00000000..d952b684 --- /dev/null +++ b/4.0/base-how-to.md @@ -0,0 +1,509 @@ +# How To Customize the UI + +--- + + + +## Look and feel + + +### Customize the menu or sidebar + +During installation, Backpack publishes a few files in you ```resources/views/vendor/backpack/base/inc``` folder. In there, you'll also find: +- ```sidebar_content.php``` +- ```topbar_left_content.php``` +- ```topbar_right_content.php``` + +Change those files as you please. + + +### 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/base/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('http://github.com/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 + +In ```config/backpack/base.php``` you'll notice there are variables where you can change exactly what CSS classes are placed on the HTML elements that represent the header, body, sidebar and footer: + +```php + // Horizontal navbar classes. Helps make the admin panel look similar to your project's design. + 'header_class' => 'app-header bg-light border-0 navbar', + // Try adding bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + // You might need to add "navbar-dark" too if the background color is a dark one. + // Add header-fixed if you want the header menu to be sticky + + // Body element classes. + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + // Try sidebar-hidden, sidebar-fixed, sidebar-compact, sidebar-lg-show + + + // Sidebar element classes. + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + // Remove "sidebar-transparent" for standard sidebar look + // Try "sidebar-light" or "sidebar-dark" for dark/light links + // You can also add a background class like bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + + // Footer element classes. + 'footer_class' => 'app-footer', +``` + +Our default design might not be pleasant for your, or you might need to make the UI integrate better into your project. We totally understand. You can use the classes above to make it look considerably different. + +You'll find a few examples below - but you should use which classes you want to get the result you need. + + +#### Backstrap + +Transparent top menu, transparent sidebar, transparent footer. This is the default. This is what _we_ think is best for most users, from our 8+ years of experience building admin panels. Prioritising _content_ over _menus_. + +![Backstrap design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/default.png) + +```php + 'header_class' => 'app-header bg-light border-0 navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer', +``` + + +#### Inspired by CoreUI + +White top menu, dark sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/coreui.png) + +```php + 'header_class' => 'app-header navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', + 'footer_class' => 'app-footer d-none', +``` + + +#### Inspired by Github + +Black top menu, white sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/github.png) + +```php + 'header_class' => 'app-header bg-dark navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar bg-white sidebar-pills', + 'footer_class' => 'app-footer d-none', +``` + + +#### Blue Top Menu + +Blue top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/blue.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-primary border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + + +#### Construction / Warning + +Yellow top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/construction.png) + +```php + 'header_class' => 'app-header navbar navbar-light bg-warning', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Red Top Menu + +Red top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/red.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Pink Top Menu + +Pink top menu, transparent sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/pink.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer d-none', +``` + + + +#### Green Top Menu + +Green top menu, white sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/green.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-green border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-white', + 'footer_class' => 'app-footer d-none', +``` + + +### 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/base.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) +- use a different CSS framework (ex: Tailwind, Bulma) + + + +### Add custom Javascript to all admin panel pages + +In ```config/backpack/base.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/base.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/base.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/base.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. + + +## 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 BackpackUser + +By default, authentication and everything else inside Backpack is done using the ```Backpack\Base\app\Models\BackpackUser``` model, which extends Laravel's default ```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; +- making sure everything inside ```BackpackUser``` is also inside your new model (this is important for recovering password, etc); + + + +### 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')->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() + { + + return backpack_view('auth.register'); + } +``` +This will make the registration process pick up a view you can create, in ```resources/views/vendor/backpack/base/auth/register.blade.php```. You can copy-paste the original view, and modify as you please. Including adding your own custom inputs. diff --git a/4.0/base-widgets.md b/4.0/base-widgets.md new file mode 100644 index 00000000..0f4ab0f7 --- /dev/null +++ b/4.0/base-widgets.md @@ -0,0 +1,341 @@ +# 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. + + + +### How to Use + +The default layout that Backpack uses has two widget sections: +- ```before_content``` +- ```after_content``` + +You can easily push widgets to these sections, by making sure a ```$widgets['before_content']``` or ```$widgets['after_content']``` variable will be present in the main view. + +1) From the controller: +```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['widgets']['before_content'] = [ + [ + 'type' => 'card', + 'wrapperClass' => 'col-sm-6 col-md-4', + 'content' => [ + 'header' => 'Some 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.', + ] + ], + ]; + + return view(backpack_view('dashboard'), $this->data); + } +``` + +2) From the view: +```php +@extends(backpack_view('blank')) + +@php + $widgets['before_content'][] = [ + 'type' => 'card', + 'wrapperClass' => 'col-sm-6 col-md-4', + 'content' => [ + 'header' => 'Some 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.', + ] + ]; +@endphp + +@section('content') +@endsection +``` + +Both of the options above will produce the same result. You can use both options above together, pushing some widgets from the controller, and some from the view. + + + +### 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 + 'wrapperClass' => 'col-sm-6 col-md-4', // customize the class on the parent element (wrapper) +``` + + + +## 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', + // 'wrapperClass' => '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) + + +### Div + +Allows you to include multiple widgets in the div attributes of your choice. For example, you can include multiple widgets in 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. + + +### Jumbotron + +Shows a Bootstrap jumbotron component, with the heading and body you specify. + +```php +[ + 'type' => 'jumbotron', + 'wrapperClass'=> '', + 'heading' => 'Welcome!', + 'content' => 'Use the sidebar to the left to create, edit or delete content.', + 'button_link' => backpack_url('http://github.com/logout'), + 'button_text' => 'Logout', +] +``` + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/jumbotron.png) + + +### 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) + + +### View + +Loads a blade view from a location you specify. Any attributes you give it will be available in the ```$widget``` variable inside that view. + +```php +[ + 'type' => 'view', + 'view' => 'path.to.custom.view', + 'someAttr' => 'some value', +] +``` + +It helps load blade files that are not specifically created to be widgets, that live in a different path than ```resources/views/vendor/backpack/base/widgets```, as if they were widgets. + + +## Overwriting Default Widget Types + +You can overwrite a widget type by placing a file with the same name in your ```resources\views\vendor\backpack\base\widgets``` 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:base:publish widgets/widget-name``` + +Examples: +- creating a ```resources\views\vendor\backpack\base\widgets\card.blade.php``` file would overwrite the ```card``` widget functionality; +- ```php artisan backpack:base:publish widgets/card``` 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 widget type, you're forfeiting any future updates for that widget. We can't push updates to a file that you're no longer using. + + +## Creating a Custom Widget Type + +Widgets consist of only one file - a blade file with the same name as the widget type (ex: ```card.blade.php```). You can create one by placing a new blade file inside ```resources\views\vendor\backpack\base\widgets```. Be careful to choose a distinctive name, otherwise you might be overwriting a default widget type (see above). + +For example, you can create a ```well.blade.php```: +```php +
    +
    + {!! $widget['content'] !!} +
    +
    +``` + +You can then use the ```well``` widget in a Controller or View: +```php +@extends(backpack_view('blank')) + +@php + $widgets['before_content'][] = [ + 'type' => 'well', + 'wrapperClass' => 'col-sm-12', + 'content' => 'This text will be in a div with the class "well".', + ]; +@endphp + +@section('content') +@endsection +``` + +To use information from the database, you can: +- use the full namespace for your models, like ```\App\Models\Product::count()```; +- 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 view composers](https://laravel.com/docs/5.7/views#view-composers) to push variables inside this view when it's loaded, Like. ```View::composer('backpack::widgets.well, 'App\Http\View\Composers\WellComposer');``` + +Inside the widget blade files, you include custom CSS and JS, by pushing to the stacks in the layout: +```php +
    +
    + {!! $widget['content'] !!} +
    +
    + +@push('after_styles') + + +@endpush + + +@push('after_scripts') + + +@endpush +``` + + + +## Using a Widget Type from a Package + +You can choose the view namespace when loading a widget: + +```diff + $this->data['widgets']['after_content'] = [ + [ + 'type' => 'card', ++ 'viewNamespace' => 'package::widgets', + 'wrapperClass' => '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/base/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/4.0/crud-api.md b/4.0/crud-api.md new file mode 100644 index 00000000..db12a2a3 --- /dev/null +++ b/4.0/crud-api.md @@ -0,0 +1,514 @@ +# 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 only get called when a certain operation is being performed; +```php +public function setup() { + // ... + $this->crud->operation('list', function() { + $this->crud->addColumn('name'); + }); +} +``` + + +### ListEntries + + +#### 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); +``` + +- **removeButton()** - remove a button from whatever stack it's in +```php +$this->crud->removeButton($name); +``` + +- **removeButtonFromStack()** - remove a button from a particular stack +```php +$this->crud->removeButtonFromStack($name, $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(); +``` + + +### Show + +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 + +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(); +``` + + +### Revisions + +A.k.a. Audit Trail. Tracks all changes to an entry and provides an interface to revert to a previous state. In order to use this, you also need to ```use \Venturecraft\Revisionable\RevisionableTrait;```. Please check out the [Revision Operation](/docs/{{version}}/crud-operation-revisions) for more info. + + +## 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']); +``` + +- **denyAccess()** - prevent users from accessing one or multiple operations +```php +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); +``` + +- **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"); +``` \ No newline at end of file diff --git a/4.0/crud-basics.md b/4.0/crud-basics.md new file mode 100644 index 00000000..4d3a32b1 --- /dev/null +++ b/4.0/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 **controller** - holds the logic for the all operations an admin can perform on that Eloquent model; will be generated in ```app/Http/Controllers/Admin```; +- a **request** file - used to validate Create and Update forms; will be generated in ```app/Http/Requests```; +- a resource **route** - points to the controller above; will be generated in ```routes/backpack/custom.php```; + +**The only difference** between building it from scratch and using Backpack\CRUD** is that: +- your controller will be extending ```Backpack\CRUD\app\Http\Controllers\CrudController```**, which allow you to easily add traits that handle the most common operations: Create, Update, Delete, List, Show, Reorder, Revisions. +- 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_ overwrite what happens inside each operation**; +- **you can _easily_ add 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 CrudController; same 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; + + +## Files + +For a ```Tag``` entity, your CRUD Panel would consist of: +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); +- a route inside ```routes/backpack/custom.php```; +- your existing model (```app/Models/Tag.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/4.0/crud-buttons.md b/4.0/crud-buttons.md new file mode 100644 index 00000000..c1fd3233 --- /dev/null +++ b/4.0/crud-buttons.md @@ -0,0 +1,202 @@ +# Buttons + +--- + + +## About + +Buttons are used inside the ListEdit operation, to allow the admin to trigger other operations. Some point to entirely new routes (```create```, ```update```, ```show```), others perform the operation on the current page using AJAX (```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) + +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 + +Backpack adds a few buttons by default: +- ```create``` to the ```top``` stack; +- ```update``` and ```delete``` to the ```line``` stack; + +Default buttons are invisible if an operation has been disabled. For example, you can: +- hide the "delete" button using ```$this->crud->denyAccess('delete')```; +- show a "preview" button by using ```$this->crud->allowAccess('show')```; + + +### Buttons API + +Here are a few things you can call in your EntityCrudController's ```setupListOperation()``` method, to manipulate buttons: + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; + +// add a button; possible types are: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); + +// add a button whose HTML is returned by a method in the CRUD model +$this->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 +$this->crud->addButtonFromView($stack, $name, $view, $position); + +// remove a button +$this->crud->removeButton($name); + +// remove a button for a certain stack (top, line, bottom) +$this->crud->removeButtonFromStack($name, $stack); +``` + +### Overwriting a Default Button + +Before showing any buttons, Backpack will check your ```resources\views\vendor\backpack\crud\buttons``` directory, to see if you've overwritten any default buttons. If it finds a blade file with the same name there as the default buttons, it will use your blade file, instead of the default. + +That means **you can overwrite an existing button simply by creating a blade file with the same name inside this directory**. + + +### Creating a Custom Button + +To create a custom button: +- create a new blade file in ```resources\views\vendor\backpack\crud\buttons```; +- add that button using the ```addButton()``` syntax above, in the EntityCrudControllers you want, inside the ```setupListOperation()``` method; + +In this 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; + + +## 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')) + 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::setup()```: +```php +$this->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::setup()```: +```php +// add a button whose HTML is returned by a method in the CRUD model +$this->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_javascript')```, because the Backpack default layout has an ```after_javascript``` 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::setup()```: +```php +$this->crud->addButtonFromView('top', 'import', 'import', 'end'); +``` diff --git a/4.0/crud-cheat-sheet.md b/4.0/crud-cheat-sheet.md new file mode 100644 index 00000000..f098cf4d --- /dev/null +++ b/4.0/crud-cheat-sheet.md @@ -0,0 +1,319 @@ +# 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 + + +### ListEntries + + +#### Columns + +Methods: addColumn(), addColumns(), modifyColumn(), removeColumn(), removeColumns(), setColumnDetails(), setColumnsDetails(), setColumns(), beforeColumn(), afterColumn(), makeFirstColumn() + +```php +// Manipulate what columns are shown in the table view. +$this->crud->addColumn($column_definition_array); // add a column, at the end of the stack +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); // add multiple columns, at the end of the stack +$this->crud->modifyColumn($name, $modifs_array); +$this->crud->removeColumn('column_name'); // remove a column from the stack +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns(); // set the columns you want in the table view, either as array of column names, or multidimensional array with all columns detailed with their types + +// ------ REORDER COLUMNS +$this->crud->addColumn()->beforeColumn('name'); // will show this before the given column +$this->crud->addColumn()->afterColumn('name'); // will show this after the given column + +$this->crud->addColumn()->makeFirstColumn(); + // will make this column the first one in the list + // you need to also specify priority 1 in your addColumn statement for details_row or responsive expand buttons to show +``` + + +#### Buttons + +Methods: addButton(), addButtonFromModelFunction(), addButtonFromView(), removeButton(), removeButtonFromStack() + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; +$this->crud->addButton($stack, $name, $type, $content, $position); // add a button; possible types are: view, model_function +$this->crud->addButtonFromModelFunction($stack, $name, $model_function_name, $position); // add a button whose HTML is returned by a method in the CRUD model +$this->crud->addButtonFromView($stack, $name, $view, $position); // add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + + +#### Filters + +Methods: addFilter(), modifyFilter(), removeFilter(), removeAllFilters(), filters() + +```php +// Manipulate what filters are shown in the table view. +// +// Note: check out CRUD > Features > Filters in the docs to see examples of $filter_definition_array +$this->crud->addFilter($filter_definition_array, $values, $filter_logic); +$this->crud->modifyFilter($name, $modifs_array); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +$this->crud->filters(); // gets all the filters +``` + + +#### 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. +$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->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. +$this->crud->enableExportButtons(); +``` + + +#### Responsive Table + +Methods: enableResponsiveTable(), disableResponsiveTable() + +```php +$this->crud->disableResponsiveTable(); +$this->crud->enableResponsiveTable(); +``` + + +#### Persistent Table + +Methods: enablePersistenTable(), disablePersistenTable() + +```php +$this->crud->disablePersistentTable(); +$this->crud->enablePersistentTable(); +``` + + +#### Page Length + +Methods: setDetaultPageLength(), setPageLengthMenu() + +```php +$this->crud->setDefaultPageLength(10); // number of rows shown in list view +$this->crud->setPageLengthMenu([100, 200, 300]); // page length menu to show in the list view +``` + + +#### Actions Column + +Methods: setActionColumnPriority() + +```php +// make the actions column (in the table view) hide when not enough space is available, by giving it an unreasonable priority +$this->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. +$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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->request->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: addField(), addFields(), modifyField(), modifyFields(), removeField(), removeFields(), removeAllFields(), beforeField(), afterField() + +```php +// ------ +// FIELDS +// ------ +// Manipulate what fields are shown in the create / update forms. +// +// Note: check out CRUD > Features > Field Types in the docs to see examples of $field_definition_array + +$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 +$this->crud->addFields($array_of_fields_definition_arrays); +$this->crud->modifyField($name, $modifs_array); +$this->crud->removeField('name'); +$this->crud->removeFields($array_of_names); +$this->crud->removeAllFields(); + +// ------ REORDER FIELDS +$this->crud->addField()->beforeField('name'); // will show this before the given field +$this->crud->addField()->afterField('name'); // will show this after the given field +``` + + +### Reorder + +Methods: enableReorder(), disableReorder(), isReorderEnabled() + +```php + protected function setupReorderOperation() + { + // model attribute to be shown on draggable items + $this->crud->set('reorder.label', 'name'); + // maximum number of nesting allowed + $this->crud->set('reorder.max_level', 2); + + // extras: + // $this->crud->disableReorder(); + // $this->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 +$this->crud->allowAccess('revisions'); +``` + + +## All Operations + +Methods: allowAccess(), denyAccess(), hasAccess(), 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. + +$this->crud->allowAccess('list'); +$this->crud->allowAccess(['list', 'create', 'delete']); +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); + +$this->crud->hasAccess('add'); // returns true/false +$this->crud->hasAccessOrFail('add'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false + +// ------------- +// EAGER LOADING +// ------------- + +// eager load a relationship +$this->crud->with('relationship_name'); + +// ------------ +// CUSTOM VIEWS +// ------------ + +// use a custom view for a CRUD operation +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); + +// ------------- +// CONTENT CLASS +// ------------- + +// use a custom CSS class for the content of a CRUD operation +$this->crud->setShowContentClass('col-md-12'); +$this->crud->setEditContentClass('col-md-12'); +$this->crud->setCreateContentClass('col-md-12'); +$this->crud->setListContentClass('col-md-12'); +$this->crud->setReorderContentClass('col-md-12'); +$this->crud->setRevisionsContentClass('col-md-12'); +$this->crud->setRevisionsTimelineContentClass('col-md-12'); + +// ------- +// GETTERS +// ------- + +$this->crud->getEntry($entry_id); +$this->crud->getEntries(); + +$this->crud->getFields('create/update/both'); + +// in your update() method, after calling parent::updateCrud() +$this->crud->getCurrentEntry(); + +// ------- +// OPERATIONS +// ------- + +$this->crud->setOperation('list'); +$this->crud->getOperation(); + +// ------- +// ACTIONS +// ------- + +$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 + +$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 + +$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 + +// --------------------------- +// CrudPanel Basic Information +// --------------------------- +$this->crud->setModel("App\Models\Example"); +$this->crud->setRoute("admin/example"); +// OR $this->crud->setRouteName("admin.example"); +$this->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 +$this->crud->setRequiredFields(StoreRequest::class, 'create'); +$this->crud->setRequiredFields(UpdateRequest::class, 'edit'); +``` diff --git a/4.0/crud-columns.md b/4.0/crud-columns.md new file mode 100644 index 00000000..ab7ab564 --- /dev/null +++ b/4.0/crud-columns.md @@ -0,0 +1,718 @@ +# 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) +- [```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) + + +### 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]); + +// 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]); +``` + +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(); +``` + + +## Default Column Types + + +### array + +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 + +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" +], +``` + + +### 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(); +``` + + +### closure + + +Show custom HTML based on a closure you specify in your EntityCrudController. Please note this column does not escape HTML before rendering. You need to do that yourself, if you consider it necessary. + +```php +[ + 'name' => 'created_at', + 'label' => 'Created At', + 'type' => 'closure', + 'function' => function($entry) { + return 'Created on '.$entry->created_at; + } +], +``` + + +### date + + +The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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/base.php``` file), whether the attribute is casted 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 +], +``` + + +### 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', +], +``` + + +### markdown + + +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', +], +``` + + +### 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 +], +``` +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.''; +} +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + + +### 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 +], +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + + +### 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 +], +``` + + +### 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; + + +### 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. + +``` +$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; + + +### 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' +], +``` + + +### 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 +], +``` + + +### 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_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 +], +``` + + +### table + + +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' + ] +], +``` + + +### upload_multiple + + +The ```table``` 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 +], +``` + + +### video + + +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', +], +``` + + +### 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 +], +``` + + +## 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:publish crud/columns/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:publish crud/columns/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 blad 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.*'); + } +]); +``` + +If you want a column to not be orderable at all, just pass ```'orderable' => false``` + + +### 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, // sure, why not +]); +``` + +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 +]); +``` + + +### 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. diff --git a/4.0/crud-fields.md b/4.0/crud-fields.md new file mode 100644 index 00000000..d8dca2fb --- /dev/null +++ b/4.0/crud-fields.md @@ -0,0 +1,1757 @@ +# 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). + + +### Mandatory Field Attributes + +For each of them, you only need to define it properly in the Controller. All field types will need at least three things: +- 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, your field definition array should look like: +```php +[ + 'name' => 'description', + 'type' => 'textarea', + 'label' => 'Article Description', +], +``` + + +### Optional Field Attributes + +There are a few optional attributes on all default field types, that you can use to easily achieve a few common customizations: + +```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 + 'wrapperAttributes' => [ + '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); +- **wrapperAttributes** - change or add actual HTML attributes to the div that contains the input; + + +### 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 +// add a field to both Create and Update operation +$this->crud->addField($field_definition_array); + +// add a field only to the Update operation +$this->crud->addField($field_definition_array); + +// shorthand: add a text field to both Create and Update operations +$this->crud->addField('db_column_name'); + +// add multiple fields +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); + +// change the attributes of a field +$this->crud->modifyField($name, $modifs_array); + +// remove a field from both operations +$this->crud->removeField('name'); + +// remove multiple fields from both operations +$this->crud->removeFields($array_of_names); + +// remove all fields from all operations +$this->crud->removeAllFields(); + +// FIELD ORDER + +// add a field before a given field +$this->crud->addField($field_definition_array)->beforeField('name'); + +// add a field after a given field +$this->crud->addField($field_definition_array)->afterField('name'); +``` + + +### Extra Fields Features + + +#### Fake Fields (all stored as JSON in the database) + +In case you want to store insignificant information for an entry that doesn't need a database column, you can add any number of Fake Fields, and all their information will be stored inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. + +**Step 1.** Use the fake attribute on your field: +```php +[ + '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 casted as array in ```$casts```; + +>If you need your fakes to also be translatable, remember to also place ```extras``` in your model's ```$translatable``` property. + +Example: +```php +[ + 'name' => 'meta_title', + 'label' => "Meta Title", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + 'name' => 'meta_description', + 'label' => "Meta Description", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + '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. + + +#### Split Fields 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) + +In order to use this feature, you just need to specify the tab name for each of your fields. Example: + +```php +// select_from_array +$this->crud->addField([ + 'name' => 'select_from_array', + 'label' => "Select from array", + 'type' => 'select_from_array', + 'options' => ['one' => 'One', 'two' => 'Two', 'three' => 'Three'], + 'allows_null' => false, + 'allows_multiple' => true, + 'tab' => 'Tab name here', +]); +``` + +If you forget to specify a tab name for a field, Backpack will place it above all tabs. + + + +## Default Field Types + + +### address_algolia + +Use [Algolia Places autocomplete](https://community.algolia.com/places/) 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 address string. + +```php +[ // Address + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_algolia', + // optional + 'store_as_json' => true +], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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. + + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-4-0/fields/address.png) + + +### address_google + +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 +[ // Address + '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' + ], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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-0/fields/address_google.png) + + +### browse + +If you've chosen to use [elFinder](http://elfinder.org/) upon Backpack installation, this button allows the admin to open [elFinder](http://elfinder.org/) and select a file from there. + +```php +[ // Browse + 'name' => 'image', + 'label' => 'Image', + 'type' => 'browse' +], +``` + + +Input preview: + +![CRUD Field - browse](https://backpackforlaravel.com/uploads/docs-4-0/fields/browse.png) + +Onclick preview: + +![CRUD Field - browse popup](https://backpackforlaravel.com/uploads/docs-4-0/fields/browse_popup.png) + + +### browse_multiple + +Open elFinder and select multiple files from there. + +```php +[ // Browse multiple + 'name' => 'files', + 'label' => 'Files', + 'type' => 'browse_multiple', + // 'multiple' => true, // enable/disable the multiple selection functionality + // 'sortable' => false, // enable/disable the reordering of chosen files 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. + +Input preview: + +![CRUD Field - browse_multiple](https://backpackforlaravel.com/uploads/docs-4-0/fields/browse_multiple.png) + + +### base64_image + +Upload an image and store it in the database as Base64. Notes: +- make sure the column type is LONGBLOB; +- detailed [instructions and customizations here](https://github.com/Laravel-Backpack/CRUD/pull/56#issue-164712261); + +```php +// base64_image +$this->crud->addField([ + '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-0/fields/base64_image.png) + + +### checkbox + +Checkbox for true/false. + +```php +[ // Checkbox + 'name' => 'active', + 'label' => 'Active', + 'type' => 'checkbox' +], +``` + +Input preview: + +![CRUD Field - checkbox](https://backpackforlaravel.com/uploads/docs-4-0/fields/checkbox.png) + + +### checklist + +```php +[ + 'label' => 'Roles', + 'type' => 'checklist', + 'name' => 'roles', + 'entity' => 'roles', + 'attribute' => 'name', + 'model' => "Backpack\PermissionManager\app\Models\Role", + 'pivot' => true, +]); +``` + +Input preview: + +![CRUD Field - checklist](https://backpackforlaravel.com/uploads/docs-4-0/fields/checklist.png) + + +### checklist_dependency + +```php + +// 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 + ], + '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 + ], + ], +], +``` + +Input preview: + +![CRUD Field - checklist_dependency](https://backpackforlaravel.com/uploads/docs-4-0/fields/checklist_dependency.png) + + +### ckeditor + +Show a wysiwyg CKEditor to the user. + +```php +[ // CKEditor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'ckeditor', + // optional: + 'extra_plugins' => ['oembed', 'widget'] + 'options' => [ + 'autoGrow_minHeight' => 200, + 'autoGrow_bottomSpace' => 50, + 'removePlugins' => 'resize,maximize', + ] +], +``` + +Input preview: + +![CRUD Field - ckeditor](https://backpackforlaravel.com/uploads/docs-4-0/fields/ckeditor.png) + + +### color + +```php +[ // Color + 'name' => 'background_color', + 'label' => 'Background Color', + 'type' => 'color', + 'default' => '#000000', +], +``` + +Input preview: + +![CRUD Field - color](https://backpackforlaravel.com/uploads/docs-4-0/fields/color.png) + + +### color_picker + +Show a pretty colour picker using [Bootstrap Colorpicker](https://itsjavi.com/bootstrap-colorpicker/). + +```php +[ // color_picker + 'label' => 'Background Color', + 'name' => 'background_color', + 'type' => 'color_picker', + 'default' => '#000000', + 'color_picker_options' => ['customClass' => 'custom-class'] +] +``` + +Input preview: + +![CRUD Field - color_picker](https://backpackforlaravel.com/uploads/docs-4-0/fields/color_picker.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 +[ // CustomHTML + 'name' => 'separator', + 'type' => 'custom_html', + 'value' => '
    ' +], +``` + + +### date + +```php +[ // Date + 'name' => 'birthday', + 'label' => 'Birthday', + 'type' => 'date' +], +``` + +Input preview: + +![CRUD Field - date](https://backpackforlaravel.com/uploads/docs-4-0/fields/date.png) + + +### date_picker + +Show a pretty [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/). + +```php +[ // 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-0/fields/date_picker.png) + + +### date_range + +Starting with Backpack\CRUD 3.1.59 + +Show a DateRangePicker and let the user choose a start date and end date. + +```php +[ // date_range + 'name' => ['start_date', 'end_date'], // db columns for start_date & end_date + 'label' => 'Event Date Range', + 'type' => 'date_range', + // OPTIONALS + 'default' => ['2019-03-28 01:01', '2019-04-05 02:00'], // default values for start_date & end_date + 'date_range_options' => [ + // options sent to daterangepicker.js + '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-0/fields/date_range.png) + + +### datetime + +```php +[ // 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'] = \Date::parse($value); + } +``` +Otherwise the input's datetime-local format will cause some errors. + +Input preview: + +![CRUD Field - datetime](https://backpackforlaravel.com/uploads/docs-4-0/fields/datetime.png) + + +### datetime_picker + +Show a [Bootstrap Datetime Picker](https://eonasdan.github.io/bootstrap-datetimepicker/). + +```php +[ // DateTime + 'name' => 'start', + 'label' => 'Event start', + 'type' => 'datetime_picker', + // optional: + 'datetime_picker_options' => [ + 'format' => 'DD/MM/YYYY HH:mm', + 'language' => 'fr' + ], + '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'] = \Date::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-0/fields/datetime_picker.png) + + +### email + +```php +[ // Email + 'name' => 'email', + 'label' => 'Email Address', + 'type' => 'email' +], +``` + +Input preview: + +![CRUD Field - email](https://backpackforlaravel.com/uploads/docs-4-0/fields/email.png) + + + +### enum + +Show a select with the values in the database for that ENUM field. Requires that the db column type is "enum". If the db column allows null, the " - " value will also show up in the select. + +```php +[ // Enum + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum' +], +``` + +PLEASE NOTE the enum field only works for MySQL databases. + +Input preview: + +![CRUD Field - enum](https://backpackforlaravel.com/uploads/docs-4-0/fields/enum.png) + + +### hidden + +Include an in the form. + +```php +[ // Hidden + 'name' => 'status', + 'type' => 'hidden' +], +``` + + +### icon_picker + +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 +[ // 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-0/fields/icon_picker.png) + + +### image + +Upload an image and store it on the disk. + +**Step 1.** Show the field. +```php +// image +$this->crud->addField([ + 'label' => "Profile Image", + 'name' => "image", + 'type' => 'image', + 'upload' => true, + 'crop' => true, // set to true to allow cropping, false to disable + 'aspect_ratio' => 1, // omit or set to 0 to allow any aspect ratio + // 'disk' => 's3_bucket', // in case you need to show images from a different disk + // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; +]); +``` + +**Step 2.** Set a mutator on your Model, so that the file can be stored. You can use this boilerplate code and modify it to match your use case: + +```php +// .. + +use Illuminate\Support\Str; + +// .. + +Class Product extends Model +{ + // .. + + public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = config('backpack.base.root_disk_name'); // or use your own disk, defined in config/filesystems.php + $destination_path = "public/uploads/folder_1/folder_2"; // path relative to the disk above + + // if the image was erased + if ($value==null) { + // delete the image from disk + \Storage::disk($disk)->delete($this->{$attribute_name}); + + // set null in the database column + $this->attributes[$attribute_name] = null; + } + + // if a base64 was sent, store it in the db + if (starts_with($value, 'data:image')) + { + // 0. Make the image + $image = \Image::make($value)->encode('jpg', 90); + + // 1. Generate a filename. + $filename = md5($value.time()).'.jpg'; + + // 2. Store the image on disk. + \Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream()); + + // 3. Delete the previous image, if there was one. + \Storage::disk($disk)->delete($this->{$attribute_name}); + + // 4. Save the public path to the database + // but first, remove "public/" from the path, since we're pointing to it from the root folder + // that way, what gets saved in the database is the user-accesible URL + $public_destination_path = Str::replaceFirst('public/', '', $destination_path); + $this->attributes[$attribute_name] = $public_destination_path.'/'.$filename; + + } + } + +// .. +``` +> **The uploaded images are not deleted for you.** If you delete an entry (using the CRUD or anywhere inside your app), the image file won't be deleted from the disk. +> If you're NOT using soft deletes on that Model and want the image to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +> ```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } + ``` + +**A note about aspect_ratio** +The value for aspect ratio is a float that represents the ratio of the cropping rectangle height and width. By way of example, + +- Square = 1 +- Landscape = 2 +- Portrait = 0.5 + +And you can, of course, use any value for more extreme rectangles. + +Input preview: + +![CRUD Field - image](https://backpackforlaravel.com/uploads/docs-4-0/fields/image.png) + + + +### month + +```php +[ // Month + 'name' => 'month', + 'label' => 'Month', + 'type' => 'month' +], +``` + +Input preview: + +![CRUD Field - month](https://backpackforlaravel.com/uploads/docs-4-0/fields/month.png) + + +### number + +Shows an input type=number to the user, with optional prefix and suffix: + +```php +[ // 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-0/fields/number.png) + + +### page_or_link + +Select an existing page from PageManager or an internal or external link. It's used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: +```php +[ // PageOrLink + 'name' => 'type', + 'label' => "Type", + 'type' => 'page_or_link', + 'page_model' => '\Backpack\PageManager\app\Models\Page' +], +``` + +Input preview: + +![CRUD Field - page_or_link](https://backpackforlaravel.com/uploads/docs-4-0/fields/page_or_link.png) + + +### password + +```php +[ // Password + 'name' => 'password', + 'label' => 'Password', + 'type' => 'password' +], +``` + +Input preview: + +![CRUD Field - password](https://backpackforlaravel.com/uploads/docs-4-0/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() + { + $this->crud->request = $this->crud->validateRequest(); + + // Encrypt password if specified. + if ($request->input('password')) { + $request->request->set('password', Hash::make($request->input('password'))); + } else { + $request->request->remove('password'); + } + + 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 +[ // 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-0/fields/radio.png) + + +### range + +```php +[ // Range + 'name' => 'range', + 'label' => 'Range', + 'type' => 'range' +], +``` + +Input preview: + +![CRUD Field - range](https://backpackforlaravel.com/uploads/docs-4-0/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 +[ // 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 + + + // optional + 'model' => "App\Models\Category", + '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 +], +``` + +Input preview: + +![CRUD Field - select](https://backpackforlaravel.com/uploads/docs-4-0/fields/select.png) + + + +### select_grouped + +Display a select where the options are grouped by a second entity (like Categories). + +```php +[ // 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 +], +``` + +Input preview: + +![CRUD Field - select_grouped](https://backpackforlaravel.com/uploads/docs-4-0/fields/select_grouped.png) + + +### select2 (1-n relationship) + +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 +[ // Select2 + 'label' => "Category", + 'type' => 'select2', + '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 + + // optional + 'model' => "App\Models\Category", // foreign key model + 'default' => 2, // set the default value of the select2 + '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 +], +``` + +Input preview: + +![CRUD Field - select2](https://backpackforlaravel.com/uploads/docs-4-0/fields/select2_nested.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 relationships should already be defined on your models as hasMany() or belongsToMany(). + +```php +[ // SelectMultiple = n-n relationship (with pivot table) + 'label' => "Tags", + '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 + 'pivot' => true, // on create&update, do you need to add/delete pivot table entries? + + // optional + 'model' => "App\Models\Tag", // foreign key model + '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 +], +``` + +Input preview: + +![CRUD Field - select_multiple](https://backpackforlaravel.com/uploads/docs-4-0/fields/select_multiple.png) + + + + +### select2_multiple (n-n relationship) + +[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 relationships should already be defined on your models as hasMany() or belongsToMany(). + +```php +[ // Select2Multiple = n-n relationship (with pivot table) + '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? + // 'select_all' => true, // show Select All and Clear buttons? + + // optional + 'model' => "App\Models\Tag", // foreign key model + '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 +], +``` + +Input preview: + +![CRUD Field - select2_multiple](https://backpackforlaravel.com/uploads/docs-4-0/fields/select2_multiple.png) + + +### select2_nested + +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 +[ // 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 +], +``` + +Input preview: + +![CRUD Field - select2_nested](https://backpackforlaravel.com/uploads/docs-4-0/fields/select2_nested.png) + + + +### select2_grouped + +Display a select2 where the options are grouped by a second entity (like Categories). + +```php +[ // 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 +], +``` + +Input preview: + +![CRUD Field - select2_grouped](https://backpackforlaravel.com/uploads/docs-4-0/fields/select2_grouped.png) + + + +### select_and_order + +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 +[ // select_and_order + 'name' => 'featured', + 'label' => "Featured", + 'type' => 'select_and_order', + 'options' => [ + 1 => "Option 1", + 2 => "Option 2" + ] +], +``` + +Also possible: + +```php +[ // 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-0/fields/select_and_order.png) + + + +### select_from_array + +Display a select with the values you want: + +```php +[ // 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-0/fields/select_from_array.png) + + +### select2_from_array + +Display a select2 with the values you want: + +```php +[ // 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-0/fields/select2_from_array.png) + + +### select2_from_ajax + +Display a select2 that takes its values from an AJAX call. + +``` +[ + // 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("http://github.com/api/category"), // url to controller search function (with /{id} should return model) + + // OPTIONAL + // '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' => 'GET', // 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) + ] +``` + +Of course, you also need to create a controller and routes for the data_source above. Here's an example: + +``` +Route::get('/api/category', 'Api\CategoryController@index'); +Route::get('/api/category/{id}', 'Api\CategoryController@show'); +``` + +``` +input('q'); + $page = $request->input('page'); + + if ($search_term) + { + $results = Category::where('name', 'LIKE', '%'.$search_term.'%')->paginate(10); + } + else + { + $results = Category::paginate(10); + } + + return $results; + } + + public function show($id) + { + return Category::find($id); + } +} +``` + +Input preview: + +![CRUD Field - select2_from_array](https://backpackforlaravel.com/uploads/docs-4-0/fields/select2_from_array.png) + + +### select2_from_ajax_multiple + +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. + +``` +[ + // 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("http://github.com/api/cities"), // 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 + '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 + // '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) + ] +``` + +Of course, you also need to create a controller and routes for the data_source above. Here's an example: + +``` +Route::get('/api/category', 'Api\CityController@index'); +Route::get('/api/category/{id}', 'Api\CityController@show'); +``` + +``` +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-0/fields/select2_from_ajax_multiple.png) + + +### simplemde + +Show a [SimpleMDE markdown editor](https://simplemde.com/) to the user. + +```php +[ // SimpleMDE + 'name' => 'description', + 'label' => 'Description', + 'type' => 'simplemde', + // optional + // 'simplemdeAttributes' => [ + // 'promptURLs' => true, + // 'status' => false, + // 'spellChecker' => false, + // 'forceSync' => true, + // ], + // 'simplemdeAttributesRaw' => $some_json +], +``` + +Input preview: + +![CRUD Field - simplemde](https://backpackforlaravel.com/uploads/docs-4-0/fields/simplemde.png) + + +### summernote + +Show a [Summernote wysiwyg editor](http://summernote.org/) to the user. + +```php +[ // Summernote + 'name' => 'description', + 'label' => 'Description', + 'type' => 'summernote', + // 'options' => [], // easily pass parameters to the summernote JS initialization +], +``` + +Input preview: + +![CRUD Field - summernote](https://backpackforlaravel.com/uploads/docs-4-0/fields/summernote.png) + + +### table + +Show a table with multiple inputs per row and store the values as JSON in the database. The user can add more rows and reorder the rows as they please. + +```php +[ // 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.co/blog/laravel-5.0-eloquent-attribute-casting) on your model when working with JSON stored in database columns, and cast your this attribute to either ```object``` or ```array```. + +Input preview: + +![CRUD Field - table](https://backpackforlaravel.com/uploads/docs-4-0/fields/table.png) + + +### text + +The basic field type, all it needs is the two mandatory parameters: name and label. + +```php +[ // 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' + //], // extra HTML attributes and values your input might need + //'wrapperAttributes' => [ + //'class' => 'form-group col-md-12' + //], // extra HTML attributes for the field wrapper - mostly for resizing fields + //'readonly'=>'readonly', +], +``` + +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-0/fields/text.png) + + +### textarea + +Show a textarea to the user. + +```php +[ // Textarea + 'name' => 'description', + 'label' => 'Description', + 'type' => 'textarea' +], +``` + +Input preview: + +![CRUD Field - textarea](https://backpackforlaravel.com/uploads/docs-4-0/fields/textarea.png) + + +### time + +```php +[ // Time + 'name' => 'start', + 'label' => 'Start time', + 'type' => 'time' +], +``` + + +### tinymce + +Show a wysiwyg (TinyMCE) to the user. + +```php +[ // 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-0/fields/tinymce.png) + + +### upload + +**Step 1.** Show a file input to the user: +```php +[ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the file from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path); + + // return $this->attributes[{$attribute_name}]; // uncomment if this is a translatable field + } +``` + +**How it works:** + +The field sends the file, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the file is stored on the disk. + +>NOTE: If this field is mandatory (required in validation) please use the [sometimes laravel validation rule](https://laravel.com/docs/5.8/validation#conditionally-adding-rules) together with **required** in your validation. (sometimes|required|file etc... ) + +[The ```uploadFileToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/CrudTrait.php#L108-L129) will take care of everything for most use cases: + +```php +/** + * Handle file upload and DB storage for a file: + * - on CREATE + * - stores the file at the destination path + * - generates a name + * - stores the full path in the DB; + * - on UPDATE + * - if the value is null, deletes the file and sets null in the DB + * - if the value is different, stores the different file and updates DB value + * / +public function uploadFileToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the file to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } +``` + +Input preview: + +![CRUD Field - upload](https://backpackforlaravel.com/uploads/docs-4-0/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 +[ // Upload + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the files from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setPhotosAttribute($value) + { + $attribute_name = "photos"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path); + } +``` + +**Step 3.** Since the filenames are stored in the database as a JSON array, we're going to use [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model, so every time we get the filenames array from the database it's converted from a JSON array to a PHP array: +```php + protected $casts = [ + 'photos' => 'array' + ]; +``` + +**How it works:** + +The field sends the files, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the files are stored on the disk. + +[The ```uploadMultipleFilesToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/CrudTrait.php#L154-L189) will take care of everything for most use cases: + +``` +/** + * Handle multiple file upload and DB storage: + * - if files are sent + * - stores the files at the destination path + * - generates random names + * - stores the full path in the DB, as JSON array; + * - if a hidden input is sent to clear one or more files + * - deletes the file + * - removes that file from the DB. + * / +public function uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the files to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + if (count((array)$obj->photos)) { + foreach ($obj->photos as $file_path) { + \Storage::disk('public_folder')->delete($file_path); + } + } + }); + } +``` + +You might notice the field is using a ```clear_photos``` variable. Don't worry, you don't need it in your db table. That's just used to delete photos upon "update". If you use ```$fillable``` on your model, just don't include it. If you use ```$guarded``` on your model, place it in guarded. + +Input preview: + +![CRUD Field - upload_multiple](https://backpackforlaravel.com/uploads/docs-4-0/fields/upload_multiple.png) + + +### url + +```php +[ // URL + 'name' => 'link', + 'label' => 'Link to video file', + 'type' => 'url' +], +``` + + +### video + +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 +[ // 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.co/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)). + + + +### view + +Load a custom view in the form. + +```php +[ // 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). + + +### week + +```php +[ // Week + 'name' => 'first_week', + 'label' => 'First week', + 'type' => 'week' +], +``` + +Input preview: + +![CRUD Field - week](https://backpackforlaravel.com/uploads/docs-4-0/fields/week.png) + + +### wysiwyg + +Show a wysiwyg (CKEditor) to the user. + +```php +[ // WYSIWYG Editor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'wysiwyg' +], +``` + + +## 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:publish crud/fields/field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:publish crud/fields/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. 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 +[ // 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 + +
    + + + + {{-- HINT --}} + @if (isset($field['hint'])) +

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

    + @endif +
    + + +@if ($crud->fieldTypeNotLoaded($field)) + @php + $crud->markFieldTypeAsLoaded($field); + @endphp + + {{-- 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 +@endif +``` + +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; diff --git a/4.0/crud-filters.md b/4.0/crud-filters.md new file mode 100644 index 00000000..a310950a --- /dev/null +++ b/4.0/crud-filters.md @@ -0,0 +1,471 @@ +# Filters + +--- + + +## About + +Backpack CRUD allows you to show a filters bar right above the entries table. When selected or modified, they reload the DataTables results. The search will then search within the filtered elements. + +![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()```. + + +### Filters API + +In order to manipulate filters, you can use: + +```php +$this->crud->addFilter($options, $values, $filter_logic); + +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); + +$this->crud->filters(); // gets all the filters +``` + + +### Adding a filter + +When adding a filter 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) + $this->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: + // $this->crud->addClause('draft'); +}); +``` +> 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->request```; +> - 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 above are simple filters. + +```php +// simple filter +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'active', + 'label'=> 'Active' +], +false, +function() { // if the filter is active + // $this->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 +// simple filter +$this->crud->addFilter([ + 'type' => 'text', + 'name' => 'description', + 'label'=> 'Description' +], +false, +function($value) { // if the filter is active + // $this->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 +// date filter +$this->crud->addFilter([ + 'type' => 'date', + 'name' => 'date', + 'label' => 'Date' +], + false, +function ($value) { // if the filter is active, apply these constraints + // $this->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 +// daterange filter +$this->crud->addFilter([ + 'type' => 'date_range', + 'name' => 'from_to', + 'label' => 'Date range' +], +false, +function ($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); +``` + + +### 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 +// dropdown filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'dropdown', + 'label'=> 'Status' +], [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', +], function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2', + 'label' => 'Status' +], function () { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function ($value) { // if the filter is active + // $this->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 second parameter (the closure), with something like ```return \Backpack\NewsCRUD\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. You can decide yourself if the query for each element should use 'where' or 'orWhere', in the third parameter of the ```addFilter()``` method. + +![Backpack CRUD Select2_multiple Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2_multiple.png) + +```php +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2_multiple', + 'label'=> 'Status' +], function() { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function($values) { // if the filter is active + // foreach (json_decode($values) as $key => $value) { + // $this->crud->addClause('where', 'published', $value); + // } +}); +``` + + +### 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) + +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'); +``` + +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'); + return $options; +} +``` + +3. Add the select2_ajax filter and for the second parameter ("values") specify the exact route. + +```php +// select2_ajax filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2_ajax', + 'label'=> 'Category', + 'placeholder' => 'Pick a category' +], +url('http://github.com/admin/test/ajax-category-options'), // the ajax route +function($value) { // if the filter is active + // $this->crud->addClause('where', 'category_id', $value); +}); +``` + + +### Range + +Shows two number inputs, for min and max. + +![Backpack CRUD Range Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/range.png) + +```php +$this->crud->addFilter([ + 'name' => 'number', + 'type' => 'range', + 'label'=> 'Range', + 'label_from' => 'min value', + 'label_to' => 'max value' +], +false, +function($value) { // if the filter is active + $range = json_decode($value); + if ($range->from) { + $this->crud->addClause('where', 'number', '>=', (float) $range->from); + } + if ($range->to) { + $this->crud->addClause('where', 'number', '<=', (float) $range->to); + } +}); +``` + +### 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 +// custom filter view +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'view', + 'view' => 'package::columns.column_type_name', // or path to blade file + 'label'=> 'Category', + 'placeholder' => 'Pick a category', +], +false, +function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'published', + 'type' => 'select2', + 'label'=> 'Published' +], function() { + return \App\Models\Test::getEnumValuesAsAssociativeArray('published'); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'published', $value); +}); +``` + +Use a select2 to filter by a 1-n relationship: +```php +// select2 filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2', + 'label'=> 'Category' +], function() { + return \App\Models\Category::all()->pluck('name', 'id')->toArray(); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'category_id', $value); +}); +``` + +Use a select2_multiple to filter Products by an n-n relationship: +```php +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'tags', + 'type' => 'select2_multiple', + 'label'=> 'Tags' +], function() { // the options that show up in the select2 + return Product::all()->pluck('name', 'id')->toArray(); +}, function($values) { // if the filter is active + 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 +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'published', + 'label'=> 'Published' +], +false, +function() { // if the filter is active (the GET parameter "published" exits) + $this->crud->addClause('published'); +}); +``` + +Use a simple filter to show the softDeleted items (trashed): +```php +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'trashed', + 'label'=> 'Trashed' +], +false, +function($values) { // if the filter is active + $this->crud->query = $this->crud->query->onlyTrashed(); +}); +``` diff --git a/4.0/crud-how-to.md b/4.0/crud-how-to.md new file mode 100644 index 00000000..2298b49f --- /dev/null +++ b/4.0/crud-how-to.md @@ -0,0 +1,358 @@ +# How To for Backpack\CRUD + +--- + +In addition the usual CRUD functionality, Backpack also allows you to do a few more complicated things: + + +## 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/crud``` folder, it will pick up those instead; you can use this method to overwrite a blade file for all CRUDs; +- 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 +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); +``` + + +## Customize CSS and JS for Default CRUD Operations + +Each default Backpack operation has its own CSS and JS file, in: +- ```public/vendor/backpack/crud/css``` +- ```public/vendor/backpack/crud/js``` + +If you don't find one there, you can create one, and Backpack will pick it up in that operation's view (ex: ```create.css``` or ```list.js```). + + +## 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. + + +## 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:publish crud/fields/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. + + +## 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); + }); + } + } +} +``` + + +## 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 + $this->crud->addColumn([ + 'label' => "Location", + 'type' => 'select', + 'name' => 'location_id', + 'entity' => 'location', + 'attribute' => 'title', + 'model' => "App\Models\Location" + ]); + + $this->crud->addColumn([ + '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" + ]); +``` + + +## Use the Media Library (File Manager) + +If you've chosen to install [elFinder](http://elfinder.org/) when installing Backpack, you already have a media manager. And it's integrated into: +- TinyMCE (as "tinymce" fieldtype) +- CKEditor (as "ckeditor" fieldtype) +- CRUD "browse" fieldtype +- standalone, at the *your-project/admin/elfinder* route; + +For the integration, barryvdh's [laravel-elfinder](https://github.com/barryvdh/laravel-elfinder) package is used. + +![Backpack CRUD ListEntries](https://backpackforlaravel.com/uploads/docs-4-0/media_library.png) + + +## 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) If you'd also like a file manager, run: +```bash +composer require barryvdh/laravel-elfinder +mkdir -p public/uploads +php artisan elfinder:publish +php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag="elfinder" +php artisan backpack:base:add-sidebar-content '
  • File manager
  • ' +``` + +3) Actually install Backpack: +```bash +php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag="minimum" +php artisan vendor:publish --provider="Prologue\Alerts\AlertsServiceProvider" +php artisan migrate +php artisan backpack:publish-user-model +php artisan backpack:publish-middleware +``` + + +## 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 +$this->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) + $this->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. + + +## Add a select2 field that depends on another field + +The ```select2_from_ajax``` and ```select2_from_ajax_multiple``` fields allow you to filter the results of a select2, depending on what has already been selected in a form. Say you have to 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 you CrudController you would do: + +```php +$this->crud->addField([ // SELECT2 + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category', + 'entity' => 'category', + 'attribute' => 'name', +]); + +$this->crud->addField([ // select2_from_ajax: 1-n relationship + '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('http://github.com/api/article'), // url to controller search function (with /{id} should return model) + 'placeholder' => 'Select an article', // placeholder for the select + 'minimum_input_length' => 0, // minimum characters to type before querying results + 'dependencies' => ['category'], // when a dependency changes, this select2 is reset to null + // 'method' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST) +]); +``` + +**DIFFERENT HERE**: ```minimum_input_length``` and ```dependencies```. + +2. That second select points to routes that need to be registered: + +```php +Route::get('api/article', 'App\Http\Controllers\Api\ArticleController@index'); +Route::get('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show'); +``` + +**DIFFERENT HERE**: Nothing. + +3. Then that controller would look something like this: + +```php +input('q'); + $form = collect($request->input('form'))->pluck('value', 'name'); + + $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 $options->paginate(10); + } + + 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. + + + +## Change the content class for an operation + +If you want to make the contents of an operation take more / less space from the window, 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 +$this->crud->setCreateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setUpdateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setListContentClass('col-md-8 col-md-offset-2'); +$this->crud->setShowContentClass('col-md-8 col-md-offset-2'); +$this->crud->setReorderContentClass('col-md-8 col-md-offset-2'); +$this->crud->setRevisionsTimelineContentClass('col-md-8 col-md-offset-2'); +``` + + + +## 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). diff --git a/4.0/crud-operation-clone.md b/4.0/crud-operation-clone.md new file mode 100644 index 00000000..856389ce --- /dev/null +++ b/4.0/crud-operation-clone.md @@ -0,0 +1,116 @@ +# Clone + +-- + + +## 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. + + +## 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 +crud->setModel(\App\Models\Product::class); + $this->crud->setRoute(backpack_url('http://github.com/product')); + $this->crud->setEntityNameStrings('product', 'products'); + } +} +``` + +This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### 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) +{ + $this->crud->hasAccessOrFail('clone'); + $this->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:publish crud/buttons/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. + + +### 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:publish crud/buttons/bulk_clone +``` diff --git a/4.0/crud-operation-create.md b/4.0/crud-operation-create.md new file mode 100644 index 00000000..5506248c --- /dev/null +++ b/4.0/crud-operation-create.md @@ -0,0 +1,120 @@ +# Create + +--- + + +## About + +This operation allows your admins to add new entries to a database table. + +![CRUD Create Operation](https://backpackforlaravel.com/uploads/screenshots/news_add.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. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**. The store code is inside a trait, so you can easily overwrite it: + +```php +crud->request->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->addField(['type' => 'hidden', 'name' => 'author_id']); + // $this->crud->request->request->remove('password_confirmation'); + // $this->crud->removeField('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/5.5/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. + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` diff --git a/4.0/crud-operation-delete.md b/4.0/crud-operation-delete.md new file mode 100644 index 00000000..16577983 --- /dev/null +++ b/4.0/crud-operation-delete.md @@ -0,0 +1,96 @@ +# Delete + +-- + + +## 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) +{ + $this->crud->hasAccessOrFail('delete'); + + return $this->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:publish crud/buttons/delete +``` + + +## Delete Multiple Items (Bulk Delete) + +In addition to the button for each entry, you can show checkboxes next to each element, and allow your 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. + + +### 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($id) +{ + // 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:publish crud/buttons/bulk_delete +``` diff --git a/4.0/crud-operation-list-entries.md b/4.0/crud-operation-list-entries.md new file mode 100644 index 00000000..993ce593 --- /dev/null +++ b/4.0/crud-operation-list-entries.md @@ -0,0 +1,229 @@ +# List + +--- + + +## 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-0/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-0/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 +crud->addColumn(); + } +} +``` + +Configuration for this operation should be done inside your ```setupListOperation()``` method. **For a minimum setup, you only need to define the columns you need to show in the table.** + + +### 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 +$this->crud->addColumn([ + '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 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) + +Backpack adds a few buttons by default: +- ```create``` to the ```top``` stack; +- ```update``` and ```delete``` to the ```line``` stack; + +To learn more about buttons, **check out the [Buttons](/docs/{{version}}/crud-buttons) documentation page**. + + +### Filters + +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**. + + +### 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://backpackforlaravel.com/uploads/docs-4-0/operations/listEntries_details_row.png) + +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. You'll want to overwrite that method to show anything you'd like in the details row. + +To use, inside your ```EntityCrudController```: +1. Enable the functionality: ```$this->crud->enableDetailsRow();``` +2. Overwrite the ```showDetailsRow($id)``` method; + +Alternative for the 2nd step: overwrite ```views/backpack/crud/details_row.blade.php``` which is called by the default ```showDetailsRow($id)``` functionality. + + +#### Export Buttons + +Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->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. + + +#### 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. +$this->crud->addClause('active'); // apply a 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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->request->has('order')) {}"; that way, your custom order is applied ONLY IF the user hasn't forced another order (by clicking a column heading) +``` + + +#### 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 ```$this->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 ```$this->crud->enablePersistentTable();``` +- disable it inside a particular crud controller with ```$this->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 `$this->crud->setOperationSetting('persistentTableDuration', 120); in your setupListOperation()` for 2 hours persistency. The default is `false` which means forever. + + +## 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. diff --git a/4.0/crud-operation-reorder.md b/4.0/crud-operation-reorder.md new file mode 100644 index 00000000..b821e30c --- /dev/null +++ b/4.0/crud-operation-reorder.md @@ -0,0 +1,82 @@ +# Reorder + +--- + + +## About + +This operation allows your admins to reoder & 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```. + +Additionnaly, 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 +crud->set('reorder.label', 'name'); + // define how deep the admin is allowed to nest the items + // for infinite levels, set it to 0 + $this->crud->set('reorder.max_level', 2); + } +} +``` + +This will: +- allow access to the Reorder operation; +- make a "Reorder" button appear next to "Add entry" in the List view, if the List operation is enabled; +- enable the routes needed by the Reorder operation; + +Where: +- ```attribute_name``` should be the attribute you want shown on the draggable elements (ex: ```name```); +- ```ALLOWED_DEPTH``` should be an integer, how many levels deep would you allow your admin to go when nesting; for infinit levels, you should set it to ```0```; + + +## How It Works + +The ```/reorder``` route points to a ```reorder()``` method in your ```EntityCrudController```. + + + +## 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 + $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:publish crud/buttons/reorder +``` \ No newline at end of file diff --git a/4.0/crud-operation-revisions.md b/4.0/crud-operation-revisions.md new file mode 100644 index 00000000..38f8f5d4 --- /dev/null +++ b/4.0/crud-operation-revisions.md @@ -0,0 +1,65 @@ +# Revisions + +--- + + +## About + +Revisions 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, ```Revisions``` 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: + +1. Create the revisions table: + +```bash +cp vendor/venturecraft/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php database/migrations/ && php artisan migrate +``` + +2. Use [venturecraft/revisionable](https://github.com/VentureCraft/revisionable#implementation) on your model, and an ```identifiableName()``` method that returns an attribute on the model that the admin can use to distiguish 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 MyApp\Models; + +class Article extends Eloquent { + use \Backpack\CRUD\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(); + } +} +``` + +3. In your CrudController, add the Revisions 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: + +![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. Inside this method, it uses ```setFromDb()``` to try to magically figure out all attributes you would like shown for this Model, and shows them using [Column types](/docs/{{version}}/crud-columns) inside ```show.blade.php```. + + +## How to Use + +To enable this operation, you need to use the ```ShowOperation``` trait on your CrudController: + +```php +crud->set('show.setFromDb', false); + + // example logic + $this->crud->addColumn([ + 'name' => 'table', + 'label' => 'Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ] + ]); + $this->crud->addColumn([ + 'name' => 'fake_table', + 'label' => 'Fake Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ], + ]); + $this->crud->addColumn('text'); + // $this->crud->removeColumn('date'); + // $this->crud->removeColumn('extras'); + } +``` + + +## 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); + // cutom logic after + return $content; +} +``` diff --git a/4.0/crud-operation-update.md b/4.0/crud-operation-update.md new file mode 100644 index 00000000..327fee5c --- /dev/null +++ b/4.0/crud-operation-update.md @@ -0,0 +1,216 @@ +# Update + +--- + + +## About + +This operation allows your admins to edit entries from the database. + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/screenshots/news_add.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(); + } +} +``` + +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); +// add a field to both the Update and Update operations +$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). + + +## 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 ```store()``` 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 typehinted 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. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**. The store code is inside a trait, so you can easily overwrite it: + +```php +crud->request->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->addField(['type' => 'hidden', 'name' => 'author_id']); + // $this->crud->request->request->remove('password_confirmation'); + // $this->crud->removeField('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, you'll need to use backpack's classes instead of the ones provided by cviebrock/eloquent-sluggable: + +```php + [ + 'source' => 'slug_or_name', + ], + ]; + } +} +``` + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` diff --git a/4.0/crud-operations.md b/4.0/crud-operations.md new file mode 100644 index 00000000..0f8d93a3 --- /dev/null +++ b/4.0/crud-operations.md @@ -0,0 +1,964 @@ +# 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 +Routes()```; this gets called in your ```routes/backpack/custom.php``` by the ```Route::crud('product', 'ProductCrudController``` macro, which determines which routes to register for that CrudController; +- default setup inside a ```setupDefaults()``` method, that gets called automatically by CrudController when you use that operation on a controller; +- methods that return views, or perform certain operations; + +Of course, you can easily [add custom operations](/#creating-a-custom-operation). + + +## 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 an Eloquent model, with pagination, search, filters; +- [Create](/docs/{{version}}/crud-operation-create) - allows the admin to add a new entry; +- [Update](/docs/{{version}}/crud-operation-update) - allows the admin to edit an existing entry; +- [Show](/docs/{{version}}/crud-operation-show) - allows the admin to preview an entry; +- [Delete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove and entry; +- [BulkDelete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove multiple entries in one go; +- [Clone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of a database entry; +- [BulkClone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of multiple database entries in one go; +- [Reorder](/docs/{{version}}/crud-operation-reorder) - allows the admin to reorder & nest all entries of a model, in a hierarchy tree; +- [Revisions](/docs/{{version}}/crud-operation-revisions) - shows an audit log of all changes to an entry, and allows the admin to undo modifications; + + + +### 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/5.7/Illuminate/Routing/Route.html): + +- ```\Route::getCurrentRoute()->getAction()``` or ```$this->request->route()->getAction()```: +``` +array:7 [▼ + "middleware" => array:2 [▼ + 0 => "web" + 1 => "admin" + ] + "as" => "crud.monster.index" + "uses" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "controller" => "App\Http\Controllers\Admin\MonsterCrudController@index" + "namespace" => "App\Http\Controllers\Admin" + "prefix" => "admin" + "where" => [] +] +``` +- ```\Route::getCurrentRoute()->getActionName()``` or ```$this->request->route()->getActionName()```: +``` +App\Http\Controllers\Admin\MonsterCrudController@index +``` +- ```\Route::getCurrentRoute()->getActionMethod()``` or ```$this->request->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']); + +$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($id) + { + // 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($id) + { + // do something + // return something + } +} +``` + +In the example above, you could just do ```use \App\Http\Controllers\Admin\CustomOperations\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 setupPublishRoutes($segment, $routeName, $controller) + { + Route::get($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 ($crud->request->ajax()) @endpush @endif + +@if (!$crud->request->ajax()) @endpush @endif +``` + +4. We can now actually add this button to our ```UserCrudController::setupCloneOperation()``` method, or our ```setupCloneDefaults()``` method: + +```php +protected 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('http://github.com/dashboard'), + $crud->entity_name_plural => url(http://github.com/$crud-%3Eroute), + '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->request->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 +``` diff --git a/4.0/crud-tutorial.md b/4.0/crud-tutorial.md new file mode 100644 index 00000000..464c4f91 --- /dev/null +++ b/4.0/crud-tutorial.md @@ -0,0 +1,375 @@ +# CRUD Tutorial + +--- + +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 + +Since we don't have an Eloquent model for it already, we're going to use [Jeffrey Way's Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) package, which you've most likely installed along with Backpack, to generate the migration. + +```zsh +# STEP 0. create migration +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate +``` + +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 item in the sidebar menu, in ```resources/views/vendor/backpack/base/inc/sidebar_content.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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + protected function setupListOperation() + { + // TODO: remove setFromDb() and manually define Columns, maybe Filters + $this->crud->setFromDb(); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + + // TODO: remove setFromDb() and manually define Fields + $this->crud->setFromDb(); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); + } +} +``` + +#### The Basics + +What we should notice inside this TagCrudController is that: +- ```TagCrudController extends CrudController```; +- ```TagCrudController``` has a ```setup()``` method, where can plug in 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; + +As we can tell from the comments in our ```setupXxxOperation()``` methods, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. + +That being said, since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys & girls. + +#### Option 1. SetupXxxOperation Methods + +We can either define each operations inside its ```setupXxxOperation()``` method: + +```php + protected function setupListOperation() + { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); // since this calls the methods above, no need to do anything here + } +``` + +This will: +- disable the ```setFromDb()``` functionality (since we deleted that line); +- 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; + +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. + +#### Option 2. Operation Closures + +An alternative to defining operation inside ```setupXxxOperation()``` methods is to do it 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 would like 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() + { + $this->crud->setModel('App\Models\Tag'); + $this->crud->setRoute(config('backpack.base.route_prefix') . '/tag'); + $this->crud->setEntityNameStrings('tag', 'tags'); + + $this->crud->operation('list', function() { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + }); + + $this->crud->operation(['create', 'update'], function() { + $this->crud->addValidation(TagCrudRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => '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 will 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. 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 ```views/vendor/backpack/base/inc/sidebar_content.php``` file. You'll see this file is pure HTML. This will allow you to customize the menu as much as you want. 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. + + +## 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. diff --git a/4.0/demo.md b/4.0/demo.md new file mode 100644 index 00000000..5aca183c --- /dev/null +++ b/4.0/demo.md @@ -0,0 +1,66 @@ +# Demo + +--- + +We've put together a working Laravel app (backend-only), that you can install on your machine. 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 6; +- installed Backpack\CRUD on top; +- created a few demo models (Monsters, Icons, Products) and admin panels for them, using dozens of field types, column types, filters, etc - to show off most of Backpack's default 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 + +If you just want to take a look at the Backpack interface and click around, you don't need to install the Demo. **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 + +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) Install all the requirements: +``` zsh +cd backpack-demo +composer install +``` + +4) Populate the database and stuff: +```zsh +php artisan key:generate +php artisan migrate +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +php artisan db:seed +``` + + +## Demo Usage + +Once everything's installed, and your database has been set up: + +- Your admin panel is available at http://localhost/backpack-demo/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 40 field types. +- The magic of Backpack is not in its standard functionality, but in 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/4.0/getting-started-advanced-features.md b/4.0/getting-started-advanced-features.md new file mode 100644 index 00000000..cd1fedbd --- /dev/null +++ b/4.0/getting-started-advanced-features.md @@ -0,0 +1,43 @@ +# 3. Advanced Features + +--- + +**Duration:** 5 min + +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 how to find it. + +--- + + +## Other Operations +- [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) +- [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes +- [Clone](/docs/{{version}}/crud-operation-clone) Operation - you can make a copy of an entry; +- [BulkDelete](/docs/{{version}}/crud-operation-delete) Operation - you can delete multiple items in one go; +- [BulkClone](/docs/{{version}}/crud-operation-clone) Operation - you can clone multiple items in one go; + +--- + + +## Other Features +- **Create & Update** + - [Manage files on disk](/docs/{{version}}/crud-how-to#use-the-media-library-file-manager) (media library) + - [Fake fields](/docs/{{version}}/crud-fields#fake-fields-all-stored-as-json-in-the-database) + - Translatable models and [multi-language CRUDs](/docs/{{version}}/crud-operation-update#translatable-models) + - [Tabs in create/update forms](/docs/{{version}}/crud-fields#split-fields-into-tabs) + +-- + +- **ListEntries** + - you can add a "+" button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) + - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) + - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in the list view + + +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 for today!** Told you we're done with long lessons :-) Hopefully some of the above have peaked your interest and you've clicked 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. diff --git a/4.0/getting-started-basics.md b/4.0/getting-started-basics.md new file mode 100644 index 00000000..9afabc44 --- /dev/null +++ b/4.0/getting-started-basics.md @@ -0,0 +1,140 @@ +# 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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2018) and accommodate yourself with Laravel first. + + + +## What is Backpack? +A software 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 what a Tag CRUD could 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. Here's what a CRUD that uses all of Backpack's features could look like: + +![Monsters CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/monster_crud_list_entries.png) + +Mind that you will _almost never_ use all of Backpack's features in one CRUD. But if you do... it still looks good, and it'll be intuitive to use. + + +## Main Features + + +### Front-End Design + +Backpack installs the [CoreUI](https://coreui.io) HTML theme, and our own design on top - [Backstrap](https://backstrap.net). It uses Bootstrap 4, and has 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 our [Backstrap demo](https://backstrap.net), or from the [CoreUI documentation](https://coreui.io/docs/getting-started/introduction/), and it look good, without you having to design anything. + +It also installs 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 a basic authentication system that's separate from Laravel's. This way, you can have different login screens for users & admins, if you need. If not, you can choose to use only one authentication - either Laravel's, or Backpack's. + +![Backpack 3.5 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 DB information. Let's browse through a simple example, of creating a CRUD administration panel for a Tag entity: + +```zsh +# STEP 1. create migration +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique,slug:string:unique" +php artisan migrate + +# STEP 2. create crud +php artisan backpack:crud tag #use singular, not plural +``` + +This will create 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", no need for customizations. But don't expect this for more complex entities. They will usually have particularities 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 code above would 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 CrudPanel 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 sidebar item inside ```resources/views/vendor/backpack/base/inc/sidebar_content.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 [ridiculously 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 (db 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 you might use to achieve the above: + +```php +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => '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 all for today! **If you want to learn more, go ahead and [read the next lesson](/docs/{{version}}/getting-started-crud-operations) of this series. diff --git a/4.0/getting-started-crud-operations.md b/4.0/getting-started-crud-operations.md new file mode 100644 index 00000000..fe8f5c05 --- /dev/null +++ b/4.0/getting-started-crud-operations.md @@ -0,0 +1,248 @@ +# 2. CRUD Operations + +--- + +**Duration:** 10 minutes + +Let's bring back the example in 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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => 'slug', + '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, BulkClone), and you can easily _create a custom operation_, but let's not get ahead of ourselves. Baby steps. **Let's go through the most important features of the operations you'll be using _all the time_: ListEntries, Create and Update**. + + +## Create & 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 what 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 ListEntries 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 a lot of times, you won't just need text inputs. You'll need datepickers, upload buttons, 1-n relationship, n-n relationships, textareas, etc. For each field, you only need to define it properly in the Controller. Here are the most used methods to manipulate fields: + +```php +$this->crud->addField($field_definition_array); +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); +$this->crud->removeField('name'); +$this->crud->removeFields(['name_1', 'name_2']); + +// pro tip: +// a quick way to add simple fields: let the CRUD decide what field type it is +$this->crud->addField('db_column_name'); +``` + +A typical *field definition array* 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'd like 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 some super-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 is 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 +$this->crud->addField([ + '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()``` 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, 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 +$this->crud->addField([ + '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? +]); +``` + +> When generating a CrudController, you might be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and what columns in your list view, but - as you'd expect - it only works for the simple field types. You can: +> +> (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields +> +> or +> +> (2) delete ```setFromDb()``` and manually define each field and column; +> +> **Our recommendation**, for anything but the simplest CRUDs, **is to manually define each field** - much easier to understand and customize, for your future self and any other developer that comes after you. + + +### Callbacks + +Developers coming from GroceryCRUD on CodeIgniter or other CRUD systems will be looking for callbacks to run ```before_insert```, ```before_update```, ```after_insert```, ```after_update```. **There are no callbacks in Backpack**. The ```store()``` and ```update()``` code is inside a trait, so you can easily overwrite 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 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 super-similar to fields**: + +```php +$this->crud->addColumn($column_definition_array); // add a single column, at the end of the table +$this->crud->addColumns([$column_definition_array_1, $column_definition_array_2]); // add multiple columns, at the end of the table +$this->crud->removeColumn('column_name'); // remove a column from the table +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the table +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns([$column_definition_array_1, $column_definition_array_2]); // make these the only columns in the table +``` + +You can use one of the [14+ 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 super-specific need. Here's an example of using the methods above: + +```php +$this->crud->addColumn([ + '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 +$this->crud->addColumn('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 well… _filter_ the ListEntries 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). + +```php +$this->crud->addFilter($options, $values, $filter_logic); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +``` + +For more on this, check out the [filters documentation page](/docs/{{version}}/crud-filters), when you need them. + + +### 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. Look for the [buttons documentation](/docs/{{version}}/crud-buttons) when you need it. + +```php +// positions: 'beginning' and 'end' +// stacks: 'line', 'top', 'bottom' +// types: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + +**That's it for today!** 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. \ No newline at end of file diff --git a/4.0/getting-started-license-and-support.md b/4.0/getting-started-license-and-support.md new file mode 100644 index 00000000..ba628e86 --- /dev/null +++ b/4.0/getting-started-license-and-support.md @@ -0,0 +1,43 @@ +# 4. Add-ons, License & Support + +--- + +**Duration:** 3 minutes + + +## Add-ons + +In addition to our core packages (Base and CRUD), we have quite a few packages you can install or download, that treat 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 **free for non-commercial use**, but needs a license code in order to prevent "_unlicensed use_" notification bubbles and interruption of service. You can get a license code for your project: +- ```free```, if you're using it for non-commercial purposes; [apply here](https://backpackforlaravel.com/pricing); +- ```free```, if you've contributed to Backpack on Github; [apply here](https://backpackforlaravel.com/pricing); +- ```€69 EUR/project```, if you're making money using it for a project; [buy here](https://backpackforlaravel.com/pricing); +- ```€399 EUR for unlimited projects```, if you use Backpack a lot; [buy here](https://backpackforlaravel.com/pricing); + +**Freelancers** or companies **who make money using Backpack** - for themselves, their employers or their clients, **should [purchase a commercial license here](https://backpackforlaravel.com/pricing)**. + + +>**You don't need a license code on LOCALHOST.** If you're just trying Backpack on your own machine, you don't need a license code. You only need a license code when you take your application to production. + + +## Support + +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: + +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) +- **[Github Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) + +Thank you for sticking up with us for so long. This is the last Backpack lesson we can give you. **Now you have absolutely no excuse not to start your first Backpack project :-)** Here are a few links for if you still don't think you're ready: + +- [Go through the demo](/docs/{{version}}/demo) and play around +- Read this [CRUD Tutorial](/docs/{{version}}/crud-tutorial) +- Take a look at the [CrudController API Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) diff --git a/4.0/index.md b/4.0/index.md new file mode 100644 index 00000000..e692fe22 --- /dev/null +++ b/4.0/index.md @@ -0,0 +1,46 @@ +#### About + +- [Getting Started](/docs/{{version}}/introduction) + - [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) +- [Demo](/docs/{{version}}/demo) +- [Installation](/docs/{{version}}/installation) +- [Release Notes](/docs/{{version}}/release-notes) +- [Upgrade Guide](/docs/{{version}}/upgrade-guide) + +#### Admin UI + +- [About](/docs/{{version}}/base-about) +- [Widgets](/docs/{{version}}/base-widgets) +- [Breadcrumbs](/docs/{{version}}/base-breadcrumbs) +- [How To](/docs/{{version}}/base-how-to) + +#### CRUD Pages + +- [Basics](/docs/{{version}}/crud-basics) +- [Tutorial](/docs/{{version}}/crud-tutorial) +- [API](/docs/{{version}}/crud-api) +- [Cheat Sheet](/docs/{{version}}/crud-cheat-sheet) +- [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) + + [Delete](/docs/{{version}}/crud-operation-delete) + + [Clone](/docs/{{version}}/crud-operation-clone) + + [Show](/docs/{{version}}/crud-operation-show) + + [Columns](/docs/{{version}}/crud-columns) + + [Reorder](/docs/{{version}}/crud-operation-reorder) + + [Revisions](/docs/{{version}}/crud-operation-revisions) +- [How To](/docs/{{version}}/crud-how-to) + + +#### Add-ons + +- [Official Add-ons](/docs/{{version}}/add-ons-official) +- [Community Add-ons](/docs/{{version}}/add-ons-community) +- [How to Create an Add-on](/docs/{{version}}/add-ons-tutorial) diff --git a/4.0/install-optionals.md b/4.0/install-optionals.md new file mode 100644 index 00000000..16d4c8c0 --- /dev/null +++ b/4.0/install-optionals.md @@ -0,0 +1,150 @@ +# 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 sidebar_content item for it +php artisan backpack:add-sidebar-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: +``` + '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: + +``` +// 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: +``` + 'log' => env('APP_LOG', 'daily'), +``` + +4) [optional] Add a menu item for it in resources/views/vendor/backpack/base/inc/sidebar_content.blade.php or menu.blade.php: + +```bash +php artisan backpack:add-sidebar-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 to the sidebar_content file +php artisan backpack:add-sidebar-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. + +[>> Installation](https://github.com/Laravel-Backpack/PermissionManager#install) +[>> Github](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) diff --git a/4.0/installation.md b/4.0/installation.md new file mode 100644 index 00000000..c67c93b4 --- /dev/null +++ b/4.0/installation.md @@ -0,0 +1,74 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 5.8, Laravel 6 or Laravel 7, 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/7.x#installation) (an existing project is fine, you don't need a *fresh* Laravel install); + +- you have put your database and email credentials in your .ENV file; + +- 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 Core Packages + +0) Open your project folder in your terminal: + +```bash +cd your-laravel-project-name +``` + +1) In your project's main directory, install CRUD using composer: + +``` bash +composer require backpack/crud:"4.0.*" + +# you might also want to install these tools that help during development +composer require backpack/generators --dev +composer require laracasts/generators --dev +``` + +2) Now run the installation commands for Backpack: + +``` bash +php artisan backpack:install +``` + +Note: If you'd also like to enable the [file manager functionality](https://backpackforlaravel.com/uploads/home_slider/4.png), reply "yes" when the installer asks you. By default it lets users manage the ```public/uploads``` directory, but you can change that in the ```elfinder.php``` config file. Most of the times it is _not_ recommended to give your admins power over file structure - not even their uploads alone. So ```elfinder``` does not come installed by default. + +3) [Optional] You should now: +- Change configuration values in ```config/backpack/base.php``` to make the admin panel your own. Backpack is white label, so you can change everything: menu color, project name, developer name etc. +- If your User model has been moved (it is not ```App\User.php```, please go change ```App\Models\BackpackUser.php``` and make sure it extends the correct user model; +- If you have separate admin panels for Users and Administrators, and already have a way to differentiate between the two, please change ```app/Http/Middleware/CheckIfAdmin.php```, particularly ```checkIfUserIsAdmin($user)```, to make sure all users who get log into the admin panel have a right to do that; +- If your application has only one login screen (for the admins), that means you're not going to use the auth controllers that Laravel provided by default. You're only going to use Backpack's auth controllers. You can keep the Laravel ones in your project, of course. But some people like to delete them to not get confused later on: + +``` bash +# OPTIONAL! Please read the notice above. +rm -rf app/Http/Controllers/Auth #deletes laravel's demo auth controllers +``` + +That's it. If you already know how to use Backpack, next up you'll probably want to [create CRUD Panels](/docs/{{version}}/crud-tutorial#generate-files). + +> If it's your first time installing Backpack, it is **highly recommended** that you go through our [Getting Started series](/docs/{{version}}/getting-started-basics), to understand how Backpack works. That's why we created it - to help you learn how to use this admin panel framework. In ~23 minutes we'll teach you 80% of what you can do, and how. + + + +### 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```, and refer to [this thread](https://github.com/Laravel-Backpack/Base/issues/217). + +- **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\\Base because of a different error, you can [try the manual installation process](/docs/{{version}}/base-how-to#manually-install-base), which you can tweak to your needs. diff --git a/4.0/introduction.md b/4.0/introduction.md new file mode 100644 index 00000000..b55de1a5 --- /dev/null +++ b/4.0/introduction.md @@ -0,0 +1,76 @@ +# Getting Started + +--- + +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: + +- Backpack will provide you with a _visual interface_ for the admin panel (the HTML, the CSS, the JS); it pulls in the excellent [CoreUI](https://coreui.io/) theme, with our own design called [Backstrap](https://backstrap.net), adds authentication functionality & bubble notifications; when you decide to build a custom feature for your admin panel, you already have the HTML blocks for the UI, and it will look good; +- Backpack will 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 for each entity in about 10 minutes / model: + +```bash +# STEP 0. create migration (in case you're starting from scratch) +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate + +# STEP 1. create a model, a request and a controller for the admin panel +php artisan backpack:crud tag #use singular, not plural + +# STEP 2. add a route for this admin panel to routes/backpack/custom.php +php artisan backpack:add-custom-route "Route::crud('tag', 'TagCrudController');" + +# STEP 3. go through the generated files, customize according to your needs +``` + + +## Need to Know + + +### Requirements + + - Laravel 7, Laravel 6 or Laravel 5.8 + - PHP 7.2.5+ + - MySQL (recommended) / PostgreSQL / SQLite / SQL Server + + +### Screenshots + +Take a look at [our homepage](https://backpackforlaravel.com/). + + +### Demo + +You can easily [install our demo Laravel project with Backpack installed](/docs/{{version}}/demo) and play around. + + +### Security + +Backpack has never had a critical vulnerability/hack. But there _have_ been important security updates for dependencies (including Laravel). Please [login with Github](/auth/github) or [subscribe to our monthly newsletter](https://backpackforlaravel.com/newsletter), so we can reach you in case anything bad happens. No spam, no marketing emails, we promise. We only send one email per month max, when we introduce major Backpack updates. + + +### Maintenance + +Backpack 4.0 is the current version, and is being actively maintained by Backpack's creator, [Cristian Tabacitu](https://tabacitu.ro), with the help of a wonderful community of Backpack veterans. [See all contributors](https://github.com/Laravel-Backpack/CRUD/graphs/contributors). + + +### License + +Backpack is under a license we call "_You make money, I make money_" (YummY). Backpack's source is public, and you can use it for free for non-commercial purposes (testing, non-profits, personal use, etc), but if you make money using it, you need to purchase a commercial license. Please see [the pricing section](https://backpackforlaravel.com/pricing) for more details. In production, you need a license code for both commercial and non-commercial use, to prevent nagging notification bubbles. **On localhost, you don't need a license code.** + + +### Add-ons + +In addition to our core CRUD package, there are a few extra 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. + +For more, please see: +- [all official add-ons](/docs/{{version}}/add-ons-official) +- [all community add-ons](/docs/{{version}}/add-ons-community) + + +## How to Start + +We heavily recommend you spend a little time to understand Backpack, and only afterwards install and use it. Currently your options are: +- **[Text Tutorial](/docs/{{version}}/getting-started-basics)** - 23 minutes +- **[Email Tutorial](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 5 days, 5 minutes each +- **Video Tutorial** - working on it diff --git a/4.0/release-notes.md b/4.0/release-notes.md new file mode 100644 index 00000000..aa590b60 --- /dev/null +++ b/4.0/release-notes.md @@ -0,0 +1,89 @@ +# Release Notes + +--- + +**Launch date:** September 24th, 2019 + +Here are the main differences between [Backpack 3.6](https://backpackforlaravel.com/docs/3.6) and Backpack 4.0. + + +# Added +- using Bootstrap 4 instead of Bootstrap 3; +- new design; we're now using a custom design we made, called [Backstrap](https://backstrap.net), based on [CoreUI](http://coreui.io); (_get it? Backpack... Bootstrap... Backstrap? Pff..._) +- no CSS and JS assets are loaded from CDNs; everything's inside the Backpack/CRUD package, installed through NPM, and gets published into your ```public``` folder upon installation; you don't need to use NPM yourself to install or update assets - the Backpack maintainers do that for you; +- offline & intranet support - thanks to our move away from CDNs, Backpack now works without an internet connection; +- ability to easily add Vue.JS, React, or any other JS file inside all your admin panel pages, by modifying an array in ```config/backpack/base.php```; +- ability to easily remove the bundled JS and CSS and use CDNs if you want to; +- ability to toggle breadcrumbs on/off, in the ```config/backpack/base.php``` config file; +- ability to choose a different theme for Backpack views, in ```config/backpack/base.php```; +- SweetAlerts - instead of showing the default browser alert(), Backpack now uses much prettier SweetAlert pop-ups; +- API to [change breadcrumb links from controllers or from views](/docs/{{version}}/base-how-to#use-breadcrumbs); +- [Widgets](/docs/{{version}}/base-widgets) - easily add stats, pie charts, line charts and other quick stats to a dashboard or CRUD (or any part of your admin panel); +- support for Right-to-Left (RTL) languages; just change ```html_direction``` to ```rtl``` inside ```config/backpack/base.php```; + + +# Removed +- AdminLTE design; we're now using Bootstrap 4 + [CoreUI](http://coreui.io), with a custom design we made, called [Backstrap](https://backstrap.net); (_get it? Backpack... Bootstrap... Backstrap?_) +- Pnotify was removed in favour of Noty to show notificatio bubbles; +- loading multiple files for CSS and JS, on every page; now Backpack's default is to load one CSS bundle file, and one JS bundle file; CSS and JS for field types is still loaded separately, when needed, of course - you wouldn't want all CSS & JS loaded for all 40+ field types, on every page load; +- ```backpack/generators``` and ```laracasts/generators``` are no longer installed automatically; however, the official installation process does instruct you to install them; +- Backpack/Base; All the functionality in Backpack\Base has now been included in the Backpack\CRUD repository; However, we've kept the separate config files (```config/backpack/base.php``` and ```config/backpack/crud.php```) and views are still in the two folders you're used to, ```resources/views/vendor/backpack/base``` and ```resources/views/vendor/backpack/crud```; We've done this so it's easier for you to upgrade, but we _will_ merge the config files and views in the next Backpack 5.0 or 6.0; + + +# Licensing + + +## New Buyers + +All licenses you can purchase now on BackpackForLaravel.com are valid for _both_ v3 and v4. We've bumped the prices a bit, they're now: +- 0 EUR for non-commercial projects (unlimited developers, one project); +- 69 EUR for a single commercial project (unlimited developers, one project); +- 399 EUR for unlimited commercial projects (unlimited developers, unlimited projects); + +Check out our [pricing page](https://backpackforlaravel.com/pricing) for more details, + + +## Backpack v3 Buyers + +You can still use Backpack v3.6 for your projects. There's nothing wrong with it, if you don't want the new features in v4. But please know - v3 will only receive _security_ updates in the future: no bug fixes, no new features. We've provided upgrades and new features for Backpack v3 for _more than 3 years_. We hope this means you've already gotten a great bang for your buck out of your Backpack v3 license. + +### Single License + +Depending on _when_ you've purchased your Backpack v3 Project license, you'll qualify for a discount for upgrading your project to v4: +- bought in 2016 - 10 EUR discount (aprox 14%); +- bought in 2017 - 15 EUR discount (aprox 21%); +- bought in 2018 - 20 EUR discount (aprox 28%); +- bought in January-June 2019 - 25 EUR discount (aprox 36%); +- bought in July-August 2019 - 39 EUR discount (aprox 56%); +- bought in September 2019 - 49 EUR discount (aprox 71%); + +If you're an EU resident/company, VAT may be added to your invoice. + +There's only one catch: **To receive the discount, you have to purchase the upgrade before October 27th 2019**. You can purchase the upgrade in your Backpack account - you'll notice a new button has shown up next to your v3 license "_Upgrade for xxx EUR_". That button will disappear on October 28th 2019, 00:01 GMT. You will not be able to purchase with a discount after that. + + +### Unlimited License + +Depending on _when_ you've purchased your Backpack v3 Unlimited license, you'll qualify for a discount for upgrading all your projects to v4 _and_ creating new projects with Backpack v4: +- bought in 2016 - 20 EUR discount (aprox 5%); +- bought in 2017 - 40 EUR discount (aprox 10%); +- bought in 2018 - 80 EUR discount (aprox 20%); +- bought in January-June 2019 - 120 EUR discount (aprox 30%); +- bought in July-August 2019 - 200 EUR discount (aprox 51%); +- bought in September 2019 - 299 EUR discount (aprox 75%); + +If you're an EU resident/company, VAT may be added to your invoice. + +There's only one catch: **To receive the discount, you have to purchase the upgrade before October 27th 2019**. You can purchase the upgrade in your Backpack account - you'll notice a new button has shown up next to your v3 license "_Upgrade for xxx EUR_". That button will disappear on October 28th 2019, 00:01 GMT. You will not be able to purchase with a discount after that. + +If you've bought the Unlimited License very recently, and haven't launched any projects using Backpack v3, we don't want you to feel cheated. We can make a special discount for you - we'll discount the entire v3 Unlimited cost from your v4 Unlimited License. You'd only pay the price difference from v3 to v4 (100 EUR), and you'd be licensed to use both v3 and v4 in production. [Contact us](https://backpackforlaravel.com/contact). + + + +# Versioning + +When installing Backpack, require its minor version (currently ```4.0.*```). Backpack follows the same versioning system as Laravel 5.x - minor versions include minor breaking changes. This allows us to push new features without charging our users again. For us, this is what ```major.minor.patch``` means: + +- ```major``` - **PAID upgrade; MAJOR breaking changes;** historically every 2-3 years; upgrading may take even 2-3 hours; includes major new features, major changes in how the whole system works, and complete rewrites; it allows us to _considerably_ improve the product, and add features that were previously impossible; +- ```minor``` - **FREE upgrade; MINOR breaking changes**; historically every 6-12 months; upgrading takes less than 30 minutes; it allows us to add big new features, for free; +- ```patch``` - **FREE upgrade; NO breaking changes**; historically every week; upgrading can be done automatically with composer; includes bug fixes and non-breaking new features; diff --git a/4.0/upgrade-guide.md b/4.0/upgrade-guide.md new file mode 100644 index 00000000..4dc5cd01 --- /dev/null +++ b/4.0/upgrade-guide.md @@ -0,0 +1,517 @@ +# Upgrade Guide + +--- + +This will guide you through upgrading from Backpack 3.6 to 4.0. + + +> **Backpack v4 is a paid upgrade.** You can upgrade your project and run it just fine on localhost - no license key needed for that. But in production you'll need a different license code for v4. For details please see this version's [release notes](/docs/{{version}}/release-notes#licensing). + + + +## Requirements + +Please make sure your project respects the requirements below, before you start the upgrade process. You can check with ```php artisan backpack:base:version```: + +- Backpack\CRUD 3.6.x installed; if not, please follow the minor upgrade guides below to get to 3.6; +- Laravel 5.8 / Laravel 6.0 installed +- PHP 7.2+ +- 30-60 minutes for most projects + +**If you're running Backpack/CRUD version 3.3, 3.4 or 3.5, please follow the minor upgrade guide first, to get to 3.6**. Only _afterwards_ can you upgrade from 3.6 to 4.0. Guides: +- [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 + + +### Composer + +**Step 0.** + +Update your ```composer.json``` file to require: +- ```"backpack/crud": "^4.0.0"``` +- ```"laravel/framework": "5.8.*|^6.0"``` +- ```"backpack/generators": "^2.0"``` (in require-dev) + +Then run ```composer update```. + +[OPTIONAL] If you have a lot of Backpack add-ons installed (and their dependencies), here are their latest versions, you can copy-paste the versions of the packages you're using: +``` + "backpack/crud": "^4.0.0", + "backpack/logmanager": "^3.0.0", + "backpack/settings": "^3.0.0", + "backpack/pagemanager": "^2.0.0", + "backpack/menucrud": "^2.0.0", + "backpack/newscrud": "^3.0.0", + "backpack/permissionmanager": "^5.0", + "backpack/backupmanager": "^2.0.0", + "backpack/generators": "^2.0", + "spatie/laravel-translatable": "^4.0", + "barryvdh/laravel-elfinder": "^0.4.2", + "spatie/laravel-backup": "^6.1" +``` + +**Note:** If you're using ```webfactor/laravel-generators```, please remove it for now. It does not support Backpack v4 yet. + + +### Models + +**Step 1.** We've moved the model traits and notifications, so please do a search-and-replace in your ```app``` or ```Models``` folder: +- replace ```Backpack\CRUD\CrudTrait``` with ```Backpack\CRUD\app\Models\Traits\CrudTrait``` +- replace ```Backpack\Base\app\Models\Traits\InheritsRelationsFromParentModel``` with ```Backpack\CRUD\app\Models\Traits\InheritsRelationsFromParentModel``` +- replace ```Backpack\Base\app\Notifications\ResetPasswordNotification``` with ```Backpack\CRUD\app\Notifications\ResetPasswordNotification``` +- replace ```Backpack\CRUD\ModelTraits\SpatieTranslatable``` with ```Backpack\CRUD\app\Models\Traits\SpatieTranslatable``` + + +### Routes + +**Step 2.0** Change all you CRUD routes as shown below (most developers hold their CRUD routes inside ```routes/backpack/custom.php```): +```diff +Route::group([ + 'prefix' => config('backpack.base.route_prefix', 'admin'), + 'middleware' => ['web', config('backpack.base.middleware_key', 'admin')], + 'namespace' => 'App\Http\Controllers\Admin', +], function () { +- CRUD::resource('menu-item', 'MenuItemCrudController'); ++ Route::crud( 'menu-item', 'MenuItemCrudController'); +}); +``` + +Best to search-and-replace in your entire ```routes``` folder: +- replace ```CRUD::resource``` with ```Route::crud``` + +**Step 2.1** Backpack no longer provides a `->with()` function for adding extra routes on your controllers. If you've used ```->with()``` on your CRUD routes, please either: +- (A) move your routes outside the with() closure, as normal routes, in your ```custom.php```; +- (B) move your routes to the controller, inside a ```setupXxxRoutes()``` method; any method on the controller that looks like that will automatically be called when loading the routes; take a look at [any operation](https://github.com/Laravel-Backpack/CRUD/tree/master/src/app/Http/Controllers/Operations) to see an example; + +See the latest docs for more details [Add Extra CRUD Routes](https://backpackforlaravel.com/docs/4.0/crud-how-to#add-extra-crud-routes) + +**Step 2.2** +If you refer to CRUD routes by name - we've dropped the "crud" prefix. You'll need to update any references in your code. So `crud.model-name.index` is now `model-name.index`. This Regex search should help you find any calls to `route()` which use the `crud.` prefix: `/route\s*\(\s*['"]crud./` + + +### CrudControllers + +The steps below should apply for each of your CrudControllers. For each Step, go through every one of your CrudControllers (usually stored in ```app\Http\Controllers\Admin```: + +**Step 3.** Make sure the method where you set up your CrudPanel is called ```setup()```, not ```__construct()```. Especially if you've generated your CRUDs using versions of Backpack v3 from 2016-2017. In most cases you can just rename ```__construct()``` to ```setup()```, since ```setup()``` is called inside ```CrudController::__construct()``` anyway. + +**Step 4.** In v4, CrudController comes with ZERO operations loaded by default (instead of create, read, update, delete). You need to use a few operation traits in each of you EntityCrudControllers, to enable the previously-default operations: + +```php + + use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation; + +``` + +Make sure they're used INSIDE you EntityCrudController, not just OUTSIDE it: + +```diff + +class SettingCrudController extends CrudController +{ ++ use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation; ++ use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation; ++ use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation; ++ use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation; + + public function setup() + { + // ... +``` + +This is a great time to think about which default operations you're NOT using with each CrudController. If you're actually not using an operation, just commend or delete the ```use``` statement for that operation. This will also prevent the routes for that operation from being registered, so your ```php artisan route:list``` will be cleaner. + +**Step 5.** You might also be using other, non-default operations, in your CrudControllers. If so, you need to do the same for them (use the operation trait on the EntityCrudController where you'd like to use that operation). Here are all the operations Backpack v4 provides, use them on your EntityCrudControllers as you see fit: + +```php + // previously default operations (in Backpack v3) + use \Backpack\CRUD\app\Http\Controllers\Operations\ListOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation; + + // previously optional operations (in Backpack v3) + use \Backpack\CRUD\app\Http\Controllers\Operations\ShowOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\ReorderOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\RevisionsOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation; + use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation; + +``` + +**Step 6.** If in any of your EntityCrudControllers, you're using ```parent::``` to call a method from Backpack's CrudController, it will not work anymore. Since the methods are now applied using a trait, not by extending a CrudController. You will now have to rename the trait's method in the Use statement in order to call it from your custom method, for example: + +``` +class MyCrudController extends CrudController +{ + use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation { + edit as parentEdit; + } + + public function edit($id) + { + // customizations go here + // then we call the parent + return $this->parentEdit($id); + } + +``` + +**(6.1)** If your ```store()``` and ```update()``` methods don't have any custom logic apart than calling the parent method, you can delete them. We no longer need the Request type-hinted. So if they look like this, you can delete them: +```php + public function store(StoreRequest $request) + { + // your additional operations before save here + $redirect_location = parent::storeCrud($request); + // your additional operations after save here + // use $this->data['entry'] or $this->crud->entry + return $redirect_location; + } + + public function update(UpdateRequest $request) + { + // your additional operations before save here + $redirect_location = parent::updateCrud($request); + // your additional operations after save here + // use $this->data['entry'] or $this->crud->entry + return $redirect_location; + } +``` + +**IMPORTANT!!!** For the validation to still take place, using the same FormRequest, you need to instruct that operation to do so. In v4 you can setup an individual operation in methods that look like this: ```setupXxxOperation()```. So in this case, if you've deleted the previous ```store()``` and ```update()``` methods, it's now: + +```php + protected function setupCreateOperation() + { + $this->crud->setValidation(StoreRequest::class); + } + + protected function setupUpdateOperation() + { + $this->crud->setValidation(UpdateRequest::class); + } +``` + +**(6.2)** If you have custom logic inside your ```store()``` and ```update()``` methods, note that we've changed the parent method names: +- from ```updateCrud()``` to ```update()``` +- from ```storeCrud()``` to ```store()``` + +Follow step 6.1 with this in mind. The end result should be something like this: + +```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 store(StoreRequest $request) + { + // do something before validation, before save, before everything; for example: + + // $this->crud->request->request->add(['author_id'=> backpack_user()->id]); + // $this->crud->addField(['type' => 'hidden', 'name' => 'author_id']); + // $this->crud->request->request->remove('password_confirmation'); + // $this->crud->removeField('password_confirmation'); + + $redirect_location = $this->traitStore(); + + // do something after save + + return $redirect_location; + } + + public function update(UpdateRequest $request) + { + // .. + $redirect_location = $this->traitUpdate($request); + // .. + return $redirect_location; + } + +``` + + +**(6.3)** To be able to call the same method, but from the trait (not the parent), please rename the method from the trait, and call that name instead: + +```diff +use Backpack\CRUD\app\Http\Controllers\Operations\ShowOperation; + +class MonsterCrudController extends CrudController +{ +- use ShowOperation; ++ use ShowOperation { ++ show as traitShow; ++ } + + public function show() + { + // do sth custom here +- return parent::show(); ++ return $this->traitShow(); + } +``` + +Notice it's now ```$this->```, not ```parent::```. You do NOT need to do this for every method in your EntityCrudController that you've overwritten completely. Only for the methods that have a ```parent::smth()``` call inside them. + + +**Step 7.** The ```store()``` and ```update()``` methods previously stored all inputs the form, _except for_ special inputs (like ```_token```, ```_method```, ```current_tab``` etc.). This process has now been changed: they now store _only_ the inputs defined by the fields. + +**(7.1)** If you've used the ```checklist_dependency``` field type, its definition has changed - it should now have an array for name, instead of a string: + +```diff +$this->crud->addField([ + 'label' => 'Roles and Permissions', + 'field_unique_name' => 'user_role_permission', + 'type' => 'checklist_dependency', +- 'name' => 'roles_and_permissions', ++ 'name' => ['roles', 'permissions'], + 'subfields' => [...] +]); +``` + +**(7.2)** If you've used the ```date_range``` field type, it's definition has changed. Its ```start_name``` and ```end_name``` have been merged into ```name``` (an array), and its ```default_start``` and ```default_end``` have been merged into ```default``` (an array) too: + +```diff +$this->crud->addField([ +- 'name' => 'date_range', // a unique name for this field +- 'start_name' => 'start_date', // the db column that holds the start_date +- 'end_name' => 'end_date', // the db column that holds the end_date ++ 'name' => ['start_date', 'end_date'] // the db column that holds the start_date and end_date + 'label' => 'Event Date Range', + 'type' => 'date_range', + // OPTIONALS +- 'start_default' => '2019-10-05 09:00', // default value for start_date +- 'end_default' => '2019-10-10 10:00', // default value for end_date ++ 'default' => ['2019-10-05 09:00', '2019-10-10 10:00'], // default value for start_date and end_date + 'date_range_options' => [ // options sent to daterangepicker.js + 'timePicker' => true, + 'locale' => ['format' => 'DD/MM/YYYY HH:mm'] + ], +]); +``` + +**(7.3)** If you have custom field types, where the `````` differs from ```$field['name']```, you need to modify your field type so that it uses ```$field['name']``` as its name. + +**(7.4)** If you have custom field types, where there are other inputs that you want stored in the database in addition to ```$field['name']```, you can use ```$field['name']``` as an array. Make sure you pass an array when you call addField(), then use that array in the blade file. Take a look at how the ```date_range``` field does it, you can do the same. + +**Step 8.** In Backpack v4, you can no longer specify which form to add a field to, as a second parameter to ```addField()```. Doing ```$this->crud->addField('smth', 'create');``` inside ```setup()``` won't add it to Create, it will add it to whatever operation is currently being performed. Which sometimes is the Update operation. The way to define which fields show up in Create and which in Update has become the same as ANY feature, for ANY operation - you place the calls inside the closure/method for that operation. + +It is recommended you split all the feature calls (fields, columns, filters, buttons) in your ```setup()``` method by operation, to avoid conflicts between operations that use the same features. We've provided two methods. Take a look at both, and decide which one you like better: + +**Option 8.A. Operation Closures**. Move your calls to an operation closure, and that code will be run ONLY when that operation is being performed. + +```php +public function setup() +{ + //.. + + // run calls for this one operation + $this->crud->operation('list', function() { + // your addColumn, addFilter, addButton calls here, for the List operation + }); + + // run calls for one of these operations + $this->crud->operation(['create', 'update'], function() { + // your addField, etc for the Create and Update operations + }); + + // .. +} +``` + +**Option 8.B. setupXxxOperation methods**. Move your calls to a method that follows the ```setupXxxOperation()``` naming convention, where ```Xxx``` is the operation name, and that code will only be run when operation ```Xxx``` is being performed. + +```php +public function setupListOperation() +{ + // calls to addColumn, addFilter, addButton, etc +} + +public function setupCreateOperation() +{ + // calls to addField +} + +public function setupUpdateOperation() +{ + // calls to addField + // $this->setupCreateOperation(); // if it's the same as Create +} + +public function setupShowOperation() +{ + // calls to addColumn + // $this->setupListOperation(); // if you want it to have the same columns as List +} +``` + +_Note: You can keep your calls inside ```setup()``` too, but they will be applied for ALL operations, like in Backpack v3. In most cases, if you haven't had a conflict in v3, you won't have a conflict in v4. But we recommended you take the time now, and split up your ```setup()```. It will also make your admin panels faster._ + +**Step 9.** Optional. We've added a ```CRUD``` facade, so that you can do ```CRUD::smth() instead of ```$this->crud->smth()```. You can replace all your ```$this->crud->smth()``` calls to ```CRUD::smth()```, if you like it better that way, if you import the facade at the top of your EntityCrudController: +- (A) ```use CRUD;``` or +- (B) ```use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;``` + +**Step 10.** Some operations that were previously non-default (Revisions, Reorder, Clone, BulkClone, BulkDelete) can now set themselves up automatically, if you use the trait on your EntityCrudController. If you're using one of these operations on your EntityCrudController, consider taking a look at the changes, and removing the useless code from your controllers. + +**(10.1) RevisionsOperation**. To enable the operation, you have to ```use use \Backpack\CRUD\app\Http\Controllers\Operations\RevisionsOperation;``` on your EntityCrudController. If you've done so, you can delete calls to ```$this->crud->allowAccess('revisions')``` from your ```setup()```. This is performed automatically by the operation now. + +**(10.2) ReorderOperation**. To enable the operation, you have to ```use use \Backpack\CRUD\app\Http\Controllers\Operations\ReorderOperation;``` on your EntityCrudController. If you've done so, you can delete calls to ```$this->crud->allowAccess('reorder')``` from your ```setup()```. This is performed automatically by the operation now. The same goes with ```$this->crud->enableReorder()``` - you can delete that, it's performed by the operation. In fact, all you need to configure the operation now is: +```php + protected function setupReorderOperation() + { + $this->crud->set('reorder.label', 'name'); // the attribute on the Model which will be shown on draggable elements + $this->crud->set('reorder.max_level', 2); // how deep do you want to allow the nesting + } +``` + +**(10.3) CloneOperation**. To enable the operation, you have to ```use use \Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;``` on your EntityCrudController. If you've done so, you can delete the calls to ```$this->crud->allowAccess('clone')``` from your ```setup()```. It's performed by default by the operation. + +**(10.4) BulkCloneOperation**. Previously to enable the BulkClone operation you needed to do this in your ```setup()```: +```php +$this->crud->enableBulkActions(); +$this->crud->allowAccess('clone'); +$this->crud->addButton('bottom', 'bulk_clone', 'view', 'crud::buttons.bulk_clone', 'beginning'); +``` +You can delete that now, if you've added ```use use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation;``` on your EntityCrudController - it's being performed by default by the operation. + +**(10.5) BulkDeleteOperation**. Previously to enable the BulkDelete operation you needed to do this in your ```setup()```: +```php +$this->crud->enableBulkActions(); +$this->crud->addBulkDeleteButton(); +``` +You can delete that now, if you've added ```use use \Backpack\CRUD\app\Http\Controllers\Operations\BulkDeleteOperation;``` on your EntityCrudController - it's being performed by default by the operation. + + +**Step 11.** Only affects advanced usage. If you've used _properties_ to interact with operation features on ```$this->crud```, instead of getters & setters, most likely that property no longer exists. So if you've used the undocumented ```$this->crud->columns```, ```$this->crud->create_fields```, ```$this->crud->update_fields```, ```$this->crud->buttons```, ```$this->crud->access``` properties or anything like that, instead of using the methods that were documented, this step is for you. + +All CrudPanel properties that were there only to support a feature for a CRUD Operation have been removed. Instead, all those things are stored in one array, ```$this->crud->settings```, and we've provided an API to easily work with that array. [Read this detailed explanation](https://github.com/Laravel-Backpack/CRUD/pull/1997#issuecomment-536255543) if you're interested in this huge change. An easier way to migrate your usage of properties would be to see how it now works inside that operation trait, and use the new Settings API to interact with operation features, just like the operation does. + +Using CrudPanel properties instead of getters & setters was undocumented, but possible. So most people will not be affected by this. But if you have used CrudPanel properties directly, use the new Settings API instead of direct properties. Your old code will not work under v4. + +**Step 12.** [OPTIONAL] If you want your calls to be shorter, you can now do ```CRUD::smth()``` instead of ```$this->crud->smth()```. For this to work, make sure your CrudController uses our new Facade. So it would be: + +```php + +// .. +use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD; +// .. + +public function CategoryCrudController { + // .. + public function setup() + { + CRUD::setModel("App\Models\Category"); + CRUD::setRoute(config('backpack.base.route_prefix', 'admin').'/category'); + CRUD::setEntityNameStrings('category', 'categories'); + } +} +``` + +instead of + +```php + public function setup() + { + $this->crud->setModel("App\Models\Category"); + $this->crud->setRoute(config('backpack.base.route_prefix', 'admin').'/category'); + $this->crud->setEntityNameStrings('category', 'categories'); + } +``` + +This is completely optional - whatever you prefer. + + + +### Views + +Most views have suffered big changes, since we've moved from Bootstrap 3 to Bootstrap 4, and from AdminLTE to CoreUI. If you've overwritten many Backpack views, the upgrade process will be more difficult for you: you have to start from our new views and make the changes again. + +**Step 13.** Check your ```resources/views/vendor/backpack``` folder for any views. If you find anything there beside ```base/inc/sidebar_content.blade.php```, you'll need to take a look at that file in our package - it most likely has changed, and you may need to make changes to your file too. We recommend you use a diff tool - should save some time. [Kaleidoscope](https://www.kaleidoscopeapp.com) is our preferred diff tool, on Mac OS. [WinMerge](https://winmerge.org) is a good option for Windows. + +If you've overwritten a lot of blade files, you may hate us for this, we know :-) But keep in mind that we've moved from Bootstrap 3 to Bootstrap 4. And from AdminLTE to CoreUI. We tried to keep the changes to a minimum. But this major change was _impossible_ to do without changing a lot of blade files. + +**Step 14.** Fix you sidebar menu. In your ```resources/views/vendor/backpack/base/inc/sidebar_content.blade.php```, apply the new classes to your sidebar elements (notice ```nav-item```, ```nav-link``` and ```nav-icon```): + +```html + + + + + + +``` + +**Step 15.** Publish the new CSS&JS, for Backpack v4: ```php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=minimum``` + +**Step 16.** Delete the old CSS & JS, for Backpack v3: +```bash +# delete the AdminLTE css and js +rm -rf public/vendor/adminlte + +# MANUAL: if you've added any custom css & js for Backpack/CRUD or Backpack/Base, move them: +# - from vendor/backpack/base to packages/backpack/base +# - from vendor/backpack/crud to packages/backpack/crud + +# delete the CSS and JS for Backpack v3 +rm -rf public/vendor/backpack + +# if the public/vendor directory is empty now, delete it +rmdir public/vendor +``` + +**Step 17.** If you have custom buttons in your ```resources/views/vendor/backpack/crud/buttons```, or added through a model function, consider using the ```btn btn-sm btn-link``` classes on the anchor, so that they match the rest of the buttons. + + +**Step 18.** If you've installed and used the File Manager (elFinder), please: +```php +# delete its published views +rm -rf resources/views/vendor/elfinder + +# re-publish them +php artisan backpack:install +``` + + +### Config + +** Step 19. ```config/backpack/crud.php```** + +Most variables have been renamed and reordered - they're now sorted by operation name. Please manually insert take [the contents of the new file](https://github.com/Laravel-Backpack/CRUD/blob/v4/src/config/backpack/crud.php). Change the values to match your old config file. [Diff here](https://github.com/Laravel-Backpack/CRUD/pull/2064/files#diff-d548ca942f541d9c99eaf64f83e92bf9). + + +** Step 20. ```config/backpack/base.php```** + +Follow the same process as with the config file above, making sure your file will have [the new content](https://github.com/Laravel-Backpack/CRUD/blob/v4/src/config/backpack/base.php). There have been NO changes in the following sections: +- Registration +- Routing +- Authentication +- File System +- License Code + + + +### Cache + +**Step 21.** Clear your app's cache: +```bash +php artisan config:clear +php artisan cache:clear +php artisan view:clear +``` + +--- + +**You're done! Good job.** Thank you for taking the time to upgrade. Keep in mind that Backpack v4 is not a free upgrade. A different license code is required. More details inside this version's [release notes](/docs/{{version}}/release-notes#licensing). diff --git a/4.1/add-ons-community.md b/4.1/add-ons-community.md new file mode 100644 index 00000000..c0cde2e2 --- /dev/null +++ b/4.1/add-ons-community.md @@ -0,0 +1,19 @@ +# 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 | +| ------------- |:-------------:| --------:| +| [BackpackBlog](https://github.com/AbbyJanke/BackpackBlog) | blog front-end and back-end | - | +| [BackpackMeta](https://github.com/AbbyJanke/BackpackMeta) | helps create meta options for extending core functions | - | +| [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/4.1/add-ons-custom-operation.md b/4.1/add-ons-custom-operation.md new file mode 100644 index 00000000..1edf1910 --- /dev/null +++ b/4.1/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 inteface, 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 subredddit](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/4.1/add-ons-how-to-create-a-backpack-addon.md b/4.1/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..194b6ed9 --- /dev/null +++ b/4.1/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/4.1/add-ons-official.md b/4.1/add-ons-official.md new file mode 100644 index 00000000..25a1f911 --- /dev/null +++ b/4.1/add-ons-official.md @@ -0,0 +1,20 @@ +# Official Add-ons + +In addition to our core packages (Base and CRUD), we've developed a few packages you can install or download, that treat common use cases. + +Premium add-ons (paid separately, on top of your [Backpack license](https://backpackforlaravel.com/pricing)): +- [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! +- [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; + + +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``` + - [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``` + + +>**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/4.1/add-ons-tutorial-using-the-addon-skeleton.md b/4.1/add-ons-tutorial-using-the-addon-skeleton.md new file mode 100644 index 00000000..1fa90683 --- /dev/null +++ b/4.1/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 wont'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 subredddit](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/4.1/add-ons-tutorial.md b/4.1/add-ons-tutorial.md new file mode 100644 index 00000000..527103e9 --- /dev/null +++ b/4.1/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 subredddit](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/4.1/base-about.md b/4.1/base-about.md new file mode 100644 index 00000000..f3a0b99c --- /dev/null +++ b/4.1/base-about.md @@ -0,0 +1,219 @@ +# About Backpack's User Interface + +--- + +Backpack helps you build admin panels faster by: +- installing our custom HTML theme, [Backstrap](https://backstrap.net), based on Bootstrap 4 and [CoreUI](https://coreui.io); +- installing [sweetalert](https://sweetalert.js.org/) for triggering pretty Confirm modals; +- installing [noty](https://ned.im/noty/#/) to show notification bubbles upon error/success/warning/info - triggered from Javascript; +- installing [prologue/alerts](https://github.com/prologuephp/alerts) for triggering notification bubbles from PHP (both on the same page and using flashdata); +- providing a separate authentication system for your admins; +- providing pretty error pages for most common errors; +- providing a horizontal menu and a side menu you can customize; +- providing a place for your admin to to change his email/name/password; +- providing a few helpers you can use throughout your admin panel; + +For the simplest projects, you will never need to know how it works, never need to customize anything but the ```config/backpack/base.php``` file. But here's how everything works, below. + + +## Layout & Design + + +### General + +Backpack pulls in our custom HTML template, [Backstrap](https://www.npmjs.com/package/@digitallyhappy/backstrap), and adds our own CSS file on top, for a few cosmetic improvements. We've chosen to base Backstrap on [CoreUI](https://coreui.io), because it provides design blocks for all common features of an administration panel. When you decide to build custom pages for your Admin Panel, you can just use Backstrap's HTML blocks - no designer needed. You can see all the HTML components Backstrap provides on [backstrap.net](https://backstrap.net), and copy-paste HTML from there, or use [CoreUI](https://coreui.io)'s documentation for details. + + +### New Files in Your App + +After installation, you'll notice Backpack has added a few files: + +**1) View to ```resources/views/vendor/backpack/base/inc/sidebar_content.blade.php```** + +This file is used to show the contents of the menu to the left (sidebar). 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/base/```: + - ```inc/sidebar_content.blade.php```; + +That file is used to show the contents of the menu to the left (sidebar). 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, then go to ```resources/views/vendor/backpack/base``` and create a file with the exact same name. Backpack\Base will use this new file, instead of the one in the package. + +For example: +- if you want to add an item to the top menu, you could just create a file called ```resources/views/vendor/backpack/base/inc/topbar_left_content.blade.php```; Backpack will now use this file's contents, instead of ```vendor/backpack/base/src/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/base/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 base/dashboard +``` + +Then inside the blade files, you can use either plain-old HTML or add dynamic content through [Backpack widgets](/docs/{{version}}/base-widgets). + + +### Folder Structure + +If you'll take a look inside any Backpack package, you'll notice the ```src``` directory is 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\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\User``` or you've changed its location, please, you can tell Backpack to use _a different_ model in ```config/backpack/base.php``` instead of the ```App\User``` model that Laravel apps usually have. 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 his/her name to go to his "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\Base\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\Base 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/base/my_account.blade.php``` that uses code from the same file in the Backpack package, 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\Base\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\Base 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(http://github.com/$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; + +### Add a custom page to your admin panel (dynamic page) + +```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', 'PageController@example'); + +# Step 2. 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/4.1/base-alerts.md b/4.1/base-alerts.md new file mode 100644 index 00000000..6bfe0be2 --- /dev/null +++ b/4.1/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/4.1/base-breadcrumbs.md b/4.1/base-breadcrumbs.md new file mode 100644 index 00000000..a3a7d5ad --- /dev/null +++ b/4.1/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/base.php```: + +```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('http://github.com/dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('http://github.com/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('http://github.com/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('http://github.com/dashboard'), + 'Dashboard' => false, + ]; +@endphp + +@section('content') + some other content here +@endsection +``` \ No newline at end of file diff --git a/4.1/base-how-to.md b/4.1/base-how-to.md new file mode 100644 index 00000000..fbaa0cb5 --- /dev/null +++ b/4.1/base-how-to.md @@ -0,0 +1,540 @@ +# FAQs for the admin UI + +--- + + + +## Look and feel + + +### Customize the menu or sidebar + +During installation, Backpack publishes a few files in you ```resources/views/vendor/backpack/base/inc``` folder. In there, you'll also find: +- ```sidebar_content.php``` +- ```topbar_left_content.php``` +- ```topbar_right_content.php``` + +Change those files as you please. + + +### 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/base/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('http://github.com/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 + +In ```config/backpack/base.php``` you'll notice there are variables where you can change exactly what CSS classes are placed on the HTML elements that represent the header, body, sidebar and footer: + +```php + // Horizontal navbar classes. Helps make the admin panel look similar to your project's design. + 'header_class' => 'app-header bg-light border-0 navbar', + // Try adding bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + // You might need to add "navbar-dark" too if the background color is a dark one. + // Add header-fixed if you want the header menu to be sticky + + // Body element classes. + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + // Try sidebar-hidden, sidebar-fixed, sidebar-compact, sidebar-lg-show + + + // Sidebar element classes. + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + // Remove "sidebar-transparent" for standard sidebar look + // Try "sidebar-light" or "sidebar-dark" for dark/light links + // You can also add a background class like bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + + // Footer element classes. + 'footer_class' => 'app-footer', +``` + +Our default design might not be pleasant for your, or you might need to make the UI integrate better into your project. We totally understand. You can use the classes above to make it look considerably different. + +You'll find a few examples below - but you should use which classes you want to get the result you need. + + +#### Backstrap + +Transparent top menu, transparent sidebar, transparent footer. This is the default. This is what _we_ think is best for most users, from our 8+ years of experience building admin panels. Prioritising _content_ over _menus_. + +![Backstrap design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/default.png) + +```php + 'header_class' => 'app-header bg-light border-0 navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer', +``` + + +#### Inspired by CoreUI + +White top menu, dark sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/coreui.png) + +```php + 'header_class' => 'app-header navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', + 'footer_class' => 'app-footer d-none', +``` + + +#### Inspired by Github + +Black top menu, white sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/github.png) + +```php + 'header_class' => 'app-header bg-dark navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar bg-white sidebar-pills', + 'footer_class' => 'app-footer d-none', +``` + + +#### Blue Top Menu + +Blue top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/blue.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-primary border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + + +#### Construction / Warning + +Yellow top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/construction.png) + +```php + 'header_class' => 'app-header navbar navbar-light bg-warning', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Red Top Menu + +Red top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/red.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Pink Top Menu + +Pink top menu, transparent sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/pink.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer d-none', +``` + + + +#### Green Top Menu + +Green top menu, white sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/green.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-green border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-white', + 'footer_class' => 'app-footer d-none', +``` + + +### 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/base.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) +- use a different CSS framework (ex: Tailwind, Bulma) + + + +### Add custom Javascript to all admin panel pages + +In ```config/backpack/base.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/base.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/base.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/base.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 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 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. + + + +### 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')->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() + { + + return backpack_view('auth.register'); + } +``` +This will make the registration process pick up a view you can create, in ```resources/views/vendor/backpack/base/auth/register.blade.php```. You can copy-paste the original view, and modify as you please. Including adding your own custom inputs. + diff --git a/4.1/base-widgets.md b/4.1/base-widgets.md new file mode 100644 index 00000000..eab8068a --- /dev/null +++ b/4.1/base-widgets.md @@ -0,0 +1,570 @@ +# 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 + +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-1/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('http://github.com/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 in the div attributes of your choice. For example, you can include multiple widgets in 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. + +
    + + +### Jumbotron + +Shows a Bootstrap jumbotron component, with the heading and body you specify. + +```php +[ + 'type' => 'jumbotron', + 'heading' => 'Welcome!', + 'content' => 'Use the sidebar to the left to create, edit or delete content.', + 'button_link' => backpack_url('http://github.com/logout'), + 'button_text' => 'Logout', +] +``` + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/jumbotron.png) + +
    + + +### 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) + +
    + + +### View + +Loads a blade view from a location you specify. Any attributes you give it will be available in the ```$widget``` variable inside that view. + +```php +[ + 'type' => 'view', + 'view' => 'path.to.custom.view', + 'someAttr' => 'some value', +] +``` + +It helps load blade files that are not specifically created to be widgets, that live in a different path than ```resources/views/vendor/backpack/base/widgets```, as if they were widgets. + +
    + + +## Overwriting Default Widget Types + +You can overwrite a widget type by placing a file with the same name in your ```resources\views\vendor\backpack\base\widgets``` 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:publish base/widgets/widget-name``` + +Examples: +- creating a ```resources\views\vendor\backpack\base\widgets\card.blade.php``` file would overwrite the ```card``` widget functionality; +- ```php artisan backpack:publish base/widgets/card``` 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 widget type, you're forfeiting any future updates for that widget. We can't push updates to a file that you're no longer using. + + +## Creating a Custom Widget Type + +Widgets consist of only one file - a blade file with the same name as the widget type (ex: ```card.blade.php```). You can create one by placing a new blade file inside ```resources\views\vendor\backpack\base\widgets```. Be careful to choose a distinctive name, otherwise you might be overwriting a default widget type (see above). + +For example, you can create a ```well.blade.php```: +```php +@includeWhen(!empty($widget['wrapper']), 'backpack::widgets.inc.wrapper_start') +
    + {!! $widget['content'] !!} +
    +@includeWhen(!empty($widget['wrapper']), 'backpack::widgets.inc.wrapper_end') +``` + +You can then use the ```well``` widget in a Controller or View: +```php +@extends(backpack_view('blank')) + +@php + Widget::add([ + 'type' => 'well', + 'wrapper' => ['class' => 'col-sm-12'], + 'content' => 'This text will be in a div with the class "well".', + ]); +@endphp + +@section('content') +@endsection +``` + +To use information from the database, you can: +- use the full namespace for your models, like ```\App\Models\Product::count()```; +- 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 view composers](https://laravel.com/docs/5.7/views#view-composers) to push variables inside this view when it's loaded, Like. ```View::composer('backpack::widgets.well, 'App\Http\View\Composers\WellComposer');``` + +Inside the widget blade files, you include custom CSS and JS, by pushing to the stacks in the layout: +```php +@includeWhen(!empty($widget['wrapper']), 'backpack::widgets.inc.wrapper_start') +
    + {!! $widget['content'] !!} +
    +@includeWhen(!empty($widget['wrapper']), 'backpack::widgets.inc.wrapper_end') + +@push('after_styles') + + +@endpush + + +@push('after_scripts') + + +@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/base/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/4.1/crud-api.md b/4.1/crud-api.md new file mode 100644 index 00000000..04857b66 --- /dev/null +++ b/4.1/crud-api.md @@ -0,0 +1,530 @@ +# 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(); +``` + + +### 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']); +``` + +- **denyAccess()** - prevent users from accessing one or multiple operations +```php +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); +``` + +- **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/4.1/crud-basics.md b/4.1/crud-basics.md new file mode 100644 index 00000000..cd734260 --- /dev/null +++ b/4.1/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 **controller** - holds the logic for the all operations an admin can perform on that Eloquent model; will be generated in ```app/Http/Controllers/Admin```; +- a **request** file - used to validate Create and Update forms; will be generated in ```app/Http/Requests```; +- a resource **route** - points to the controller above; will be generated in ```routes/backpack/custom.php```; + +**The only difference** between building it from scratch and using Backpack\CRUD** is that: +- your controller will be extending ```Backpack\CRUD\app\Http\Controllers\CrudController```**, which allow you to easily add traits that handle the most common operations: Create, Update, Delete, List, Show, Reorder, Revisions. +- 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_ overwrite what happens inside each operation**; +- **you can _easily_ add 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 CrudController; 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; + + +## Files + +For a ```Tag``` entity, your CRUD Panel would consist of: +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); +- a route inside ```routes/backpack/custom.php```; +- your existing model (```app/Models/Tag.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/4.1/crud-buttons.md b/4.1/crud-buttons.md new file mode 100644 index 00000000..08e7b037 --- /dev/null +++ b/4.1/crud-buttons.md @@ -0,0 +1,202 @@ +# Buttons + +--- + + +## About + +Buttons are used inside the ListEdit operation, to allow the admin to trigger other operations. Some point to entirely new routes (```create```, ```update```, ```show```), others perform the operation on the current page using AJAX (```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) + +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 + +Backpack adds a few buttons by default: +- ```create``` to the ```top``` stack; +- ```update``` and ```delete``` to the ```line``` stack; + +Default buttons are invisible if an operation has been disabled. For example, you can: +- hide the "delete" button using ```$this->crud->denyAccess('delete')```; +- show a "preview" button by using ```$this->crud->allowAccess('show')```; + + +### Buttons API + +Here are a few things you can call in your EntityCrudController's ```setupListOperation()``` method, to manipulate buttons: + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; + +// add a button; possible types are: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); + +// add a button whose HTML is returned by a method in the CRUD model +$this->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 +$this->crud->addButtonFromView($stack, $name, $view, $position); + +// remove a button +$this->crud->removeButton($name); + +// remove a button for a certain stack (top, line, bottom) +$this->crud->removeButtonFromStack($name, $stack); +``` + +### Overwriting a Default Button + +Before showing any buttons, Backpack will check your ```resources\views\vendor\backpack\crud\buttons``` directory, to see if you've overwritten any default buttons. If it finds a blade file with the same name there as the default buttons, it will use your blade file, instead of the default. + +That means **you can overwrite an existing button simply by creating a blade file with the same name inside this directory**. + + +### Creating a Custom Button + +To create a custom button: +- create a new blade file in ```resources\views\vendor\backpack\crud\buttons```; +- add that button using the ```addButton()``` syntax above, in the EntityCrudControllers you want, inside the ```setupListOperation()``` method; + +In this 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; + +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')) + 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 +$this->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 +$this->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 +$this->crud->addButtonFromView('top', 'import', 'import', 'end'); +``` diff --git a/4.1/crud-cheat-sheet.md b/4.1/crud-cheat-sheet.md new file mode 100644 index 00000000..7f507e6d --- /dev/null +++ b/4.1/crud-cheat-sheet.md @@ -0,0 +1,336 @@ +# 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 + + +### ListEntries + + +#### Columns + +Methods: addColumn(), addColumns(), modifyColumn(), removeColumn(), removeColumns(), setColumnDetails(), setColumnsDetails(), setColumns(), beforeColumn(), afterColumn(), makeFirstColumn() + +```php +// Manipulate what columns are shown in the table view. +$this->crud->addColumn($column_definition_array); // add a column, at the end of the stack +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); // add multiple columns, at the end of the stack +$this->crud->modifyColumn($name, $modifs_array); +$this->crud->removeColumn('column_name'); // remove a column from the stack +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns(); // set the columns you want in the table view, either as array of column names, or multidimensional array with all columns detailed with their types + +// ------ REORDER COLUMNS +$this->crud->addColumn()->beforeColumn('name'); // will show this before the given column +$this->crud->addColumn()->afterColumn('name'); // will show this after the given column + +$this->crud->addColumn()->makeFirstColumn(); + // will make this column the first one in the list + // you need to also specify priority 1 in your addColumn statement for details_row or responsive expand buttons to show +``` + + +#### Buttons + +Methods: addButton(), addButtonFromModelFunction(), addButtonFromView(), removeButton(), removeButtonFromStack() + +```php +// possible positions: 'beginning' and 'end'; defaults to 'beginning' for the 'line' stack, 'end' for the others; +$this->crud->addButton($stack, $name, $type, $content, $position); // add a button; possible types are: view, model_function +$this->crud->addButtonFromModelFunction($stack, $name, $model_function_name, $position); // add a button whose HTML is returned by a method in the CRUD model +$this->crud->addButtonFromView($stack, $name, $view, $position); // add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + + +#### Filters + +Methods: addFilter(), modifyFilter(), removeFilter(), removeAllFilters(), filters() + +```php +// Manipulate what filters are shown in the table view. +// +// Note: check out CRUD > Features > Filters in the docs to see examples of $filter_definition_array +$this->crud->addFilter($filter_definition_array, $values, $filter_logic); +$this->crud->modifyFilter($name, $modifs_array); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +$this->crud->filters(); // gets all the filters +``` + + +#### 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. +$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->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. +$this->crud->enableExportButtons(); +``` + + +#### Responsive Table + +Methods: enableResponsiveTable(), disableResponsiveTable() + +```php +$this->crud->disableResponsiveTable(); +$this->crud->enableResponsiveTable(); +``` + + +#### Persistent Table + +Methods: enablePersistenTable(), disablePersistenTable() + +```php +$this->crud->disablePersistentTable(); +$this->crud->enablePersistentTable(); +``` + + +#### Page Length + +Methods: setDetaultPageLength(), setPageLengthMenu() + +```php +// you can define the default page length. If it does not exist we will add it to the pagination array. +$this->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: +$this->crud->setPageLengthMenu([[100, 200, 300], ['one hundred', 'two hundred', 'three hundred']]); + +// values and labels in one array: +$this->crud->setPageLengthMenu([100 => 'one hundred', 200 => 'two hundred', 300 => 'three hundred']); + +// only values, we will use the values as labels: +$this->crud->setPageLengthMenu([100, 200, 300]); // OR +$this->crud->setPageLengthMenu([[100, 200, 300]]); + +// only one option available: +$this->crud->setPageLengthMenu(10); +``` + +NOTE: Do not use 0 as a key, if you want to represent "ALL" use -1 instead. + + +#### Actions Column + +Methods: setActionColumnPriority() + +```php +// make the actions column (in the table view) hide when not enough space is available, by giving it an unreasonable priority +$this->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. +$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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->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: addField(), addFields(), modifyField(), modifyFields(), removeField(), removeFields(), removeAllFields(), beforeField(), afterField() + +```php +// ------ +// FIELDS +// ------ +// Manipulate what fields are shown in the create / update forms. +// +// Note: check out CRUD > Features > Field Types in the docs to see examples of $field_definition_array + +$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 +$this->crud->addFields($array_of_fields_definition_arrays); +$this->crud->modifyField($name, $modifs_array); +$this->crud->removeField('name'); +$this->crud->removeFields($array_of_names); +$this->crud->removeAllFields(); + +// ------ REORDER FIELDS +$this->crud->addField()->beforeField('name'); // will show this before the given field +$this->crud->addField()->afterField('name'); // will show this after the given field +``` + + +### Reorder + +Methods: enableReorder(), disableReorder(), isReorderEnabled() + +```php + protected function setupReorderOperation() + { + // model attribute to be shown on draggable items + $this->crud->set('reorder.label', 'name'); + // maximum number of nesting allowed + $this->crud->set('reorder.max_level', 2); + + // extras: + // $this->crud->disableReorder(); + // $this->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 +$this->crud->allowAccess('revisions'); +``` + + +## All Operations + +Methods: allowAccess(), denyAccess(), hasAccess(), 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. + +$this->crud->allowAccess('list'); +$this->crud->allowAccess(['list', 'create', 'delete']); +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); + +$this->crud->hasAccess('add'); // returns true/false +$this->crud->hasAccessOrFail('add'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false + +// ------------- +// EAGER LOADING +// ------------- + +// eager load a relationship +$this->crud->with('relationship_name'); + +// ------------ +// CUSTOM VIEWS +// ------------ + +// use a custom view for a CRUD operation +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); + +// ------------- +// CONTENT CLASS +// ------------- + +// use a custom CSS class for the content of a CRUD operation +$this->crud->setShowContentClass('col-md-12'); +$this->crud->setEditContentClass('col-md-12'); +$this->crud->setCreateContentClass('col-md-12'); +$this->crud->setListContentClass('col-md-12'); +$this->crud->setReorderContentClass('col-md-12'); +$this->crud->setRevisionsContentClass('col-md-12'); +$this->crud->setRevisionsTimelineContentClass('col-md-12'); + +// ------- +// GETTERS +// ------- + +$this->crud->getEntry($entry_id); +$this->crud->getEntries(); + +$this->crud->getFields('create/update/both'); + +// in your update() method, after calling parent::updateCrud() +$this->crud->getCurrentEntry(); + +// ------- +// OPERATIONS +// ------- + +$this->crud->setOperation('list'); +$this->crud->getOperation(); + +// ------- +// ACTIONS +// ------- + +$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 + +$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 + +$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 + +// --------------------------- +// CrudPanel Basic Information +// --------------------------- +$this->crud->setModel("App\Models\Example"); +$this->crud->setRoute("admin/example"); +// OR $this->crud->setRouteName("admin.example"); +$this->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 +$this->crud->setRequiredFields(StoreRequest::class, 'create'); +$this->crud->setRequiredFields(UpdateRequest::class, 'edit'); +``` diff --git a/4.1/crud-columns.md b/4.1/crud-columns.md new file mode 100644 index 00000000..117a482f --- /dev/null +++ b/4.1/crud-columns.md @@ -0,0 +1,937 @@ +# 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) + + +### 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]); + +// 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(); +``` + + +## Default Column Types + + +### array + +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 + +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" +], +``` + +
    + + +### 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(); +``` + +
    + + +### closure + + +Show custom HTML based on a closure you specify in your EntityCrudController. Please note this column does not escape HTML before rendering. You need to do that yourself, if you consider it necessary. + +```php +[ + 'name' => 'created_at', + 'label' => 'Created At', + 'type' => 'closure', + 'function' => function($entry) { + return 'Created on '.$entry->created_at; + } +], +``` + +
    + + +### custom_html + + +Show the HTML that you provide in the page. You can optionaly escape the text when displaying it on page. + +```php +[ + 'name' => 'my_custom_html', + 'label' => 'Custom HTML', + 'type' => 'custom_html', + 'value' => 'Something', + 'escaped' => false //optional, if the "value" should be escaped when displayed in the page. +], +``` + +
    + + +### date + + +The date column will show a localized date in the default date format (as specified in the ```config/backpack/base.php``` file), whether the attribute is casted 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/base.php``` file), whether the attribute is casted 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 +], +``` + +
    + + +### 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 //optional, if the "value" should be escaped when displayed in the page. +], +``` + +
    + + +### markdown + + +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', +], +``` + +
    + + +### 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 +], +``` +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.''; +} +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + +
    + + +### 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 +], +``` + +**Note:** When displaying this column's value, the text is not escaped. That is intentional. This way, you can use it to show labels, color text, italic, bold, links, etc. If you might have malicious JS or CSS in your values, you can create a new escaped field yourself. But it's probably better to treat the problem at the source, and prevent JS and CSS from reaching your DB in the first place. + +
    + + +### 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 +], +``` + +
    + + +### 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; + +
    + + +### relationship + +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. + +
    + + +### 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" +], +``` + +**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 +], +``` + +
    + +### 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_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 +], +``` + +
    + + +### table + + +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' + ] +], +``` + +
    + + +### 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 //if the text should be escaped +], +``` + + +### upload_multiple + + +The ```table``` 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 +], +``` + +
    + + +### video + + +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', +], +``` + +
    + + +### 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 +], +``` + +
    + + +## 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:publish crud/columns/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:publish crud/columns/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 blad 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. + +For example, you can wrap the text in an anchor element, to point to that Article's Show operation: + +```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 + '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('http://github.com/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->addColumn([ + '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'; + }, + ], +]); +``` + + + +### 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 +]); +``` + + +### 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. diff --git a/4.1/crud-fields.md b/4.1/crud-fields.md new file mode 100644 index 00000000..3a5ff7dd --- /dev/null +++ b/4.1/crud-fields.md @@ -0,0 +1,2399 @@ +# 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: Starting with Backpack 4.1, 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 +// add a field to both Create and Update operation +$this->crud->addField($field_definition_array); + +// add a field only to the Update operation +$this->crud->addField($field_definition_array); + +// shorthand: add a text field to both Create and Update operations +$this->crud->addField('db_column_name'); + +// add multiple fields +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); + +// change the attributes of a field +$this->crud->modifyField($name, $modifs_array); + +// remove a field from both operations +$this->crud->removeField('name'); + +// remove multiple fields from both operations +$this->crud->removeFields($array_of_names); + +// remove all fields from all operations +$this->crud->removeAllFields(); + +// FIELD ORDER + +// add a field before a given field +$this->crud->addField($field_definition_array)->beforeField('name'); + +// add a field after a given field +$this->crud->addField($field_definition_array)->afterField('name'); + + +// ------------------- +// New in Backpack 4.1 +// ------------------- +// add a field with this name +$this->crud->field('price'); + +// change the type attribute on the 'price' field +$this->crud->field('price')->type('number'); +``` + + +### 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); + +Every other field attribute other than `name`, Backpack 4.1+ will try to guess. + + +#### Recommended Field Attributes + +Normally 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, your field definition array should look like: +```php +[ + 'name' => 'description', + 'type' => 'textarea', + 'label' => 'Article Description', +] +``` + +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 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 customizations: + +```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 - Fake Field Attributes (stores fake attributes as JSON in the database) + +In case you want to store insignificant information for an entry that doesn't need a database column, you can add any number of Fake Fields, and all their information will be stored inside one column in the db, as JSON. By default, an ```extras``` column is assumed on the database table, but you can change that. + +**Step 1.** Use the fake attribute on your field: +```php +[ + '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 casted 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 +[ + 'name' => 'meta_title', + 'label' => "Meta Title", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + 'name' => 'meta_description', + 'label' => "Meta Description", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + '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) + +In order to use this feature, you just need to specify the tab name for each of your fields. Example: + +```php +// select_from_array +$this->crud->addField([ + 'name' => 'select_from_array', + 'label' => "Select from array", + 'type' => 'select_from_array', + 'options' => ['one' => 'One', 'two' => 'Two', 'three' => 'Three'], + 'allows_null' => false, + 'allows_multiple' => true, + 'tab' => 'Tab name here', +]); +``` + +If you forget to specify a tab name for a field, Backpack will place it above all tabs. + + + +#### 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 4.1+ 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, or +- (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'; + } +} +``` + + + +## Default Field Types + + +### address_algolia + +Use [Algolia Places autocomplete](https://community.algolia.com/places/) 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 address string. For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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. + +```php +[ // Address algolia + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_algolia', + // optional + 'store_as_json' => true +], +``` + +> **Algolia is killing Places.** Please note that Algolia Places **will stop working in May 2022**, as reported in [this announcement](https://www.algolia.com/blog/product/sunseting-our-places-feature/). For that reason, it's probably a good idea to use the `address_google` field instead (it's right after this one). + + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-4-1/fields/address.png) + +
    + + +### address_google + +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 +[ // 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' +], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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-1/fields/address_google.png) + +
    + + +### browse + +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 +[ // Browse + 'name' => 'image', + 'label' => 'Image', + 'type' => 'browse' +], +``` + + +Input preview: + +![CRUD Field - browse](https://backpackforlaravel.com/uploads/docs-4-1/fields/browse.png) + +Onclick preview: + +![CRUD Field - browse popup](https://backpackforlaravel.com/uploads/docs-4-1/fields/browse_popup.png) + +
    + + +### browse_multiple + +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 +[ // 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-1/fields/browse_multiple.png) + +
    + + +### base64_image + +Upload an image and store it in the database as Base64. Notes: +- make sure the column type is LONGBLOB; +- detailed [instructions and customizations here](https://github.com/Laravel-Backpack/CRUD/pull/56#issue-164712261); + +```php +// base64_image +$this->crud->addField([ + '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-1/fields/base64_image.png) + +
    + + +### checkbox + +Checkbox for true/false. + +```php +[ // Checkbox + 'name' => 'active', + 'label' => 'Active', + 'type' => 'checkbox' +], +``` + +Input preview: + +![CRUD Field - checkbox](https://backpackforlaravel.com/uploads/docs-4-1/fields/checkbox.png) + +
    + + +### checklist + +Show a list of checkboxes, for the user to check one or more of them. + +```php +[ // Checklist + 'label' => 'Roles', + 'type' => 'checklist', + 'name' => 'roles', + 'entity' => 'roles', + 'attribute' => 'name', + 'model' => "Backpack\PermissionManager\app\Models\Role", + 'pivot' => true, +], +``` + +**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-1/fields/checklist.png) + +
    + + +### checklist_dependency + +```php +[ // 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 + ], + '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 + ], + ], +], +``` + +Input preview: + +![CRUD Field - checklist_dependency](https://backpackforlaravel.com/uploads/docs-4-1/fields/checklist_dependency.png) + +
    + + +### ckeditor + +Show a wysiwyg CKEditor to the user. + +```php +[ // CKEditor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'ckeditor', + + // optional: + 'extra_plugins' => ['oembed', 'widget'], + 'options' => [ + 'autoGrow_minHeight' => 200, + 'autoGrow_bottomSpace' => 50, + 'removePlugins' => 'resize,maximize', + ] +], +``` + +If you'd like to be able to select files from elFinder, you need to also run ```composer require backpack/filemanager``` to install elFinder. + +Input preview: + +![CRUD Field - ckeditor](https://backpackforlaravel.com/uploads/docs-4-1/fields/ckeditor.png) + +
    + + +### color + +```php +[ // Color + 'name' => 'background_color', + 'label' => 'Background Color', + 'type' => 'color', + 'default' => '#000000', +], +``` + +Input preview: + +![CRUD Field - color](https://backpackforlaravel.com/uploads/docs-4-1/fields/color.png) + +
    + + +### color_picker + +Show a pretty colour picker using [Bootstrap Colorpicker](https://itsjavi.com/bootstrap-colorpicker/). + +```php +[ // color_picker + 'label' => 'Background Color', + 'name' => 'background_color', + 'type' => 'color_picker', + 'default' => '#000000', + + // optional + // Anything your define inside `color_picker_options` will be passed as JS + // to the JavaScript plugin. For more information about the options available + // please see the plugin docs at: + // ### https://itsjavi.com/bootstrap-colorpicker/module-options.html + 'color_picker_options' => [ + 'customClass' => 'custom-class', + 'horizontal' => true, + 'extensions' => [ + [ + 'name' => 'swatches', // extension name to load + 'options' => [ // extension options + 'colors' => [ + 'primary' => '#337ab7', + 'success' => '#5cb85c', + 'info' => '#5bc0de', + 'warning' => '#f0ad4e', + 'danger' => '#d9534f' + ], + 'namesAsValues' => false + ] + ] + ] + ] +], +``` + +Input preview: + +![CRUD Field - color_picker](https://backpackforlaravel.com/uploads/docs-4-1/fields/color_picker.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 +[ // CustomHTML + 'name' => 'separator', + 'type' => 'custom_html', + 'value' => '
    ' +], +``` + + +### date + +```php +[ // Date + 'name' => 'birthday', + 'label' => 'Birthday', + 'type' => 'date' +], +``` + +Input preview: + +![CRUD Field - date](https://backpackforlaravel.com/uploads/docs-4-1/fields/date.png) + +
    + + +### date_picker + +Show a pretty [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/). + +```php +[ // 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-1/fields/date_picker.png) + +
    + + +### date_range + +Starting with Backpack\CRUD 3.1.59 + +Show a DateRangePicker and let the user choose a start date and end date. + +```php +[ // 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-1/fields/date_range.png) + +
    + + +### datetime + +```php +[ // 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-1/fields/datetime.png) + +
    + + +### datetime_picker + +Show a [Bootstrap Datetime Picker](https://eonasdan.github.io/bootstrap-datetimepicker/). + +```php +[ // 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-1/fields/datetime_picker.png) + +
    + + +### easymde + +Show an [EasyMDE - Markdown Editor](https://github.com/Ionaru/easy-markdown-editor) to the user. EasyMDE is a well-maintained fork of SimpleMDE. + +```php +[ // 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-1/fields/easymde.png) + +
    + + +### email + +```php +[ // Email + 'name' => 'email', + 'label' => 'Email Address', + 'type' => 'email' +], +``` + +Input preview: + +![CRUD Field - email](https://backpackforlaravel.com/uploads/docs-4-1/fields/email.png) + + +
    + + +### enum + +Show a select with the values in the database for that ENUM field. Requires that the db column type is "enum". If the db column allows null, the " - " value will also show up in the select. + +```php +[ // Enum + 'name' => 'status', + 'label' => 'Status', + 'type' => 'enum' +], +``` + +PLEASE NOTE the enum field only works for MySQL databases. + +Input preview: + +![CRUD Field - enum](https://backpackforlaravel.com/uploads/docs-4-1/fields/enum.png) + +
    + + +### hidden + +Include an in the form. + +```php +[ // Hidden + 'name' => 'status', + 'type' => 'hidden', + 'value' => 'active', +], +``` + +
    + + +### icon_picker + +Show an icon picker. 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/). + +The stored value will be the class name (ex: fa-home). + +```php +[ // icon_picker + 'label' => "Icon", + 'name' => 'icon', + 'type' => 'icon_picker', + 'iconset' => 'fontawesome' // options: fontawesome, lineawesome, 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-1/fields/icon_picker.png) + +
    + + +### image + +Upload an image and store it on the disk. + +**Step 1.** Show the field. +```php +// image +$this->crud->addField([ + '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 + // 'disk' => 's3_bucket', // in case you need to show images from a different disk + // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; +]); +``` + +**Step 2.** Add a [mutator](https://laravel.com/docs/7.x/eloquent-mutators#defining-a-mutator) to your Model, where you pick up the uploaded file and store it wherever you want. You can use this boilerplate code and modify it to match your use case. + +**NOTE: The code below requires that you have ```intervention/image``` installed. If you don't, please do ```composer require intervention/image``` first.** + +```php +// .. + +use Illuminate\Support\Str; +use Intervention\Image\ImageManagerStatic as Image; + +// .. + +Class Product extends Model +{ + // .. + + public function setImageAttribute($value) + { + $attribute_name = "image"; + // or use your own disk, defined in config/filesystems.php + $disk = config('backpack.base.root_disk_name'); + // destination path relative to the disk above + $destination_path = "public/uploads/folder_1/folder_2"; + + // if the image was erased + if ($value==null) { + // delete the image from disk + \Storage::disk($disk)->delete($this->{$attribute_name}); + + // set null in the database column + $this->attributes[$attribute_name] = null; + } + + // if a base64 was sent, store it in the db + if (Str::startsWith($value, 'data:image')) + { + // 0. Make the image + $image = \Image::make($value)->encode('jpg', 90); + + // 1. Generate a filename. + $filename = md5($value.time()).'.jpg'; + + // 2. Store the image on disk. + \Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream()); + + // 3. Delete the previous image, if there was one. + \Storage::disk($disk)->delete($this->{$attribute_name}); + + // 4. Save the public path to the database + // but first, remove "public/" from the path, since we're pointing to it + // from the root folder; that way, what gets saved in the db + // is the public URL (everything that comes after the domain name) + $public_destination_path = Str::replaceFirst('public/', '', $destination_path); + $this->attributes[$attribute_name] = $public_destination_path.'/'.$filename; + } + } + +// .. +``` +> **The uploaded images are not deleted for you.** If you delete an entry (using the CRUD or anywhere inside your app), the image file won't be deleted from the disk. +> If you're NOT using soft deletes on that Model and want the image to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +> ```php +> public static function boot() +> { +> parent::boot(); +> static::deleted(function($obj) { +> \Storage::disk('public_folder')->delete($obj->image); +> }); +> } +> ``` + +**A note about aspect_ratio** +The value for aspect ratio is a float that represents the ratio of the cropping rectangle height and width. By way of example, + +- Square = 1 +- Landscape = 2 +- Portrait = 0.5 + +And you can, of course, use any value for more extreme rectangles. + +Input preview: + +![CRUD Field - image](https://backpackforlaravel.com/uploads/docs-4-1/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 + +
    + + +### month + +```php +[ // Month + 'name' => 'month', + 'label' => 'Month', + 'type' => 'month' +], +``` + +Input preview: + +![CRUD Field - month](https://backpackforlaravel.com/uploads/docs-4-1/fields/month.png) + +
    + + +### number + +Shows an input type=number to the user, with optional prefix and suffix: + +```php +[ // 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-1/fields/number.png) + +
    + + +### page_or_link + +Select an existing page from PageManager or an internal or external link. It's used in the MenuManager package, but can be used in any other model just as well. Its definition looks like this: +```php +[ // PageOrLink + 'name' => ['type', 'link', 'page_id'], + 'label' => "Type", + 'type' => 'page_or_link', + 'page_model' => '\Backpack\PageManager\app\Models\Page' +], +``` + +Input preview: + +![CRUD Field - page_or_link](https://backpackforlaravel.com/uploads/docs-4-1/fields/page_or_link.png) + +
    + + +### password + +```php +[ // Password + 'name' => 'password', + 'label' => 'Password', + 'type' => 'password' +], +``` + +Input preview: + +![CRUD Field - password](https://backpackforlaravel.com/uploads/docs-4-1/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() + { + $this->crud->setRequest($this->crud->validateRequest()); + + /** @var \Illuminate\Http\Request $request */ + $request = $this->crud->getRequest(); + + // Encrypt password if specified. + if ($request->input('password')) { + $request->request->set('password', Hash::make($request->input('password'))); + } else { + $request->request->remove('password'); + } + + $this->crud->setRequest($request); + $this->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 +[ // 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-1/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 +[ // Range + 'name' => 'range', + 'label' => 'Range', + 'type' => 'range', + //optional + 'attributes' => [ + 'min' => 0, + 'max' => 10, + ], +], +``` + +Input preview: + +![CRUD Field - range](https://backpackforlaravel.com/uploads/docs-4-1/fields/range.png) + +
    + + +### relationship + +Allows the user to choose one/more entries of an Eloquent Model that has a relationship with the current model, using a ```select2``` input. In order to work, this field needs the relationships to be properly defined on the Eloquent models (```hasOne```, ```belongsTo```, ```belongsToMany``` etc). + +Input preview (for both 1-n and n-n relationships): + +![CRUD Field - range](https://backpackforlaravel.com/uploads/docs-4-1/fields/relationship.png) + + +Take a look at the examples below to understand the correct syntax for your use case. + +**Example 1. Few options (0-100). Entries are loaded onpage, using a simple Eloquent query. No AJAX.** +```php +[ // relationship + 'type' => "relationship", + 'name' => 'category', // the method on your model that defines the relationship + + // 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 + ], +``` + +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). + +**Example 2. Many options. Entries are loaded using AJAX.** + +If your related entry can have hundreds, thousands or millions of entries, it's not practical to load the options using an Eloquent query onpage, because the Create/Update page would be very slow to load. In this case, you should instruct ```select2``` to fetch the entries using AJAX calls. To do that, in your ```relationship``` field definition you should add ```'ajax' => true```: + +```php +[ // 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("http://github.com/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 + // '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) + ], +``` + +Then, you need to create the route and method that allows ```select2``` to search and fetch the results of that search. Fortunately, the ```FetchOperation``` allows you to easily do just that. Inside the CrudController where you've defined the ```relationship``` field, use the ```FetchOperation``` trait, and define a new method that will respond to AJAX queries: + +```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 in a format ```select2``` likes. For more on how this operation works, and how you can customize it, check out the [FetchOperation docs](/docs/{{version}}/crud-operation-fetch). + +**Additional operation. InlineCreate - lets the user create a related entry in a modal.** + +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 form. If you are using the Fetch operation to get the entries, you're already halfway there. In addition, you only need two additional steps: + +```php +// Inside ArticleCrudController +// for 1-n relationships (ex: category) +[ // relationship + 'type' => "relationship", + 'name' => 'category', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => true, +], +// Inside ArticleCrudController +// for n-n relationships (ex: tags) +[ // relationship + 'type' => "relationship", + 'name' => 'tags', // the method on your model that defines the relationship + 'ajax' => true, + 'inline_create' => [ 'entity' => 'tag' ] // you need to specify the entity in singular +], +``` + +Now, on the CrudController of that secondary entity the user will be able to create on-the-fly (ex: ```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. + +
    + + +### repeatable + +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-1/fields/repeatable.png) + +Clicking on the "New Item" button will add another group with the same fields (in the example, another Testimonial). The end result is a JSON with the values for those fields, nicely grouped. + +You can use most field types inside the field groups, add as many fields you need, and change their width using ```wrapper``` like you would do outside the repeatable field. But please note that: +- **all fields 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 to be included inside a field group (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 usual, 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 a repeatable group (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; +- you _can_ add validation to subfields, but naturally it'll be a little different; for a quick example of how to add validation to your repeatable fields, check out our Demo, particularly [`DummyRequest.php`](https://github.com/Laravel-Backpack/demo/blob/master/app/Http/Requests/DummyRequest.php#L31-L61); + + +```php +[ // repeatable + 'name' => 'testimonials', + 'label' => 'Testimonials', + 'type' => 'repeatable', + '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 + +], +``` + + +
    + + +### 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 +[ // 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-1/fields/select.png) + + +
    + + + +### select_grouped + +Display a select where the options are grouped by a second entity (like Categories). + +```php +[ // 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-1/fields/select_grouped.png) + +
    + + +### select2 (1-n relationship) + +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 +[ // 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-1/fields/select2_nested.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 +[ // 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-1/fields/select_multiple.png) + + +
    + + +### select2_multiple (n-n relationship) + +[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 +[ // 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-1/fields/select2_multiple.png) + +
    + + +### select2_nested + +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 +[ // 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-1/fields/select2_nested.png) + +
    + + +### select2_grouped + +Display a select2 where the options are grouped by a second entity (like Categories). + +```php +[ // 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-1/fields/select2_grouped.png) + + +
    + + +### select_and_order + +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 +[ // select_and_order + 'name' => 'featured', + 'label' => "Featured", + 'type' => 'select_and_order', + 'options' => [ + 1 => "Option 1", + 2 => "Option 2" + ] +], +``` + +Also possible: + +```php +[ // 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-1/fields/select_and_order.png) + + +
    + + +### select_from_array + +Display a select with the values you want: + +```php +[ // 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-1/fields/select_from_array.png) + +
    + + +### select2_from_array + +Display a select2 with the values you want: + +```php +[ // 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-1/fields/select2_from_array.png) + +
    + + +### select2_from_ajax + +Display a select2 that takes its values from an AJAX call. + +```php +[ // 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("http://github.com/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' => 'GET', // 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/4.1/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::get('/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-1/fields/select2_from_array.png) + +
    + + +### select2_from_ajax_multiple + +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 +[ // 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("http://github.com/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 + // '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::get('/api/city', 'Api\CityController@index'); +Route::get('/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-1/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')); + } +``` + + +
    + + +### simplemde + +Show a [SimpleMDE markdown editor](https://simplemde.com/) to the user. + +> **NOTE:** SimpleMDE works, but it has not received any updates in 4 years. We recommend you use EasyMDE instead, a fork of SimpleMDE that seems to be well looked after. Check out the [```easymde``` field type](#easymde) for Backpack - it works exactly the same as this one. + +```php +[ // SimpleMDE + 'name' => 'description', + 'label' => 'Description', + 'type' => 'simplemde', + // optional + // 'simplemdeAttributes' => [ + // 'promptURLs' => true, + // 'status' => false, + // 'spellChecker' => false, + // 'forceSync' => true, + // ], + // 'simplemdeAttributesRaw' => $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 - simplemde](https://backpackforlaravel.com/uploads/docs-4-1/fields/simplemde.png) + +
    + + +### summernote + +Show a [Summernote wysiwyg editor](http://summernote.org/) to the user. + +```php +[ // 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 +[ + '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-1/fields/summernote.png) + +
    + + +### table + +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 +[ // 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.co/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. + +Input preview: + +![CRUD Field - table](https://backpackforlaravel.com/uploads/docs-4-1/fields/table.png) + +
    + + +### text + +The basic field type, all it needs is the two mandatory parameters: name and label. + +```php +[ // 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-1/fields/text.png) + +
    + + +### textarea + +Show a textarea to the user. + +```php +[ // Textarea + 'name' => 'description', + 'label' => 'Description', + 'type' => 'textarea' +], +``` + +Input preview: + +![CRUD Field - textarea](https://backpackforlaravel.com/uploads/docs-4-1/fields/textarea.png) + +
    + + +### time + +```php +[ // Time + 'name' => 'start', + 'label' => 'Start time', + 'type' => 'time' +], +``` + +
    + + +### tinymce + +Show a wysiwyg (TinyMCE) to the user. + +```php +[ // 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-1/fields/tinymce.png) + +
    + + +### upload + +**Step 1.** Show a file input to the user: +```php +[ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the file from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path); + + // return $this->attributes[{$attribute_name}]; // uncomment if this is a translatable field + } +``` + +**How it works:** + +The field sends the file, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the file is stored on the disk. + +>NOTE: If this field is mandatory (required in validation) please use the [sometimes laravel validation rule](https://laravel.com/docs/5.8/validation#conditionally-adding-rules) together with **required** in your validation. (sometimes|required|file etc... ) + +[The ```uploadFileToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/app/Models/Traits/HasUploadFields.php#L31-L59) will take care of everything for most use cases: + +```php +/** + * Handle file upload and DB storage for a file: + * - on CREATE + * - stores the file at the destination path + * - generates a name + * - stores the full path in the DB; + * - on UPDATE + * - if the value is null, deletes the file and sets null in the DB + * - if the value is different, stores the different file and updates DB value + * / +public function uploadFileToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the file to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } +``` + +Input preview: + +![CRUD Field - upload](https://backpackforlaravel.com/uploads/docs-4-1/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 +[ // Upload + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the files from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setPhotosAttribute($value) + { + $attribute_name = "photos"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path); + } +``` + +**Step 3.** Since the filenames are stored in the database as a JSON array, we're going to use [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model, so every time we get the filenames array from the database it's converted from a JSON array to a PHP array: +```php + protected $casts = [ + 'photos' => 'array' + ]; +``` + +**How it works:** + +The field sends the files, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the files are stored on the disk. + +[The ```uploadMultipleFilesToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/app/Models/Traits/HasUploadFields.php#L76-L113) will take care of everything for most use cases: + +``` +/** + * Handle multiple file upload and DB storage: + * - if files are sent + * - stores the files at the destination path + * - generates random names + * - stores the full path in the DB, as JSON array; + * - if a hidden input is sent to clear one or more files + * - deletes the file + * - removes that file from the DB. + * / +public function uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the files to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + if (count((array)$obj->photos)) { + foreach ($obj->photos as $file_path) { + \Storage::disk('public_folder')->delete($file_path); + } + } + }); + } +``` + +You might notice the field is using a ```clear_photos``` variable. Don't worry, you don't need it in your db table. That's just used to delete photos upon "update". If you use ```$fillable``` on your model, just don't include it. If you use ```$guarded``` on your model, place it in guarded. + +Input preview: + +![CRUD Field - upload_multiple](https://backpackforlaravel.com/uploads/docs-4-1/fields/upload_multiple.png) + + +### url + +```php +[ // URL + 'name' => 'link', + 'label' => 'Link to video file', + 'type' => 'url' +], +``` + +
    + + +### video + +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 +[ // 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.co/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. + + +
    + + +### view + +Load a custom view in the form. + +```php +[ // 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). + +
    + + +### week + +```php +[ // Week + 'name' => 'first_week', + 'label' => 'First week', + 'type' => 'week' +], +``` + +Input preview: + +![CRUD Field - week](https://backpackforlaravel.com/uploads/docs-4-1/fields/week.png) + +
    + + +### wysiwyg + +Show a wysiwyg (CKEditor) to the user. + +```php +[ // WYSIWYG Editor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'wysiwyg' +], +``` + + +## 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:publish crud/fields/field_name```. For example, to publish the number field type, you'd type ```php artisan backpack:publish crud/fields/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. 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 +[ // 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') + + +@if ($crud->fieldTypeNotLoaded($field)) + @php + $crud->markFieldTypeAsLoaded($field); + @endphp + + {{-- 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 +@endif +``` + +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; + + + +## FAQs + + +### What field should I use for a relationship? + +With so many field types, it can be a little overwhelming for a first-timer to quickly grasp what field type to use for your Eloquent relationship. Here's a list of what most people use, most of the times: + +#### hasOne (1-1 relationship) + +- example: + - `User -> Phone` + - a User has one Phone; 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 `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 aproximately how many entries the dropdown will have: + - for 0-10 dropdown items - we recommend you use the [`select`](#select) field; + - for 0-500 dropdown items - we recommend you use the [`select2`](#select2) or [`relationship`](#relationship) 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 or by using 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 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 in a modal, without leaving the current Create Phone form; + +#### hasMany (1-n relationship) + +- example: + - `Post -> Comment` + - a Post has many Comments; a Comment can only belong to one Post + - the foreign key is stored on the Comment (`post_id` on `comments` table) +- 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')->type('repeatable')->fields([['name' => 'comment_text']]); //note comment_text is one field in the comment table. +``` +- you should use the [`repeatable`](#repeatable) field on the Post create/update form, to add Comments to your Posts, but you'll also need to modify your `store()` and `update()` methods to take that into account, and transform the JSON received from the [`repeatable`](#repeatable) field into actual entries in the database (and back); check out [this example here](https://gist.github.com/pxpm/d584a109037d3efc7519872ac2096334) for a full example on how to do it. + +- notes: + - you _cannot_ use [the InlineCreate operation](/docs/{{version}}/crud-operation-inline-create) to have show a [+ Add Item] button next to the dropdown; that's because the Comment needs a `post_id` (not nullable); and until the Save button is clicked to submit the Create Post form, there is no `post_id`; + +#### belongsToMany (n-n relationship) + +- example: + - `User -> Role` + - a User has many Roles; a Role can also have many Users + - the foreign key is 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'); //use the relation name here and backpack will assume that it is a [`relationship`](#relationship) field type +CRUD::field('roles')->type('select_multiple'); +CRUD::field('roles')->type('select2_multiple'); + +// inside RoleCrudController::setupCreateOperation() +CRUD::field('users')->type('relationship'); //if you omit the type here, since `users` is a relation in Role model it would be auto-infered by backpack +CRUD::field('users')->type('select_multiple'); +CRUD::field('users')->type('select2_multiple'); +``` +- 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 aditional data in the pivot table +- To be able to save aditional data in the pivot table you should use [`repeatable`](#repeatable) field, but you'll also need to modify your `store()` and `update()` methods to take that into account, and transform the JSON received from the [`repeatable`](#repeatable) field into actual entries in the database (and back); check out [this example here](https://gist.github.com/pxpm/516f55c303235b799ac4e38f1167094e) for a full example on how to do it. + +#### morphOne (1-1 polymorphic relationship) + +- example: + - Post/User -> Video. + - The User model and the Post model have 1 Video each. + - [the `morphOne` relationship should be properly defined](https://laravel.com/docs/8.x/eloquent-relationships#one-to-one-polymorphic-relations) in both the Post/User and Video models; + +You should now use the [`repeatable`](#repeatable) field on the Post/User create/update form, to add a `Video` to your models, but you'll also need to modify your `store()` and `update()` methods to take that into account, and transform the JSON received from the [`repeatable`](#repeatable) field into actual entries in the database (and back); check out [this example here](https://gist.github.com/pxpm/c49c4ebb3c8d8af8a5bcce3122622a61) for a full example on how to do it. + +#### morphMany (1-n polymorphic relationship) + +- example: + - Video/Post -> Comment. + - The Video model and the Post model can have multiple Comment model but the comment belong to only one of them. + - [the `morphMany` relationship should be properly defined](https://laravel.com/docs/8.x/eloquent-relationships#one-to-many-polymorphic-relations) in both the Post/Video and Comment models; + +You should now use the [`repeatable`](#repeatable) field on the Post/Video create/update form, to add one or more `Comment` to your models, but you'll also need to modify your `store()` and `update()` methods to take that into account, and transform the JSON received from the [`repeatable`](#repeatable) field into actual entries in the database (and back); check out [this example here](https://gist.github.com/pxpm/d7709df4e1fac04c0c329a2ad0b35a5b) for a full example on how to do it. + +#### morphToMany (n-n polymorphic relationship) + +- example: + - Video/Post -> 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/8.x/eloquent-relationships#many-to-many-polymorphic-relations) in both the Post/Video and Tag models; + +```php +CRUD::field('tags'); //will assume to be a relationship field type. +CRUD::field('tags')->type('select2_multiple')->model('\Backpack\NewsCRUD\app\Models\Tag')->attribute('name'); +``` + +- 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 aditional data in the pivot table + +You should now use the [`repeatable`](#repeatable) field on the Post/Video create/update form, to add one or more `Tag` to your models, but you'll also need to modify your `store()` and `update()` methods to take that into account, and transform the JSON received from the [`repeatable`](#repeatable) field into actual entries in the database (and back); check out [this example here](https://gist.github.com/pxpm/2a45de69d755f673e3fc5fa60b2b0441) for a full example on how to do it. + +#### hasOneThrough (1-1-1 relationship) and hasManyThrough (1-1-n relationship) + +- Both are "read" relations and should not be edited from the far side of the relation. To give some context imagine 3 models: Product, Category and Order. +The Product has a Category (category_id), and the Order has a Product (product_id). We would use `HasOneThrough` in the `Order` model to get the `Category` through `Product` model. diff --git a/4.1/crud-filters.md b/4.1/crud-filters.md new file mode 100644 index 00000000..c31540a5 --- /dev/null +++ b/4.1/crud-filters.md @@ -0,0 +1,494 @@ +# Filters + +--- + + +## About + +Backpack CRUD allows you to show a filters bar right above the entries table. When selected or modified, they reload the DataTables results. The search will then search within the filtered elements. + +![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()```. + + +### Filters API + +In order to manipulate filters, you can use: + +```php +$this->crud->addFilter($options, $values, $filter_logic); + +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); + +$this->crud->filters(); // gets all the filters +``` + + +### Adding a filter + +When adding a filter 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) + $this->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: + // $this->crud->addClause('draft'); +}); +``` +> 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 above are simple filters. + +```php +// simple filter +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'active', + 'label' => 'Active' +], +false, +function() { // if the filter is active + // $this->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 +// simple filter +$this->crud->addFilter([ + 'type' => 'text', + 'name' => 'description', + 'label' => 'Description' +], +false, +function($value) { // if the filter is active + // $this->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 +// date filter +$this->crud->addFilter([ + 'type' => 'date', + 'name' => 'date', + 'label' => 'Date' +], + false, +function ($value) { // if the filter is active, apply these constraints + // $this->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 +// daterange filter +$this->crud->addFilter([ + 'type' => 'date_range', + 'name' => 'from_to', + 'label' => 'Date range' +], +false, +function ($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); +``` + +
    + + +### 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 +// dropdown filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'dropdown', + 'label' => 'Status' +], [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', +], function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2', + 'label' => 'Status' +], function () { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function ($value) { // if the filter is active + // $this->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 second parameter (the closure), with something like ```return \Backpack\NewsCRUD\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 +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2_multiple', + 'label' => 'Status' +], function() { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function($values) { // if the filter is active + // $this->crud->addClause('whereIn', 'status', json_decode($values)); +}); +``` + +
    + + +### 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). + +NOTE: if you want to setup your ajax routes using FetchOperation, have a look at: FetchOperation with ajax filter + +![Backpack CRUD Select2_ajax Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2_ajax.png) + +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'); +``` + +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'); + return $options; +} +``` + +3. Add the select2_ajax filter and for the second parameter ("values") specify the exact route. + +```php +// select2_ajax filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2_ajax', + 'label' => 'Category', + 'placeholder' => 'Pick a category' +], +url('http://github.com/admin/test/ajax-category-options'), // the ajax route +function($value) { // if the filter is active + // $this->crud->addClause('where', 'category_id', $value); +}); +``` + +
    + + +### Range + +Shows two number inputs, for min and max. + +![Backpack CRUD Range Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/range.png) + +```php +$this->crud->addFilter([ + 'name' => 'number', + 'type' => 'range', + 'label' => 'Range', + 'label_from' => 'min value', + 'label_to' => 'max value' +], +false, +function($value) { // if the filter is active + $range = json_decode($value); + if ($range->from) { + $this->crud->addClause('where', 'number', '>=', (float) $range->from); + } + if ($range->to) { + $this->crud->addClause('where', 'number', '<=', (float) $range->to); + } +}); +``` + +
    + + +### 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 +// custom filter view +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'view', + 'view' => 'package::columns.column_type_name', // or path to blade file + 'label' => 'Category', + 'placeholder' => 'Pick a category', +], +false, +function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'published', + 'type' => 'select2', + 'label' => 'Published' +], function() { + return \App\Models\Test::getEnumValuesAsAssociativeArray('published'); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'published', $value); +}); +``` + +Use a select2 to filter by a 1-n relationship: +```php +// select2 filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2', + 'label' => 'Category' +], function() { + return \App\Models\Category::all()->pluck('name', 'id')->toArray(); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'category_id', $value); +}); +``` + +Use a select2_multiple to filter Products by an n-n relationship: +```php +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'tags', + 'type' => 'select2_multiple', + 'label' => 'Tags' +], function() { // the options that show up in the select2 + return Product::all()->pluck('name', 'id')->toArray(); +}, function($values) { // if the filter is active + 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 +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'published', + 'label' => 'Published' +], +false, +function() { // if the filter is active (the GET parameter "published" exits) + $this->crud->addClause('published'); +}); +``` + +Use a simple filter to show the softDeleted items (trashed): +```php +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'trashed', + 'label' => 'Trashed' +], +false, +function($values) { // if the filter is active + $this->crud->query = $this->crud->query->onlyTrashed(); +}); +``` diff --git a/4.1/crud-fluent-syntax.md b/4.1/crud-fluent-syntax.md new file mode 100644 index 00000000..f087704f --- /dev/null +++ b/4.1/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('http://github.com/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('http://github.com/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('http://github.com/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('http://github.com/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/4.1/crud-how-to.md b/4.1/crud-how-to.md new file mode 100644 index 00000000..31540bf1 --- /dev/null +++ b/4.1/crud-how-to.md @@ -0,0 +1,363 @@ +# FAQs for CRUDs + +--- + +In addition the usual CRUD functionality, Backpack also allows you to do a few more complicated things: + + + +## 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/crud``` folder, it will pick up those instead; you can use this method to overwrite a blade file for all CRUDs; +- 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 +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setDetailsRowView('your-view'); +``` + + +### Customize CSS and JS for Default CRUD Operations + +Each default Backpack operation has its own CSS and JS file, in: +- ```public/packages/backpack/crud/css``` +- ```public/packages/backpack/crud/js``` + +Backpack will pick it up in that operation's view (ex: ```create.css``` or ```list.js```). + + +### 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. + + +### 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:publish crud/fields/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. + + +### 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); + }); + } + } +} +``` + + +### 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 + $this->crud->addColumn([ + 'label' => "Location", + 'type' => 'select', + 'name' => 'location_id', + 'entity' => 'location', + 'attribute' => 'title', + 'model' => "App\Models\Location" + ]); + + $this->crud->addColumn([ + '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" + ]); +``` + + +### 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) + + +### 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:"4.1.x-dev as 4.0.99" +``` + +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 +``` + + +### 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 +$this->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) + $this->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. + + +### Add a select2 field that depends on another field + +The ```select2_from_ajax``` and ```select2_from_ajax_multiple``` fields allow you to filter the results of a select2, depending on what has already been selected in a form. Say you have to 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 +$this->crud->addField([ // SELECT2 + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category', + 'entity' => 'category', + 'attribute' => 'name', +]); + +$this->crud->addField([ // select2_from_ajax: 1-n relationship + '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('http://github.com/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' => 'GET', // optional - HTTP method to use for the AJAX call (GET, POST) +]); +``` + +**DIFFERENT HERE**: ```minimum_input_length```, ```dependencies``` and ```include_all_form_fields```. + +2. That second select points to routes that need to be registered: + +```php +Route::get('api/article', 'App\Http\Controllers\Api\ArticleController@index'); +Route::get('api/article/{id}', 'App\Http\Controllers\Api\ArticleController@show'); +``` + +**DIFFERENT HERE**: Nothing. + +3. Then that controller would look something like this: + +```php +input('q'); + + // 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. + + + +### Change the content class for an operation + +If you want to make the contents of an operation take more / less space from the window, 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 +$this->crud->setCreateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setUpdateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setListContentClass('col-md-8 col-md-offset-2'); +$this->crud->setShowContentClass('col-md-8 col-md-offset-2'); +$this->crud->setReorderContentClass('col-md-8 col-md-offset-2'); +$this->crud->setRevisionsTimelineContentClass('col-md-8 col-md-offset-2'); +``` + + + +### 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). diff --git a/4.1/crud-operation-clone.md b/4.1/crud-operation-clone.md new file mode 100644 index 00000000..96d8a8c6 --- /dev/null +++ b/4.1/crud-operation-clone.md @@ -0,0 +1,127 @@ +# Clone Operation + +-- + + +## 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. + + +## 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 +crud->setModel(\App\Models\Product::class); + $this->crud->setRoute(backpack_url('http://github.com/product')); + $this->crud->setEntityNameStrings('product', 'products'); + } +} +``` + +This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### 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) +{ + $this->crud->hasAccessOrFail('clone'); + $this->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:publish crud/buttons/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. + + +### 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:publish crud/buttons/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/4.1/crud-operation-create.md b/4.1/crud-operation-create.md new file mode 100644 index 00000000..29dfa829 --- /dev/null +++ b/4.1/crud-operation-create.md @@ -0,0 +1,135 @@ +# 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-1/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. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**. 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'); + + $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/5.5/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. + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` diff --git a/4.1/crud-operation-delete.md b/4.1/crud-operation-delete.md new file mode 100644 index 00000000..36e4a90c --- /dev/null +++ b/4.1/crud-operation-delete.md @@ -0,0 +1,96 @@ +# 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) +{ + $this->crud->hasAccessOrFail('delete'); + + return $this->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:publish crud/buttons/delete +``` + + +## Delete Multiple Items (Bulk Delete) + +In addition to the button for each entry, you can show checkboxes next to each element, and allow your 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. + + +### 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($id) +{ + // 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:publish crud/buttons/bulk_delete +``` diff --git a/4.1/crud-operation-fetch.md b/4.1/crud-operation-fetch.md new file mode 100644 index 00000000..3f4a8f3e --- /dev/null +++ b/4.1/crud-operation-fetch.md @@ -0,0 +1,157 @@ +# Fetch Operation + +--- + + +## 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 + +None. + + +## How to Use + +In order to enable this operation in your CrudController, you need to: +1. Use the ```FetchOperation``` trait; +2. Add a ```fetchEntityName()``` method, that will respond to the AJAX requests (following this naming convention); + +```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 + 'query' => function($model) { + return $model->active(); + } // to filter the results that are returned + ]); + } +} +``` + +3. A route following has been created automatically to point to the ```fetchTag``` method you created. You now point your AJAX select to this route, using ```backpack_url('http://github.com/your-main-entity/fetch/tag')``` . + + + +## How It Works + +Based on the fact that the ```fetchTag()``` method exist, 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 of guessing the searchable attributes** + +If not specified `searchable_attributes` will be automatically infered from model dabatase columns. To prevent this behaviour you can setup an empty `searchable_attributes` array. + +That said, you can use something like below: + +```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 AJAX method from `GET` (the default for this filter) to `POST` (the default for the Fetch operation); +- tell the filter what attribute we want to show to the user; + +``` +$this->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('http://github.com/product/fetch/category'), // the fetch route on the ProductCrudController +function($value) { // if the filter is active + // $this->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: + +**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. + +**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/4.1/crud-operation-inline-create.md b/4.1/crud-operation-inline-create.md new file mode 100644 index 00000000..1f5e83c7 --- /dev/null +++ b/4.1/crud-operation-inline-create.md @@ -0,0 +1,112 @@ +# InlineCreate Operation + +--- + + +## 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-1/release_notes/inline_create_small.gif) + + + +## Requirements + +- 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('http://github.com/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() + 'include_main_form_fields' => ['field1', 'field2'], // pass certain fields from the main form to the modal + ] +``` + + +**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. diff --git a/4.1/crud-operation-list-entries.md b/4.1/crud-operation-list-entries.md new file mode 100644 index 00000000..07575546 --- /dev/null +++ b/4.1/crud-operation-list-entries.md @@ -0,0 +1,237 @@ +# 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-1/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-1/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 +crud->addColumn(); + } +} +``` + +Configuration for this operation should be done inside your ```setupListOperation()``` method. **For a minimum setup, you only need to define the columns you need to show in the table.** + + +### 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 +$this->crud->addColumn([ + '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 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) + +Backpack adds a few buttons by default: +- ```add``` to the ```top``` stack; +- ```edit``` and ```delete``` to the ```line``` stack; + +To learn more about buttons, **check out the [Buttons](/docs/{{version}}/crud-buttons) documentation page**. + + +### Filters + +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**. + + +### 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://backpackforlaravel.com/uploads/docs-4-0/operations/listEntries_details_row.png) + +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. You'll want to overwrite that method to show anything you'd like in the details row. + +To use, inside your ```EntityCrudController```: +1. Enable the functionality: ```$this->crud->enableDetailsRow();``` +2. Overwrite the ```showDetailsRow($id)``` method; + +Alternative for the 2nd step: overwrite ```views/backpack/crud/details_row.blade.php``` which is called by the default ```showDetailsRow($id)``` functionality. + +```php +class EntityCrudController extends CrudController +{ + use \Backpack\CRUD\app\Http\Operations\ListOperation; + + protected $setupDetailsRowRoutes = false; +} +``` + +#### Export Buttons + +Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->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. + + +#### 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. +$this->crud->addClause('active'); // apply a 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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->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) +``` + + +#### 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 ```$this->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 ```$this->crud->enablePersistentTable();``` +- disable it inside a particular crud controller with ```$this->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 `$this->crud->setOperationSetting('persistentTableDuration', 120); in your setupListOperation()` for 2 hours persistency. The default is `false` which means forever. + + +## 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. diff --git a/4.1/crud-operation-reorder.md b/4.1/crud-operation-reorder.md new file mode 100644 index 00000000..4f2de640 --- /dev/null +++ b/4.1/crud-operation-reorder.md @@ -0,0 +1,82 @@ +# 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 +crud->set('reorder.label', 'name'); + // define how deep the admin is allowed to nest the items + // for infinite levels, set it to 0 + $this->crud->set('reorder.max_level', 2); + } +} +``` + +This will: +- allow access to the Reorder operation; +- make a "Reorder" button appear next to "Add entry" in the List view, if the List operation is enabled; +- enable the routes needed by the Reorder operation; + +Where: +- ```attribute_name``` should be the attribute you want shown on the draggable elements (ex: ```name```); +- ```ALLOWED_DEPTH``` should be an integer, how many levels deep would you allow your admin to go when nesting; for infinite levels, you should set it to ```0```; + + +## How It Works + +The ```/reorder``` route points to a ```reorder()``` method in your ```EntityCrudController```. + + + +## 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:publish crud/buttons/reorder +``` diff --git a/4.1/crud-operation-revisions.md b/4.1/crud-operation-revisions.md new file mode 100644 index 00000000..8f841408 --- /dev/null +++ b/4.1/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: + +![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. Inside this method, it uses ```setFromDb()``` to try to magically figure out all attributes you would like shown for this Model, and shows them using [Column types](/docs/{{version}}/crud-columns) inside ```show.blade.php```. + + +## How to Use + +To enable this operation, you need to use the ```ShowOperation``` trait on your CrudController: + +```php +crud->set('show.setFromDb', false); + + // example logic + $this->crud->addColumn([ + 'name' => 'table', + 'label' => 'Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ] + ]); + $this->crud->addColumn([ + 'name' => 'fake_table', + 'label' => 'Fake Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ], + ]); + $this->crud->addColumn('text'); + // $this->crud->removeColumn('date'); + // $this->crud->removeColumn('extras'); + + // Note: if you HAVEN'T set show.setFromDb to false, the removeColumn() calls won't work + // because setFromDb() is called AFTER setupShowOperation(); we know this is not intuitive at all + // and we plan to change behaviour in the next version; see this Github issue for more details + // https://github.com/Laravel-Backpack/CRUD/issues/3108 + } +``` + + +## 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); + // cutom logic after + return $content; +} +``` diff --git a/4.1/crud-operation-update.md b/4.1/crud-operation-update.md new file mode 100644 index 00000000..3e4aa3f0 --- /dev/null +++ b/4.1/crud-operation-update.md @@ -0,0 +1,239 @@ +# Update Operation + +--- + + +## About + +This operation allows your admins to edit entries from the database. + +![CRUD Update Operation](https://backpackforlaravel.com/uploads/docs-4-1/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); +// add a field to both the Update and Update operations +$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). + + +## 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 typehinted 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. + + +## Callbacks + +Developers coming from GroceryCRUD or other CRUD systems will be looking for callbacks to run before_insert, before_update, after_insert, after_update. **There are no callbacks in Backpack**. 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: + +```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. + + +## Separate Validation Rules for Create and Update + +**Differences between the Create and Update validations?** If your Update operation requires a different validation than the Create operation, just: +- create a separate request file for each operation; +- instruct your EntityCrudController to use separate files, in the "use" section; + +For example, we could create ```UpdateTagRequest.php``` and ```CreateTagRequest.php```, with different validations, then in TagCrudController just do: +```diff +- use App\Http\Requests\TagRequest as StoreRequest; ++ use App\Http\Requests\CreateTagRequest as StoreRequest; +- use App\Http\Requests\TagRequest as UpdateRequest; ++ use App\Http\Requests\UpdateTagRequest as UpdateRequest; +``` diff --git a/4.1/crud-operations.md b/4.1/crud-operations.md new file mode 100644 index 00000000..c4a60df6 --- /dev/null +++ b/4.1/crud-operations.md @@ -0,0 +1,966 @@ +# 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 an Eloquent model, with pagination, search, filters; +- [Create](/docs/{{version}}/crud-operation-create) - allows the admin to add a new entry; +- [Update](/docs/{{version}}/crud-operation-update) - allows the admin to edit an existing entry; +- [Show](/docs/{{version}}/crud-operation-show) - allows the admin to preview an entry; +- [Delete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove and entry; +- [BulkDelete](/docs/{{version}}/crud-operation-delete) - allows the admin to remove multiple entries in one go; +- [Clone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of a database entry; +- [BulkClone](/docs/{{version}}/crud-operation-clone) - allows the admin to make a copy of multiple database entries in one go; +- [Reorder](/docs/{{version}}/crud-operation-reorder) - allows the admin to reorder & nest all entries of a model, in a hierarchy tree; +- [Revisions](/docs/{{version}}/crud-operation-revisions) - shows an audit log of all changes to an entry, and allows the admin to undo modifications; + + + +### 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']); + +$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\CustomOperations\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 setupPublishRoutes($segment, $routeName, $controller) + { + Route::get($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('http://github.com/dashboard'), + $crud->entity_name_plural => url(http://github.com/$crud-%3Eroute), + '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', 'crud::buttons.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 +``` diff --git a/4.1/crud-save-actions.md b/4.1/crud-save-actions.md new file mode 100644 index 00000000..deb0aa7a --- /dev/null +++ b/4.1/crud-save-actions.md @@ -0,0 +1,132 @@ +# 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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->crud->orderSaveActions(['save_action_one','save_action_two']); +// or +$this->crud->orderSaveActions(['save_action_one' => 3,'save_action_two' => 2]); +``` + +#### setSaveActions(array $saveActions) + +Alias for ```replaceSaveActions(array $saveActions)```. diff --git a/4.1/crud-tutorial.md b/4.1/crud-tutorial.md new file mode 100644 index 00000000..410741da --- /dev/null +++ b/4.1/crud-tutorial.md @@ -0,0 +1,382 @@ +# 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 + +> **NEW!!!** Starting with Aug 2021, there's a much simpler way to generate everything 🎉 **Check out our new paid addon - [Backpack DevTools](https://backpackforlaravel.com/products/devtools).** It's a GUI that will help you generate Migrations, Models (complete with relationships), 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. + +Since we don't have an Eloquent model for it already, we're going to use [Jeffrey Way's Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) package, which you've most likely installed along with Backpack, to generate the migration. + +```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 +``` + +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 item in the sidebar menu, in ```resources/views/vendor/backpack/base/inc/sidebar_content.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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + protected function setupListOperation() + { + // TODO: remove setFromDb() and manually define Columns, maybe Filters + $this->crud->setFromDb(); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + + // TODO: remove setFromDb() and manually define Fields + $this->crud->setFromDb(); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); + } +} +``` + +#### The Basics + +What we should notice inside this TagCrudController is that: +- ```TagCrudController extends CrudController```; +- ```TagCrudController``` has a ```setup()``` method, where can plug in 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; + +As we can tell from the comments in our ```setupXxxOperation()``` methods, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. + +That being said, since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys & girls. + +#### Option 1. SetupXxxOperation Methods + +We can either define each operations inside its ```setupXxxOperation()``` method: + +```php + protected function setupListOperation() + { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); // since this calls the methods above, no need to do anything here + } +``` + +This will: +- disable the ```setFromDb()``` functionality (since we deleted that line); +- 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; + +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. + +#### Option 2. Operation Closures + +An alternative to defining operation inside ```setupXxxOperation()``` methods is to do it 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 would like 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() + { + $this->crud->setModel('App\Models\Tag'); + $this->crud->setRoute(config('backpack.base.route_prefix') . '/tag'); + $this->crud->setEntityNameStrings('tag', 'tags'); + + $this->crud->operation('list', function() { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + }); + + $this->crud->operation(['create', 'update'], function() { + $this->crud->addValidation(TagCrudRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => '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 will 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. 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 ```views/vendor/backpack/base/inc/sidebar_content.php``` file. You'll see this file is pure HTML. This will allow you to customize the menu as much as you want. 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. + + +## 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/4.1/demo.md b/4.1/demo.md new file mode 100644 index 00000000..ec1e9328 --- /dev/null +++ b/4.1/demo.md @@ -0,0 +1,68 @@ +# Demo + +--- + +We've put together a working Laravel app (backend-only), that you can install on your machine. 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 6; +- installed Backpack\CRUD on top; +- created a few demo models (Monsters, Icons, Products) and admin panels for them, using dozens of field types, column types, filters, etc - to show off most of Backpack's default 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 need to install the Demo. **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 + +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) Install all the requirements: +``` zsh +cd backpack-demo +composer install +``` + +4) Populate the database and stuff: +```zsh +php artisan key:generate +php artisan migrate +php artisan db:seed --class="Backpack\Settings\database\seeds\SettingsTableSeeder" +php artisan db:seed +``` + + +## Demo Usage + +Once everything's installed, and your database has been set up: + +- Your admin panel is available at http://localhost/backpack-demo/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 40 field types. +- The magic of Backpack is not in its standard functionality, but in 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/4.1/faq.md b/4.1/faq.md new file mode 100644 index 00000000..5724c731 --- /dev/null +++ b/4.1/faq.md @@ -0,0 +1,100 @@ +# Frequently Asked Questions + +--- + + + +## Licensing + + +### Do I need a license to test Backpack? + +You don't need a license code AT ALL, if you're just installing Backpack on localhost. Just go ahead and install Backpack, you'll have zero limitations until you go to production. + + + +### Do I need a license to put a project on a testing domain? + +Yes, you do need a license code. But not a _separate_ license code. If you've already purchased a license code, you should use the same code for both your staging and production instances - even if the domains are different. + +If you're interested in a license code to test your project online, _before_ you purchase a Backpack license - take note that we don't do that. We do not issue licenses for testing an already built app on a staging domain. If you've reached a point where you put your project on a staging server, that should mean Backpack has already helped you or your company save dozens or hundreds of hours of work - so that qualifies as commercial use. We recommend you purchase a license code - you can use the same license code on both your staging and production domain. + +An alternative to be able to test a project online, without paying anything, would be to put your project online and bear the annoying notification bubbles. If you don't mind the notification bubbles, you can get a general idea of what's working and what's not. It's not ideal, but it's free. + + + +### Do I need a license to create an open-source project? + +No, you do not. But there's a huge catch. + +Because Backpack requires a license code to work in production, all users of _your open-source project_ will require a Backpack license code for their apps to work in production. So yes, your project will be open-source, but developers _will_ need to pay for a Backpack license if their project is commercial, or apply for a free non-commercial license on our website. This makes it a bit inconvenient to use Backpack for open-source work, we know that. + +To rephrase and clarify - _you_ don't need a license code to develop an open-source project that uses Backpack. But _your users_ will. And no, if you purchase a Backpack license, your open-source users are not covered by your license, only you are. Even if you do have a Backpack license code, that is for you alone, and you should never _ever_ make that code public, or share it with anybody outside your company. + +We plan to address this inconvenience in Backpack v5, by having a "Backpack Lite" package under MIT License, with limited features. Then you could easily build things on top of that, instead of the main Backpack package. But until then, I'm afraid the problem remains - and the only solution is to clearly state in your README file that your package is open-source, but one of your dependencies requires payment for commercial use. Feel free to email hello@tabacitu.ro with details about your project, for more information. + + + + +### How do I use the Backpack license key I purchased/received? + +There are two places where you can put your Backpack license code: + +- (A) inside your .ENV file, ona a new line, as `BACKPACK_LICENSE=xxx` +- (B) inside your `config/backpack/base.php`, at the bottom, you'll find `'license_code' => env('BACKPACK_LICENSE', false)` - you can replace `false` with your license code (wrapped by single quotes) + +Option (A) is the recommended one, because there's no point in saving the license code within the code base (in git), and exposing it to whoever has access to the source code. The Backpack license code is only needed in production, so the best way is to add an environment variable there, in on your production server. + + + +## Miscellaneous + + + +### How do I update Backpack to the latest non-breaking version? + +First of all, **run `composer update` on your project**. That will pull in the latest supported version of all your Backpack packages. + +Then you should **re-publish the JS and CSS assets**. There are two ways to do that: + +(A) Run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force`. Please note this will overwrite anything that's already there. This B solution has a downside: _unused files are not removed_. A few files Backpack no longer uses will still be in your public/packages folder, even though they're no longer used. + +(B) If you have NOT touched you `public/packages` folder, or placed anything custom inside it: +- delete the public/packages directory and all its contents; +- run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public` +- if you use elFinder, also delete `resources/views/vendor/elfinder` and run `php artisan backpack:filemanager:install` + + + +### 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 public/packages +rm -rf resources/views/errors +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 + +# (maybe) remove other Backpack dev tools you've used +composer remove --dev laracasts/generators + +# remove Backpack from your composer.json requirements +composer remove --dev backpack/generators +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 didn't like Backpack, or it didn't fit your project. It might help us take Backpack in a different direction, one where you might want to use it. Thank you 🙏 diff --git a/4.1/getting-started-advanced-features.md b/4.1/getting-started-advanced-features.md new file mode 100644 index 00000000..525cd4d2 --- /dev/null +++ b/4.1/getting-started-advanced-features.md @@ -0,0 +1,49 @@ +# 3. Advanced Features + +--- + +**Duration:** 5 min + +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 how to find it. + +--- + + +## Other Operations +- [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) +- [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes +- [Clone](/docs/{{version}}/crud-operation-clone) Operation - you can make a copy of an entry; +- [BulkDelete](/docs/{{version}}/crud-operation-delete) Operation - you can delete multiple items in one go; +- [BulkClone](/docs/{{version}}/crud-operation-clone) Operation - you can clone multiple items in one go; + +--- + + +## Other Features +- **Create & Update** + - [Manage files on disk](/docs/{{version}}/crud-how-to#use-the-media-library-file-manager) (media library) + - [Fake fields](/docs/{{version}}/crud-fields#optional-fake-field-attributes-stores-fake-attributes-as-json-in) + - Translatable models and [multi-language CRUDs](/docs/{{version}}/crud-operation-update#translatable-models) + - [Tabs in create/update forms](/docs/{{version}}/crud-fields#split-fields-into-tabs) + +-- + +- **ListEntries** + - you can add a "+" button next to each entry, to allow the admin to easily preview some quick information that was too big to fit inside a columns - we call it [details row](/docs/{{version}}/crud-operation-list-entries#details-row) + - export all visible items in the table by adding [export buttons](/docs/{{version}}/crud-operation-list-entries#export-buttons) + - [custom search logic](/docs/{{version}}/crud-columns#custom-search-logic) for the columns in the list view + + +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 for today!** Told you we're done with long lessons :-) Hopefully some of the above have piqued your interest and you've clicked 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/4.1/getting-started-basics.md b/4.1/getting-started-basics.md new file mode 100644 index 00000000..12016657 --- /dev/null +++ b/4.1/getting-started-basics.md @@ -0,0 +1,163 @@ +# 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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2018) and accommodate yourself with Laravel first. + + + +## What is Backpack? +A software 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 what a Tag CRUD could 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. Here's what a CRUD that uses all of Backpack's features could look like: + +![Monsters CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/monster_crud_list_entries.png) + +Mind that you will _almost never_ use all of Backpack's features in one CRUD. But if you do... it still looks good, and it'll be intuitive to use. + + +## Main Features + + +### Front-End Design + +Backpack installs the [CoreUI](https://coreui.io) HTML theme, and our own design on top - [Backstrap](https://backstrap.net). It uses Bootstrap 4, and has 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 our [Backstrap demo](https://backstrap.net), or from the [CoreUI documentation](https://coreui.io/docs/getting-started/introduction/) and it will look good, without you having to design anything. + +It also installs Noty for triggering JS notification bubbles, and SweetAlerts. So you can easily use these across your admin panel. You can [trigger alerts in PHP](/docs/{{version}}/base-alerts#triggering-alerts-in-php) or [trigger alerts in JavaScript](/docs/{{version}}/base-alerts#triggering-alerts-in-javascript). + + + +### Authentication + +Backpack comes with a basic authentication system that's separate from Laravel's. This way, you can have different login screens for users & admins, if you need. If not, you can choose to use only one authentication - either Laravel's, or Backpack's. + +![Backpack 3.5 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 DB information. 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) PAID - 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 needed files. It's that simple. Check out [the images here](https://backpackforlaravel.com/products/devtools) for how it works. It's especially useful for more complex entities. It is a paid tool though, and you might not be ready to purchase yet, so let's explore a 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", no need for customizations. But don't expect this for more complex entities. They will usually have particularities 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 CrudPanel 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 sidebar item inside ```resources/views/vendor/backpack/base/inc/sidebar_content.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 [ridiculously 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 (db 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 you might use to achieve the above: + +```php +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => '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 all for today!** If you want to learn more, go ahead and [read the next lesson](/docs/{{version}}/getting-started-crud-operations) of this series. + + +
    + + Next → + diff --git a/4.1/getting-started-crud-operations.md b/4.1/getting-started-crud-operations.md new file mode 100644 index 00000000..9fd25ce2 --- /dev/null +++ b/4.1/getting-started-crud-operations.md @@ -0,0 +1,254 @@ +# 2. CRUD Operations + +--- + +**Duration:** 10 minutes + +Let's bring back the example in 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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => 'slug', + '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, BulkClone), and you can easily _create a custom operation_, but let's not get ahead of ourselves. Baby steps. **Let's go through the most important features of the operations you'll be using _all the time_: ListEntries, Create and Update**. + + +## Create & 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 what 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 ListEntries 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 a lot of times, you won't just need text inputs. You'll need datepickers, upload buttons, 1-n relationship, n-n relationships, textareas, etc. For each field, you only need to define it properly in the Controller. Here are the most used methods to manipulate fields: + +```php +$this->crud->addField($field_definition_array); +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); +$this->crud->removeField('name'); +$this->crud->removeFields(['name_1', 'name_2']); + +// pro tip: +// a quick way to add simple fields: let the CRUD decide what field type it is +$this->crud->addField('db_column_name'); +``` + +A typical *field definition array* 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'd like 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 some super-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 is 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 +$this->crud->addField([ + '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()``` 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, 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 +$this->crud->addField([ + '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? +]); +``` + +> When generating a CrudController, you might be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and what columns in your list view, but - as you'd expect - it only works for the simple field types. You can: +> +> (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields +> +> or +> +> (2) delete ```setFromDb()``` and manually define each field and column; +> +> **Our recommendation**, for anything but the simplest CRUDs, **is to manually define each field** - much easier to understand and customize, for your future self and any other developer that comes after you. + + +### Callbacks + +Developers coming from GroceryCRUD on CodeIgniter or other CRUD systems will be looking for callbacks to run ```before_insert```, ```before_update```, ```after_insert```, ```after_update```. **There are no callbacks in Backpack**. The ```store()``` and ```update()``` code is inside a trait, so you can easily overwrite 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 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 super-similar to fields**: + +```php +$this->crud->addColumn($column_definition_array); // add a single column, at the end of the table +$this->crud->addColumns([$column_definition_array_1, $column_definition_array_2]); // add multiple columns, at the end of the table +$this->crud->removeColumn('column_name'); // remove a column from the table +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the table +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns([$column_definition_array_1, $column_definition_array_2]); // make these the only columns in the table +``` + +You can use one of the [14+ 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 super-specific need. Here's an example of using the methods above: + +```php +$this->crud->addColumn([ + '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 +$this->crud->addColumn('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 well… _filter_ the ListEntries 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). + +```php +$this->crud->addFilter($options, $values, $filter_logic); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +``` + +For more on this, check out the [filters documentation page](/docs/{{version}}/crud-filters), when you need them. + + +### 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. Look for the [buttons documentation](/docs/{{version}}/crud-buttons) when you need it. + +```php +// positions: 'beginning' and 'end' +// stacks: 'line', 'top', 'bottom' +// types: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + +**That's it for today!** 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 → + \ No newline at end of file diff --git a/4.1/getting-started-license-and-support.md b/4.1/getting-started-license-and-support.md new file mode 100644 index 00000000..e455e857 --- /dev/null +++ b/4.1/getting-started-license-and-support.md @@ -0,0 +1,56 @@ +# 4. Add-ons, License & Support + +--- + +**Duration:** 3 minutes + + +## Add-ons + +In addition to our core packages (Base and CRUD), we have quite a few packages you can install or download, that treat 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 **free for non-commercial use**, but needs a license code in order to prevent "_unlicensed use_" notification bubbles and interruption of service. You can get a license code for your project: +- ```free```, if you're using it for non-commercial purposes; [apply here](https://backpackforlaravel.com/pricing); +- ```free```, if you've contributed to Backpack on Github; [apply here](https://backpackforlaravel.com/pricing); +- ```€69 EUR/project```, if you're making money using it for a project; [buy here](https://backpackforlaravel.com/pricing); +- ```€399 EUR for unlimited projects```, if you use Backpack a lot; [buy here](https://backpackforlaravel.com/pricing); + +**Freelancers** or companies **who make money using Backpack** - for themselves, their employers or their clients, **should [purchase a commercial license here](https://backpackforlaravel.com/pricing)**. + + +>**You don't need a license code on LOCALHOST.** If you're just trying Backpack on your own machine, you don't need a license code. You only need a license code when you take your application to production. + + +## Support + +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: + +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) +- **[Github Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) +- **[/r/BackpackForLaravel subreddit](https://www.reddit.com/r/BackpackForLaravel/) for showing off your work, asking for opinions on implementation, sharing tips, packages, etc.** + +Thank you for sticking up with us for so long. This is the last Backpack lesson we can give you. **Now you have absolutely no excuse not to start your first Backpack project :-)** Here are a few links for if you still don't think you're 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 + \ No newline at end of file diff --git a/4.1/getting-started-videos.md b/4.1/getting-started-videos.md new file mode 100644 index 00000000..4d76b0bf --- /dev/null +++ b/4.1/getting-started-videos.md @@ -0,0 +1,118 @@ +# Getting Started Videos + +--- + +**Total Duration:** 31 minutes + + + +

    Intro

    + + +Meet your teacher, [Cristian Tabacitu](https://twitter.com/tabacitu), the founder of Backpack for Laravel. + +
    + +
    +Mentioned in this video: +- [Laravel](https://laravel.com) +- ["Laravel from Scratch" series on Laracasts](https://laravelfromscratch.com/) +- [Backpack's docs repo on Github](https://github.com/laravel-backpack/docs) + +---- + + +

    The Admin Interface

    + +Before we go deep into the PHP features offered by Backpack, it's important to understand the HTML & CSS it uses, and how that can make your life easier when building admin panels. This is often an afterthought in admin panels, but Backpack makes it really really easy to create admin pages that are 100% custom. + +
    + +
    +Mentioned in this video: +- [Laravel docs - installation](https://laravel.com/docs/7.x) +- [Backpack docs - installation](/docs/{{version}}/installation) +- [Backpack docs - how to customize the user interface](/docs/{{version}}/base-how-to#customizing-the-design-of-the-menu-sidebar-footer) +- [Backpack docs - widgets](/docs/{{version}}/base-widgets) +- [Backpack docs - add-ons](/addons) +- [Bootstrap 4 docs](https://getbootstrap.com/) +- [Backstrap.net - the default HTML template & UI blocks](https://backstrap.net/) + +---- + + +

    Generating and Understanding CRUDs

    + +Let's generate a few Backpack CRUDs - places where the admin can Create, Read, Update or Delete entries. CRUDs will make it easy for you to build admin panels, 10x faster than before. + +
    + + +> **NEW!!!** Starting with Aug 2021, there's a much simpler way to generate everything 🎉 Instead of using Blueprint, + **check out our new paid addon - [Backpack DevTools](https://backpackforlaravel.com/products/devtools).** It's a GUI that will help you generate Migrations, Models (complete with relationships), 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. + +
    +Mentioned in this video: +- [Backpack's online demo](https://demo.backpackforlaravel.com/admin/login) +- [laravel-shift/blueprint](https://blueprint.laravelshift.com/) - CLI tool to generate Eloquent models +- [Laravel docs - migrations](https://laravel.com/docs/7.x/migrations#introduction) +- [backpack/generators - CLI tool to generate CRUDs](https://github.com/laravel-backpack/generators) +- [Fork - Git client for Mac OS & Windows](https://git-fork.com/) +- [Backpack's List Operation](/docs/{{version}}/crud-operation-list-entries) - features [columns](/docs/{{version}}/crud-columns), [filters](/docs/{{version}}/crud-filters), [buttons](/docs/{{version}}/crud-buttons), [widgets](/docs/{{version}}/base-widgets), [other features](/docs/{{version}}/crud-operation-list-entries#other-features) +- [Backpack's Create Operation](/docs/{{version}}/crud-operation-create) - features [fields](/docs/{{version}}/crud-fields), [widgets](/docs/{{version}}/base-widgets), [save actions](/docs/{{version}}/crud-save-actions), [split fields into tabs](/docs/{{version}}/crud-fields#split-fields-into-tabs), [add custom HTML between the fields](/docs/{{version}}/crud-fields#custom-html) +- [Backpack's Update Operation](/docs/{{version}}/crud-operation-update) +- [Backpack's Delete Operation](/docs/{{version}}/crud-operation-delete) + +Not mentioned in this video, but heavily recommended: +- [Backpack DevTools](https://backpackforlaravel.com/products/devtools) + +---- + + +

    Using Operations and Features in CRUDs

    + +I'd argue the best part of Backpack is not how easy it is to generate CRUDs, but how easy it is to customize them. In this video, we'll be taking a look at the default Operations that Backpack offers, and changing things around to fit our purpose. In the process, you'll understand how they work, and just how easy it is to use, customize or overwrite Operations like Create, Update, List, Delete, Reorder, Clone, BulkClone and BulkDelete. + +
    + +
    +Mentioned in this video: +- [Backpack operations](/docs/{{version}}/crud-operations) +- Laravel docs - [validation using Laravel Form Requests](https://laravel.com/docs/7.x/validation#form-request-validation) +- Laravel docs - [validation rules](https://laravel.com/docs/7.x/validation#available-validation-rules) +- [Reorder Operation](/docs/{{version}}/crud-operation-reorder) +- [Clone & BulkClone Operations](/docs/{{version}}/crud-operation-clone) +- Columns - [the 20+ column types](/docs/{{version}}/crud-columns#default-column-types), [columns API](/docs/{{version}}/crud-columns#columns-api), [fluent syntax](/docs/{{version}}/crud-fluent-syntax#fluent-columns), [array syntax](/docs/{{version}}/crud-columns#about), [overwriting a column type](/docs/{{version}}/crud-columns#overwriting-default-column-types), [creating a custom column type](/docs/{{version}}/crud-columns#creating-a-custom-column-type) +- Fields - [the 50+ field types](/docs/{{version}}/crud-fields#default-field-types), [fluent syntax](/docs/{{version}}/crud-fluent-syntax#fluent-fields-api), [array syntax](/docs/{{version}}/crud-fields#about), [overwriting a field type](/docs/{{version}}/crud-fields#overwriting-default-field-types), [custom field types](/docs/{{version}}/crud-fields#creating-a-custom-field-type) +- [the contents of an Operation - sidebar item, routes, controller](/docs/{{version}}/crud-operations#contents-of-a-custom-operation) + +---- + + +Thank you for dedicating these 31 minutes to learning Backpack. **You should now be able to build your first admin panel.** But if you feel like you're _not quite ready yet_, here are a few more things you can do: + +- [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/4.1/index.md b/4.1/index.md new file mode 100644 index 00000000..e73f725f --- /dev/null +++ b/4.1/index.md @@ -0,0 +1,58 @@ +#### About + +- [Introduction](/docs/{{version}}/introduction) +- [Demo](/docs/{{version}}/demo) +- [Installation](/docs/{{version}}/installation) +- [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) + +#### Admin UI + +- [About](/docs/{{version}}/base-about) +- [Alerts](/docs/{{version}}/base-alerts) +- [Breadcrumbs](/docs/{{version}}/base-breadcrumbs) +- [Widgets](/docs/{{version}}/base-widgets) +- [FAQ](/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) + + [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) +- [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) +- [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) diff --git a/4.1/install-optionals.md b/4.1/install-optionals.md new file mode 100644 index 00000000..8ef1b9db --- /dev/null +++ b/4.1/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 sidebar_content item for it +php artisan backpack:add-sidebar-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/base/inc/sidebar_content.blade.php or menu.blade.php: + +```bash +php artisan backpack:add-sidebar-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 to the sidebar_content file +php artisan backpack:add-sidebar-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/4.1/installation.md b/4.1/installation.md new file mode 100644 index 00000000..b1375db2 --- /dev/null +++ b/4.1/installation.md @@ -0,0 +1,73 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 8, 7 or 6, 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/8.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 Core Packages + +0) Open your project folder in your terminal: + +```bash +cd your-laravel-project-name +``` + +1) In your project's main directory: + +``` bash +# require Backpack using Composer +composer require backpack/crud:"4.1.*" +composer require --dev backpack/generators + +# run the installation command +php artisan backpack:install +``` + +> Backpack install is interactive and will ask questions during installation, if you don't want that add the `--no-interaction` argument to the install command. + +2) [optional] 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. + +3) Take note that: +- 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; This is only needed in Laravel 7+; +- Change configuration values in ```config/backpack/base.php``` to make the admin panel your own. Backpack is white label, so you can change everything: menu color, project name, developer name etc. +- If your User model has been moved (it is not ```App\User.php```), please change ```config/backpack/base.php``` to use the correct user model using the ```user_model_fqn``` config key. If you are using Laravel 8 and up, you need to change this. Because by default Laravel 8 and up save the ```User``` model inside the ```Models``` directory. + +That's it. If you already know how to use Backpack, next up you'll probably want to [create CRUD Panels](/docs/{{version}}/crud-tutorial#generate-files). + +> If it's your first time installing Backpack, it is **highly recommended** that you go through our [Getting Started series](/docs/{{version}}/getting-started-basics), to understand how Backpack works. That's why we created it - to help you learn how to use this admin panel framework. In ~23 minutes we'll teach you 80% of what you can do, and how. + + + +### 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#manually-install-backpack), which you can tweak to your needs. diff --git a/4.1/introduction.md b/4.1/introduction.md new file mode 100644 index 00000000..37722481 --- /dev/null +++ b/4.1/introduction.md @@ -0,0 +1,93 @@ +# 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: + +- Backpack will provide you with a _visual interface_ for the admin panel (the HTML, the CSS, the JS); it pulls in the excellent [CoreUI](https://coreui.io/) theme, with our own design called [Backstrap](https://backstrap.net), adds authentication functionality & bubble notifications; when you decide to build a custom feature for your admin panel, you already have the HTML blocks for the UI, and it will look good; +- Backpack will 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 for each entity in about 10 minutes / model: + +```bash +# STEP 0. create migration (in case you're starting from scratch) +composer require --dev laracasts/generators +php artisan make:migration:schema create_tags_table --model=0 --schema="name:string:unique" +php artisan migrate + +# STEP 1. create a Model, Request, Controller, Route and sidebar item for the admin panel +php artisan backpack:crud tag #use singular, not plural + +# STEP 2. go through the generated files, customize according to your needs +``` + +Better yet, if you purchase our brand-new [Backpack DevTools](https://backpackforlaravel.com/products/devtools) (premium addon, paid extra) you can generate better Migrations, Models, Seeders, Factories and CRUDs... from the comfort of your browser window 🤯 It's never been this easy: + +![](https://user-images.githubusercontent.com/1032474/128379216-72ae55fa-fcff-4747-8c35-42c733923c94.gif) + +--- + + +## 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)** - 31 minutes +- **[Text Course](/docs/{{version}}/getting-started-basics)** - 20 minutes +- **[Email Course](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 4 days, 5 minutes each + + +
    + Video Course + Text Course + Email Course + +--- + + +## Need to Know + + +### Requirements + + - Laravel 8.x, 7.x or 6.x + - MySQL (recommended) / PostgreSQL / SQLite / SQL Server + + +### How does it look? + +- Take a look at [our homepage](https://backpackforlaravel.com/). +- Play around in our [live demo](https://demo.backpackforlaravel.com/admin/login). +- [Install the demo](/docs/{{version}}/demo) and fiddle with the code. + + +### Security + +Backpack has never had a critical vulnerability/hack. But there _have_ been important security updates for dependencies (including Laravel). Please [login with Github](/auth/github) or [subscribe to our security newsletter](https://backpackforlaravel.com/newsletter), so we can reach you in case anything bad happens. No spam, no marketing emails, we promise. We only send about 2 emails per year, when we introduce major Backpack updates or there's a problem you should know about. + + +### Maintenance + +Backpack 4.1 is the current version, and is being 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 under a license we call "_You make money, I make money_" (YummY). Backpack's source is public, and you can use it for free for non-commercial purposes (testing, non-profits, personal use, etc), but if you make money using it, you need to purchase a commercial license. Please see [the pricing section](https://backpackforlaravel.com/pricing) for more details. In production, you need a license code for both commercial and non-commercial use, to prevent nagging notification bubbles. **On localhost, you don't need a license code at all.** + + +### Versioning + +When installing Backpack, require its minor version (currently ```4.1.*```). Backpack follows the same versioning system as did prior to Laravel 6 - minor Backpack versions _will_ include breaking changes. This allows us to push new features without charging our users again. For us, this is what ```major.minor.patch``` means: + +- ```major``` - **PAID upgrade; MAJOR breaking changes;** historically every 2-3 years; upgrading may take even 2-3 hours; includes MAJOR new features, MAJOR changes in how the whole system works, and complete rewrites; it allows us to _considerably_ improve how we build admin panels and add features that were previously impossible; +- ```minor``` - **FREE upgrade; SOME breaking changes**; historically every 6 months; upgrading takes 15-60 minutes; offers big new features, for free; +- ```patch``` - **FREE upgrade; NO breaking changes**; historically every week; upgrading can be done automatically with composer; includes bug fixes and non-breaking new features; + + +### Add-ons + +In addition to our core CRUD package, there are a few additional packages 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. + +For more information, please see: +- [all official add-ons](/docs/{{version}}/add-ons-official) +- [all community add-ons](/docs/{{version}}/add-ons-community) diff --git a/4.1/release-notes.md b/4.1/release-notes.md new file mode 100644 index 00000000..1a690293 --- /dev/null +++ b/4.1/release-notes.md @@ -0,0 +1,174 @@ +# Release Notes + +--- + +**Launch date:** May 5th, 2020 + +Anybody with a 4.0 license can install and use 4.1, but in order to get the new features in a project that currently runs 4.0, you need to [follow the upgrade guide](/docs/{{version}}/upgrade-guide). Don't get fooled by the fact that we kept the 4.x.x prefix - that's just to show that **Backpack 4.1 is a FREE upgrade from 4.0**. But **Backpack 4.1 is a MAJOR new version**, with over 6 months of work put into it, and major improvements under-the-hood too. It is the current and recommended version of Backpack and it's got so many cool new things that we couldn't fit them all inside this page. + +But here's the big stuff Backpack 4.1 brings to the table, and why you should upgrade from [Backpack 4.0](/docs/4.0) to 4.1. + + + +## Added + +### Operations + +#### **New operation: ```InlineCreate```** + +- allows admins to create related items on-the-fly, without leaving the current form; +- shows a new button next to your relationship fields; when you click [+ Add], a modal is shown with all the fields needed to create a related item on-the-fly; it basically brings that entry's Create form on this page; +- it works well for entities with a few inputs, but it also works well for huge entities with dozens of inputs, tabs, etc; +- see [docs](https://backpackforlaravel.com/docs/4.1/crud-operation-inline-create), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2311), [demo](https://demo.backpackforlaravel.com/admin/monster) + +![Backpack InlineCreate Operation](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/inline_create_small.gif) + +
    + +#### **New operation: ```Fetch```** + +- responds to AJAX requests and returns the results in a format the Select2 likes; +- this operation makes adding AJAX selects much MUCH easier; you don't even have to leave the controller; +- see [docs](/docs/4.1/crud-operation-fetch), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2308) + +![Backpack Fetch Operation](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/fetch.png) + +
    + +### Fields + +#### **New field type: ```relationship```** + +- **one relationship field to rule them all**; instead of thinking whether you need ```select2```, ```select2_multiple```, ```select2_from_ajax``` or ```select2_from_ajax_multiple``` you can now just use the ```relationship``` field, that includes the functionality of all of them... and more; +- **intelligent defaults**; it automatically figures out the ```entity```, ```model```, ```attribute```, ```multiple```, ```pivot``` and ```label```, so most of the time you'll just need to define two things for it to work - ```name``` and ```type```; +- **supports both non-AJAX and AJAX**; for the dropdown options, it defaults to immediate querying; if you want to load the options using AJAX, you just have to point it to the correct route, using ```data_source```; which is super-easy to do using the new Fetch operation detailed above; +- see [docs](/docs/4.1/crud-fields#relationship), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2311), [demo](https://demo.backpackforlaravel.com/admin/monster) + +![Backpack Relationship Field](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/relationship.png) + +
    + +#### **New field type: ```repeatable```** (aka ```matrix```, aka ```groups```, aka ```multiply```) + +- **lets your admin define one or multiple "_somethings_" right inside their create/update form**, when a "_something_" includes more than one input (ex: Testimonial can require ```name```, ```position```, ```company``` and ```quote```); +- the end result is stored as JSON by default, but you can intercept the saving and store it any way you like; +- makes it dead-simple to allow your admins to add/edit/delete multiple "things" inside an entry, when that "thing" is not important enough to have its own database table and Model; +- see [docs](/docs/4.1/crud-fields#repeatable), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2266) + +![Backpack InlineCreate Operation](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/repeatable_small.gif) + +
    + +#### **New field type: ```easymde```** (alternative to ```simplemde```) + +- SimpleMDE (the JS library) still works, but has not received any updates in 4 years; that's why we recommend using a different markdown editor - one that is maintained; +- fortunately, EasyMDE is a SimpleMDE fork that looks and works the same, and it is well looked after; +- we recommend replacing all your SimpleMDE fields with EasyMDE - the change couldn't be simpler; +- see [docs](/docs/4.1/crud-fields#easymde), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2737) + + +![Backpack InlineCreate Operation](https://backpackforlaravel.com/uploads/docs-4-1/fields/easymde.png) + +
    +### Columns + +#### **New columns feature: ```wrapper```** + +- allows you to **change how columns look and feel, by wrapping the text into a custom HTML element**; similarly to how we've been able to do for years with Fields, you can now specify a wrapper for columns, and change that wrapper's ```class```, ```style```, etc. +- you can now **easily add links to your columns**; make it easy for admins to jump from one CRUD to another, by making relationship columns point to that Model's CRUD; but they can also point to whatever else you want; +- you can now **easily make your column text a different color depending on its content**, just by adding a Bootstrap class (ex: add ```badge badge-warning``` or ```text-success```); +- see [docs](/docs/{{version}}/crud-columns#wrap-column-text-in-an-html-element), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2448), [demo](https://demo.backpackforlaravel.com/admin/article) + + +![Backpack Column Wrapper](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/column_wrapper_small.gif) + +
    + +#### **New column: ```relationship```** + +- the perfect companion to the new ```relationship``` field; +- **intelligent defaults**; it automatically figures out the ```entity```, ```model```, ```attribute```, ```multiple```, ```pivot``` and ```label```, so most of the time you'll just need to define two things for it to work - ```name``` and ```type```; +- see [docs](/docs/{{version}}/crud-columns#relationship), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2615), [demo](https://demo.backpackforlaravel.com/admin/monster) + + +
    + +#### **New column: ```relationship_count```** + +- shows the number of items related to the current entry, on that relationship, for example "413 items"; +- perfect for when your entity points to A LOT of other entries; +- if you have a CRUD with Filters for that related Model, you can add the `wrapper` attribute mentioned above to this column, to turn it into a link that points to a filtered view of that related CRUD; that way the admin can click this column and see those 413 items; +- see [docs](/docs/{{version}}/crud-columns#relationship_count), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2615), [demo](https://demo.backpackforlaravel.com/admin/monster) + + +
    + +### Widgets + +#### **New widget: ```chart```** + +- easily add a widget with a chart, on any admin panel page, directly from your Controller or blade file; +- the same/similar syntax for multiple chart libraries: ChartJS, Highcharts, Fusioncharts, Echarts, Frappe, C3, thanks to [Laravel Charts](https://charts.erik.cat/); easily switch between charting libraries; +- easily defer DB queries to an AJAX call (recommended) or make them upon page load; +- see [docs](/docs/4.1/base-widgets#chart), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2596), [demo](https://demo.backpackforlaravel.com/admin/) + + +![Backpack Chart Widget](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/chart_widget_small.gif) + +
    + +#### **New ```Widgets``` class** + +- a global object that allows you to more easily add/edit Widgets to your admin panel pages; +- you can still manipulate the ```$data['widgets']``` directly, but you can also use this new class; +- you can pass the entire widget definition array to ```Widget::add()``` or you can use the new fluent syntax; +- see [docs](/docs/4.1/base-widgets#widgets-api), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2599), [demo](https://demo.backpackforlaravel.com/admin/) + + +![Backpack Widget Class](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/widget_class.png) + +
    + +### One more thing + +#### **Alternative fluent syntax for fields, columns, filters, buttons** + +- An optional new way of interacting with Fields, Columns, Filters, Buttons; +- **Add or modify. It's the same method.** Create or modify a field/column/filter/button/widget with one call - think of ```CRUD::field()``` as a new ```$this->crud->addOrModifyField()```; +- **Reduce your CrudController length by 20-60%.** Most field&column definitions will fit on just one line. +- **Better syntax highlighting. Better auto-completion.** For most IDEs at least. +- **Intelligent defaults.** Most fields only actually need the `name` from you - Backpack will try to guess everything else. Even the `type`. Even the relationship. +- see [docs](/docs/4.1/crud-fluent-syntax), [PR](https://github.com/Laravel-Backpack/CRUD/pull/2513) + + +![Backpack Chart Widget](https://backpackforlaravel.com/uploads/docs-4-1/release_notes/before_after_maybe_fluent_syntax.png) + +
    + + + +## Changed + +- For the ```List Operation```, you can now easily: + - **hide/show the search bar** - see docs, [PR](https://github.com/Laravel-Backpack/CRUD/pull/2479); + - **hide/show a "Reset" button** that resets the DataTable to its default search, filtering, pagination - see docs, [PR](https://github.com/Laravel-Backpack/CRUD/pull/2509); +- You can now easily **customize the buttons at the end of the Create/Update forms** - see docs, [PR](https://github.com/Laravel-Backpack/CRUD/pull/2356); +- You can now make all Backpack routes **use a different web middleware** than ```web``` - see docs, [PR](https://github.com/Laravel-Backpack/CRUD/pull/2408); +- Field ```wrapperAttributes``` was renamed to ```wrapper```; + + + +## Removed + +- Support for Laravel 5.8; +- Support for PHP lower than 7.2.5; +- ```laravel/helpers``` dependency, but you can still install it yourself, if you want to use the array and string helpers; +- ```venturecraft/revisionable``` dependency; in order to use the Revisions operation you now have to install [the backpack/revise-operation add-on](https://github.com/laravel-backpack/revise-operation); +- ```barryvdh/laravel-elfinder``` dependency; in order to use the File Manager screen, the ```browse``` or ```browse_multiple``` field types, you now need to install the [backpack/filemanager add-on](https://github.com/Laravel-Backpack/FileManager) that we created; +- ```intervention/image``` dependency; in order to use the ```image``` field type you might need to [install the package](http://image.intervention.io/getting_started/installation), if you've copy-pasted our example mutator in your Model; +- ```App\Models\BackpackUser``` is no longer needed, recommended or published when installing Backpack; authentication now works with your default ```App\User``` model (or whatever it is); this eliminates a bit of unneeded complexity, and fixes a bunch of problems when there are morph relationships towards the User model; but you can still keep it if you like to have a separate model for you admins; + + +--- + +In order to get all of the features above (and a few more hidden gems), please [follow the upgrade guide](/docs/{{version}}/upgrade-guide), to get from Backpack 4.0 to Backpack 4.1. diff --git a/4.1/upgrade-guide.md b/4.1/upgrade-guide.md new file mode 100644 index 00000000..67bdb70e --- /dev/null +++ b/4.1/upgrade-guide.md @@ -0,0 +1,273 @@ +# Upgrade Guide + +--- + +This will guide you to upgrade from Backpack 4.0 to 4.1. The steps are color-coded by the probability that you will need it for your application: High, Medium and Low. + + +## 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 7.4.x, 7.3.x or 7.2.5+ +- Laravel 7.x or 6.x +- Backpack\CRUD 4.0.x +- 10-30 minutes for most projects, on top of the Laravel upgrade + +**If you're running Backpack version 3.x, please follow ALL the minor upgrade guides first, to incrementally get to use Backpack 4.0**. Test that your app works well with each version, after each upgrade. Only _afterwards_ can you follow this guide, to upgrade from 4.0 to 4.1. Previous upgrade guides: +- [upgrade from 3.6 to 4.0](https://backpackforlaravel.com/docs/4.0/upgrade-guide); this is a major upgrade, and requires a v4 license code; if you've purchased a Backpack v3 license, but don't have a Backpack v4 license yet, [read the 4.0 Release Notes here](/docs/4.0/release-notes#backpack-v3-buyers). +- [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 6](https://laravel.com/docs/6.x/upgrade). Then [upgrade to Laravel 7](https://laravel.com/docs/7.x/upgrade), if you can.** This is not _required_, but it's _recommended_. You need to do this _one day_ anyway - might as well do it now. Backpack 4.0 (that your project is already running) supports Laravel 5.8, 6 and 7. So you should: +- **upgrade your project to use Laravel v6 or v7** +- test your app is working fine with them +- continue with "Step 1" below, to also upgrade Backpack to 4.1 + +Our recommendation is to _not_ stick to Laravel 6.0 just because it's a [LTS version](https://en.wikipedia.org/wiki/Laravel#Release_history), but to upgrade to the latest Laravel. All upgrades after Laravel 5.6 have been very _very_ easy, and it's expected for this to continue, since Laravel has reached stability for some years now. If you're scared or lazy, for just $9 you can purchase a [Laravel Shift](https://laravelshift.com/), to automatically upgrade, and make sure you haven't missed a step. + + +### Composer + +Step 1. Update your ```composer.json``` file to require ```"backpack/crud": "4.1.*"``` + +Step 2. If you have a lot of Backpack add-ons installed (and their dependencies), here are their latest versions, that support Backpack 4.1, you can copy-paste the versions of the packages you're using: +``` + "backpack/logmanager": "^4.0.0", + "backpack/settings": "^3.0.0", + "backpack/pagemanager": "^3.0.0", + "backpack/menucrud": "^2.0.0", + "backpack/newscrud": "^4.0.0", + "backpack/permissionmanager": "^6.0.0", + "backpack/backupmanager": "^2.0.0", + "spatie/laravel-translatable": "^4.0", + "backpack/langfilemanager": "^3.0.0", + + /* and in require-dev */ + + "backpack/generators": "^3.0", + "laracasts/generators": "^1.0" +``` + +Step 3. If you're using the Revisions operation, it has now been split into a separate package. So please add ```"backpack/revise-operation": "^1.0",``` to your composer's require section. + +Step 4. Backpack itself is no longer using ```laravel/helpers```. Instead of using helpers like ```str_slug()``` we're now doing ```Str::slug()``` everywhere. We recommend you do the same. But if you want to keep using string and array helpers, please add ```"laravel/helpers": "^1.1",``` to your composer's require section. + +Step 5. Since we're no longer using ```laravel/helpers```, we need the ```Str``` and ```Arr``` classes to be aliased. You should have already done this when upgrading to Laravel 5.8/6.x/7.x. But please make sure that in your ```config/app.php``` you have these aliases: +```php + 'aliases' => [ + // ... + 'Arr' => Illuminate\Support\Arr::class, + // .. + 'Str' => Illuminate\Support\Str::class, + // .. + ], +``` + +Step 6. Backpack no longer installs the FileManager by default (elFinder), since most projects don't need it. If you did use the FileManager, or the ```browse``` or ```browse_multiple``` fields, make sure you require ```"backpack/filemanager": "^1.0",``` in your ```composer.json``` - we've separated it into an add-on. + +Step 7. Backpack no longer installs ```intervention/image``` by default. If you use the ```image``` field type and you've used our example Mutator in your Model, you might be using ```intervention/image``` - please check. If so, please add ```"intervention/image": "^2.3",``` to your ```composer.json```'s require section. + +Step 8. Run ```composer update``` in the command line. + + +### Models + +No changes needed. + + +### Routes + +No changes needed. + + +### Config + +Step 9. Backpack no longer provides, needs or uses the ```App\Models\BackpackUser``` model for authentication. New installs default to using ```App\User```. Since that model was only used for authentication & forgotten-password functionality, we recommend you: +- delete the ```App\Models\BackpackUser.php``` file; +- change your ```config/backpack/base.php``` file to: + +```diff +- 'user_model_fqn' => App\Models\BackpackUser::class, ++ 'user_model_fqn' => App\User::class, +``` +- if you've used PermissionManager, change your ```config/backpack/permissionmanager.php``` file to: + +```diff + 'models' => [ +- 'user' => App\Models\BackpackUser::class, ++ 'user' => App\User::class, + 'permission' => Backpack\PermissionManager\app\Models\Permission::class, + 'role' => Backpack\PermissionManager\app\Models\Role::class, + ], +``` + +- your ```App\User``` will need some changes for backpack to use it properly. It must use ```CrudTrait``` and ```HasRoles``` traits: + +```diff + **IMPORTANT:** If you use polymorphic relationships, you might have mentions of the ```App\Models\BackpackUser``` model in your BD. You will need to replace all those mentions in the DB with the standard ```App\User``` model. + +**If you've used ```PermissionManager``` or ```spatie/laravel-permission```, please note that you DO use polymorphic relationships.** All permissions and roles that pointed to ```App\Models\BackpackUser``` in the database need to be pointed to ```App\User``` model now: + +```bash +# ------------------- +# ONLY if you've used PermissionManager and/or spatie/laravel=permission +# ------------------- + +# Step 9.1. +# Create a database backup. Please. Pretty please. + +# Step 9.2. +# Publish the migration for the standard configuration. +# This should work as-is if you haven't changed table names. +php artisan vendor:publish --provider="Backpack\PermissionManager\PermissionManagerServiceProvider" --tag="migrations" + +# Step 9.3. +# Take a look at the migration and change whatever you need: +# - are the table names the same in the DB & in the migration? +# - are the column names the same? +# - are there entries in the DB with App\Models\BackpackUser? +# - did you notice there's no down() method for the migration? maybe you'll create a DB backup now, if you haven't? + +# Step 9.4. +# Run the migration. +php artisan migrate + +# Step 9.5. +# Take another look at your model_has_roles and model_has_permissions tables. +# Are there any more entries that point to App\Models\BackpackUser? +``` + +Another example of polymorphic relationships is the ```revisions``` table, if you've used it you may have references to ```App\Models\BackpackUser``` there too, please replace it with ```App\User```. + + +### CrudControllers + +The steps below should apply for each of your CrudControllers. For each Step, go through every one of your CrudControllers (usually stored in ```app\Http\Controllers\Admin```: + +Step 10. If you're using the ```RevisionsOperation``` inside your CrudControllers, that operation has been moved to a separate package (that you've already installed in steps 3 & 8). Now you need to: +- inside your CrudControllers, search for ```Backpack\CRUD\app\Http\Controllers\Operations\RevisionsOperation``` and replace with ```Backpack\ReviseOperation\ReviseOperation``` +- if you've customized the Revisions operation, take note that the specialty methods to set a Revisions view no longer exist; inside your CrudController, please use the normal ```get()```/```set()```, but please note that the operation is now called "_revise_" (verb) not "_revisions_" (noun), so your changes should be: + - from ```$this->crud->setRevisionsView()``` to ```$this->crud->set('revise.view')``` + - from ```$this->crud->getRevisionsView()``` to ```$this->crud->get('revise.view')``` + - from ```$this->crud->setRevisionsTimelineView()``` to ```$this->crud->set('revise.timelineView')``` + - from ```$this->crud->getRevisionsTimelineView()``` to ```$this->crud->set('revise.timelineView')``` + - from ```$this->crud->setRevisionsTimelineContentClass()``` to ```$this->crud->set('revise.timelineContentClass')``` + - from ```$this->crud->getRevisionsTimelineContentClass()``` to ```$this->crud->set('revise.timelineContentClass')``` +- if you've defined settings for the "_revisions_" operation for all CRUDs, inside your ```config/backpack/crud.php```, please note that the operation name has changed, so inside ```operations``` you should change ```revisions``` to ```revise```; + +Step 11. Inside CrudControllers, Backpack 4.1: +- no longer defines ```$this->request``` +- still defines ```$this->crud->request``` (aka ```CRUD::request```) but uses getters and setters to work with it (```$this->crud->getRequest()``` and ```$this->crud->setRequest()```); + +Why? Since ```$this->request``` did nothing at all, we've removed it, to avoid any confusion between working with ```$this->request``` and ```$this->crud->request```. Make sure that: +- **If you have ```$this->request``` anywhere in your CrudControllers custom logic**, please replace it with either Laravel's ```request()``` helper or with ```$this->crud->getRequest()```. +- **If you have ```$this->crud->request``` anywhere inside your custom CrudController logic**, please replace it with either ```$this->crud->getRequest()``` or ```$this->crud->setRequest()``` depending on what your intention is. + +Step 12. Inside CrudControllers, if you've used ```wrapperAttributes``` on fields, please note that it's now called ```wrapper```. Please search & replace ```wrapperAttributes``` with ```wrapper``` in your CrudControllers. + +Additionally, for the `select2_from_ajax` and `select2_from_ajax_multiple` field types, the inputs on the main form are no longer sent through the AJAX request by default. If you're using [dependant selects](https://backpackforlaravel.com/docs/4.1/crud-how-to#add-a-select2-field-that-depends-on-another-field), please make sure that you add `'include_all_form_fields' => true` to your `select2_from_ajax` and `select2_from_ajax_multiple` field definition to keep the same behaviour as before. + + +### CSS & JS Assets + +Step 13. We've updated most CSS & JS dependencies to their latest versions. There are two ways to publish the latest styles and scripts for these dependencies: +- (A) If you have NOT touched you ```public/packages``` folder, or placed anything custom inside it: + - delete the ```public/packages``` directory and all its contents; + - run ```php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public``` + - if you use elFinder, also delete ```resources/views/vendor/elfinder``` and run ```php artisan backpack:filemanager:install``` +- (B) Run ```php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force```. Please note this will overwrite anything that's already there. This B solution has a downside: unused files are not removed. A few files Backpack no longer uses will still be in your ```public/packages``` folder, even though they're no longer used. + + + + +### Views + + +Step 14. **If you've overwritten default Fields, or have custom Fields**, take note that ALL of them have suffered changes (for the better); as a minimum, if you have any files in ```resources/views/vendor/backpack/crud/fields``` you should: +- find & replace ```crud::inc.field_attributes``` with ```crud::fields.inc.attributes``` +- find & replace ```crud::inc.field_translatable_icon``` with ```crud::fields.inc.translatable_icon``` +- change the wrapping element; take a look at the diff below or at [the diff for the text field](https://github.com/Laravel-Backpack/CRUD/pull/2601/files#diff-9b83997dcde20848b90e97048aca5485), and do the same for all the fields you've created or overwritten: + +```diff +-
    ++ @include('crud::fields.inc.wrapper_start') + +// Your actual field HTML + +-
    ++ @include('crud::fields.inc.wrapper_end') +``` + + +Step 15. **If you've overwritten Columns, or have custom Columns**, take note that ALL columns have suffered changes (for the better). Most notably: +- ```wrapper``` allows you to add links to your columns; +- ```escaped``` allows you to output HTML instead of text; + +If you want your custom/overwritten columns to have these cool new features, they're very easy to implement: +- [take a look here](https://github.com/Laravel-Backpack/CRUD/pull/2508/files#diff-e82fc7ec56e0617fb83762cc2a9467d4) for a simple column (ex: ```text```); +- [take a look here](https://github.com/Laravel-Backpack/CRUD/pull/2508/files#diff-ab965b3cd4720c4c2ec4a2423f3ea648) for a column that has multiple entries (ex: ```select```); + + +If you need a difftool, we recommend [Kaleidoscope](https://www.kaleidoscopeapp.com) on Mac OS, [WinMerge](https://winmerge.org) on Windows, or [Fork](https://git-fork.com/) on both Mac OS and Windows. + + +Step 16. Backpack 4.1 uses the same icon set, [Line Awesome](https://icons8.com/line-awesome), but we've upgraded to the latest version. +- The good news - this new version brings a total of 1000+ icons, and complete icon-parity with Font Awesome 5.11.2. Any icon Font Awesome 5 has, Line Awesome has it too. +- The bad news: + - it's not 100% backwards-compatible. The same icons are there, but a few of them have changed names. For example, there's no more ```newspaper-o``` it's now just ```newspaper```; + - additionally, we're no longer using the "_font awesome compatible syntax_" to load Line Awesome Icons (```fa fa-home```), we're now using the "_line awesome syntax_" (```la la-home```), to prevent conflicts when both fonts are used at the same time; + +In order to account for this icon font change please: +- inside your ```resources/views/vendor/backpack``` folder, search & replace ```fa fa-``` with ```la la-```; +- for each icon you fix above, double-check that it shows in the browser; if it doesn't, search for an alternative on the [Line Awesome website](https://icons8.com/line-awesome); usually the syntax change is very _very_ small; and there are a lot more icons now, so you'll definitely find something; + +Step 17. In order to be able to use the new fluent syntax for Widgets, you should make sure all main admin panel views (ex: dashboard, create, update, etc) extend the ```blank``` template: + +```diff +//from +- @extends('backpack::layouts.top_left') +- @extends(backpack_view('layouts.top_left')) + +// to ++ @extends(backpack_view('blank')) +// or ++ @extends('backpack::blank') + +``` + + +### Cache + +Step 18. Clear your app's cache: +``` +php artisan config:clear +php artisan cache:clear +php artisan view:clear +``` + +--- + +**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 4.1](/docs/4.1/release-notes); diff --git a/5.x/add-ons-community.md b/5.x/add-ons-community.md new file mode 100644 index 00000000..c0cde2e2 --- /dev/null +++ b/5.x/add-ons-community.md @@ -0,0 +1,19 @@ +# 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 | +| ------------- |:-------------:| --------:| +| [BackpackBlog](https://github.com/AbbyJanke/BackpackBlog) | blog front-end and back-end | - | +| [BackpackMeta](https://github.com/AbbyJanke/BackpackMeta) | helps create meta options for extending core functions | - | +| [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/5.x/add-ons-custom-operation.md b/5.x/add-ons-custom-operation.md new file mode 100644 index 00000000..fa19ab02 --- /dev/null +++ b/5.x/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/5.x/add-ons-how-to-create-a-backpack-addon.md b/5.x/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..41a8e7d7 --- /dev/null +++ b/5.x/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/5.x/add-ons-official.md b/5.x/add-ons-official.md new file mode 100644 index 00000000..6d892966 --- /dev/null +++ b/5.x/add-ons-official.md @@ -0,0 +1,22 @@ +# 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 5 more operations, 10 filters, 28 more fields, 6 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 + + +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 + - [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 + + +>**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/5.x/add-ons-tutorial-using-the-addon-skeleton.md b/5.x/add-ons-tutorial-using-the-addon-skeleton.md new file mode 100644 index 00000000..06e09c15 --- /dev/null +++ b/5.x/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/5.x/add-ons-tutorial.md b/5.x/add-ons-tutorial.md new file mode 100644 index 00000000..01da4d66 --- /dev/null +++ b/5.x/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/5.x/base-about.md b/5.x/base-about.md new file mode 100644 index 00000000..1cc67367 --- /dev/null +++ b/5.x/base-about.md @@ -0,0 +1,219 @@ +# About Backpack's User Interface + +--- + +Backpack helps you build admin panels faster by: +- installing our custom HTML theme, [Backstrap](https://backstrap.net), based on Bootstrap 4 and [CoreUI](https://coreui.io); +- installing [sweetalert](https://sweetalert.js.org/) for triggering pretty Confirm modals; +- installing [noty](https://ned.im/noty/#/) to show notification bubbles upon error/success/warning/info - triggered from JavaScript; +- installing [prologue/alerts](https://github.com/prologuephp/alerts) for triggering notification bubbles from PHP (both on the same page and using flashdata); +- providing a separate authentication system for your admins; +- providing pretty error pages for most common errors; +- providing a horizontal menu and a side menu you can customize; +- providing a place for your admin to to change his email/name/password; +- providing a few helpers you can use throughout your admin panel; + +For the simplest projects, you will never need to know how it works, never need to customize anything but the ```config/backpack/base.php``` file. But here's how everything works, below. + + +## Layout & Design + + +### General + +Backpack pulls in our custom HTML template, [Backstrap](https://www.npmjs.com/package/@digitallyhappy/backstrap), and adds our own CSS file on top, for a few cosmetic improvements. We've chosen to base Backstrap on [CoreUI](https://coreui.io), because it provides design blocks for all common features of an administration panel. When you decide to build custom pages for your Admin Panel, you can just use Backstrap's HTML blocks - no designer needed. You can see all the HTML components Backstrap provides on [backstrap.net](https://backstrap.net), and copy-paste HTML from there, or use [CoreUI](https://coreui.io)'s documentation for details. + + +### New Files in Your App + +After installation, you'll notice Backpack has added a few files: + +**1) View to ```resources/views/vendor/backpack/base/inc/sidebar_content.blade.php```** + +This file is used to show the contents of the menu to the left (sidebar). 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/base/```: + - ```inc/sidebar_content.blade.php```; + +That file is used to show the contents of the menu to the left (sidebar). 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, then go to ```resources/views/vendor/backpack/base``` and create a file with the exact same name. Backpack\Base will use this new file, instead of the one in the package. + +For example: +- if you want to add an item to the top menu, you could just create a file called ```resources/views/vendor/backpack/base/inc/topbar_left_content.blade.php```; Backpack will now use this file's contents, instead of ```vendor/backpack/base/src/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/base/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 base/dashboard +``` + +Then inside the blade files, you can use either plain-old HTML or add dynamic content through [Backpack widgets](/docs/{{version}}/base-widgets). + + +### Folder Structure + +If you'll take a look inside any Backpack package, you'll notice the ```src``` directory is 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\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\User``` or you've changed its location, please, you can tell Backpack to use _a different_ model in ```config/backpack/base.php``` instead of the ```App\User``` model that Laravel apps usually have. 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 his/her name to go to his "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\Base\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\Base 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/base/my_account.blade.php``` that uses code from the same file in the Backpack package, 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\Base\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\Base 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(http://github.com/$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; + +### 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/5.x/base-alerts.md b/5.x/base-alerts.md new file mode 100644 index 00000000..d2c91701 --- /dev/null +++ b/5.x/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/5.x/base-breadcrumbs.md b/5.x/base-breadcrumbs.md new file mode 100644 index 00000000..a3a7d5ad --- /dev/null +++ b/5.x/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/base.php```: + +```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('http://github.com/dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('http://github.com/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('http://github.com/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('http://github.com/dashboard'), + 'Dashboard' => false, + ]; +@endphp + +@section('content') + some other content here +@endsection +``` \ No newline at end of file diff --git a/5.x/base-how-to.md b/5.x/base-how-to.md new file mode 100644 index 00000000..43003379 --- /dev/null +++ b/5.x/base-how-to.md @@ -0,0 +1,638 @@ +# FAQs for the admin UI + +--- + + + +## Look and feel + + +### Customize the menu or sidebar + +During installation, Backpack publishes a few files in you ```resources/views/vendor/backpack/base/inc``` folder. In there, you'll also find: +- ```sidebar_content.php``` +- ```topbar_left_content.php``` +- ```topbar_right_content.php``` + +Change those files as you please. + + +### 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/base/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('http://github.com/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 + +In ```config/backpack/base.php``` you'll notice there are variables where you can change exactly what CSS classes are placed on the HTML elements that represent the header, body, sidebar and footer: + +```php + // Horizontal navbar classes. Helps make the admin panel look similar to your project's design. + 'header_class' => 'app-header bg-light border-0 navbar', + // Try adding bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + // You might need to add "navbar-dark" too if the background color is a dark one. + // Add header-fixed if you want the header menu to be sticky + + // Body element classes. + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + // Try sidebar-hidden, sidebar-fixed, sidebar-compact, sidebar-lg-show + + + // Sidebar element classes. + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + // Remove "sidebar-transparent" for standard sidebar look + // Try "sidebar-light" or "sidebar-dark" for dark/light links + // You can also add a background class like bg-dark, bg-primary, bg-secondary, bg-danger, bg-warning, bg-success, bg-info, bg-blue, bg-light-blue, bg-indigo, bg-purple, bg-pink, bg-red, bg-orange, bg-yellow, bg-green, bg-teal, bg-cyan + + // Footer element classes. + 'footer_class' => 'app-footer', +``` + +Our default design might not be pleasant for your, or you might need to make the UI integrate better into your project. We totally understand. You can use the classes above to make it look considerably different. + +You'll find a few examples below - but you should use which classes you want to get the result you need. + + +#### Backstrap + +Transparent top menu, transparent sidebar, transparent footer. This is the default. This is what _we_ think is best for most users, from our 8+ years of experience building admin panels. Prioritising _content_ over _menus_. + +![Backstrap design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/default.png) + +```php + 'header_class' => 'app-header bg-light border-0 navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer', +``` + + +#### Inspired by CoreUI + +White top menu, dark sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/coreui.png) + +```php + 'header_class' => 'app-header navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', + 'footer_class' => 'app-footer d-none', +``` + + +#### Inspired by GitHub + +Black top menu, white sidebar. + +![CoreUI design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/github.png) + +```php + 'header_class' => 'app-header bg-dark navbar', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar bg-white sidebar-pills', + 'footer_class' => 'app-footer d-none', +``` + + +#### Blue Top Menu + +Blue top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/blue.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-primary border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + + +#### Construction / Warning + +Yellow top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/construction.png) + +```php + 'header_class' => 'app-header navbar navbar-light bg-warning', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Red Top Menu + +Red top menu, dark sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/red.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar', // add "bg-white sidebar-pills" for light sidebar + 'footer_class' => 'app-footer d-none', +``` + + +#### Pink Top Menu + +Pink top menu, transparent sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/pink.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-error border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-light', + 'footer_class' => 'app-footer d-none', +``` + + + +#### Green Top Menu + +Green top menu, white sidebar. + +![Construction or warning design](https://backpackforlaravel.com/uploads/docs-4-0/ui/examples/green.png) + +```php + 'header_class' => 'app-header navbar navbar-color bg-green border-0', + 'body_class' => 'app aside-menu-fixed sidebar-lg-show', + 'sidebar_class' => 'sidebar sidebar-pills bg-white', + 'footer_class' => 'app-footer d-none', +``` + + + +### Change the colors + +#### Change primary color from purple to blue + +We thought you might want to do this. Purple isn't for everybody. That's why Backpack 4.1.57+ comes with two bundle CSS files: `bundle.css` and `blue-bundle.css`. That second file does exactly what you expect - changes the primary color from electric purple to blue. That's it. In order to use it, go to your `config/backpack/base.php` and use that file instead: + +```diff +'styles' => [ +- 'packages/backpack/base/css/bundle.css', ++ 'packages/backpack/base/css/blue-bundle.css', +``` + +That's it. Now go to your browser and refresh, you should see blue buttons and text everywhere, instead of purple. + +#### Custom colors for primary, secondary, success, warning etc. + +This assumes you have: +- Backpack 4.1.57+ (check with `php artisan backpack:version`) and its assets published (otherwise run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force`); +- a working Laravel install with NPM and Laravel Mix; + +Add Backstrap, Noty and Animate.css as dev-dependencies to your project: +``` +npm install --dev +npm i @digitallyhappy/backstrap --save-dev +npm i noty --save-dev +npm i animate.css@3.7.2 --save-dev +``` + +Then create a SCSS file for that custom bundle you want. We recommend doing it in `resources/scss/custom-backpack-bundle.scss`. Then customize this file to your liking: + +```scss +// create a bundle CSS file for the an alternative Backstrap style (blue instead of purple for primary color) + +@import "http://github.com/node_modules/@digitallyhappy/backstrap/src/scss/_backstrap_colors"; + +$primary: $black !default; // <--- For eg. This will make all buttons and texts black instead of purple +$secondary: $gray-300 !default; +$success: $green !default; +$info: $blue !default; +$warning: $yellow !default; +$danger: $red !default; +$light: $gray-200 !default; +$dark: $black !default; + +$hover-color: rgba(105, 171, 239, 0.12); +$border-color: rgba(0, 40, 100, 0.12); +$muted-bg-color: rgba(0, 0, 0, 0.02); + +$theme-colors: () !default; +$theme-colors: map-merge( + ( + "primary": $primary, + "default": $secondary, + "secondary": $secondary, + "success": $success, + "info": $info, + "notice": $info, + "warning": $warning, + "danger": $danger, + "error": $danger, + "light": $light, + "dark": $dark + ), + $theme-colors +); + +@import "http://github.com/node_modules/@digitallyhappy/backstrap/src/scss/_backstrap_miscellaneous"; + +@import "http://github.com/node_modules/@coreui/coreui/scss/coreui"; +@import "http://github.com/node_modules/@digitallyhappy/backstrap/src/scss/_custom"; +@import "http://github.com/node_modules/@digitallyhappy/backstrap/src/scss/_noty"; +@import "http://github.com/node_modules/animate.css/source/_base"; +@import "http://github.com/node_modules/noty/src/noty"; +``` + +Now add the command to your `webpack.mix.js` file to compile this new SASS file: + +```js +// create a custom Backpack bundle CSS, with custom colors +mix.sass('resources/scss/custom-backpack-bundle.scss', 'public/css/') + .options({ + processCssUrls: false + }); +``` + +And run `npm run dev`. If SASS is not a dev-requirement in your project already, Mix will add it automatically and ask you to run `npm run dev` again. Do that if necessary. + +Your result should say "_webpack compiled successfully_". If so, you can now use that new bundle file in all your Backpack pages by going to `config/backpack/base.php` and changing the main bundle CSS file that Backpack uses... with your own: + +```diff + 'styles' => [ +- 'packages/backpack/base/css/bundle.css', ++ 'css/custom-backpack-bundle.css', +``` + + + +### 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/base.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) +- use a different CSS framework (ex: Tailwind, Bulma) + + + +### Add custom JavaScript to all admin panel pages + +In ```config/backpack/base.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, Noty, 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/base.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/base.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/base.php``` file: + +```php + + // JS files that are loaded in all pages, using Laravel's asset() helper + 'scripts' => [ + // Backstrap includes jQuery, Bootstrap, CoreUI, Noty, 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 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 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. + + + +### 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')->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() + { + + return backpack_view('auth.register'); + } +``` +This will make the registration process pick up a view you can create, in ```resources/views/vendor/backpack/base/auth/register.blade.php```. You can copy-paste the original view, and modify as you please. Including adding your own custom inputs. + diff --git a/5.x/base-widgets.md b/5.x/base-widgets.md new file mode 100644 index 00000000..f665bcc3 --- /dev/null +++ b/5.x/base-widgets.md @@ -0,0 +1,620 @@ +# 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('http://github.com/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 in the div attributes of your choice. For example, you can include multiple widgets in 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. + +
    + + +### Jumbotron + +Shows a Bootstrap jumbotron component, with the heading and body you specify. + +```php +[ + 'type' => 'jumbotron', + 'heading' => 'Welcome!', + 'content' => 'Use the sidebar to the left to create, edit or delete content.', + 'button_link' => backpack_url('http://github.com/logout'), + 'button_text' => 'Logout', +] +``` + +Widget Preview: + +![Backpack card widget](https://backpackforlaravel.com/uploads/v4/widgets/jumbotron.png) + +
    + + +### 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/base/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/5.x/crud-api.md b/5.x/crud-api.md new file mode 100644 index 00000000..04857b66 --- /dev/null +++ b/5.x/crud-api.md @@ -0,0 +1,530 @@ +# 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(); +``` + + +### 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']); +``` + +- **denyAccess()** - prevent users from accessing one or multiple operations +```php +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); +``` + +- **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/5.x/crud-basics.md b/5.x/crud-basics.md new file mode 100644 index 00000000..cd734260 --- /dev/null +++ b/5.x/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 **controller** - holds the logic for the all operations an admin can perform on that Eloquent model; will be generated in ```app/Http/Controllers/Admin```; +- a **request** file - used to validate Create and Update forms; will be generated in ```app/Http/Requests```; +- a resource **route** - points to the controller above; will be generated in ```routes/backpack/custom.php```; + +**The only difference** between building it from scratch and using Backpack\CRUD** is that: +- your controller will be extending ```Backpack\CRUD\app\Http\Controllers\CrudController```**, which allow you to easily add traits that handle the most common operations: Create, Update, Delete, List, Show, Reorder, Revisions. +- 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_ overwrite what happens inside each operation**; +- **you can _easily_ add 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 CrudController; 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; + + +## Files + +For a ```Tag``` entity, your CRUD Panel would consist of: +- a controller (```app/Http/Controllers/Admin/TagCrudController.php```); +- a request (```app/Http/Requests/TagCrudRequest.php```); +- a route inside ```routes/backpack/custom.php```; +- your existing model (```app/Models/Tag.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/5.x/crud-buttons.md b/5.x/crud-buttons.md new file mode 100644 index 00000000..6e669bd9 --- /dev/null +++ b/5.x/crud-buttons.md @@ -0,0 +1,237 @@ +# Buttons + +--- + + +## About + +Buttons are used inside the ListEdit operation, to allow the admin to trigger other operations. Some point to entirely new routes (```create```, ```update```, ```show```), others perform the operation on the current page using AJAX (```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) + +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 + +Backpack adds a few buttons by default: +- ```create``` to the ```top``` stack; +- ```update``` and ```delete``` to the ```line``` stack; + +Default buttons are invisible if an operation has been disabled. For example, you can: +- hide the "delete" button using ```$this->crud->denyAccess('delete')```; +- show a "preview" button by using ```$this->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 +$this->crud->buttons(); + +// add a button; possible types are: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); + +// add a button whose HTML is returned by a method in the CRUD model +$this->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 +$this->crud->addButtonFromView($stack, $name, $view, $position); + +// remove a button +$this->crud->removeButton($name); + +// remove a button for a certain stack +$this->crud->removeButtonFromStack($name, $stack); + +// remove multiple buttons +$this->crud->removeButtons($names, $stack); + +// remove all buttons +$this->crud->removeAllButtons(); + +// remove all buttons for a certain stack +$this->crud->removeAllButtonsFromStack($stack); + +// order buttons in a stack, order is an array with the ordered names of the buttons +$this->crud->orderButtons($stack, $order); + +// modify button, modifications are the attributes and their new values. +$this->crud->modifyButton($name, $modifications); + +// Move the target button to the destination position, target and destion are the button names, where is 'before' or 'after' +$this->crud->moveButton($target, $where, $destination); +``` + + +### Overwriting a Default Button + +Before showing any buttons, Backpack will check your ```resources\views\vendor\backpack\crud\buttons``` directory, to see if you've overwritten any default buttons. If it finds a blade file with the same name there as the default buttons, it will use your blade file, instead of the default. + +That means **you can overwrite an existing button simply by creating a blade file with the same name inside this directory**. + + +### Creating a Custom Button + +To create a 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 above, in the EntityCrudControllers you want, inside the ```setupListOperation()``` method; + +In this 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; + +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')) + 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 +$this->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 +$this->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 +$this->crud->addButtonFromView('top', 'import', 'import', 'end'); +``` + +### 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/5.x/crud-cheat-sheet.md b/5.x/crud-cheat-sheet.md new file mode 100644 index 00000000..8ce3d0bd --- /dev/null +++ b/5.x/crud-cheat-sheet.md @@ -0,0 +1,344 @@ +# 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 + + +### ListEntries + + +#### Columns + +Methods: addColumn(), addColumns(), modifyColumn(), removeColumn(), removeColumns(), setColumnDetails(), setColumnsDetails(), setColumns(), beforeColumn(), afterColumn(), makeFirstColumn() + +```php +// Manipulate what columns are shown in the table view. +$this->crud->addColumn($column_definition_array); // add a column, at the end of the stack +$this->crud->addColumns([$column_definition_array, $another_column_definition_array]); // add multiple columns, at the end of the stack +$this->crud->modifyColumn($name, $modifs_array); +$this->crud->removeColumn('column_name'); // remove a column from the stack +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the stack +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns(); // set the columns you want in the table view, either as array of column names, or multidimensional array with all columns detailed with their types + +// ------ REORDER COLUMNS +$this->crud->addColumn()->beforeColumn('name'); // will show this before the given column +$this->crud->addColumn()->afterColumn('name'); // will show this after the given column + +$this->crud->addColumn()->makeFirstColumn(); + // will make this column the first one in the list + // you need to also specify priority 1 in your addColumn statement for details_row or responsive expand buttons to show +``` + + +#### Buttons + +Methods: 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; +$this->crud->buttons(); // collection of all buttons +$this->crud->addButton($stack, $name, $type, $content, $position); // possible types are: 'view', 'model_function' +$this->crud->addButtonFromModelFunction($stack, $name, $model_function_name, $position); // add a button whose HTML is returned by a method in the CRUD model +$this->crud->addButtonFromView($stack, $name, $view, $position); // add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +$this->crud->removeButtons($names, $stack); +$this->crud->removeAllButtons(); +$this->crud->removeAllButtonsFromStack($stack); +$this->crud->orderButtons($stack, $order); // order is an array with button names in the new order +$this->crud->modifyButton($name, $modifications); // modifications are the attributes and their new values +$this->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' +``` + + +#### Filters + +Methods: addFilter(), modifyFilter(), removeFilter(), removeAllFilters(), filters() + +```php +// Manipulate what filters are shown in the table view. +// +// Note: check out CRUD > Features > Filters in the docs to see examples of $filter_definition_array +$this->crud->addFilter($filter_definition_array, $values, $filter_logic); +$this->crud->modifyFilter($name, $modifs_array); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +$this->crud->filters(); // gets all the filters +``` + + +#### 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. +$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->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. +$this->crud->enableExportButtons(); +``` + + +#### Responsive Table + +Methods: enableResponsiveTable(), disableResponsiveTable() + +```php +$this->crud->disableResponsiveTable(); +$this->crud->enableResponsiveTable(); +``` + + +#### Persistent Table + +Methods: enablePersistentTable(), disablePersistentTable() + +```php +$this->crud->disablePersistentTable(); +$this->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. +$this->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: +$this->crud->setPageLengthMenu([[100, 200, 300], ['one hundred', 'two hundred', 'three hundred']]); + +// values and labels in one array: +$this->crud->setPageLengthMenu([100 => 'one hundred', 200 => 'two hundred', 300 => 'three hundred']); + +// only values, we will use the values as labels: +$this->crud->setPageLengthMenu([100, 200, 300]); // OR +$this->crud->setPageLengthMenu([[100, 200, 300]]); + +// only one option available: +$this->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 +$this->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. +$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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->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: addField(), addFields(), modifyField(), modifyFields(), removeField(), removeFields(), removeAllFields(), beforeField(), afterField() + +```php +// ------ +// FIELDS +// ------ +// Manipulate what fields are shown in the create / update forms. +// +// Note: check out CRUD > Features > Field Types in the docs to see examples of $field_definition_array + +$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 +$this->crud->addFields($array_of_fields_definition_arrays); +$this->crud->modifyField($name, $modifs_array); +$this->crud->removeField('name'); +$this->crud->removeFields($array_of_names); +$this->crud->removeAllFields(); + +// ------ REORDER FIELDS +$this->crud->addField()->beforeField('name'); // will show this before the given field +$this->crud->addField()->afterField('name'); // will show this after the given field +``` + + +### Reorder + +Methods: enableReorder(), disableReorder(), isReorderEnabled() + +```php + protected function setupReorderOperation() + { + // model attribute to be shown on draggable items + $this->crud->set('reorder.label', 'name'); + // maximum number of nesting allowed + $this->crud->set('reorder.max_level', 2); + + // extras: + // $this->crud->disableReorder(); + // $this->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 +$this->crud->allowAccess('revisions'); +``` + + +## All Operations + +Methods: allowAccess(), denyAccess(), hasAccess(), 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. + +$this->crud->allowAccess('list'); +$this->crud->allowAccess(['list', 'create', 'delete']); +$this->crud->denyAccess('list'); +$this->crud->denyAccess(['list', 'create', 'delete']); + +$this->crud->hasAccess('add'); // returns true/false +$this->crud->hasAccessOrFail('add'); // throws 403 error +$this->crud->hasAccessToAll(['create', 'update']); // returns true/false +$this->crud->hasAccessToAny(['create', 'update']); // returns true/false + +// ------------- +// EAGER LOADING +// ------------- + +// eager load a relationship +$this->crud->with('relationship_name'); + +// ------------ +// CUSTOM VIEWS +// ------------ + +// use a custom view for a CRUD operation +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->crud->setRevisionsView('your-view'); +$this->crud->setRevisionsTimelineView('your-view'); +$this->crud->setDetailsRowView('your-view'); + +// ------------- +// CONTENT CLASS +// ------------- + +// use a custom CSS class for the content of a CRUD operation +$this->crud->setShowContentClass('col-md-12'); +$this->crud->setEditContentClass('col-md-12'); +$this->crud->setCreateContentClass('col-md-12'); +$this->crud->setListContentClass('col-md-12'); +$this->crud->setReorderContentClass('col-md-12'); +$this->crud->setRevisionsContentClass('col-md-12'); +$this->crud->setRevisionsTimelineContentClass('col-md-12'); + +// ------- +// GETTERS +// ------- + +$this->crud->getEntry($entry_id); +$this->crud->getEntries(); + +$this->crud->getFields('create/update/both'); + +// in your update() method, after calling parent::updateCrud() +$this->crud->getCurrentEntry(); + +// ------- +// OPERATIONS +// ------- + +$this->crud->setOperation('list'); +$this->crud->getOperation(); + +// ------- +// ACTIONS +// ------- + +$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 + +$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 + +$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 + +// --------------------------- +// CrudPanel Basic Information +// --------------------------- +$this->crud->setModel("App\Models\Example"); +$this->crud->setRoute("admin/example"); +// OR $this->crud->setRouteName("admin.example"); +$this->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 +$this->crud->setRequiredFields(StoreRequest::class, 'create'); +$this->crud->setRequiredFields(UpdateRequest::class, 'edit'); +``` diff --git a/5.x/crud-columns.md b/5.x/crud-columns.md new file mode 100644 index 00000000..0d72296b --- /dev/null +++ b/5.x/crud-columns.md @@ -0,0 +1,1020 @@ +# 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]); + +// 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(); +``` + +
    + + +### 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. + +
    + + +### 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/base.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/base.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' +], +``` + +
    + + +### 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 +], +``` + +
    + + +### 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 +], +``` + +
    + + +### 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; + +
    + + +### 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 +], +``` + +
    + +### 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_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 +], +``` + +
    + + +### 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 +], +``` + + +### upload_multiple + + +The ```table``` 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 +], +``` + +
    + + +### 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 +], +``` + +
    + + + +## PRO Column Types + + +### 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" +], +``` + +
    + + +### 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. + +
    + + +### 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' + ] +], +``` + +
    + + +### 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', +], +``` + +
    + + +## 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. + +For example, you can wrap the text in an anchor element, to point to that Article's Show operation: + +```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 + '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('http://github.com/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->addColumn([ + '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'; + }, + ], +]); +``` + + + +### 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. diff --git a/5.x/crud-fields-javascript-api.md b/5.x/crud-fields-javascript-api.md new file mode 100644 index 00000000..fd7be1e5 --- /dev/null +++ b/5.x/crud-fields-javascript-api.md @@ -0,0 +1,293 @@ +# 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; + +The beauty of this solution is that... it's flexibility. 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 +``` diff --git a/5.x/crud-fields.md b/5.x/crud-fields.md new file mode 100644 index 00000000..648053a7 --- /dev/null +++ b/5.x/crud-fields.md @@ -0,0 +1,2715 @@ +# 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: Starting with Backpack 4.1, 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 +// add a field +$this->crud->addField($field_definition_array); + +// shorthand: add a text field +$this->crud->addField('db_column_name'); + +// add multiple fields +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); + +// change the attributes of a field +$this->crud->modifyField($name, $modifs_array); + +// remove a field from both operations +$this->crud->removeField('name'); + +// remove multiple fields from both operations +$this->crud->removeFields($array_of_names); + +// remove all fields from all operations +$this->crud->removeAllFields(); + +// FIELD ORDER + +// add a field before a given field +$this->crud->addField($field_definition_array)->beforeField('name'); + +// add a field after a given field +$this->crud->addField($field_definition_array)->afterField('name'); + + +// ------------------- +// New in Backpack 4.1 +// ------------------- +// add a field with this name +$this->crud->field('price'); + +// change the type attribute on the 'price' field +$this->crud->field('price')->type('number'); +``` + + +### 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); + +Every other field attribute other than `name`, Backpack 4.1+ will try to guess. + + +#### 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 +[ + '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 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 - 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 +[ + '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 +[ + 'name' => 'meta_title', + 'label' => "Meta Title", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + 'name' => 'meta_description', + 'label' => "Meta Description", + 'fake' => true, + 'store_in' => 'metas' // [optional] +], +[ + '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. + +If you need to perform any manipulation of the data before storing the fake fields, you should define a mutator for the column used to store the fake attributes, by default it's `extras`. It can be any other column you define in `store_in`. +```php +// in YourModel.php +public function setExtrasAttribute($value) +{ + // Your code +} + +#### 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) + +In order to use this feature, you just need to specify the tab name for each of your fields. Example: + +```php +// select_from_array +$this->crud->addField([ + 'name' => 'select_from_array', + 'label' => "Select from array", + 'type' => 'select_from_array', + 'options' => ['one' => 'One', 'two' => 'Two', 'three' => 'Three'], + 'allows_null' => false, + 'allows_multiple' => true, + 'tab' => 'Tab name here', +]); +``` + +If you forget to specify a tab name for a field, Backpack will place it above all tabs. + + + +#### 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 4.1+ 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/9.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 +[ // 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 +[ // Checklist + 'label' => 'Roles', + 'type' => 'checklist', + 'name' => 'roles', + 'entity' => 'roles', + 'attribute' => 'name', + 'model' => "Backpack\PermissionManager\app\Models\Role", + 'pivot' => true, + // '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 +[ // 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 + ], + '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 + ], + ], +], +``` + +Input preview: + +![CRUD Field - checklist_dependency](https://backpackforlaravel.com/uploads/docs-4-2/fields/checklist_dependency.png) + +
    + + +### color + +```php +[ // 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 +[ // 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 +[ // Date + 'name' => 'birthday', + 'label' => 'Birthday', + 'type' => 'date' +], +``` + +Input preview: + +![CRUD Field - date](https://backpackforlaravel.com/uploads/docs-4-2/fields/date.png) + +
    + + +### datetime + +```php +[ // 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 +[ // 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 +[ + '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 +[ + '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 +[ + '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 +[ // 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 +[ // 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 +[ + '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 +[ // 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 +[ // 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() + { + $this->crud->setRequest($this->crud->validateRequest()); + + /** @var \Illuminate\Http\Request $request */ + $request = $this->crud->getRequest(); + + // Encrypt password if specified. + if ($request->input('password')) { + $request->request->set('password', Hash::make($request->input('password'))); + } else { + $request->request->remove('password'); + } + + $this->crud->setRequest($request); + $this->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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ + '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 +[ // 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 +[ // 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 +[ // Textarea + 'name' => 'description', + 'label' => 'Description', + 'type' => 'textarea' +], +``` + +Input preview: + +![CRUD Field - textarea](https://backpackforlaravel.com/uploads/docs-4-2/fields/textarea.png) + +
    + + +### time + +```php +[ // Time + 'name' => 'start', + 'label' => 'Start time', + 'type' => 'time' +], +``` + +
    + + +### upload + +**Step 1.** Show a file input to the user: +```php +[ // Upload + 'name' => 'image', + 'label' => 'Image', + 'type' => 'upload', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the file from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setImageAttribute($value) + { + $attribute_name = "image"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path, $fileName = null); + + // return $this->attributes[{$attribute_name}]; // uncomment if this is a translatable field + } +``` + +**How it works:** + +The field sends the file, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the file is stored on the disk. + +>NOTE: If this field is mandatory (required in validation) please use the [sometimes laravel validation rule](https://laravel.com/docs/9.x/validation#conditionally-adding-rules) together with **required** in your validation. (sometimes|required|file etc... ) + +[The ```uploadFileToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/app/Models/Traits/HasUploadFields.php#L31-L59) will take care of everything for most use cases: + +```php +/** + * Handle file upload and DB storage for a file: + * - on CREATE + * - stores the file at the destination path + * - generates a name + * - stores the full path in the DB; + * - on UPDATE + * - if the value is null, deletes the file and sets null in the DB + * - if the value is different, stores the different file and updates DB value + * / +public function uploadFileToDisk($value, $attribute_name, $disk, $destination_path, $fileName = null) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the file to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + \Storage::disk('public_folder')->delete($obj->image); + }); + } +``` + +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 +[ // Upload + 'name' => 'photos', + 'label' => 'Photos', + 'type' => 'upload_multiple', + 'upload' => true, + 'disk' => 'uploads', // if you store files in the /public folder, please omit this; if you store them in /storage or S3, please specify it; + // optional: + 'temporary' => 10 // if using a service, such as S3, that requires you to make temporary URLs this will make a URL that is valid for the number of minutes specified +], +``` + +**Step 2.** In order to save/update/delete the files from disk&db, we need to create [a mutator](https://laravel.com/docs/5.3/eloquent-mutators#defining-a-mutator) on your model: +```php +public function setPhotosAttribute($value) +{ + $attribute_name = "photos"; + $disk = "public"; + $destination_path = "folder_1/subfolder_1"; + + $this->uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path); +} +``` + +**Step 3.** Since the filenames are stored in the database as a JSON array, we're going to use [attribute casting](https://laravel.com/docs/5.3/eloquent-mutators#attribute-casting) on your model, so every time we get the filenames array from the database it's converted from a JSON array to a PHP array: +```php +protected $casts = [ + 'photos' => 'array' +]; +``` + +**Step 4.** Validation - If you need to validate the field within your `EntityRequest.php`, you can use Laravel's built-in array validation (notice the asterisk): + +```php +return [ + 'upload_multiple.*' => [ + 'nullable', + 'max:2048', // file size in KB + 'mimetypes:image/jpeg', // allow only some mimetypes + ], +]; +``` +**How it works:** + +The field sends the files, through a Request, to the Controller. The Controller then tries to create/update the Model. That's when the mutator on your model will run. That also means we can do any [file validation](https://laravel.com/docs/5.3/validation#rule-file) (```file```, ```image```, ```mimetypes```, ```mimes```) in the Request, before the files are stored on the disk. + +[The ```uploadMultipleFilesToDisk()``` method](https://github.com/Laravel-Backpack/CRUD/blob/master/src/app/Models/Traits/HasUploadFields.php#L76-L113) will take care of everything for most use cases: + +``` +/** + * Handle multiple file upload and DB storage: + * - if files are sent + * - stores the files at the destination path + * - generates random names + * - stores the full path in the DB, as JSON array; + * - if a hidden input is sent to clear one or more files + * - deletes the file + * - removes that file from the DB. + */ +public function uploadMultipleFilesToDisk($value, $attribute_name, $disk, $destination_path) {} +``` + +If you wish to have a different functionality, you can delete the method call from your mutator and do your own thing. + +>**The uploaded files are not deleted for you.** When you delete an entry (whether through CRUD or the application), the uploaded files will not be deleted. + +If you're NOT using soft deletes on that Model and want the files to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +```php + public static function boot() + { + parent::boot(); + static::deleting(function($obj) { + if (count((array)$obj->photos)) { + foreach ($obj->photos as $file_path) { + \Storage::disk('public_folder')->delete($file_path); + } + } + }); + } +``` + +You might notice the field is using a ```clear_photos``` variable. Don't worry, you don't need it in your db table. That's just used to delete photos upon "update". If you use ```$fillable``` on your model, just don't include it. If you use ```$guarded``` on your model, place it in guarded. + +Input preview: + +![CRUD Field - upload_multiple](https://backpackforlaravel.com/uploads/docs-4-2/fields/upload_multiple.png) + +
    + + +### url + +```php +[ // URL + 'name' => 'link', + 'label' => 'Link to video file', + 'type' => 'url' +], +``` + +
    + + +### view + +Load a custom view in the form. + +```php +[ // 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 +[ // 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_algolia PRO + +Use [Algolia Places autocomplete](https://community.algolia.com/places/) 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 address string. For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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. + +```php +[ // Address algolia + 'name' => 'address', + 'label' => 'Address', + 'type' => 'address_algolia', + // optional + 'store_as_json' => true +], +``` + +> **Algolia is killing Places.** Please note that Algolia Places **will stop working in May 2022**, as reported in [this announcement](https://www.algolia.com/blog/product/sunseting-our-places-feature/). For that reason, it's probably a good idea to use the `address_google` field instead (it's right after this one). + + +Input preview: + +![CRUD Field - address](https://backpackforlaravel.com/uploads/docs-4-2/fields/address.png) + +
    + + +### 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 +[ // 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' +], +``` + +> **Use attribute casting.** For information stored as JSON in the database, it's recommended that you use [attribute casting](https://mattstauffer.co/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 +[ // 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 +[ // 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 +$this->crud->addField([ + '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 +[ // CKEditor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'ckeditor', + + // optional: + 'extra_plugins' => ['oembed', 'widget'], + 'options' => [ + 'autoGrow_minHeight' => 200, + 'autoGrow_bottomSpace' => 50, + 'removePlugins' => 'resize,maximize', + ] +], +``` + +If you'd like to be able to select files from elFinder, you need to also run ```composer require backpack/filemanager``` to install elFinder. + +Input preview: + +![CRUD Field - ckeditor](https://backpackforlaravel.com/uploads/docs-4-2/fields/ckeditor.png) + +
    + + +### color_picker PRO + +Show a pretty colour picker using [Bootstrap Colorpicker](https://itsjavi.com/bootstrap-colorpicker/). + +```php +[ // color_picker + 'label' => 'Background Color', + 'name' => 'background_color', + 'type' => 'color_picker', + 'default' => '#000000', + + // optional + // Anything your define inside `color_picker_options` will be passed as JS + // to the JavaScript plugin. For more information about the options available + // please see the plugin docs at: + // ### https://itsjavi.com/bootstrap-colorpicker/module-options.html + 'color_picker_options' => [ + 'customClass' => 'custom-class', + 'horizontal' => true, + 'extensions' => [ + [ + 'name' => 'swatches', // extension name to load + 'options' => [ // extension options + 'colors' => [ + 'primary' => '#337ab7', + 'success' => '#5cb85c', + 'info' => '#5bc0de', + 'warning' => '#f0ad4e', + 'danger' => '#d9534f' + ], + 'namesAsValues' => false + ] + ] + ] + ] +], +``` + +Input preview: + +![CRUD Field - color_picker](https://backpackforlaravel.com/uploads/docs-4-2/fields/color_picker.png) + +
    + + +### date_range PRO + +Show a DateRangePicker and let the user choose a start date and end date. + +```php +[ // 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 +[ // 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 +[ // 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) + +
    + + +### 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 +[ // 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::addField([ + '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/9.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, lineawesome, 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 +[ // icon_picker + 'label' => "Icon", + 'name' => 'icon', + 'type' => 'icon_picker', + 'iconset' => 'fontawesome' // options: fontawesome, lineawesome, 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 +$this->crud->addField([ + '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 + // 'disk' => 's3_bucket', // in case you need to show images from a different disk + // 'prefix' => 'uploads/images/profile_pictures/' // in case your db value is only the file name (no path), you can use this to prepend your path to the image src (in HTML), before it's shown to the user; +]); +``` + +**Step 2.** Add a [mutator](https://laravel.com/docs/7.x/eloquent-mutators#defining-a-mutator) to your Model, where you pick up the uploaded file and store it wherever you want. You can use this boilerplate code and modify it to match your use case. + +**NOTE: The code below requires that you have ```intervention/image``` installed. If you don't, please do ```composer require intervention/image``` first.** + +```php +// .. + +use Illuminate\Support\Str; +use Intervention\Image\ImageManagerStatic as Image; + +// .. + +Class Product extends Model +{ + // .. + + public function setImageAttribute($value) + { + $attribute_name = "image"; + // or use your own disk, defined in config/filesystems.php + $disk = config('backpack.base.root_disk_name'); + // destination path relative to the disk above + $destination_path = "public/uploads/folder_1/folder_2"; + + // if the image was erased + if (empty($value)) { + // delete the image from disk + if (isset($this->{$attribute_name}) && !empty($this->{$attribute_name})) { + \Storage::disk($disk)->delete($this->{$attribute_name}); + } + // set null on database column + $this->attributes[$attribute_name] = null; + } + + + // if a base64 was sent, store it in the db + if (Str::startsWith($value, 'data:image')) + { + // 0. Make the image + $image = \Image::make($value)->encode('jpg', 90); + + // 1. Generate a filename. + $filename = md5($value.time()).'.jpg'; + + // 2. Store the image on disk. + \Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream()); + + // 3. Delete the previous image, if there was one. + if (isset($this->{$attribute_name}) && !empty($this->{$attribute_name})) { + \Storage::disk($disk)->delete($this->{$attribute_name}); + } + + // 4. Save the public path to the database + // but first, remove "public/" from the path, since we're pointing to it + // from the root folder; that way, what gets saved in the db + // is the public URL (everything that comes after the domain name) + $public_destination_path = Str::replaceFirst('public/', '', $destination_path); + $this->attributes[$attribute_name] = $public_destination_path.'/'.$filename; + } elseif (!empty($value)) { + // if value isn't empty, but it's not an image, assume it's the model value for that attribute. + $this->attributes[$attribute_name] = $this->{$attribute_name}; + } + } + +// .. +``` +> **The uploaded images are not deleted for you.** If you delete an entry (using the CRUD or anywhere inside your app), the image file won't be deleted from the disk. +> If you're NOT using soft deletes on that Model and want the image to be deleted at the same time the entry is, just specify that in your Model's ```deleting``` event: +> ```php +> public static function boot() +> { +> parent::boot(); +> static::deleted(function($obj) { +> \Storage::disk('public_folder')->delete($obj->image); +> }); +> } +> ``` + +**A note about aspect_ratio** +The value for aspect ratio is a float that represents the ratio of the cropping rectangle height and width. By way of example, + +- Square = 1 +- Landscape = 2 +- Portrait = 0.5 + +And you can, of course, use any value for more extreme rectangles. + +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 +[ // 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 +[ // 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 +[ // 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("http://github.com/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) +[ + 'type' => "relationship", + 'name' => 'category', + 'ajax' => true, + 'inline_create' => true, // <--- THIS +], +// for n-n relationships (ex: tags) +[ + '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 +[ + '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. + +Additionally, if you want to change something about the primary select, you can do that using the `pivotSelect` attribute: + +```php +[ + 'name' => 'companies', + 'type' => "relationship", + 'subfields' => [ ... ], + // .. + 'pivotSelect'=> [ + // 'ajax' => true, + 'placeholder' => 'Pick a company', + 'wrapper' => [ + 'class' => 'col-md-6', + ], + ], +] +``` + +#### 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 +[ + '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 +[ + '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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ // 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 +[ // select_and_order + 'name' => 'featured', + 'label' => "Featured", + 'type' => 'select_and_order', + 'options' => [ + 1 => "Option 1", + 2 => "Option 2" + ] +], +``` + +Also possible: + +```php +[ // 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 +[ // 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 +[ // 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("http://github.com/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/4.1/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 +[ // 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("http://github.com/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')); + } +``` + +
    + +### slug PRO + +Track the value of a different text input and turn it into a valid URL segment (aka. slug), as you type, using JS: + +```php +[ // Text + 'name' => 'slug', + 'target' => 'title', // will turn the title input into a slug + 'label' => "Slug", + 'type' => 'slug', + + // 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 + +], +``` + +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 + $this->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 +[ // 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.co/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. + +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 +[ // 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 +[ // 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.co/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 +[ // WYSIWYG Editor + 'name' => 'description', + 'label' => 'Description', + 'type' => 'wysiwyg' +], +``` + + + +## 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 +[ // 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') + + +@if ($crud->fieldTypeNotLoaded($field)) + @php + $crud->markFieldTypeAsLoaded($field); + @endphp + + {{-- 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 +@endif +``` + +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; + + + +## 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). diff --git a/5.x/crud-filters.md b/5.x/crud-filters.md new file mode 100644 index 00000000..1c3800c4 --- /dev/null +++ b/5.x/crud-filters.md @@ -0,0 +1,507 @@ +# Filters PRO + +--- + + +## About + +Backpack CRUD allows you to show a filters bar right above the entries table. When selected or modified, they reload the DataTables results. The search will then search within the filtered elements. + +![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()```. + +This is a PRO feature. It requires that you have [purchased access to `backpack/pro`](https://backpackforlaravel.com/products/pro-for-unlimited-projects). + + +### Filters API + +In order to manipulate filters, you can use: + +```php +$this->crud->addFilter($options, $values, $filter_logic); + +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); + +$this->crud->filters(); // gets all the filters + +// aditionally you should be able to change Filter attributes by appending `->methods` in your filter definition. Eg - this would set the filter attribute `type` to be equal to `number`: +$this->crud->addFilter($options, $values, $filter_logic)->type('number'); +``` + + +### Adding a filter + +When adding a filter 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) + $this->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: + // $this->crud->addClause('draft'); +}); +``` +> 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 +// simple filter +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'active', + 'label' => 'Active' +], +false, +function() { // if the filter is active + // $this->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 +// simple filter +$this->crud->addFilter([ + 'type' => 'text', + 'name' => 'description', + 'label' => 'Description' +], +false, +function($value) { // if the filter is active + // $this->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 +// date filter +$this->crud->addFilter([ + 'type' => 'date', + 'name' => 'date', + 'label' => 'Date' +], + false, +function ($value) { // if the filter is active, apply these constraints + // $this->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 +// daterange filter +$this->crud->addFilter([ + 'type' => 'date_range', + 'name' => 'from_to', + 'label' => 'Date range' +], +false, +function ($value) { // if the filter is active, apply these constraints + // $dates = json_decode($value); + // $this->crud->addClause('where', 'date', '>=', $dates->from); + // $this->crud->addClause('where', 'date', '<=', $dates->to . ' 23:59:59'); +}); +``` + +
    + + +### 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 +// dropdown filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'dropdown', + 'label' => 'Status' +], [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', +], function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2', + 'label' => 'Status' +], function () { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function ($value) { // if the filter is active + // $this->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 second parameter (the closure), with something like ```return \Backpack\NewsCRUD\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 +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'status', + 'type' => 'select2_multiple', + 'label' => 'Status' +], function() { + return [ + 1 => 'In stock', + 2 => 'In provider stock', + 3 => 'Available upon ordering', + 4 => 'Not available', + ]; +}, function($values) { // if the filter is active + // $this->crud->addClause('whereIn', 'status', json_decode($values)); +}); +``` + +
    + + +### 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). + +NOTE: if you want to setup your ajax routes using FetchOperation, have a look at: FetchOperation with ajax filter + +![Backpack CRUD Select2_ajax Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/select2_ajax.png) + +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'); +``` + +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; +} +``` + +3. Add the select2_ajax filter and for the second parameter ("values") specify the exact route. + +```php +// select2_ajax filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2_ajax', + 'label' => 'Category', + 'placeholder' => 'Pick a category', + 'method' => 'POST', // by default is 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 +], +url('http://github.com/admin/test/ajax-category-options'), // the ajax route, you can also use FetchOperation here, just make sure you define `'method' => 'POST'` in your filter. +function($value) { // if the filter is active + // $this->crud->addClause('where', 'category_id', $value); +}); +``` + +
    + + +### Range + +Shows two number inputs, for min and max. + +![Backpack CRUD Range Filter](https://backpackforlaravel.com/uploads/docs-4-0/filters/range.png) + +```php +$this->crud->addFilter([ + 'name' => 'number', + 'type' => 'range', + 'label' => 'Range', + 'label_from' => 'min value', + 'label_to' => 'max value' +], +false, +function($value) { // if the filter is active + $range = json_decode($value); + if ($range->from) { + $this->crud->addClause('where', 'number', '>=', (float) $range->from); + } + if ($range->to) { + $this->crud->addClause('where', 'number', '<=', (float) $range->to); + } +}); +``` + +
    + + +### 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 +// custom filter view +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'view', + 'view' => 'package::columns.column_type_name', // or path to blade file + 'label' => 'Category', + 'placeholder' => 'Pick a category', +], +false, +function($value) { // if the filter is active + // $this->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 +// select2 filter +$this->crud->addFilter([ + 'name' => 'published', + 'type' => 'select2', + 'label' => 'Published' +], function() { + return \App\Models\Test::getEnumValuesAsAssociativeArray('published'); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'published', $value); +}); +``` + +Use a select2 to filter by a 1-n relationship: +```php +// select2 filter +$this->crud->addFilter([ + 'name' => 'category_id', + 'type' => 'select2', + 'label' => 'Category' +], function() { + return \App\Models\Category::all()->pluck('name', 'id')->toArray(); +}, function($value) { // if the filter is active + $this->crud->addClause('where', 'category_id', $value); +}); +``` + +Use a select2_multiple to filter Products by an n-n relationship: +```php +// select2_multiple filter +$this->crud->addFilter([ + 'name' => 'tags', + 'type' => 'select2_multiple', + 'label' => 'Tags' +], function() { // the options that show up in the select2 + return Product::all()->pluck('name', 'id')->toArray(); +}, function($values) { // if the filter is active + 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 +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'published', + 'label' => 'Published' +], +false, +function() { // if the filter is active (the GET parameter "published" exits) + $this->crud->addClause('published'); +}); +``` + +Use a simple filter to show the softDeleted items (trashed): +```php +$this->crud->addFilter([ + 'type' => 'simple', + 'name' => 'trashed', + 'label' => 'Trashed' +], +false, +function($values) { // if the filter is active + $this->crud->query = $this->crud->query->onlyTrashed(); +}); +``` diff --git a/5.x/crud-fluent-syntax.md b/5.x/crud-fluent-syntax.md new file mode 100644 index 00000000..738b88fe --- /dev/null +++ b/5.x/crud-fluent-syntax.md @@ -0,0 +1,775 @@ +# 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('http://github.com/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('http://github.com/monster/fetch/product')) + ->inline_create(['entity' => 'product']) + // ->wrapper(['class' => 'form-group col-md-6']) + ->tab('Others'); +``` + + +### Conditionable methods + +Sometimes you may need a method to be chained on a field based on another condition. Backpack takes advantage of Laravel Conditionable to easily let you accomplish that. You can either use ```->when()``` or ```unless()```: + +```php +CRUD::field('email') + ->label('Email') + ->when($condition, function ($field) { + $field->hint('My hint'); + }); +``` + + +## 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(); +``` + + +### Conditionable methods + +Sometimes you may need a method to be chained on a column based on another condition. Backpack takes advantage of Laravel Conditionable to easily let you accomplish that. You can either use ```->when()``` or ```unless()```: +```php +CRUD::column('name') + ->unless($condition, function ($column) { + $column->wrapper([ + 'href' => function ($crud, $column, $entry, $related_key) { + return backpack_url('http://github.com/category/'.$related_key.'/show'); + }, + ]) + }); +``` + + +### 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('http://github.com/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('http://github.com/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(); +``` + + +### Conditionable methods + +Sometimes you may need a method to be chained on a button based on another condition. Backpack takes advantage of Laravel Conditionable to easily let you accomplish that. You can either use ```->when()``` or ```unless()```: + +```php +CRUD::button('name') + ->unless($condition, function ($button) { + $button->before('description') + ->when($secondCondition, , function ($button) { + $button->forget('suffix'); + }); + }); +``` + + + +### 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(); +``` + + +### Conditionable methods + +Sometimes you may need a method to be chained on a filter based on another condition. Backpack takes advantage of Laravel Conditionable to easily let you accomplish that. You can either use ```->when()``` or ```unless()```: + +```php +CRUD::filter('name') + ->when($condition, function ($filter) { + $filter->remove(); + }); +``` + + +### 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/5.x/crud-how-to.md b/5.x/crud-how-to.md new file mode 100644 index 00000000..9d37b62f --- /dev/null +++ b/5.x/crud-how-to.md @@ -0,0 +1,908 @@ +# 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/crud``` folder, it will pick up those instead; you can use this method to overwrite a blade file for all CRUDs; +- 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 +$this->crud->setShowView('your-view'); +$this->crud->setEditView('your-view'); +$this->crud->setCreateView('your-view'); +$this->crud->setListView('your-view'); +$this->crud->setReorderView('your-view'); +$this->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`; + + + +## 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 + $this->crud->addColumn([ + 'label' => "Location", + 'type' => 'select', + 'name' => 'location_id', + 'entity' => 'location', + 'attribute' => 'title', + 'model' => "App\Models\Location" + ]); + + $this->crud->addColumn([ + '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 +$this->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) + $this->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 +$this->crud->addField([ + 'label' => 'Category', + 'type' => 'select', + 'name' => 'category', + 'entity' => 'category', + 'attribute' => 'name', +]); + +// select2_from_ajax: 1-n relationship +$this->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('http://github.com/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('http://github.com/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/8.x/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/8.x/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/8.x/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/8.x/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('http://github.com/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 +$this->crud->addField([ + 'name' => 'commentable', + 'morphOptions' => [ + ['App\Models\PetShop\Owner', 'Owners'], + ['monster', 'Monsters', [ + 'placeholder' => 'Select a little monster' + ]], + ['App\Models\PetShop\Pet', 'Pets', [ + 'data_source' => backpack_url('http://github.com/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 +$this->crud->setCreateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setUpdateContentClass('col-md-8 col-md-offset-2'); +$this->crud->setListContentClass('col-md-8 col-md-offset-2'); +$this->crud->setShowContentClass('col-md-8 col-md-offset-2'); +$this->crud->setReorderContentClass('col-md-8 col-md-offset-2'); +$this->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 +``` + + + +### 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. diff --git a/5.x/crud-operation-clone.md b/5.x/crud-operation-clone.md new file mode 100644 index 00000000..3ceaeba5 --- /dev/null +++ b/5.x/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 +crud->setModel(\App\Models\Product::class); + $this->crud->setRoute(backpack_url('http://github.com/product')); + $this->crud->setEntityNameStrings('product', 'products'); + } +} +``` + +This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### 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) +{ + $this->crud->hasAccessOrFail('clone'); + $this->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/5.x/crud-operation-create.md b/5.x/crud-operation-create.md new file mode 100644 index 00000000..0da4dd7c --- /dev/null +++ b/5.x/crud-operation-create.md @@ -0,0 +1,325 @@ +# 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; + }, + ], +]); +``` + +#### 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/5.x/crud-operation-delete.md b/5.x/crud-operation-delete.md new file mode 100644 index 00000000..07ca2a7a --- /dev/null +++ b/5.x/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) +{ + $this->crud->hasAccessOrFail('delete'); + + return $this->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/5.x/crud-operation-fetch.md b/5.x/crud-operation-fetch.md new file mode 100644 index 00000000..e6f7b4f0 --- /dev/null +++ b/5.x/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('http://github.com/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; + +``` +$this->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('http://github.com/product/fetch/category'), // the fetch route on the ProductCrudController +function($value) { // if the filter is active + // $this->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() { + $this->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/5.x/crud-operation-inline-create.md b/5.x/crud-operation-inline-create.md new file mode 100644 index 00000000..4d6bc1f5 --- /dev/null +++ b/5.x/crud-operation-inline-create.md @@ -0,0 +1,117 @@ +# 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('http://github.com/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. diff --git a/5.x/crud-operation-list-entries.md b/5.x/crud-operation-list-entries.md new file mode 100644 index 00000000..b3ed6835 --- /dev/null +++ b/5.x/crud-operation-list-entries.md @@ -0,0 +1,390 @@ +# 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 +crud->addColumn(); + } +} +``` + +Configuration for this operation should be done inside your ```setupListOperation()``` method. **For a minimum setup, you only need to define the columns you need to show in the table.** + + +### 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 +$this->crud->addColumn([ + '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; + + +**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://backpackforlaravel.com/uploads/docs-4-0/operations/listEntries_details_row.png) + +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. You'll want to overwrite that method to show anything you'd like in the details row. + +To use, inside your ```EntityCrudController```: +1. Enable the functionality: ```$this->crud->enableDetailsRow();``` +2. Overwrite the ```showDetailsRow($id)``` method; + +Alternative for the 2nd step: overwrite ```views/backpack/crud/details_row.blade.php``` which is called by the default ```showDetailsRow($id)``` functionality. + + +#### Export Buttons PRO + +Exporting the DataTable to PDF, CSV, XLS is as easy as typing ```$this->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. +$this->crud->addClause('active'); // apply a 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(); + }); +$this->crud->groupBy(); +$this->crud->limit(); + +// 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: +$this->crud->addBaseClause('where', 'name', '=', 'car'); + +$this->crud->orderBy(); +// please note it's generally a good idea to use crud->orderBy() inside "if (!$this->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) +``` +**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 ```$this->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 ```$this->crud->enablePersistentTable();``` +- disable it inside a particular crud controller with ```$this->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 `$this->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); + // ... +} +``` + + +## 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/5.x/crud-operation-reorder.md b/5.x/crud-operation-reorder.md new file mode 100644 index 00000000..668f4364 --- /dev/null +++ b/5.x/crud-operation-reorder.md @@ -0,0 +1,140 @@ +# 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 +crud->set('reorder.label', 'name'); + // define how deep the admin is allowed to nest the items + // for infinite levels, set it to 0 + $this->crud->set('reorder.max_level', 2); + } +} +``` + +This will: +- allow access to the Reorder operation; +- make a "Reorder" button appear next to "Add entry" in the List view, if the List operation is enabled; +- enable the routes needed by the Reorder operation; + +Where: +- ```attribute_name``` should be the attribute you want shown on the draggable elements (ex: ```name```); +- ```ALLOWED_DEPTH``` should be an integer, how many levels deep would you allow your admin to go when nesting; for infinite levels, you should set it to ```0```; + + +## 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/5.x/crud-operation-revisions.md b/5.x/crud-operation-revisions.md new file mode 100644 index 00000000..8f841408 --- /dev/null +++ b/5.x/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 + $this->crud->addColumn([ + 'name' => 'table', + 'label' => 'Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ] + ]); + $this->crud->addColumn([ + 'name' => 'fake_table', + 'label' => 'Fake Table', + 'type' => 'table', + 'columns' => [ + 'name' => 'Name', + 'desc' => 'Description', + 'price' => 'Price', + ], + ]); + + // or maybe remove a column + $this->crud->removeColumn('text'); + } +``` +### Tabs - display columns in tabs + +Starting Backpack `v5.6` you can display your columns in tabs on `ShowOperation`. +For that you need to set the `tab` attribute in your column definition and enable the tabs in the operation settings. + +```php +public function setupShowOperation() +{ + $this->crud->setOperationSetting('tabsEnabled', true); + $this->crud->addColumn([ + 'name' => 'name', + 'tab' => 'General', + ]); + $this->crud->addColumn([ + 'name' => '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. + +```php + 'tabsEnabled' => true, + 'tabsType' => 'vertical', +``` + + +## 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/5.x/crud-operation-trash.md b/5.x/crud-operation-trash.md new file mode 100644 index 00000000..25472c9a --- /dev/null +++ b/5.x/crud-operation-trash.md @@ -0,0 +1,159 @@ +# 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); +} +``` + +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')) { + $this->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) +{ + $this->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() +{ + $this->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/5.x/crud-operation-update.md b/5.x/crud-operation-update.md new file mode 100644 index 00000000..5c0f1305 --- /dev/null +++ b/5.x/crud-operation-update.md @@ -0,0 +1,451 @@ +# 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); +// add a field to both the Update and Update operations +$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). + + +## 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`. + +**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); +} +``` + +#### 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/5.x/crud-operations.md b/5.x/crud-operations.md new file mode 100644 index 00000000..ccf01fb2 --- /dev/null +++ b/5.x/crud-operations.md @@ -0,0 +1,966 @@ +# 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']); + +$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('http://github.com/dashboard'), + $crud->entity_name_plural => url(http://github.com/$crud-%3Eroute), + '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 +``` diff --git a/5.x/crud-save-actions.md b/5.x/crud-save-actions.md new file mode 100644 index 00000000..85a6ad85 --- /dev/null +++ b/5.x/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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->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 +$this->crud->orderSaveActions(['save_action_one','save_action_two']); +// or +$this->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 +$this->crud->setOperationSetting('showSaveActionChange', false); +``` \ No newline at end of file diff --git a/5.x/crud-tutorial.md b/5.x/crud-tutorial.md new file mode 100644 index 00000000..410741da --- /dev/null +++ b/5.x/crud-tutorial.md @@ -0,0 +1,382 @@ +# 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 + +> **NEW!!!** Starting with Aug 2021, there's a much simpler way to generate everything 🎉 **Check out our new paid addon - [Backpack DevTools](https://backpackforlaravel.com/products/devtools).** It's a GUI that will help you generate Migrations, Models (complete with relationships), 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. + +Since we don't have an Eloquent model for it already, we're going to use [Jeffrey Way's Generators](https://github.com/laracasts/Laravel-5-Generators-Extended) package, which you've most likely installed along with Backpack, to generate the migration. + +```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 +``` + +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 item in the sidebar menu, in ```resources/views/vendor/backpack/base/inc/sidebar_content.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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + protected function setupListOperation() + { + // TODO: remove setFromDb() and manually define Columns, maybe Filters + $this->crud->setFromDb(); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + + // TODO: remove setFromDb() and manually define Fields + $this->crud->setFromDb(); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); + } +} +``` + +#### The Basics + +What we should notice inside this TagCrudController is that: +- ```TagCrudController extends CrudController```; +- ```TagCrudController``` has a ```setup()``` method, where can plug in 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; + +As we can tell from the comments in our ```setupXxxOperation()``` methods, in most cases we _shouldn't_ use ```$this->crud->setFromDb()```, which automagically figures out which columns and fields to show. That's because for real models, in real projects, it would _never_ be able to 100% figure out which field types to use. Real projects are very custom - that's a fact. In real projects, models are complicated, use a bunch of field types and you'll want to customize things. Instead of using ```setFromDb()``` then gradually changing what you don't like, **we heavily recommend you manually define all fields and columns you need**. + +That being said, since our ```Tag``` model is so simple, we _can_ leave it like this - it will work perfectly, since we only need a ```text``` field and a ```text``` column. But let's not do that. Let's define our fields and columns manually, like big boys & girls. + +#### Option 1. SetupXxxOperation Methods + +We can either define each operations inside its ```setupXxxOperation()``` method: + +```php + protected function setupListOperation() + { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupCreateOperation() + { + $this->crud->setValidation(TagRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + } + + protected function setupUpdateOperation() + { + $this->setupCreateOperation(); // since this calls the methods above, no need to do anything here + } +``` + +This will: +- disable the ```setFromDb()``` functionality (since we deleted that line); +- 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; + +It's the exact same thing ```setFromDb()``` would have figured out, but done manually. This way, if we want to add [other columns](/docs/{{version}}/crud-columns)) or [other fields](/docs/{{version}}/crud-fields), we can easily do that. If we want to change the label of the ```name``` field from ```Name``` to ```Tag name```, we just make that small change. The benefits of _not_ using ```setFromDb()``` will be more obvious once you use Backpack on real models, we promise. + +#### Option 2. Operation Closures + +An alternative to defining operation inside ```setupXxxOperation()``` methods is to do it 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 would like 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() + { + $this->crud->setModel('App\Models\Tag'); + $this->crud->setRoute(config('backpack.base.route_prefix') . '/tag'); + $this->crud->setEntityNameStrings('tag', 'tags'); + + $this->crud->operation('list', function() { + $this->crud->addColumn(['name' => 'name', 'type' => 'text', 'label' => 'Name']); + }); + + $this->crud->operation(['create', 'update'], function() { + $this->crud->addValidation(TagCrudRequest::class); + $this->crud->addField(['name' => 'name', 'type' => 'text', 'label' => '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 will 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. 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 ```views/vendor/backpack/base/inc/sidebar_content.php``` file. You'll see this file is pure HTML. This will allow you to customize the menu as much as you want. 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. + + +## 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/5.x/demo.md b/5.x/demo.md new file mode 100644 index 00000000..03676965 --- /dev/null +++ b/5.x/demo.md @@ -0,0 +1,78 @@ +# 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 8; +- 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 +``` + + +## Demo Usage + +Once everything's installed, and your database has been set up: + +- Your admin panel is available at http://localhost/backpack-demo/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/5.x/faq.md b/5.x/faq.md new file mode 100644 index 00000000..a3d5dd80 --- /dev/null +++ b/5.x/faq.md @@ -0,0 +1,105 @@ +# Frequently Asked Questions + +--- + + + +## Licensing + + +### Do I need a license to test Backpack? + +You don't need a license code AT ALL. Go head 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 (eg. [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 as many staging/test/beta domains or subdomains as you need; if someone from our team contacts you, 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 v5](https://github.com/laravel-backpack/crud), which is free & 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 v5](https://github.com/laravel-backpack/crud) instead, which is free & 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... 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, sorry, we're no longer giving away free licenses. But we _have_ released [Backpack CRUD v5](https://github.com/laravel-backpack/crud) under MIT License, which means it's free & open-source. It has fewer features, but you can do absolutely anything you want with it. + +For background... until Feb 2022, we've been giving away our paid software, for free, if you used it for non-commercial purposes. We introduced this as a way for us to give back to the community. And we thought it's a fair system - that we only ask money from people who _make_ money. We still think that's fair. However, giving away free licenses turned out to be very time-consuming. In order to get a free license, we required people to read the terms & conditions and fill in an application form. A LOT of people submitted the application form, but VERY FEW people actually read the T&C or properly filled in the form. In consequence, because we're good guys and wanted to treat each case with care, it took us _dozens of hours_ every week (_dozens_! every week!) to analyze applications and do the necessary back-and-forth emails required... because people did not give proper (or any) information about their organizations and projects. We even ended up _hiring_ someone to do that, so we developers don't do it. So yes, we ended up _paying_ for someone in our team... to answer people who were not considerate enough to answer the questions we asked. Naturally, we had to put an end to that, it wasn't a good use of our time or money. + +We still _want_ to help with free access to Backpack PRO: +- if you've contributed to our community with PRs, answering people or creating add-ons please DO NOT PAY; send us an email, we WANT TO give you access for free; +- if you have a good cause, you're giving away _your time_ for free too and Backpack CRUD is not enough for you, then please (1) create a Backpack account and (2) send us an email, asking for a free license - we're grateful to be able to help, and _want to_ help; but please make it dead-simple for us to say "yes", otherwise our default answer is "no"; +- if you've previously received a free Backpack v4 license and your project is still non-commercial, please send an email to hello@backpackforlaravel.com, happy to give you access to PRO v5 for another year; + + +## Miscellaneous + + + +### How do I update Backpack to the latest non-breaking version? + +First of all, **run `composer update` on your project**. That will pull in the latest supported version of all your Backpack packages. + +Then you should **re-publish the JS and CSS assets**. There are two ways to do that: + +(A) Run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force`. Please note this will overwrite anything that's already there. This B solution has a downside: _unused files are not removed_. A few files Backpack no longer uses will still be in your public/packages folder, even though they're no longer used. + +(B) If you have NOT touched you `public/packages` folder, or placed anything custom inside it: +- delete the public/packages directory and all its contents; +- run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public` +- if you use elFinder, also delete `resources/views/vendor/elfinder` and run `php artisan backpack:filemanager:install` + + + +### 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 public/packages +rm -rf resources/views/errors +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 didn't like Backpack, or it didn't fit your project. It might help us take Backpack in a different direction, one where you might want to use it. Thank you 🙏 diff --git a/5.x/features-free-vs-paid.md b/5.x/features-free-vs-paid.md new file mode 100644 index 00000000..269c232d --- /dev/null +++ b/5.x/features-free-vs-paid.md @@ -0,0 +1,213 @@ +# Features (Free vs Paid) + +--- + +Starting with Backpack v5, 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 addon, that adds features for more complex use cases, in `backpack\pro`; PRO + +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   25 columns types + 25 free + + 6 pro columns +
          - Buttons   FREEFREE
          - Search   FREEFREE
          - Filters   -10+ filter types
          - Export Buttons   -PRO
          - Details Row   -PRO
      - Create & Update OperationsFREEFREE
          - Fields   29 field types + 29 free + + 28 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   25 column types + 25 free + + 6 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. But in principle, new features will _not_ be added to `backpack/crud`. We will keep adding new features to `backpack/pro`, though. If you have suggestions, please [vote on top new features](https://github.com/Laravel-Backpack/CRUD/discussions/3960) or [open a new suggestion](https://github.com/laravel-backpack/ideas). + + +## Add-ons + +In addition the 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 & development teams. + +For more information, see: +- [our addons page](https://backpackforlaravel.com/addons) for more addons (including third-party addons); +- [our pricing page](https://backpackforlaravel.com/pricing) for the first-party addons that we think are _so good_, that they're worth paying for; diff --git a/5.x/generating-code.md b/5.x/generating-code.md new file mode 100644 index 00000000..b95199ad --- /dev/null +++ b/5.x/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
    + + +#### 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-assetsPublish new CSS and JS assets (will override existing ones).
    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/5.x/getting-started-advanced-features.md b/5.x/getting-started-advanced-features.md new file mode 100644 index 00000000..33f62485 --- /dev/null +++ b/5.x/getting-started-advanced-features.md @@ -0,0 +1,49 @@ +# 3. Advanced Features + +--- + +**Duration:** 5 min + +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 how to find it. + +--- + + +## Other Operations +- [Show](/docs/{{version}}/crud-operation-show) Operation - you can let your admins preview an entry FREE +- [Reorder](/docs/{{version}}/crud-operation-reorder) Operation - you can reorder and nesting entries (hierarchy tree) FREE +- [Revisions](/docs/{{version}}/crud-operation-revisions) Operation - you can keep a record of all modifications to an entry, and let your admin revert changes FREE +- [Clone](/docs/{{version}}/crud-operation-clone) Operation - you can make a copy of an entry; PRO +- [BulkDelete](/docs/{{version}}/crud-operation-delete) Operation - you can delete multiple items in one go; PRO +- [BulkClone](/docs/{{version}}/crud-operation-clone) Operation - you can 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 the admin to easily preview some quick information that was too big to fit inside a columns - we call it [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 the 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 for today!** Told you we're done with long lessons :-) Hopefully some of the above have peaked your interest and you've clicked 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/5.x/getting-started-basics.md b/5.x/getting-started-basics.md new file mode 100644 index 00000000..32f673c0 --- /dev/null +++ b/5.x/getting-started-basics.md @@ -0,0 +1,164 @@ +# 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 go ahead and [watch this excellent intro series on Laracasts](https://laracasts.com/series/laravel-from-scratch-2018) and accommodate yourself with Laravel first. + + + +## What is Backpack? +A software 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 what a Tag CRUD could 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. Here's what a CRUD that uses all of Backpack's features could look like: + +![Monsters CRUD - List Entries Operation](https://backpackforlaravel.com/uploads/docs-4-0/getting_started/monster_crud_list_entries.png) + +Mind that you will _almost never_ use all of Backpack's features in one CRUD. But if you do... it still looks good, and it'll be intuitive to use. + + +## Main Features + + +### Front-End Design + +Backpack installs the [CoreUI](https://coreui.io) HTML theme, and our own design on top - [Backstrap](https://backstrap.net). It uses Bootstrap 4, and has 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 our [Backstrap demo](https://backstrap.net), or from the [CoreUI documentation](https://coreui.io/docs/getting-started/introduction/) and it will look good, without you having to design anything. + +It also installs 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 a basic authentication system that's separate from Laravel's. This way, you can have different login screens for users & admins, if you need. If not, you can choose to use only one authentication - either Laravel's, or Backpack's. + +![Backpack 3.5 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 DB information. 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) PAID - 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 needed files. It's that simple. Check out [the images here](https://backpackforlaravel.com/products/devtools) for how it works. It's especially useful for more complex entities. It is a paid tool though, and you might not be ready to purchase yet, so let's explore a 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 +composer require --dev backpack/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", no need for customisations. But don't expect this for more complex entities. They will usually have particularities 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 CrudPanel 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 sidebar item inside ```resources/views/vendor/backpack/base/inc/sidebar_content.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 [ridiculously 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 (db 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 you might use to achieve the above: + +```php +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => '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 all for today!** If you want to learn more, go ahead and [read the next lesson](/docs/{{version}}/getting-started-crud-operations) of this series. + + +
    + + Next → + diff --git a/5.x/getting-started-crud-operations.md b/5.x/getting-started-crud-operations.md new file mode 100644 index 00000000..9fd25ce2 --- /dev/null +++ b/5.x/getting-started-crud-operations.md @@ -0,0 +1,254 @@ +# 2. CRUD Operations + +--- + +**Duration:** 10 minutes + +Let's bring back the example in 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 +crud->setModel("App\Models\Tag"); + $this->crud->setRoute("admin/tag"); + $this->crud->setEntityNameStrings('tag', 'tags'); + } + + public function setupListOperation() + { + $this->crud->setColumns(['name', 'slug']); + } + + public function setupCreateOperation() + { + $this->crud->setValidation(TagCrudRequest::class); + + $this->crud->addField([ + 'name' => 'name', + 'type' => 'text', + 'label' => "Tag name" + ]); + $this->crud->addField([ + 'name' => 'slug', + '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, BulkClone), and you can easily _create a custom operation_, but let's not get ahead of ourselves. Baby steps. **Let's go through the most important features of the operations you'll be using _all the time_: ListEntries, Create and Update**. + + +## Create & 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 what 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 ListEntries 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 a lot of times, you won't just need text inputs. You'll need datepickers, upload buttons, 1-n relationship, n-n relationships, textareas, etc. For each field, you only need to define it properly in the Controller. Here are the most used methods to manipulate fields: + +```php +$this->crud->addField($field_definition_array); +$this->crud->addFields([$field_definition_array_1, $field_definition_array_2]); +$this->crud->removeField('name'); +$this->crud->removeFields(['name_1', 'name_2']); + +// pro tip: +// a quick way to add simple fields: let the CRUD decide what field type it is +$this->crud->addField('db_column_name'); +``` + +A typical *field definition array* 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'd like 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 some super-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 is 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 +$this->crud->addField([ + '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()``` 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, 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 +$this->crud->addField([ + '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? +]); +``` + +> When generating a CrudController, you might be using the ```$this->crud->setFromDb();``` method by default, which tries to figure out what fields you might need in your create/update forms and what columns in your list view, but - as you'd expect - it only works for the simple field types. You can: +> +> (1) choose to keep using ```setFromDb()``` and add/remove/change additional fields +> +> or +> +> (2) delete ```setFromDb()``` and manually define each field and column; +> +> **Our recommendation**, for anything but the simplest CRUDs, **is to manually define each field** - much easier to understand and customize, for your future self and any other developer that comes after you. + + +### Callbacks + +Developers coming from GroceryCRUD on CodeIgniter or other CRUD systems will be looking for callbacks to run ```before_insert```, ```before_update```, ```after_insert```, ```after_update```. **There are no callbacks in Backpack**. The ```store()``` and ```update()``` code is inside a trait, so you can easily overwrite 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 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 super-similar to fields**: + +```php +$this->crud->addColumn($column_definition_array); // add a single column, at the end of the table +$this->crud->addColumns([$column_definition_array_1, $column_definition_array_2]); // add multiple columns, at the end of the table +$this->crud->removeColumn('column_name'); // remove a column from the table +$this->crud->removeColumns(['column_name_1', 'column_name_2']); // remove an array of columns from the table +$this->crud->setColumnDetails('column_name', ['attribute' => 'value']); +$this->crud->setColumnsDetails(['column_1', 'column_2'], ['attribute' => 'value']); + +$this->crud->setColumns([$column_definition_array_1, $column_definition_array_2]); // make these the only columns in the table +``` + +You can use one of the [14+ 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 super-specific need. Here's an example of using the methods above: + +```php +$this->crud->addColumn([ + '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 +$this->crud->addColumn('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 well… _filter_ the ListEntries 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). + +```php +$this->crud->addFilter($options, $values, $filter_logic); +$this->crud->removeFilter($name); +$this->crud->removeAllFilters(); +``` + +For more on this, check out the [filters documentation page](/docs/{{version}}/crud-filters), when you need them. + + +### 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. Look for the [buttons documentation](/docs/{{version}}/crud-buttons) when you need it. + +```php +// positions: 'beginning' and 'end' +// stacks: 'line', 'top', 'bottom' +// types: view, model_function +$this->crud->addButton($stack, $name, $type, $content, $position); +$this->crud->removeButton($name); +$this->crud->removeButtonFromStack($name, $stack); +``` + +**That's it for today!** 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 → + \ No newline at end of file diff --git a/5.x/getting-started-license-and-support.md b/5.x/getting-started-license-and-support.md new file mode 100644 index 00000000..30019ee2 --- /dev/null +++ b/5.x/getting-started-license-and-support.md @@ -0,0 +1,56 @@ +# 4. Add-ons, License & Support + +--- + +**Duration:** 3 minutes + + +## Add-ons + +In addition to our core packages (Base and CRUD), we have quite a few packages you can install or download, that treat 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 + + +Starting with v5, Backpack has become open-core. The main features are now 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 + +With thousands of developers using Backpack, a lot of them non-commercial, and such a small price, **we can't offer official support for the packages**. We've been doing this since 2016, we actively maintain the packages, we try to squash any bugs ASAP and add new features all the time, but we unfortunately can't spend time on back-and-forth on implementation issues in your project. 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 preference, I highly recommend you join our gang. Help others get started, create cool stuff, or even influence the direction of Backpack: + +- **[StackOverflow tag](https://stackoverflow.com/questions/tagged/backpack-for-laravel) for support 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?) +- **[Gitter Chatroom](https://gitter.im/BackpackForLaravel/Lobby) for quick questions** (If you have an urgent matter that won't take much time to answer, use our 24/7 Gitter chatroom. Be considerate, everyone's probably working on their own project right now.) +- **[GitHub Issues](https://github.com/laravel-backpack/) 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 learn about it and make Backpack better. ) +- **[/r/BackpackForLaravel subreddit](https://www.reddit.com/r/BackpackForLaravel/) for showing off your work, asking for opinions on implementation, sharing tips, packages, etc.** + +Thank you for sticking up with us for so long. This is the last Backpack lesson we can give you. **Now you have absolutely no excuse not to start your first Backpack project :-)** Here are a few links for if you still don't think you're 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/5.x/getting-started-videos.md b/5.x/getting-started-videos.md new file mode 100644 index 00000000..5a6e5d49 --- /dev/null +++ b/5.x/getting-started-videos.md @@ -0,0 +1,118 @@ +# Getting Started Videos + +--- + +**Total Duration:** 31 minutes + + + +

    Intro

    + + +Meet your teacher, [Cristian Tabacitu](https://twitter.com/tabacitu), the founder of Backpack for Laravel. + +
    + +
    +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) + +---- + + +

    The Admin Interface

    + +Before we go deep into the PHP features offered by Backpack, it's important to understand the HTML & CSS it uses, and how that can make your life easier when building admin panels. This is often an afterthought in admin panels, but Backpack makes it really really easy to create admin pages that are 100% custom. + +
    + +
    +Mentioned in this video: +- [Laravel docs - installation](https://laravel.com/docs/7.x) +- [Backpack docs - installation](/docs/{{version}}/installation) +- [Backpack docs - how to customize the user interface](/docs/{{version}}/base-how-to#customizing-the-design-of-the-menu-sidebar-footer) +- [Backpack docs - widgets](/docs/{{version}}/base-widgets) +- [Backpack docs - add-ons](/addons) +- [Bootstrap 4 docs](https://getbootstrap.com/) +- [Backstrap.net - the default HTML template & UI blocks](https://backstrap.net/) + +---- + + +

    Generating and Understanding CRUDs

    + +Let's generate a few Backpack CRUDs - places where the admin can Create, Read, Update or Delete entries. CRUDs will make it easy for you to build admin panels, 10x faster than before. + +
    + + +> **NEW!!!** Starting with Aug 2021, there's a much simpler way to generate everything 🎉 Instead of using Blueprint, + **check out our new paid addon - [Backpack DevTools](https://backpackforlaravel.com/products/devtools).** It's a GUI that will help you generate Migrations, Models (complete with relationships), 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. + +
    +Mentioned in this video: +- [Backpack's online demo](https://demo.backpackforlaravel.com/admin/login) +- [laravel-shift/blueprint](https://blueprint.laravelshift.com/) - CLI tool to generate Eloquent models +- [Laravel docs - migrations](https://laravel.com/docs/7.x/migrations#introduction) +- [backpack/generators - CLI tool to generate CRUDs](https://github.com/laravel-backpack/generators) +- [Fork - Git client for Mac OS & Windows](https://git-fork.com/) +- [Backpack's List Operation](/docs/{{version}}/crud-operation-list-entries) - features [columns](/docs/{{version}}/crud-columns), [filters](/docs/{{version}}/crud-filters), [buttons](/docs/{{version}}/crud-buttons), [widgets](/docs/{{version}}/base-widgets), [other features](/docs/{{version}}/crud-operation-list-entries#other-features) +- [Backpack's Create Operation](/docs/{{version}}/crud-operation-create) - features [fields](/docs/{{version}}/crud-fields), [widgets](/docs/{{version}}/base-widgets), [save actions](/docs/{{version}}/crud-save-actions), [split fields into tabs](/docs/{{version}}/crud-fields#split-fields-into-tabs), [add custom HTML between the fields](/docs/{{version}}/crud-fields#custom-html) +- [Backpack's Update Operation](/docs/{{version}}/crud-operation-update) +- [Backpack's Delete Operation](/docs/{{version}}/crud-operation-delete) + +Not mentioned in this video, but heavily recommended: +- [Backpack DevTools](https://backpackforlaravel.com/products/devtools) + +---- + + +

    Using Operations and Features in CRUDs

    + +I'd argue the best part of Backpack is not how easy it is to generate CRUDs, but how easy it is to customize them. In this video, we'll be taking a look at the default Operations that Backpack offers, and changing things around to fit our purpose. In the process, you'll understand how they work, and just how easy it is to use, customize or overwrite Operations like Create, Update, List, Delete, Reorder, Clone, BulkClone and BulkDelete. + +
    + +
    +Mentioned in this video: +- [Backpack operations](/docs/{{version}}/crud-operations) +- Laravel docs - [validation using Laravel Form Requests](https://laravel.com/docs/7.x/validation#form-request-validation) +- Laravel docs - [validation rules](https://laravel.com/docs/7.x/validation#available-validation-rules) +- [Reorder Operation](/docs/{{version}}/crud-operation-reorder) +- [Clone & BulkClone Operations](/docs/{{version}}/crud-operation-clone) +- Columns - [the 20+ column types](/docs/{{version}}/crud-columns#default-column-types), [columns API](/docs/{{version}}/crud-columns#columns-api), [fluent syntax](/docs/{{version}}/crud-fluent-syntax#fluent-columns), [array syntax](/docs/{{version}}/crud-columns#about), [overwriting a column type](/docs/{{version}}/crud-columns#overwriting-default-column-types), [creating a custom column type](/docs/{{version}}/crud-columns#creating-a-custom-column-type) +- Fields - [the 50+ field types](/docs/{{version}}/crud-fields#default-field-types), [fluent syntax](/docs/{{version}}/crud-fluent-syntax#fluent-fields-api), [array syntax](/docs/{{version}}/crud-fields#about), [overwriting a field type](/docs/{{version}}/crud-fields#overwriting-default-field-types), [custom field types](/docs/{{version}}/crud-fields#creating-a-custom-field-type) +- [the contents of an Operation - sidebar item, routes, controller](/docs/{{version}}/crud-operations#contents-of-a-custom-operation) + +---- + + +Thank you for dedicating these 31 minutes to learning Backpack. **You should now be able to build your first admin panel.** But if you feel like you're _not quite ready yet_, here are a few more things you can do: + +- [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/5.x/index.md b/5.x/index.md new file mode 100644 index 00000000..80ccbc44 --- /dev/null +++ b/5.x/index.md @@ -0,0 +1,61 @@ +#### 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) + +#### Admin UI + +- [About](/docs/{{version}}/base-about) +- [Alerts](/docs/{{version}}/base-alerts) +- [Breadcrumbs](/docs/{{version}}/base-breadcrumbs) +- [Widgets](/docs/{{version}}/base-widgets) +- [FAQ](/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) + + [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) diff --git a/5.x/install-optionals.md b/5.x/install-optionals.md new file mode 100644 index 00000000..bebbc755 --- /dev/null +++ b/5.x/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 sidebar_content item for it +php artisan backpack:add-sidebar-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/base/inc/sidebar_content.blade.php or menu.blade.php: + +```bash +php artisan backpack:add-sidebar-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 to the sidebar_content file +php artisan backpack:add-sidebar-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/5.x/installation.md b/5.x/installation.md new file mode 100644 index 00000000..66f87a31 --- /dev/null +++ b/5.x/installation.md @@ -0,0 +1,84 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 8 or 9, 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/9.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, in your terminal, then: + +``` 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 and 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 configuration values in ```config/backpack/base.php``` to make the admin panel your own. Backpack is white label, so you can change everything: menu color, project name, 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 (singular) +``` + +Alternatively, you can generate CRUDs for all Eloqunet models, by running: + +```bash +php artisan backpack:build +``` + +Then go through each CRUD file (Controller, Request, Route Item, Sidebar Item) and customize as you fit. If you don't know what those are, and how you can customize them... please take 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#manually-install-backpack), which you can tweak to your needs. diff --git a/5.x/introduction.md b/5.x/introduction.md new file mode 100644 index 00000000..decfe0f3 --- /dev/null +++ b/5.x/introduction.md @@ -0,0 +1,116 @@ +# 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: + +- Backpack will provide you with a _visual interface_ for the admin panel (the HTML, the CSS, the JS); it pulls in the excellent [CoreUI](https://coreui.io/) theme, with our own design called [Backstrap](https://backstrap.net), and adds authentication functionality & bubble notifications; when you decide to build a custom feature for your admin panel, you already have the HTML blocks for the UI, and it will look good; +- 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 - [`laracasts/generators`](https://github.com/laracasts/Laravel-5-Generators-Extended) as the best **command-line tool** for this; +- 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)** - 31 minutes +- **[Text Course](/docs/{{version}}/getting-started-basics)** - 20 minutes +- **[Email Course](https://backpackforlaravel.com/getting-started-emails)** - 1 email per day, for 4 days, 5 minutes each + + +
    + Video Course + Text Course + Email Course + +--- + + +## Need to Know + + +### Requirements + + - Laravel 9.x or 8.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 v5 is the current version, and is being 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 + +Starting with v5, Backpack has become open-core. Its features have been separated into two packages: +- **Backpack CRUD is licensed under the [MIT License](https://github.com/Laravel-Backpack/CRUD/blob/main/LICENSE.md)** (open-source free software); 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 licensed under our [EULA](https://backpackforlaravel.com/eula)**; it is a closed-source, paid add-on; [PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) will be useful when your admin panel needs grow, because it adds adds A LOT of features for complex use cases (see our [FREE vs PRO comparison](https://backpackforlaravel.com/docs/5.x/features-free-vs-paid)); + +[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 Backpack v5, all our packages follow [semantic versioning](https://semver.org/). Here's what `major.minor.patch` (eg. `5.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 addons receive support for it the same day. + +When you buy a premium Backpack addon, you get access to not only _updates_, but also _upgrades_ (for 12mo), that means that... **any time you buy a Backpack addon, it is very likely that you're not only buying the _current_ version** (`v5` at the moment), **but also the upgrade to the _next version_** (`v6` 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 addons, there are a few other addons 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 addons page](/addons). diff --git a/5.x/release-notes.md b/5.x/release-notes.md new file mode 100644 index 00000000..6354260f --- /dev/null +++ b/5.x/release-notes.md @@ -0,0 +1,272 @@ +# Release Notes + +--- + +**Launch date:** Feb 9th, 2022 + +Backpack v5 is a major release, but NOT a huge time-consuming upgrade. Think of it as a "4.2" release, that we've had to name "v5", because we've also changed our business model. We have added new features and major improvements, but also kept the upgrading process as easy as possible. Plus... + +> 🎉   🎉   🎉   **Backpack v5 is a FREE upgrade, for everybody who's purchased a Backpack v4 license after 9 Feb 2021.** Please read on to understand why.   🎉   🎉   🎉 + + +But yes, **Backpack v5 is a MAJOR new version**, with 8+ months of work put into it, and major improvements under-the-hood. It is the current and recommended version of Backpack and it's got so many cool new things... that we couldn't fit them all here. + +Here are the big things Backpack v5 brings to the table and why you should upgrade from [Backpack 4.1](/docs/4.1) to v5. But first... + + +## New Pricing + +**The good news** - if you've purchased Backpack v4 after 9 Feb 2021: +- you have access to all v5.x.x updates for 12 months from your purchase date; +- if we release a v6 in those 12 months, you'll also have access to the v6 upgrade; +- even after those 12 months go by, you'll still have access to those versions, for ever; +- you can [generate a token & password for v5 automatically, in your Backpack acount](https://backpackforlaravel.com/user/tokens); + +Basically, if you expected your updates to end... because we launched a new version... they don't. We think it's important that when you purchase Backpack, you get an extraordinary bang-for-buck. So if you've purchased towards the end of Backpack's release cycle, we've made sure that you receive at least 12 months of updates & upgrades... which means... free upgrade to v5. We believe that's the _fair_ thing to do - give you guarantees that whenever you buy Backpack, you'll receive updates for a reasonable amount of time. It's one of the reasons we changed our pricing. + +**The bad news**... it's a little more difficult to _really_ understand why we've done that. If you're interested, please read on, it makes perfect sense, we promise. If not, just read what's in **bold**. + +### Backpack is now open-core + +We try not to make a big fuss out of this, but this is HUGE deal. One that we consider a _bug fix_. Yes, it's a _licensing bug fix_. + +Previously, when you were buying a Backpack v4 license, you didn't know how many updates you're getting. Depending on _when_ you purchased, you could get 2 years of updates or 2 days. You'd then have to purchase the next version (using the limited-time discount we always give when launching). We consider that a stressful experience, both for you and us, so we've found a way to eliminate that stress, and make the whole buying, maintaining, updating and upgrading process a lot more _fair_, for all parties involved: + +**Starting with v5, the features that were previously in "Backpack\CRUD" are now split between two packages:** +- **[Backpack\CRUD](https://github.com/laravel-backpack/crud) is the free & open-source core**, released under the [MIT License](https://github.com/Laravel-Backpack/CRUD/blob/master/LICENSE.md); FREE +- **[Backpack\PRO](https://backpackforlaravel.com/products/pro-for-unlimited-projects) is a paid closed-source Backpack add-on**, released under our [EULA](https://backpackforlaravel.com/eula); PRO + +Since `backpack/pro` is released under the same terms of our `backpack/devtools` package: +- you get 12 months of updates _and upgrades_, guaranteed; +- after those 12 months you still have access to all versions you purchased; + +We're very happy with this new pricing, because we believe it is _fair to everyone_: +- Open-source devs - you can now use Backpack for open-source projects; +- Devs with low/no budget - you can now use Backpack\CRUD: no strings attached, no application form; +- Professionals - when you buy `backpack/pro`, you get 12 months of updates and upgrades, _guaranteed_; no more worries about where we are in the release cycle; it's _always_ a good time to buy Backpack; +- Maintainers - less piracy, more time & resources to spend on adding new features; + +You can see the result of this change on [our pricing page](https://backpackforlaravel.com/pricing). We hope now you understand _why_ we're giving free access to v5 to everyone who purchased after 9 Feb 2021. We consider it _a bug fix_. We think it's only _fair_ for a dev who purchased in 2021 to _also_ get what we offer now... which is 12 months of updates & upgrades. So we're activating this policy retroactively. + +**After this open-core split, we no longer offer non-commercial licenses for free, because our core offering is now free. So we've disabled our application form.** But... if you've previously received a free non-commercial Backpack v4 license, we've got your back. Reach out by email, we'll give you access to `backpack/pro` if your project is still non-commercial. + + + +## Added + +### Operations + +#### Warning before leaving the Create or Update form + +For large forms, it's often smart to ask the user if they really want to lose their form changes, before they leave the page. That's exactly what `warnBeforeLeaving` will do. Just call it in your `setup()` method for all operations that use a form: `CRUD::set('warnBeforeLeaving', true)`. Or if you want only in specific operations like `setupCreateOperation()` or `setupUpdateOperation()` set it as a setting for the operation: `CRUD::setOperationSetting('warnBeforeLeaving', true)`. + + +#### Quickly validate forms inside CrudController, no FormRequest needed + +Previously, you could validate the Create & Update forms in one way - by telling Backpack which FormRequest holds the validation rules: `CRUD::setValidation(TagRequest::class)`. Which works very well for large models. But for very simple models... like Tags... do you really _need_ a `TagRequest` to say the name is required? If you've found that inconvenient, we have good news: + +```php +protected function setupCreateOperation() +{ + // instead of these (which still work, btw) + $this->crud->setValidation(TagRequest::class); + $this->crud->setValidation('App\Http\Requests\TagRequest'); + + // you can now also do this + $this->crud->setValidation([ + 'name' => 'required|min:2', + ]); + + // you even define custom validation messages, using the second parameter + $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); +} +``` + +#### Use Model Events inside CrudControllers + +You can now define closures to be run when Model Events get triggered, right inside your CrudController: + +```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 + }); +} + +public function setupCreateOperation() +{ + // this will only get triggered inside the Create operation because + // that's where we've defined it + Monster::creating(function ($entry) { + // TODO: something to the entry + }); +} +``` + +This is particularly useful if you've ever overridden your `create()` or `store()` methods in your CrudController. Now... you don't really have to do that any more, you can do stuff directly on the model, upon an event. But it's not limited to that use case, since it supports [all Eloquent events](https://laravel.com/docs/master/eloquent#events). + +
    + +### Fields + +#### Define validation rules directly on fields + +In addition to the array validation above... Backpack v5 has one more trick up its sleeve. You can now also define the validation rules and validation messages right when you define the field. No more `FormRequest`, not even defining a validation array separately: + +```php +protected function setupCreateOperation() +{ + $this->crud->addField([ + 'name' => 'content', + 'label' => 'Content', + 'type' => 'ckeditor', + 'placeholder' => 'Your text here', + 'validationRules' => 'required|min:10', + 'validationMessages' => [ + 'required' => 'You gotta write smth man.', + 'min' => 'More than 10 characters, bro. Wtf... You can do this!', + ] + ]); + // This MUST be called AFTER the fields are defined, never before. + $this->crud->setValidation(); +} +``` + +In short, just call `CRUD::setValidation()` without passing anything and it will validate all the fields that have `validationRules` defined. But be careful - you have to call `setValidation()` _after_ you've defined your fields. + + +#### Define Model Events directly on fields + +Previously, if you wanted to change the value of a field when retrieving it or storing in the database, Backpack encouraged you to use Accessors and Mutators. You can still do that, they're stock Laravel features and they'll work fine. But sometimes... you might not want to change the Model itself. + +Now, you can define closures to run when standard Eloquent events are triggered, right on your fields: + +```php +// using the ARRAY SYNTAX, define an array of events and closures +CRUD::addField([ + 'name' => 'name', + 'events' => [ + 'saving' => function ($entry) { + $entry->name = strtoupper($entry->name); + }, + ], +]); + +// using the FLUENT SYNTAX, you can define the same array +CRUD::field('name')->events([ + 'saving' => function ($entry) { + $entry->name = strtoupper($entry->name); + }, +]); + +// BUT you can also use the convenience method "on" to define just ONE event +CRUD::field('name')->on('saving', function ($entry) { + $entry->name = strtoupper($entry->name); +}); +``` + +It supports [all Eloquent events](https://laravel.com/docs/master/eloquent#events), but... the ones that make sense are just: ~~`retrieved`~~, `creating`, `created`, `updating`, `updated`, `saving`, `saved`, ~~`deleting`~~, ~~`deleted`~~, ~~`restoring`~~, ~~`restored`~~, and ~~`replicating`~~, because those are the events that actually take place inside the Create and Update operations. + +We think you'll find this particularly useful for the `image`, `upload` and `upload_multiple` fields. Previously, they required you to create Mutators for them to work. Which meant you had to go to the Model to do that. But now... you can just do that in your CrudController: + +```php +CRUD::field('attachment')->on('saving', function ($entry) { + $value = request()->file('upload'); + + if (!is_null($value)) { + $entry->uploadFileToDisk($value, 'upload', 'public', 'uploads/monsters/upload_field'); + } +}); +``` + +#### Simple validation for `repeatable` entries + +Previously, in order to validate the contents of a subfield in `repeatable`, you had to write your own custom validation, which could take you quite some time. Now, you can just do `'testimonial.*.name' => 'required|min:5|max:256'` and use [Laravel's nested array validation](https://laravel.com/docs/8.x/validation#validating-nested-array-input). + + +#### Better than ever `relationship` field, now with `subfields` too + +We've spent _months_ building up and polishing the `relationship` field. It's now a one-stop-shop for all your needs, when creating/editing a relationship. Because it supports all common Eloquent 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 + +**One HUGE addition we've made in v5 is... 🥁🥁🥁 subfields.** For complex relationship, just define `subfields` and the select2 will be turned into a subform, allowing your admin to define extra attributes. This will save you _hours_ or even _days_ when you have complex relationships. Because right in the current form, your admin can now easily: +- [create/update/delete a related 1-1 entry](/docs/{{version}}/crud-how-to#hasone-1-1-relationship) (`hasOne` and `morphOne`, eg. User and UserDetail) +- [create/update/delete related 1-n entries](/docs/{{version}}/crud-fields#manage-related-entries-in-the-same-form-create-update-delete) (`hasMany` and `morphMany`, eg. Invoice and InvoiceItem) +- [edit columns on the pivot table of n-n relationships](/docs/{{version}}/crud-fields#save-additional-data-to-pivot-table) (`belongsToMany` and `morphToMany`, eg. 'position' on company_user) + +Check out the links above for more info, but in short, `subfields` will look and work just as you'd expect, like a `repeatable` field (because, under the hood, that's what it uses): + +![](https://backpackforlaravel.com/uploads/docs-4-2/fields/repeatable_hasMany_entries.png) + +Achieving this feature in a "general" way was crazy-difficult. All the credit here goes to our team member Pedro Martins (@pxpm) - he's been a real hero, working on this for _months_, so that you can do it in _seconds_. + +
    + +### Widgets + +#### New widget - `script` + +You can now easily "push" a JS file to any page you want, straight from a Controller or view. This is particularly useful when you want something custom done inside a CRUD Operation. See [the `script` widget docs](/docs/{{version}}/base-widgets#script-1) for more details. + +#### New widget - `style` + +You can now easily "push" a CSS file to any page you want, straight from a Controller or view. This is particularly useful when you want something custom done inside a CRUD Operation. See [the `script` widget docs](/docs/{{version}}/base-widgets#style-1) for more details. + +
    + +### Developer Experience + +#### Easily Debug AJAX Errors using the Error Frame Modal + +Did your AJAX request error? No need to dig into your Chrome Devtools and read that awful output. When you have your APP_DEBUG to `true`, Backpack will show that error in a modal. This seems like a small thing, but it makes it _much_ easier to work with some complex Backpack features, like [ListOperation](https://backpackforlaravel.com/docs/4.1/crud-operation-list-entries), [InlineCreateOperation](https://backpackforlaravel.com/docs/4.1/crud-operation-inline-create), [filters](https://backpackforlaravel.com/docs/4.1/crud-filters), [`select2_from_ajax`](https://backpackforlaravel.com/docs/4.1/crud-fields#select2_from_ajax) field, [`relationship`](https://backpackforlaravel.com/docs/4.1/crud-fields#relationship-1) field. During development, if something goes wrong, Backpack will show it to you - it's that simple: + +![image](https://user-images.githubusercontent.com/1838187/141500871-039ef53f-4207-4018-ac93-c41b547b62a5.png) + +
    + + + +## Changed + +#### Easily Include CSS and JS Assets Only Once Per Page + +Backpack now ships with a new `@loadOnce()` directive, which makes sure that piece of JS/CSS/code is only loaded once per pageload. You can see [more info about it here](https://github.com/digitallyhappy/assets) (and why it's more than `@once`). + +Of course, Backpack now uses this directive in all views that could get repeatedly loaded, so for example if you're loading in a single form a `select2`, a `select2_multiple` and a `select2_from_ajax`... you won't be getting the assets three times... but once. + +#### Increased Default Security + +Starting with v5, Backpack will throttle password request attempts. This will prevent malicious actors from using the "reset password" functionality of your admin panel to send unsolicited emails. + +In addition, most column types now escape the output by default. This will better prevent your admins from XSS attacks, in case where you do not trust the information in the database to not contain JavaScript payloads. + + +## Removed + +- Support for Laravel 6; +- Support for Laravel 7; +- Support for PHP lower than 7.3 (since Laravel 8 does not support them); +- `simplemde` field, in favor of `easymde`; + +--- + +In order to get all of the features above (and a few more hidden gems), please [follow the upgrade guide](/docs/{{version}}/upgrade-guide), to get from Backpack 4.1 to Backpack v5. diff --git a/5.x/upgrade-guide.md b/5.x/upgrade-guide.md new file mode 100644 index 00000000..82ba76c4 --- /dev/null +++ b/5.x/upgrade-guide.md @@ -0,0 +1,327 @@ +# Upgrade Guide + +--- + +This will guide you to upgrade from Backpack 4.1 to v5. The steps are color-coded by the probability that you will need it for your application: High, Medium and Low. + + +## 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.x, 7.4 or 7.3 +- Laravel 9.x or 8.x +- Backpack\CRUD 4.1.x +- 10-15 minutes (for most projects) + +**If you're running Backpack version 3.x or 4.0, please follow ALL other upgrade guides first, to incrementally get to use Backpack 4.1**. Test that your app works well with each version, after each upgrade. Only _afterwards_ can you follow this guide, to upgrade from 4.1 to v5. Previous upgrade guides: +- [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 + +The upgrade guide might seem long or intimidating, but really, it's an easy upgrade. **Most projects will only be affected by a few of the breaking changes outlined below**. And when there are changes needed, they're pretty small. + +Please **go thorough all steps**, to ensure a smooth upgrade process. The steps are color-coded by how likely we think that step is needed for your project: High, Medium and Low. **At the very least, read what's in bold**. + +
    + + +Step 0. **[Upgrade to Laravel 8](https://laravel.com/docs/8.x/upgrade) if you don't use it yet, then test to confirm your app is working fine.** If you also want to upgrade to [Laravel 9](https://laravel.com/docs/9.x/upgrade), we recommend you do that _after_ you've upgraded Backpack to v5. It'll be easier that way. + + +### Composer + +Step 1. Update your ```composer.json``` file to require: + +``` + "backpack/crud": "^5.0.0", + "backpack/pro": "^1.0.0", +``` + +> If the spit between these two packages is news to you, please [read the open-core section of the release notes](https://backpackforlaravel.com/docs/5.x/release-notes#backpack-is-now-open-core), to understand how this affects you. + +These two packages together will help you have all the features in v4.1 and more. But since `backpack/pro` is a closed-source package, to download it, you need to generate [your token and password here](https://backpackforlaravel.com/user/tokens). If no button is there for you, it means you don't have free access to `backpack/pro`, so you'll need to [purchase it](https://backpackforlaravel.com/pricing). **[Follow the 2-step process called "Instructions" in your token](https://backpackforlaravel.com/user/tokens), if you haven't already done that on this project.** + + +Step 2. If you have other backpack addons installed (eg. Backpack\PermissionManager), **most of them don't need a version bump**, but you should take into consideration that some might need (eg: MenuCRUD). However, if you have third-party Backpack add-ons installed, you might want to bump their versions - please check each addon's page. + +Step 3. Run ```composer update``` in the command line. + +> If you get any conflicts with **backpack first party addons** most of the time is just moving one version up, eg: `backpack/menucrud: ^2.0` to `backpack/menucrud: ^3.0`. + + +### Models + +Step 4. The `repeatable` field has been completely rewritten, in order to work with values as a nested PHP array, not a JSON. This has HUGE benefits, but it does present some small breaking changes. **If you've used the `repeatable` field in your CRUDs** +- please make sure that db column is cast as `array` or `json` (eg. add `protected $casts = ['testimonials' => 'array'];` to your model); +- a data syntax bug has been fixed - previously, if inside `repeatable` you used a subfield with multiple values (`select_multiple` or `select2_multiple` etc.), it would not store `'categories': [1, 2]` like you'd expect, but `'categories[]': [1, 2]`; those brackets were not intentional, but we could not fix them without telling you about it; so, when upgrading to v5: + - if you've coded a workaround to strip those brackets, you can now remove the workaround; + - if you use the attribute with brackets anywhere, please expect it to be either _with_ or _without_ brackets; Backpack will NOT strip all the brackets automatically, it will only strip them upon saving, when an admin edits that particular entry; + + +### Form Requests + +Step 5. **For `repeatable` fields, you no longer have to create custom validation logic. You can now use Laravel's [nested array validation](https://laravel.com/docs/validation#validating-nested-array-input).** If you've used a `repeatable` in your CRUDs and validated it in your FormRequest: +- you can keep your custom validation logic, but make sure you no longer `json_decode()` the value in the input; if you've used our validation example, that means instead of `$fieldGroups = json_decode($value);` you should do `$fieldGroups = is_array($value) ? $value : [];`; +- you can rewrite that validation in a cleaner and more concise way, using [Laravel's nested array input validation rules](https://laravel.com/docs/validation#validating-nested-array-input); for example, you can do `'testimonial.*.name' => 'required|min:5|max:256'` to validate all `name` subfields inside a repeatable `testimonial`; + + +### Routes + +No changes needed. + + +### Config + +Step 6. Security improvement: Starting with v5, Backpack will throttle password request attempts, so that a malicious actor cannot use the "reset password" functionality to send unsolicited emails. We recommend you add this configuration entry to your `config/backpack/base.php` (before "_Authentication_"). See [#3862](https://github.com/Laravel-Backpack/CRUD/pull/3862) for details. + +```php + /* + |-------------------------------------------------------------------------- + | Security + |-------------------------------------------------------------------------- + */ + + // Backpack will prevent visitors from requesting password recovery too many times + // for a certain email, to make sure they cannot be spammed that way. + // How many seconds should a visitor wait, after they've requested a + // password reset, before they can try again for the same email? + 'password_recovery_throttle_notifications' => 600, // time in seconds + + // Backpack will prevent an IP from trying to reset the password too many times, + // so that a malicious actor cannot try too many emails, too see if they have + // accounts or to increase the AWS/SendGrid/etc bill. + // + // How many times in any given time period should the user be allowed to + // attempt a password reset? Take into account that user might wrongly + // type an email at first, so at least allow one more try. + // Defaults to 3,10 - 3 times in 10 minutes. + 'password_recovery_throttle_access' => '3,10', +``` + +---- + + +Step 7. **Operation configurations have been moved from `config/backpack/crud.php`, each to its own file.** That way, if an operation config value isn't present, it will fall back to Backpack's default value. To upgrade, please: +- run `php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=config` - this will publish a new config file for each operation (eg. `config/backpack/operations/create.php`, `update.php` etc.); +- if you've changed things inside your `config/backpack/crud.php`, then make the same changes inside the config files you've published above; +- in your `config/backpack/crud.php` delete the `operations` array entirely; + +---- + +Step 8. Inside `config/backpack/crud.php`, you were previously able to change what inputs are stripped from the request before saving, by configuring `saveAllInputsExcept` for the Create and Update operations. We have moved the configuration to `config/backpack/operations/create.php` & `config/backpack/operations/update.php`, then: +- renamed it to `strippedRequest`; +- given you the possibility to do whatever you want to the request,by allowing you to add an `invokable` class, acting like a closure, that backpack will run to strip the request. +- kept the default behaviour; if `strippedRequest` is undefined or false, Backpack will strip all inputs that don't have fields, which we consider is the safest approach; + +**If in your `config/backpack/operations/create.php` or `config/backpack/operations/update.php` you've copied `saveAllInputsExcept` as `null` or `false`, you don't have to do anything.** + +However, **if you have an array for your `saveAllInputsExcept`**, you can now achieve the same thing by stripping the request yourself in an invokable class. Basically you first create the invokable class and then add it into the config: +```php +except('_token', '_method', '_http_referrer', '_current_tab', '_save_action'); + } +} +``` +```diff +- 'saveAllInputsExcept' => ['_token', '_method', 'http_referrer', 'current_tab', 'save_action'], ++ 'strippedRequest' => '\App\Http\Requests\StripBackpackRequest', ++ }), +``` +But you can also do a lot more, because you have the `$request` in that class. You can see [an example here]( https://backpackforlaravel.com/docs/5.x/crud-how-to#add-non-editable-input-inside-create-or-update-operation-stripped-request). +In addition, please notice that **all hidden parameters are now prefixed by an underscore**. Starting with v5, if it starts with an underscore, you know it's not an actual database column. + +---- + +Step 9. The **Show operation** will now show `created_at`, `updated_at` columns by default, if they exist. In most cases, this is what you want - show as many things as possible inside the Show operation - and `created_at` and `updated_at` provide some useful information about the entry. So it should NOT negatively affect you. But if you _don't_ want to show those columns, you can turn off this new default behavior in your `config/backpack/operations/show.php`, by defining `'timestamps' => false,`. You can also do `'softDeletes' => true,` if you want to show that column, by the way. + + +### CrudControllers + +Step 10. We've improved the guessing of column types, by also taking into consideration your Model casts ([PR here](https://github.com/Laravel-Backpack/CRUD/pull/3618)). If you have places in your CrudControllers where you have NOT defined a column type (and just assumed it'll probably be `text`), but that item is cast as `date`, `json`, `array` etc... Backpack will now try to show a more appropriate column type for it, instead of `text`. This is unlikely to affect you negatively, but... it's not a terrible idea to go through all your ListOperation and ShowOperation views, to make sure you're happy with what's displayed. + +---- + +Step 11. There have been some **changes in how the `repeatable` field works** by default: +- it now shows no rows when empty (previously it was showing one); we believe that provides a better UX for most projects, but if you don't like it, please define `'min_rows' => 1`; +- we're renamed the `fields` attribute to `subfields` for more clarity; +- if you have subfields that do NOT have their type defined, Backpack will now assume you wanted a `text` field; +- we've fixed a bug in the unofficial "_subfield type guessing_" functionality: previously, if you added a subfield with the name `category` (for example), and your main entity happened to have a `category` relationship on it, then the repeatable field would show a relationship field, which is probably not what you wanted; + +---- + +Step 12. We have **removed the `simplemde` field**, since that JS library hasn't received any updates since 2016. However, there is a drop-in replacement called `easymde` which is well maintained. If you're using the `simplemde` field type anywhere in your project, please use `easymde` instead. A simple find & replace in your Controllers should do. + +---- + +Step 13. If you're using the **Reorder operation** _and_ have overridden some of its functionality in your CrudController, please take note that the information is now passed as JSON. Replicate the [small changes here](https://github.com/Laravel-Backpack/CRUD/pull/3808/files) in your custom code too. + +---- + +Step 14. When setting up your **Show operation** in Backpack 4.1, after your `setupShowOperation()` was run, Backpack would try to add more columns. That made it very difficult to delete auto-added columns. You might have used a workaround for this. + +Starting with Backpack v5, the operation will no longer do any "_automatic setup_" inside `setupShowOperation()`... nor after it. Instead, if you want Backpack to guess column names, you have to manually call a new method, `$this->autoSetupShowOperation()`. To upgrade: +- if you _do not_ have a `setupShowOperation()` in your CrudControllers, you are not affected by this - don't worry; +- if you _do_ have a `setupShowOperation()` method in your CrudControllers: + - **to keep the same behaviour as before (guess columns), please run `$this->autoSetupShowOperation();` wherever you want inside your `setupShowOperation()`;** add it to the end of your `setupShowOperation()` to keep the same behaviour as before; + - if you have any workarounds for the problem mentioned above (difficult to delete columns in `setupShowOperation()`), call `$this->autoSetupShowOperation();` at the start of your `setupShowOperation()` call and eliminate your workarounds, it should now "_just work_"; + +---- + +Step 15. The **Create and Update operations** no longer save the `request()`, they save your `ProductFormRequest` (the one that contains the validation). **If you have NOT modified the `request()` or `CRUD::getRequest()` in any of your CrudControllers, you will not be affected by this, move on.** + +However, **if you _have_ modified the request (most likely to add or remove inputs)**, those changes will never reach the database now. To give you an example, if you've ever overridden the `store()` or `update()` methods to add an input, it might look something like this: +```php +use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; } + +public function store() +{ + $this->crud->setOperationSetting('saveAllInputsExcept', ['save_action', 'token', 'http_referrer']); + $this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]); + + return $this->traitStore(); +} +``` + +That will no longer work, because Backpack won't save the `request()` or `$this->crud->getRequest()` any more, but your `ProductRequest`. But there are now more ways than ever to achieve the same thing. Please read all solutions below and decide which one is best for you: + +**Solution (A)** + +That actually means it's easier to change the request now inside a CrudController, but you need to do it inside the `strippedRequest` closure: + +```php +public function store() +{ + $this->crud->setOperationSetting('strippedRequest', function($request) { + $request->request->add([ 'updated_by' => backpack_user()->id ]); + return $request->except(['_save_action', '_token', '_http_referrer']); + }); + + return $this->traitStore(); +} +``` + +**Solution (B)** + +If you feel overriding the `store()` or `update()` methods was messy... you're not alone. We have good news for you, you can now move that logic to your `ProductRequest`, for example inside the lesser-known `prepareForValidation()` method: +```php +// app/Http/Requests/ProductRequest.php + + protected function prepareForValidation() + { + \CRUD::setOperationSetting('strippedRequest', function ($request) { + $input = $request->only(\CRUD::getAllFieldNames()); + $input['updated_by'] = backpack_user()->id; + + return $input; + }); + } +``` + +**Solution (C)** + +Alternatively... if you absolutely _hate_ this new behaviour and want your previous code to continue working, you can now easily tell Backpack to save the `CRUD::getRequest()`, for all CRUDs, in your `config/backpack/operations/create.php` and `config/backpack/operations/update.php`. That way, you can keep your old CrudController overrides: +```php + 'strippedRequest' => (function ($request) { + return CRUD::getRequest()->request->except('_token', '_method', '_http_referrer', '_current_tab', '_save_action'); + }), +``` + + +---- + +Step 16. If you've customized the saving process of the Create or Update operations (read: you've overridden the `store()` or `update()` methods), please take into consideration that starting with Backpack v5, **when a select multiple is emptied, it will still be part of the request, as `null`**. Whereas previously (if emptied) it was missing entirely. This applies to all `select` and `select2` fields when used as `multiple`. You might need to change your saving logic accordingly, instead of expecting them to be missing, to expect them to be `null`. + +---- + + +Step 17. The `page_or_link` field has been moved from `backpack/crud` to `backpack/menucrud` because it made little sense outside it. If you've used the `page_or_link` field anywhere in your CrudControllers: +- if you have `MenuCRUD` installed: + - bump the MenuCRUD version in your `composer.json` (`"backpack/menucrud": "^3.0.0"`) + - anywhere you've used the `page_or_link` field, make sure to specify its view_namespace (`'view_namespace' => 'menucrud::fields'`); +- if you DO NOT have `MenuCRUD` installed; + - tell us that this has affected you, in [this PR](https://github.com/Laravel-Backpack/CRUD/pull/3459); + - you can create a new file in your `resources/views/vendor/backpack/fields/page_or_link.blade.php` and paste [the content from here](https://raw.githubusercontent.com/Laravel-Backpack/MenuCRUD/e0afc7960d5513017ef847480f50591400bd4a6b/src/resources/views/fields/page_or_link.blade.php); + + + +### CSS & JS Assets + +Step 18. We've removed the custom CSS & JS files that Backpack provided for each operation (eg. `list.css` and `create.js` - [see why here](https://github.com/Laravel-Backpack/CRUD/pull/3942)). + +- If you've added any custom code in `public/packages/backpack/crud/css` and `public/packages/backpack/crud/js`, copy them to a different location (we suggest `public/assets/admin/css` and `public/assets/admin/js`). Then use the brand-new `script` and `style` widgets to load them only where you need them. See the [updated docs section](/docs/{{version}}/crud-how-to#add-css-and-js-to-a-page-or-operation) for more information. This is only needed if YOU have added any custom code there. The Backpack CSS that was there is now included in the `bundle.css` and `bundle.js` files. +- if you haven't modified those at all... it is now safe to delete the `public/packages/backpack/crud/css` and `public/packages/backpack/crud/js` directories - those files are no longer loaded. + +---- + +Step 19. We've updated a lot of CSS & JS dependencies to their latest versions - with one notable exception - Bootstrap. Since not all dependencies support Bootstrap 5 yet, we'll still be using Bootstrap 4 for a while. But as soon as that changes, we'll release a new version for that upgrade alone. This will also make it easier to upgrade - no worries that the interface breaks now, since we haven't upgraded Bootstrap. And thanks to our new business model - you'll most likely have access to the next Backpack version too. + +There are two ways to publish the latest styles and scripts for these dependencies: +- (A) If you have NOT touched you ```public/packages``` folder, or placed anything custom inside it: + - delete the ```public/packages``` directory and all its contents; + - run ```php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public``` + - if you use elFinder, also delete ```resources/views/vendor/elfinder``` and run ```php artisan backpack:filemanager:install``` +- (B) Run ```php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force```. Please note this will overwrite anything that's already there. This B solution has a downside: unused files are not removed. A few files Backpack no longer uses will still be in your ```public/packages``` folder, even though they're no longer used. + + +### Views + +Step 20. **Have you developed any custom fields or columns?** Rephrased: do you have anything inside your `resources/views/vendor/backpack/crud/fields` or `resources/views/vendor/backpack/crud/columns`? If so, and those fields or columns load any external CSS or JS, we recommended you load them using `@loadOnce('path/to/file.css')` and `@loadOnce('path/to/file.js')` instead of `` and ``. This will make sure that piece of JS/CSS/code is only loaded once per pageload. You can find [more info about it here](https://github.com/digitallyhappy/assets) (and why it's more than `@once`). Your custom fields should still work without this change, but it's such an easy one. + +---- + +Step 21. If you've overwritten any of the default operations in any way (blade files or PHP classes), take note that we've renamed the system GET/POST parameters (aka hidden inputs) - they're all prefixed by underscore now, to differentiate them from actual database columns. Please replace `http_referrer`, `locale`, `current_tab` with `_http_referrer`, `_locale`, `_current_tab`, respectively. [Take a look at the PR](https://github.com/Laravel-Backpack/CRUD/pull/3955/files) to see the affected files. In 99% of all cases you won't be affected by this, there's little reason to overwrite the default operations. This also applies if you've overridden the `SaveActions` or `form_content`. + +---- + +Step 22. If you've overwritten `resources/views/crud/form_content.blade.php` you may need to update it. +JS Fields API is now imported on that file, the easiest way is to add that include directly near the end of the file. +In most of the cases you won't be affected by this, but if you have this file in your project source, please make sure it includes the following line. + +```diff + ... + + ++ @include('crud::inc.form_fields_script') +@endsection +``` + + +### Security + +Step 23. By default, all columns now echo using `{{ }}` instead of `{!! !!}`. That means they "_escape the output_", assuming they contain strings, not HTML. This was done to increase _default security_, to protect the admin from any malicious strings that might have been stored in the database. There are two exceptions to this, two columns that are not `escaped` by default: `custom_html` and `markdown`, where Backpack assumes you store HTML. To upgrade: +- If you've been showing HTML using the `array`, `array_count`, `closure`, `model_function`, `model_function_attribute`, `relationship_count` or `textarea` columns, you can use `'escaped' => false` on those columns to go back to the previous behaviour. But please [read more about this](/docs/{{version}}/crud-columns#escape-column-output), it might be a good idea to sanitize your input/output if you've forgotten to do so. +- If you're using the `markdown` or `custom_html` columns, please note that they still DO NOT escape the output by default (since they most likely store HTML); make sure you've properly sanitized your input or output - it's super-easy using an [HTML Purifier package](https://github.com/mewebstudio/Purifier) (you can do that by casting the attribute to `CleanHtmlOutput::class` in your Model or [manually](https://github.com/Laravel-Backpack/demo/commit/7342cffb418bb568b9e4ee279859685ddc0456c1)); + + + +### Cache + +Step 24. 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. + +--- + +**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 v5](/docs/{{version}}/release-notes); diff --git a/6.x/add-ons-community.md b/6.x/add-ons-community.md new file mode 100644 index 00000000..70fea4c4 --- /dev/null +++ b/6.x/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/6.x/add-ons-custom-operation.md b/6.x/add-ons-custom-operation.md new file mode 100644 index 00000000..fa19ab02 --- /dev/null +++ b/6.x/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/6.x/add-ons-how-to-create-a-backpack-addon.md b/6.x/add-ons-how-to-create-a-backpack-addon.md new file mode 100644 index 00000000..41a8e7d7 --- /dev/null +++ b/6.x/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/6.x/add-ons-official.md b/6.x/add-ons-official.md new file mode 100644 index 00000000..f94e4c53 --- /dev/null +++ b/6.x/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/6.x/add-ons-tutorial-how-to-create-a-theme.md b/6.x/add-ons-tutorial-how-to-create-a-theme.md new file mode 100644 index 00000000..ed49d550 --- /dev/null +++ b/6.x/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(resource_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.footer')) +
    +
    + + @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/6.x/add-ons-tutorial-using-the-addon-skeleton.md b/6.x/add-ons-tutorial-using-the-addon-skeleton.md new file mode 100644 index 00000000..06e09c15 --- /dev/null +++ b/6.x/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/6.x/add-ons-tutorial.md b/6.x/add-ons-tutorial.md new file mode 100644 index 00000000..01da4d66 --- /dev/null +++ b/6.x/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/6.x/base-about.md b/6.x/base-about.md new file mode 100644 index 00000000..e1adba73 --- /dev/null +++ b/6.x/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(http://github.com/$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/6.x/base-alerts.md b/6.x/base-alerts.md new file mode 100644 index 00000000..d2c91701 --- /dev/null +++ b/6.x/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/6.x/base-breadcrumbs.md b/6.x/base-breadcrumbs.md new file mode 100644 index 00000000..1512ee39 --- /dev/null +++ b/6.x/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('http://github.com/dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('http://github.com/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('http://github.com/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('http://github.com/dashboard'), + 'Dashboard' => false, + ]; +@endphp + +@section('content') + some other content here +@endsection +``` diff --git a/6.x/base-components.md b/6.x/base-components.md new file mode 100644 index 00000000..8f61b769 --- /dev/null +++ b/6.x/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/6.x/base-how-to.md b/6.x/base-how-to.md new file mode 100644 index 00000000..3e136130 --- /dev/null +++ b/6.x/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('http://github.com/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/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/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/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/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/6.x/base-themes.md b/6.x/base-themes.md new file mode 100644 index 00000000..3ce384a1 --- /dev/null +++ b/6.x/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/6.x/base-widgets.md b/6.x/base-widgets.md new file mode 100644 index 00000000..d984ee64 --- /dev/null +++ b/6.x/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('http://github.com/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('http://github.com/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/6.x/crud-api.md b/6.x/crud-api.md new file mode 100644 index 00000000..c55e1f8b --- /dev/null +++ b/6.x/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/6.x/crud-basics.md b/6.x/crud-basics.md new file mode 100644 index 00000000..8e231a8b --- /dev/null +++ b/6.x/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/6.x/crud-buttons.md b/6.x/crud-buttons.md new file mode 100644 index 00000000..b0b064c0 --- /dev/null +++ b/6.x/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('http://github.com/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("http://github.com/invoice/$entry-%3Eid/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("http://github.com/invoice/$entry-%3Eid/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/6.x/crud-cheat-sheet.md b/6.x/crud-cheat-sheet.md new file mode 100644 index 00000000..74a74cff --- /dev/null +++ b/6.x/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/6.x/crud-columns.md b/6.x/crud-columns.md new file mode 100644 index 00000000..1ba20aa8 --- /dev/null +++ b/6.x/crud-columns.md @@ -0,0 +1,1785 @@ +# 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 + // OPTIONAL + 'separator' => ',', // if you want to use a different separator than the default ',' +], +``` + +
    + + +### 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. + //'element' => 'a' // let's you change the element of the link. + //'rel' => false OR 'rel' => 'noopener' // let's you disable or change the rel attribute of the link. +], +``` + +
    + + +### 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.category_id') + ->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('http://github.com/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('http://github.com/category/'.$related_key.'/show'); + }, +]); + +// or as a closure shortcut: +$this->crud->column('category')->linkTo(fn($entry, $related_key) => backpack_url('http://github.com/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/6.x/crud-fields-javascript-api.md b/6.x/crud-fields-javascript-api.md new file mode 100644 index 00000000..91113898 --- /dev/null +++ b/6.x/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/6.x/crud-fields.md b/6.x/crud-fields.md new file mode 100644 index 00000000..b054b92e --- /dev/null +++ b/6.x/crud-fields.md @@ -0,0 +1,2880 @@ +# 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' => '#232323', // in CoreUI v2 theme you can also specify bootstrap colors, like `primary`, `danger`, `success`, etc You can also overwrite the `--bg-switch-checked-color` css variable to change the color of the switch when it's checked + '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. + +**IMPORTANT NOTE**: Your key needs access to the following APIS: +- Maps JavaScript API; +- Places API; +- Geocoding API. + +While developing you can use an "unrestricted key" (no restrictions for where the key is used), but for production you should use a separate key, and **MAKE SURE** you restrict the usage of that key to your own domain. + +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' +], +``` + +**IMPORTANT NOTE**: Your key needs access to the following APIS: +- Maps JavaScript API; +- Places API; +- Geocoding API. + +While developing you can use an "unrestricted key" (no restrictions for where the key is used), but for production you should use a separate key, and **MAKE SURE** you restrict the usage of that key to your own domain. + +**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("http://github.com/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('http://github.com/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; + // 'sortable' => true, // requires the field to accept multiple values, and allow the selected options to be sorted. +]); +``` + +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("http://github.com/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("http://github.com/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('http://github.com/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/6.x/crud-filters.md b/6.x/crud-filters.md new file mode 100644 index 00000000..8a1221ae --- /dev/null +++ b/6.x/crud-filters.md @@ -0,0 +1,692 @@ +# 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('http://github.com/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('http://github.com/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 + + +### Add a debounce time to filters + +Filters can be debounced, so that the filtering logic is only applied after the user has stopped typing for a certain amount of time. This is useful when the filtering logic is expensive and you don't want it to run on every keystroke. To debounce a filter, you can use the following code: + +```php + +CRUD::filter('name') + ->type('text') + ->debounce(1000) // debounce time in milliseconds + ->whenActive(function($value) { + // CRUD::addClause('where', 'name', 'LIKE', "%$value%"); + }); +``` + +All filter types accept a `debounce`, like for example the simple filter, range filter etc. + + +### 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'); +}); +``` + + + +### When to use `addClause()` or `addBaseClause()` to apply filtering logic + +In most of our filter examples we use `CRUD::addClause()` to apply filtering logic to that Eloquent model. This allows you to call methods on that model, either standard Laravel methods like `where()` or custom methods like scopes: + +```php +CRUD::addClause('where', 'total', '=', '0'); // call standard laravel method "where" with params +CRUD::addClause('free'); // call free() on the Model, which would itself call scopeFree() +``` + +When the filter is selected, your List operation will also show a small text: "_Showing 1 to 10 entries (filtered from 998)_". + +But there's another method you can use to add filtering to the Eloquent model - `addBaseClause()`. This works EXACTLY the same as `addClause()` but the filtering happens _earlier_ in the lifecycle, before pagination is even calculated. That means your users will no longer see the end part of the text above. With the EXACT same filtering, happening before pagination is calculated, your text will become: "_Showing 1 to 10 entries._". In short: + +```php +CRUD::addClause('where', 'total', '=', '0'); // Showing 1 to 10 entries (filtered from 998) +CRUD::addBaseClause('where', 'total', '=', '0'); // Showing 1 to 10 entries +``` + +When should you use one or the other? +- in 99% of the cases you should probably use `addClause()`; it's more intuitive and informative for the admin, once they click a filter, to have feedback that what they see is a filtered list, out of a bigger list; +- in 1% of the cases though, when you want to actually _hide_ the total number of entries in the table, you should use `addBaseClause()` instead; for example, if you want to ONLY show the articles of the current logged in person, you would not want them to how many entries are in the database, so you should use `addBaseClause()`; diff --git a/6.x/crud-fluent-syntax.md b/6.x/crud-fluent-syntax.md new file mode 100644 index 00000000..f087704f --- /dev/null +++ b/6.x/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('http://github.com/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('http://github.com/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('http://github.com/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('http://github.com/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/6.x/crud-how-to.md b/6.x/crud-how-to.md new file mode 100644 index 00000000..8c98268c --- /dev/null +++ b/6.x/crud-how-to.md @@ -0,0 +1,989 @@ +# 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 + +See: [How to add extra CRUD routes](/docs/{{version}}/crud-operations#operation-routes) + + +## 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('http://github.com/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('http://github.com/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('http://github.com/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('http://github.com/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/6.x/crud-operation-clone.md b/6.x/crud-operation-clone.md new file mode 100644 index 00000000..e6b259d7 --- /dev/null +++ b/6.x/crud-operation-clone.md @@ -0,0 +1,146 @@ +# 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 +crud->set('clone.redirect_after_clone', true); + + // you can also use a closure to define the redirect URL + $this->crud->set('clone.redirect_after_clone', function($entry) { + return backpack_url('http://github.com/product/'.$entry-%3Eid.'/show'); // redirect to show view instead of edit + }); + } +} +``` + +This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### 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/6.x/crud-operation-create.md b/6.x/crud-operation-create.md new file mode 100644 index 00000000..32b3bc8a --- /dev/null +++ b/6.x/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/6.x/crud-operation-delete.md b/6.x/crud-operation-delete.md new file mode 100644 index 00000000..36fe3c89 --- /dev/null +++ b/6.x/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/6.x/crud-operation-fetch.md b/6.x/crud-operation-fetch.md new file mode 100644 index 00000000..cc13a988 --- /dev/null +++ b/6.x/crud-operation-fetch.md @@ -0,0 +1,198 @@ +# 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('http://github.com/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' => [] + ]); + } +``` + +**Adding attributes to fetched models without appends** + +It's already possible to add attributes to the fetched models using the `appends` property in the model. However, this method has some drawbacks, like the fact that the appended attributes will be added to all instances of the model, whenever they are used, not only when they are fetched. If you want to add attributes to the fetched models without using the `appends` property in the model, you can use the `append_attributes` in the fetch configuration. For example: + +```php +public function fetchUser() { + return $this->fetch([ + 'model' => User::class, + 'append_attributes' => ['something'], + ]); + } + +// User.php model +public function something(): Attribute +{ + return Attribute::make( + get: function (mixed $value, array $attributes) { + return $attributes['something_else']; + }, + ); +} + +// and in your field definition +CRUD::field('my_ajax_field')->attribute('something'); +``` + + +## 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('http://github.com/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/6.x/crud-operation-inline-create.md b/6.x/crud-operation-inline-create.md new file mode 100644 index 00000000..ad2447e5 --- /dev/null +++ b/6.x/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('http://github.com/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/6.x/crud-operation-list-entries.md b/6.x/crud-operation-list-entries.md new file mode 100644 index 00000000..fdd7c962 --- /dev/null +++ b/6.x/crud-operation-list-entries.md @@ -0,0 +1,479 @@ +# 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);` + +##### Available options + +Additionally you can control the dropdown behavior with `lineButtonsAsDropdownMinimum` and `lineButtonsAsDropdownShowBefore`. By default the dropdown is created no matter how many buttons are present in the line stack. You can change this behavior by setting `lineButtonsAsDropdownMinimum` to a number. If the number of buttons in the line stack is less than this number, the buttons will not be converted to a dropdown. You can also set `lineButtonsAsDropdownShowBefore` to a number to drop the buttons after that number of buttons. + +```php +CRUD::setOperationSetting('lineButtonsAsDropdown', true); +CRUD::setOperationSetting('lineButtonsAsDropdownMinimum', 5); // if there are less than 5 buttons, don't create the dropdown (default: 1) +CRUD::setOperationSetting('lineButtonsAsDropdownShowBefore', 3); // force the first 3 buttons to be inline, and the rest in a dropdown (default: 0) + +// in the above example, when: +// - there are 3 or less buttons, they will be shown inline +// - there are 4 buttons, all 4 will be shown inline +// - there are 5 or more buttons, the first 3 will be shown inline, and the rest in a dropdown +``` + +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 ```exportOnlyColumn => 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 +'exportOnlyColumn' => 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 `exportOnlyColumn => 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/6.x/crud-operation-reorder.md b/6.x/crud-operation-reorder.md new file mode 100644 index 00000000..a0eab207 --- /dev/null +++ b/6.x/crud-operation-reorder.md @@ -0,0 +1,158 @@ +# 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```. The names are optional, you can change them in the ```setupReorderOperation()``` method. + +```php + +protected function setupReorderOperation() +{ + CRUD::setOperationSetting('reorderColumnNames', [ + 'parent_id' => 'custom_parent_id', + 'lft' => 'left', + 'rgt' => 'right', + 'depth' => 'deep', + ]); +} +``` + +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/6.x/crud-operation-revisions.md b/6.x/crud-operation-revisions.md new file mode 100644 index 00000000..8f841408 --- /dev/null +++ b/6.x/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/6.x/crud-operation-trash.md b/6.x/crud-operation-trash.md new file mode 100644 index 00000000..e1b56b60 --- /dev/null +++ b/6.x/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/6.x/crud-operation-update.md b/6.x/crud-operation-update.md new file mode 100644 index 00000000..f994b6f9 --- /dev/null +++ b/6.x/crud-operation-update.md @@ -0,0 +1,457 @@ +# 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 to the **List Operation** afterwards. diff --git a/6.x/crud-operations.md b/6.x/crud-operations.md new file mode 100644 index 00000000..459b393c --- /dev/null +++ b/6.x/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('http://github.com/dashboard'), + $crud->entity_name_plural => url(http://github.com/$crud-%3Eroute), + '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/6.x/crud-save-actions.md b/6.x/crud-save-actions.md new file mode 100644 index 00000000..4fb61c85 --- /dev/null +++ b/6.x/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/6.x/crud-tutorial.md b/6.x/crud-tutorial.md new file mode 100644 index 00000000..dca4eccd --- /dev/null +++ b/6.x/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/6.x/crud-uploaders.md b/6.x/crud-uploaders.md new file mode 100644 index 00000000..2b49ef32 --- /dev/null +++ b/6.x/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/6.x/custom-validation-rules.md b/6.x/custom-validation-rules.md new file mode 100644 index 00000000..86cfd6b9 --- /dev/null +++ b/6.x/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/6.x/demo.md b/6.x/demo.md new file mode 100644 index 00000000..72394941 --- /dev/null +++ b/6.x/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/6.x/faq.md b/6.x/faq.md new file mode 100644 index 00000000..6491a5bf --- /dev/null +++ b/6.x/faq.md @@ -0,0 +1,251 @@ +# 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. + + +### Can I get a discount for university use? + +Yes, if you are a teacher at highschool or university and want to use Backpack to teach your students how to code, we're happy to give you a discount for our MULTI-PROJECT and EVERYTHING plans, so your students can quickly learn PHP, Laravel and Backpack. Reach out to us from your .edu email (or provide other proof). For this use case, please make sure to "refresh" your token every year, to prevent misuse. + + +### Can I get a discount for a backlink? + +Yes, but only for your second/third purchases. Before your license expires, you will receive an email offering you 20% discount if you include a backlink from your website to ours. Reply to that email with a link, and our team will help you get the discount. + +To get the 20% discount: +- include a backlink from your MAIN WEBSITE (not from your admin panel), to our main website: `https://backpackforlaravel.com?utm_source=customer_ref`; most people add a link to their footer; here are a few ideas: `Admin Panel by Backpack`, `Backoffice by Backpack`, `CMS Powered by Backpack`, `ERP Powered by Backpack`, `Powered By Backpack`; +- make sure the link is `dofollow` (regular link); +- make sure to keep the link there for 12 months; + + +## 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); + + +### How do I deploy Backpack to production? + +Deploying a Laravel+Backpack project to production isn't very different from deploying a normal Laravel project to production. You only need to account for Basset, the system in Backpack that publishes the CSS and JS assets your admin panel needs. + +That being said, here's a detailed step-by-step guide to deploying a Backpack project to production, that should work for most production servers: + +1. Local Preparations +Before deploying your application, make sure your development environment is in order: +- Update dependencies: +``` +composer install --no-dev --optimize-autoloader +npm install && npm run prod +``` +- Configure your .env file for production. This file should not be included in version control (git), but make sure to have a copy with production settings. + +2. Configure the Production Server +Ensure your production server meets the following requirements: + +Web Server: Nginx or Apache. +PHP: Version compatible with your Laravel version. +Database: MySQL, PostgreSQL, etc. +Composer: Installed globally. +Node.js and npm: If you are using asset compilation with Laravel Mix. + +3. Deploy the Code +There are several ways to deploy code to production, here’s one method using Git: +- Clone your repository on the server: +``` +git clone https://github.com/your_user/your_repository.git +cd your_repository +``` +- Copy your local .env file to the production environment and adjust the settings accordingly. +``` +APP_ENV=production +APP_URL=https://MY_DOMAIN.COM #This need to be correctly set for Basset +``` + +4. Install Dependencies +Install Composer and npm dependencies: +``` +composer install --no-dev --optimize-autoloader +npm install && npm run prod +``` + +5. Configure the Application +Make the necessary configurations for the application: +- Generate the application key: +``` +php artisan key:generate +``` +- Set permissions: +``` +sudo chown -R www-data:www-data storage +sudo chown -R www-data:www-data bootstrap/cache +sudo chmod -R 775 storage +sudo chmod -R 775 bootstrap/cache +``` +- Run database migrations: +``` +php artisan migrate --force +``` +- Optimize configuration: +``` +php artisan basset:clear +php artisan basset:cache +php artisan optimize:clear +php artisan optimize +``` + +6. Configure the Web Server +Set up your web server (Nginx or Apache) to point to the public directory of your Laravel application. Here’s an example Nginx configuration: +``` +server { + listen 80; + server_name your_domain.com; + root /path/to/your/project/public; + + index index.php index.html; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.ht { + deny all; + } +} +``` + +7. Configure Cron Jobs +If your Laravel application uses scheduled tasks, add the following line to your crontab: +``` +* * * * * cd /path/to/your/project && php artisan schedule:run >> /dev/null 2>&1 +``` + +8. Monitor and Maintain +Monitor your application in production: +- Logs: Check logs in storage/logs/laravel.log. +- Services: Ensure services like queue workers are running properly. + +9. Optimize for Production +Perform additional optimizations if necessary: +- Optimize Autoload: +``` +composer dump-autoload --optimize +``` +- Disable Debug in .env: +``` +APP_DEBUG=false +``` +- Clean & Set config cache +``` +php artisan config:clear +php artisan config:cache +``` diff --git a/6.x/features-free-vs-paid.md b/6.x/features-free-vs-paid.md new file mode 100644 index 00000000..997825e8 --- /dev/null +++ b/6.x/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/6.x/generating-code.md b/6.x/generating-code.md new file mode 100644 index 00000000..a73cc55c --- /dev/null +++ b/6.x/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/6.x/getting-started-advanced-features.md b/6.x/getting-started-advanced-features.md new file mode 100644 index 00000000..f1c3ff57 --- /dev/null +++ b/6.x/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/6.x/getting-started-basics.md b/6.x/getting-started-basics.md new file mode 100644 index 00000000..3f2e947f --- /dev/null +++ b/6.x/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/6.x/getting-started-crud-operations.md b/6.x/getting-started-crud-operations.md new file mode 100644 index 00000000..0c79813c --- /dev/null +++ b/6.x/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/6.x/getting-started-license-and-support.md b/6.x/getting-started-license-and-support.md new file mode 100644 index 00000000..fed5a439 --- /dev/null +++ b/6.x/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/6.x/getting-started-videos.md b/6.x/getting-started-videos.md new file mode 100644 index 00000000..ae0f8de0 --- /dev/null +++ b/6.x/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/6.x/index.md b/6.x/index.md new file mode 100644 index 00000000..cb6360d6 --- /dev/null +++ b/6.x/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/6.x/install-optionals.md b/6.x/install-optionals.md new file mode 100644 index 00000000..e297b5fb --- /dev/null +++ b/6.x/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/6.x/installation.md b/6.x/installation.md new file mode 100644 index 00000000..8fc6859d --- /dev/null +++ b/6.x/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/6.x/introduction.md b/6.x/introduction.md new file mode 100644 index 00000000..b7d36647 --- /dev/null +++ b/6.x/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 or 12.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, which 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. `6.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** (`v6` at the moment), **but also the upgrade to the _next version_** (`v7` 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/6.x/release-notes.md b/6.x/release-notes.md new file mode 100644 index 00000000..514f6598 --- /dev/null +++ b/6.x/release-notes.md @@ -0,0 +1,234 @@ +# Release Notes + +--- + +**Launch date:** July 2nd, 2023 + +Backpack v6 is a major release, with major changes. But don't worry, we've kept MOST changes backwards-compatible. And even for the things that are hugely breaking, we have provided 3 different upgrade paths, so that you can _easily_ use the latest version of Backpack, even if you've _heavily_ customized it. But first, a reminder: + +> **If you've purchased a Backpack add-on after 1st of July 2022 (eg. PRO, DevTools), you get free access to the version that supports Backpack v6.** All purchases get you 12 months of updates _and_ upgrades. + +Here are the BIG things Backpack v6 brings to the table and why you should upgrade from [Backpack v5](/docs/5.x) to v6. But first... we should give credit where credit is due. **Big BIG thanks to**: +- **[Pedro Martins](https://github.com/pxpm)** for creating [Uploaders](#uploaders-making-upload-fields-easier-than-ever) and [MediaLibrary Uploaders](#spatie-medialibrary-support); +- **[Antonio Almeida](https://github.com/promatik)** for creating [Basset](#basset-a-better-way-to-load-css-and-js-assets); +- **[Mauro Martinez](https://github.com/maurohmartinez)** for creating the [CoreUIv4](#coreui-v4-theme-easy-upgrade) & [Tabler](#tabler-theme-new-default) themes, plus our [new video course](/docs/6.x/getting-started-videos); +- **[Jorge Castro](https://github.com/jcastroa87)** for creating [the dropzone field](#new-field-dropzone); +- **[Munjal Mayank](https://github.com/munjaldevelopment)** for the [dozens of new columns](#23-new-column-types) we've added; +- **[Karan Datwani](https://github.com/karandatwani92)** and **[Mohammad Emran](https://github.com/phpfour)** for bug fixes and docs improvements; +- **[Cristian Tabacitu](https://github.com/tabacitu)** for [custom form operations](#custom-form-operations-now-easy), [quick buttons](#quick-buttons-now-one-line) and guidance on all of the above; +- **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 v6 what it is - more than 870 commits, across 6 months, all while still maintaining, bug fixing and improving v5. Again, big thanks to everybody who has helped made this happen 🙏 + + +## Added + +### User Interface + +Backpack v6 brings a MAJOR overhaul of the user interface. We've: +- upgraded from Bootstrap v4 to Bootstrap v5 +- invented a whole new way to use CSS and JS assets in Laravel projects +- added the ability to easily create new themes for Backpack +- created 3 themes: Tabler, CoreUIv4 and CoreUIv2 + +Read on to find out more. + +#### Themes + +Starting with Backpack v6, themes are first-class citizens: +- we have an official, documented way to create Backpack themes; +- our 1st party themes use that system themselves; even the default Backpack theme can now be uninstalled completely, it's an add-on; +- it's reasonably fast to create new themes (5 hours max for any Boostrap admin panel template); +- we have developed 3 themes for you: [Tabler](https://github.com/Laravel-Backpack/theme-tabler), [CoreUI v4](https://github.com/Laravel-Backpack/theme-coreuiv4) and [CoreUI v2](https://github.com/Laravel-Backpack/theme-coreuiv2); + +##### Tabler Theme (new default) + +We've been working with [Tabler](https://tabler.io/) for a long time, and it is _spectactular_. It's by far the best Bootstrap-based HTML template we've worked with: it's open-source, well-maintained, has a great team and a great community around it. And it has TONS of components baked-in, and tons of layouts too (horizontal, vertical etc.). It's by far the best open-source, Bootstrap-based admin template in 2023... which is why we've chosen it for the _default_ Backpack v6 theme. +There's lots and lots to love here: + +![Backpack v6 Tabler theme - layouts horizontal vertical etc](https://user-images.githubusercontent.com/1032474/240274915-f45460a7-b876-432c-82c3-b0b3c60a39f2.png) + +Sounds interesting? [Read more here](https://github.com/Laravel-Backpack/theme-tabler). + +##### CoreUI v4 Theme (easy upgrade) + +If you have lots of customizations... but would still like to upgrade to Booststrap v5, we've developed a theme for you too. You can use the CoreUI v4, which uses the latest version of Bootstrap and CoreUI, but without bringing too many breaking changes: + +![Backpack v6 CoreUI v4 theme](https://user-images.githubusercontent.com/1032474/240274314-184d328e-0e6c-4d67-942b-4e4d4efd96c8.png) + +Sounds interesting? [Read more here](https://github.com/Laravel-Backpack/theme-coreuiv4). + +##### CoreUI v2 Theme (for maximum backwards compatibility) + +If you've made TONS of customizations to your blade files... it might be too time-consuming for you to upgrade to Tabler and Bootstrap v5. It's sad... but we understand that use case. Which is why we've developed a Backpack v6 theme specifically for you, where... not a lot changes. We use the new asset-loading system but other than that... no changes. This theme should allow you to easily upgrade to Backpack v6 and keep you custom pages, changes etc. because we're prioritized backwards-compatibility over adding new features and modernizing: + +![Backpack v6 CoreUI v2 theme](https://user-images.githubusercontent.com/1032474/240272550-456499a0-ef31-48a1-a985-1de3ff6107e5.png) + +Sounds interesting? [Read more here](https://github.com/Laravel-Backpack/theme-coreuiv2). + +#### Dark Mode + +You wanted it, you've got it! Starting v6, Backpack's default theme ([Tabler](https://github.com/Laravel-Backpack/theme-tabler)) has dark mode... out of the box! And not only that... we've spent a lot of time polishing our dark mode, to make sure it's beautiful, legible and usable. Take a look: + +![Backpack v6 Tabler theme with dark mode](https://user-images.githubusercontent.com/1032474/240271611-b028aa6c-eb53-495a-b7bb-66b10880fce9.gif) + +#### Layouts + +One regular need we've identified was needing to change the layout _slightly_. In Backpack v5 some customization was possible using classes, but now, in Backpack v6 and specifically in the Tabler theme, we've overhauled it, and we offer: +- the 9 most common layouts out-of-the-box (vertical, horizontal, horizontal overlap, right vertical etc.); +- an easy way for you to create your own layout, and have all Backpack pages use _your custom layout_; + +![Backpack v6 Tabler theme now has layouts](https://user-images.githubusercontent.com/1032474/240273221-8d5f5d53-ead0-4242-b83c-7dbb56028085.gif) + +Go head, check out the new layouts in [our online demo](https://demo.backpackforlaravel.com/). + +#### Auth Layouts + +One other customization we've often seen people make is changing the way the login/register screens look. To make that easier, in our new default theme, we've created a few alternative auth layouts. You can choose between `default`, `illustration` and `cover`... or create your own. Of course, all of them have dark mode too: + +![Backpack v6 Tabler theme auth layouts](https://user-images.githubusercontent.com/1032474/240278320-df379c68-0d37-4f7a-a8e0-ec2b1175e67f.gif) + +For details, check out the [Tabler theme docs](https://github.com/Laravel-Backpack/theme-tabler). + +#### Basset - a better way to load CSS and JS assets + +We've never been happy with "traditional" ways of loading CSS and JS assets in Laravel (link tags, script tags), nor with using NPM. That's why we've invented a new dead-simple way to load assets... either from your machine or from a CDN. But don't worry, we've fixed all the privacy problems CDNs might have. Plus, we've made that solution fully open-source. We call this better assets solution... [Basset](https://github.com/Laravel-Backpack/basset). Now it's easy to: + +```php +// just do +@basset('https://unpkg.com/vue@3/dist/vue.global.js') + +// and the system will internalize it and output something like + +``` + +This means you don't have to compile or bundle assets. You don't have to publish assets. Backpack's core uses this system behind-the-hood, and it's made our CRUD repo go from 90.3 MB... down to 1.9 MB 🤯 Crazy, right?! And with the improvements in HTTP... most of the time the assets get loaded at the same time... so pageload times have improved considerably too! + +![Backpack Basset for asset loading](https://user-images.githubusercontent.com/1032474/240285417-786c4cd4-d51c-49ba-a90a-8f1cade965f3.png) + +Sounds interesting? [Read more about Basset](https://github.com/Laravel-Backpack/basset). + +#### CSS hook classes + +Starting with v6, if you want change the CRUD Panel styling - colors, border, padding etc (especially if you're creating a new theme), there's an easy way to target CRUD elements. 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 the List operation provides: + +- `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`. For more information please check out [this section](/docs/{{version}}/crud-how-to#how-to-customize-crud-panel-design-css-hooks) in the docs. + +
    + +### CRUD + +#### 23 New Column Types + +We've hunkered down and created a column type... for every field type we have, both FREE and PAID. So yes, starting with Backpack v6... all fields now have corresponding column types. That makes it MUCH easier to use the equivalent of a field... in the List or Show operation. + +Interested? [See all the column types we have, here](https://backpackforlaravel.com/docs/{{version}}/crud-columns). + +#### New Field: Dropzone + +Backpack v5 provided you with a few ways to upload files: `upload`, `upload_multiple`, `browse`, `browse_multiple`. They are perfectly fine for most use cases, but they had technical limitations, due to their dependencies (how HTML works or elFinder). + +That's why we've created _one upload field to rule them all_... the `dropzone` field. This field was a difficult one to achieve, so big BIG thanks to Jorge and Pedro for working 3+ months at it. This new PRO field will allow your admins to drag&drop files to upload them... using AJAX. This means they can: +- upload bigger files +- upload more files than ever +- no longer "_wait for the form to load_" + +And as a developer, it means you hava an upload field... that you can use as a subfield, in your `repeatable` and `relationship` fields 🤯 We are super-excited about this field, because it covers so much more ground than our previous upload fields. We think you'll love it too - in fact we think this will become your _default_ upload field. It has already become ours: + +![Backpack v6 Dropzone field](https://user-images.githubusercontent.com/1032474/256584669-865ef1cc-12cb-49fe-b587-7a1f746a3b8e.gif) + +#### Uploaders - making upload fields easier than ever + +Backpack v5 already made uploads pretty easy - by using model mutators or model events. With Backpack v6, uploads take another huge leap forward. You can now attach Uploaders to fields and columns. And those uploaders encapsulate all the logic needed to do the upload & retrieval. That way... we can provide you with some standard uploaders, that cover 90% of all use cases. To be brief, in order to have a field upload a file, the only thing you need to do is: + +```php +CRUD::field('document')->type('upload')->hasFiles(); +``` + +And that is all 🤯 That's crazy-easy, right? Of course, we have a bunch of configurations for the most common scenarios. And you can create your own Uploader class if you want. [Read more about Uploader classes here](/docs/{{version}}/crud-uploaders). + +#### Spatie MediaLibrary Support + +One popular way to add media (pictures, videos etc) to your Laravel models is to use [Spatie MediaLibrary](https://github.com/spatie/laravel-medialibrary). We're happy to announce that Backpack now has first-party support for this very popular Laravel package. In short, **all you need to do is to [install our add-on](https://github.com/laravel-backpack/medialibrary-uploaders) and call `withMedia()` on your Backpack fields and columns**. And that's it, files will get uploaded using MediaLibrary, fetched using MediaLibrary etc. Uploads just became dead-simple! + +Interested? [Read more about it here](https://github.com/laravel-backpack/medialibrary-uploaders). + +#### Custom form operations, now easy! + +Another thing that was possible in Backpack v5, but not very easy, was to create a custom operation that included a Backpack form. You could do it... but you had to re-do a lot of the things we do in the Create & Update operations. No longer! Creating a custom form operation now is as easy as doing: + +``` +php artisan backpack:crud-form-operation Comment +``` + +That will create a CommentOperation trait that works right away: + +![Custom form operation in Backpack v6](https://github.com/Laravel-Backpack/CRUD/assets/1032474/0bc88660-70d5-41c8-a298-b8a971fd9539) + +You just customize the logic, use it on your CrudControllers and you're done. For more information, please see [the docs](/docs/{{version}}/crud-operations#creating-a-new-operation-with-a-form). + +#### Quick buttons, now one-line + +Another thing that's ridiculously easy now... is for you custom operation to provide simple buttons. Previously you had to create a blade file just to hold that simple button (bleah). Now, CRUD comes with a quick button you can use for most simple use cases: + +```php +// by default, the quick button will figure out the Name and Label from the button name +$this->crud->button('email')->stack('line')->view('crud::buttons.quick'); + +// but you can easily customize Name, Label, Icon and the attributes of the element in meta +$this->crud->button('email')->stack('line')->view('crud::buttons.quick')->meta([ + 'access' => 'Email', + 'label' => 'Email', + 'icon' => 'la la-envelope', + 'wrapper' => [ + 'href' => url('http://github.com/something'), + 'target' => '_blank', + 'title' => 'Create a new email to this user', + ] +]); +``` + + +## Changed + +#### We've merged the fluent and array syntaxes + +In 2018-2022, we had one way of adding fields & columns, the array syntax: +```php +CRUD::addField([ + 'name' => 'description', + 'label' => 'Article Description', + 'type' => 'textarea', +]); +``` + +In 2022, we added a new (fluent) way of defining fields and columns: +```php +CRUD::field('description')->label('Article Description')->type('textarea'); +``` + +In 2023, we're merging the two syntaxes. You can now use them together, first the array syntax then the fluent syntax: +```php +CRUD::field([ + 'name' => 'description', + 'label' => 'Article Description', + 'type' => 'textarea', +])->label('Overriden Description'); +``` + +This means you have all the benefits of the array syntax... and all the benefits of the fluent syntax (chaining), in one simple `CRUD::field()` call. + + +## Removed + +- Support for Laravel 8 and 9; +- Support for PHP lower than 8.1 (since Laravel 10 does not support them); +- `address_algolia` field, since their API got discontinued; +- `color_picker` field, due to package lack of maintenaince; + +--- + +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/6.x/theme-tabler.md b/6.x/theme-tabler.md new file mode 100644 index 00000000..f3e9dcab --- /dev/null +++ b/6.x/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/6.x/themes/theme-tabler.md b/6.x/themes/theme-tabler.md new file mode 100644 index 00000000..943e68ef --- /dev/null +++ b/6.x/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/6.x/tutorials.md b/6.x/tutorials.md new file mode 100644 index 00000000..0b144afc --- /dev/null +++ b/6.x/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/6.x/upgrade-guide.md b/6.x/upgrade-guide.md new file mode 100644 index 00000000..84866270 --- /dev/null +++ b/6.x/upgrade-guide.md @@ -0,0 +1,272 @@ +# Upgrade Guide + +--- + +This will guide you to upgrade from Backpack v5 to v6. 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 10.x +- Backpack\CRUD 5.x +- 15-30 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 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 10](https://laravel.com/docs/10.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": "^6.0", +``` + +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": "^6.0", + "backpack/pro": "^2.0", + "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": "^3.0", + "backpack/generators": "^4.0", +``` + +Step 3.1. We removed `PackageVersions` and the cache busting string is now handled by `Basset`. +To avoid errors on the next step, please remove `cachebusting_string` from `config/backpack/base.php`. +```diff +- 'cachebusting_string' => \PackageVersions\Versions::getVersion('backpack/crud'), +``` + +Step 3.2. 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:install +``` + +When asked to install a theme, please choose the CoreUIv2 theme. It'll be easier to upgrade using it. + + +### Models + +**No changes needed.** But there are a few improvements you _could_ make, if you want, that will help clean up your Models and move a bit of logic that might be admin-panel-only to the admin panel files: + +Step 4. (OPTIONAL) If you use acessors and mutators for upload fields, you can now [use Uploaders instead](/docs/{{version}}/crud-uploaders). + +Step 5. (OPTIONAL) If you use accessors and mutators any other fields, you can [use Eloquent model events on fields](/docs/{{version}}/crud-operation-create#use-events-in-your-field-definition). + + +### Form Requests + +**No changes needed.** But there are a few improvements you _could_ make, if you want + +Step 6. (OPTIONAL) If any of your requests have only a few attributes, you can delete the request file and move the validation to the CrudController; you can specify the [validation rules as an array](/docs/{{version}}/crud-operation-create#b-validating-fields-using-a-rules-array) or [validate directly on fields](/docs/{{version}}/crud-operation-create#c-validating-fields-using-field-attributes); + +Step 7. (OPTIONAL) If you were manually validating the `upload`, `upload_multiple` or `image` field to do complex stuff, v6 provides an easier way to do it; check [the `upload` field docs](/docs/{{version}}/crud-fields#upload), [`upload_multiple` field docs](/docs/{{version}}/crud-fields#upload_ultiple_) for the new validation classes we've introduced - `ValidUpload` and `ValidUploadMultiple`; + + +### Routes + +No changes needed. + + +### Config + +Step 8. We've moved all user-interface-related configs from `config/backpack/base.php` to `config/backpack/ui.php`. Please: +- change your `ui.php` to have any customizations you've done in your `base.php`; +- change your `base.php` - delete all the configs that have been moved to `ui.php`; you should end up with a `base.php` file that has [these options only](https://github.com/Laravel-Backpack/CRUD/blob/v6/src/config/backpack/base.php). +- make sure you remove this line from the global scripts section in `ui.php`: + +```php +'packages/backpack/base/js/bundle.js', +``` + +---- + +Step 9. Backpack v6 comes with brand new themes! And the default theme is `theme-tabler`, a competely new theme, that introduces quite a few breaking changes. It'll be easier to upgrade if you don't use Tabler from the start, but use `theme-coreuiv2` instead. That theme is there for maximum backwards compatiblity. So please do `composer require backpack/theme-coreuiv2`, then go to `config/backpack/ui.php` and change your view namespace to use that theme: + +```php + 'view_namespace' => 'backpack.theme-coreuiv2::', + 'view_namespace_fallback' => 'backpack.theme-coreuiv2::', +``` + + +### CrudControllers + +Step 10. We have removed the `address_algolia` field type, because the API it was using has been sunsetted. You're unlikely to use it at this point, and if you are it's already broken for you. You can use our `google_address` or `google_map` fields as alternatives, but please note you will probably need to edit the DB entries too. + +---- + +Step 11. We have removed the `color_picker` field and column type, because the package was not maintained anymore and didn't support bootstrap 5. You can replace those with the `color` field/column that has a similar functionality. + +---- + +Step 12. (OPTIONAL) We have added column types for all field types (33+ new columns). If previously you weren't showing an attribute in your List and Show operation, because we didn't have a column for it... now we do. Feel free to use the same name for the column as you're using for the field. + +---- + +Step 13. (OPTIONAL) We've merged the array and fluent syntaxes. If you're using `$this->crud->addField()` and `$this->crud->addColumn()`, then using the fluent syntax to modify that field or column, you can now do them in one go. Both `CRUD::field()` and `CRUD::column()` methods now support an array too, so you can do: + +```php +CRUD::field([ + 'name' => 'price', + 'label' => 'Product price', +])->type('number'); +``` + +---- + +Step 14. When defining multi-input fields, use comma instead of array. If you're using the `date_range` or `checklist_dependency` fields, change the name definition from `['first_input', 'second_input']` to `'first_input,second_input'`. + +---- + +Step 15. (OPTIONAL) If you have your `store()` or `update()` methods overriden just as callbacks, to do stuff when entries are `created` or `updated`, we have a suggestion for you that will clean up your code. Instead of overriding those methods, you can use Eloquent model events to achieve the same thing. See [the updated callbacks docs](/docs/6.x/crud-operation-create#callbacks) for all the options available. + + +### CSS & JS Assets + +Step 16. We have invented a whole new way of using CSS & JS assets in Laravel projects. We heavily recommend you use it too for you custom assets. Instead of installing assets using NPM, then minimizing and compiling them: + +```php +// you can now do +@basset('https://example.com/link/to/asset.js') +// instead of + +@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.footer')) +
    +
    + + @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(http://github.com/$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('http://github.com/dashboard'), + trans('backpack::base.dashboard') => false, + ]; + + $breadcrumbs = [ + trans('backpack::crud.admin') => backpack_url('http://github.com/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('http://github.com/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('http://github.com/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..dd5da5a9 --- /dev/null +++ b/7.x-dev/base-components.md @@ -0,0 +1,373 @@ +# 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. + + + +## UI 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; + +
    + + +## Data Components + +These are the components that Backpack uses inside the default CRUD operations. Starting Backpack v7, they are exposed as components, so that you can also use them _outside_ the CrudControllers, or in your custom operations. + + + +### Dataform + +![Backpack v7 Dataform component](https://backpackforlaravel.com/uploads/v7/dataform_component.jpg) + +This component helps you show a form _anywhere you want_, so the admin can easily create or edit an entries for an Eloquent model. The dataform component is a extension of a CrudController - so a CrudController for that entity needs to be already set up, and passed to this component as a parameter: + +```html + +``` + +**Configuration options:** +- `operation='create'` - by default, the datatable component will pick up everything that controller sets up for the Create operation; if you want to change the operation it will initialize, you can pass this parameter; +- `:entry="\App\Models\Invoice::find(1)"` - if you want to use UpdateOperation or a custom form operation that needs the entry; +- `:setup="function($crud, $parent) {}"` - if you want to make changes to the operation setup (eg. add/remove fields, configure functionality), you can use this parameter; the closure passed here will be run _after_ the setup of that operation had already completed; +- `:save-actions="[]"` - provide an array of save action definitions or save action classes to replace the defaults (see [Custom save actions](#dataform-custom-save-actions)); +- `:form-inside-card="true"` - render the form inside a Backpack card wrapper so it visually matches the default create/update screens; leave it `false` to output only the raw form markup. + +**Advanced example:** + +```html + +``` + + +#### Custom save actions + +The Dataform component can swap out the default `Save and back / edit / new` buttons with your own logic. Pass an array to the `:save-actions` attribute containing save action classes (or definitions) that implement Backpack's `SaveActionInterface`: + +```php +@php + use App\Backpack\Crud\SaveActions\SaveAndApprove; + use Backpack\CRUD\app\Library\CrudPanel\SaveActions\SaveAndBack; +@endphp + + +``` + +Each entry in the array can be: +- an instance of a class that implements `SaveActionInterface` (recommended); +- the fully qualified class name of a save action (the container will resolve it); +- a plain array definition (see [`crud-save-actions.md`](crud-save-actions.md)). + +Backpack will replace the default actions for that form, honour the order defined by each class, and fallback to the first action if no default applies. + +
    + + +### Dataform Modal + +![Backpack v7 Dataform Modal component](https://backpackforlaravel.com/uploads/v7/dataform_component.jpg) + +This component helps you show a form _anywhere you want_ inside a modal, so the admin can easily create or edit an entry for an Eloquent model without having to refresh the whole page. + +To use this component you are required to add `CreateInModalOperation` and/or `UpdateInModalOperation` in your CrudController. The dataform modal component is a extension of a CrudController - so a CrudController for that entity needs to be already set up, and passed to this component as a parameter: + +First, in your CrudController, either remove `CreateOperation` in favor of `CreateInModalOperation`, or you can keep both operations. Having both of them is usefull if you want your ListOperation to still show the regular "Create" button, but you would like also to have the possibility to create this entity somewhere else in your application using a modal form. + +```php +use \Backpack\DataformModal\Http\Controllers\Operations\CreateInModalOperation; +``` + +```html + +``` + +**Configuration options:** +- `operation='createInModal'` - by default, the component will pick up everything that controller sets up for the Create operation; if you want to change the operation it will initialize, you can pass this parameter, eg: `updateInModal` +- `:entry="\App\Models\Invoice::find(1)"` - if you want to use UpdateInModalOperation or a custom form operation that needs the entry; +- `:setup="function($crud, $parent) {}"` - if you want to make changes to the operation setup (eg. add/remove fields, configure functionality), you can use this parameter; the closure passed here will be run _after_ the setup of that operation had already completed; +- `:save-actions="[]"` - replace the default modal buttons with your own save action classes. + +**Advanced example:** + +```html + +``` +
    + +> **NOTE**: The date_picker (jquery version) does not properly work in this context. Please use any alternative. + + +### Datatable + +![Backpack v7 Datatable component](https://backpackforlaravel.com/uploads/v7/datatable_component.jpg) + +Useful if you want to show the entries in the database, for an Eloquent model. This component shows a datatable _anywhere you want_, so the admin to easily list, filter, search and perform other operations on entries of an Eloquent model. The datatable component is a extension of a CrudController - so a CrudController for that entity needs to be already set up, and passed to this component as a parameter: + +```html + +``` + +**Configuration options:** +- `name='invoices_datatable'` - by default, a name will be generated; but you can pick one you can recognize; +- `operation='list'` - by default, the datatable component will pick up everything that controller sets up for the List operation; if you want to change the operation it will initialize, you can pass this parameter; +- `:useFixedHeader="false"` - set this to explicitly enable or disable the sticky header; it defaults to the operation's `useFixedHeader` setting, falling back to `true`; +- `:setup="function($crud, $parent) {}"` - if you want to make changes to the operation setup (eg. add/remove columns, configure functionality), you can use this parameter; the closure passed here will be run _after_ the setup of that operation had already completed; + +**Advanced example:** + +```html + +``` + +
    + + +### Datagrid + +![Backpack v7 Datagrid component](https://backpackforlaravel.com/uploads/v7/datagrid_component.jpg) + +Useful if you want to show the attributes of an entry in the database (the attributes of an Eloquent model). This components shows a grid view with all attributes that are configured using CRUD columns. + +There are two ways to use the Datagrid component: + +#### Datagrid for an existing CrudController + +Your datagrid will pick up the configuration in your CrudController automatically. + + +```html + +``` + +**Configuration options:** +- `name='datagrid'` - by default, a name will be generated; but you can pick one you can recognize; +- `operation='show'` - by default, the datagrid component will pick up everything that controller sets up for the Show operation; if you want to change the operation it will initialize, you can pass this parameter; +- `:setup="function($crud, $entry) {}"` - if you want to make changes to the operation setup (eg. add/remove columns, configure functionality), you can use this parameter; the closure passed here will be run _after_ the setup of that operation had already completed; + +**Advanced example:** + +```html + +``` + +#### Datagrid for an Eloquent entry + +If you want to show a datagrid component for a entity that does _not_ have a CrudController, you can do that too. But you have to manually specify the columns you want to be shown: + +```html + +``` + + +
    + + +### Datalist + +![Backpack v7 Datalist component](https://backpackforlaravel.com/uploads/v7/datalist_component.jpg) + +Useful if you want to show the attributes of an entry in the database (the attributes of an Eloquent model). This components shows a table with all attributes that are configured using CRUD columns. + +There are two ways to use the Datalist component: + +#### Datalist for an existing CrudController + +Your datalist will pick up the configuration in your CrudController automatically. + + +```html + +``` + +**Configuration options:** +- `name='invoices_datalist'` - by default, a name will be generated; but you can pick one you can recognize; +- `operation='show'` - by default, the datalist component will pick up everything that controller sets up for the Show operation; if you want to change the operation it will initialize, you can pass this parameter; +- `:setup="function($crud, $entry) {}"` - if you want to make changes to the operation setup (eg. add/remove columns, configure functionality), you can use this parameter; the closure passed here will be run _after_ the setup of that operation had already completed; + +**Advanced example:** + +```html + +``` + +#### Datalist for an Eloquent entry + +If you want to show a datalist component for a entity that does _not_ have a CrudController, you can do that too. But you have to manually specify the columns you want to be shown: + +```html + +``` + + +
    + + +## Syntaxes for Using Components + +All components that Backpack provides are available to use both using the "full namespace" syntax: + +```html + +``` + +and using the slightly shorter "alias" syntax: + +```html + +``` + +You can use whichever one you prefer. But please note that if you need to pass the components to a dynamic Laravel Blade component, only the "alias" syntax will work (eg. you will pass `:component='bp-datagrid'`). This is a limitation from Laravel, not Backpack. + + + +## 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..8d2a90a1 --- /dev/null +++ b/7.x-dev/base-how-to.md @@ -0,0 +1,526 @@ +# FAQs for the admin UI + +--- + + + +## Look and feel + + +### Text direction: LTR or RTL + +By default, the text direction is set to left-to-right. If your UI is in Arabic, Hebrew or any other language that needs to show right-to-left, you can easily enable that - just go to `config/backpack/ui.php` and change the `html_direction` variable to `rtl`: + +``` + // Direction, according to language + // (left-to-right vs right-to-left) + 'html_direction' => 'ltr', +``` + + +### 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('http://github.com/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/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/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/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/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..c26f0f0a --- /dev/null +++ b/7.x-dev/base-widgets.md @@ -0,0 +1,757 @@ +# 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('http://github.com/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']); + } +} + +``` + + +### Dataform + +Shows a dataform component from a particular CrudController. For more info about the configuration parameter, please see the [dataform component docs](/docs/{{version}}/base-components#dataform). + +```php +[ + 'type' => 'dataform', + 'controller' => 'App\Http\Controllers\Admin\InvoiceCrudController', + 'name' => 'invoice_form', + 'setup' => function($crud, $parent) { + // you can use this closure to modify your CrudController definition. + $crud->removeField('notes'); + } +] +``` + +
    + + +### Chip + +Shows a chip blade view - which is useful to show more information about a database entry, using little screen real estate. + +```php +[ + 'type' => 'chip', + 'view' => 'crud::chips.general', + 'title' => 'invoices', + 'entry' => Invoice::first(), +] +``` + + +
    + + +### Datatable + +Shows a datatable component from a particular CrudController. For more info about the configuration parameter, please see the [datatable component docs](/docs/{{version}}/base-components#datatable). + +```php +[ + 'type' => 'datatable', + 'controller' => 'App\Http\Controllers\Admin\PetShop\InvoiceCrudController', + 'name' => 'invoices', + 'setup' => function($crud, $parent) { + // you can use this closure to modify your CrudController definition. + if ($parent) { + $crud->addClause('where', 'owner_id', $parent->id); + } + } +] +``` + + +
    + + +### 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('http://github.com/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('http://github.com/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("http://github.com/invoice/$entry-%3Eid/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("http://github.com/invoice/$entry-%3Eid/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-chips.md b/7.x-dev/crud-chips.md new file mode 100644 index 00000000..4f571ba3 --- /dev/null +++ b/7.x-dev/crud-chips.md @@ -0,0 +1,268 @@ +# Chips + +--- + + +## About + +A chips helps show the information of a database entry, in a format that takes up little space visually. It can be used anywhere you want, but it's particularly useful inside operations to: +- show more info inside a table cell in the **ListOperation**; +- show a related item in more detail in the **ShowOperation**; + +A chip consists of only one file - a blade file with the same name as the chip type (ex: ```general.blade.php```). Backpack provides you with one chip type, that is very general (hence the name). This chip is designed to accomodate most types of database entries - but if your needs are more particular, you can easily [create an entirely new chip type](#creating-a-custom-chip-type). + + +### Default Chip Types + + +#### General Chip + +![Backpack v7 general chip](https://backpackforlaravel.com/uploads/v7/general_chip.jpg) + +This chip was designed to be so general, that it's useful to show _most_ types of entries from the database. The general chip has 3 sections: `heading`, `image` and `details`. All sections are optional. Each of those sections has one mandatory attribute, `content`. Any other attributes you specify on those sections will be placed on that DOM element. + +Here's a very minimal usage of the general chip: + +```php +@include('crud::chips.general', [ + 'heading' => [ + 'content' => 'John Doe', + ], + 'image' => [ + 'content' => asset('uploads/person1.jpg'), + ], + 'details' => [ + [ + 'icon' => 'la la-hashtag', + 'content' => '8AH13A7', + ], + [ + 'icon' => 'la la-envelope', + 'content' => 'john.doe@example.com', + ], + [ + 'icon' => 'la la-phone', + 'content' => '+1 (555) 123-4567', + ] + ] +]) +``` + +But you can also specify more attributes, to enhance your chip with links, titles etc: + +```php +@include('crud::chips.general', [ + 'heading' => [ + 'content' => 'John Doe', + 'href' => 'https://google.com/', + 'target' => '_blank', + 'title' => 'Example of a chip without URL', + ], + 'image' => [ + 'content' => asset('uploads/person1.jpg'), + 'element' => 'a', + 'href' => 'https://chatgpt.com/', + 'target' => '_blank', + 'title' => 'Image can have its own URL, but why?! Falls back to the one in the heading', + ], + 'details' => [ + [ + 'icon' => 'la la-hashtag', + 'content' => '8AH13A7', + 'url' => 'mailto:john.doe@example.com', + 'title' => 'Click to email', + ], + [ + 'icon' => 'la la-envelope', + 'content' => 'john.doe@example.com', + 'url' => 'mailto:john.doe@example.com', + 'title' => 'Click to email', + ], + [ + 'icon' => 'la la-phone', + 'content' => '+1 (555) 123-4567', + 'url' => 'tel:+15551234567', + 'title' => 'Click to call', + ] + ] +]) +``` + + +### How to use chips + +Depending on _where_ you want to use a chip, there are a few ways you can do that. Remember - a chip is a simple blade file, so the methods below should be pretty intuitive: + + +#### How to use a chip inside a custom blade view + +If you want to load a chip inside a custom page, custom component or anything else custom, you can just include the blade view directly, and pass whatever attributes you want to show. For example, if you want to use the `general` chip you can just include that blade file, and pass some of the variables it supports: + +```php +{{-- Example of General chip for a person, with data from a User model --}} +@include('crud::chips.general', [ + 'heading' => [ + 'content' => $user->name, + 'href' => backpack_url('http://github.com/user/'.$user-%3Eid.'/show'), + 'title' => 'Click to preview', + ], + 'image' => [ + 'content' => backpack_avatar_url(http://github.com/$user), // doesn't work well with dummy data + 'element' => 'a', + 'href' => backpack_url('http://github.com/user/'.$user-%3Eid.'/show'), + 'title' => 'Because of dummy data, this image is not available, but it would show a profile image', + ], + 'details' => [ + [ + 'icon' => 'la la-hashtag', + 'content' => $user->id, + 'url' => backpack_url('http://github.com/user/'.$user-%3Eid.'/show'), + 'title' => 'Click to preview', + ], + [ + 'icon' => 'la la-envelope', + 'content' => $user->email, + 'url' => 'mailto:'.$user->email, + 'title' => 'Click to email', + ], + [ + 'icon' => 'la la-calendar', + 'content' => $user->created_at->format('F j, Y'), + 'title' => 'Created at '.$user->created_at, + ] + ] +]) +``` + + +#### How to use a chip as a datatable column + +![Backpack v7 general chip in datatable component](https://backpackforlaravel.com/uploads/v7/general_chip_in_datatable_component.jpg) + +When your datatables have too many columns, chips become particularly useful. They allow you to compress the info from 5 or more columns... into a single chip column. This improves the UX of big datatables - your admins will no longer have to expand the table row or use horizontal scrolling to see crucial info. + +Remember, a chip is just a simple blade file. So to use a chip as a column, we can just use the `view` column type, and pass the path to our chip file. For example: + +```php +// after we create an `invoice` chip +// we can use that chip as a column: +CRUD::addColumn([ + 'name' => 'info', + 'type' => 'view', + 'view' => 'crud::chips.invoice', +]); +``` + +Now create that blade file, by running `php artisan backpack:chip invoice`. This will create a file in `resources/views/admin/chips` for you to edit, and customize as you like. By default, it just uses the `$entry` variable (which will be present if you use it as a column). You can include the `general` chip view if it's good enough for you, or copy-paste the HTML from the `general` chip, and modify it to your liking (you can run `php artisan backpack:chip invoice --from=general` to create a chip with all the HTML from general). + +Please note: +- If your chip uses any info from RELATED items, you should probably eager load those items. For example if you're in an InvoiceCrudController you could do this in your `setupListOperation()` or hell maybe even in setup(): `CRUD::with(['event', 'event.production', 'event.venue', 'event.venue.city']);` - that way when your chip needs that info, it already has it onpage, and makes no extra queries; +- By default, the view column type is not searchable. In order to make your chip columns searchable you need to [specify a custom ```searchLogic``` in your declaration](/docs/{{version}}/crud-columns#custom-search-logic). +- By default, the view column type is not orderable. In order to make your chip columns orderable you need to [specify a custom ```orderLogic``` in your declaration](/docs/{{version}}/crud-columns#custom-order-logic). + + +#### How to use a chip as a widget + +![Backpack v7 general chip as widget](https://backpackforlaravel.com/uploads/v7/general_chip_as_widget.jpg) + +Chip files usually only contain the minimum content and styling necessary. You can include them as widgets directly, but they probably won't look very pretty on a custom page, because they don't have a background, borders, shadow etc. That's why we've also created a `chip` widget, which adds wrappers just like the other widgets - so that your chip will look good when placed on a custom page (or an existing CRUD page, why not). + +To use the `chip` widget, you can do: + +```php +Widget::add() + ->to('after_content') // optional + ->type('chip') + ->view('crud::chips.owner') + ->title('Owner') + ->entry($owner); +``` + +
    + + +## Overwriting Default Chip Types + +You can override a chip by create a file in ```resources\views\vendor\backpack\crud\chips``` with the same name. But it is NOT recommended to override the `general` chip type. When you do that, you're forfeiting any future updates for that chip. We can't push updates to a file that you're no longer using. + +In 99.9% of the cases, it's recommended NOT to override the default `general` chip file, but to create a _custom_ chip file. That will make it a lot easier to upgrade to newer versions of Backpack - because the file is completely in your control. + +
    + + +## Creating a Custom Chip Type + +Chips consist of only one file - a blade file with the same name as the chip type (ex: ```person.blade.php```). You can create one by placing a new blade file inside ```resources\views\vendor\backpack\crud\chips```. Be careful to choose a distinctive name - usually the model name works best. + +To create a new chip file in the standard directory, you can run: +- `php artisan backpack:chip {chip-name}` to create a new file in that directory, from our stub that assumes you want to use that chip inside the ListOperation and ShowOperation, so you'll be using the `$entry` variable to define what you want the chip to include; +- `php artisan backpack:chip {chip-name} --from=general` to create a new file in that directory, from our `general` chip, so you can change the HTML however you want; + +For example, you can do `php artisan backpack:chip invoice` to create ```invoice.blade.php``` that helps you define what that chips includes: + +```php +@php + $last_purchase = $entry->invoices()->orderBy('issuance_date', 'DESC')->first()->issuance_date; +@endphp + +@include('crud::chips.general', [ + 'heading' => [ + 'content' => 'Invoice '.$entry->series.' '.$entry->number.' - '.$entry->owner->name, + 'href' => backpack_url('http://github.com/pet-shop/invoice/'.$entry-%3Eid.'/show'), + ], + 'details' => [ + [ + 'icon' => 'la la-dollar', + 'content' => $entry->total, + 'title' => 'Total invoice amount $'.$entry->total, + ], + [ + 'icon' => 'la la-tags', + 'content' => $entry->items->count().' items', + ], + [ + 'icon' => 'la la-calendar', + 'content' => $last_purchase->format('F j, Y'), + 'title' => 'Issuance date: '.$last_purchase, + ] + ] +]) +``` + +But you can also run `php artisan backpack:chip custom --from=general`, wipe everything inside the generated file, and include your own custom HTML, hardcoded or not: + +```html +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + + + 8AH13A7 + + + + john.doe@example.com + + + + +1 (555) 123-4567 + +
    +
    +
    +
    +``` + +Otherwise, you can create a completely custom chip, that looks and works differently from the `general` chip, and re-use that in your application. There are no limitations - since chips are simple blade files. Just copy-paste the HTML from the `general` chip and change it to match your needs. diff --git a/7.x-dev/crud-columns.md b/7.x-dev/crud-columns.md new file mode 100644 index 00000000..a06774b3 --- /dev/null +++ b/7.x-dev/crud-columns.md @@ -0,0 +1,1742 @@ +# 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 + ], + ], +] +``` + +
    + + +### ckeditor PRO + +The perfect match for the [CKEditor field](https://github.com/Laravel-Backpack/ckeditor-field). The CKEditor column will just output the non-escaped text value of a db column (or model attribute). Its definition is simple: + +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'ckeditor', +], +``` + +For more information, please see [backpack/ckeditor-field](https://github.com/Laravel-Backpack/ckeditor-field) on Github - that is the add-on that provides this functionality. + +
    + + +### 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 + 'toggle' => true //show a toggle button on the column that show/hide the json contents +], +``` + +
    + + +### 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 + // OPTIONAL + 'separator' => ',', // if you want to use a different separator than the default ',' +], +``` + +
    + + +### 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 +], +``` + +
    + + +### tinymce PRO + +The perfect match for the [`tinymce` field](https://github.com/Laravel-Backpack/tinymce-field). The tinymce column will just output the non-escaped text value of a db column (or model attribute). Its definition is simple: +```php +[ + 'name' => 'info', // The db column name + 'label' => 'Info', // Table column heading + 'type' => 'tinymce', +], +``` + +For more information on the TinyMCE field and column, see [backpack/tinymce-field]( +The perfect match for the [`tinymce` field](https://github.com/Laravel-Backpack/tinymce-field)) on Github - that's the addon that provides this functionality. + +
    + + +### 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. + //'element' => 'a' // let's you change the element of the link. + //'rel' => false OR 'rel' => 'noopener' // let's you disable or change the rel attribute of the link. +], +``` + +
    + + +### 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', +], +``` + +
    + + +### 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' + ] +], +``` + +
    + + +### 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', +], +``` + +
    + + +## 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.category_id') + ->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('http://github.com/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('http://github.com/category/'.$related_key.'/show'); + }, +]); + +// or as a closure shortcut: +$this->crud->column('category')->linkTo(fn($entry, $related_key) => backpack_url('http://github.com/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..2bd742f6 --- /dev/null +++ b/7.x-dev/crud-fields.md @@ -0,0 +1,2699 @@ +# 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) + +
    + + +### ckeditor + +Show a WYSIWIG field to the user, powered by CKEditor. This field is provided as a first-party add-on - see instructions [here](https://github.com/Laravel-Backpack/ckeditor-field). + +
    + + +### 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). + +#### Uploading files with summernote + +Summernote saves images as base64 encoded strings in the database. If you want to save them as files on the server, you can use the [Summernote Uploader](https://backpackforlaravel.com/docs/7.x/crud-uploaders). Please note that the Summernote Uploader is part of the `backpack/pro` package. +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' => '#232323', // in CoreUI v2 theme you can also specify bootstrap colors, like `primary`, `danger`, `success`, etc You can also overwrite the `--bg-switch-checked-color` css variable to change the color of the switch when it's checked + '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' +]); +``` + +
    + + +### tinymce + +Show a WYSIWYG editor powered by TinyMCE to the admin. This is provided using a first-party add-on - see instructions [here](https://github.com/Laravel-Backpack/tinymce-field). + +
    + + +### 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. + +**IMPORTANT NOTE**: Your key needs access to the following APIS: +- Maps JavaScript API; +- Places API; +- Geocoding API. + +While developing you can use an "unrestricted key" (no restrictions for where the key is used), but for production you should use a separate key, and **MAKE SURE** you restrict the usage of that key to your own domain. + +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) + +
    + + +### 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) + +
    + + +### 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' +], +``` + +**IMPORTANT NOTE**: Your key needs access to the following APIS: +- Maps JavaScript API; +- Places API; +- Geocoding API. + +While developing you can use an "unrestricted key" (no restrictions for where the key is used), but for production you should use a separate key, and **MAKE SURE** you restrict the usage of that key to your own domain. + +**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("http://github.com/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('http://github.com/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; + // 'sortable' => true, // requires the field to accept multiple values, and allow the selected options to be sorted. +]); +``` + +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("http://github.com/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("http://github.com/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('http://github.com/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) + + +
    + + +### 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. + + + +## 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..29a2bcd0 --- /dev/null +++ b/7.x-dev/crud-filters.md @@ -0,0 +1,762 @@ +# 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('http://github.com/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('http://github.com/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 + + +### Use Filters on custom admin panel pages + +Filters can be added to any admin panel page, not just the main CRUD table. Imagine that you want to have a dashboard page, with a few widgets that show some data. You can add filters to that page, and use them to filter the data shown in the widgets. + + +![](https://backpackforlaravel.com/uploads/docs/filters/filters-in-custom-page.png) + +You start by [creating a new page](/docs/{{version}}/base-about#custom-pages-1) to hold your custom content, eg: a reports page. + +```bash +php artisan backpack:page Reports +``` + +To use filters on a custom admin panel page, you should edit the blade file (in this example the `resources/views/admin/reports.blade.php` file) to **add the filters navbar** and **the event listeners**: +```diff +@extends(backpack_view('blank')) + +@section('content') +
    +

    Reports

    +

    Page for Reports

    +
    +
    +
    +
    +
    +
    ++ @include('crud::inc.filters_navbar') +
    +
    +
    +
    +@endsection + ++@push('after_scripts') ++ ++@endpush +``` + +After that, time to add your own filters in your controller (in this example, `ReportsController.php`): + +```php +class ReportsController extends Controller +{ + public function index() + { + $crud = app('crud'); + + $crud->addFilter([ + 'type' => 'simple', + 'name' => 'checkbox', + 'label' => 'Simple', + ], false); + + $crud->addFilter([ // dropdown filter + 'name' => 'select_from_array', + 'type' => 'dropdown', + 'label'=> 'Dropdown', + ], ['one' => 'One', 'two' => 'Two', 'three' => 'Three']); + + return view('admin.reports', [ + 'title' => 'Reports', + 'breadcrumbs' => [ + trans('backpack::crud.admin') => backpack_url('http://github.com/dashboard'), + 'Reports' => false, + ], + 'crud' => $crud, + ]); + } +} +``` + +That's it, you should now have the filters navbar on your reports page. You can use the event listeners to update the data shown on the page based on the filters selected by the user. +Here are the Javascript events you can listen to: +- `backpack:filter:changed` when a filter is changed; +- `backpack:filter:cleared` when a filter is cleared; +- `backpack:filters:cleared` when all filters are cleared; + + +### Add a debounce time to filters + +Filters can be debounced, so that the filtering logic is only applied after the user has stopped typing for a certain amount of time. This is useful when the filtering logic is expensive and you don't want it to run on every keystroke. To debounce a filter, you can use the following code: + +```php + +CRUD::filter('name') + ->type('text') + ->debounce(1000) // debounce time in milliseconds + ->whenActive(function($value) { + // CRUD::addClause('where', 'name', 'LIKE', "%$value%"); + }); +``` + +All filter types accept a `debounce`, like for example the simple filter, range filter etc. + + +### Add 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('http://github.com/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('http://github.com/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('http://github.com/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('http://github.com/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..8c98268c --- /dev/null +++ b/7.x-dev/crud-how-to.md @@ -0,0 +1,989 @@ +# 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 + +See: [How to add extra CRUD routes](/docs/{{version}}/crud-operations#operation-routes) + + +## 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('http://github.com/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('http://github.com/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('http://github.com/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('http://github.com/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..e6b259d7 --- /dev/null +++ b/7.x-dev/crud-operation-clone.md @@ -0,0 +1,146 @@ +# 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 +crud->set('clone.redirect_after_clone', true); + + // you can also use a closure to define the redirect URL + $this->crud->set('clone.redirect_after_clone', function($entry) { + return backpack_url('http://github.com/product/'.$entry-%3Eid.'/show'); // redirect to show view instead of edit + }); + } +} +``` + +This will make the Clone button appear in the table view, and will allow access to the controller method if manually accessed. + + +### 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..cc13a988 --- /dev/null +++ b/7.x-dev/crud-operation-fetch.md @@ -0,0 +1,198 @@ +# 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('http://github.com/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' => [] + ]); + } +``` + +**Adding attributes to fetched models without appends** + +It's already possible to add attributes to the fetched models using the `appends` property in the model. However, this method has some drawbacks, like the fact that the appended attributes will be added to all instances of the model, whenever they are used, not only when they are fetched. If you want to add attributes to the fetched models without using the `appends` property in the model, you can use the `append_attributes` in the fetch configuration. For example: + +```php +public function fetchUser() { + return $this->fetch([ + 'model' => User::class, + 'append_attributes' => ['something'], + ]); + } + +// User.php model +public function something(): Attribute +{ + return Attribute::make( + get: function (mixed $value, array $attributes) { + return $attributes['something_else']; + }, + ); +} + +// and in your field definition +CRUD::field('my_ajax_field')->attribute('something'); +``` + + +## 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('http://github.com/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('http://github.com/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..fdd7c962 --- /dev/null +++ b/7.x-dev/crud-operation-list-entries.md @@ -0,0 +1,479 @@ +# 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);` + +##### Available options + +Additionally you can control the dropdown behavior with `lineButtonsAsDropdownMinimum` and `lineButtonsAsDropdownShowBefore`. By default the dropdown is created no matter how many buttons are present in the line stack. You can change this behavior by setting `lineButtonsAsDropdownMinimum` to a number. If the number of buttons in the line stack is less than this number, the buttons will not be converted to a dropdown. You can also set `lineButtonsAsDropdownShowBefore` to a number to drop the buttons after that number of buttons. + +```php +CRUD::setOperationSetting('lineButtonsAsDropdown', true); +CRUD::setOperationSetting('lineButtonsAsDropdownMinimum', 5); // if there are less than 5 buttons, don't create the dropdown (default: 1) +CRUD::setOperationSetting('lineButtonsAsDropdownShowBefore', 3); // force the first 3 buttons to be inline, and the rest in a dropdown (default: 0) + +// in the above example, when: +// - there are 3 or less buttons, they will be shown inline +// - there are 4 buttons, all 4 will be shown inline +// - there are 5 or more buttons, the first 3 will be shown inline, and the rest in a dropdown +``` + +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 ```exportOnlyColumn => 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 +'exportOnlyColumn' => 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 `exportOnlyColumn => 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..a0eab207 --- /dev/null +++ b/7.x-dev/crud-operation-reorder.md @@ -0,0 +1,158 @@ +# 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```. The names are optional, you can change them in the ```setupReorderOperation()``` method. + +```php + +protected function setupReorderOperation() +{ + CRUD::setOperationSetting('reorderColumnNames', [ + 'parent_id' => 'custom_parent_id', + 'lft' => 'left', + 'rgt' => 'right', + 'depth' => 'deep', + ]); +} +``` + +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..f994b6f9 --- /dev/null +++ b/7.x-dev/crud-operation-update.md @@ -0,0 +1,457 @@ +# 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 to 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..baf14bbe --- /dev/null +++ b/7.x-dev/crud-operations.md @@ -0,0 +1,1267 @@ +# 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. + + + +## Operation Lifecycle + +When making customizations to existing operations or creating custom operations, it's important to understand how Backpack loads operations in the first place. Let's take it from the top, with a practical example. Backpack CRUDs follow the simple MVC pattern (Model-View-Controller): +- a route points to a CrudController (eg. `Route::crud('article', 'ArticleCrudController')`) +- that `ArticleCrudController` then loads operations as traits eg. `use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;` + +Inside `DeleteOperation` (or any other operation) you will typically have these at least three methods, including `setupXxxRoutes()` and `setupXxxDefaults()` methods: + +```php + +trait DeleteOperation +{ + // Defines which routes are needed for the operation + protected function setupDeleteRoutes($segment, $routeName, $controller) {} + + // Add the default settings, buttons, etc that this operation needs. + protected function setupDeleteDefaults() {} + + // Custom methods, that the routes registered above call + public function destroy($id) {} +} +``` + +When do these get called? Well: +- `setupDeleteRoutes()` gets called _when routes are being set up_; if your CRUD routes are defined in `routes/backpack/custom.php` like our convention, then _that_ is when those methods are called; +- `setupDeleteDefaults()` and all other methods in the CrudController get called when the request is processed; let's get deeper into that; + +When a request to `example.com/admin/article` gets made: +- Laravel will setup all routes; that includes the CRUD routes, so it will call all setupXxxRoutes methods, in all operation traits, used on all CrudControllers); +- Laravel will identify that the route points to a particular CrudController, and a particular method inside that controller (in our example, `DeleteOperation::destroy`, used inside `ArticleCrudController`); so it will instantiate the ArticleCrudController; +- when `ArticleCrudController` gets instantiate, its `parent::__construct()` method will set up the operation for you; and it will do it in the following order: + +1. CrudController will set up the operation defaults (by calling `DeleteOperation::setupDeleteDefaults()`); +2. CrudController will call the `ArticleController::setup()`, to allow you as a developer to set up all operation in one place. It's discouraged to use the `setup()` method for that - in practice it's much cleaner to have a setup method for each operation. +3. CrudController will call the `ArticleController::setupDeleteOperation()` method, if present, to allow the developer to configure that operation; + +This means you can think of the Operation lifecycle of having the following "lifecycle events": +- the operation routes being set up +- the defaults being set up +- the operation being set up (aka configured by developer) + +Understanding these moments and their order, is important in order to place your custom logic _in the right place_ and _at the right time_ in the operation lifecycle. + + +### Lifecycle Hooks + +At important points in the CRUD Lifecycle, Backpack triggers what we call "_lifecycle events_". You can hook into those events - by registering custom code that will run when that lifecycle event happens. This allows you to customize the process, without having to override any of the core files for that CRUD or Operation. + +For example, in a Backpack CRUD all routes are setup on the **CrudController** using methods like `setupModerateOperationRoutes()`. Before those methods are called, Backpack triggers an event called `crud:before_all_route_setup`. If you want to add your own code that runs there, you can do: + +```php +use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; + +LifecycleEvent::hookInto('crud:before_setup_routes', function($controller) { + // do something before the routes are setup +}); +``` + +#### General Lifecycle Events + +Here are all the general lifecycle events Backpack triggers: + +- `crud:before_setup_routes` - before any operation routes are registered +- `crud:after_setup_routes` - after all operation routes have been registered +- `crud:before_setup_defaults` - before all defaults are setup +- `crud:after_setup_defaults` - after all defaults have been setup +- `crud:before_setup` - before any operation is set up +- `crud:after_setup` - after that operation has been set up + + +#### Operation Lifecycle Events + +In addition to the general Lifecycle events above, each operation can trigger its own lifecycle events. For example, here are the lifecycle events triggered by the Create operation: + +`create:before_setup` - exposes parameters: $crud +`create:after_setup` - exposes parameters: $crud + +You can hook into those events using a similar syntax to the general lifecycle events: + +```php +LifecycleEvent::hookInto(['create:before_setup'], function() { + $this->crud->addButton('top', 'create', 'view', 'crud::buttons.create'); +}); +``` + +Note that when using the hooks for specific operations, the hook is prefixed with the operation name followed by the hook name. This allow you to hook into specific operation events, or even to multiple events at the same time: + +```php +LifecycleEvent::hookInto(['create:before_setup', 'list:before_setup'], function() { + // do something before the create operation and the list operation are setup +}); +``` + + +### How to add your own hooks + +You can add your own lifecycle events to your custom operations by calling the `LifecycleEvent::trigger()` method at the appropriate points in your operation. For example, if you have a custom operation that need to do something after some action happens in the operation, you can trigger a lifecycle event like this: + +```php +public function moderate() { + // do something to "moderate" the entry and register the hook + LifecycleEvent::trigger('moderate:after_moderation', [ + 'controller' => $this, + 'operation' => 'moderate', + ]); +} +``` + +Then, other developers can hook into that event like this: + +```php +LifecycleEvent::hookInto(['moderate:after_moderation'], function($controller, $operation) { + // do something after the moderate operation has been executed +}); +``` + + +## 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('http://github.com/dashboard'), + $crud->entity_name_plural => url(http://github.com/$crud-%3Eroute), + '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..679aac24 --- /dev/null +++ b/7.x-dev/crud-save-actions.md @@ -0,0 +1,226 @@ +# 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 Action Classes + +Save actions are now first-class citizens. Instead of maintaining large array definitions in each CrudController, you can encapsulate the behaviour inside PHP classes that implement `Backpack\CRUD\app\Library\CrudPanel\SaveActions\SaveActionInterface`. Backpack ships with `SaveAndBack`, `SaveAndEdit`, `SaveAndNew`, and `SaveAndPreview` as examples, and also provides `SaveAndList` for projects that want an explicit "Save and go to list" button. + +### Quick start + +1. **Create a class** inside your application (for example `app/Backpack/Crud/SaveActions/SaveAndApprove.php`). +2. **Extend** `AbstractSaveAction` (recommended) or implement `SaveActionInterface` directly. +3. **Override** the methods that describe your button. +4. **Register** the class with `CRUD::addSaveAction()` / `CRUD::replaceSaveActions()` or pass it to Blade components like ``. + +```php +hasAccess('update') && $crud->entry?->canBeApproved(); + } + + public function getRedirectUrl(CrudPanel $crud, Request $request, $itemId = null): ?string + { + return route('admin.invoices.approve', $itemId ?? $request->input('id')); + } +} +``` + +> **Tip:** `AbstractSaveAction` already implements `toArray()`, order handling, and sensible defaults. Override only what you need. If you must store additional data, you can still return a custom array by implementing `SaveActionInterface` yourself. + +### Registering class-based actions + +Inside your CrudController you can now pass the class instead of an array: + +```php +use App\Backpack\Crud\SaveActions\SaveAndApprove; + +CRUD::replaceSaveActions([ + new SaveAndApprove(), + \Backpack\CRUD\app\Library\CrudPanel\SaveActions\SaveAndBack::class, +]); +``` + +Backpack recognizes three inputs when registering save actions: +- an instantiated save action class; +- the fully qualified class name (it is resolved via the container so dependencies can be injected); +- the legacy associative array definition (still supported). + +The action `order` is taken from the class (or array) and Backpack reorders conflicts automatically. If you need to adjust the order later you can still call `CRUD::orderSaveActions()`. + +### Using classes in Blade components + +The `bp-dataform` component accept save action classes through the `:save-actions` attribute. This allows you to reuse the same custom buttons outside CrudControllers: + +```php + +``` + +When no save actions are provided, Backpack falls back to the defaults registered on the controller. + + +## 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..e5089291 --- /dev/null +++ b/7.x-dev/crud-uploaders.md @@ -0,0 +1,399 @@ +# Uploaders + +--- + + +## About + +Uploading and managing files is a common task in Admin Panels. In Backpack v7, your field definition can include uploading logic, thanks to some classes we call Uploaders. You don't need to create mutators, manual validation of input or custom code to handle file upload - though you can still do that, if you want. + + +## How to Use Uploaders + +When adding an upload field (`upload`, `upload_multiple`, `image`, `dropzone`, `easymde`, `summernote`) 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) + + + +## How to Configure 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. + + +## Available Uploaders + +We've already created Uploaders for the most common scenarios: +- CRUD comes with `SingleFile`, `MultipleFiles`, `SingleBas64Image` +- PRO comes with `DropzoneUploader`, `EasyMDEUploader`, `SummernoteUploader` +- if you want to use spatie/medialibrary you can just install [medialibrary-uploaders](https://github.com/Laravel-Backpack/medialibrary-uploaders) to get `MediaAjaxUploader`, `MediaMultipleFiles`, `MediaSingleBase64Image`, `MediaSingleFile` + + + +## How to Create Uploaders + +Do you want to create your own Uploader class, for your custom field? Here's how you can do that, and how Uploader classes work behind the scenes. + +First thing you need to decide if you are creating a _non-ajax_ or _ajax_ uploader: +- _non-ajax_ uploaders process the file upload when you submit your form; +- _ajax_ uploaders process the file upload before the form is submitted, by submitting an AJAX request using Javascript; + + +### How to Create a Custom Non-Ajax Uploader + +First let's see how to create a non-ajax uploader, for that we will create a `CustomUploader` class that extends the abstract class `Uploader`. + +```php +namespace App\Uploaders\CustomUploader; + +use Backpack\CRUD\app\Library\Uploaders\Uploader; + +class CustomUploader extends Uploader +{ + // the function we need to implement + public function uploadFiles(Model $entry, $values) + { + // $entry is the model instance we are working with + // $values is the sent files from request. + + // do your upload logic here + + return $valueToBeStoredInTheDatabaseEntry; + } + + // this is called when your uploader field is a subfield of a repeatable field. In here you receive + // the sent values in the current request and the previous repeatable values (only the uploads values). + protected function uploadRepeatableFiles($values, $previousValues) + { + // you should return an array of arrays (each sub array is a repeatable row) where the array key is the field name. + // backpack will merge this values along the other repeatable fields and save them in the database. + return [ + [ + 'custom_upload' => 'path/file.jpg' + ], + [ + 'custom_upload' => 'path/file.jpg' + ] + ]; + } +} +``` + +You can now use this uploader in your field definition: + +```php +CRUD::field('avatar')->type('upload')->withFiles([ + 'uploader' => \App\Uploaders\CustomUploader::class, +]); +``` + +If you custom uploader was created to work for a custom field (say it's called `custom_upload`), you can tell Backpack to always use this uploader for that field type - that way you don't have to specify it every time you use the field. You can do that in your Service Provider `boot()` method, by adding it to the `UploadersRepository`: + +```php +// in your App\Providers\AppServiceProvider.php + +protected function boot() +{ + app('UploadersRepository')->addUploaderClasses(['custom_upload' => \App\Uploaders\CustomUploader::class], 'withFiles'); +} +``` + +You can now use `CRUD::field('avatar')->type('custom_upload')->withFiles();` and it will use your custom uploader. What happens behind the scenes is that Backpack will register your uploader to run on 3 different model events: `saving`, `retrieved` and `deleting`. + +The `Uploader` class has 3 "entry points" for the mentioned events: **`storeUploadedFiles()`**, **`retrieveUploadedFiles()`** and **`deleteUploadedFiles()`**. You can override these methods in your custom uploader, but typically you will not need to do that. The methods already delegate what will happen to the relevant methods (eg. if it's not a repeatable, call ```uploadFiles()```, othewise call ```uploadRepeatableFiles()```). + +Notice this custom class you're creating is extending `Backpack\CRUD\app\Library\Uploaders\Uploader`. That base uploader class has most of the functionality implemented and uses **"strategy methods"** to configure the underlying behavior. + +**`shouldUploadFiles`** - a method that returns a boolean to determine if the files should be uploaded. By default it returns true, but you can overwrite it to add your custom logic. + +**`shouldKeepPreviousValuesUnchanged`** - a method that returns a boolean to determine if the previous values should be kept unchanged and not perform the upload. + +**`hasDeletedFiles`** - a method that returns a boolean to determine if the files were deleted from the field. + +**`getUploadedFilesFromRequest`** - this is the method that will be called to get the values sent in the request. Some uploaders require you get the `->files()` others the `->input()`. By default it returns the `->files()`. + +This is the implementation of those methods in `SingleFile` uploader: +```php +protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool +{ + // if a string is sent as the value, it means the file was not changed so we should keep + // previous value unchanged + return is_string($entryValue); +} + +protected function hasDeletedFiles($entryValue): bool +{ + // if the value is null, it means the file was deleted from the field + return $entryValue === null; +} + +protected function shouldUploadFiles($value): bool +{ + // when the value is an instance of UploadedFile, it means the file was uploaded and we should upload it + return is_a($value, 'Illuminate\Http\UploadedFile', true); +} + + +### How to Create a Custom Ajax Uploader + +For the ajax uploaders, the process is similar, but your custom uploader class should extend `BackpackAjaxUploader` instead of `Uploader` (**note that this requires backpack/pro**). + +```php + +namespace App\Uploaders\CustomUploader; + +use Backpack\Pro\Uploaders\BackpackAjaxUploader; + +class CustomUploader extends BackpackAjaxUploader +{ + // this is called on `saving` event of the main entry, at this point you already performed the upload + // of the files in the ajax endpoint. By default they are in a temp folder, so here is the place + // where you should move them to the final disk and path and setup what will be saved in the database. + public function uploadFiles(Model $entry, $values) + { + return $valueToBeStoredInTheDatabaseEntry; + } + + // this is called when your uploader field is a subfield of a repeatable field. In here you receive + // the sent values in the current request and the previous repeatable values (only the uploads values). + protected function uploadRepeatableFiles($values, $previousValues) + { + // you should return an array of arrays (each sub array is a repeatable row) where the array key is the field name. + // backpack will merge this values along the other repeatable fields and save them in the database. + return [ + [ + 'custom_upload' => 'path/file.jpg' + ], + [ + 'custom_upload' => 'path/file.jpg' + ] + ]; + } +} +``` + +The process to register the uploader in the `UploadersRepositoy` is the same as the non-ajax uploader. `app('UploadersRepository')->addUploaderClasses(['custom_upload' => \App\Uploaders\CustomUploader::class], 'withFiles');` in the boot method of your provider. + +In addition to the field configuration, ajax uploaders require that you use the `AjaxUploadOperation` trait in your controller. The operation is responsible to register the ajax route where your files will be sent and the upload process will be handled and the delete route from where you can delete **temporary files**. + +Similar to model events, there are two "setup" methods for those endpoints: **`processAjaxEndpointUploads()`** and **`deleteAjaxEndpointUpload()`**. You can overwrite them to add your custom logic but most of the time you will not need to do that and just implement the `uploadFiles()` and `uploadRepeatableFiles()` methods. + +The ajax uploader also has the same "strategy methods" as the non-ajax uploader (see above), but adds a few more: +- **`ajaxEndpointSuccessResponse($files = null)`** - This should return a `JsonResponse` with the needed information when the upload is successful. By default it returns a json response with the file path. +- **`ajaxEndpointErrorResponse($message)`** - Use this method to change the endpoint response in case the upload failed. Similar to the success it should return a `JsonResponse`. +- **`getAjaxEndpointDisk()`** - By default a `temporaryDisk` is used to store the files before they are moved to the final disk (when uploadFiles() is called). You can overwrite this method to change the disk used. +- **`getAjaxEndpointPath()`** - By default the path is `/temp` but you can override this method to change the path used. +- **`getDefaultAjaxEndpointValidation()`** - Should return the default validation rules (in the format of `BackpackCustomRule`) for the ajax endpoint. By default it returns a `ValidGenericAjaxEndpoint` rule. + + +For any other customization you would like to perform, please check the source code of the `Uploader` and `BackpackAjaxUploader` classes. + + +## FAQ about Uploaders + + +### 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); + }); + } +} +``` + + +## Deleting temporary files + +When using ajax uploaders, the files are uploaded to a temporary disk and path before being moved to the final disk and path. If by some reason the user does not finish the operation, those files may lay around in your server temporary folder. +To delete them, we have created a `backpack:purge-temporary-folder` command that you can schedule to run every day, or in the time frame that better suits your needs. + +```php +// in your routes/console +use Illuminate\Console\Scheduling\Schedule; + +Schedule::command('backpack:purge-temporary-folder')->daily(); + +``` + +For additional configuration check the `config/backpack/operations/ajax-uploads.php` file. Those configurations can also be passed on a "per-command" basis, eg: `backpack:purge-temporary-folder --disk=public --path=temp --older-than=5`. + + +### 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..c46ebe04 --- /dev/null +++ b/7.x-dev/index.md @@ -0,0 +1,68 @@ +#### 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) + + [Chips](/docs/{{version}}/crud-chips) + + [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) + + [Chips](/docs/{{version}}/crud-chips) +- [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..b6e6197d --- /dev/null +++ b/7.x-dev/installation.md @@ -0,0 +1,92 @@ +# Installation + +--- + + +## Requirements + +If you can run Laravel 12, 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/12.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 +# during beta, configure your Composer.json to accept unstable version, by running: +composer config minimum-stability dev +composer config prefer-stable true + +# then require the v7-beta: +composer require backpack/crud:"^7.0@dev" + +# and finally, install Backpack: +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..3ea33a23 --- /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 or 12.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, which will be useful when your admin panel grows (see our [FREE vs PRO comparison](https://backpackforlaravel.com/docs/7.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/7.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..e6555e9c --- /dev/null +++ b/7.x-dev/release-notes.md @@ -0,0 +1,109 @@ +# Release Notes + +--- + +**Planned launch date:** August 1st, 2025 + +For the past 2.5 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 to upgrade stuff. But... it's time for a new release. Have no fear though, we've made this super easy for you - and the new features & bug fixes are worth it! + +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 our team: [Pedro Martins](https://github.com/pxpm), [Jorge Castro](https://github.com/jcastroa87)**, [Karan Datwani](https://github.com/karandatwani92)** and [Cristian Tabacitu](https://github.com/tabacitu) for working on this new version - and of course **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 1000 commits, across more than 8 months, all while still maintaining, bug fixing and improving v6. Again, big thanks to everybody who has helped made this happen - and of course, BIG thanks to our beta testers 🙏 + + +## Added + +### Data Components + +Our team has spend a lot of time and effort to make it possible to include the content of our main operations... anywhere you want. In the process, we've not only bent the laws of physics (ok maybe only PHP), but also made those operations _cleaner_. + +#### Datatable component + +![Datatable component in Backpack for Laravel v7](https://backpackforlaravel.com/uploads/v7/datatable_component.jpg) + +You can now include a datatable anywhere you want! Just use the component in your custom views, custom pages or custom operations - it will pick up all the setup from your existing CrudController. [Read more](/docs/{{version}}/base-components#datatable-1). + +#### Dataform component + +![Dataform component in Backpack for Laravel v7](https://backpackforlaravel.com/uploads/v7/dataform_component.jpg) + +You can now include a form anywhere you want! Same as the datatable - just use the component in your custom blade files, and it will pick up the fields from your CrudController. [Read more](/docs/{{version}}/base-components#dataform-component). + +#### Datalist component + +![Datalist component in Backpack for Laravel v7](https://backpackforlaravel.com/uploads/v7/datalist_component.jpg) + +You can now use the content of the Show page for a particular entry... anywhere you want. It's as simple as loading the component and passing the CrudController. [Read more](/docs/{{version}}/base-components#datalist-component). + +#### Datagrid component + +![Datagrid component in Backpack for Laravel v7](https://backpackforlaravel.com/uploads/v7/datagrid_component.jpg) + +Our previous design for the Show operation worked fine... but it wasn't pretty. We've developed an alternative comopnent, so that your Show operation looks a little better - just specify you want the show operation to use `datagrid` in your `config/backpack/operations/show.php`. Of course... you can also use it (you guessed it)... anywhere you want. It's as simple as loading the component and passing the CrudController. [Read more](/docs/{{version}}/base-components#datagrid-component). + +### Chips + +![Chips in Backpack v7](https://backpackforlaravel.com/uploads/v7/general_chip.jpg) + +You know columns, you know fields - please welcome... chips! A chips helps show the information of a database entry, in a format that takes up little space visually. It can be used anywhere you want, but it's particularly useful inside the Show and List operations, to cram as much info as possible in as little space as possible. [Read more](/docs/{{version}}/crud-chips). + +### Skins + +![Chips in Backpack v7](https://backpackforlaravel.com/uploads/v7/glass_skin.jpg) + +It's been 2.5 years since we've adopted Tabler as our default HTML template - and in this time, we have only grown more fond of it. We are truly convinced that it's the best free Bootstrap template for admin panels, and our partnership and commitment has only grown. This new version of Backpack comes with a new feature inside our Tabler theme... skins. + +Skins are a simple CSS file, that you can enable/not, to give your admin panel a completely different look. We're launching with one modern skin (that we call "non-liquid glass"). Soon enough, we'll follow up with a few more skins and... a way for you to _quickly_ create a custom skin, that will match your brand colors. + +### CRUD Lifecycle Hooks + +Previously when working with Operations, developers found themselves needing to _override_ an entire operation method, in order to do things before/after the routes/defaults/operation is set up. This created a lot of duplicate code, and made it hard to maintain. Now, you can use CRUD Lifecycle Hooks to add your own code before/after each operation method. This is a much cleaner way to add your own code, without having to override the entire method. [Read more](/docs/{{version}}/crud-operations#lifecycle-hooks). + +### Re-usable Filters - Inside Custom Pages or Operations + +![](https://backpackforlaravel.com/uploads/docs/filters/filters-in-custom-page.png) + +Starting with this Backpack version, you can use the [filters](/docs/{{version}}/crud-filters) in custom pages too. Instead of being tied to DataTables, filters now trigger generic Javascript events like `backpack:filter:changed`. You can catch those events using custom code in Javascript or Livewire... and do stuff. This it possible to use filters on completely custom pages - like custom dashboards, custom reports or custom operations. [Read more](/docs/{{version}}/crud-filters#use-filters-on-custom-admin-panel-pages). + +### Filters inside CustomViews + +Filters can now be used inside [Custom Views for your List operation](https://backpackforlaravel.com/docs/{{version}}/crud-operation-list-entries#custom-views-for-listoperation-pro). This means once the admin has selected the Custom View, they can further drill down in the list, using the filters. But not only that... you can _remove_ the general filters and add entirely new filters, just for that Custom View. + + +## Changed + +### New Versions for All Assets + +We've bumped the version of ALL javascript and CSS assets we have, across the board. By upgrading to Backpack v7, you're automatically getting the best they have to offer - including Tabler, Bootstrap, Datatables etc. + +### Uploaders + +We've ironed out all the quirks of Uploaders (and uploaders inside repeatables etc). This needed a few small breaking changes, but nothing that should affect you, if you haven't created custom uploaders. Try them again - they should all work fine now! + +### Moved TinyMCE and CKEditor fields & columns + +We've moved the TinyMCE & CKeditor fields & columns from PRO to their own addons - released under open-source licenses. Please note that the underlying JS libraries are under GPLv2 license - which means you should not use them inside paid projects without purchasing a license from their respective creators. + +### Basset + +A revolution in how easy it is to load CSS and JS assets in PHP, but we found out the hard way that making things easy is... _hard_. We're glad to tell you we've finally fixed most problems that came with Basset - and it's more reliable than ever. Additionally, you can now: +- name your assets, to easily use the same asset in multiple places; +- publish the assetmap, to override assets from vendor packages; +- use Basset on localhost (to code without an internet connection); +- store your assets in Git, so there's zero need to run basset in production; + +For more information, check out [the docs for Basset's next version](https://github.com/Laravel-Backpack/basset/tree/next). + +### Parent Theme + +// TODO: docs + + +## 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..d09616e2 --- /dev/null +++ b/7.x-dev/upgrade-guide.md @@ -0,0 +1,179 @@ +# 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**. + +> IMPORTANT NOTE: This upgrade guide is NOT AT ALL final. We do not recommend you upgrade your existing project to v7. We're working on making the upgrade process easier for you. We will let you know when this changes. + + +## 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 12](https://laravel.com/docs/12.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": "^7.0.0-beta", +``` + +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": "^7.0.0-beta", + "backpack/pro": "^3.0.0-alpha", + "backpack/filemanager": "dev-next", + "backpack/theme-coreuiv2": "dev-next", + "backpack/theme-coreuiv4": "dev-next", + "backpack/theme-tabler": "dev-next", + "backpack/logmanager": "dev-next", + "backpack/settings": "dev-next", + "backpack/newscrud": "dev-next", + "backpack/permissionmanager": "dev-next", + "backpack/pagemanager": "dev-next", + "backpack/menucrud": "dev-next", + "backpack/backupmanager": "dev-next", + "backpack/editable-columns": "dev-next", + "backpack/revise-operation": "dev-next", + "backpack/medialibrary-uploaders": "dev-next", + "backpack/devtools": "dev-next", + "backpack/generators": "dev-next", +``` + +> **Note:** To install Backpack v7 beta and its add-ons, set `"minimum-stability": "beta"` in your `composer.json`. + +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 + +Step X. **Operation Config Files** - We have added new configuration options in the files inside `config/backpack/operations/`. If you have those files published, it is recommended that you copy the new options in your files too. + +Step X. **Show Operation New Default** - inside `config/backpack/operations/show.php` you'll find a new option, to choose which component will be used on the show operation. By default it's the new component `bp-datagrid`, but you can switch to `bp-datalist` if you want to keep the same look as before: + +```php + // Which component to use for displaying the Show page? + 'component' => 'bp-datalist', // options: bp-datagrid, bp-datalist, or a custom component alias +``` + +Step X. **Theme Tabler** - The default layout for theme tabler has changed. If you had the tabler config published you are good to go. **In case you don't have the tabler theme config published** and want to keep the old layout, you should publish it by running `php artisan vendor:publish --tag="theme-tabler-config"` and changing: +```diff +- 'layout' => 'horizontal', ++ 'layout' => 'horizontal_overlap', +``` +You should also remove the glass skin and fuzzy background from the theme styles: +```diff +'styles' => [ + base_path('vendor/backpack/theme-tabler/resources/assets/css/skins/backpack-color-palette.css'), +- base_path('vendor/backpack/theme-tabler/resources/assets/css/skins/glass.css'), +- base_path('vendor/backpack/theme-tabler/resources/assets/css/skins/fuzzy-background.css'), + ], +``` + + +### CrudControllers + +Step X. The `wysiwyg` field and column have been removed, because they were hiding functionality. But don't worry, they were just alias columns. Behind the scenes, they were just loading the `ckeditor` field and `text` column respectively. If you're using the `wysiwyg` column of field in your CrudController, replace them with their originals. + +Step X. The `ckeditor` field and column have been moved from the PRO package to an open-source addon. If you are using either of them and want to keep doing so, just run `composer require backpack/ckeditor-field`. Please note that CKEditor is a 3rd party JS library that requires _payment_ when used for commercial purposes (GPL licensed). See their [pricing page](https://ckeditor.com/pricing/) for options. We can give away a few sub-licenses every year - [contact us](https://backpackforlaravel.com/contact) to see if any are still available. If you would rather move away from CKEditor, you can just change your field/column to use `summernote` instead, they are both HTML editors so your content should be compatible. Summernote has more limited editing options, but it's MIT-licensed. + +Step X. Similarly, the `tinymce` field and column have been moved from the PRO package to an open-source addon. If you are using either of them and want to keep doing so, just run `composer require backpack/tinymce-field`. Please note that TinyMCE is a 3rd party JS library that normally requires _payment_ when used for commercial purposes (GLP licensed). See their [pricing page](https://www.tiny.cloud/pricing/) for options. You can sign-up for [free commercial licenses on their website here](https://www.tiny.cloud/get-tiny/). If you would rather move away from TinyMCE, you can just change your field/column to use `summernote` instead, they are both HTML editors so your content should be compatible. Summernote has more limited editing options, but it's MIT-licensed. + + + +### CSS & JS Assets + + + + +### Views + +**List Operation View** - The List Operation view got a huge change. We decoupled the datatable from the view, so that you can use the table anywhere you would like. +Most of the code is still identical but moved to `datatable.blade.php`. The `list.blade.php` view now only includes the mentioned datatable component. + +If you had customized the `list.blade.php` you should move your customizations to `datatable.blade.php`. + + +### Security + +No changes needed. + + +### Cache + +Step xx. Clear your app's cache: +``` +php artisan basset:clear +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 + +**backpack/file-manager** Using the File Manager package? Most of the views that weren't in use were removed, and the dependencies were bumped. If you didn't do any customization you should delete the `resources/views/vendor/elfinder` (`rm -rf resources/views/vendor/elfinder`) folder. +No need to publish any views anymore if you are not customizing them. If you were, publish the new view files (`php artisan vendor:publish --provider="Backpack\FileManager\FileManagerServiceProvider" --tag="elfinder-views"`). Then apply your customization on the new files, now located at: `resources/views/vendor/backpack/filemanager/` + +Additional the `browse` and `browse_multiple` **fields/columns** are now part of this package. If you previously made any modifications to this fields/columns you should publish the new views (`php artisan vendor:publish --provider="Backpack\FileManager\FileManagerServiceProvider" --tag="filemanger-fields"` and `php artisan vendor:publish --provider="Backpack\FileManager\FileManagerServiceProvider" --tag="filemanager-columns"`), and carry over the modifications from the old files to this new files. + +--- + +**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); diff --git a/README.md b/README.md index d20e1800..8056e3e6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ You can find the online version of the Backpack documentation at https://backpackforlaravel.com/docs ### How it works -- the ```master``` branch is the one showing up on https://backpackforlaravel.com/docs +- we only keep one branch, ```master```; that's the one showing up on https://backpackforlaravel.com/docs +- for each breaking version of Backpack we keep a separate folder with all the docs for that version - inside each version folder there's an index.md file, where we store the sidebar menu for that version - inside each version folder there's an introduction.md file, which is the home page for that version @@ -11,5 +12,5 @@ You can find the online version of the Backpack documentation at https://backpac - pull requests are welcome and appreciated; - pull requests should point to the ```master``` branch; - all links should point to the current version of the documentation page (ex: ```/docs/{{version}}/installation```); -- all images should be uploaded to ```http://backpackforlaravel.com/uploads/docs/``` and used from there; ask @tabacitu to help upload them; -- all headings should be prepended by an HTML anchor, with the name of the heading; this way, Algolia can take the user directly to that heading (ex: ``````); \ No newline at end of file +- all images should be uploaded to ```https://backpackforlaravel.com/uploads/docs/``` and used from there; ask [@tabacitu](https://github.com/tabacitu) to help upload them; +- all headings should be prepended by an HTML anchor, with the name of the heading; this way, Algolia can take the user directly to that heading (ex: ``````);