Many-to-many relationship between resources

It’s not really a problem. I’d just prefer if the data was only in one place. That way no-one has to guess where the correct data is stored.

I suppose you are right.

I was just hoping, there was some easy way to read the tv-value in an event ($tv_value = $_POST['tv1'])) and store it in a custom table and then to clear that value ($_POST['tv1'] = ''; so that it doesn’t get stored to the TV-table. But I guess that’s not possible.

Easy enough. Just put this at the end of the plugin script before the final brace:

if(!empty($resource->uri)) {
	$thisres = $modx->getObject('modResource',array('uri'=>$resource->uri));
	.
	.
	.
	$tv = $modx->getObject('modTemplateVar',array('name'=>'spkr_conferences'));
	$tv->setValue($thisres->id,'');
	$tv->save();
}

I just added that on my conference site, and it works. No need to store the TV value in a custom table.

Well, I think you’re misunderstanding what I’m trying to do. Of course I can remove the entry from the TV-table in the event onDocFormSave, but constantly adding and then immediately deleting rows from a database table isn’t my idea of a clean solution.

Furthermore with the solution you propose, when you open a speaker-resource, the database-entry is created again and if you leave the page without clicking the “save”-button, it won’t be deleted.

I suspected you would say that! You are certainly thorough. Yes, I was aware of that, but it doesn’t bother me. (Well, it bothers me a little. See below.) I know no one is going to notice that data or try to do something with it because I’m the only one who codes this website. If I were creating a plugin for public distribution I might have to rethink how I accomplish this. But as long as it’s clear to everyone involved that this TV is for temporary data, I don’t see an issue.

When you upload a file through a web form using a php script, the file gets stored in a temporary location on the server first, then it gets moved or copied to the final destination. Does this make the process not a “clean” solution? There is a “tmp” folder used for all sorts of purposes by operating system software. I just took a look in the tmp folder on my web server, and there is an astounding amount of stuff in there. Is this not a “clean” solution? There are many good reasons for using a temporary holding place for data.

If you’d rather use a javascript solution to get the data into the TV after loading, then fine, but that’s going to involve a lot more code. I’d rather keep things simple. The MODX architecture is in general really flexible and capable, but sometimes there are things that are hard to do within it, so I end up having to find creative, if not ideal, solutions without spending hours doing it. If MODX had an “OnAfterDocFormRender” event, then I could easily prevent any data getting saved in this TV. There are lots of other times I’ve wished for such an event as well.

To be honest, though, I don’t know why “OnDocFormRender” works here. The description of that event says it “Fires after a Resource editing form is loaded in the manager. Useful for inserting HTML into forms”. If it fires AFTER the editing form is loaded, then why does saving data into the TV on this event result in that data being displayed in the TV? Shouldn’t that data already have been fetched and loaded? I’m not inserting HTML into the forms here, I’m using PHP to edit the TV value in the database. From the event description, I would expect that if I delete the TV data OnDocFormRender, then that would delete the entry in the database after the editing form is already created and displaying the data I want. I can instead use OnDocFormPrerender to fetch the data I want in the TV and save it to the database, and that works as well. But, I would expect the form to load along with the data, and then I can delete the temporary data in the database OnDocFormRender, but that’s not what happens. If I delete the TV data in the DB OnDocFormRender, it’s gone from the TV in the loaded form as well.

I suspect that ExtJS is loading the TV data after OnDocFormRender has fired. If that’s the case, then maybe injecting a script that uses ajax to run another script that deletes the data after ExtJS has finished loading would delete the saved temporary data before the Save button is clicked?

Following up on this. Yes, “$resource” is available, so I don’t need to use getObject here. Don’t know why I did that; I suspect I copied this script from somewhere else I used it, in a place where $resource wasn’t available. Much simpler!

Ideally, you wouldn’t even need to do that. The value of the TV is generated on the fly when the resource editing form is loaded. The question is, how do we get that data into the TV in the form without having to use the database at all? If I knew more about how ExtJS works, I could probably inject some javascript to make that happen.

I’m continuing that topic here:

I realise (from reading your post) that I must have come across more negative than intended.

To be clear, your (initial) solution is probably the most reasonable and practical solution. I’d probably do the same, if I had this use case in a real-world project.

In your initial post, you stated that you “created a multiselect TV” that “doesn’t get stored”. So I was intrigued, because I tried to do that, but couldn’t avoid the TV-value being stored. Turns out, the value of your TV is stored as well :wink:.


I believe your solution with “OnDocFormRender” works, because the event fires after the resource is loaded but before the TVs are loaded from the database. As far as I can tell this happens in the same PHP function and no ExtJS is used to load the TV data.

This part can be achieved with the event ‘OnResourceTVFormRender’. It’s the saving part (without writing data to modx_site_tmplvar_contentvalues) that I could never figure out.

use OnBeforeDocFormSave to unset the value before it gets saved?

I have tried that. The problem is, that at the time when the event “OnBeforeDocFormSave” fires, the new TV values are already copied from $_POST to the properties of the underlying processor. And from the plugin there is no way (that I can see) to change these processor-properties.

Keep in mind that a TV value for a resource that’s equal to the default value of the TV will not be stored in the TV table. You can use any default value, so you should be able to remove values from the table by setting the value to some arbitrary default value.

I know that default values are never saved initially, but I’ve never checked to make sure that existing values are removed when the TV is set to the default.

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.