⚠ In case you've missed it, we have migrated to our new website, with a brand new forum. For more details about the migration you can read our blog post for website migration. This is an archived forum. ⚠

  •     

profile picture

Drag and drop for order of items



Kobus

Kobus
  • profile picture
  • Member

Posted 27 January 2013 - 13:30 PM

Hi,

The actual question is short: Does GroceryCRUD have a drag-drop feature built in?

I am going to explain why I need it, because I think it could be a very handy feature.

Let's say I have the following content entered into my site's database:

Item 1: Welcome to the site (sticky)
Item 2: New release of product X
Item 3: New release of product Y
Item 4: Bug release of product X

Using default date sort, items will be in order: item 1, item 4, item 3, lastly item 2 (date ascending, factoring in sticky post). So items will now be displayed as such:

Item 1: Welcome to the site (sticky)
Item 4: Bug release of product X
Item 3: New release of product Y
Item 2: New release of product X

In most cases that will work just fine, but in order to change item dates to set the display order is tedious and not really intuitive, so I want to assign a weight in the item as follows:

Item 1: Welcome to the site (sticky, weight: 1)
Item 2: New release of product X (weight: 4)
Item 3: New release of product Y (weight: 2)
Item 4: Bug release of product X (weight: 3)

This would result in me overriding the display order to the following:

Item 1: Welcome to the site (sticky, weight: 1)
Item 3: New release of product Y (weight: 2)
Item 4: Bug release of product X (weight: 3)
Item 2: New release of product X (weight: 4)

So - in concept a simple thing to do, but what would be the best way using GroceryCRUD to do this? I currently have a dropdown, but the drawback of this is that you can not see an overview of content items in that dropdown in the content piece (setting the weight of the item in the "content" edit screen).

I would imagine it would be better to put the weight in the "page" listing screen where I could add an action "reorder page content", and upon clicking that, opens a dialog box with a drag and drop functionality where I can get all content items assigned to the page, and then drag-drop them in the order I want.

Kind regards,

Kobus

filtudor

filtudor
  • profile picture
  • Member

Posted 09 April 2013 - 13:25 PM

bump on this.

Such a feature will be awesome!


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 19 May 2013 - 20:05 PM

Well it had been a long time even i was thinking of having the same feature and so i finally decided to build the same over it. I will share you how the stuff works ... i know it aint that clean but it did work like a charm for me. Drag and drop as well as a complete prioirty / row -order management you can say is developed here. I wish all the best to the grocery crud team if they can incorporate this feature in their existing library, it will be an add-on stuff and a great utility for the app developers on grocery crud.

 

Have attached 2 files

1 - priority_manager - this file is the library file that is responsible for managing the re-ordering of the rows in given table.

2 - priority_model - the mode for managing all the queries for managing of order / priority.

 

The code is dynamic enough to manage the order management for any table. And also is capable of handling order management for each group in table (for ex - a category_id in case of product listing table).

 

Now here is how to add / use the same  (all part of the controller)

function updatePosition($table, $sourceId, $distance, $direction) {

		$this->load->library('Priority_manager');
		$manager = new Priority_manager();
		$manager->setTable($table);
		$manager->setPriorityField('priority');

		switch ($direction) {
			case 'up' :
				$manager->moveUpBy($sourceId, $distance);
				break;
			case 'down' :
				$manager->moveDownBy($sourceId, $distance);
				break;
			case 'top' :
				$manager->moveToTop($sourceId);
				break;
			case 'bottom' :
				$manager->moveToBottom($sourceId);
				break;
			case 'default' :
				$manager->moveTo($sourceId, $distance);
				break;
		}
	}
	
	function updateGroupPosition($table, $group, $sourceId, $distance, $direction) {
	
		$this->load->library('Priority_manager');
		$manager = new Priority_manager();
		$manager->setTable($table);
		$manager->setGroupField($group);
		$manager->setPriorityField('priority');
	
		switch ($direction) {
			case 'up' :
				$manager->moveUpBy($sourceId, $distance);
				break;
			case 'down' :
				$manager->moveDownBy($sourceId, $distance);
				break;
			case 'top' :
				$manager->moveToTop($sourceId);
				break;
			case 'bottom' :
				$manager->moveToBottom($sourceId);
				break;
			case 'default' :
				$manager->moveTo($sourceId, $distance);
				break;
		}
	}

    function resetPositions($table, $group_field=FALSE, $group_value=FALSE) {
        $this->load->library('Priority_manager');
        $manager = new Priority_manager();
        $manager->setTable($table);
        $manager->setGroupField($group_field);
        $manager->setPriorityField('priority');
        $manager->rearrangePriorities($group_value);
    }

