⚠ 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

Multi List Hack - Chaining CRUD forms together



steveoc

steveoc
  • profile picture
  • Member

Posted 07 January 2012 - 14:44 PM

Following on from this discussion :
/topic/75-multi-list-hack-chaining-crud-forms-together/

I have done some work on this now, and (much to my surprise) ... it worked pretty much first time around !!

Wonderful ! - I can now chain multiple CRUD forms together, and the display of the child forms works automatically whenever the parent is in edit or view mode.

Only took a small amount of code, and turned out to be pretty painless to do.

The API for this is as follows :[list=1]
[*]Write your controller to create a CRUD form as per normal. We will call this $crud1
[*]Before calling render() on the main CRUD, create another crud form, Lets say we call this $crud2
[*]call $crud2->chain_to($crud1, 'name_of_related_field', 'The title to go above the second form' (optional))
[*]Chain more forms together if you like, eg $crud3->chain_to($crud2, 'next_related_field','Title for the third form')
[*]When you are done, call $crud1->render() as per normal ... however the return value will include working CRUD forms for the child records as well.
[/list]
And thats all that is needed. Very simple to use.

When the main CRUD form goes into edit mode (or view mode as well if you have my patches applied to the library), then the secondary CRUD form is automatically loaded below this, in list mode. The second CRUD form is filtered automatically based on which parent record is being edited / displayed.

Internally, I am using a doubly linked list to connect the CRUDs together, so this allows for CRUDs to be chained together without limitation.

Here is the git patch to make this work. (to my already hacked v1.1.6 version of application/libraries/grocery_crud.php)


diff --git a/application/libraries/grocery_crud.php b/application/libraries/grocery_crud.php
index 566c275..a0ea4c2 100644
--- a/application/libraries/grocery_crud.php
+++ b/application/libraries/grocery_crud.php
@@ -1091,6 +1091,12 @@ class grocery_Layout extends grocery_Model_Driver

protected $css_files = array();
protected $js_files = array();
+
+ protected $parent_crud = null;
+ protected $child_crud = null;
+ protected $parent_key_field = null;
+ protected $parent_primary_key = 0;
+ protected $title = '';

protected function set_basic_Layout()
{
@@ -1104,7 +1110,11 @@ class grocery_Layout extends grocery_Model_Driver
protected function showList($ajax = false)
{
$data = $this->get_common_data();
-
+
+ if ($this->get_chain_depth() > 0) {
+ $this->where($this->parent_key_field,$this->parent_primary_key);
+ }
+
$data->order_by = $this->order_by;

$data->types = $this->get_field_types();
@@ -1112,7 +1122,6 @@ class grocery_Layout extends grocery_Model_Driver
$data->list = $this->get_list();
$data->list = $this->change_list($data->list , $data->types);
$data->list = $this->change_list_add_actions($data->list);
-
$data->total_results = $this->get_total_results();

$data->columns = $this->get_columns();
@@ -1394,11 +1403,19 @@ class grocery_Layout extends grocery_Model_Driver

public function get_css_files()
{
+ if ($this->parent_crud) {
+ // The top level parent supplies all the CSS files, so we dont have to add any more
+ return array();
+ }
return $this->css_files;
}

public function get_js_files()
{
+ if ($this->parent_crud) {
+ // The top level parent supplies all the JS files, so we dont have to add any more
+ return array();
+ }
return $this->js_files;
}

@@ -1831,7 +1848,7 @@ class grocery_Layout extends grocery_Model_Driver
return $buffer;
}

- $this->views_as_string .= $buffer;
+ $this->views_as_string .= $this->title . $buffer;
}

