vanchuck
vanchuck

Hi there,

I'm aware of the baked-in Sortable trait, and have used it extensively with "top-level" models as well as nested sets.

However, I'd like to be able to extend this behavior to child items. For example in a many-to-many relationship between Products and Categories. My solution for now is to have the relation manager pop up a form to edit the sort_order (stored as pivot data in the "category has product" link table) manually for the Products in a Category, which works, but is a bit clunky and could mean a lot of editing of records if you want to insert a new item.

Does anyone know of any examples of drag-and-drop reordering either directly in the relation manager, or through some other solution? Any other ideas on how to make this a little more user-friendly?

Thanks for any input!

Crazymodder
Crazymodder

Scott have wrote a plugin where he make a list sortable. Maybe you can take a look at this:

https://github.com/scottbedard/draganddrop

vanchuck
vanchuck

Thanks for the reply Crazymodder! You set me on the right path for sure. I managed to get this working for Relation lists, though it did require a bit of fiddling. If anyone is going to try this I'd recommend trying out the draganddrop plugin on a non-relation list first to get familiar with it.

Here's the steps I took, using my setup of a Product hasMany Variants

1) Add the hidden input as per usual with the draganddrop plugin (see the example model in the plugin) -- this just adds the object id to each row of the list so that it can be accessed via javascript when the "Reorder" button is clicked.

2) Set up the toolbar partial to add the button to the relation list toolbar. Create the toolbarPartial under the 'view' section of the $relationConfig file. You'll have to copy the existing buttons you want to keep from their source files, eg check here: https://github.com/octobercms/october/tree/master/modules/backend/behaviors/relationcontroller/partials

3) Add the reorder button (similar to the usual way of doing things, but note the extra model_id parameter being sent

here's my reorder button:

<button
    id="updatePosition"
    class="btn btn-reorder oc-icon-list-ol"
    disabled="disabled"
    onclick="$(this).data('request-data', {
    checked: $('.index_reposition').map(function(){  return $(this).val() }).get(), model_id: <?php echo $this->formGetModel()->id; ?> })"
    data-request-success="initializeSorting();"
    data-request="onUpdateVariantPosition"
    data-stripe-load-indicator>
    Update Order
</button>

4) Set up the handler in the controller.

Here's mine:

public function update_onUpdateVariantPosition()
{
    $moved = [];
    $position = 0;
    if (($reorderIds = post('checked')) && is_array($reorderIds) && count($reorderIds)) {
        foreach ($reorderIds as $id) {
            if (in_array($id, $moved) || !$record = Variant::find($id))
                continue;
            $record->sort_order = $position;
            $record->save();
            $moved[] = $id;
            $position++;
        }
        Flash::success('Successfully re-ordered variants.');
    }

    $model = Product::find(post('model_id'));
    $this->initForm($model);
    $this->initRelation($model, 'variants');

    return $this->relationRefresh('variants');
} 

The last 4 lines of code are needed to re-initalize the relation in the context of the controller before the updated list can be returned to the browser. See discussion about this behaviour here: https://octobercms.com/forum/post/problem-with-relationrefresh

Edit: Some changes were needed to be made to scottbedard's plugin, which he's now merged. Here are the changes from the pull request: https://github.com/scottbedard/draganddrop/pull/1/commits/7f5fec6945b8eac649f11ef5b97443b2ec918e80

Hope this helps someone. Cheers.

Last updated

yarbala24847
yarbala24847

Thank vanchuck, it realy helped me

BG78
BG78

Thanks vanchuck. How did you implement the js files into the relation list? My js files wont load with $this->addJs() in __construct().

hauserj22885
hauserj22885

Thanks @vanchuck, much appreciated!

ukhome1127
ukhome1127

vanchuck said:

Thanks for the reply Crazymodder! You set me on the right path for sure. I managed to get this working for Relation lists, though it did require a bit of fiddling. If anyone is going to try this I'd recommend trying out the draganddrop plugin on a non-relation list first to get familiar with it.

Here's the steps I took, using my setup of a Product hasMany Variants

1) Add the hidden input as per usual with the draganddrop plugin (see the example model in the plugin) -- this just adds the object id to each row of the list so that it can be accessed via javascript when the "Reorder" button is clicked.

