# Metafilters

A "metafilter" is a global filter that's always active in addition to the current "regular" user-selected filter (e.g., an era or ratings filter). This allows a metafilter to further narrow the list of games selected by the current regular filter, or even to override the regular filter's decisions by ruling in some of the games that the regular filter ruled out.

Metafilters are useful for additional layers of filtering that you want to keep in effect at all times while still letting the user select games with the regular filters. For example, a metafilter could be used for a "family mode" setting that excludes games that you've marked as having adult themes. While this family metafilter is in effect, the user can still filter games normally (by era, by rating, etc), but adult-themed games will be excluded no matter what other filter is selected. For a worked example of how to create this type of filter, see Family Filter.

Currently, there are no built-in metafilters, so any metafilters in effect will always be Javascript add-ons.

## Creating a metafilter

To create a metafilter, you call gameList.createMetaFilter(desc). The argument desc is an object the describes the new metafilter via its properties. The properties of this object are:

- after: An optional function that's called at the end of a selection cycle, after the last call to select for the cycle. This lets the metafilter do any desired clean-up (such as forgetting any large cached objects, to free up memory that's no longer needed). This can be omitted if no clean-up work is required
- before: An optional function that's called at the start of a selection cycle, before the first call to select on each cycle. This lets the metafilter do any preparation, such as caching information that applies to this selection run. This can be omitted if no prep work is required on each cycle.
- includeExcluded: An optional boolean specifying whether or not the select function should be called for games that are excluded by the regular filter or the previous metafilter. This is false by default, meaning that the select function won't see games excluded by the other filters, and hence can only narrow the selection list decided upon by the other filters. If you set this to true, the select function will be called for every game in the collection, even for games that were excluded by the other current filters, which allows it to rule games back in even after they've been ruled out by the other filters. You can tell that a game was excluded by the other filters by checking the included argument to the select function.
- priority: An optional number value giving the execution order for this metafilter relative to any other metafilters that are also in effect at the same time. This lets you control which metafilter gets the final say on each game. See Multiple metafilters and the priority order below.
- select: Required. A function that's called once for each game, to test to see if the game should be included in the final selection set. This function is called like this:

  select(game, included)

  game is a GameInfo object describing the game to be tested for inclusion. included is a boolean value indicating whether or not the game is in the selection set for the current "regular" filter and for the previous metafilter (if other metafilters are in effect). The function returns true if the game should be included in the final selection set, false if the game should be excluded.

The return value from gameList.createMetaFilter() is an opaque identifier that you can use to later remove the filter via gameList.removeMetaFilter().

## Removing a metafilter

Once created, a metafilter stays in effect until it's explicitly removed. Changes to the current main filter selection don't affect it, and the creation or removal or other metafilters don't affect it.

To remove a metafilter, call gameList.removeMetaFilter(), passing the identifier returned from the call to gameList.createMetaFilter() that originally created the filter.

## Multiple metafilters and the priority order

Metafilters always have the last say on the filtered game list, because the metafilters are called after the current regular filter has been called. The metafilters therefore override the regular filter: if the regular filter rules a game in, but a metafilter then rules it out, the game is out.

But what if there are multiple metafilters in effect? This is specifically allowed, so we have to consider how they negotiate for control over the final decision.

The main point of negotiation is the priority setting for each filter. When multiple metafilters are simultaneously active, PinballY calls them in ascending numeric order of their priorities. For example, if metafilter A has priority 1000, and metafilter B has priority 2000, A's select function will be called first for each game (because it has the lower numeric value for the priority), and then B's select function will be called after that.

In most situations, going first is the privileged position. In this case, though, going last is where all the power lies. This is because each metafilter's decision replaces the prior filter's decision. There's no concept of voting or combining the decisions; the last filter called simply has the last say.

## Narrowing vs. widening metafilters

Most metafilters are "narrowing" filters, meaning that their function is to further narrow the selection that the regular filter produces by excluding some of the games it selects. For example, a "family filter" that excludes adult-themed games is only interested in removing games that match its adult-content test.

A metafilter can also act as a "widening" filter. A widening filter is one that adds games to the regular filter's selections, including games in the final results that the regular filter would have normally excluded. For example, if you wanted to make sure that a certain core set of games was always available in the wheel UI, no matter what filter was selected, you could do that with a widening filter.

To create a filter that can widen the selection, you must set the includeExcluded property in the filter description to true when creating the filter. This allows its select function to be called with every game in the collection, even games that were excluded by other active filters. In the select function, you can tell whether or not a game was accepted by the other filters via the included argument.

Here's an example that keeps the other current filters' decisions intact, but also adds games marked with the "Core" category tag, even if they were excluded by other filters.

gameList.createMetaFilter({ includeExcluded: true, // consider all games, even if excluded by other filters priority: 100000, // high priority so that get the last say select: function(game, included) { // keep it if it's included by the other filters, OR if // it has the "Core" category tag return included || game.categories.indexOf("Core") >= 0; } });