laravel / nova-issues

553 stars 34 forks source link

Default ordering resource #156

Closed franzdumfart closed 6 years ago

franzdumfart commented 6 years ago

Found nothing in the docs, so here is my question:

How can I set the default ordering of a Resources items on list view? At the moment it looks like it is orderBy('id', 'desc'). Setting the first visible and sortable field would be a great indicator for the planned behavior. Or is possible the set it via a property or method?

crnkovic commented 6 years ago

I’m on mobile now but can you try overriding indexQuery method on the resource and put it there?

danrichards commented 6 years ago

@crnkovic is correct @franzdumfart

This can be accomplished by overloading the indexQuery method.

Unfortunately the solution is slightly more fugly than probably desired, because the order by id beats the request to the query. But this works fine.

    const DEFAULT_INDEX_ORDER = 'last_name';

    public static function indexQuery(NovaRequest $request, $query)
    {
        $query->when(empty($request->get('orderBy')), function(Builder $q) {
            $q->getQuery()->orders = [];

            return $q->orderBy(static::DEFAULT_INDEX_ORDER);
        });
    }

I should note, that the UI state and url state does not know sorting has been changed. So if you are sorting by a column that is visible by default, the UI will not reflect that.

See suggestion from @jasonlav, this is a bit more desirable.

franzdumfart commented 6 years ago

Thanks so much, this helps! Will extend all my Nova resources from a Base class and add a helper method there, so I can use it on every Resource with just setting one property. Hopefully this will be implemented someday in Nova itself. Need this very often.

loren138 commented 6 years ago

Slightly, updated code to make it one line per Resource.

In App/Nova/Resource.php:

    public static $defaultSort = null; // Update to your default column

    /**
     * Build an "index" query for the given resource.
     *
     * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function indexQuery(NovaRequest $request, $query)
    {
        if (static::$defaultSort && empty($request->get('orderBy'))) {
            $query->getQuery()->orders = [];
            return $query->orderBy(static::$defaultSort);
        }
        return $query;
    }

In any resource where you want to change the sort:

public static $defaultSort = 'name';
franzdumfart commented 6 years ago

Thanks @loren138, works perfect! We also should add another static variable like public static $defaultSortDirection = null; to define the direction. I think we can close this one.

crnkovic commented 6 years ago

I added protected static $sort = ['id' => 'desc']; and then returned $query->orderBy(key(static::$sort), reset(static::$sort)); :)

I hate that PHP doesn't have tuples, god damn.

Nice solution, though. :)

loren138 commented 6 years ago

@franzdumfart Probably shouldn't close it until it is a feature request or a pull request.

franzdumfart commented 6 years ago

I've ended with this code in App\Nova\Resource

/**
 * Default ordering for index query.
 *
 * @var array
 */
public static $indexDefaultOrder = [
    'id' => 'desc'
];

/**
 * Build an "index" query for the given resource.
 *
 * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
 * @param  \Illuminate\Database\Eloquent\Builder  $query
 * @return \Illuminate\Database\Eloquent\Builder
 */
public static function indexQuery(NovaRequest $request, $query)
{
    if (empty($request->get('orderBy'))) {
        $query->getQuery()->orders = [];
        return $query->orderBy(key(static::$indexDefaultOrder), reset(static::$indexDefaultOrder));
    }
    return $query;
}

Thanks for all the suggestions! 🙌

telkins commented 6 years ago

How 'bout sorting on a relation field...?

For example, activities belong to an event and an event has a date. If you view activities, you may want to sort on their event dates.

taylorotwell commented 6 years ago

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

loren138 commented 6 years ago

@taylorotwell Is the customer support email listed somewhere on the Nova website? The only email I've been able to find so far is your email from the composer.json file.

phoenixg commented 6 years ago

@taylorotwell Is it possible to open a nova-feature-requests repository, so we can all submit to that?

jasonlav commented 6 years ago

The applyOrderings method from Laravel\Nova\PerformsQueries can be overridden on a per-model basis to set default ordering.

carloshc commented 5 years ago

It's seems that this approach does not works on relationship models.

So I put a orderBy() on my Model.

    public function comments()
    {
        return $this->belongsToMany('App\Posts')
                    ->withPivot('order')
                                          ->orderBy('order');
    }
shane-smith commented 5 years ago

I'm trying to resolve this myself at the moment, could you provide a more detailed example please @jasonlav? Attempting to set the default sort order for a HasMany-related resource.

e.g. Town resource has many Roads, which appear as a relationship when viewing a Town. Attempting to simply sort the Roads panel by name ascending, without success. Perhaps my brain is just broken at the moment :)

jasonlav commented 5 years ago

