rpsep2
rpsep2

Reputation: 3111

Dynamically add or remove input fields as they're required

I have a set-up of multiple file inputs, which I've styled by hiding the inputs, creating my own in html/css and then controlling the original inputs through jQuery to get the functionality.

I want to be able to add another input and associated HTML if all the inputs are filled, like this:

I tried creating a js fiddle with my code, but it doesn't seem to work through it, so here's a link to the test on my site: LINK

and the code, JS:

$('.file-browse').click(function(){
    var thisone = $(this).attr('id');
    $('input[name="input-'+ thisone+'"]').click();
});

$('input[type="file"]').on('change', function(){
    var thetext = $(this).val();
    var thetextsplit = thetext.split('\\').pop();
    var thisone = $(this).attr('name').split('-').pop();
    if($('div[id^="info-file"]').text() == thetextsplit){
        alert('you have already selected this file');
        $(this).replaceWith( $(this).val('').clone( true ) );
    }
    else{
        $('#info-'+ thisone).text(thetextsplit);
        $('#clear-'+ thisone).fadeIn(100);
    }
});

$('.file-clear').click(function(){
    var thisone = $(this).attr('id').split('-').pop();
    $('input[name="input'+ thisone +'"]').replaceWith( $('input[name="input'+ thisone +'"]').val('').clone( true ) );
    $('#info-'+ thisone).text('');
    $(this).fadeOut(100);
});

HTML:

    <div class="file-container">
      <div class="file-info" id="info-file1"></div>
      <div class="file-browse" id="file1">Browse</div>
      <div class="file-clear" id="clear-file1">X</div>
    </div>

    <div class="file-container">
      <div class="file-info" id="info-file2"></div>
      <div class="file-browse" id="file2">Browse</div>
      <div class="file-clear" id="clear-file2">X</div>
    </div>


    <div class="file-container">
      <div class="file-info" id="info-file3"></div>
      <div class="file-browse" id="file3">Browse</div>
      <div class="file-clear" id="clear-file3">X</div>
    </div>

    <form action="" method="POST" enctype="multipart/form-data">
  <input type='file' name="input-file1" class="file-input-hidden" />
      <input type='file' name="input-file2" class="file-input-hidden" />
      <input type='file' name="input-file3" class="file-input-hidden" />   
  <input type="submit" style="clear:both; float:left;"/>
    </form>

CSS:

.file-container{
clear:both;
float:left;
width:445px;
height:40px;
overflow:hidden;
padding:0;
margin:0;
}

.file-info{
float:left;
width:250px;
height:15px;
padding:5px;
border-radius:5px;
-webkit-border-radius:5px;
border:1px solid #95d2d2;
margin:0 20px 0 0;
font-family:Verdana, Geneva, sans-serif;
font-size:14px;
color:#373737;
overflow:hidden;
}

.file-browse{
float:left;
width:100px;
height:15px;
padding:3px 5px 8px 5px;
border-radius:5px;
-webkit-border-radius:5px;
border:1px solid #95d2d2;
background-color:#02b3b3;
color:#ffffff;
font-family:Arial, Gadget, sans-serif;
font-size:16px;
font-weight:bold;
letter-spacing:normal;
text-align:center;  
cursor:pointer;
}

.file-input-hidden{
opacity:0;
visibility:hidden;
float:left;
}

.file-clear{
display:none;
float: left;
width: 18px;
height: 18px;
padding: 5px;
color: #ffffff;
background-color: #CC0000;
font-family: Verdana, Geneva, sans-serif;
font-size: 14px;
font-weight: bold;
margin-left: 20px;
text-align: center;
border-radius: 5px;
cursor:pointer;
}

Sorry about the lengthy post! I wasn't sure of the best way to show my work so far.

Upvotes: 0

Views: 824

Answers (1)

Beetroot-Beetroot
Beetroot-Beetroot

Reputation: 18078

Rpsep,

I think things would be simpler starting with a template :

<div id="input_template" style="display:none;">
    <div class="file-container">
        <div class="file-info"></div>
        <div class="file-browse">Browse</div>
        <div class="file-clear">X</div>
    </div>
    <input type='file' name="input-file[]" class="file-input-hidden" />
</div>

Notes:

  • Only the outer container has an id; everything else has a class.
  • Don't worry about the input element being in the same container as the everything else; after cloning it will be put in the proper place.

Then attach functionality to the template :

$("#input_template").find('.file-browse').on('click', function() {
    $(this).closest(".file-container").data('hidden').click();
});

$("#input_template").find('.file-clear').on('click', function(){
    $container = $(this).closest(".file-container");
    $container.data('hidden').remove();
    $container.remove();
});

$('#input_template').find(".file-input-hidden").on('change', function(){
    var val = $(this).val().split('\\').pop();
    var $visible = $(this).data('visible');
    var $matches = $(".file-info").filter(function() {
        return $(this).text() === val;
    });
    if($matches.length > 0) {
        alert('You have already selected this file');
        var $clone = $this.val('').clone(true);
        $visible.data('hidden', $clone);
        $this.replaceWith( $clone );
    }
    else{
/*
        $visible.find(".file-info").text(val);
        $visible.find(".file-clear").fadeIn(100);
        makeInputSet();
*/
        var $fi = $visible.find(".file-info");
        if(!$fi.text()) 
            makeInputSet();
        $fi.text(val).end().find(".file-clear").fadeIn(100);
    }
});

It would probably be better to delegate the event handling to containers, but this code was easier to write, based on the code in the question.

Invoke the template with the following function, then create the first instance by calling it immediately:

function makeInputSet() {
    var $visible = $("#input_set").find(".file-container").clone(true, true).appendTo("#...");//appropriate selector required
    var $hidden = $("#input_set").find(".file-input-hidden").clone(true, true).appendTo("#...");//appropriate selector required
    $visible.data('hidden', $hidden);
    $hidden.data('visible', $visible);
}

makeInputSet();

Notes:

  • makeInputSet() is then called from inside the 'change' handler whenever an existing input field (and its visible counterpart) gets populated.
  • the two .data() calls ensure that the hidden element and the visible elements have the means of communicating with each other without having to string-handle ids or form rather inefficient closures.

Everything is untested, therfore probably needs debugging

Upvotes: 1

Related Questions