{"id":19215557,"url":"https://github.com/alwaysblank/climber","last_synced_at":"2025-05-12T23:28:20.838Z","repository":{"id":62518217,"uuid":"116429972","full_name":"alwaysblank/climber","owner":"alwaysblank","description":"An alternative to WordPress's Walker for navigation menus.","archived":false,"fork":false,"pushed_at":"2019-07-17T18:01:31.000Z","size":116,"stargazers_count":48,"open_issues_count":3,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-20T19:37:52.791Z","etag":null,"topics":["wordpress"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alwaysblank.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-05T21:47:52.000Z","updated_at":"2024-02-27T04:46:24.000Z","dependencies_parsed_at":"2022-11-02T15:32:03.482Z","dependency_job_id":null,"html_url":"https://github.com/alwaysblank/climber","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysblank%2Fclimber","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysblank%2Fclimber/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysblank%2Fclimber/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysblank%2Fclimber/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alwaysblank","download_url":"https://codeload.github.com/alwaysblank/climber/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253839507,"owners_count":21972334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["wordpress"],"created_at":"2024-11-09T14:14:03.199Z","updated_at":"2025-05-12T23:28:20.800Z","avatar_url":"https://github.com/alwaysblank.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🧗 Climber\r\n### Why [walk](https://codex.wordpress.org/Class_Reference/Walker) when you can climb? \r\n\r\nAn alternative to WordPress's built-in Nav_Walker, 🧗 Climber creates a more \r\nreasonable data structure, which can be interacted with directly or used to \r\ngenerate the HTML for a navigation menu.\r\n\r\n[![Build Status](https://travis-ci.org/alwaysblank/climber.svg?branch=master)](https://travis-ci.org/alwaysblank/climber)\r\n\r\n#### ☠️ Currently in Development ☠️\r\n#### ⚡ Probably not ready for production ⚡\r\n\r\n## Usage\r\n\r\nThe simplest implementation of 🧗 Climber looks like this:\r\n\r\n```php\r\nuse Livy\\Climber;\r\n\r\necho new Climber(\r\n  new Tree(\r\n    new Spotter\\WordPress(wp_get_nav_menu_items($menuID))\r\n  )\r\n);\r\n\r\n// \u003cnav class=\"simpleMenu\" \u003e\r\n//    \u003cul class=\"simpleMenu__menu level-0\"\u003e\r\n//        ...etc\r\n```\r\n\r\n...Maybe not quite so simple. There will be convenience functions eventually!\r\n\r\nThis document doesn't detail all the methods and ways of interaction with \r\n🧗 Climber—only the ones you're most likely to use. The methods themselves\r\nare heavily documented inline, so feel free to dive into the code if you're\r\ncurious how something works.\r\n\r\n## How Does It Work?\r\n\r\n🧗 Climber has three basic components that it needs to work:\r\n\r\n* Spotter\r\n* Tree\r\n* Climber\r\n\r\n**Spotter** allows for 🧗 Climber to be platform agnostic: While it ships with a\r\nWordPress Spotter, you can add whatever Spotters you like to collect and process\r\ndata. The Spotter's job is to return data in the format that Tree expects.\r\n\r\n**Tree** processes the data recieved from Spotter and implements several methods\r\nthat can be used to quickly and easily interact with the data it contains. Its\r\norganization allows Climber to quickly find the things it needs to generate a\r\nnavigation menu.\r\n\r\n**Climber** collects a Tree and returns a properly-constructed navigation menu\r\nin HTML.\r\n\r\nGenerally, you'll only be interacting directly with Climber, but you can easily\r\nextend or modify the other components to suit your needs—or use them to modify\r\nyour data.\r\n\r\n## Show Me\r\n\r\nThe **Usage** section at the top illustrates the simplest version, which will\r\nreturn a simple menu. In most situations, though, you'll want to tell the menu\r\nwhat page you're on (so it can highlight that section). Or you might want to\r\nhook into some part of the process to add or modify elements, CSS classes, or\r\nHTML attributes.\r\n\r\n```php\r\n/**\r\n * Instantiate our Climber, and tell it where we are.\r\n */\r\n$Climber = new Climber(\r\n  new Tree(new Spotter\\WordPress(wp_get_nav_menu_items($menuID))),\r\n  get_permalink(get_the_ID()))   // This returns the URL for the current page.\r\n);\r\n\r\n/**\r\n * Open all links in a new window.\r\n */\r\n$Climber-\u003ehook(\r\n  'link',\r\n  function ($data) {\r\n    $data['attrs'][] = ['target', '_blank'];\r\n    return $data;\r\n  }\r\n);\r\n\r\n/**\r\n * Put the link after the submenu, instead of before.\r\n */\r\n$Climber-\u003ehook(\r\n  'itemOutput',\r\n  function($data) {\r\n    $data['format'] = '%2$s%1$s';\r\n    return $data;\r\n  }\r\n);\r\n\r\n/**\r\n * Print out our menu\r\n */\r\necho $Climber;\r\n```\r\n\r\nThe `Climber` object will return an HTML menu if treated as a string. Handy!\r\n\r\n## Hooks\r\n\r\nIn order to allow you to modify the content and behavior of the menu without\r\nhaving to extend the classes, Climber exposes several hooks. Each hook provides\r\na single variable—`$data`—to whatever function is hooked to them. The content of\r\nthat variable depends on the hook you attach it to. Generally, it is an array\r\nwith keyed values.\r\n\r\nAny function that is passed to a hook **must return something similar to the\r\n`$data` it recieved**. Otherwise things will break. See the **Show Me** section\r\nof this document, or the contents of `Climber::__construct()` for examples of\r\nhow to properly construct hooks.\r\n\r\n### `top`\r\n\r\nThis is the 'top' level of the navigation menu: The `\u003cnav\u003e` element. The array\r\nit provides includes:\r\n\r\n* `class` - *string* The CSS class(es) for this element.\r\n* `attrs` - *array* An array of HTML attributes for this element.\r\n* `tree` - *Tree* The Tree instance used for this menu.\r\n* `element` - *string* Format for `sprintf`, to be used in element generation.\r\n* `echo` - *boolean* Whether or not `Tree::element()` should echo the menu. Very\r\n  few reasons to change this.\r\n\r\n### `menu`\r\n\r\nThis hook is run for each submenu. If you want to target a *particular* submenu,\r\nyou'll need to run some sort of test in your passed function to target it. The\r\narray it provides includes:\r\n\r\n* `class` - *string* The CSS class(es) for this element.\r\n* `attrs` - *array* An array of HTML attributes for this element.\r\n* `level` - *integer* The current depth of this menu. Probably don't change it.\r\n* `element` - *string* Format for `sprintf`, to be used in element generation.\r\n* `bud` - *array* A full leaf, with all leaf data.\r\n\r\n### `item`\r\n\r\nAn individual `\u003cli\u003e` inside a `\u003cul\u003e`. Will ultimately contain a link to a part\r\nof your site, and possibly a submenu. The array it provides includes:\r\n\r\n* `class` - *string* The CSS class(es) for this element.\r\n* `attrs` - *array* An array of HTML attributes for this element.\r\n* `element` - *string* Format for `sprintf`, to be used in element generation.\r\n* `bud` - *array* A full leaf, with all leaf data.\r\n\r\n### `itemOutput`\r\n\r\nThis describes the actual *content* of `item`. It can be used to add new things\r\nto an `item` (i.e. a `\u003cbutton\u003e` to open a submenu) or to modify the order of the\r\nthings appearing in the `item`. The array it provides includes:\r\n\r\n* `format` - *string* A formatting string to be used in `vsprintf()`.\r\n* `args` - *array* An array of values that will be passed to `vsprintf()`. Their\r\n  order is important, because it corresponds to the `format`.\r\n\r\n### `link`\r\n\r\nThis is a link element in an `item`. The array it provides includes:\r\n\r\n* `link` - *string* The URL this link goes to.\r\n* `class` - *string* The CSS class(es) for this element.\r\n* `attrs` - *array* An array of HTML attributes for this element.\r\n* `element` - *string* Format for `sprintf`, to be used in element generation.\r\n* `content` - *string* The content of the link element. Usually the name of\r\n  whatever it links to, i.e. \"About\".\r\n\r\n## Settings\r\n\r\nThese settings will be applies to all elements of their type, so they can be\r\nuseful for setting styling or behavior across your menu. The built-in classes\r\nare formatted strings. You can change them all with one line by modifying\r\n`baseClass`. You can also override them completely, if you wish.\r\n\r\n| Name           | Type      | Default            |\r\n|----------------|-----------|--------------------|\r\n| `baseClass`    | string    | 'simpleMenu'       |\r\n| `topClass`     | string    | '%s'               |\r\n| `menuClass`    | string    | '%s__menu'         |\r\n| `itemClass`    | string    | '%s__item'         |\r\n| `linkClass`    | string    | '%s__link'         |\r\n| `topAttr`      | array     | `[]`               |\r\n| `menuAttr`     | array     | `[]`               |\r\n| `itemAttr`     | array     | `[]`               |\r\n| `linkAttr`     | array     | `[]`               |\r\n\r\nYou can override or set Climber-wide properties for element classes and\r\nattributes. Doing so is very simple:\r\n\r\n```php\r\n// For string-type properties...\r\n$Climber-\u003etopClass = 'newMenuClass';\r\n\r\n// For array-type properties...\r\n$Climber-\u003etopAttrs = ['target', '_blank'];\r\n```\r\n\r\nKeep in mind:\r\n\r\n* String-type properties are **overriden**.\r\n* Array-type properties are **appended**.\r\n\r\nThis means that if you want to supplement the existing classes, you would do\r\nthe following:\r\n\r\n```php\r\n$Climber-\u003etopClass .= ' newMenuClass';\r\n\r\n// \u003cul class=\"simpleMenu newMenuClass\"\u003e ...\r\n```\r\n\r\nYou can override and remove array-type property entries as well:\r\n\r\n```php\r\n// override\r\n$Climber-\u003etopAttr = ['data-star', 'wars'];\r\n$Climber-\u003etopAttr = ['data-star', 'trek'];\r\n\r\n// \u003cnav data-star=\"trek\"\u003e ...\r\n\r\n// remove\r\n$Climber-\u003etopAttr = ['data-star', 'wars'];\r\n$Climber-\u003etopAttr = ['data-star', false];\r\n\r\n//\u003cnav\u003e ...\r\n```\r\n\r\n## That's Too Complicated\r\n\r\nI agree! Fortunately there are some nice, clean, convenience functions to help\r\ndo what you want without filling your code up with all those Classes and stuff.\r\n\r\n*(This list will expand as necessary. If you think there should be a function\r\nhere that isn't here, open an issue or submit a PR to add it!)*\r\n\r\nAll conveninece functions begin with `pulley__`. They follow a convention\r\n(borrowed from WordPress): Functions named `get_something` will return a\r\nvalue, while functions simply named `something` will echo that value. For this\r\nreason, these functions often come in pairs. If they don't, it's probably\r\nbecause the `get_` version returns something that can't be echoed.\r\n\r\n### General\r\n\r\nThese functions will work in any context, but they require you to pass a valid\r\nSpotter.\r\n\r\n#### pulley__get_menu()\r\n\r\n```php\r\npulley__get_menu(\r\n  Spotter   $Spotter,\r\n  string    $currentUrl = null\r\n)\r\n```\r\n\r\n##### Arguments\r\n\r\n- **$Spotter** - _Spotter_\r\n  An instance of `Spotter` that applies to the context you're calling this in.\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_Climber | false_ A Climber object if successful, boolean `false` otherwise.\r\n\r\n_\r\n\r\n#### pulley__menu()\r\n\r\n```php\r\npulley__menu(\r\n  Spotter    $Spotter,\r\n  string     $currentUrl = null\r\n)\r\n```\r\n\r\nThe same as `pulley__get_menu()`, except that this will echo the menu\r\nautomatically.\r\n\r\n##### Arguments\r\n\r\n- **$Spotter** - _Spotter_\r\n  An instance of `Spotter` that applies to the context you're calling this in.\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_string | false_ HTML string of the menu if successful, boolean `false`\r\notherwise.\r\n\r\n\r\n### WordPress\r\n\r\nThese functions will only work with WordPress (in fact, Climber will only load\r\nthem if it believes it is being used in a WordPress context). Because Climber\r\ncomes packaged with a WordPress Spotter, there's no need to manually pass in\r\na Spotter!\r\n\r\n#### pulley__wp_get_menu()\r\n\r\n```php\r\npulley__wp_get_menu(\r\n  int|string|WP_Term     $menu,\r\n  string                 $currentUrl = null\r\n)\r\n```\r\n\r\nReturns a `Climber` for the `$menu` passed to it.\r\n\r\n##### Arguments\r\n\r\n- **$menu** - _int|string|WP_Term_\r\n  The value of `$menu` can a menu ID, slug, name, or object. More specifically,\r\n  it can be any value that [`wp_get_nav_menu_items()`](https://developer.wordpress.org/reference/functions/wp_get_nav_menu_items/)\r\n  would accept as a menu identifier.\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_Climber | false_ A Climber object if successful, boolean `false` otherwise.\r\n\r\n#### pulley__wp_menu()\r\n\r\n```php\r\npulley__wp_menu(\r\n  int|string|WP_Term     $menu,\r\n  string                 $currentUrl = null\r\n)\r\n```\r\nEchoing version of `pully__wp_get_menu()`.\r\n\r\n##### Arguments\r\n\r\n- **$menu** - _int|string|WP_Term_\r\n  The value of `$menu` can a menu ID, slug, name, or object. More specifically,\r\n  it can be any value that [`wp_get_nav_menu_items()`](https://developer.wordpress.org/reference/functions/wp_get_nav_menu_items/)\r\n  would accept as a menu identifier.\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_string | false_ HTML string of the menu if successful, boolean `false`\r\notherwise.\r\n\r\n#### pulley__wp_get_menu_by_location()\r\n\r\n```php\r\nfunction pulley__wp_get_menu_by_location(\r\n    string    $location,\r\n    string    $currentUrl = null\r\n)\r\n```\r\n\r\nGet a menu based on its location.\r\n\r\n##### Arguments\r\n\r\n- **$location** - _string_\r\n  The name of a $location, as defined in as defined in [`register_nav_menus()`](https://codex.wordpress.org/Function_Reference/register_nav_menus).\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_Climber | false_ A Climber object if successful, boolean `false` otherwise.\r\n\r\n#### pulley__wp_menu_by_location()\r\n\r\n```php\r\nfunction pulley__wp_menu_by_location(\r\n    string    $location,\r\n    string    $currentUrl = null\r\n)\r\n```\r\n\r\nSame as `pulley__wp_get_menu_by_location()`, just echoes.\r\n\r\n##### Arguments\r\n\r\n- **$location** - _string_\r\n  The name of a $location, as defined in as defined in [`register_nav_menus()`](https://codex.wordpress.org/Function_Reference/register_nav_menus).\r\n- **$currentUrl** - _string_\r\n  The URL you're currently act, for activating the correct Tree branch.\r\n  Default: `null`.\r\n\r\n##### Returns\r\n\r\n_string | false_ HTML string of the menu if successful, boolean `false`\r\notherwise.\r\n\r\n## That's Not Complicated Enough\r\n\r\nAll right, fair. Maybe you need something we haven't covered here: Maybe the menu you're passing to Climber only\r\nincludes the top level of your site, but you want parent items to still be highlighted when their children are being\r\nvisited. Since Climber is (by design) ignorant of your site's internal organization, it doesn't directly support this:\r\nClimber is only aware of the things its aware of.\r\n\r\nYou can, however, use a bit of logic to tell Climber what it should consider \"active\".\r\n\r\n### Set Current URL\r\n\r\nThe most common usage of Climber involves passing a URL to it on instantiation—either with the class directly, or\r\nthrough one of the helper functions. In either case, internally Climber is using the same method to activate the\r\ncurrent URL, and it's a method you can use too!\r\n\r\n`Climber::setCurrentUrl()` can be used to pass a URL directly to Climber. Climber will then look for a leaf (or leaves)\r\nthat point to that URL, and mark them as \"active\" (if you have a multi-level menu, it will also mark their ancestors as\r\nwell). Using it is very simple:\r\n\r\n```php\r\n$Climber = new Climber(\r\n  new Tree(new Spotter\\WordPress(wp_get_nav_menu_items($menuID)))\r\n);\r\n$Climber-\u003esetCurrentURL(get_permalink(get_the_ID())); // Now this is the current URL!\r\n```\r\n\r\nIf the Climber you're working with doesn't have that URL, it will do nothing.\r\n\r\nIt is also worth noting that Climber does not assume there can be only one active leaf: Running `setCurrentUrl()` does\r\nnot remove previously active leaves.\r\n\r\nAccess to `setCurrentUrl()` means that with some elbow grease, you can make Climber think you're on any page you like!\r\nUnfortunately, if you need to do this with a large number of URLs, `setCurrentUrl()` starts get a little less useful.\r\n\r\nThat's why there's another way!\r\n\r\n### Surveyor\r\n\r\nThe Surveyor class provides you with a simple way of writing lookups for URL patterns that you want to match particular\r\nURLs in Climber. It uses an array of regular expressions to determine what URLs match what. \r\n\r\nTo use Surveyor, instantiate it with an array of regular expressions and URLs, and then call `Surveyor::evaluateUrl()`:\r\n\r\n```php\r\n$Surveyor = new Surveyord([\r\n    ['/(?:stories\\/bedtime\\/[\\w_-]*)/', 'https://example.com/stories/bedtime/'],\r\n    ['/(?:stories(?:|\\/[\\w_-]*))/', 'https://example.com/stories/'],\r\n]);\r\necho $Surveyor-\u003eevaluateUrl('https://example.com/stories/bedtime/goodnight-moon');\r\n// Yields `https://example.com/stories/bedtime/`\r\n```\r\n\r\nPossible URLs are evaluated in sequence, and returned as soon as a match is found. If no match is found, then the URL\r\noriginally passe dto `evaluateUrl()` is returned. This means that you can easily use Surveyor inside a Climber call, and\r\nit will fall back to whatever Climber wants to do with that URL if no matches are found. Like so:\r\n\r\n```php\r\n$Surveyor = new Surveyord([\r\n    ['/(?:stories\\/bedtime\\/[\\w_-]*)/', 'https://example.com/stories/bedtime/'],\r\n    ['/(?:stories(?:|\\/[\\w_-]*))/', 'https://example.com/stories/'],\r\n]);\r\n$Climber = new Climber(\r\n  new Tree(new Spotter\\WordPress(wp_get_nav_menu_items($menuID))),\r\n  $Surveyor-\u003eevaluateUrl(get_permalink(get_the_ID()))\r\n);\r\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwaysblank%2Fclimber","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falwaysblank%2Fclimber","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwaysblank%2Fclimber/lists"}