Git Product home page Git Product logo

nova-repeatable-fields's Introduction

NOTE

It is strongly recommended that you use the Nova Flexible Content package rather than this one:

Nova Flexible Content

The Nova Flexible Content package is actively maintained and allows you to use any valid Nova field as a sub field. Due to other commitment, this package is only sporadically maintained and is unlikely to have new features added so new users should strongly consider Nova Flexible Content instead of this package.

Existing users of this package who would like to move to Nova Flexible Content will need to do a little bit of work to migrate your data across. Please see the appendix below offering a potential solution for migrating your data.


A repeatable field for Nova apps

This package contains a Laravel Nova field that enables the creation of repeatable sets of 'sub' fields. Nova users are free to create, reorder and delete multiple rows of data with the sub fields you define. Data is saved to the database as JSON.

Example

Nova repeatable field set on Nova form view

Installation

You can install this package in to a Laravel app that uses Nova via composer:

composer require fourstacks/nova-repeatable-fields

Usage

To add a repeater field, use the Fourstacks\NovaRepeatableFields\Repeater field in your Nova resource:

namespace App\Nova;

use Fourstacks\NovaRepeatableFields\Repeater;

// ...

class Petstore extends Resource
{
    // ...
    
    public function fields(Request $request)
    {
        return [
            // ...
            
            Repeater::make('Dogs'),

            // ...
        ];
    }
}

In order to use this package you should also ensure the Eloquent model that your Nova resource represents, is casting the attribute you wish to use a repeater field for, to an array:

namespace App;

// ...

class Petstore extends Model
{
    protected $casts = [
        'dogs' => 'array'
    ]
}

The underlying database field should be either a string or text type field.

Configuration

This package comes with various options that you can use to define the sub fields within your repeater and

addField

Parameters: array

Every repeater field you create should contain at least one sub field added via addField. The addField method accepts an array of sub field configuration options:

Repeater::make('Dogs')
    ->addField([
        // configuation options
    ])
         

Configuration options are:

label
[ 
    'label' => 'Dog name',
    //...
]

All sub fields must, at a minimum, be defined with a 'label'. This is a human-readable string that will appear in the Nova UI.

name
[ 
    'name' => 'dog_name',
    //...
]

By default, the name of the sub field (used when saving the data in the database) will be generated automatically using a snake case version of the sub field label. Alternatively you are free to override this convention and define a custom name to be used.

type
[ 
    'type' => 'number',
    //...
]

By default, the input type of the sub field will be a standard text field. You are free to define a different field type if you wish. Currently supported sub field types are: 'text', 'number', 'select', 'textarea'.

placeholder
[ 
    'placeholder' => 'Name that dog',
    //...
]

By default, the input placeholder will be the same as the sub field label. However you are free to define a custom placeholder using this option that will appear instead.

width

[ 
    'width' => 'w-1/2',
    //...
]

If you choose to display your sub fields in a row (rather than stacked - see the displayStackedForm option below) you can define the widths of your fields using Tailwind's fractional width classes. You do not need to define widths for all your fields unless you want to. If no widths are entered for any sub fields all sub fields will be the same width.

Note that you are free to mix and match widths. For example you may with to set your first two fields to 50% width using w-1/2 then set the final field to be full width via w-full.

If you are displaying your sub fields in a stacked layout then width options will have no effect.

options
[ 
    'options' => [
        'fido' => 'Fido',
        'mr_bubbles' => 'Mr Bubbles',
        'preston' => 'Preston'
    ],
    //...
]

If the type of the sub field you are defining is 'select', you will need to define an array of options for the select field. These are defined using an array of key/value pairs.

attributes
[ 
    'attributes' => [
        'min' => 1,
        'max' => '20',
        'style' => 'color: red'
    ],
    //...
]

Via the attributes key you are free to define any custom properties you wish to add to the input via an associative array. These will be added via v-bind. For example you may wish to add min or max steps to a number field or a style attribute to a text field.

addButtonText

Repeater::make('Dogs')
    ->addButtonText('Add new dog');

You are free to configure the text for the button used to add a new set of sub fields in the Nova UI. By default this button is labelled 'Add row' but you can override this using the addButtonText option.