protected function get_views_as_string()
@@ -1841,6 +1858,37 @@ class grocery_Layout extends grocery_Model_Driver
else
return null;
}
+
+ /**
+ *
+ * Chain this CRUD to a parent CRUD .. it only displays when the parent CRUD is in edit or view record mode
+ * @param class grocery_CRUD $parent_crud
+ * @param string $field_name - name of field in this table which is a foreign key to the parent crud primary key
+ */
+ public function chain_to ($parent_crud, $field_name, $title='')
+ {
+ if (!is_object($parent_crud) || get_class ($parent_crud) != 'grocery_CRUD') {
+ die ("FATAL ERROR: Parent is not an object of class grocery_CRUD\m");
+ }
+ $this->parent_crud = $parent_crud;
+ $this->parent_key_field = $field_name;
+ $parent_crud->child_crud = $this;
+ $this->title = $title;
+ }
+
+ // Return the depth of chaining. 0 = top level, 1 = first chained crud, 2 = 2nd, etc
+ protected function get_chain_depth()
+ {
+ $retval = 0;
+ $crud = $this;
+ while (true) {
+ if (!$crud->parent_crud) {
+ return $retval;
+ }
+ $crud = $crud->parent_crud;
+ $retval++;
+ }
+ }
}


@@ -1930,17 +1978,42 @@ class grocery_States extends grocery_Layout

$segment_position = count($ci->uri->segments) + 1;
$operation = 'list';
-
$segements = $ci->uri->segments;
+ $num_hits = 0;
+ $depth = $this->get_chain_depth();
+ // echo "depth of $this->title = $depth\n";
foreach($segements as $num => $value)
{
+ // if ($depth > 0) {
+ // echo "check $num $value - hits = $num_hits\n";
+ // }
if($value != 'unknown' && in_array($value, $this->states))
{
- $segment_position = (int)$num;
- $operation = $value; //I don't have a "break" here because I want to ensure that is the LAST segment with name that is in the array.
+ // SteveOC 07-Jan-2012
+ // Break after the Nth occurance of a hit - where N = the depth of this CRUD in a chained heirachy
+ if ($num_hits >= $depth) {
+ $segment_position = (int)$num;
+ $operation = $value;
+ // J Skoumbouris has comment in original code ...
+ // I don't have a "break" here because I want to ensure that is the LAST segment with name that is in the array.
+ //
+ // Not sure why that is, maybe application specific ?
+ break;
+ }
+ $num_hits++;
}
+ $last_value = $value;
}

+ // echo "operation = $operation, posn = $segment_position\n";
+
+ if ($depth) {
+ // In ALL cases of a child crud being displayed, the previous segment examined is always the primary key value
+ // to filter on !!
+ $this->parent_primary_key = $last_value;
+ }
+
+
$function_name = $this->get_method_name();

if($function_name == 'index' && !in_array('index',$ci->uri->segments))
@@ -1949,6 +2022,7 @@ class grocery_States extends grocery_Layout
$first_parameter = !empty($segements[$segment_position+1]) || (!empty($segements[$segment_position+1]) && $segements[$segment_position+1] == 0) ? $segements[$segment_position+1] : false;
$second_parameter = !empty($segements[$segment_position+2]) || (!empty($segements[$segment_position+2]) && $segements[$segment_position+2] == 0) ? $segements[$segment_position+2] : false;

+ // echo "seg pos = $segment_position, op = $operation, param1 = $first_parameter, param2 = $second_parameter\n";
return (object)array('segment_position' => $segment_position, 'operation' => $operation, 'first_parameter' => $first_parameter, 'second_parameter' => $second_parameter);
}

@@ -2201,7 +2275,7 @@ class grocery_CRUD extends grocery_States
private $columns_checked = false;
private $add_fields_checked = false;
private $edit_fields_checked = false;
-
+
protected $default_theme = 'flexigrid';
protected $default_theme_path = 'assets/grocery_crud/themes';
protected $default_language_path= 'assets/grocery_crud/languages';
@@ -2817,6 +2891,8 @@ class grocery_CRUD extends grocery_States
*/
public function render()
{
+ $show_child = false;
+
$this->_load_language();
$this->state_code = $this->getStateCode();

@@ -2882,7 +2958,8 @@ class grocery_CRUD extends grocery_States
$state_info = $this->getStateInfo();

$this->showViewForm($state_info);
-
+
+ $show_child = true;
break;

case 3://edit
@@ -2902,7 +2979,8 @@ class grocery_CRUD extends grocery_States
$state_info = $this->getStateInfo();

$this->showEditForm($state_info);
-
+
+ $show_child = true;
break;

case 4://delete
@@ -3018,7 +3096,13 @@ class grocery_CRUD extends grocery_States

}

