Sort and save order of model-elements drag & drop

Suppose you have Products,Categories and each product belongs in many categories

Product (id,name) Category (id,name) ProductCategory (id,productId,categoryId,Order)

Suppose also that the Order define the priority of the product category or any other meaning of "order" priority

You know how to display the categories of a product (using CGridView or CListView) but how can change their ordering in easy way?

The main way is using a text for each record tha define the order, but you have to change each record separately

For example you have the order of products

  • id3=>order1

  • id4=>order2

  • id1=>order3

  • id2=>order4

If you want to change id3 order and put it between id1 and id2 then

  • id3=>order3

  • id4=>order1

  • id1=>order2

  • id2=>order4

many changes and tiring especially for many items

Drupal CMS makes it using drag&drop. I will show how to do that in Yii

In your view of product

$relProvider = new CArrayDataProvider($model->categories, array(
    'keyField' => 'id', //optional
        'pagination' => array(
            'pageSize' => 200,
        ),
    ));

$this->widget('zii.widgets.CListView', array(
    'dataProvider' => $relProvider,
    'id' => 'items-list',
    'itemView' => '_item',
    'template' => "{pager}{items}",
    'afterAjaxUpdate' => 'function (id,data) {refreshSortable(); }',
));


<script>
    $(document).ready(function() {
        refreshSortable();
    });


    function refreshSortable() {
        $("#items-list .items").sortable(
                {stop: function(event, ui) {
                        saveOrder(event, ui);
                    }}
        );
    }

    function saveOrder(event, ui) {
        var order = [];
        $('#items-list .items #item_id').each(function(k, v) {
            var v1 = $(v).val();
            order.push(v1);
        }
        );

        $.ajax({
            type: "POST",
            url: "<?php echo $this->createUrl('setOrder',array('productId'=>$model->id));?>",
            dataType: 'json',
            data: {data: order}
        });
    }
</script>

In _item.php (related category)

<?php echo $form->hiddenField($data, 'id'); ?>
 
    <div class="view">
          <?php echo CHtml::encode($data->name); ?>
    </div>
    
    ...

And In yourController.php

//data variable has data like {productId1,roductId4,roductId2} (order is important that generated from saveOrder javascript function)
    public function actionSetOrder($productId) {
        if (isset($_POST['data'])) :
            $data = $_POST['data'];
            $mods = array();
            $ords = array();
            $ai = 0;
            foreach ($data as $i):
                $m = ProductCategory::model()->findByAttributes(array('categoryId' => $i, 'productId' => $i));

                if ($m) :  //collect All posted ProductCategories and its order
                    $mods[] = $m;
                    $ords[] = ($m->Order != 0) ? $m->Order : ++$ai;
                endif;

            endforeach;
            sort($ords); //set in order collected orders

            
            $pr = 0;
            foreach ($ords as &$o): //if a record has the same order with another one shift by one
                if ($pr == $o) {
                    $o++;
                }
                $pr = $o;
            endforeach;

            $idx = 0;
            foreach ($mods as $m) : //set the new order foreach model that involved in this action
                $m->Order = $ords[$idx];
                $m->save();
                $idx++;
            endforeach;
        endif;
    }

That's it!