summaryLabel

Repeater::make('Dogs')
    ->summaryLabel('Dogs');

The detail and index views show a summary of the data inputted using the repeater field. By default this will show the count of the rows e.g. '3 rows' along with a link to expand to show the full data that was inputted:

Nova repeatable field set on Nova index view - collapsed state

You can overrides this summary text to something more specific e.g. '3 dogs':

Nova repeatable field set on Nova index view - expanded state

displayStackedForm

Repeater::make('Dogs')
    ->displayStackedForm();

By default, a set of sub fields will appear as a series of horizontally aligned input fields:

Nova repeatable field set on Nova form view - default horizontal fields

This works well for repeater fields with only 2 or 3 sub fields, however for larger field sets a stacked form that displays repeater sub fields above one another will generally be a more usable layout. You can switch to a stacked layout using this option.

initialRows

Repeater::make('Dogs')
    ->initialRows(4);

Sets the initial number of rows that will be pre-added on form initialization. For forms with existing rows, it will append up to the set number of rows.

maximumRows

Repeater::make('Dogs')
    ->maximumRows(4);

Sets the maximum number of rows as the upper limit. Upon reaching this limit, you will not be able to add new rows.

heading

Repeater::make('Dogs')
    ->heading('Dog');

Sets the heading between each row (eg. Dog #1, Dog #2). Only works when used with "displayStackedForm".

Appendix - Migrating data to Nova Flexible Content

This guide is only intended for existing users of this package that wish to use the Nova Flexible Content package instead and want an idea of how to migrate data.

Please note that the following solution is a guide only. It is up to you to implement and test a solution for your data and you are strongly recommended to backup any data before running any code that mutates multiple database rows.

I accept no responsibility for changes made to existing data as a result of you using the code below. Got it? OK, on with the show...

This guide assumes that you have already installed the Nova Flexible Content package and you have set up a layout the for the data that you wish to migrate.

Next, in your application, create a new artisan command: php artisan make:command MigrateRepeaterData

Add the following code so that your command looks something like this:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;

class MigrateRepeaterData extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'nfc:migrate';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Migrate your repeater data to be compatible with Nova Flexible Content';


    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // Get the model to run this command for
        $model = $this->ask('Which model do you want to migrate data for?  Enter the full namespace e.g. App\Post');

        if(! class_exists($model)){
            $this->error("Sorry - could not find a model called {$model}");
            return;
        }

        // Get the attribute on the model that holds the old repeater data
        $attribute = $this->ask('Which model attribute holds the data you want to migrate?');

        if(! Schema::hasColumn((new $model)->getTable(), $attribute) ){
            $this->error("Sorry - could not find a database column  called {$attribute} for {$model}");
            return;
        }

        // Get the Nova Flexible Content layout name
        $layout = $this->ask('What is the name of the Nova Flexible Content layout for this data?');

        $models = $model::all();

        if ($this->confirm("About to migrate data for {$models->count()} models.  Do you wish to continue?")) {
            $models->each(function($model) use ($attribute, $layout){
                $model->$attribute = $this->updateValues($model->$attribute, $layout);
                $model->save();
            });

            $this->info('All done!  Please check your data to ensure it was migrated correctly');
        }
    }

    protected function updateValues($data, $layout)
    {
        // Skip anything that is not an array with elements and keep the value the same
        if(! $data){
            return $data;
        }

        return collect($data)
            ->map(function($attributes) use ($layout){
                return [
                    // Create a random key
                    'key' => $this->generateKey(),
                    // my_nova_flexible_content_layout should match the name
                    // you gave to your Nova Flexible Content layout
                    'layout' => $layout,
                    // The data for a given repeater 'row'
                    'attributes' => $attributes
                ];
            })
            ->all();
    }

    protected function generateKey()
    {
        if (function_exists("random_bytes")) {
            $bytes = random_bytes(ceil(16/2));
        }
        elseif (function_exists("openssl_random_pseudo_bytes")) {
            $bytes = openssl_random_pseudo_bytes(ceil(16/2));
        }
        else {
            throw new \Exception("No cryptographically secure random function available");
        }
        return substr(bin2hex($bytes), 0, 16);
    }
}

