Support » Plugin: Pods - Custom Content Types and Fields » Filtering a multi selct list

  • Resolved Photogent

    (@photogent)


    I have a project I have been working on, and I have encountered a bit of frustration. Unfortunately, I have never been good at learning programing languages, so I am stuck. Sorry if this is confusing, or if I get terminology wrong.

    I have two pods on this project so far. One labeled “Building,” and one labeled “Events.” There is a relational field in “Building” (named “events”,) that links to the event pod, using the building name as the common field. The field in both tables is named “official_name.”

    When you set up a new building, you hit the events field, and you are presented with a multi-select field for which events you want to display (display works already, I figured out that much!) Unfortunately, the multi select shows ALL event fields, which will soon number in the thousands, of which I will want ten or twelve.

    I get the feeling that in the “building” pod, on the set up for the “event” field, I should be able to filter the multi select list down. In the field’ “Advanced Options,” there is an option labeled “Customized WHERE.” I get the impression that I should be able to filter the multi-selct list to only show events that have the same official name as the building I am currently editing. Something like:

    where event.official.name = building.official_name.

    Only, try as I might, I can’t seem to find a version of the code pods understands. I have tried all kinds of variations, but the “Building” pod fails to load when editing a building because my addition is not recognized. Can someone point me in a better direction (Or just straight up tell me what code I need?)

    Thanks in advance.

