One-to-many relationship on CRUD object error


#1

Hi there,

I am very very new to this project, so please bear with me. I want to use agiletool within Wordpress. I used this very helpful example plugin to get started: https://github.com/ibelar/atk-wordpress-sample.

In this project some admin urls route to a specific file that in the end extends \atk4\ui\View.
I’m trying to implement a one-to-many form. So a form within a form so to speak. I have the model Event that can have many Items. Item just has a single title, thats all, nothing special. The mappings are setup in the models, see the code below.

The list of items gets loaded, so that is fine. (I do have problems getting the model id however). However, when creating a new item and returning to the edit mode of the event, it crashes, see last image. Hopefully anyone can help me with this. Thanks in advance!

<?php
/**
 * Model Event.
 * Create a model base on the event table in Db.
 */

namespace atksample\models;

use \atk4\data\Model;

class Event extends Model
{

    public function init()
    {
        parent::init();

        $this->table = 'wp_atksample_event';

        $this->addField('name', ['type' => 'string', 'caption' => 'Name', 'required' => true]);

        $this->addField('description', ['type' => 'string', 'caption' => 'Description', 'required' => true]);
        $this->addField('category',
            ['enum' => ['week' => 'Weekly', 'month' => 'Monthly', 'year' => 'Yearly'], 'required' => true]);
        $this->addField('date', ['type' => 'date', 'required' => true]);

        $this->hasOne('\\atksample\\models\\Place', [
            'model' => '\\atksample\\models\\Place',
            'our_field' => 'place_id',
            'their_field' => 'id',
            'caption' => 'Place',
        ]);

        $this->hasMany('\\atksample\\models\\Item', [
            'model' => '\\atksample\\models\\Item',
            'our_field' => 'id',
            'their_field' => 'event_id',
            'label' => 'asd',
        ]);


        $a = 1;
    }
}
<?php
/**
 * Model Event.
 * Create a model base on the event table in Db.
 */

namespace atksample\models;

use \atk4\data\Model;

class Item extends Model
{

	public function init()
	{
		parent::init();

        $this->table = 'wp_atksample_item';
//        $this->title = 'Item';

		$this->addField('name', ['type'=> 'string', 'caption'=>'Name', 'required' => true]);
        $this->hasOne('\\atksample\\models\\Event', [
            'model' => '\\atksample\\models\\Event',
            'our_field' => 'event_id',
            'their_field' => 'id',
            'caption' => 'Event',
        ]);

    }
}
<?php
/**
 * Panel to display an Event CRUD.
 */

namespace atksample\panels;

use atk4\ui\CRUD;
use atk4\ui\Form;
use atk4\ui\View;
use atksample\models;
use atkwp\components\PanelComponent;

class EventPanel extends PanelComponent
{
    public function init()
    {
        parent::init();
//        session_start();

        $msg = $this->add([
            'Message',
            'Agile Toolkit for Wordpress!',
        ]);


        $model = new models\Event($this->getDbConnection(),
            ['table' => $this->getPluginInstance()->getDbTableName('event')]);
        $view = $this->add(new View(['ui' => 'segment']));

        /**
         * @var CRUD $crud
         */
        $crud = $view->add(['CRUD', 'notify' => new \atk4\ui\jsNotify(['content' => 'Data saved'], $this)]);
        $crud->setModel($model);
        $crud->itemCreate->set('Event');
//
//        $modelId = null;
//        foreach ($_GET as $key => $val) {
//            if (strpos($key,'__crud') !== false) {
//                $modelId = $val;
//                $_SESSION['eventModelId'] = $val;
//            }
//        }
//        if (!$modelId) {
//            $modelId = isset($_SESSION['eventModelId']) ? $_SESSION['eventModelId'] : null;
//        }



        $modelId = 3;
        if ($modelId) {
            /**
             * @var Form $formEdit
             */
            $formEdit = $crud->formEdit;
            $child = $formEdit->add(new CRUD());
//            $child->entity_name = 'Item';
            $child->setModel(
                $crud->model->load($modelId)->
                ref("\\atksample\\models\\Item"));


        }



    }
}


