# FilterInfo

A FilterInfo object contains information on a game filter, which is essentially a selection rule that selects a subset of games to show in the wheel UI. The system provides a number of predefined filters, such as the era filters ("1970s games"), manufacturer filters ("Gottlieb games"), and system filters ("Visual Pinball X games"). You can also create your own custom filters, which we'll explain below.

The main wheel UI lets the user select which filter to use at any given time via the menu system. Whenever the selected filter changes, a filterselect event is fired. You can use this to do additional work as needed when the filter selection changes.

## Creation

You don't create FilterInfo objects directly. You get them by calling gameList.getFilterInfo(). That function returns a FilterInfo object populated with details about the filter you specified.

If you want to create a custom filter, see Custom filters below.

## Properties and methods

cmd: An integer command ID value. This will always be in the range from command.FilterFirst to command.FilterLast (see Commands). (The command ID is assigned for the current session only, so it shouldn't be saved externally.)

getGames(): Returns an array of GameInfo objects listing all games that the filter currently selects.

group: A string giving the menu group for the filter. System filters all have groups with names enclosed in [square brackets]. These are never displayed to the user; the system uses localized menu titles for the groups instead. User-defined filters have optional group names defined when the filter was created; these can refer to system groups, or can be given as the actual menu title to show in the main menu. This is omitted if the filter isn't part of a menu group.

groupCmd: An integer giving the command ID associated with the group. This is the command used for the menu item shown in the main menu, if any, that opens the group submenu. This is undefined if the filter isn't part of a group, or it's part of one of the top-level groups that doesn't have an associated command ([Top], [Op]).

id: The ID of the filter (see Filter IDs). This is a permanent ID for the filter, so it can be stored in external files or settings.

testGame(game): Determines whether the filter includes or excludes the given game. game is a GameInfo object specifying the game to test. Returns true if the filter includes the game in its selection list, false if the filter excludes the game. (If game doesn't refer to a valid game, the return value is null, which for conditional purposes is equivalent to false, but can be distinguished with the Javascript === operator if the distinction is important to your code for some reason.)

title: A string giving the display title of the filter as it appears in menus. Note that this might be localized, and might change across versions, so you shouldn't use it to recognize a particular filter; use the id value for that.

## Filter IDs

Each filter defined by the system has a unique ID string. This ID string is designed to be permanent across sessions, so it can be stored in external files and saved settings.

Each class of predefined system filters uses an ID that follows a particular format. You can test filter IDs against these formats if you want to recognize specific filters or specific classes of filters. Here are the ID formats for the system's filters:

- AddedBefore.N: selects games added to the database via PinballY's "Edit Game Details" dialog earlier than N days ago; the system creates filters for 7, 30, and 365 days
- AddedWithin.N: selects games added to the database via PinballY's "Edit Game Details" dialog within the past N days; the system creates filters for 7, 30, and 365 days
- All: the All Games filter, which selects all non-hidden games
- Category.name: selects games tagged with category name
- Favorites: the Favorites filter, which selects games with the user-set "favorite" flag
- Hidden: the Hidden Games filter, which selects only games with the user-set "hidden" flag; these games are excluded by all other built-in filters
- Manuf.name: selects games whose manufacturer names in the XML database match name
- NeverPlayed: selects games that have never been played under PinballY
- NotPlayedWithin.N: selects games not played within the past N days using PinballY
- PlayedWithin.N: selects games played within the past N days using PinballY; the system creates filters for 7, 30, and 365 days
- Rating.N: N is from 0 to 5; the star rating filters, each of which selects games with N- or N½-star ratings
- Rating.-1: the Unrated Games filter, which selects games that don't have star ratings
- Uncategorized: selects games with no category tags
- Unconfigured: the Unconfigured Games filter, which selects only games with no XML database entries
- User.id: a user-defined filter created via gameList.createFilter(); the id portion is the id property specified in the filter descriptor when creating the new filter. See Custom filters above.
- YearRange.from.to: selects games with release years between from and to, inclusive; the system creates one such filter for each 20th century decade that includes at least one database entry (e.g., YearRange.1970.1979 is the 1970s filter), and one filter for all years from 2000 onward (this uses 9999 as the ending year, to essentially mean "the end of time", so the actual ID is YearRange.2000.9999)

## Custom filters

You can define your own custom filters via gameList.createFilter(desc). This function creates a new filter with a selection rule you define via a Javascript function, which can express just about any kind of selection criteria you can think of.

The return value from gameList.createFilter() is a command ID that the system assigns for use by the filter. This is used if the filter is automatically added to system menus (see Automatic menu placement below). You can also use this command ID if you want to create an item for this filter in any custom menus. The command will always be in the range from command.FilterFirst to command.FilterLast, which is shared with the built-in system filters. The newly assigned command will always be chosen to be unique, so it won't conflict with any system filters.

The desc argument to createFilter() is an object describing the new filter. It contains the following properties:

- id: Required. A string giving the ID of the new filter. This is an arbitrary string you provide to identify the filter in the settings file. You can use letters, numbers, underscores, and periods in the ID. The ID must be unique across all filters in the system, so it's a good idea to include some kind of author name or organization name prefix ("mjr.TitleLength.Short"). The ID must be permanent (always the same across sessions), so that it can match the saved filter ID in the settings file when restoring settings in a future session.
- title: Required. A title for the filter. This will be used in the status line messages when this filter is active, if any status line messages include the filter name. It's also used by default as the menu title for the filter, but you can override that with menuTitle.
- menuTitle: Optional. The title for the filter as it appears in menus. The title string is used if this isn't specified. The separate menu title is sometimes useful when the filter is part of a group, where it's nicer to avoid repeating the same verbiage for every menu in the group. The system "Played Within" menus use this capability, listing just the time frame in the menu,
- select: Required. The selection function. When the filter is activated (that is, when the user selects this as the current filter in the wheel UI), this function is called once for each game in the game database, to determine which games are selected by the filter and which are excluded. The function takes one argument, a GameInfo object describing a game to test for inclusion. The function returns true if the game is included in the filter's selection, false if not.
- before: Optional. This is a function, which is called once each time the filter is activated, just before scanning the list of games through the select function. The function is called with no arguments, and any return value is ignored. This function gives the filter a chance to initialize in preparation for the game list scan; it can, for example, cache any information that will be needed on each select call, or initialize internal counters.
- after: Optional. A function that's called just after the game list scan has been completed. The filter can do any necessary cleanup, store final counter values, etc.
- group: Optional. The group name for the filter. If provided, the system's main menu will include a menu item with this name, which shows a submenu with all filters in this group. Use a name that makes a good title for this menu item, such as "Filter by Title Length". If omitted, the filter isn't automatically included in any system menus, but you can still select it via your own custom menus or via gameList.setCurFilter().
- sortKey: Optional. A string giving a menu sorting key for this filter relative to other filters in its group. When the group menu is displayed, the menu items are sorted according to this key, in alphabetical order. The title string is used by default, so that the menu items appear in alphabetical order of title. That's not always the most desirable order for some groups, though. For example, if you had a group that filters by game title, with entries for "Short Titles", "Medium-Length Titles", and "Long Title", it would make more sense to display the menu in that order (short, medium, long) rather than alphabetically. You could accomplish that by using sort keys "A", "B", and "C" for the short, medium, and long items, respectively. The sort key is never displayed anywhere, so you can use whatever's convenient to get the desired sorting order.
- includeHidden: Optional. If true, games marked as hidden by the user are included in the set of games tested with the select function; if omitted or false, hidden games are assumed to be omitted, so they're not even tested with the select function, saving a little bit of time by skipping unnecessary tests. This is called out as a separate test because all of the system filters exclude hidden games, except for the "Hidden Games" filter, which only includes hidden games. Setting this to true won't necessarily cause hidden games to appear on the wheel; rather, it allows hidden games to be tested by your select function for inclusion. You should only set this to true when your filter is explicitly some kind of hidden games filter, because it might be confusing for the user to see games they intentionally marked as hidden show up in a filter that isn't specifically about hidden games.
- includeUnconfig: Optional. If true, games that aren't configured (meaning they have no XML data entries) are included in the set of games tested with your select function. If false or undefined, and the user has selected the "hide unconfigured games" option in the settings, unconfigured games are assumed to be omitted, so they aren't even tested with the select function to save some time. Note that setting this to true might cause unwanted behavior, since it goes against the user's instructions to "hide unconfigured games" from most filters. You should only set this if your filter is explicitly some kind of unconfigured selection filter, so that it doesn't confuse the user when unconfigured games appear there.

## Automatic menu placement

Custom filter group menus: If you give the filter a group property, the system automatically creates a new menu item for the group in the top-level menu. Selecting this menu opens a sub-menu showing all of the filters with the same group property. The group menu is added after the standard filter group items in the standard menu (Filter by Era, Filter by Manufacturer, and so on), so this lets you create your own custom filter groups of the same style. The group property string is displayed as the menu title, so you'll probably want to use similar wording ("Filter by something") so that the menu meshes well with the existing items.

Adding the filter to an existing group: Instead of creating a custom filter group, you can add a custom filter to one of the existing groups. This would be appropriate if the filter augments one of those groups. For example, you could add your own "recently played" filter that selects a time period that's not covered in the standard set, or a "manufacturer" filter that selects games from all of the WMS brands. For the predefined system filter groups, though, you don't use the name that appears in the menu. Instead, you have to refer to a special internal name. We use the internal name instead of the name in the menu because the menu name can be localized for different languages; the internal name doesn't have to be translated, since it's never displayed to the user, so you can always count on it being the same. The system filter groups are shown below.

- [Era] - the decade and era filters (70s Tables, etc). The menu sorting keys for these filters are the four-digit starting year of the decade, so "1970" for 70s Tables. If you want to slot in your own filter at a specific place in the menu, set the sort property to a string that sorts where you want it relative to those four-digit year strings. Note that the numeric value isn't important; the order is based on the lexical ordering of the string, not the numeric value.
- [Manuf] - the manufacturer filters (Williams Tables, etc). The sort keys are the manufacturer names.
- [Sys] - the player system filters (Visual Pinball 9 Tables, etc). The sort keys are the system names.
- [Rating] - the star rating filters. The sort keys are the digits for the star values, "1", "2", etc. The "unrated" filter has sort key "Z" to force it to the end of the list.
- [Cat] - the individual category filters. The sort keys are the category names. The "uncategorized" filter has sort key "\uE800": that's Unicode code point E800, a special Unicode "private use" character that sorts after all alphabetic characters from all languages in the Unicode set, so that the uncategorized filter sorts after all actual categories. Note that the Unicode private use area is from \uE000 to \uF8FF, and we intentionally used a character from the middle of this region to let you slot in custom filters after all of the named categories but before "Uncategorized", by using sort keys starting with characters from \uE000 to \uE7FF.
- [Played] - the "played within" time filters. The sort keys are the number of days expressed as five digits with leading zeroes, so "played within a week" has sort key 00007. The "Recently Played" menu has an internal structure with separate groups for the "played within", "not played within", and "never played" filters, so "played within" sort keys are only compared to other "played within" keys.
- [!Played] - the "not played within" time filters. These likewise have sort keys with the number of days expressed as five digits.
- [!!Played] - the "never played filter. This has sort key "Z" (which isn't normally used at all, since this item normally appears in a group by itself within the "Recently Played" submenu; but the sort key is there just in case you want to add another item to the group).
- [Added] - the "added within" time filters. The sort keys are the number of days expressed as five digits, as with the "played within" filters.
- [!Added] - the "added before" time filters. The sort keys are the number of days expressed as five digits.

Adding to a top-level menu: There are two special filter groups that go in top-level menus rather than in group submenus, "[Top]" (the main menu), and "[Op]" (the operator menu). If you use one of these strings for the group property, your filter will appear in the corresponding menu. Use this capability sparingly, since the menus will quickly become unusable if you add too many top-level items. If you're creating more than one or two filters that don't belong in obvious groups, you'll probably want to create a "Custom Filters" or "Filter by Misc" group as a catch-all, to avoid too much clutter in the top level menus.

- [Top] - filters that appear directly in the main menu. The system filters in this group are "All Games", (sort key "3000"), "Favorites" (sort key "7000"). The sort keys are chosen so that the built-in menu items appear in this order, and to make it easy for you to slot in any number of your custom filters before, between, or after the built-in items, should you want to add any custom filters to the top-level menu.
- [Op] - filters that appear directly in the Operator menu. the system filters in this group are "Hidden Games" (sort key "3000"), "Unconfigured Games" (sort key "7000"). The sort keys are chosen to make it easy for you to slot in your own custom filters before, between, or after the predefined system items, should you want to add any filters of your own to the Operator menu.

If you want to skip the automatic menu inclusion: Just leave group undefined if you don't want the system to automatically show the filter in any of the standard menus. You can control the menu placement more precisely using the menu customization mechanisms described in Menus. You can also activate the filter entirely under script control via gameList.setCurFilter().

## Examples

"Played within two weeks" filter: Suppose you wanted to supplement the standard "played within" filters with some more time periods than offered in the standard menus. Here's a filter that selects games played within the last two weeks. The filter is automatically included in the "Filter by Last Played" group because it uses the special system group "[Played]".

let filterReferenceDate; gameList.createFilter({ id: "PlayedWithin.14", title: "Played Within 2 Weeks", group: "[Played]", // add to the standard Recently Played menu sortKey: "00014", // number of days as a five-digit string before: function() { // Figure the reference date: 12:00AM at the start of the // day 14 days ago. Javascript's Date constructor conveniently // accepts day-of-the-month values outside of the 1..31 range, // which it interprets by projecting forwards or backwards into // ajacent months until finding a valid date. So if the current // day is Jan 1, 2019, then new Date(2019, 0, -13) will work // backwards to December 18, 2018. And since that works in // terms of dates in the local time zone, it even properly // takes into account special situtions, such as the usually // nettlesome transitions between daylight time and standard // time. // // Note that we *could* compute this value on every select() // call rather than caching it, but it makes things go a little // faster if we do this computation once ahead of time. The // system will call this before() function every time it's // about to do a scan of the table list, so we'll always have // a fresh reference point for each new scan. // start with today's date let d = new Date(); // now construct a date 14 days before today, at 12:00 AM filterReferenceDate = new Date(d.getFullYear(), d.getMonth(), d.getDate() - 14, 0, 0, 0, 0); }, select: function(game) { return game.lastPlayed >= filterReferenceDate; } });

Short, medium, and long titles: For this example, we'll create a group of filters that select according to the length of a game's title: short (6 character or fewer), medium (7-15 characters), or long (16+ characters). We'll create these as three filters that appear on a custom submenu, which will be reachable from a new top-level menu item called "Filter by Title Length". This is a pretty contrived example, but it shows how to create a custom filter group and present it in the main menu.

gameList.createFilter({ id: "Names.Short", title: "Short Titles", group: "Filter by Title Length", select: function(game) { return game.title.length <= 6; }, sortKey: "A" }); gameList.createFilter({ id: "Names.Medium", title: "Medium-length Titles", group: "Filter by Title Length", select: function(game) { return game.title.length > 6 && game.title.length <= 15; }, sortKey: "B" }); gameList.createFilter({ id: "Names.Long", title: "Long Titles", group: "Filter by Title Length", select: function(game) { return game.title.length > 15; }, sortKey: "C" });

One thing to note here is that we gave each filter a sort key. Sort keys are optional, but in this case, it's nice to include one. Without an explicit sort key, the system would just sort the menu items alphabetically by their labels. That's usually great, but this is one of those cases where the "natural" order that a user would expect isn't alphabetical. The intuitive order here is clearly short, medium, long. The sort keys let us override the default alphabetical sorting and provide our own custom sorting instead. When you provide sort keys, the system sorts the menus alphabetically by the sort keys, rather than alphabetically by the visible label. That lets you put the menus in any order you want, without having to change the labels.

The sort keys aren't visible to the user anywhere, so we can use any arbitrary strings that are convenient for putting things into the right order. In this case, we simply use "A", "B", and "C". If you wanted to leave gaps for inserting more items in the future, you could have made those "A", "M", and "Z", or something more flexible like "1000", "2000", and "3000". But for our fixed list of three items, the simplistic "A", "B", "C" will do just fine.

Note that you can use numeric strings like "1000" and "2000" for sort keys, but remember that they're still treated as strings, meaning they're sorted "lexically" rather than numerically. This has some odd consequences when you use strings that happen to contain digits. For example, the string "10" sorts before the string "2", for the same reason that "A0" sorts before "B". Lexical sorting mean that we compare the strings one character at a time, starting at the left end of the string. So if the very first character of string A sorts before the first character of string B, string A will go before string B, even if A has more characters following that make it look like a larger number when interpreted as a number. Hence "10" goes before "2", simply because "1" sorts before "2". The easy way to deal with this is to make sure that digit strings used as sort keys are all the same number of digits, using leading zeroes if necessary - so "0002" rather than just "2", for example.

Top-rated games: For a more extensive example, see A "Top 10" Filter in the Worked Examples section. That shows how to create several variations on a Top 10 filter that selects the current top-rated games in your collection.