# EditableColumns

[![The Whole Fruit Manifesto](https://img.shields.io/badge/writing%20standard-the%20whole%20fruit-brightgreen)](https://github.com/the-whole-fruit/manifesto)

This package adds a few _editable columns_ for projects that use [Backpack for Laravel](https://backpackforlaravel.com/) v6:
- `editable_text`
- `editable_checkbox`
- `editable_switch`
- `editable_select`

When edited, those columns will submit an AJAX request to the controller, so that attribute is updated in the database. The developer is in complete control of the saving process, to make these column as versatile as possible:
- the saving process can succeed => will show green dot or green text (and/or notification bubble)
- the saving process can fail => will show red dot or red text (and/or notification bubble)

![Backpack Editable Columns Addon](https://user-images.githubusercontent.com/1032474/173112345-f98113e2-50da-4e09-85a0-8e9a31d951ee.gif)

For the `editable_text` column, as an admin, you'll notice that:
- the columns that are editable are underlined (dotted)
- you can click that value to edit it, then
    - `Enter` saves the value
    - `Esc` reverts the changes
    - `↑` (up arrow) moves one row up (same editable column)
    - `↓` (down arrow) moves one row down (same editable column)
    - `Tab` moves to the next editable input
    - `Shift` + `Tab` moves to the previous editable input
    - clicking out of the input keeps the changes, but does not submit them (text becomes greyed out)

For the `editable_select`:
- the columns that are editable are underlined (dotted) and there is a dropdown arrow
- you can click the dropdown to edit the value as you normally do with a select input
- once the value is selected, it is saved immediately, but you can disable this by setting the `save_on_change` to `false` (it will save on only on focus out)
- the navigation between the `editable_select` is the same as for the `editable_text` (`Enter`, `Esc`, `↑`, `↓`, `Tab`, `Shift` + `Tab`)
    - `Space` or `Enter` opens the dropdown
- you can use `options` attribute to provide the options of the select input, you can directly pass an array or, you can use a closure, in the closure you'll have the entry as an argument ([check the examples bellow](#usage)).
- **Note**: when using closures, if you want to have a similar behavior like `"select_from_ajax"` you can enable the `forceReloadAfterUpdate` that will redraw the table on update and update the select options given the current entry state. Eg. `CRUD::setOperationSetting('forceReloadAfterUpdate', true)` in your `setupListOperation()`

For the `editable_checkbox` and `editable_switch` columns, as an admin... you'll notice that it "_just works_".

## Demo

Don't believe how simple it is to use? Go ahead, try it right now, in [our online demo](https://demo.backpackforlaravel.com/admin/editable-monster).

## Installation

### Quick Installation

### Quick Installation

In your Laravel + Backpack project, run:

```bash
php artisan backpack:require:editablecolumns
```

It will ask you for your token & password - which you get after you purchase this package. If you've purchased previously, you can [see your token and password in your Backpack account](https://backpackforlaravel.com/user/tokens).

### Manual Installation

Alternatively, if the quick installation above doesn't work, you can follow the steps below:

**Step 1.** Buy access to this package and you'll get an [access token](https://backpackforlaravel.com/user/tokens). With that token in hand, you should instruct your project to pull in missing packages from our private repository, instead of Packagist:
- add [your token](https://backpackforlaravel.com/user/tokens) to your project's `auth.json` file by running `composer config http-basic.backpackforlaravel.com [your-token-username] [your-token-password]`
- add the Backpack private repo to your `composer.json`:

```json
"repositories": [
    {
        "type": "composer",
        "url": "https://repo.backpackforlaravel.com/"
    }
],
```

**Step 2.** In your project, via command line:

``` bash
composer require backpack/editable-columns
```

## Usage

**Step 1.** Inside your ProductCrudController (for example), use this new operation we've provided:

```diff
class ProductCrudController 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\CloneOperation;
    use \Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
+   use \Backpack\EditableColumns\Http\Controllers\Operations\MinorUpdateOperation;

    public function setup()
    {
        CRUD::setModel(\App\Models\Product::class);
        CRUD::setRoute(config('backpack.base.route_prefix').'/product');
        CRUD::setEntityNameStrings('product', 'products');
    }
```

**Step 2.** In your ListOperation, instead of using `text`, `number`, `check`, `select_from_array` columns, use the columns this package provides, if you want them editable:

```php
public function setupListOperation() 
{
    // editable_text
    CRUD::addColumn([
        'name'             => 'price',
        'type'             => 'editable_text',
        'label'            => 'Price',

        // Optionals
        'underlined'       => true, // show a dotted line under the editable column for differentiation? default: true
        'min_width'        => '120px', // how wide should the column be?
        'select_on_click'  => false, // select the entire text on click? default: false
        'save_on_focusout' => false, // if user clicks out, the value should be saved (instead of greyed out)
        'on_error' => [
            'text_color'          => '#df4759', // set a custom text color instead of the red
            'text_color_duration' => 0, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
            'text_value_undo'     => false, // set text to the original value (user will lose the value that was recently input)
        ],
        'on_success' => [
            'text_color'          => '#42ba96', // set a custom text color instead of the green
            'text_color_duration' => 3000, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
        ],
        'auto_update_row' => true, // update related columns in same row, after the AJAX call?
    ]);

    // editable_checkbox
    CRUD::addColumn([
        'name'  => 'agreed',
        'label' => 'Agreed',
        'type'  => 'editable_checkbox',

        // Optionals
        'underlined' => true, // show a dotted line under the editable column for differentiation? default: true
        'on_error' => [
            'status_color'          => '#df4759', // set a custom text color instead of the red
            'status_color_duration' => 0, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
            'switch_value_undo'     => false, // set checkbox to the original value (user will lose the value that was recently input)
        ],
        'on_success' => [
            'status_color'          => '#42ba96', // set a custom text color instead of the green
            'status_color_duration' => 3000, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
        ],
        'auto_update_row' => true, // update related columns in same row, after the AJAX call?
    ]);

    // editable_switch
    CRUD::addColumn([
        'name'  => 'agreed',
        'label' => 'Agreed',
        'type'  => 'editable_switch',

        // Optionals
        // All the options available on editable_checkbox are available here too, plus;
        'color'   => 'success',
        'onLabel' => '✓',
        'offLabel' => '✕',
    ]);

    // editable_select
    CRUD::addColumn([
        'name'    => 'categories',
        'label'   => 'Categories',
        'type'    => 'editable_select',
        'options' => \App\Models\Category::all()->pluck('name', 'id')->toArray(),
        // or
        'options' => (function ($entry) {
            return \App\Models\Category::whereDate('created_at', '<=', $entry->created_at)
                ->pluck('name', 'id')->toArray(),
        }),
        // or
        'options' => [
            '1' => 'One',
            '2' => 'Two',
            '3' => 'Three',
        ],

        // Optionals
        'underlined'       => true, // show a dotted line under the editable column for differentiation? default: true
        'save_on_focusout' => true, // if user clicks out, the value should be saved (instead of greyed out)
        'save_on_change'   => true,
        'on_error' => [
            'text_color'          => '#df4759', // set a custom text color instead of the red
            'text_color_duration' => 0, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
            'text_value_undo'     => false, // set text to the original value (user will lose the value that was recently input)
        ],
        'on_success' => [
            'text_color'          => '#42ba96', // set a custom text color instead of the green
            'text_color_duration' => 3000, // how long (in milliseconds) should the text stay that color (0 for infinite, aka until page refresh)
        ],
        'auto_update_row' => true, // update related columns in same row, after the AJAX call?
    ]);
}
```

**Step 3.A.** Then inside `setupMinorUpdateOperation()`, define what `FormRequest` you want to use for validation. You can use the exact same FormRequest that you've used in your Create/Update operations, because `MinorUpdate` will only consider the validation rule for the currently edited attribute (and ignore everything else). So in most cases you just need to do:

```php
protected function setupMinorUpdateOperation()
{
    $this->crud->setValidation(StoreRequest::class);
}
```

**Step 3.B.** Alternatively, if you don't want to use a FormRequest for validation, but instead want to write some custom validation logic, or you need to modify the saving process, you can easily do that too. Just override the methods `saveMinorUpdateFormValidation()` (to override the validation), and `saveMinorUpdateEntry()` (to override the saving process). This methods are used by `saveMinorUpdate()` which you may also override although it's not really necessary. The example below shows how to confirm/deny the edit. Modify it so it fits your project, it's in your control.

```php
public function saveMinorUpdateFormValidation()
{
    // validation example for the "price"
    // note that this is just an example, you may do this with the FormRequest
    if (request('attribute') === 'price' && !is_numeric(request('value'))) {
        throw ValidationException::withMessages([
            'price' => ['The price has to be a number.'],
        ]);
    }
}
```

```php
public function saveMinorUpdateEntry()
{
    // Save the minor update
    $entry = $this->crud->getModel()->find(request('id'));
    $entry->{request('attribute')} = request('value');
    
    // Perform specific logic
    $entry->status = 'draft';

    $entry->save();

    return $entry->refresh();
}
```

### Allowing `null` on the select column

If your column is nullable on database and you want to allow your users to send an empty selection you should add the "empty option" yourself when providing the select options. 

```php
CRUD::addColumn([
        'name'    => 'categories',
        'label'   => 'Categories',
        'type'    => 'editable_select',
        'options' => array_merge(['' => 'No category'], \App\Models\Category::all()->pluck('name', 'id')->toArray()),
        // or
        'options' => [
            ''  => 'No Category',
            '1' => 'One',
            '2' => 'Two',
            '3' => 'Three',
        ],
    ]);
```

### Search Logic for Editable Columns

Editable columns need to know what search logic to use. We are working on a way to avoid this step, but as of now, you should manually define the `searchLogic` that should be applied for each column type. Example:
```php
CRUD::addColumn([
        'name'             => 'email',
        'type'             => 'editable_text',
        'label'            => 'Email',
        
        'searchLogic'      => 'text', // this will tell backpack to use the "text" column search logic.
        
        // or alternatively provide your own custom search logic in a closure as you would do for any other column
        'searchLogic'      => function ($query, $column, $searchTerm) {
                $query->orWhere('email', 'like', '%'.$searchTerm.'%');
            });
        
    ]);
```
For text based columns you can just tell Backpack to use the `text` column `searchLogic` as shown in the example above.

At the moment the other columns don't have any default `searchLogic` so you should write your own if needed. 

| Editable Column  | Apply Search Logic From Column  |
|:-:|:-:|
| editable_text  | text  |
| editable_checkbox  |  write your custom |
| editable_switch  | write your custom  |
| editable_select  | write your custom  |

### Reloading the table after column is updated

If you want to reload the table after a column is updated you can use the `forceReloadAfterUpdate` setting. This will redraw the whole table on update Eg. `CRUD::setOperationSetting('forceReloadAfterUpdate', true)` in your `setupListOperation()`

Alternatively you can just trigger full table the reload if some specific columns change. For That you can set the `forceReloadAfterUpdate` to an array of column names, or a single string of a column name, that should trigger the reload. Eg. `CRUD::setOperationSetting('forceReloadAfterUpdate', ['price', 'status'])` or `CRUD::setOperationSetting('forceReloadAfterUpdate', 'status')` in your `setupListOperation()`.

## Security

If you discover any security related issues, please email cristian.tabacitu@backpackforlaravel.com instead of using the issue tracker.

## Credits

Big thanks to Kevin Ohashi of [Review Signal](https://reviewsignal.com?ref=backpack) and [NameBio.com](https://namebio.com/?ref=backpack), who has sponsored building this addon. If you need a Backpack addon too, [reach out](https://backpackforlaravel.com/need-freelancer-or-development-team), we love to build them. Created by:

- [Antonio Almeida](https://github.com/promatik)
- [Cristian Tabacitu](https://github.com/tabacitu)

## License

This project was released under EULA, so you can install it on top of any Backpack & Laravel project. Please see the [license file](https://backpackforlaravel.com/products/editable-columns/license.md) for more information. 

[ico-version]: https://img.shields.io/packagist/v/backpack/editable-columns.svg?style=flat-square
[ico-downloads]: https://img.shields.io/packagist/dt/backpack/editable-columns.svg?style=flat-square
[link-author]: https://github.com/backpack