The above 2 functions a re self explanatory. The 1st one is useful for making calls to re-ordering in case there is no group. The other one is having an addition group setting for the same.

The last one is handy for reseting the priority for a table. If there is a need to reset only for a group, provide the group value so it will reset only for the given group with the passed value.

 

As for the addition what i did in here was created a dynamic JS output from the controller itself because i wanted to set everything dynamic in place.

    function dragdrop_js() {
    	$js = '
    		var startPosition;
    		var endPosition;
    		var itemBeingDragged;
    		var allIds = new Array();
    			
    			
			function makeAjaxCall(_url) {
			  /* Send the data using post and put the results in a div */
			    $.ajax({
			      url: _url,
			      type: "get",
			      success: function(){
			           $(".pReload").click();
    				   makeTableSortable();
			      },
			      error:function(){
			          alert("There was a failure while repositioning the element");
			      }   
			    });
    		}
    			
			function moveUp(sourceId) {
    			url="' . $this->session->userdata('callableAction') . '/" + sourceId +"/1/up";
    			makeAjaxCall(url);
    		}
    		
			function moveDown(sourceId) {
    			url="' . $this->session->userdata('callableAction') . '/" + sourceId +"/1/down";
    			makeAjaxCall(url);
    		}
    					
			function moveToTop(sourceId) {
    			url="' . $this->session->userdata('callableAction') . '/" + sourceId +"/1/top";
    			makeAjaxCall(url);
    		}

    		function moveToBottom(sourceId) {
    			url="' . $this->session->userdata('callableAction') . '/" + sourceId +"/1/bottom";
    			makeAjaxCall(url);
    		}
    			
    		// Return a helper with preserved width of cells
	    	var fixHelper = function(e, ui) {
	    		ui.children().each(function() {
	    			$(this).width($(this).width());
	    		});
	    		return ui;
	    	};
	    	
    		function makeTableSortable() {
				$("#flex1 tbody").sortable(
	    		{
	    			helper: fixHelper,
	    			cursor : "move",
	    			create: function(event, ui) {
	    				allRows = $( "#flex1 tbody" ).sortable({ items: "> tr" }).children();
	    				for(var i=0; i< allRows.length; i++) {
	    					var _row = allRows[i];
	    					_id = _row.attributes["data_id"].value;
	    					allIds.push(_id);
	    				}
	    			},
	    			start : function(event, ui) {
	    				startPosition = ui.item.prevAll().length + 1;
	    				itemBeingDragged = ui.item.attr("data_id");
	    			},
	    			update : function(event, ui) {
	    				endPosition = ui.item.prevAll().length + 1;
	    				if(startPosition != endPosition) {
	    					if(startPosition > endPosition) {
    							distance = startPosition - endPosition;
    							url="' . $this->session->userdata('callableAction') . '/" + itemBeingDragged +"/" + distance + "/up";
    							makeAjaxCall(url);
    						} else {
    							distance = endPosition - startPosition;
    							url="' . $this->session->userdata('callableAction') . '/" + itemBeingDragged +"/" + distance + "/down";
    							makeAjaxCall(url);
    						}
	    				}
	    			}
	    		})
    		}
    					
	    	window.onload = function() {
	    		makeTableSortable();
	    	};';
    	header("Content-type: text/javascript");
		echo $js;
    }

The code below states my integration with flexgrid. Of course the same can be integrated with datatables to but i found it much troublesum for managing the orders and all.. Datatables, automatically re-sets the orders of its default set value. Plus, crud paging in datatables failed for me at some places. The rows used to be shattered / un-ordered. A lot, i dont blame the code but i am sure i might be missing some important aspect over it and hence i failed. It will be a great opportunity for me to thank some other gentleman / lady whoever comes up with a tried / tested solution on datatables.

I will share the changes i needed to integrate in the flexgrid also so you guys can refere to and make the same. I will recomend it getting done to get accurate results.

 

