Many-to-many relationship between resources

OK, I finally got this working. Here’s what I changed:

OnDocFormRender, I create the value I need and then do this:
$resource->setTVValue(‘ spkr_conferences’,$string);
rather than saving the TV value to the DB.

And then OnDocFormSave, I do this to delete the temporary value so that it doesn’t get saved in the database:
$resource->setTVValue(‘spkr_conferences’,’');

It’s working great; no TV values are getting saved at any point into the database.

Just checked, and if you specifically select a value that is the default and save the resource, then no value is saved in the database for the TV.

But, I can’t use default values for this purpose, because the “default value” would be different for each resource; it’s a dynamic value that depends on which resource is being edited combined with the status of various other resources at the exact time of editing.

I didn’t at all take your post to be negative! And I hope you didn’t take my response to be negative. Just inquiring into this, trying to find out how things work. Your responses always make me try to learn more.

This line does save the TV value to the DB! (setTVValue() doesn’t require you to save the resource.)

This line deletes the TV value from the DB. To be more precise, you set the TV value to the default value (an empty string) and that has the effect that the TV (that was saved to the DB before the event OnDocFormSave fires) gets deleted from the DB.

Your new code does exactly the same as your old code. You just used a different method to save the TV.

I think snowcreative said that the default value varies, so it’s not an empty string. That said, it should be possible to get the default value with $tv->get(‘default_text’) then save that to remove the value from the TV value table.

No, it doesn’t. I checked the DB right after the resource gets loaded for editing (and before the second script gets triggered to delete the value). The value is correctly loaded in the TV editing form, and nothing gets stored in the DB. If you load the page for editing (loading the TV with values), then just leave the page without saving, there is no TV value in the database. That was the problem with the way I was doing this before.

There’s no default value assigned to this TV.

I tested this and I can’t confirm it. When I check the database, I can see the TV.

I also wrote this plugin code to test it. It queries the database before and after the call to setTVValue() to check if there is an entry for the TV and writes the result to the error log.

switch ($modx->event->name) {
    case 'OnDocFormRender':
        $modx->log(modX::LOG_LEVEL_ERROR, '=== onDocFormRender ===');
        
		//SQL query to test if there is a value for the tv 'spkr_conferences' and the current resource-id in the database
        $sql = "SELECT COUNT(*) FROM `modx_site_tmplvar_contentvalues` tvr JOIN `modx_site_tmplvars` tv ON tvr.tmplvarid = tv.id WHERE tv.name = 'spkr_conferences' AND tvr.contentid = " . $id;
        $stmt = $modx->prepare($sql);
        $stmt->execute();
        $modx->log(modX::LOG_LEVEL_ERROR, 'Before call to setTVValue(): ' . $stmt->fetch()[0] . ' rows in the DB');
        
        $resource->setTVValue('spkr_conferences','whatever');
        
        $stmt->execute();
        $modx->log(modX::LOG_LEVEL_ERROR, 'After call to setTVValue(): ' . $stmt->fetch()[0] . ' rows in the DB');
        
        break;
}
return;

OK, this is really weird. I tested multiple times yesterday, and never saw the TV in the DB. Today, the TV is appearing.

Why is this so hard? How can we insert values into the TV form after it loads, without saving anything to the DB?

So, I’ve added this:

	case 'OnResourceTVFormRender':
	  $modx->resource->setTVValue('spkr_conferences','');

“$resource” isn’t available during this event, so using “$modx->resource” works. This always fires after OnDocFormRender, so at least this addition guarantees that no saved value for the TV stays in the database.

Here is a different approach using a dynamic default value that might work. The idea is, that when the page is saved, the default value is always equal to the current value and therefore nothing gets saved to the database.


For your TV add a @-CHUNK binding to the field “Default Value”

@CHUNK set_default_value_tv28

Create a new chunk “set_default_value_tv28” with this content: (It’s just a snippet call.)

[[set_default_value_tv28]]

Create a new snippet “set_default_value_tv28” with this content:

<?php
if (isset($_POST["tv28"])){
    //Page is saved -> return whatever the $_POST value of this tv is
    return $_POST["tv28"];
} else {
    //for a real implementation, read the value from a custom db table (or from somewhere else)
    $modx->log(modX::LOG_LEVEL_ERROR,'read tv value for resource ' . $modx->resource->get('id'));
    $tv_value = "whatever";
    return $tv_value;
}

Create a new plugin (to save the TV value) that runs on the event “OnDocFormSave”

switch ($modx->event->name) {
    case 'OnDocFormSave':
        
        //Get the value of the TV from $resource (or alternatively from $_POST)
        $tv = 28; //TV ID
        $tv_value = $resource->get('tv' . $tv);
        
        //for a real implementation write the value to a custom db table (or somewhere else)
        $modx->log(modX::LOG_LEVEL_ERROR,'saving tv value "' . $tv_value . '" from resource ' . $id);

        break;
}

In this example the ID of the TV is 28. Change the code to make if work for your TV.

Well, I was wondering whether you could use a snippet for the default values of TVs. Looks like you can.

But, I’m not sure if this works all the way through in the way I need it to work. Here’s my work flow:

  1. The resource loads in the manager, with the TV set to a dynamic value.

  2. The user has the option to change that value (picking from the multi-select list).

  3. On saving, the new TV value is used to edit some data elsewhere in the DB.

Since the TV value may be different than the original value when the user saves, will your method still work?

I haven’t tested this approach intensely, but in principle it should work for your work flow.

The code I posted could be improved of course. E.g. in the plugin check the template first ($resource->get('template')) to make sure the code only runs for the correct template. Or account for the case in the snippet when the resource is newly created ($modx->resource->get('id') == 0).


There is a possibility that this approach could fail, namely when somehow by accident the value gets saved to modx_site_tmplvar_contentvalues anyway. As a precaution you could append the plugin to check and delete a rogue db entry:

switch ($modx->event->name) {
    case 'OnDocFormRender':
		$tv_id = 28;
        $tv = $modx->getObject('modTemplateVarResource',array('tmplvarid' => $tv_id, 'contentid' => $id));
        if($tv){
            $tv->remove();
        }
        break;
}

Thank you, Bob! For you answer and especially for your book!

Found the idea in “advanced snippets” section, when you talk about creating objects with many-to-many relationship. I’m not yet as comfortable with raw database manipulation, however, mastering it is something from a long to-do list. Your links there might be helpful, though. Iterating through exploded TV lists is kind of ugly, i know, but there are not too many entries to affect perfomance, so i did it for now as a fast patch. Will come back to it later.

Taking an opportunity to ask…

There’s another issue i’m trying resolve right now. I created a custom TV for multiple file upload, with a custom responsive template (based on jQueryUI Touch), both for backend an frontend usage (this is for a musical college, you know, so forcing madam-content-manager to fight MIGX from backend just to drop some docs/pics on page seemed bad idea). Everything’s fine, yet one thing makes me wonder. I registered a namespace, yes, created a pathing plugin, created input and output controllers, wrote a UI template for dropping-naming-sorting-uploading files, producing JSON to store it in hard-coded invisible field, tied to controllers with tv{$tv->id}, tv{$tv->value}. Success! But now i want another custom TV UI element and just don’t get how to separate them from each other. Links to controllers are hard-coded in pathing plugin, tied to same events. All i have unique here of each TV is then tv{$tv->id} value sticked into template. Is that so? Should i struggle to code all custom UI elements in one template file, selecting them to render as needed, by id, or there’s something i missed? What is the exact mechanics of process? Found some “how to” guides for simplest cases (including the one in official MODX docs), but lack explaination. Tried to reverse-engineer MIGX, however, it’s still far beyond my dev level.

And another one… There’s a line in official MODX Docs:
”The pathing plugin will not be required in MODX 2.3; the Namespace will handle all the pathing. This is why we told you earlier to make the Namespace.”

It’s 2.8.3 here, but still doesn’t seem the case. Or maybe i’m missing something here again?

Quite new in all this stuff, so i’m sorry if asking something obvious.

This topic was automatically closed 2 days after discussion ended and a solution was marked. New replies are no longer allowed. You can open a new topic by clicking the link icon below the original post or solution and selecting “+ New Topic”.

Thanks for the kind words. I almost never use TVs for anything, so I’m probably not the person to ask about your project. I don’t really understand what you’re doing, but TV IDs are unique and won’t change if that helps. It it’s a text TV, you can also store multiple values in one TV by putting them in a comma-separated string or JSON.

I think the namespace advice refers to MODX 3, which has not been released yet, not to MOD 2.3.