Search code examples
javascriptasp.net-mvcformsinputhtml-helper

An ASP.NET MVC with a view with a dynamic number of inputs


Ok, this is a complicated question and I'm so new at this, maybe I don't even know how to ask it properly.

I'm building an app using ASP.NET MVC. I have a view that contains a form that has an textbox input that asks for a user to name a recipient to their estate for a will. Below the input, I want a button ("Add another recipient") that adds another input to the form.

Here's how I've mocked this up using Javascript (Click "Yes" and then click "Cash" to see what I'm talking about): https://jsfiddle.net/cinerobert/409k27ot/7/

<p>Would you like to leave specific gift in your will?</p>
<p>
  <button onclick="q1AnswerYes()">Yes</button>
  <button onclick="q1AnswerNo()">No</button>
</p>
<br />
<!-- If answer to Q1 is Yes show this block -->
<div id="q1AnswerYesBlock" style="display:none; text-align:center;
margin:auto;">
  <div>What specific gifts would you like to leave?</div>
  <hr>
  <div>
    <button onclick="q2AnswerCash('q2AnswerCashDisp')">Cash</button>
    <button>Car</button>
    <button>Real Estate</button>
    <button>Jewelry</button>
    <button>Other</button>
  </div>
  <div id="q2AnswerCashDisp">
    <!-- Recipient forms get added here -->
  </div>
  <div style="text-align:center;
margin:auto; display:inline">
    <button id="addCashRecipient" style="display:none" onclick="addCashRecipientForm('q2AnswerCashDisp')">Add another recipient</button>
  </div>
</div>

<!-- If answer to Q1 is No show this block -->
<div id="q1AnswerNoBlock" style="display:none">
  <p>Some other crap</p>
</div>
<div id="testFormHTML" style="display:none">
  <form id="testFormId" method="get" style="border-syle:none">
    <fieldset id="fieldsetid" style="border-style:none">
      <div class="formPrompt">
        Amount:
      </div>
      <input type="text">
      <div class="formPrompt">
        Recipient:
      </div>
      <input onblur="submit()" type="text" name="recipient">
      <input style="display:none" class="testFormParamInputId" name="testFormParam" value="">
      <br>
      <br>
    </fieldset>
  </form>
</div>

<script>
var cashRecipientNum = 1;
function q1AnswerYes() {
  document.getElementById("q1AnswerYesBlock").style.display = "block";
  document.getElementById("q1AnswerNoBlock").style.display = "none";
}

function q1AnswerNo() {
  document.getElementById("q1AnswerYesBlock").style.display = "none";
  document.getElementById("q1AnswerNoBlock").style.display = "block";
}

function q2AnswerCash(blockId) {
  if (cashRecipientNum == 1) {
    addForm(blockId, "testForm", cashRecipientNum++);
    document.getElementById("addCashRecipient").style = "display:inline";
  };
}

function addCashRecipientForm(blockId) {
  addForm(blockId, "testForm", cashRecipientNum++);
}

// Function to add a new form with unique action argument to the document.
function addForm(blockId, formArg, numInstance) {
  var formHTML = formArg + "HTML";
  var formId = formArg + "Id";
  var formParamInputId = formArg + "ParamInputId";
  var newFormId = formArg + "Id" + numInstance;
  var div = document.getElementById(formId),
    clone = div.cloneNode(true); // true means clone all childNodes and all eventhandlers
  clone.id = newFormId;
  clone.style = "border-style:none";
  document.getElementById(blockId).appendChild(clone);
  var x = document.getElementById(newFormId).getElementsByClassName(formParamInputId);
  x[0].value = numInstance;

  console.log(x[0].class);
  console.log("here");
}    
</script>

What I can't wrap my head around is this:

  • since I'm using a hardcoded html form (using tags) rather than html helpers (like @Html.TextBoxFor) each input isn't bound to anything in the model. I can read the inputs in using FormCollection, but when the form submits I don't know how to repost the form with the values the user entered still in place.

  • if I use html helpers (@Html.TextBoxFor) and bind each input to a property in the model, then I don't understand how to allow the form to add an unlimited number of input fields

I've searched around for examples of dynamic views, but the examples I've found have to do with a view that responds to changes in the model. I haven't found one that involves adding an unlimited number of input fields based on a user action.

I know this is kind of a shaggy dog of a question, but if someone could help point me in the right direction I would be very grateful. Thank in advance for being patient with a newbie.


Solution

  • The scenario you're describing screams View Model. Even a simple one like this would help

    public class WillRecipientsViewModel
    {
        public List<string> Recipients { get; set; }
    }
    

    Then your controller would need the corresponding action of your form

    // Controller
    public ActionResult SetWillRecipients()
    {
        // Added this to force at least 1 text box into the view
        WillRecipientsViewModel model = new WillRecipientsViewModel
        {
            Recipients = new List<string> { "" };
        };
        return View(model);
    }
    
    [HttpPost]
    public ActionResult SetWillRecipients(WillRecipientsViewModel model)
    {
        // Business logic
    
        // Go back to the view if an error occurs
        return View(model);
    }
    
    // View
    @model WillRecipientsViewModel
    @using(Html.BeginForm("SetWillRecipients"))
    {
        @Html.DisplayFor(model => model.Recipients)
        for (int i = 0; i < Model.Recipients.Count; i++)
        {
            // Generate the textboxes for any values that were previously submitted
            Html.InputFor(model => model.Recipients[i])
        }
    }
    

    This will bind the values in your form to the Model object which will make things easier. Then you just need to make sure your view is using <input type="text" name="Recipients" /> to capture the data in the model object in your post. I would also recommend doing your best to have the input name attributes closely match the casing of the view models to avoid and conflicts

    Updated

    I included some more code to exemplify how you can pull the previous recipients into the interface