<repo_name>tokarskimarcin/laravel-admin-ext-select-inline-create<file_sep>/resources/lang/pl/select.php
<?php

return [
    'empty_option' => 'Opcja nie została dodana ze względu na brak etykiety.',
    'entity_not_found' => 'Jednostka nie znaleziona z nieznanego powodu. Skontaktuj się z administratorem.'
];
<file_sep>/src/Form/SelectResponse.php
<?php


namespace Encore\SelectInlineCreate\Form;


use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;

class SelectResponse
{
    /**
     * Get proper response for SelectInlineCreate field model request
     * @param Model $entity
     * @param \closure $callback
     * @return JsonResponse
     */
    public static function get(Model $entity, \closure $callback)
    {
        $response = [
            'success' => false
        ];
        if($entity){
            $option = $callback($entity);
            if(self::isEmpty($option)){
                $response['message'] = trans('select-inline-create::select.empty_option');
            }else{
                $response['success'] = true;
                $response['option'] = $option;
            }
        }else{
            $response['message'] = trans('select-inline-create::select.entity_not_found');
        }
        return JsonResponse::create($response);
    }

    protected static function isEmpty($option){
        return !isset($option['id'], $option['text']) || empty($option['id']) || empty($option['text']);
    }
}
<file_sep>/resources/assets/js/select.js
$('.select-inline-button:not([data-has-event])').each((key, element)=>{
    var $modalButton = $(element);
    $modalButton.attr('data-has-event', true);
}).on('modelCreated', function (e) {
    var $modalButton = $(e.target);
    var $select = $modalButton.closest('.form-group').find('.select-inline-create');
    var searchUrl = $select.data('model-search-url');
    var createdModelId = $modalButton.data('model-id');
    console.log(createdModelId, searchUrl);
    if(createdModelId && searchUrl){
        disable($modalButton);
        $.ajax({
            'url': searchUrl,
            'method': 'GET',
            'data': {
                'id': createdModelId
            }
        }).error((jqXHR, textStatus, errorThrown)=>{
            swal(jqXHR.status.toString(), errorThrown, 'error');})
            .success((result)=>{
                if(result.success){
                    var option = result.option;
                    // Set the value, creating a new option if necessary
                    if ($select.find("option[value='" + option.id + "']").length) {
                        $select.val(option.id).trigger('change');
                    } else {
                        // Create a DOM Option and pre-select by default
                        var newOption = new Option(option.text, option.id, true, true);
                        // Append it to the select
                        $select.append(newOption).trigger('change');
                    }
                }else{
                    toastr.error(result.message);
                }
            })
            .always(() => {
                enable($modalButton);
            })
    }else{
        console.warn("There is no model id in form response or there is no url of model searching.");
    }
});


function disable($button){
    switch ($button.prop('tagName')) {
        case 'A':
            $button.data('data-href', $button.attr('href'));
            $button.attr('href', 'javascript:void(0)');
        //intentional break statement missing
        case 'BUTTON':
            $button.attr('disabled', true);
            break;
    }
}

function enable($button) {
    switch ($button.prop('tagName')) {
        case 'A':
            $button.attr('href', $button.data('data-href'));
            $button.removeAttr('data-href');
        //intentional break statement missing
        case 'BUTTON':
            $button.removeAttr('disabled');
            break;
    }
}
<file_sep>/src/SelectInlineCreateServiceProvider.php
<?php

namespace Encore\SelectInlineCreate;

use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;