- return $this->get_layout();
+ $retval = $this->get_layout();
+ if ($show_child && $this->child_crud) {
+ // append the child Crud rendering to this content
+ $child_content = $this->child_crud->render();
+ $retval->output .= $child_content->output;
+ }
+ return $retval;
}

protected function get_common_data()



Attached file : my full version of grocery_crud.php, modifications applied from GC v1.1.6

--------------------------------

I am putting this patch up here for review and feedback as-is. Hopefully some of you can download it, have a play with the concept, and see if you can add any ideas.

So being a quick patch for testing, it is not perfect.

Known Issues :

1) GC is now up to v1.1.7 ... so my archive above does not include any of the fixes that have come through since v1.1.6. Please be aware of that if testing.

2) Johnny originally set things up so that in the URL, the LAST occurance of the operation is the one that grocery crud uses. Not sure why - he must have had a good reason for doing that. I had to change that behaviour to get this working, since there are now multiple operations encoded into the URL. This might break some applications ?

3) AJAX does not work on sub-lists at the moment. Not a huge problem as is, but I will get around to fixing that. Debugging AJAX on a good day can send you crazy - but doing in on a cascaded set of forms with multiple states all happening at once is another matter. Hopefully wont be too long fixing this.

4) Update / View / Insert / Delete all seem to work in sub-forms, and I havent seen a problem with it yet. Works nice. I do need to do a tonne more proper unit testing yet to _prove_ that it works in various situations, and catch any strange errors that might come up. So use this with test data only please, until it gets the stamp of approval !

--------------------------------------------

Summary :

This is a super useful addition to an already excellent library (built on top of a great framework). I really recommend that people have a play with it and see if it is useful for them, It is certainly useful for me !!

Chaining CRUDs together, and having the framework automatically link the displayed records with hardly any code is all pretty staggering really - its still early days with this toolset, but the power it gives to the programmer is just awesome.

Kudos to Johnny (and any other authors too), as this codebase is such a joy to dig into. Very very cool stuff !

web-johnny

web-johnny
  • profile picture
  • Administrator
  • 1,166 posts

Posted 07 January 2012 - 16:31 PM

Hello steveoc.

Your idea is great and I will read all the changes to see if something perhaps causes other problems to the future features that I have in mind. I will read the code and I will have a response to if it is going to be inculded in a newer version. If it's not be included in a newer version please don't be mad at me :) . As I already read the code it will help for sure many people as a patch for the users as there are really many posts that asks EXACTLY this thing (there are almost 20 posts that want this patch). As for the issues, I have your answers below:

Known Issues :

1) Don't worry for 1.1.7 it is the quickest version that I had ever realeased. It has only BUG fixes (one or two lines of code max) and adding some languages also. So propably you will not have any problem with this.

2) This is actually very important for grocery CRUD and it was the most difficult part for me to do grocery CRUD to actually be clever as for the URL. This is done for a simple thing. To use it as a library so for example let's say we have something like this:


function products_management($category_id = null)
{
$c = new grocery_CRUD();

$c->where('category_id',$category_id);
$c->set_table('test_table');
$output = $c->render();

.....
}


so if you have for example this url: [b]your_project/index.php/examples/products_management/5[/b] it means that you are in category 5. But what if the user press the edit button? The url will be automatically without session [b]your_project/index.php/examples/products_management/5/edit/S10_16782245 [/b] so as you see the first segment will always be a part of the url in ALL the redirections. You can add as many segments you like so even if the segment has the same name with the operation for example: [b]your_project/index.php/examples/products_management/edit/my_test/edit/S10_16782245[/b] this will works as grocery_CRUD understand that it is the last segment that is the real operation segment.

3) AJAX will actually make you crazy so good luck with this :-). I have an AJAX with textarea , don't blame me for this. It was the ONLY way I found in the internet to have ajax jquery form AND uploading files together and it was an actually work around from the developers of jquery form. Just to inform you and know why I take this decision.

4) I think you will not have any problem with this. In php when something works fine, it works fine for almost all the situations. If you have a Unit Testing of course will make your life easier to ensure that everything will go well.

