I inherited some markup where a series of top-level <a> elements each contain a set of <span> elements, and using CSS, they're rendered as clickable blocks in a list, like this:
.list {
display: inline-flex;
flex-flow: column nowrap;
font: 14px Arial;
}
.list a {
display: flex;
flex-flow: column nowrap;
align-items: stretch;
border: 1px solid #CCC;
border-bottom: none;
background: #FFF;
padding: 4px 10px;
text-decoration: none;
color: #000;
}
.list a:last-child {
border-bottom: 1px solid #CCC;
}
.list a:hover {
background: #CDE;
}
.list a .name {
font-weight: bold;
}
.list a .secondary {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
color: #678;
font-size: 85%;
padding-top: 2px;
}
.list a .address {
padding-right: 16px;
padding-left: 8px;
}
.list a .company-id {
color: #B88;
cursor: text;
padding-left: 4px;
padding-right: 4px;
margin-right: -4px;
}
<div class="list">
<a href="/link/to/company/10101">
<span class="name">Alice Jones & Co.</span>
<span class="secondary">
<span class="address">55 Oak Street, Anytown 15151</span>
<span class="company-id">#10101</span>
</span>
</a>
<a href="/link/to/company/12345">
<span class="name">John Smith Inc.</span>
<span class="secondary">
<span class="address">123 Main Street, Anytown 15151</span>
<span class="company-id">#12345</span>
</span>
</a>
<a href="/link/to/company/20123">
<span class="name">Bob Johnson LLC</span>
<span class="secondary">
<span class="address">17 Spruce Street, Anytown 15152</span>
<span class="company-id">#20123</span>
</span>
</a>
</div>
The Request
A product owner asked me the other day if I could make the company IDs not clickable — our users want to be able to select the text of the IDs for copy-and-paste. Fine, I thought: Turn each <a> element into an <li> like it should be anyway, add a little JavaScript to follow the links on clicks, and ignore clicks on the company IDs, and done.
Then I learned there's another user requirement — that the <a> elements must also be middle-clickable or Ctrl-clickable to open them in a new tab. I intended to tweak the JavaScript to invoke window.open() if the Ctrl-key or middle-mouse button was down, but it seems that ad-blockers and browser popup blockers get in the way of that working reliably: The <a> element needs to be a real <a> element, and its events must be left more-or-less untouched. But that means that the <a> will capture every bubbling event on its content, including events I'd prefer it not touch, like the click-and-drag (and double-click) events on the company ID.
And since the list has a flexible layout, I can't put the company ID element outside the <a> element, and then appear to make it part of the same block using position or margin tricks: The spacing won't work, because the IDs vary pretty widely in length (from 1 to 129370-5486).
tl;dr: I need a child element to exist inside an <a> element for layout — but it needs to exist outside the same element for behavior.
Requirements
For a valid solution, I have to meet these requirements:
- The full
<a>element must be clickable as a link, except for the company ID<span>. - The full
<a>element can be middle-clicked to open it in a new tab, except for the company ID<span>. - A user must be able to click-and-drag on the company ID
<span>to select and copy its text. - A user must be able to double-click on the company ID
<span>to select and copy its text. - The layout must be flexible, allowing text spans of arbitrary length, and collapsing to the narrowest overall width.
- The solution must work in modern evergreen browsers (i.e., Chrome, Firefox, Edge — no old-IE compatibility required!).
Beyond that, the sky's the limit: Dependencies, no dependencies, add/tweak the CSS, add some JS, change the markup — as long as those six bullet points are met, you can do whatever you want.
My Best Solution
I've tried an awful lot of JavaScript event-capturing tricks so far, most of which were failures. The best working solution I've found involves no JS at all: I include the company ID in the markup twice — once inside the <a> with visibility:hidden for layout purposes, and then again in the markup after the </a>, with a position:relative-containing <li> element around all of it, and position:absolute / bottom: / right: on the visible, selectable <span>. But it seems like there ought to be a better way that doesn't involve mutating the markup; and if the product owners ever want more text in each box, or a slightly different layout, my solution is not likely to adjust to those changes very well.
So do you have any better ideas than I have for pulling off normal, selectable text elements inside an otherwise-clickable <a> element parent?