Adding buttons using onclick function. Need to limit buttons to 10 HELP

<Div ID=files>  
  <br>
    
  File 1 : <input type="file" name="File1"><br>    
  
</Div>
<br> 

<Input Type=Button Value="Add a file" OnClick=return(Expand()) 
 Style="border=0;background=yellow;cursor:hand"><br>
    
<p align="center"> 
  
<br>
   
<input type="submit" name="submit" value="Upload Images">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   
</Form>

<% trying to control this area%>
<%if nfiles reaches 10 then function is disabled%>

<Script>
//Expand form with a new File fields if needed.
var nfiles = 1;
function Expand(){
  nfiles++
  var adh = '<BR> File '+nfiles+' : <input type="file" name="File'+nfiles+'">';
  files.insertAdjacentHTML('BeforeEnd',adh);
  return false;
}
</Script>

Hi,

Maybe I’m missing something obvious, but couldn’t you do this:

var nfiles = 1;
function Expand(){
  if (nfiles <= 10){
    var adh = '<BR> File '+nfiles+' : <input type="file" name="File'+nfiles+'">';
    files.insertAdjacentHTML('BeforeEnd',adh);
    nfiles++
  }
  return false;
}

If you must use innerHTML:

<!DOCTYPE html>
<head>
<style>
img{ width:30px; height:30px; border:solid 4px red}
</style>
</head>
<html>

<form>

<div ID=files>
  <br>
  File 1 : <input type="file" name="File1"><br>

</div>
<br>

<Input Type=Button Value="Add a file" OnClick="this.disabled = !Expand( 10 )"
 style="border=0;background=yellow;cursor:hand">
 <br>
 <p align="center">
<br>

<input type="submit" name="submit" value="Upload Images">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</form>

<% trying to control this area%>
<%if nfiles reaches 10 then function is disabled%>

<script>

function Expand( limit )
{
  var formDiv = document.getElementById( 'files' ),
      nFiles = formDiv.getElementsByTagName( 'input' ).length;

  if( nFiles < limit )
  {
    var adh = '<BR> File ' + ( nFiles + 1 ) + ' : <input type="file" name="File'+ (nFiles + 1) + '"><br>';

    formDiv.insertAdjacentHTML( 'beforeend', adh );
  }

  return formDiv.getElementsByTagName( 'input' ).length < limit;
}
</script>

</body>
</html>

Hi Ali,

Just for my personal interest, why do you consider it bad practice to use innerHTML?

Thanks Guys, this worked great :slight_smile:

It allows more opportunity to introduce syntax errors that may not be easily detected.

So would you recommend using createElement, createTextNode and appendChild and co?

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Unbenanntes Dokument</title>
    <style>
      .center{ margin:0 auto; }
      #addButton{ cursor:pointer }
      #submitButton, #addButton { margin: 10px 0; display: block; }
      #files > div { margin: 15px 0; }
    </style>
  </head>
  
  <body>
    <form>
      <div id="files">  
        <div>
          File 1:
          <input type="file" name="file_1">
        </div>
      </div>
      <input type="button" id="addButton" value="Add a file" />
      <input type="submit" id="submitButton" name="submit" value="Upload Images">
    </form>
    
    <script>
      var addButton = document.getElementById( 'addButton' );
      addButton.onclick = function()
      {
        this.disabled = !expand( 10 );
      }
      
      function expand( limit )
      {
        var formDiv = document.getElementById( 'files' ),
            nFiles = formDiv.getElementsByTagName( 'input' ).length;
        
        if( nFiles < limit )    
        {
          var div = document.createElement( 'div' ),
              input = document.createElement( 'input' );
              
          div.appendChild(document.createTextNode( 'File ' + (nFiles + 1) + ': ' ));
          formDiv.appendChild( div );
          
          input.type = 'file';
          input.name = 'file_' + nFiles + 1;
          div.appendChild( input );
          
          formDiv.appendChild( div );
        }
        
        return formDiv.getElementsByTagName( 'input' ).length < limit;
      }
    </script>
  </body>
</html>

Historically there have been some reservations about using innerHTML because it’s not supported with XHTML code, and innerHTML is unofficial - it’s not backed by specs of any sort.