Viewing 10 replies - 1 through 10 (of 10 total)
  • Plugin Support Paul Clark

    (@pdclark)

    I tried this a couple different ways and checked with Scott.

    The WHERE clause can’t be used for relationships that are in the process of being created — the interface doesn’t provide a way to reference back to the originating post before the relationship exists.

    The workaround is to filter the relationship field with PHP.

    Here’s an example in the form of a plugin, based on https://docs.pods.io/code-snippets/multiple-fields-pick-list/

    <?php
    /**
     * Plugin Name: Filter Events Relationship
     * Description: Filter Pods meta field "events" relationship field to instances where "official_name" is equal on building and event.
     */
    
    add_filter(
    	'pods_field_pick_data',
    	function ($data, $name, $value, $options, $pod, $id) {
    		if ( 'pods_meta_events' === $name && 'building' === get_post_type( $id ) ) { // Field name: "events", post type "building"
    
    			$building = pods( 'building', $id );
    			
    			foreach ( $data as $dataid => &$value ) {
    				if( $dataid ){
    					$event = pods( 'event', $dataid );
    
    					if ( $event->display('official_name') !== $building->display('official_name') ) { // Field name: "official_name"
    						unset( $data[ $dataid ] );
    					}
    				}
    			}
    		}
    		return $data;
    	},
    	1,
    	6
    );

    The post types above are “event” and “building”. The meta fields are “events” and “official_name”.

    • This reply was modified 3 months, 3 weeks ago by Paul Clark.
    Thread Starter Photogent

    (@photogent)

    Thank you!

    I messed up in my explanation. I listed the field name wrong (It’s actually “building_event”) and I had building as plural instead of singular for the pod name, but, with your code, I was able to figure out what needed to be renamed and install the “plugin” via Pluginception. I even kind of understand what the code is doing. I’m not ready to raise myself up to the level of PHP “novice” yet, that’s probably still too lofty, but this works, and I think I can adapt it should a similar situation arise (my long term plans indicate this will happen!)

    I assume If I want to do something similar later, I need to make sure I rename the fields, the pod names, and assign new variable names, so that there is no conflict between the mini plugins?

    Plugin Support Paul Clark

    (@pdclark)

    Great!

    Should be enough to rename the field and pod names; variable names are scoped within the anonymous function.

    Plugin Support Paul Clark

    (@pdclark)

    Here’s rewritten version of the plugin that generates the appropriate function with a class:

    <?php
    /**
     * Plugin Name: Filter Pods Relationships
     * Description: Filter Pods meta fields with a generic <code>Filter</code> class.
     * Version: 2
     */
    
    namespace Filter_Pods_Relationships;
    
    add_action(
    	'plugins_loaded',
    	function(){
    		new Filter([
    			'relationship_field_name' => 'events',
    			'source_post_type' => 'building',
    			'filter_post_type' => 'event',
    			'source_meta_key' => 'official_name',
    			'filter_meta_key' => 'official_name',
    		]);
    	}
    );
    
    class Filter {
    	function __construct( array $args ) {
    		add_filter(
    			'pods_field_pick_data',
    			function ( $data, $name, $value, $options, $pod, $id ) use ( $args ) {
    				if ( 'pods_meta_' . $args['relationship_field_name'] === $name && $args['source_post_type'] === get_post_type( $id ) ) { 
    
    					$source_pod = pods( $args['source_post_type'], $id );
    
    					foreach ( $data as $data_id => $value ) {
    						if( $data_id ){
    							$filter_pod = pods( $args['filter_post_type'], $data_id );
    							if ( $filter_pod->display( $args['filter_meta_key'] ) !== $source_pod->display( $args['source_meta_key'] ) ) {
    								unset( $data[ $data_id ] );
    							}
    						}
    					}
    				}
    				return $data;
    			},
    			1,
    			6
    		);
    	}
    }
    

    Using this method, a new filter of the same type can be instantiated with the signature:

    new Filter([
    	'relationship_field_name' => 'events',
    	'source_post_type' => 'building',
    	'filter_post_type' => 'event',
    	'source_meta_key' => 'official_name',
    	'filter_meta_key' => 'official_name',
    ]);

    So with your field names, it’d likely be:

    new Filter([
    	'relationship_field_name' => 'building_event',
    	'source_post_type' => 'buildings',
    	'filter_post_type' => 'events',
    	'source_meta_key' => 'official_name',
    	'filter_meta_key' => 'official_name',
    ]);

    Or something very close depending on whether your post type IDs are plural or not.

    Multiple filters can be instantiated by duplicating the new Filter([...]); block.

    Plugin Support Paul Clark

    (@pdclark)

    (If it doesn’t load with Pluginception, try changing plugins_loaded to init.)

    Thread Starter Photogent

    (@photogent)

    I’m afraid the new version is going over my head! What is the advantage of using the class?

    Plugin Support Paul Clark

    (@pdclark)

    It’s a slight advantage of separating configuration from code.

    Version 1 and version 2 do the exact same thing.

    In version 1, each new filter would result in duplicating the entire code block.
    In version 2, each new filter would result in duplicating only the new Filter([...]); portion.

    It could be written without a class.

    Plugin Support Paul Clark

    (@pdclark)

    Here’s version 3 with a function instead of a class:

    <?php
    /**
     * Plugin Name: Filter Pods Relationships
     * Description: Filter Pods meta fields with <code>add_pods_field_relationship_filter()</code> function.
     * Version: 3
     */
    
    namespace Filter_Pods_Relationships;
    
    add_action(
    	'plugins_loaded',
    	function(){
    		add_pods_field_relationship_filter([
    			'relationship_field_name' => 'events',
    			'source_post_type' => 'building',
    			'filter_post_type' => 'event',
    			'source_meta_key' => 'official_name',
    			'filter_meta_key' => 'official_name',
    		]);
    	}
    );
    
    function add_pods_field_relationship_filter( array $args ) {
    	add_filter(
    		'pods_field_pick_data',
    		function ( $data, $name, $value, $options, $pod, $id ) use ( $args ) {
    			if ( 'pods_meta_' . $args['relationship_field_name'] === $name && $args['source_post_type'] === get_post_type( $id ) ) { 
    
    				$source_pod = pods( $args['source_post_type'], $id );
    
    				foreach ( $data as $data_id => $value ) {
    					if( $data_id ){
    						$filter_pod = pods( $args['filter_post_type'], $data_id );
    						if ( $filter_pod->display( $args['filter_meta_key'] ) !== $source_pod->display( $args['source_meta_key'] ) ) {
    							unset( $data[ $data_id ] );
    						}
    					}
    				}
    			}
    			return $data;
    		},
    		1,
    		6
    	);
    }

    So then each new filter would be a variation of:

    add_pods_field_relationship_filter([
    	'relationship_field_name' => 'events',
    	'source_post_type' => 'building',
    	'filter_post_type' => 'event',
    	'source_meta_key' => 'official_name',
    	'filter_meta_key' => 'official_name',
    ]);

    All three versions do the same thing — it’s just a matter of configuration / maintenance.

    Thread Starter Photogent

    (@photogent)

    The new code works great. I have not added another field filter yet, but it is clear how to do that, so I am looking forward to the chance!

    Thread Starter Photogent

    (@photogent)

    Just wanted to add one last update. Today I added an extra set of filtering criteria to the code, and it works perfectly. Fantastic work, thank you!

Viewing 10 replies - 1 through 10 (of 10 total)
  • You must be logged in to reply to this topic.