Changelog

Please see CHANGELOG for more information on what has changed recently.

Credits

License

The MIT License (MIT). Please see License File for more information.

nova-repeatable-fields's People

Contributors

chbbc avatar fourstacks avatar ilyasfoo avatar jeffreydevreede avatar kkoppenhaver avatar michaelradionov avatar mikeaag avatar webspilka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nova-repeatable-fields's Issues

I am getting error unknown column

Laravel 5.8.25
PHP 7.3.5
Nova 2.0

I am getting error

Column not found: 1054 Unknown column 'items' in 'field list' (SQL: insert into items (items, updated_at, created_at) values ([{"supplier":"supplier","sku":"sku12","description":"some desc","qty":"12","price":"10","total":"120"}], 2019-06-19 11:40:12, 2019-06-19 11:40:12))

In my model Item

protected $casts = [
       'items' => 'array'
   ];

and in Item resource

 public function fields(Request $request)
    {
        return [
            //ID::make()->sortable(),

            Repeater::make('Items')->addField([ 
                'label' => 'Supplier',
                'type' => 'text',
                'width' => 'w-1/6'
            ])
            ->addField([ 
                'label' => 'SKU',
                'type' => 'text',
                'width' => 'w-1/6'
            ])
            ->addField([ 
                'label' => 'Description',
                'type' => 'text',
                'width' => 'w-1/6'
            ])
            ->addField([ 
                'label' => 'Qty',
                'type' => 'number',
                'width' => 'w-1/6'
            ])
            ->addField([ 
                'label' => 'Price',
                'type' => 'text',
                'width' => 'w-1/6'
            ])
            ->addField([ 
                'label' => 'Total',
                'type' => 'text',
                'width' => 'w-1/6'
            ])
        ];
    }

@fourstacks Can you please tell me, what I am doing wrong?

Rich text as a field type

Hey there, thanks for this package, it's working great!

Just wondering if adding rich text as a supported field type is on the roadmap?

For example, we have a repeatable field with a headline (text) field and a content (textarea) field. We would love this to be able to be a rich text field like laravel supports natively for fields that aren't repeaters. See: https://nova.laravel.com/docs/1.0/resources/fields.html#trix-field

I'm happy to look into working on a PR for this, as we'll likely have to do it for a project we're working on anyway.

JS Errors in Nova

Hi
When trying to use with Nova 2.* and Laravel 5.8 i am getting the following Javascript error.

TypeError: "cannot use 'in' operator to search for 'title' in ''"

This happens on keypress and when i try to press any of the buttons.

On resource list i am getting this error: TypeError: "this.value.map is not a function"

Thanks

Add options to number fields

Is there an option to add max, min and step to the number field?
As of now it is impossible to add values like 0.5

-

By bad.

Multi language support

Hello,

have you plan to add a support of multi language ?

Example : Im using spatie/laravel-translatable together with DigitalCloud/multilingual-nova

Thanks for your work ;)

[Feature Request] Allow groups in select options

Would it be possible to allow grouping of select options, just like the Nova Field Select?

Select::make('Size')->options([
    'MS' => ['label' => 'Small', 'group' => 'Men Sizes'],
    'MM' => ['label' => 'Medium', 'group' => 'Men Sizes'],
    'WS' => ['label' => 'Small', 'group' => 'Women Sizes'],
    'WM' => ['label' => 'Medium', 'group' => 'Women Sizes'],
])->displayUsingLabels()

TypeError: Cannot read property 'type' of undefined

Hi.
I use accessor and mutator for save my offer product in relation, but i have error TypeError: Cannot read property 'type' of undefined