#2

i think this could be an integration issue with wordpress. does the same code work OK in a stand-alone application?

Also - it would be nice if you could clean / shorten the example code.


#3

Hi Romans,

Thanks for your reply. Next time I will clean up my code, sorry for that. I will check if the code works well in a stand-alone application, but I guess it would, right?


#4

@belair_alain mentioned in our chat that this does not seem like a wp issue, so it’s quite hard to determine. So as long as I can try your example myself, I should be able to help.


#5

Hi Romans,

I was very busy, sorry to keep you waiting. It does not seem to be a WP issue indeed. I polished my example and pulled it out of WP, see it below. To recap my goal: I want to add the child CRUD to the edit form of it’s parent CRUD. There were 2 issues is atk4/ui 1.3.1:

  1. how can I attach the related children (specifically, how do I know if the parent CRUD is in edit mode and how to know the parent’s id)?
  2. When passing point 1 by hardcoding the parent’s id you get an error after the child form is being saved and the view returns to the parent’s edit form (see original post). Thanks again for your help and also infinite amount of thanks for building and maintaining ATK.
    2b) I updated atk4/ui to version 1.4 and when trying to edit a parent item the following error occurs:
class Event extends Model
{

    public function init()
    {
        parent::init();
        $this->title = 'Event';
        $this->table = 'wp_atksample_event';
        $this->addField('name', ['type' => 'string', 'caption' => 'Name', 'required' => true]);
        $this->hasMany('Item', [
            'model' => 'Item',
            'our_field' => 'id',
            'their_field' => 'event_id',
        ]);
    }
}

class Item extends Model
{

    public function init()
    {
        parent::init();

        $this->table = 'wp_atksample_item';
        $this->title = 'Item';

        $this->addField('name', ['type' => 'string', 'caption' => 'Name', 'required' => true]);

        $this->hasOne('event_id', [
            new Event(),
        ])->addTitle();

    }
}

$app = new \atk4\ui\App('Test');
$app->initLayout('Centered');
$view = $app->add(new View(['ui' => 'segment']));
$crud = $view->add(['CRUD']);
$model= new Event($db);
$crud->setModel($model);

$modelId = 3; // hardcoded as I don't know how to get it
$formEdit = $crud->formEdit;
$child = $formEdit->add(['CRUD']);
$child->setModel(
    $crud->model->load($modelId)->
    ref("Item"));

$app->run();

#6

Hi Roel. I have found this practice quite bad for some reason:

  • editing form opens in a dialog, quite small to do the CRUD
  • resulting in double-modals, which are not very well implemented in semantic-ui.

I was planning this component: https://github.com/atk4/ui/issues/272 which kinda do what you need in a very simple to use way, but it’s not ready yet. I do have a need for it in my current project, so will probably work on it soon.

But to finally give you the answer, here are 2 paths you can take:

Add into modal

CRUD creates virtual pages during initialization and adds forms inside those pages. After adding CRUD, you should be able to add more stuff there:

$model = new Event($app->db);
$crud = $app->add('CRUD');

if($id = $app->stickyGet($crud->name)) {
  $i_cr = $crud->pageEdit->add('CRUD');
  $i_cr->setModel($model->load($id)->ref('Item'));
}

$crud->setModel($model);

Logically it would make sense if “setModel” appears above “if()” you can try it like that too.

Same as above but use a custom layout

You may want to display CRUD on the side of the form. Although I have built some flexibility in CRUD properties letting you inject things, I haven’t tried it with any use-case yet. What you are proposing is quite reasonable and would be useful if implemented correctly.

So we can review CRUD, (see it’s line 45-50) where it initialize things. Perhaps using factory here would offer more flexibility allowing you to supply both the “page” and “form” like this:


$vp = new VirtualPage();
$cc = $vp->add('Colums');
$f = $cc->addColumn()->add('Form');

$cr = $app->add([ 
    'CRUD',
    'pageEdit'=>$vp,
    'formEdit'=>$f,
]);

// use $i_cr = $cc->addColumn()->add('CRUD')

Use separate page.