2) Set up the toolbar partial to add the button to the relation list toolbar. Create the toolbarPartial under the 'view' section of the $relationConfig file. You'll have to copy the existing buttons you want to keep from their source files, eg check here: https://github.com/octobercms/october/tree/master/modules/backend/behaviors/relationcontroller/partials

3) Add the reorder button (similar to the usual way of doing things, but note the extra model_id parameter being sent

here's my reorder button:

<button id="updatePosition" class="btn btn-reorder oc-icon-list-ol" disabled="disabled" onclick="$(this).data('request-data', { checked: $('.index_reposition').map(function(){ return $(this).val() }).get(), model_id: <?php echo $this->formGetModel()->id; ?> })" data-request-success="initializeSorting();" data-request="onUpdateVariantPosition" data-stripe-load-indicator> Update Order

4) Set up the handler in the controller.

Here's mine:

public function update_onUpdateVariantPosition() { $moved = []; $position = 0; if (($reorderIds = post('checked')) && is_array($reorderIds) && count($reorderIds)) { foreach ($reorderIds as $id) { if (in_array($id, $moved) || !$record = Variant::find($id)) continue; $record->sort_order = $position; $record->save(); $moved[] = $id; $position++; } Flash::success('Successfully re-ordered variants.'); }

  $model = Product::find(post('model_id'));
  $this->initForm($model);
  $this->initRelation($model, 'variants');

  return $this->relationRefresh('variants');

}

The last 4 lines of code are needed to re-initalize the relation in the context of the controller before the updated list can be returned to the browser. See discussion about this behaviour here: https://octobercms.com/forum/post/problem-with-relationrefresh

Edit: Some changes were needed to be made to scottbedard's plugin, which he's now merged. Here are the changes from the pull request: https://github.com/scottbedard/draganddrop/pull/1/commits/7f5fec6945b8eac649f11ef5b97443b2ec918e80

Hope this helps someone. Cheers.

Thank you for your efforts. It really helps. Many thanks.

K.Singh
K.Singh

vanchuck said:

Thanks for the reply Crazymodder! You set me on the right path for sure. I managed to get this working for Relation lists, though it did require a bit of fiddling. If anyone is going to try this I'd recommend trying out the draganddrop plugin on a non-relation list first to get familiar with it.

Here's the steps I took, using my setup of a Product hasMany Variants

1) Add the hidden input as per usual with the draganddrop plugin (see the example model in the plugin) -- this just adds the object id to each row of the list so that it can be accessed via javascript when the "Reorder" button is clicked.

2) Set up the toolbar partial to add the button to the relation list toolbar. Create the toolbarPartial under the 'view' section of the $relationConfig file. You'll have to copy the existing buttons you want to keep from their source files, eg check here: https://github.com/octobercms/october/tree/master/modules/backend/behaviors/relationcontroller/partials

3) Add the reorder button (similar to the usual way of doing things, but note the extra model_id parameter being sent

here's my reorder button:

<button id="updatePosition" class="btn btn-reorder oc-icon-list-ol" disabled="disabled" onclick="$(this).data('request-data', { checked: $('.index_reposition').map(function(){ return $(this).val() }).get(), model_id: <?php echo $this->formGetModel()->id; ?> })" data-request-success="initializeSorting();" data-request="onUpdateVariantPosition" data-stripe-load-indicator> Update Order

4) Set up the handler in the controller.

Here's mine:

public function update_onUpdateVariantPosition() { $moved = []; $position = 0; if (($reorderIds = post('checked')) && is_array($reorderIds) && count($reorderIds)) { foreach ($reorderIds as $id) { if (in_array($id, $moved) || !$record = Variant::find($id)) continue; $record->sort_order = $position; $record->save(); $moved[] = $id; $position++; } Flash::success('Successfully re-ordered variants.'); }

  $model = Product::find(post('model_id'));
  $this->initForm($model);
  $this->initRelation($model, 'variants');

  return $this->relationRefresh('variants');

}

The last 4 lines of code are needed to re-initalize the relation in the context of the controller before the updated list can be returned to the browser. See discussion about this behaviour here: https://octobercms.com/forum/post/problem-with-relationrefresh