Following is a sample action call from my controller for the same.

	function categories()
	{
		$crud = new grocery_CRUD();
		$crud->set_table('categories');
		//$crud->set_theme('datatables');
		$crud->set_subject('Category');
		$crud->add_action('News', base_url() . 'assets/images/news-icon.png', base_url() . 'admin/news/');
		$crud->set_relation('language', 'languages', 'language');
		$crud->order_by('language asc,priority asc' ,'');
		$crud->columns('language', 'category', 'start_at', 'end_at', 'move_up_down');
		//Along with drag / drop, i did provide option for user to move the rows up / down / top / bottom
		$crud->callback_column('move_up_down', array($this, 'populate_up_down'));
		//I recomend setting the callableAction in session for the action so that the javascript that is being generated (above code) can pickup the dynamic url to be called
		$this->session->set_userdata('callableAction', base_url(). 'admin/updateGroupPosition/categories/language');
                //This is the call that makes the table rows dragable / dropable
		$crud->set_js("admin/dragdrop_js");  //Here 
		$output = $crud->render();
		$this->load->view('crud',$output);
	}

    public function populate_up_down($value, $row) {
        $str = "<a href='javascript:moveToTop(" . $row->id . ")'><img src='" . base_url() . "assets/images/navigate-top-icon.png'></a>";
        $str .= "<a href='javascript:moveUp(" . $row->id . ")'><img src='" . base_url() . "assets/images/navigate-up-icon.png'></a>";
        $str .= "<a href='javascript:moveDown(" . $row->id . ")'><img src='" . base_url() . "assets/images/navigate-down-icon.png'></a>";
        $str .= "<a href='javascript:moveToBottom(" . $row->id . ")'><img src='" . base_url() . "assets/images/navigate-bottom-icon.png'></a>";
        return $str;
    }

Following are the tweeks required in the grocerycrud code for making the flexgrid tables dragable / dropable.

1 - In assets/grocery_crud/themes/flexigrid/js/flexigrid.js

$('#filtering_form').submit(function(){		
.....		
		$(this).ajaxSubmit({
....				
				this_form.ajaxSubmit({
					 success:    function(data){
						$('#ajax_list').html(data);
						call_fancybox();
                                                //Added this 2 lines to make sure that after every reload of the table / data, it makes it re-sortable
                                                //Trust me, you will not like to leave this un-edited, i have faced the consequences of loosing the drag drop
						if (makeTableSortable && typeof(makeTableSortable) == "function")
							makeTableSortable();
					 }
				}); 
			 }
		});

2 - assets/grocery_crud/themes/flexigrid/views/list.php

..... ///The whole intention here for me was to add a row id that would have helped us identify which row we need to move up / down
<?php foreach($list as $num_row => $row){ ?>        
		<tr  <?php if($num_row % 2 == 1){?>class="erow"<?php }?> data_id="<?php echo $row->id ?>">

3 - assets/grocery_crud/themes/flexigrid/views/list_template.php

    .... All this code at the begining of the file
    $this->set_css($this->default_css_path.'/ui/simple/'.grocery_CRUD::JQUERY_UI_CSS);
    $this->set_js($this->default_javascript_path.'/'.grocery_CRUD::JQUERY);
    $this->set_js($this->default_javascript_path.'/jquery_plugins/ui/'.grocery_CRUD::JQUERY_UI_JS);


I know many of you will argue that i could have had argued to put JQUERY_UI_CSS / JS all in the action class itself but there is a catch here in grocery crud. What the user adds up as inclusive files in the action, those files sit on the top of other files nad JQUERY UI will throw error as JQUERY is not yet loaded before it is. And anyways, adding this will provide no harm to the look and feel / features / functionalities. I have tested it and it works like a charm. Yes, only scenario where it is not needed and it still gets loaded, that is the place where it will have the impact of bandwidth cost. But rest apart, all is good to go with.

 

 

Well.. thats all for today - I hope i have not missed out anything over it. I really love grocery crud and i really love to be of any help possibile to integrate new / exciting feature in the code.

 

Happy drag and drop with grocery crud now.


davidoster

davidoster
  • profile picture
  • Member

Posted 20 May 2013 - 10:47 AM



Well it had been a long time even i was thinking of having the same feature and so i finally decided to build the same over it. I will share you how the stuff works ... i know it aint that clean but it did work like a charm for me. Drag and drop as well as a complete prioirty / row -order management you can say is developed here. I wish all the best to the grocery crud team if they can incorporate this feature in their existing library, it will be an add-on stuff and a great utility for the app developers on grocery crud.

...

Hello [member=amit shah]. Thank you for this contribution of yours and thank you for your support to Grocery CRUD.

I will check it later today and I will let you know of any findings I might come across.

I will let [member=web-johnny] know about this.

The good thing about your solution is that is built as a library. This means that it is built on top of GC as an add-on and this is the recommended practise.

Can you please confirm if this library works also with the new development version, https://github.com/scoumbourdis/grocery-crud

 

UPDATE: Is there a way to make it without changing the theme's files?


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 20 May 2013 - 11:32 AM

I surely will in a day or 2. I just want to get rid of my backlog of the project work. I hope you too understand the situation. But be assured, i will workout a test / pilot project on the latest version of grocery-crud. I did the same on the current production release. I will share you the credentials / details so you too can have a handy look at the same.


Kobus

Kobus
  • profile picture
  • Member

Posted 21 May 2013 - 07:03 AM

Hi Amit,

 

Looking forward to this! Will download once it does not need to modify GC base files - Perhaps webjohnny can put it in the base code? Not sure.

 

Kobus


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 21 May 2013 - 11:36 AM

It surely dose not need to alter the gc base files but there were a certain areas in the themes that can be modified. Especially the flexgrid theme as described earlier.

 

 

Attaching new code files.

Was running throught the application merged on the same and found a minor bug when moving a record to the top. Hence fixed the code. Also modified a function (getMaxCount) ... where last param was sourceId .. i changed it to groupvalue. It created issue when i was using the library independantly and just wanted to get max value for a group when i didnt have a row - (mostly like before insert)

 

Also the last post, missed out on the common_model.php - This is a generic model library with a certain commonly found functions across tables. It is a handy little utility that one can consume in any CI based project.

And of course the icons that i missed out.

 

Now - it makes it easy for any1 to download all the files, place them properly and start using it.


Kobus

Kobus
  • profile picture
  • Member

Posted 21 May 2013 - 15:39 PM

Hi Amit,

 

I am struggling to implement this. I must be missing something in your post above. I will try again later, and hopefully I get it right then.

 

Thanks for your effort.

 

Regards,

 

Kobus


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 21 May 2013 - 16:15 PM

What are the exact issues you are comming across. If you can let me know i may just be in the position to help. In a day or 2 i am planning to apply the same to the latest code from the github and create a demo implementation project. I, then will share the same to you guys so you can chop off the right ingredients from that and cook the curry correctly :)


davidoster

davidoster
  • profile picture
  • Member

Posted 21 May 2013 - 18:00 PM

What are the exact issues you are comming across. If you can let me know i may just be in the position to help. In a day or 2 i am planning to apply the same to the latest code from the github and create a demo implementation project. I, then will share the same to you guys so you can chop off the right ingredients from that and cook the curry correctly :)