The PerformsQueries trait -- implemented by Nova's Resource class -- has an applyOrderings method that can be overwritten on the Nova Resource for custom sorting.

    /**
     * Apply any applicable orderings to the query.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  array  $orderings
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected static function applyOrderings($query, array $orderings)
    {
        if (empty($orderings)) {

            // To maintain user-initiated column sorting, I would recommend placing your custom sorting here.

            return empty($query->orders)
                        ? $query->latest($query->getModel()->getQualifiedKeyName())
                        : $query;
        }

        foreach ($orderings as $column => $direction) {
            $query->orderBy($column, $direction);
        }

        return $query;
    }
aozen commented 5 years ago
public static $indexDefaultOrder = [
    'id' => 'desc'
];

public static function indexQuery(NovaRequest $request, $query)
{
    if (empty($request->get('orderBy'))) {
        $query->getQuery()->orders = [];
        return $query->orderBy(key(static::$indexDefaultOrder), reset(static::$indexDefaultOrder));
    }
    return $query;
}

This code is working. But i have several questions. I am using this query without order

         $companyID= Company::where('user_id', auth()->user()->id)->first()->id;
            return $query->where('company_id', $companyID)->first();

This is showing users own data. Other users informations are not displaying. When i use with order query its showing all data. All user can display all datas. How can i add my own query with order?

I made this. But when sort some column. All datas showing up again...

public static $indexDefaultOrder = [
    'id' => 'desc'
];

public static function indexQuery(NovaRequest $request, $query)
{
    $companyID= Company::where('user_id', auth()->user()->id)->first()->id;
    if (empty($request->get('orderBy'))) {
        $query->where('company_id', $companyID)->getQuery()->orders = [];
        return $query->where('company_id', $companyID)->orderBy(key(static::$indexDefaultOrder), reset(static::$indexDefaultOrder));
    }
    return $query;
}

How can i solve this. How can i use order and display limit (each user) together?

rasmuscnielsen commented 5 years ago

The PerformsQueries trait -- implemented by Nova's Resource class -- has an applyOrderings method that can be overwritten on the Nova Resource for custom sorting.

    /**
     * Apply any applicable orderings to the query.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  array  $orderings
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected static function applyOrderings($query, array $orderings)
    {
        if (empty($orderings)) {

            // To maintain user-initiated column sorting, I would recommend placing your custom sorting here.

            return empty($query->orders)
                        ? $query->latest($query->getModel()->getQualifiedKeyName())
                        : $query;
        }

        foreach ($orderings as $column => $direction) {
            $query->orderBy($column, $direction);
        }

        return $query;
    }

Using this approach I ended up with a simple overwrite in my base Resource file

    protected static function applyOrderings($query, array $orderings)
    {
        if (empty($orderings) && property_exists(static::class, 'orderBy')) {
            $orderings = static::$orderBy;
        }

        return parent::applyOrderings($query, $orderings);
    }

Now in any Resource I can simple add

    public static $orderBy = ['name' => 'asc'];

Works like a charm

SHAH0001 commented 5 years ago

I used sorting by overriding the indexQuery method. But when I try to apply filters they do not work. What could be the reason?

sonalmahajan01 commented 5 years ago

I have my resource like : ` ID::make()->sortable(),

        Text::make('Content', 'content', function() {
            return '<a href="' . $this->getContentLink() . '" target="_blank">'.$this->content->title->title.'</a>';
        })->asHtml()->hideWhenUpdating()->sortable(),

        Text::make('Seller', function(){
            return $this->content->organization->company_name;
        })->hideWhenUpdating()->sortable(),

        Text::make('Buyer', function() {
            if(empty($this->parent_offer_id) || $this->created_by_buyer == 1) {
                return $this->user->organization->company_name;
            }else {
                $parentOffer = AppOffer::find($this->parent_offer_id);
                return $parentOffer->user->organization->company_name;
            }
        })->hideWhenUpdating()->sortable(),`

The sorting doesn't work on the seller and buyer. What is the way to implement it ?

bakerkretzmar commented 3 years ago

For anyone else still coming back to this issue, Nova v3.19.1 introduced changes that (at least for me) broke @rasmuscnielsen's awesome workaround. Nova now explicitly orders all index queries by a resource model's primary key by default, so to get the snippet above working again, I had to remove the orderBy clauses manually:

    protected static function applyOrderings($query, array $orderings)
    {
        if (empty($orderings) && property_exists(static::class, 'orderBy')) {
+           $query->reorder();

            $orderings = static::$orderBy;
        }

        return parent::applyOrderings($query, $orderings);
    }
rdarcy1 commented 3 years ago

If you sort a field and cycle through until you're not sorting any more (click the field name three times), you can end up with a null entry in the $orderings array, e.g. ['quantity' => null].

To make sure you're still applying your custom default sort, filter the orderings array:

    protected static function applyOrderings($query, array $orderings)
    {
-       if (empty($orderings) && property_exists(static::class, 'orderBy')) {
+       if (empty(array_filter($orderings)) && property_exists(static::class, 'orderBy')) {
           $query->reorder();

            $orderings = static::$orderBy;
        }

        return parent::applyOrderings($query, $orderings);
    }
sfinktah commented 2 years ago

@rdarcy1 That's fixed by now (3.29 anyway).

It now looks like this (I have changed latest to oldest because that's the order I wanted).

    protected static function applyOrderings($query, array $orderings)
    {
        $orderings = array_filter($orderings);

        if (empty($orderings)) {
            return empty($query->getQuery()->orders) && ! static::usesScout()
                        ? $query->oldest($query->getModel()->getQualifiedKeyName())
                        : $query;
        }

        foreach ($orderings as $column => $direction) {
            $query->orderBy($column, $direction);
        }

        return $query;
    }
benedict-w commented 2 years ago

Is this still the best way with Nova 4?

JshGrn commented 2 years ago

^ +1, Nova 4 still the best way? Seems strange it wasn't implemented if so

sfinktah commented 1 year ago

Still the same solution, nothing implemented in Nova 4.