How to create a drop-down list (combo-box) for TV field in MODX Collections table

How to create a drop-down list (combo-box) for TV field in MODX Collections table

I like MODX Collections very much already and this is exactly the feature I was missing. Let's see how to make drop-down lists of values for TV fields for editing directly in the table.

For a long time I agonized with this topic, because there are no comprehensive guides on the Internet. Actually, who works with MODX Collections - surely there is a desire to edit TV fields with a list of values directly in the table? I have a wish. I'm sharing how to realize it. So far it's hastily done, then I'll put it in order.

How to set the field editor

Let's start with the basics to understand what we are talking about. In Collections settings, each column has an editor parameter - how this column can be edited by clicking directly in the table.

We can use the standard ones - as described in the documentation, such as textfield, textarea, numberfield, etc. I used to specify textfield and didn't worry - so I can change images, text, and multiple TVs with ID list manually.

You can also specify field settings in a more extended JSON format. All standard xtype (string, number, list, etc.) are available in this way too. Here is an example of a simple dropdown list of 2 values:

{
    "xtype": "combo",
    "fields": ["value"],
    "store": ["Article","Post"],
    "displayField": "value",
    "valueField": "value",
    "triggerAction": "all"
}

I came to this solution last time after a lot of research. But in this variant we can't set list values dynamically - it turns out to be a bit (much) inconvenient if TV fields have a large list of values or @SELECT binding to resources.

After every TV edit, you have to go to Collections settings and change it there. Not very convenient, especially if there are several Collections views.

Searching for a solution

To solve this problem, I thought to add a JS script to the manager page by plugin, which would create a global variable with an array of values, which would be selected from the database and formatted in the desired form.

However, in the Collections column settings, specifying a variable rather than a string or array causes the settings string to be escaped. This will cause a JSON format error and the configuration just doesn't work and the page is rendered empty.