Edit: Some changes were needed to be made to scottbedard's plugin, which he's now merged. Here are the changes from the pull request: https://github.com/scottbedard/draganddrop/pull/1/commits/7f5fec6945b8eac649f11ef5b97443b2ec918e80

Hope this helps someone. Cheers.

how to use sort_order instead of position variable and error comes from model $record not found

Last updated

miroslav.shubin
miroslav.shubin

vanchuck said:

Thanks for the reply Crazymodder! You set me on the right path for sure. I managed to get this working for Relation lists, though it did require a bit of fiddling. If anyone is going to try this I'd recommend trying out the draganddrop plugin on a non-relation list first to get familiar with it.

Here's the steps I took, using my setup of a Product hasMany Variants

1) Add the hidden input as per usual with the draganddrop plugin (see the example model in the plugin) -- this just adds the object id to each row of the list so that it can be accessed via javascript when the "Reorder" button is clicked.

2) Set up the toolbar partial to add the button to the relation list toolbar. Create the toolbarPartial under the 'view' section of the $relationConfig file. You'll have to copy the existing buttons you want to keep from their source files, eg check here: https://github.com/octobercms/october/tree/master/modules/backend/behaviors/relationcontroller/partials

3) Add the reorder button (similar to the usual way of doing things, but note the extra model_id parameter being sent

here's my reorder button:

<button id="updatePosition" class="btn btn-reorder oc-icon-list-ol" disabled="disabled" onclick="$(this).data('request-data', { checked: $('.index_reposition').map(function(){ return $(this).val() }).get(), model_id: <?php echo $this->formGetModel()->id; ?> })" data-request-success="initializeSorting();" data-request="onUpdateVariantPosition" data-stripe-load-indicator> Update Order

4) Set up the handler in the controller.

Here's mine:

public function update_onUpdateVariantPosition() { $moved = []; $position = 0; if (($reorderIds = post('checked')) && is_array($reorderIds) && count($reorderIds)) { foreach ($reorderIds as $id) { if (in_array($id, $moved) || !$record = Variant::find($id)) continue; $record->sort_order = $position; $record->save(); $moved[] = $id; $position++; } Flash::success('Successfully re-ordered variants.'); }

  $model = Product::find(post('model_id'));
  $this->initForm($model);
  $this->initRelation($model, 'variants');

  return $this->relationRefresh('variants');

}

The last 4 lines of code are needed to re-initalize the relation in the context of the controller before the updated list can be returned to the browser. See discussion about this behaviour here: https://octobercms.com/forum/post/problem-with-relationrefresh

Edit: Some changes were needed to be made to scottbedard's plugin, which he's now merged. Here are the changes from the pull request: https://github.com/scottbedard/draganddrop/pull/1/commits/7f5fec6945b8eac649f11ef5b97443b2ec918e80

Hope this helps someone. Cheers.

I'm do some changes for universal usage (willn't change code for different relations). Button in toolbar:


    <button
        id="updatePosition"
        class="btn btn-reorder oc-icon-list-ol"
        disabled="disabled"
        onclick="$(this).data('request-data', {
    checked: $('.index_reposition').map(function(){  return $(this).val() }).get(), model_id: formGetModel()->id; ?> , relationName: "= $relationField ?>"})"
        data-request-success="initializeSorting();"
        data-request="onUpdateRelationItems"
        data-stripe-load-indicator>
        Update Order
    </button>

controller action:


 public function update_onUpdateRelationItems()
    {
        $main_model = $this->formCreateModelObject();
        $relationName = post('_relation_field');
        $relationModel = $main_model->getRelationDefinition($relationName)[0];

        $moved = [];
        $position = 0;

        if (($reorderIds = post('checked')) && is_array($reorderIds) && count($reorderIds)) {
            foreach ($reorderIds as $id) {
                if (in_array($id, $moved) || !$record = $relationModel::find($id))
                    continue;
                $record->sort_order = $position;
                $record->save();
                $moved[] = $id;
                $position++;
            }
            Flash::success('Successfully re-ordered variants.');
        }

        $model = $main_model::find(post('model_id'));
        $this->initForm($model);
        $this->initRelation($model, $relationName);

        return $this->relationRefresh($relationName);
    }

Thank you for working decision!

Last updated

1-9 of 9