You can disable the “edit” action on the crud, and instead make name clickable or re-add a button which will link to a separate page (which can be virtual page); This is a plan for the “https://github.com/atk4/ui/issues/272”, but you can do it directly in a simpler implementation:

// disable editing
$crud = $app->add(['CRUD', 'ops'=>['u'=>false]]);

// add link
$crud->addDecorator('name', ['Link', 'edit', ['event_id'=>'id']]);

// or add icon (may need fine-tuning), jsRedirect require 1.4.0
$crud->addAction(
    ['icon'=>'edit'],
   $app->jsRedirect(['edit', 'event_id'=>$crud->jsRow()->data('id')])
);

Then on a separate ‘edit’ page:

$m = new Event($app->db);
$m->load($app->stickyGet('event_id');
$crud = $app->add('CRUD');
$crud->setModel($m->ref('Items'));

Not sure what will work, so tell me if my suggestions result in a solution.

I think we can take 2 suggestions out of this situation:

  • make some example at embedding stuff on the editing modal.
  • need a simpler CRUD for inline editing of elements.

#7

Hi Romans,

Thanks for your suggestions. That component seems very promising. It would be awesome to be able to chain relations indefinitely. In that manner users can deal with a lot of complex relations without even knowing it and without having to reload new pages. Therefore, instead of inline editing (which is a cool feature on its own), I would suggest having the ability to set the modal window’s size. That way a complete CRUD form can be shown easily. As a side idea, borrowed from (https://github.com/sonata-project/SonataAdminBundle), stand alone views (child CRUDs) could potentially be loaded from a parent’s CRUD with a parentId as a filter. That way a child view has to be defined once.

Add into modal + custom layout suggestion

I’ve tested your suggestions. I was very charmed by the first part, because it doesn’t require a separate page load. However, when trying to edit a child row (item) from the parent’s edit page (Event), an additional Event edit page is being opened on top of the already existing one.

Use separate page

For this suggestion the link location does not seem to work. There are a few issues:

// add link
$crud->addDecorator('name', ['Link', 'edit', ['event_id'=>'id']]);

Here, the event_id is never added as parameter (the url simply becomes /edit).

// or add icon (may need fine-tuning), jsRedirect require 1.4.0
$crud->addAction(
    ['icon'=>'edit'],
   $app->jsRedirect(['edit', 'event_id'=>$crud->jsRow()->data('id')])
);

Here, the data id is not taken correctly, the parameter url becomes some parsed object. I’ve tried getting the id myself by using a custom jsExpression, but all attempts resulted in some error. Example:

$crud->addAction(
    ['icon'=>'edit'],
    new \atk4\ui\jsExpression('document.location = event-items.php?event_id=[]', [$crud->jsRow()->data('id')])
);

JS error: SyntaxError: missing : in conditional expression

Also, when using $app->jsRedirect(), the url ALWAYS gets suffixed with .php, that’s either an unpleasant feature (for my use cases) or a bug.

Hope to hear from you soon!


#8

Yeah, those wouldn’t be out-of-the-box solutions, they probably require some tinkering to make them work. Still some good ideas for demos :slight_smile:

I’ll work on the multi-crud component, still not sure what would be a good name. How about MultiCRUD ?

And yes - it will automatically traverse relations for as deep as necessary and apply filters for listing, editing and deleting automatically. Beautify of ATK Data traversal :dancing_men:


#9

So nice that it will traverse indefinitely! MultiCrud doesn’t cover the functionality for me since it doesn’t entail the references. I’d like CrudRelations(hips) more. Or CrudReferences, or MultiCrudManager. I can’t wait till it’s finished and I can’t say enough how happy I am with this project!


#10

Wait - relationships is the sole purpose of it. I’ll give you example.

You see list of clients that are associated with your user (user_id = you) with the following columns:

  • client name
  • client email
  • client company
  • client country

From the above company and country is clickable. Also you have additional column for action icons (or can contain text too), also clickable:

  • Invoices
  • Payments

If you click on invoices, then the breadcrumb on top would say “clients > client name > Invoices” and the CRUD here would be based on $clients->load($client_id)->ref(‘Invoice’); Columns:

  • Reference
  • Total Amount
  • Paid amount

Action icons:

  • Invoice Lines
  • Allocated payments

If you click on Allocated payments you go even deeper, $clients->load($client_id)->ref(‘Invoice’)->load($invoice_id)->ref(‘Allocation’);

This one allows you to see a special model entity that links payments and invoices together. Since it references both invoice and payments you can click on payment and would jump to $clients->load($client_id)->ref(‘Payment’)->load($payment_id).

Well, i haven’t worked out all the details yet, but would you think this covers the functionality you need?


#11

I see I didn’t make myself clear. What you describe is exactly what I thought we were talking about. I meant to say that the NAME MultiCrud doesn’t cover the fact that it is about relationships. That’s why I suggested different names.


#12

OH, :smiley:

how about CRUDManager or DataManager?

CRUDception could be another funny name :slight_smile:


#13

Or InCRUDible :slight_smile:
U don’t really like the name CRUDManager since it doesn’t show it is really about the relationships between CRUDs. DataManager seems too unspecific. CRUDReferenceManager gets too long. CRUDRefManager? Or CRUDChainer?


#14

crudception.pdf (358.3 KB)

Here i put together some documentation. What do you think about “CRUDception?”


#15

Nice work, can’t wait to see it in action!
Feedback on documentation:
Introduction is very clear (part before Reference UI)

Reference UI:

You start of by explaining the tweaking possibilities and extra pages before it is even clear to me what it is about. I would do this at the end and use more words to explain it well.

Selecting client shows a detailed view: nice visual and very clear, well done.
What I miss however is the CRUD abilities? The client and its payments can be edited or deleted, right? It is not showing this however in the screenshots.

Installation and Use

Of course I understand that the array is used to setup all relations that can be shown within the crudception (and to go even deeper). However, I need more words and examples to understand it completely I think. (what is Allocation, for example and how does it relate to the visual example. Perhaps a side to side view between code and visual could make it clear in one image with arrows from code to visual?

How does it work

I still don’t understand where the Edit and Delete actions are within this CRUDception.

Question: crudception has it’s own namespace, is that a temporal thing? Doesn’t it belong to UI?


#16

Thanks for feedback. I decided to call it “MasterCRUD”. Will set up repository and share the link here when it’s ready.

To answer your question - it will be in a namespace atk4\mastercrud. It’s fine to have it in a separate namespace.


#17

Hey Roel, looks like first version of MasterCRUD is ready to use:

It’s still quite raw, so please try it out and report to me if anything is missing.

You will also need to switch “ui” to branch “feature/various-fixes” (described https://github.com/atk4/ui/pull/354).


#18

1 word: Awesome, 2 words: Absolutely Awesome :slight_smile:
This is exactly what I missed. I could not test it with a real case datastructure however, because the Wordpress integration is failing. I created an issue for this, see https://github.com/atk4/ui/issues/369.


#19

Hi Romans,

I have been using MasterCRUD for a while now in my project and I absolutely love it.
I have two questions about it:

  1. It is still in an unstable version and the composer.json does not refer to the correct atk4/ui branch (it still requires feature/various-fixes, right?). Will you be able to make it stable soon?
  2. Is it possible to add a custom tab to the MasterCRUD? For example, given your example in the readme:
$crud = $app->add('\atk4\mastercrud\MasterCRUD');
$crud->setModel('Client', [
  'Invoices'=>[
    'Lines'=>[],
    'Allocations'=>[
      'payment_id'=>['path'=>'Payments', 'payment_id'=>'payment_id']
    ]
  ], 
  'Payments'=>[
    'Allocations'=>[
      'invoice_id'=>['path'=>'Invoices', 'invoice_id'=>'invoice_id']
    ]
  ]
]);

Would it be possible to add a tab to the invoices MasterCRUD (next to Lines and Allocations)? My goal is to populate that tab with custom table.

Thanks in advance!
Roel


#20

Hi Roel.

You are absolutely right, I need to make a stable release of it. I’ll work on it. :slight_smile:

Romans