But the plugin idea turned out to be good - I just need to create not a JS variable, but a complete new type of field (it's not difficult if you figure it out), which we can specify as an editor for the column later.

We can take standard MODx fields (which are extended from ExtJS fields) and extend from them, specifying our own configuration of this field. For example, first we create a new modx-combo-mycategories field type with static values (I know it's not good practice to mix code, but I was just experimenting):

$modx->regClientStartupScript("<script>
MODx.combo.MyCategories = function(config) {
    config = config || {};
	Ext.applyIf(config,{
		name: 'categories'
		,hiddenName: 'categories'
		,displayField: 'name'
		,valueField: 'id'
		,fields: ['name','id']
		,pageSize: 20
		,store: ['testvalue', 'testvalue2']
	});
	MODx.combo.MyCategories.superclass.constructor.call(this,config);
};
Ext.extend(MODx.combo.MyCategories,MODx.combo.ComboBox);
Ext.reg('modx-combo-mycategories',MODx.combo.MyCategories);
</script>
");

If you now specify the value modx-combo-mycategories in the Collections view of the field in the Editor setting, you will see a drop-down list of testvalue, testvalue2 when editing this cell in the table.

But here again we see a static array in the list of values. Where are the dynamics, you may ask? All in order, I explain to make it clear to those who have not yet encountered it, because I had a headache from all this).

Dynamic list of values

So, in store we set a list of values for a field. Let's make it dynamic. There are 2 options here:

  • use MODx processors or our own
  • generate an array in PHP and insert it into the string

Processors

Lyrical digression. In MODx there is a concept of a connector - it is a file that allows you to access backend functionality from the frontend, usually through processors.

This cannot be done directly for security reasons, so the backend (the core folder) is closed from the outside. Connectors are stored in the public assets folder or the MODx connectors folder, and can be queried from the outside. But they contain certain accesses and security settings, so that strictly defined user can access strictly defined functions through them.

And the processor is a ready-made script with a set of actions that we can run through the connector. For example, create, delete, view TV field. Almost all MODX admin works through processors.

In this variant of using the dropdown list, we set the connector address and query parameters in the field configuration - and the result of the processor will be displayed as a list of values.

From the JS code of the field configuration we remove store, but add 2 important parameters - url and baseParams. I didn't find any examples of official explanation on MODx, I think they are on ExtJS. As I understand it - url is the address the field makes a request to, and baseParams are the parameters of that request.

In this example, we are making a request to the normal MODX connector, with the action parameter specifying the path to the category list retrieval processor. 

So, let's make a drop-down list with categories:

MODx.combo.MyCategories = function(config) {
config = config || {};
	Ext.applyIf(config,{
		name: 'categories'
		,hiddenName: 'categories'
		,displayField: 'name'
		,valueField: 'id'
		,fields: ['name','id']
		,pageSize: 20
		,url: MODx.config.connector_url
		,baseParams: {
		    action: 'element/category/getlist'
		}
	});
	MODx.combo.MyCategories.superclass.constructor.call(this,config);
};
Ext.extend(MODx.combo.MyCategories,MODx.combo.ComboBox);
Ext.reg('modx-combo-mycategories',MODx.combo.MyCategories);

You can replace the processor name and specify additional parameters. For example, to get the characteristics of a certain category or a list of users. You can try to interact with the processor from the console (install the Console or ModalConsole package):

$response = $modx->runProcessor('element/category/getlist');
return $response->response;

This will output an array of categories, which our new field receives as its values. 

Extensions, by the way, can use their own processors - Collections will have a different url and a different action.

PHP generation

For example, if standard processors are not enough, and you don't want to write your own, you can query the database and generate a string from the results in the form of an array, which you can add to the plugin in a line with JS code.

Where $test_arr - can be any database query logic. For example, get TV field settings with value options, split them into an array (although there is a processor for this too, I need to look for it).

$test_arr = "[['test1', 1], ['test1', 2]]";

$modx->regClientStartupScript("
<script>
	MODx.combo.MyCategories = function(config) {
    config = config || {};
	    Ext.applyIf(config,{
	        name: 'categories'
	        ,hiddenName: 'categories'
	        ,displayField: 'name'
	        ,valueField: 'id'
	        ,fields: ['name','id']
	        ,pageSize: 20
	        ,store: ".$test_arr."
	        }
	    });
	    MODx.combo.MyCategories.superclass.constructor.call(this,config);
	};
	Ext.extend(MODx.combo.MyCategories,MODx.combo.ComboBox);
	Ext.reg('modx-combo-mycategories',MODx.combo.MyCategories);
</script>"
);

Another option I want to try is to plugin add an array of values for this TV field to the frontend in JS, and then use it when calling this field. But it doesn't work in Collections because the settings string is escaped.

$modx->regClientStartupScript('
	<script>
	Ext.onReady(function() {
		MODx.test_tv_value = ["a123sdsad", "a456dasd", "a678sdasd"];
	});
	</script>'
);

The bottom line with any of these variations is that we need to specify in the Collections Column Editor setting the name of the created type - in the current example modx-combo-mycategories.

Solution

At the moment I have settled on this option:

  • in the plugin on the OnManagerPageBeforeRender event we get possible TV field values;
  • We split them into an array, and the values can be in a pair of key==value, or they can be just values - the script works it out;
  • then we add a code to the frontend in the context of JS manager, where we create a new xtype, inheriting from MODX combo - xtype of a regular list. I think you can do this with multiselects as well. 

An example of getting the input list by TV:

$result = [];
$getTV = $modx->getObject('modTemplateVar',
  array('id'=>17)
);
$getTV = $getTV->get('elements');
$getTV = explode('||',$getTV);
foreach($getTV as $key => $value) {
  $row = explode('==',$value);
  if (!empty($result[ $row[1] ])) {
  	$result[ $row[1] ] = $row[0];	
  } else {
  	$result[] = $row[0];
  }
}
$store_str = json_encode($result);

$modx->regClientStartupScript("
<script>
    MODx.combo.ArticleStatus = function(config) {
    config = config || {};
        Ext.applyIf(config,{
            name: 'article_status'
            ,hiddenName: 'article_status'
            ,displayField: 'name'
            ,valueField: 'name'
            ,fields: ['name']
            ,pageSize: 20
            ,store: ".$store_str."
        });
        MODx.combo.ArticleStatus.superclass.constructor.call(this,config);
    };
    Ext.extend(MODx.combo.ArticleStatus,MODx.combo.ComboBox);
    Ext.reg('modx-combo-article-status',MODx.combo.ArticleStatus);
</script>"
);

Change the TV field ID and in the column settings in the Collections view set Editor modx-combo-article-status. We specify the name of the new type in the last line of the configuration.

The names of these types can be changed, so we can add at least 10 different lists in the same plugin. In fact, you can generate a query not only by possible TV values, but any query to the database.

Here is a variant where the field settings specify just values. If you have a key==value, then you need to change the parameters in the configuration to correctly output and substitute the value:

,displayField: 'name'
,valueField: 'id'
,fields: ['name', 'id']

I still need to finalize how to handle TV fields with @SELECT binding, but I think it's not that complicated. 

References

Well, and a set of useful links on the topic, because I've seen different examples, but there is no specific described solution for Collections.

Please rate this article
(5 stars / 2 votes)