I will have an answer for your patch also soon .

Kindest Regards
John Skoumbourdis

steveoc

steveoc
  • profile picture
  • Member

Posted 07 January 2012 - 16:55 PM

Thanks for that.

I see what you mean now about case 2) .. where you are using it as a library. That makes sense to me now.

I will have a sleep on that, and think about ways around that particular problem. Several different ways of solving it, should not be too hard.

3am now on a Sat night - time for bed for me ! Talk soon.

steveoc

steveoc
  • profile picture
  • Member

Posted 09 January 2012 - 13:00 PM

I think you can easily get around the point 2) by giving the names of functions a specific prefix to make them unique .. ie

gc_edit
gc_list
gc_add
gc_ajax_list

might work ?

Still looking at Ajax - the URLs look OK, so its not that, its something else that is stopping it from working. Probably a mix of jquery and ajax and flexigrid all at the same time ... no luck finding it yet, but then I knew it would be a pain to find.

However - no problem at all doing text area updates and file uploads inside secondary forms ... which is using Ajax, so I suspect its something happening in flexigrid at this stage. Nasty !

kidino

kidino
  • profile picture
  • Member

Posted 23 January 2012 - 02:07 AM

@steveoc, got your files from git. And the sublist is great! I found out what's wrong with the Ajax -- it's the submit() form binding, clashing form ID with the previous form.

Did you check my other reply, where you have your git URLs?

/topic/60-making-some-data-read-only/page__view__findpost__p__318

David Leiva

David Leiva
  • profile picture
  • Member

Posted 01 October 2012 - 11:05 AM

Hello, was this functionality implemented in 1.3.1 ?

I think it's very interesting...

opolette

opolette
  • profile picture
  • Member

Posted 04 January 2013 - 20:53 PM

Hello steveoc

This is quite an old thread for a still important feature.

Is your code valid with GC 1.3.3 ?

It seems to me that there are quite a lot of changes compare to the version on your GIT. And I am not completely sure of how far I should apply changes.

Thanks in advance for your reply.

opolette

opolette
  • profile picture
  • Member

Posted 04 January 2013 - 20:53 PM

Hello steveoc

This is quite an old thread for a still important feature.

Is your code valid with GC 1.3.3 ?

It seems to me that there are quite a lot of changes compare to the version on your GIT. And I am not completely sure of how far I should apply changes.

Thanks in advance for your reply.

garaa

garaa
  • profile picture
  • Member

Posted 11 April 2013 - 06:32 AM

@steveoc  awesome hack ... is this costum library compatible with  latest gcrud version. ?? 

how can i costum latest gcrud library, for chaining table feature ? because, your tricks for grud library mixed with the original code. that make me sticky for know Which additional code in gcrud library. :)

maybe you can make a independent library for this feature.... thanks before 


Kinon

Kinon
  • profile picture
  • Member

Posted 14 January 2015 - 19:08 PM

Here i leave gc v1.5.0 patched, thankyou steveoc por your work, i'll try to complete it.


Erivelton

Erivelton
  • profile picture
  • Member

Posted 28 April 2015 - 19:19 PM

Hi for all! I have 3 tables:

 

projetos
-----------
projeto_id
projeto_nome

projeto_frente
------------
frente_id
projeto_id
frente_nome

frente_remocao

frente_valor

 

projeto_situacao
------------
situacao_id
projeto_id
situacao_descricao

situacao_data

 

Is possible chain the two last tables with the first using chain_to ? I made a test, and only I see one. 

Another question: how to send the first table id when creating a new item in the linked table?

Many thanks (and sorry for my english)!


eldoge

eldoge
  • profile picture
  • Member

Posted 09 August 2016 - 15:37 PM

Hello! Any update for the latest version of GC?


sfthurber

sfthurber
  • profile picture
  • Member

Posted 02 February 2017 - 14:36 PM

there may be a simple alternative that does not require modifying the core, like so:

 

start with the code i've posted here: https://github.com/sfthurber/grocery_crud_example_refactored

 

then change the callback in the customers CRUD so that the link, instead of navigating, opens a modal containing the cust_orders_management CRUD