Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Not much to say, really. Just some simple code.

What I'm curious is if someone can reiimplement this without and javascript.



This was a tough puzzle to crack, but I've got it fully solved here:

https://jsfiddle.net/5c0ruh8s/10/

Works cross-browser, submits a POST request to record votes, and hides both vote buttons after submitting.

Each up/down arrow is an <input type="image">, part of a form that submits to a hidden iframe. When either goes :active, a CSS sibling selector sets the height of a floating div from 0px to 100% to cover up the vote arrows. There's no selector for "button that's been clicked once before", so I use a CSS transition that has a delay of 0s to expand the height and a delay of 99999999s (basically forever) to shrink the height back down again when the arrow button goes from active->inactive. While the arrow button is :active I use "pointer-events: none" to make sure the click goes through, but once the form is submitted and the button goes inactive the div becomes opaque to click events again, so the UI only allows a vote to go through once.

No JavaScript required!


I feel like there must be a simpler way than that.


That's what I thought too, at first. Some of the iterations I went through before reaching this point:

- Links for the up/down arrows with target=<hidden iframe>, that set "visibility: hidden" on :visited. But oh, only color-changing CSS properties are allowed for :visited selectors.

- Links for the up/down arrows with unicode ▲ and ▼ that set their color to the page background color on :visited, using a sibling selector to hide the other arrow as well once either has been clicked. But oh, sibling selectors are forbidden in conjunction with :visited selectors. Same for selecting nested elements inside a :visited link - only the simplest uses of :visited are allowed. One could wrap both arrows in the same <a> tag, but then there'd be no differentiating between an up/down vote.

- An invisible radio button next to each up/down arrow, with both wrapped inside a <label> so that clicking the arrow causes the radio button to become :checked. Use a sibling selector to hide the arrows when either radio button becomes checked. But oh, if you click a link inside a <label> it's not counted as marking the radio button as selected.

- An invisible radio button hidden under each vote up/down image. Each radio button is wrapped in an <a> tag that causes a hidden iframe to navigate to the voting link. The vote up/down buttons are "pointer-events: none", so trying to click on one really marks the radio button underneath as selected and triggers a link click. This works fine in Firefox, but in Chrome, marking a radio button as selected doesn't trigger a link click.


You could put each of them in an <iframe>.

(now to hide from the abuse, and hope my karma doesn't fall below the voting threshhold for suggesting this)


I thought of that, but then the arrows don't hide immediately after you click them as on HN - they'd still be visible until the request completes. So you'd end up with the same problem: hiding both arrows when either is clicked without using JS.

(I assume you mean putting each pair of arrows in an <iframe>, rather than each individual arrow.)


If it's a button, it'll be in the :focused state, you can use that to style it. Or you could use a CSS transition with a very very long time in one direction on the :active state.

And of course, you can use adjacency selectors with buttons, while you can't with :visited links.


That's exactly what I did, though :P

Check my first comment.


If scripts are turned off, the link just navigates to /vote?etc, which redirects you back to the thread. So it works already! It doesn't bring you back to where you were- but an anchor tag can fix that.

If you don't want to leave the page, iframes still work, right? So you can set the target to an invisible iframe, and the vote will go through. The arrow doesn't disappear, but I think you might be able to handle that with CSS:

http://www.inserthtml.com/2012/04/css-click-states/


This should work:

  <input type="checkbox" />
  <div style="background-image: url('http://voting-url.com')"></div>

  div {
    display: none;
  }

  input:checked + div {
    display: block;
  }
A background-image won't be requested unless it is visible. (This is why lots of peoples on-hover icons flicker if they don't spritemaps, SVGS or iconfonts).

Edit: Obviously you'd style it to make it pretty :p

Edit 2: For accessibility you'd probably want a visibly hidden - but visible to screen readers - anchor that votes.

Edit 3: Looks like chrome pre-fetches things even when they're not visible now. Presumably to fix all those flickery icons out there :(


This won't save any votes, but:

input, input:checked ~ label { display: none; }

---

<input type="radio" id="up">

<input type="radio" id="down">

<label for="up" class="show">Up</label>

<label for="down" class="hide">Down</label>


And if you wrap that inside a form that submit this to a url that respond with status 204, this will work without any js :-)


Amazing. I've been doing web stuff for the past decade, and had no idea you could do this.


Well there appear to be two things stopping this:

- Hiding two elements on the page (one can be hidden with the CSS :visited selector)

- Remaining with your position on the page whilst also sending a message to the server and without opening new windows.

So the only way to do the task is to send a full request to the server which would return you to the page with the buttons correctly disabled. Extra load on the server of course, and a bit of an annoying user experience, but doable - hence why this is the disabled fallback.


You could render the vote buttons in iframes, so just the iframe would reload. But you'd have to load N iframes for N vote buttons on page load. Not worth the performance hit.


> But you'd have to load N iframes for N vote buttons on page load.

No you don't. Use a FORM with a target of the iframe, make each arrow an <input type=image name="id#">

One form can cover all the arrows at once. Or you can have a form per set of arrows. Either way you only need one iframe.

Making it hide is a bit more complicated:

One way is have it hide on focus using visiblity, then set a very long transition in the CSS rules to unhide.


> But you'd have to load N iframes for N vote buttons on page load.

Not with the iframe srcdoc= attribute! That allows you to make an iframe that initially loads HTML you've specified inline.


1. I think you could hide both with some adjacent sibling selector trickery. (Assuming you can select a sibling of a :visited)

2. Could you set a background image on a visited to a 1x1 gif of the tracking url? Not sure if CSS lets you do that.

I know there are some limitations around :visited specifically to stop privacy snooping, but out so I can't look up the specifics right now.


Ah that might work


Without javascript you would just keep track of which comments the logged-in user voted on in the requested thread and not render those buttons.


HN has to do that anyway. The hide() and vote() functions only run on click. If you refresh the page, you'll see the response lacks anchor tags for comments/posts you've voted on. Those tags are replaced with a spacer image.


It already does work without javascript.

Or do you mean is there a way to asynchronously vote and hide the voting arrows without a page reload?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: