laravel / nova-issues

553 stars 35 forks source link

Default values for fields? #58

Closed Jam0r85 closed 5 years ago

Jam0r85 commented 5 years ago

Not sure if i've missed this from the docs but is there anyway to populate a field in a create form with a default value?

eg.

Date::make('Date')->defaultValue(today())

This would be helpful with relationship fields also, especially with BelongsTo (example of using Spark with it's current_team_id column in the users table.

BelongsTo::make('Team')->defaultValue(Auth::user()->current_team_id)

Would help reduce bits and pieces being added to model observers checking for missing values and populating them etc

chinleung commented 5 years ago

You can do this by setting a default attribute to your model, but yeah I think there should be a way to pass a value to the fields.

mink commented 5 years ago

This would be especially useful for the code field. Being able to provide placeholder JSON data into a field by default would allow for more context on how that field can be filled appropriately, rather than being constricted to having it filled with "null" as it currently does.

Jam0r85 commented 5 years ago

@mink Currently I hide my code field's when creating a record and use a model observer to input the default json values. Not the best solution but it does the job.

mink commented 5 years ago

I've found that if you set the default attribute on a model (as @chinleung mentioned) as an array, it will fill a JSON code field on the new resource view. If you set the default value as a string, any code field that is not declared as JSON will be filled in with the specified value too.

Other fields that I've tested such as the Text field will accept various types as a default value to fill in the field.

This would be great to have in the documentation. It is personally enough of a solution for my usage, however, being able to set default values within the resource class would be useful as well.

Edit: If you override a model's constructor you can programmatically declare the default attribute values, which you could use to pull in config data for instance. If you require different default values for the model specifically in Nova you could assign the values this way as well.

crnkovic commented 5 years ago

Is there a way to do this for an action field? I have an action field asking me to input something, it would be great if it would fill a field for me with a default data.

Something like:

Text::make('Field')->defaultValue('a-value');

MadMikeyB commented 5 years ago

Would be great to be able to default let's say a BelongsTo::make('User') with the currently authenticated user.

s-solodovnikov commented 5 years ago

Something is known, how can I set a default value?

stevelacey commented 5 years ago

As per what @mink suggested, default values for json (code) fields can be achieved by setting the value to the attribute in the model's constructor

This works for me:

    protected $casts = [
        'fields' => 'array',
    ];

    public function __construct(array $attributes = [])
    {
        $this->fields = config('app.default_fields');

        parent::__construct($attributes);
    }

I do however also have this field on a pivot... and haven't found a way to specify a default for that... the best way would probably indeed by a defaultValue method on Nova fields. I am toying with pivot classes but as of yet have had no success, I've also tried using resolveUsing but so far no luck

chinleung commented 5 years ago

@MadMikeyB You can achieve set the authenticated user by default for the BelongsTo::make('User') field like this:

public function __construct(array $attributes = [])
{
    if (! isset($attributes['user_id']) && auth()->check()) {
        $attributes['user_id'] = auth()->user()->id;
    }

    parent::__construct($attributes);
}
crnkovic commented 5 years ago

Yeah but this seems like a hacky way to do it. Taylor explicitly said "Nova doesn't interact with your code, it doesn't even touch your code at all". I don't want to make adjustments to my code, especially overriding a constructor of a model, just so I can set default value in Nova. IMHO, this should be resolved, especially since this doesn't feel like a hard bug to fix, since there is a value property on the Field (or Element, not sure) class.

Until then, I'm fine with overriding certain methods in Nova resources, or extending the base Resource class or something, but I'm not fine with modifying my models, especially setting default attributes with some hardcoded values.

stevelacey commented 5 years ago

Your point is valid, I was already setting this default in the creating block, for other code paths, it was just a case of making it happen earlier so it applies to an unsaved model e.g. the nova create forms — nova should have specific defaultValue handling also, so that changes aren’t necessary, and for when the defaults need to be different for the admin On Thu, 30 Aug 2018 at 15:08, Josip Crnković notifications@github.com wrote:

Yeah but this seems like a hacky way to do it. Taylor explicitly said "Nova doesn't interact with your code, it doesn't even touch your code at all". I don't want to make adjustments to my code, especially overriding a constructor of a model, just so I can set default value in Nova. IMHO, this should be resolved, especially since this doesn't feel like a hard bug to fix, since there is a value property on the Field (or Element, not sure) class.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/laravel/nova-issues/issues/58#issuecomment-417313187, or mute the thread https://github.com/notifications/unsubscribe-auth/AARq-8eBkVN_UDfIbPkg18KuZbB1-8xnks5uV-PlgaJpZM4WJ-2I .

taylorotwell commented 5 years ago

This repository is for tracking bugs. Feature requests may be emailed to Nova customer support.

idragon81 commented 5 years ago

Add the following to your resource file

    /**
     * Get a fresh instance of the model represented by the resource.
     *
     * @return mixed
     */
    public static function newModel()
    {
        $model = static::$model;
        $var = new $model;
        $var->attributeForDefaultValue = defaultValue;
        return $var;
    }
roshangautam commented 5 years ago

This is how you set default values. Will work most of the times except when your field is a number and you want to set 0 as default then it wouldn't work.

Text::make("Your Field")
->withMeta(["value" => "Some Value"])
sonalmahajan01 commented 5 years ago

How to specify meta tag only while creating a new value and not for all. Right now it ignores its own values and displays this for all the records.

mattivie14 commented 5 years ago

@sonalmahajan01 You can do this to have it only populate the default value if the item doesn't have a value for that field already: Text::make("Name")->withMeta($this->name ? [] : ["value" => "John Smith"])

tdondich commented 5 years ago

Because of new PHP syntax, it can be even easier.

Text::make("Name")->withMeta(['value' => $this->name ?? 'Default']);

Makes use of the Null Coalescing Operator.

peterhaluska commented 5 years ago

@Jam0r85 I have managed to set a default value (current user's ID) on a BelongsTo field this way:

BelongsTo::make('User')
    ->withMeta([
        'value' => $this->user_id ?? auth()->user()->id, 
        'belongsToId' => $this->user_id ?? auth()->user()->id
    ])
    ->rules('required'),
nickpoulos commented 5 years ago

@peterhaluska small note on your solution. It works great for Create/Edit. However on Index/Details pages it causes the BelongTo value to be the ID instead.

I got around by adding two BelongsTo. The original BelongTo with ->hideWhenCreating()->hideWhenUpdating() attached. And then adding ->hideFromIndex()->hideFromDetail() to your BelongsTo with default value.

BelongsTo::make('Author', 'user', 'App\Nova\User')->withMeta([ 'value' => $this->user_id ?? auth()->user()->id, 'belongsToId' => $this->user_id ?? auth()->user()->id ])->hideFromIndex()->hideFromDetail()

BelongsTo::make('Author', 'user', 'App\Nova\User')->hideWhenCreating()->hideWhenUpdating()

MohmmedAshraf commented 5 years ago

You should use this package: Nova Hidden Fields also you can set default value for your hidden fields such as current user_id and much more..

dillingham commented 5 years ago

If you’re using the auth user for the id, don’t pass it via hidden fields. Use observers. Creating and updating events can access the auth user

josemanuelsh commented 5 years ago

@peterhaluska great solution, you saved me lots of time!!

@nickpoulos there is no need to make two BelongsTo. I am only using the 'belongsToId' and removed the 'value', it works great like that.

BelongsTo::make('User')
    ->withMeta([
        'belongsToId' => $this->user_id ?? auth()->user()->id
    ]);
devonmather commented 5 years ago

Unfortunately using the withMeta function doesn't appear to work in combination displayUsingLabels() when using a select field. Does anyone have a solution for this situation?

Select::make('Rate')
    ->options('work_rates' => [
        '1' => 'Standard',
        '2' => 'Priority',
    ])
    ->withMeta(['value' => '1'])
    ->displayUsingLabels()
chinleung commented 5 years ago

@devonmather You are not passing the options correctly. It should be:

Select::make('Rate')
    ->options([
        '1' => 'Standard',
        '2' => 'Priority',
    ])
    ->withMeta(['value' => '1'])
    ->displayUsingLabels()

Then the value Standard is selected by default.

devonmather commented 5 years ago

@chinleung Thanks for pointing out the syntax error I had in the arguments. However, that wasn't the issue. The issue is on the detail and index where the number '1' shows in place of the text 'Standard'.

Please see screenshots.

screen shot 2019-01-07 at 3 36 04 pm

screenshot

I have since solved the issue by using the model's attributes.

It seems the withMeta functionality for setting defaults could be flawed in that it will always set that value for any page, not just the create form. It also does not respect the displayUsingLabels() function.

zareismail commented 5 years ago

you can use fillUsing callback and in relation use filled callback like this:

public function fields(Request $request)
    {
        return [
            ID::make()->sortable(),
            ColorField::make('hex')->sortable()->fillUsing(function(NovaRequest $request, $model) {
                if(! isset($model->hex)){
                    $model->hex = '#000000';
                } 
            }), 
            BelongsTo::make('user', 'owner')->nullable()->filled(
                function(NovaRequest $request, $model) { 
                    if(is_null($model->owner_id)) {
                        $model->owner_id = $request->user()->id;
                    }

                }
            ),
        ];
    }
yehudahkay commented 5 years ago

+1

ghost commented 5 years ago

Hey, could anybody resolve the issue of @devonmather ? I got the same problem while using the withMeta function. The default value is set in detail view but also everywhere else in index view. So when a user changes the default value while creating a resource, the value is stored correctly in the database, but the default value is shown on index view of all created resources instead of the changed value which is stored in database.

tylernathanreed commented 5 years ago

I'm looking to solve for this issue too. However, I'm working with Actions, rather than Models. I'm wanting to create a checkbox that is checked by default.

Jam0r85 commented 5 years ago

I'm looking to solve for this issue too. However, I'm working with Actions, rather than Models. I'm wanting to create a checkbox that is checked by default.

->withMeta(['value' => true])

chrisbjr commented 5 years ago

I've been running into so many issues like this with Nova. I love Nova because it helps us rapidly develop a standardized admin panels - but issues like this are such a show stopper and you have no idea when it will be fixed/added. I feel like I'm battling with Nova a lot of times.

kitbs commented 5 years ago

I've just published a package which wraps this default behaviour in some convenience methods, including caching previous values. Hope this is useful! https://novapackages.com/packages/inspheric/nova-defaultable

tylernathanreed commented 4 years ago

@LukasRothZeitraum, @devonmather

I've come up with a solution that works for me, and it seems to solve this problem. It also accounts for the issue that @crnkovic pointed out, and thus my solution doesn't require changing anything outside of Nova.

The problem that you guys were seeing is that using the withMeta approach replaces the value returned to the client, even if a value was already present (which is certainly the case for existing models).

Additionally, I'm dealing with a field that uses a resolve callback, which the value provided in withMeta has already handled, so if you override that component, any effect that your callback would have isn't performed (in my case, I'm working with a "percent" field, which is a number field that shows "35" to represent "0.35" in the database. I'd have to use "100" for the withMeta approach, instead of the desired value of "1").

The solution lies with provide default attributes to the model, but doing it within the Nova codebase. This is similar to solution @idragon81 posted, but more abstract.

In my base resource class, I've added the following methods:

/**
 * Creates and returns a fresh instance of the model represented by the resource.
 *
 * @return mixed
 */
public static function newModel()
{
    $model = static::$model;

    $instance = new $model;

    $instance->setRawAttributes(static::getDefaultAttributes());

    return $instance;
}

/**
 * Returns the default attributes for new model instances.
 *
 * @return array
 */
public static function getDefaultAttributes()
{
    return static::$defaultAttributes ?? [];
}

This allows me to provide either a defaultAttributes static attribute on the resource, or I can override the getDefaultAttributes method on the implementing resource (which is useful if I needed to make function calls instead of providing constants).

Now in my specific resource, I've added the following attribute:

/**
 * The default attributes for new model instances.
 *
 * @var array
 */
public static $defaultAttributes = [
    'percent' => 1
];

Doing this will send my "1" through my resolve callback, and converting it to "100" (similar to how "0.35" would have been changed to "35", had "0.35" been the value stored in the database). If the model is instead retrieved from the database, then it will replace my default value (even if the stored value is null, null would be used for the "percent" attribute rather than "1").

Hopefully this helps.

ZebTheWizard commented 4 years ago

I don't know if this is the Laravel way of doing things, but I just returned the default value within the resolve callback. However, I tried the same thing for relationships and it threw an error.

Boolean::make('My Boolean', 'working', function () {
    return true;
})
Date::make('My Default Date', 'date', function () {
    return now();
})
rasmuscnielsen commented 4 years ago

For anyone interested I made this macro which seems to do the trick:

In NovaServiceProvider:

public function boot()
{
    Field::macro('default', function ($default) {
        return $this->resolveUsing(function ($value) use ($default) {
            return $value ?? $default;
        });
    });

    parent::boot();
}

Use it like:

Text::make('Name')
    ->sortable()
    ->rules('required', 'max:255')
    ->default('John Doe'),
intraordinaire commented 4 years ago

@rasmuscnielsen Excellent thanks a lot ! I've juste updated a bit your solution. In some cases, I needed a default value only when creating, but user can empty the field.

In NovaServiceProvider :

public function boot()
{
    Field::macro('default', function ($default) {
        return $this->resolveUsing(function ($value, $model) use ($default) {
            return $model->getKey() === null ? $default : $value;
        });
    });

    parent::boot();
}

And the usage stay the same when creating the field.

As i'm also using, NovaFlexibleContent package, i've added :

public function getKey()
{
    return $this->inUseKey();
}

In my layouts in order to have them working like expected.

ajthinking commented 4 years ago

@rasmuscnielsen and @intraordinaire when defining a macro like that I get

"Using $this when not in object context",…}

at $this->resolveUsing

Any ideas what I am missing?

mpoma commented 4 years ago

@intraordinaire Excelent, but i dont understand where you put and how to use in contenflexible public function getKey() { return $this->inUseKey(); }

intraordinaire commented 4 years ago

@mpoma i have a BaseLayout class, all my flexibles extend this class. Just an example, but you can put that on every class that extend the Layout class from the FlexibleContent package, or implementing your own solution if necessary.

use Whitecube\NovaFlexibleContent\Layouts\Layout;

class BaseLayout extends Layout
{
    /**
     * Return the layout identifier
     *
     * @return null|string
     */
    public function getKey()
    {
        return $this->inUseKey();
    }
}

@ajthinking Not sure what can be the problem here. Are you well in the NovaServiceProvider, is Field the good Laravel Nova class (use Laravel\Nova\Fields\Field;), can you dump $this ?

florentpoujol commented 4 years ago

Just for completeness, setting the default value via the 'value' attribute and withMeta() method not only override existing values (including in lists), but also prevent the value to be changed in the form, even if a different value was manually entered.

The correct answer is indeed from https://github.com/laravel/nova-issues/issues/58#issuecomment-419691484 (and https://github.com/laravel/nova-issues/issues/58#issuecomment-533821333) : set the default values directly on the model attributes.

I personally do it in the fields() methods of the Nova resource, when the model exists property is false meaning that we are on the creation form.

avonian commented 4 years ago

As per what @mink suggested, default values for json (code) fields can be achieved by setting the value to the attribute in the model's constructor

  • if you add the value in attributes static it'd have to be a json string given laravel/nova tries to json decode it because of the cast
  • if you add it in creating, that's too late

This works for me:

    protected $casts = [
        'fields' => 'array',
    ];

    public function __construct(array $attributes = [])
    {
        $this->fields = config('app.default_fields');

        parent::__construct($attributes);
    }

I do however also have this field on a pivot... and haven't found a way to specify a default for that... the best way would probably indeed by a defaultValue method on Nova fields. I am toying with pivot classes but as of yet have had no success, I've also tried using resolveUsing but so far no luck

did you ever figure out a way to set default values on pivot fields? i've looked up and down with no luck

ziming commented 4 years ago

How would default value be handled for hasMany?

For example on User Detail page, I click 'add book', but on the book creation form i want school_id of Book to be prefilled with that User's school_id

Theohorsmeier commented 4 years ago

@ziming

How would default value be handled for hasMany?

For example on User Detail page, I click 'add book', but on the book creation form i want school_id of Book to be prefilled with that User's school_id

I have tried something similar to your situation, in the default callback function I want to use another relation of the current model to find the ID to use as default value, but seems not to work, the callback is not even called?

What I tried was something like this (in the Book fields):

BelongsTo::make('School')->default(function () {
  return $this->user->school->id;
})

So that when I create a new book from the User page, where the user ID is already fixed, it should be able to also assign the default school ID.

But since it doesn't seem to work, what would be the correct way of using default to achieve this?

Theohorsmeier commented 4 years ago

It seems like the callback is simply not executed for BelongsTo fields? This works:

BelongsTo::make('School')->default(1)

This works (like in the examples):

Text::make('School')->default(function () {
  return 'Saturn';
}),

But then this does nothing:

BelongsTo::make('School')->default(function () {
  return 1;
}),

Another problem seems to be that at the moment of assigning the default value, the user relation is not known yet (even though we create the Book from the User resource page, and the dropdown on the Book create page is greyed out with the correct value).

This means that when I try to do it like this:

BelongsTo::make('School')->default($this->user->school->id)

I get Trying to get property 'school' of non-object

So maybe using default for this is not even possible?

oleksandr-roskovynskyi commented 4 years ago

Unfortunately using the withMeta function doesn't appear to work in combination displayUsingLabels() when using a select field. Does anyone have a solution for this situation?

Select::make('Rate')
    ->options('work_rates' => [
        '1' => 'Standard',
        '2' => 'Priority',
    ])
    ->withMeta(['value' => '1'])
    ->displayUsingLabels()

Have you solved this problem? Right now, I also can't solve the problem of displaying labels when default values are set for a field

Select::make('Тип', 'type')
                ->options(\App\Cost::TYPE)
                ->sortable()
                ->withMeta([
                    'value' => $this->type ?? 'expense' // default value for the select
                ])
lopandpe commented 4 years ago

Easier, for the date field asked:

Date::make('Upload date', 'uploadDate', function ($value) {
                if ($value){
                    return $value;
                }else{
                    return now();
                }
            })
eugenefvdm commented 4 years ago

Wow this super cool soap opera is like the longest ever explanation of how to set defaults for Nova! For those still struggling with Select and the initial default value and displayUsingLabels because you're using ENUMs and you have nice friendly names for example Lead Statuses, that point to sensible lower case defaults in your database, e.g.:

'proposal_sent' => 'Proposal Sent'

The key is don't use withMeta but rather resolveUsing as in the example below:

Select::make('Status')->options(
           Leads::ui_options()
            )
            ->sortable()
            ->resolveUsing(function () {
                return $this->status ?? 'new';
            })
           ->displayUsingLabels(),

Compliments of this Stack post.

cja-github commented 3 years ago

This is how to set a default value on create only for a DateTime field:

            DateTime::make('When')
                ->withMeta(['value' => $this->when ?? Carbon::now()->toDateTimeString()])
                ->rules('required')
                ->hideFromIndex()
                ->hideFromDetail(),

            DateTime::make('When')
                ->sortable()
                ->hideWhenCreating()
                ->hideWhenUpdating()

Inspired by https://vander.host/knowledgebase/software-development/how-to-set-the-default-date-using-laravel-nova/

namnh06 commented 3 years ago

I met this issue today, should use this instead of the default or withMeta, from: https://github.com/laravel/nova-issues/issues/493#issuecomment-419735037

use Illuminate\Database\Eloquent\Model;

class Booking extends Model
{
    protected $attributes = [
        'status' => 'pending',
        'price' => 0,
        'commission' => 0,
    ];
}
n1k-crimea commented 3 years ago

Wow this super cool soap opera is like the longest ever explanation of how to set defaults for Nova! For those still struggling with Select and the initial default value and displayUsingLabels because you're using ENUMs and you have nice friendly names for example Lead Statuses, that point to sensible lower case defaults in your database, e.g.:

'proposal_sent' => 'Proposal Sent'

The key is don't use withMeta but rather resolveUsing as in the example below:

Select::make('Status')->options(
           Leads::ui_options()
            )
            ->sortable()
            ->resolveUsing(function () {
                return $this->status ?? 'new';
            })
           ->displayUsingLabels(),

Compliments of this Stack post.

it's work, tnx