kristijanhusak / laravel-form-builder

Laravel Form builder for version 5+!
https://packagist.org/packages/kris/laravel-form-builder
MIT License
1.7k stars 298 forks source link

Suggestions in collections #275

Open dan-developer opened 8 years ago

dan-developer commented 8 years ago

Collections are often used (at least by me) in application, even when business application.

I will propose the following situation:

The documentation I should do something like this:

@section('content')
    {!! form_start($form) !!}
    <div class="collection-container" data-prototype="{{ form_row($form->tags->prototype()) }}">
        {!! form_row($form->tags) !!}
    </div>
    {!! form_end($form) !!}
    <button type="button" class="add-to-collection">Add to collection</button>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script>
        $(document).ready(function() {
            $('.add-to-collection').on('click', function(e) {
                e.preventDefault();
                var container = $('.collection-container');
                var count = container.children().length;
                var proto = container.data('prototype').replace(/__NAME__/g, count);
                container.append(proto);
            });
        });
    </script>
@endsection

In {!! form_row($form->tags) !!}

I would not like to have the option to do a foreach manually thus making it a bit more "malleable", something like:

     {{-- the variable $tag is instance of TagForm with special attribute '__key' --}}
        @foreach($form->tags as $tag)
            {!! form_row_collection($tag, 'partials._form-tag') !!}
        @endforeach

In the view:

<div class="row">
    <div class="col-md-6 col-lg-3">
        {{ form_row($tag->name) }}
    </div>
    <div class="col-md-6 col-lg-3">
        {{ form_row($tag->another) }}
    </div>
</div>

So back greater flexibility. There may also be the case that instead of using prototype, we can use ajax to get that "piece" of the tag, and enables reuse the same view.

So to summarize the scenario is registered something, and then it can be edited, and this edition will be "bootstrap" the form where there should already be filled, can be added more items in the collection, so when more flexible better.

As I'm doing manually:

trait CollectionTrait
{

    public function associateCollection($model, $relation, $data)
    {
        if (!is_array($data)) {
            return [];
        }
        /** @var HasOneOrMany $relationConfig */
        $relationConfig = $model->$relation();
        $relatedModel = $relationConfig->getQuery()->getModel();
        $relatedModelClassName = get_class($relatedModel);
        $associated = [];

        // update de existing records
        foreach ($model->$relation as $k => $record) {
            /** @var Model $record */
            if (array_key_exists($k, $data)) {
                $record->fill($data[$k]);
                $associated[] = $record;
                unset($data[$k]);
            }
        }
        // create new records
        foreach ($data as $k => $fields) {
            $newRelation = new $relatedModelClassName($fields);
            $model->$relation->add($newRelation);
            $associated[] = $newRelation;
        }
        return $associated;
    }
}
class DemoFormController extends Controller
{

    use CollectionTrait;

    public function getCreate()
    {
        $carro = new Carro();
        $this->associateCollection($carro, 'tags', app('request')->get('tags'));
        if ($carro->tags->count() === 0) {
           // initialize with one tag
           $carro->tags->add(new CarroTag());
        }
        return view('teste.demo-form.create', compact('carro'));
    }

    public function postCreate()
    {
        // As test "associateCollection"
        $carro = new Carro();
        $this->associateCollection($carro, 'tags', app('request')->get('tags'));
        return redirect()
            ->to('teste/demo-form/create')
            ->withInput(app('request')->all());
    }
}

If you are already doing this, tell me, but I saw in the documentation.

Thank you.

dan-developer commented 8 years ago

The following example of use, pay attention as I did in getTagForm method in the controller, how do I create a new one.

example.zip

dan-developer commented 8 years ago

I'm trying to facilitate and not to rewrite code using a new method in FormBuilderTrait. Option is_child causes "Argument 1 passed to form_row() must be an instance of Kris\LaravelFormBuilder\Fields\FormField, null given".

<?php

namespace Yoocloud\LaravelFormBuilder;

use Kris\LaravelFormBuilder\Fields\ChildFormType;

trait FormBuilderTrait
{

    /**
     * Create a Form instance
     *
     * @param string $name Full class name of the form class
     * @param array  $options Options to pass to the form
     * @param array  $data additional data to pass to the form
     *
     * @return \Kris\LaravelFormBuilder\Form
     */
    protected function form($name, array $options = [], array $data = [])
    {
        return \App::make('laravel-form-builder')->create($name, $options, $data);
    }

    /**
     * Create a plain Form instance
     *
     * @param array $options Options to pass to the form
     * @param array $data additional data to pass to the form
     *
     * @return \Kris\LaravelFormBuilder\Form
     */
    protected function plain(array $options = [], array $data = [])
    {
        return \App::make('laravel-form-builder')->plain($options, $data);
    }

    /**
     * Create a Child Form instance
     *
     * @param \Kris\LaravelFormBuilder\Form $form
     * @param string $collectionName
     * @param int $key
     *
     * @return \Kris\LaravelFormBuilder\Fields\ChildFormType
     */
    private function childForm($form, $collectionName, $key = null)
    {
        try {
            // test if collection exists
            $form->$collectionName;
        } catch (\InvalidArgumentException $e) {
            throw new \InvalidArgumentException('The collection ' . $collectionName . ' not exists in '.get_class($form));
        }
        if (is_null($key)) {
            $key = 0;
        }
        $options = $form->$collectionName->getOption('options');
        // bug treatment
        if (array_key_exists('is_child', $options)) {
            unset($options['is_child']);
        }
        $type = $form->$collectionName->getOption('type');
        return new ChildFormType("{$collectionName}[{$key}]", $type, $form, $options);
    }
}
namespace App\Http\Controllers\Teste;

use App\Forms\Teste\PostForm;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Models\Teste\Post;
use Illuminate\Http\Request;
use Kris\LaravelFormBuilder\FormBuilder;
use Yoocloud\LaravelFormBuilder\FormBuilderTrait;

class DefaultController extends Controller
{
    use FormBuilderTrait;

    public function getCreate(FormBuilder $formBuilder)
    {
        $post = new Post();
        $form = $formBuilder->create(PostForm::class, [
            'model' => $post,
            'method' => 'POST',
            'url' => url('teste/default/create'),
        ]);
        return view('teste.default.create', compact('form'));
    }

    public function postCreate(Request $request)
    {
        //
    }

    public function getTagForm()
    {
        $key = \Input::get('key');
        /** @var PostForm $form */
        $form = $this->form(PostForm::class);
        $child = $this->childForm($form, 'tags', $key);
        return view('teste.default.partials._form-tag-child', compact('child'));
    }
}
dan-developer commented 8 years ago

What would be the proper way to create a new child to a specific index?