My Model Product:

    protected $casts = [
        'offers_array' => 'array'
    ];

    public function getOffersArrayAttribute()
    {
        return $this->offers->toArray();
    }

    public function setOffersArrayAttribute(?array $values = []): void
    {
        $ids = [];
        foreach ($values as $arrayValue) {
            $validator = validator((array)$arrayValue, [
                'price' => 'required|numeric',
                'quantity' => 'required|numeric'
            ]);
            $validator->validate();
            if (isset($arrayValue->option_id)) {
                $offer = $this->offers()->where('id', $arrayValue->option_id)->first();
                /** @var Offer $offer */
                $offer->name = $arrayValue->name;
                $offer->price = $arrayValue->price;
                $offer->price_install = $arrayValue->price_install;
                $offer->quantity = $arrayValue->quantity;
                $offer->external_id = $arrayValue->external_id;
                $offer->order = $arrayValue->order;
                $offer->save();

            } else {
                $offer = new Offer();
                $offer->name = $arrayValue->name;
                $offer->price = $arrayValue->price;
                $offer->price_install = $arrayValue->price_install;
                $offer->quantity = $arrayValue->quantity;
                $offer->external_id = $arrayValue->external_id;
                $offer->order = $arrayValue->order;
                $this->offers()->save($offer);
            }
            $ids[] = $offer->id;
        }

        foreach ($this->offers as $offer) {
            if (!in_array($offer->id, $ids)) {
                $offer->delete();
            }
        }
    }

My Ressouce:

           Repeater::make('Price', 'offers_array')
                    ->addField([
                        'label' => 'Name',
                        'name' => 'name',
                        'type' => 'text',
                        'width' => 'w-1/2',
                    ])
                    ->addField([
                        'label' => 'Price',
                        'name' => 'price',
                        'type' => 'number',
                        'width' => 'w-1/2',
                    ])
                    ->addField([
                        'label' => 'Quantity',
                        'name' => 'quantity',
                        'type' => 'number',
                        'width' => 'w-1/2',
                    ])
                    ->addField([
                        'label' => 'External id',
                        'name' => 'external_id',
                        'type' => 'text',
                        'width' => 'w-1/2',
                    ])
                    ->addField([
                        'label' => 'Order',
                        'name' => 'order',
                        'type' => 'number',
                        'width' => 'w-1/2',
                    ])
            ])

I have json:
2018-10-08_19-59-28

And i have error
2018-10-08_19-52-58

Other - Write In

If I wanted to have "Other" in the select options and when this was selected show text input-- how would I accomplish this?

Thanks!

displayStackedForm not Working

My displayStackedForm option not working as expected
Here is my code
Repeater::make('Employees') ->addField([ 'label' => 'Full name', 'name' => 'full_name', 'type' => 'text', 'placeholder' => 'Full Name', ]) ->addField([ 'label' => 'ID No', 'name' => 'id_no', 'type' => 'text', 'placeholder' => 'ID No', ]) ->displayStackedForm() ->initialRows(1)->hideFromIndex(),

But it doesnot show form in stacked way
Screenshot_1
Didn't find where the problem was?

json_decode() expects parameter 1 to be string, array given

Hi everyone,

I'm trying this package out, but it seems I can't get it to work.
I've followed the instructions as presented in the repo, but get the following error:

json_decode() expects parameter 1 to be string, array given

I added the following things to my resource;
use Fourstacks\NovaRepeatableFields\Repeater; Repeater::make('Benefits')->addField(['label' => 'benefits']),

In my model I've added the following cast properties;
protected $casts = ['benefits' => 'array'];

However, it does not seem to work! Can anyone give me some advise, help me out? Thank you!

New field creation when pressing enter

Hi,

when inserting text in a repeatable text input, if the "enter" key is pressed a new repeatable field will be created, which is nice, but unfortunately this behavior happens again when pressing the enter key on an unrelated text field (on the same Nova model, like a title field), which results in creating empty text fields and preventing the form submission.

I suggest removing this feature because it interferes with the normal form submission process and instead keep the permanent button "Add field" below a fields list.

Thank you for your work.

Simon

Using Repeatable field with a json attribute breaks loading saved data.

As you guys probably know, normally with a nova text field in combination with a json attribute you can do the following:

Works:

// In model:
protected $casts = ['content' => 'array'];

// In resource:
Text::make('Example title', 'content->exampleTitle')

But when I am trying to do this in combination with the Repeatable field it doesn't resolve the saved value, the rows I created are not being displayed, however the data seems to be as expected in the database.