Good comparison with a curry dish! Vindaloo is just fine for me.


Kobus

Kobus
  • profile picture
  • Member

Posted 23 May 2013 - 08:20 AM

Hi Amit,

 

I have decided to custom develop this for each module I want to make. Mine is now done in about 8 lines of code in the controller, and 2 small queries and one view file.

 

Yours is obviously much more flexible, but mine is working well for my specific implementation. I will eventually upgrade to yours if time permits. You can see screenshots of my implementation and what I needed done here: /topic/1703-modifying-the-add-url/

 

Thanks for your library - I am sure lots will find it very useful. I just needed something quicker and I only needed it for two modules, so no use to implement a full library for this :-)

 

Regards,

 

Kobus


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 23 May 2013 - 14:48 PM

Well as promised,

 

I have applied the library to the latest build of the grocery crud (pulled from svn).

 

You guys can find the implemented example code for the following at https://github.com/blakdronzer/gc_with_drag_drop

 

Well i should really thanks the team up here who have provided me an opportunity to improve my code. Earlier, i ran through with a certain set of assumption that everyone (like i used to do) would be using up id as primary key. I know i was wrong to assume that and had improvised the code over while implementing it with the example on the grocerycruds pre-provided example (products manager)

So i am also attaching the fresh code here along with this post.

 

Hope guys can take the advantage of the application / library to the max.


rothkj1022

rothkj1022
  • profile picture
  • Member

Posted 14 June 2013 - 16:15 PM

Thank you for contributing this!  This is a really great start.  I'd just suggest the following changes:

  • It's not an easy integration - I thought your instructions could be a little more clear (but thanks for taking the time to offer instructions!  So many developers don't write documentation).
  • I would think it could be integrated in such a way that you would not have to implement all that code on every one of your controllers that has sorting.  Perhaps if the GC devs want to bring this functionality into the base code (yes please!) this would be something they could resolve.
  • The priority field should not be defaulted to a particular named field (in your case "priority"), but force user to set this per usage instance, as not everyone wants to name their sort order fields "priority".
  • Drop the session variables and find another way to pass the values.  Perhaps pass the callableAction to the dragdrop_js function client-side instead.

Hopefully you don't mind a little constructive criticism.  Thanks again for contributing!

 

Kevin


milan

milan
  • profile picture
  • Member

Posted 06 July 2013 - 21:15 PM