class SelectInlineCreateServiceProvider extends ServiceProvider
{
    protected $commands = [
      Console\PublishCommand::class
    ];

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'admin.select-inline-create.bootstrap'  => Middleware\Bootstrap::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'admin' => [
            'admin.select-inline-create.bootstrap',
        ],
    ];

    /**
     * {@inheritdoc}
     */
    public function boot(SelectInlineCreate $extension)
    {
        if (! SelectInlineCreate::boot()) {
            return ;
        }

        if ($views = $extension->views()) {
            $this->loadViewsFrom($views, 'select-inline-create');
        }

        if ($this->app->runningInConsole() && $assets = $extension->assets()) {
            $this->publishes(
                [$assets => public_path('vendor/laravel-admin-ext/select-inline-create')],
                'select-inline-create-assets'
            );
        }

        if($translations = $extension->translations()){
            $this->loadTranslationsFrom($translations, 'select-inline-create');
        }
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->commands($this->commands);

        $this->registerRouteMiddleware();
    }

    public function registerRouteMiddleware(){
        // register route middleware.
        /**
         * @var Router $router
         */
        $router = app('router');
        foreach ($this->routeMiddleware as $key => $middleware) {
            $router->aliasMiddleware($key, $middleware);
        }

        // register middleware group.
        foreach ($this->middlewareGroups as $key => $middleware) {
            $middlewareGroups = $router->getMiddlewareGroups();
            if(key_exists($key, $middlewareGroups)){
                $groupMiddleware = $middlewareGroups[$key];
                $middleware = array_merge($groupMiddleware, $middleware);
            }
            $router->middlewareGroup($key, $middleware);
        }
    }
}
<file_sep>/src/bootstrap.php
<?php

/**
 * Extending Admin/Form with additional field
 */
\Encore\Admin\Form::extend('selectInlineCreate', \Encore\SelectInlineCreate\Form\SelectInlineCreate::class);
<file_sep>/src/Form/SelectInlineCreate.php
<?php


namespace Encore\SelectInlineCreate\Form;


use Encore\Admin\Facades\Admin;
use Encore\Admin\Form\Field;
use Encore\Admin\Form\Field\Select;
use Encore\ModalForm\Form\ModalButton;
use Encore\SelectInlineCreate\Exceptions\ParameterMissingException;
use Illuminate\Database\Eloquent\Model;

class SelectInlineCreate extends Select
{
    protected $view = 'select-inline-create::select';

    /**
     * @var string
     */
    private $createUrl;

    /**
     * @var string
     */
    protected $searchUrl = null;

    /**
     * @var ModalButton
     */
    protected $modalButton;

    public function __construct($column = '', $arguments = [])
    {
        parent::__construct($column, $arguments);
        $this->createRoute(url()->current());
        $this->modalButton = new ModalButton('+', $this->createUrl);
    }

    public function createRoute(string $createRoute)
    {
        $this->createUrl = $createRoute;
        return $this;
    }

    /**
     * Load options from ajax results.
     *
     * @param string $url
     * @param string $idField
     * @param string $textField
     * @return SelectInlineCreate
     */
    public function ajax($url, $idField = 'id', $textField = 'text')
    {
        $this->setSearchUrl($url);
        return parent::ajax($url, $idField, $textField);
    }

    /**
     * Sets only ajax url for pulling model information purpose.
     * @param string $url
     * @return $this
     */
    public function setSearchUrl(string $url)
    {
        $this->searchUrl = $url;
        return $this;
    }

    public function disable(): Field
    {
        $this->modalButton->disable();
        return parent::disable();
    }

    public function render()
    {
        if (empty($this->searchUrl)) {
            throw new ParameterMissingException(sprintf('`%s` missing from %s field. Use "setSearchUrl" or "ajax" method.',
                'searchUrl', self::class));
        }
        $this->modalButton->setHref($this->createUrl);
        $this->modalButton->setClass('btn btn-success select-inline-button');
        $this->addVariables([
            'modalButton' => $this->modalButton->render()
        ]);

        Admin::script(file_get_contents(public_path('/vendor/laravel-admin-ext/select-inline-create/js/select.js')));
        Admin::style(file_get_contents(public_path('/vendor/laravel-admin-ext/select-inline-create/css/select.css')));


        $this->attribute('data-model-search-url', $this->searchUrl);

        return parent::render();
    }