Broken:

// In model:
protected $casts = ['content' => 'array'];

// In resource:
Repeater::make('USPs', 'content->usps')->addField([
    'label' => 'USP',
])

Field order incorrect in Detail view

I've set up my field like this:
Repeater::make('Addresses', 'addresses')
->addField([
'label' => 'Label',
'name' => 'label',
'placeholder' => 'Label/Department',
'type' => 'text'
])
->addField([
'label' => 'Street',
'name' => 'street',
'type' => 'text'
])
->addField([
'label' => 'Zip',
'name' => 'zip',
'type' => 'text'
])
->addField([
'label' => 'City',
'name' => 'city',
'type' => 'text'
])
->addField([
'label' => 'Country',
'name' => 'country',
'type' => 'text'
])
->addButtonText('Add Address')
->summaryLabel('Addresses')
->hideFromIndex()
,

But in Detail view the fields are in a strange order
Zip:
City:
Label:
Street:
Country:

Support for large input field

Hi,
thanks for great package!

I was wondering, however, if you could add a support for larger (width, multiline) input fields. The change for width seems quite a simple, so as for swapping input type to textarea.

Current:
image
Area width change:
image
Input -> textarea:
image

Date field support

Works great with current supported field. How can I add date field in the array? appreciated.

Unable to install via composer

[InvalidArgumentException]
  Could not find a version of package fourstacks/nova-repeatable-fields matching your m
  inimum-stability (dev). Require it with an explicit version constraint allowing its d
  esired stability.

Validation rules for subfields

Is it possible to add validation rules to the individual subfields? I'm mostly interested in simply marking them required or not.

Support for BelongsToMany fields

Kindly add support for BelongsToMany fields.
It shows "Object of class stdClass could not be converted to string" when attaching.

support relationships

Thanks for the great package.

Any plan to support master details relationship? rather than a JSON field?

Save data in different raws.

Hi,
I am little bit new to Nova. I find this package very helpful.
My need is little but different.
I want to add 5 records and it should be saved in 5 raw of my table table.

I think this package makes array of all 5 records and save it in one single raw.
Is there any way in this package to achieve what I need ?

Support nova fields

I definitely think nova fields should be used.
The package’s role is to repeat fields.
I expected to be able to use any field.
Like using a toggle and a wysiwyg editor.
I would open it up and offload that concern

Repeatable relation field

Can this fiel be used with a relation?

Like if i have Order model and a OrderItens model?

Thanks.

Array to string conversion error

Hello, and thanks for the help beforehand.

With Nova 2.0, and fourstacks/nova-repeatable-fields v1.3.0, when submitting a form with a Repeater field I'm getting this "Array to string conversion error".

This is the migration to create the field

public function up()
    {
        Schema::table('courses', function (Blueprint $table) {
	        $table->text('calendar')->nullable(true);
        });
    }

This is how I cast the field in the Model

    protected $casts = [
	    'calendar' => 'array'
    ];

And this is the field definition:

    public function fields(Request $request)
    {
        return [
                // other fields here               
	        Repeater::make('Calendario')->addField([
		        'label' => 'Título',
		        'name' => 'lesson_title',
		        'type' => 'text',
		        'placeholder' => 'Ej: Clase Introducción',
	        ])->addButtonText('Agregar Clase')->hideFromIndex(),
        ];
    }

image

Thanks!

Data structure breaks on row deletion

Hi, thanks for a useful package. Found a bug but don't know how to fix, deleteRow function seems ok.

Initial data (I've added 3 states):

image

Hitting remove button on the middle row (NY).

Expected result:
CA => California
MN => Maine

Real result:

image

Code of the field:

Repeater::make('State Differences')->addButtonText('Add state')->addField([
            'label' => 'State',
            'type' => 'select',
            'name' => 'state',
            'options' => array_combine(State::OPTIONS, State::OPTIONS),
            'width' => 'w-1/6',
            'placeholder' => 'State',
        ])->addField([
            'width' => 'w-5/6',
            'label' => 'Difference',
            'type' => 'textarea',
            'name' => 'text',
        ])

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.