Hi,
I tried this library with implemented example code for the following at https://github.com/b..._with_drag_drop. Drag and drop function works fine, but if I click on the icons (images up, down, top or down) - no action. I expected, that corresponding line will move to new position accordance to icon on which I clicked.
In console is for example "ReferenceError: S10_4962 is not defined"  (S10_4962 is a product code of item).

milan


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 08 July 2013 - 19:33 PM

hi rothkj1022 - First of all thank you for your feedback, i know it aint easy and straight forward to get through building extensions. That was my first of extension. That too, i have had 1st built it inside the GC but then decided to extract it. Though it might be a bit clumsy but it surely was the best i could have had done it. 

I appreciate your critics as it teaches me to improve .. so no hard feelings. I may have missed out on priority, i will check and update on the code as and where required and make sure that i dont force people to use priority only as the field. I believe either i have missed out or you have missed out.. but i have clearly mentioned in the description of the class PriorityManager .. that there is a function to set the priority field - $manager->setPriorityField ... by default it will hold priority as the field name if none is defined. Well as for your suggestion to drop the session and use a callback is a good option, i surely will work around on the same. i know i too was not keen on using session but eventually when i wrote it 1st, i didn't have much time to have a look at the same as i had to deliver the project. Over that, i was not much in a position to re-build stuff from scratch and write a new extension .. time constraints.

But thank you for your valuable suggestion, i really appreciate them. 

 

Milan,

Sorry for that, there must be some minor glitch where i may have gone wrong for sure, but can you share me the exact error / screen grab of the error so i can give you a proper solution for the same.


milan

milan
  • profile picture
  • Member

Posted 08 July 2013 - 21:50 PM

Hi,

thanks for response. Here is screen with error console when I'm clicking on image navigate-up-icon.png on the line with product code S10_4698. I don't know, if it will help you for solution.

 

milan

 

[attachment=608:gc_drag_drop.png]


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 09 July 2013 - 20:39 PM

Okie.. i got the issue.. please replace this code in the controller and that should solve your issue,

    public function populate_up_down($value, $row) {
		$primary_key = $this->session->userdata('primary_key');
    	$str = "<a href='javascript:moveToTop(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-top-icon.png'></a>";
    	$str .= "<a href='javascript:moveUp(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-up-icon.png'></a>";
    	$str .= "<a href='javascript:moveDown(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-down-icon.png'></a>";
    	$str .= "<a href='javascript:moveToBottom(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-bottom-icon.png'></a>";
    	return $str;
    }	

My mistake, i assumed the id to be numeric (thats what i usually use) ..but then made it to pass string value and the issue is resolved now.

Have also committed the changes in github .


milan

milan
  • profile picture
  • Member

Posted 09 July 2013 - 21:05 PM

Hi,

works perfectly, thanks very much, nice job

 

 

milan


davidoster

davidoster
  • profile picture
  • Member

Posted 10 July 2013 - 07:05 AM

Okie.. i got the issue.. please replace this code in the controller and that should solve your issue,

    public function populate_up_down($value, $row) {
		$primary_key = $this->session->userdata('primary_key');
    	$str = "<a href='javascript:moveToTop(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-top-icon.png'></a>";
    	$str .= "<a href='javascript:moveUp(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-up-icon.png'></a>";
    	$str .= "<a href='javascript:moveDown(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-down-icon.png'></a>";
    	$str .= "<a href='javascript:moveToBottom(\"" . $row->$primary_key . "\")'><img src='" . base_url() . "assets/images/navigate-bottom-icon.png'></a>";
    	return $str;
    }	

My mistake, i assumed the id to be numeric (thats what i usually use) ..but then made it to pass string value and the issue is resolved now.

Have also committed the changes in github .

 

[member=Amit Shah] why do you need this line $primary_key = $this->session->userdata('primary_key');

if you use $row->primary_key only?


Amit Shah

Amit Shah
  • profile picture
  • Member

Posted 10 July 2013 - 13:29 PM

[member=Amit Shah] why do you need this line $primary_key = $this->session->userdata('primary_key');

if you use $row->primary_key only?

 

Well.. that is a good idea .. and surely a good option to use but unfortunately the $row comes up with the representation of the row in table but nothing like primary_key as object element to the same. And this is required in here because it is being written in the controller and not the view .. this is a script that is required just for drag and drop and i couldn't have generated dynamic code calls using javascript. Hence i was left with no choice but to use it like this.

Earlier i used to manage it using ID .. a standard pattern i use for building applications but eventually noticed that ID is not the primary key everyone uses and hence i was forced to use this session mechanism.

If you can find something interesting, please do share / guide me, i will be happy to learn.