    /**
     * @param Model $entity
     * @param \closure $callback
     */
    public static function response(Model $entity, \closure $callback){
        return SelectResponse::get($entity, $callback);
    }
}
<file_sep>/resources/lang/en/select.php
<?php

return [
    'empty_option' => 'Option could not be added because of empty label.',
    'entity_not_found' => 'Entity could not be found for not know reason. Contact with administrator.'
];
<file_sep>/src/Middleware/Bootstrap.php
<?php


namespace Encore\SelectInlineCreate\Middleware;

use Closure;
use Illuminate\Http\Request;

class Bootstrap
{
    public function handle(Request $request, Closure $next)
    {
        //bootstraping
        require $this->getBootstrapPath();

        return $next($request);
    }

    /**
     * @return string
     */
    protected function getBootstrapPath(){
        return __DIR__.'/../bootstrap.php';
    }
}
<file_sep>/src/SelectInlineCreate.php
<?php

namespace Encore\SelectInlineCreate;

use Encore\Admin\Extension;

class SelectInlineCreate extends Extension
{
    public $name = 'select-inline-create';

    public $views = __DIR__.'/../resources/views';

    public $assets = __DIR__.'/../resources/assets';

    public $translations = __DIR__ . '/../resources/lang';


    /**
     * Get the path of translation files.
     *
     * @return string
     */
    public function translations(){
        return $this->translations;
    }
}
<file_sep>/src/Exceptions/ParameterMissingException.php
<?php


namespace Encore\SelectInlineCreate\Exceptions;

class ParameterMissingException extends \Exception
{

}
<file_sep>/README.md
laravel-admin-ext/SelectInlineCreate
======
![preview](preview-field.png)
Use select field to create entity if not exists. Created entity is selected immediately.
## Requires
- "php": ">=7.2.0",
- "encore/laravel-admin": "~1.6"
- "tokarskimarcin/laravel-admin-ext-modal-form": "^0.1"

## Installation
### Publishing
Execute command below to publish package. 
It will copy vendor resources to your application public directory.
~~~
php artisan admin:select-inline-create:publish
~~~
### Update
To update/overwrite assets of package add ```--force``` option to publishing command.

## Documentation
### Usage
SelectInlineCreate field can be used by executing method ```selectInlineCreate``` on Admin/Form.
```php
use Encore\Admin\Auth\Database\Administrator;
use Encore\Admin\Facades\Admin;
use Encore\Admin\Form;

Admin::form(new Administrator(), function (Form $form){
    $form->selectInlineCreate('manager', 'Manager');
    ...;
});
```

While entity is created it looks for the option by searching url with `id` parameter in query string.
Use `SelectResponse` class and static function `get` to return proper response.
```php
use Encore\SelectInlineCreate\Form\SelectResponse;
use Illuminate\Support\Facades\Request;

class SearchController{
    public function search(){
        $id = Request::offsetGet('id');

        // SelectResponse::get($entity, $callback)
        // $entity can be null, then SelectResponse will return suitable message
        // in $callback return array with 'id' and 'text' keys. 
        // js will create an option based on this

        return SelectResponse::get(
            Administrator::query()->find($id),
            function(Administrator $administrator){
                return ['id' => $administrator->id, 'text' => $administrator->name];
            });
    }
}

```
### Methods
SelectInlineCreate has same methods as regular Select from Admin

#### 1. `setSearchUrl`
If `ajax(...)` function it's not used so `setSearchUrl` should be used.
```php
//Example
$form->selectInlineCrate(...)
    ->setSearchUrl(route('manager.search'))
    ->options(...);
```
If ```ajax(...)``` is used then `setSearchUrl` is executed inside of ajax and searching url is the same as ajax.
```php
$form->selectInlineCrate(...)
    ->ajax(route('manager.search'));
```
Search url can be overwritten if it's set by ajax function. To do that `setSearchUrl` must be executed after `ajax`.
```php
$form->selectInlineCrate(...)
    ->ajax(route('manager.search-by-query'))
    ->setSearchUrl(route('manager.search-by-id'));
```