Since IE refused to support XHTML, that cause for the issue is now moot, and we now have HTML5 specs for innerHTML.

I don’t favour using innerHTML in my own code, perhaps because I’m a crotchety old bugger who aims to do things that are right (morally), but using innerHTML certainly is more and more tempting now.

There is only one good reason that I know of now to not use innerHTML, and that’s due to it bring HTML code in to your JavaScript, for which a separation of concerns should be maintained if at all possible.

Yes, but only within a function written to simplify the whole process required.

I would recommend using a template. Where the HTML structure is maintained as hidden elements on the page, and the script retrieves that template and uses it as a basis for which to update the page.

We will soon have a <template> tag too. A good read about them can be found in this article on HTML’s New Template Tag

As a measure of backwards-compatibility, we can also use a smattering of CSS to hide such templates when making use of them now with our code.


template { display: none; }

I’ll come up with an example of using them with the above code, sometime tomorrow as I have to shortly head away offline.

In fact, I can do it now.

We can add a template for the files section, anywhere on the HTML page but I would choose to place it just at the start of the <body> tag


<body>
<template id="fileinput">File <span></span> : <input type="file" name="File1"><br></template>
...

The empty span allows us to easily find it from the script, and place what we need in to that location.

For backwards compatibility with web browser that don’t know how to handle templates, we have some CSS and JavaScript that hide such templates and a polyfill to help set things up:


<script>
    // Shim so we can style in IE6/7/8
    document.createElement('template');
</script>


template { display: none; }


<script src="template-polyfill.js"></script>

which contains the following code:


/* POLYFILL */

(function templatePolyfill(d) {
    if('content' in d.createElement('template')) {
        return false;
    }

    var qPlates = d.getElementsByTagName('template'),
        plateLen = qPlates.length,
        elPlate,
        qContent,
        contentLen,
        docContent;

    for(var x=0; x<plateLen; ++x) {
        elPlate = qPlates[x];
        qContent = elPlate.childNodes;
        contentLen = qContent.length;
        docContent = d.createDocumentFragment();

        while(qContent[0]) {
            docContent.appendChild(qContent[0]);
        }

        elPlate.content = docContent;
    }
})(document);

And we are now ready to start making use of that template.

Here’s a simple function that retrieves the template and creates some new content from it:


function createFileInput(num) {
    var template = document.getElementById('fileinput');

    fileInput = template.content.cloneNode(true);
    fileInput.querySelector('span').appendChild(document.createTextNode(num));
    fileInput.querySelector('input').name = 'File' + (num);
    return fileInput;
}

And we can use that from the script as follows:


filesDiv.appendChild(createFileInput(nFiles + 1));

@Pullo or @Logic-Ali - do you feel like experimenting with that while I’m away?

Hi Paul,

Great answer.
I knew (vaguely) about the new <template> tag, but it was fun to use it in context.

Aye, why not?
JavaScript is inline, so that the OP can copy and paste the code if desired.

<!DOCTYPE HTML>
<html>
  <head>
    <!--createElement, createTextNode and appendChild-->
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Dynamically add buttons (using template)</title>
    <script>
      // Shim so we can style in IE6/7/8
      document.createElement('template');
    </script>
    <style>
      .center{ margin:0 auto; }
      #addButton{ cursor:pointer }
      #submitButton, #addButton { margin: 10px 0; display: block; }
      #files > div { margin: 15px 0; }
      template { display: none; }
    </style>
  </head>
  
  <body>
    <form>
      <div id="files">  
        <div>
          File 1:
          <input type="file" name="file_1">
        </div>
      </div>
      <input type="button" id="addButton" value="Add a file" />
      <input type="submit" id="submitButton" name="submit" value="Upload Images">
    </form>
    
    <template id="fileinput">
      <div>
        <span>File </span>
        <input type="file">
      </div>
    </template>
    
    <script>
      (function templatePolyfill(d) {
        if('content' in d.createElement('template')) {
          return false;
        }
        
        var qPlates = d.getElementsByTagName('template'),
            plateLen = qPlates.length,
            elPlate,
            qContent,
            contentLen,
            docContent;
        
        for(var x=0; x<plateLen; ++x) {
          elPlate = qPlates[x];
          qContent = elPlate.childNodes;
          contentLen = qContent.length;
          docContent = d.createDocumentFragment();
          
          while(qContent[0]) {
            docContent.appendChild(qContent[0]);
          }
          
          elPlate.content = docContent;
        }
      })(document);

      var addButton = document.getElementById('addButton');
      addButton.onclick = function(){
        this.disabled = !expand(10);
      }
      
      function createFileInput(num) {
          var template = document.getElementById('fileinput');
       
          fileInput = template.content.cloneNode(true);
          fileInput.querySelector('span').appendChild(document.createTextNode(num + ':'));
          fileInput.querySelector('input').name = 'File' + (num);
          return fileInput;
      }
      
      function expand(limit){
        var formDiv = document.getElementById('files'),
            nFiles = formDiv.getElementsByTagName('input').length;
        
        if(nFiles < limit) {
          formDiv.appendChild(createFileInput(nFiles + 1));
        }
        
        return formDiv.getElementsByTagName('input').length < limit;
      }
    </script>
  </body>
