actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
<% content_for :style do %>
h2, p {
padding-left: 30px;
}
#route_table {
margin: 0;
border-collapse: collapse;
word-wrap:break-word;
table-layout: fixed;
width:100%;
}
#route_table thead tr {
border-bottom: 2px solid #ddd;
}
#route_table th {
padding-left: 30px;
text-align: left;
}
#route_table thead tr.bottom {
border-bottom: none;
}
#route_table thead tr.bottom th {
padding: 10px 30px;
line-height: 15px;
}
#route_table #search_container {
padding: 7px 30px;
}
#route_table thead tr th input#search {
-webkit-appearance: textfield;
width:100%;
}
#route_table thead th.http-verb {
width: 10%;
}
#route_table tbody tr {
border-bottom: 1px solid #ddd;
}
#route_table tbody tr:nth-child(odd) {
background: #f2f2f2;
}
#route_table tbody.exact_matches,
#route_table tbody.fuzzy_matches {
background-color: LightGoldenRodYellow;
border-bottom: solid 2px SlateGrey;
}
#route_table tbody.exact_matches tr,
#route_table tbody.fuzzy_matches tr {
background: none;
border-bottom: none;
}
#route_table td {
padding: 4px 30px;
}
@media (prefers-color-scheme: dark) {
#route_table tbody tr:nth-child(odd) {
background: #282828;
}
#route_table tbody.exact_matches tr,
#route_table tbody.fuzzy_matches tr {
background: DarkSlateGrey;
}
}
<% end %>
<table id='route_table'>
<thead>
<tr>
<th>Helper
(<%= link_to "Path", "#", 'data-route-helper' => '_path',
title: "Returns a relative path (without the http or domain)" %> /
<%= link_to "Url", "#", 'data-route-helper' => '_url',
title: "Returns an absolute URL (with the http and domain)" %>)
</th>
<th class="http-verb">HTTP Verb</th>
<th>Path</th>
<th>Controller#Action</th>
<th>Source Location</th>
</tr>
<tr>
<th colspan="5" id="search_container"><%= search_field(:query, nil, id: 'search', placeholder: "Search") %></th>
</tr>
</thead>
<tbody class='exact_matches' id='exact_matches'>
</tbody>
<tbody class='fuzzy_matches' id='fuzzy_matches'>
</tbody>
<tbody>
<%= yield %>
</tbody>
</table>
<script>
// support forEach iterator on NodeList
NodeList.prototype.forEach = Array.prototype.forEach;
// Enables query search functionality
function setupMatchingRoutes() {
// Check if there are any matched results in a section
function checkNoMatch(section, trElement) {
if (section.children.length <= 1) {
section.appendChild(trElement);
}
}
// get JSON from URL and invoke callback with result
function getJSON(url, success) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (this.status == 200)
success(JSON.parse(this.response));
};
xhr.send();
}
function delayedKeyup(input, callback) {
var timeout;
input.onkeyup = function(){
if (timeout) clearTimeout(timeout);
timeout = setTimeout(callback, 300);
}
}
// remove params or fragments
function sanitizeQuery(query) {
return query.replace(/[#?].*/, '');
}
var pathElements = document.querySelectorAll('#route_table [data-route-path]'),
searchElem = document.querySelector('#search'),
exactSection = document.querySelector('#exact_matches'),
fuzzySection = document.querySelector('#fuzzy_matches');
// Remove matches when no search value is present
searchElem.onblur = function(e) {
if (searchElem.value === "") {
exactSection.innerHTML = "";
fuzzySection.innerHTML = "";
}
}
function buildTr(string) {
var tr = document.createElement('tr');
var th = document.createElement('th');
th.setAttribute('colspan', 4);
tr.appendChild(th);
th.innerText = string;
return tr;
}
// On key press perform a search for matching paths
delayedKeyup(searchElem, function() {
var query = sanitizeQuery(searchElem.value),
defaultExactMatch = buildTr("Routes matching '" + query + "':"),
defaultFuzzyMatch = buildTr("Routes containing '" + query + "':"),
noExactMatch = buildTr('No exact matches found'),
noFuzzyMatch = buildTr('No fuzzy matches found');
if (!query)
return searchElem.onblur();
getJSON('/rails/info/routes?query=' + query, function(matches){
// Clear out results section
exactSection.replaceChildren(defaultExactMatch);
fuzzySection.replaceChildren(defaultFuzzyMatch);
// Display exact matches and fuzzy matches
pathElements.forEach(function(elem) {
var elemPath = elem.getAttribute('data-route-path');
if (matches['exact'].indexOf(elemPath) != -1)
exactSection.appendChild(elem.parentNode.cloneNode(true));
if (matches['fuzzy'].indexOf(elemPath) != -1)
fuzzySection.appendChild(elem.parentNode.cloneNode(true));
})
// Display 'No Matches' message when no matches are found
checkNoMatch(exactSection, noExactMatch);
checkNoMatch(fuzzySection, noFuzzyMatch);
})
})
}
// Enables functionality to toggle between `_path` and `_url` helper suffixes
function setupRouteToggleHelperLinks() {
// Sets content for each element
function setValOn(elems, val) {
elems.forEach(function(elem) {
elem.innerHTML = val;
});
}
// Sets onClick event for each element
function onClick(elems, func) {
elems.forEach(function(elem) {
elem.onclick = func;
});
}
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
onClick(toggleLinks, function(){
var helperTxt = this.getAttribute("data-route-helper"),
helperElems = document.querySelectorAll('[data-route-name] span.helper');
setValOn(helperElems, helperTxt);
});
}
setupMatchingRoutes();
setupRouteToggleHelperLinks();
// Focus the search input after page has loaded
document.getElementById('search').focus();
</script>