Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a CSS selector to detect if an input has a text file selected?

I'm trying to have some file inputs, and have them only show up if the previous one has been filled. This can use css 3 as well.

like image 974
manixrock Avatar asked Nov 13 '10 22:11

manixrock


2 Answers

An example worth thousands words: Display X input, one at a time

The idea is simple, if an input set as required is empty, it's invalid. From there, all you have to do is set all input as required and use the :invalid pseudo class. Should work great with label too.

input:invalid~input:invalid {
  display: none;
}
<input type="file" required>
<input type="file" required>
<input type="file" required>
like image 101
gkr Avatar answered Oct 25 '22 22:10

gkr


To expand on Yi Jiang's comment, selectors against the "value" attribute won't notice changes to the "value" property. The "value" attribute is bound to the "defaultValue" property, while the "value" property isn't bound to any attribute (thanks to porneL for pointing this out).

Note there's a similar relationship with the "checked" attribute and "defaultChecked" and "checked" properties; if you use an attribute selector [checked] rather than the pseudo-class :checked, you won't see style change when a checkbox's state changes. Unlike the "checked" family, "value" doesn't have a corresponding pseudo-class that you could use.

Try the following test page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Dynamic attribute selectors</title>
    <style type="text/css">
     input:not([value]), div:not([value]) {
       background-color: #F88;
     }

     input[value], div[value] {
       border: 5px solid #8F8;
     }
     input[value=""], div[value=""] {
       border: 5px solid #F8F;
     }

     input:not([value=""]), div:not([value=""]) {
       color: blue;
       border-style: dashed;
     }

     *.big {
         font-size: 200%;
     }
    </style>
    <script>
      function getElt() {
          var id=prompt("Enter ID of element", "d1");
          if (id) {
              return document.getElementById(id);
          } else {
              return {className: ''};
          }
      }

      function embiggen() {
          getElt().className="big";
          return false;
      }

      function smallify() {
          getElt().className="";
          return false;
      }
    </script>
  </head>

  <body>
    <form method="post"  enctype="multipart/form-data"> 
      <div id="d1">no value</div>
      <div id="d2" value="">empty value</div>
      <div id="d3" value="some">some value</div>
      <p><label for="foo">foo:</label> <input name="foo" id="foo" /></p> 
      <p><label for="bam">bam:</label> <input name="bam" id="bam" value="bug-AWWK" /></p> 
      <p><label for="file">File to upload:</label> <input type="file" name="file" id="file" onchange="setValueAttr(this)"/></p>
      <input type="button" value="Embiggen" onclick="return embiggen()" />
      <input type="button" value="Smallify" onclick="return smallify()" />
    </body>
</html>

Changing the value of anything and the style won't change. Change the class of anything and the style will change. If you add the following JS function and bind it to a change event on an input, the background style will change.

      function bindValue(elt) {
          var oldVal=elt.getAttribute('value');
          elt.setAttribute('value', elt.value);
          var newVal=elt.getAttribute('value');
          if (oldVal != newVal) {
              alert('Had to change value from "'+oldVal+'" to "'+newVal+'"');
          }
      }

This binds the "value" property to the "value" attribute, so updates to the former by user input will propagate to the latter (programmatically setting the "value" property won't cause a change event).

In examining the JS properties of file inputs before and after (by use of the following script), the only one with an appreciable change was "value". From this, I doubt there are any other HTML attributes that change and could hence be used in an attribute selector.

<script>
  var file = {blank: {}, diff: {}};
  var fInput = document.getElementById('file');
  for (p in fInput) {
    try {
      file.blank[p] = fInput[p];
    } catch (err) {
      file.blank[p] = "Error: setting '"+p+"' resulted in '"+err+"'";          
    }
  }

  function fileDiff() {
    for (p in fInput) {
      try {
        if (file.blank[p] != fInput[p]) {
          file.diff[p] = {orig: file.blank[p], now: fInput[p]};
        }
      } catch (err) {
        //file.diff[p] = "Error: accessing '"+p+"' resulted in '"+err+"'";          
      }
    }

  }

  if (fInput.addEventListener) {
    fInput.addEventListener('change', fileDiff, false);
  } else if (fInput.attachEvent) {
    fInput.attachEvent('onchange', fileDiff);
  } else {
    fInput.onchange = fileDiff;
  }
</script>

You can hack together something using a link to a non-existent fragment and the :visited pseudo class, but it's quite egregious.

<style>
 a input {
   display: none;
 }
 :not(a) + a input,
 a:visited + a input
 {
   display: block /* or "inline" */ ;
 }

</style>
...
<a href="#asuhacrpbt"><input type="file" ... /></a>
<a href="#cmupbnhhpw"><input type="file" ... /></a>
<a href="#mcltahcrlh"><input type="file" ... /></a>

You'd need to generate unvisited targets for the links every time the page is loaded. Since you'd have to do it server side, you couldn't do this with complete certainty, though you could get the probability of generating a previously visited target arbitrarily close to 0. It also doesn't work on all browsers, such as Safari. I suspect this is due to the following from CSS2 and CSS3:

Note: It is possible for style sheet authors to abuse the :link and :visited pseudo-classes to determine which sites a user has visited without the user's consent.

UAs may therefore treat all links as unvisited links, or implement other measures to preserve the user's privacy while rendering visited and unvisited links differently.

You might be able to hack something together using other selectors on other elements, but I suspect this can't be done cleanly.

like image 43
outis Avatar answered Oct 25 '22 23:10

outis