</html>

This works down to IE8, but IE7 balks, as it doesn’t like the use of querySelector.
As per our JS challenge I was thinking about the best way to polyfill this functionality.

I tried including Sizzle (http://cdnjs.cloudflare.com/ajax/libs/sizzle/1.10.1/sizzle.min.js), but in this case you have to pass Sizzle the context as a seperate parameter

e.g.

Sizzle('span', fileInput)

Which means that things start to get a bit messy.
Can you think of a more elegant way to make this work in IE7?

The following is a good polyfill for querySelector and querySelectorAll support:


// Selectors API Level 1 (http://www.w3.org/TR/selectors-api/)
  // http://ajaxian.com/archives/creating-a-queryselector-for-ie-that-runs-at-native-speed
  //
  if (!document.querySelectorAll) {
    document.querySelectorAll = function (selectors) {
      var style = document.createElement('style'), elements = [], element;
      document.documentElement.firstChild.appendChild(style);
      document._qsa = [];

      style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
      window.scrollBy(0, 0);
      style.parentNode.removeChild(style);

      while (document._qsa.length) {
        element = document._qsa.shift();
        element.style.removeAttribute('x-qsa');
        elements.push(element);
      }
      document._qsa = null;
      return elements;
    };
  }

  if (!document.querySelector) {
    document.querySelector = function (selectors) {
      var elements = document.querySelectorAll(selectors);
      return (elements.length) ? elements[0] : null;
    };
  }

  if (!document.getElementsByClassName) {
    document.getElementsByClassName = function (classNames) {
      classNames = String(classNames).replace(/^|\\s+/g, '.');
      return document.querySelectorAll(classNames);
    };
  }

Something that’s also worth considering is the aspect of how much additional code to include for the sake of backwards compatibility.

It’s also possible to easily achieve things without needing querySelector, for example by using getElementsByTagName:


function createFileInput(num) {
    var template = document.getElementById('fileinput'),
        fileInput = template.content.cloneNode(true),
        span = fileInput.getElementsByTagName('span')[0],
        input = fileInput.getElementsByTagName('input')[0];

    span.appendChild(document.createTextNode(num + ':'));
    input.name = 'File' + (num);
    return fileInput;
}

Although, more complex situations are easier when using querySelector, so that can be a good reason to include the polyfill.

Hi Paul,

I’ve been playing about with this for a while now, but unfortunately I cannot get the querySelectorAll pollyfill to work.
I know it’s not strictly necessary, as the same functionality could be acheived using getElementsByTagName, but nonetheless I would be interested to hear your thoughts.

The exact error I receive is:

SCRIPT438: Object doesn't support property or method "querySelector"
HTML5_template.html, Line 112 Character 11

in the emulated version of IE7 (real version IE10).

<!DOCTYPE HTML>
<html>
  <head>
    <!--createElement, createTextNode and appendChild-->
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Dynamically add buttons (using template)</title>
    <script>
      // Shim so we can style in IE6/7/8
      document.createElement('template');
    </script>
    <style>
      .center{ margin:0 auto; }
      #addButton{ cursor:pointer }
      #submitButton, #addButton { margin: 10px 0; display: block; }
      #files > div { margin: 15px 0; }
      template { display: none; }
    </style>
  </head>
  
  <body>
    <form>
      <div id="files">  
        <div>
          File 1:
          <input type="file" name="file_1">
        </div>
      </div>
      <input type="button" id="addButton" value="Add a file" />
      <input type="submit" id="submitButton" name="submit" value="Upload Images">
    </form>
    
    <template id="fileinput">
      <div>
        <span>File </span>
        <input type="file">
      </div>
    </template>
    
    <script>
      // Selectors API Level 1 (http://www.w3.org/TR/selectors-api/)
      // http://ajaxian.com/archives/creating-a-queryselector-for-ie-that-runs-at-native-speed
      //
      if (!document.querySelectorAll) {
        document.querySelectorAll = function (selectors) {
          var style = document.createElement('style'), elements = [], element;
          document.documentElement.firstChild.appendChild(style);
          document._qsa = [];
     
          style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
          window.scrollBy(0, 0);
          style.parentNode.removeChild(style);
     
          while (document._qsa.length) {
            element = document._qsa.shift();
            element.style.removeAttribute('x-qsa');
            elements.push(element);
          }
          document._qsa = null;
          return elements;
        };
      }
     
      if (!document.querySelector) {
        document.querySelector = function (selectors) {
          var elements = document.querySelectorAll(selectors);
          return (elements.length) ? elements[0] : null;
        };
      }
     
      if (!document.getElementsByClassName) {
        document.getElementsByClassName = function (classNames) {
          classNames = String(classNames).replace(/^|\\s+/g, '.');
          return document.querySelectorAll(classNames);
        };
      }    
        
      (function templatePolyfill(d) {
        if('content' in d.createElement('template')) {
          return false;
        }
        
        var qPlates = d.getElementsByTagName('template'),
            plateLen = qPlates.length,
            elPlate,
            qContent,
            contentLen,
            docContent;
        
        for(var x=0; x<plateLen; ++x) {
          elPlate = qPlates[x];
          qContent = elPlate.childNodes;
          contentLen = qContent.length;
          docContent = d.createDocumentFragment();
          
          while(qContent[0]) {
            docContent.appendChild(qContent[0]);
          }
          
          elPlate.content = docContent;
        }
      })(document);

      var addButton = document.getElementById('addButton');
      addButton.onclick = function(){
        this.disabled = !expand(10);
      }
      
      function createFileInput(num) {
          var template = document.getElementById('fileinput');
       
          fileInput = template.content.cloneNode(true);
          fileInput.querySelector('span').appendChild(document.createTextNode(num + ':'));
          fileInput.querySelector('input').name = 'File' + (num);
          return fileInput;
      }
      
      function expand(limit){
        var formDiv = document.getElementById('files'),
            nFiles = formDiv.getElementsByTagName('input').length;
        
        if(nFiles < limit) {
          formDiv.appendChild(createFileInput(nFiles + 1));
        }
        
        return formDiv.getElementsByTagName('input').length < limit;
      }
    </script>
  </body>
</html>

Any ideas?

Yes, that’s happening because IE7 doesn’t have an Elements object from which to inherit things like querySelector.
You may find it easier to experiment in a more recent IE with the F12 debug screen set as IE7.

I suspect that the only solution for compatibility there is to refrain from using querySelector on partial searches, and use things like getElementsByTagName instead.

For example:


fileInput.getElementsByTagName('span')[0].appendChild(document.createTextNode(num + ':'));
fileInput.getElementsByTagName('input')[0].name = 'File' + (num);

It may also be feasible to use a separate function for the task, such as get.one(fileInput, ‘span’, ‘tag’) which can attempt the querySelector method, and if it fails then attempt other compatible methods.

It’s always the case though that the further back you go, the more difficult it is to retain modern techniques, and have to resort to facades behind which the work gets done.

Due to how IE7 and earlier have troubles with querySelector on unattached elements, I thought that I had found an effective way by adding the content from the template to the page first, then querying the page for that added section. But to no avail.

If you want compatibility with IE7 and earlier, we are restricted to using querySelector to retrieve elements from the page, and should only use querySelector to attain references from the already existing document.

Which is fine - getElementsByTagName and other methods are just perfect for editing the clones template.

Thanks!
I’ve just been going over this now and it has helped me formulate my thoughts a bit more clearly on backwards compatibility.