0

My model has a list property and in the view i need to be able to add an unlimited number of strings to it. So far it's not working and my lousy idea to make it work is the following: Each time a string is added, there's a postback. The new string is in the ViewModel's "newString" property (not a list). The HttpPost method will then save "newString" to the database, refill the list "allStrings" with all strings stored in the database and return the view with all strings and an emtpy textbox to add another string.

This is not a good solution for me because:

  • There's a lot of postbacks if the user wants to add multiple strings
  • If the user adds some strings to his item (a supplier), all these strings are saved to the database. When he then decides he doesn't want to save the supplier all the stored strings are useless and need to be deleted from the database.

I have not implemented this because I know there's far better solutions and I just don't find them. This is what I have:

The ViewModel:

public class SupplierViewModel
{
    public Supplier Supplier { get; set; }
    public List<string> allStrings;
    public string newString { get; set; }
}

The Controller:

    [HttpPost]
    public ActionResult Create(SupplierViewModel model)
    {
        model.allStrings.Add(model.newString);

        if (ModelState.IsValid && model.newString == "")  
            db.Suppliers.Add(model.Supplier);
            db.SaveChanges();

            return RedirectToAction("Index");
        }

        model.newString = "";
        return View(model);
    }

The View:

   <div class="editor-label">
        @Html.LabelFor(model => model.allStrings)
    </div>

    @for (int i = 0; i < Model.allStrings.Count; i++)
    {
        <div class="editor-label">
            @Html.EditorFor(model => model.allStrings[i])
        </div>
    }

    <div class="editor-label">
        @Html.EditorFor(model => model.newString)
    </div>

Note that in this implemented version, none of the strings are saved to the database and the list is cleared after each postback. Only one string (the last one added) is displayed on the view.

Basically the question is: How can I have the user add as many strings as he wants with as few postbacks and database-interaction as possible?

Thanks in advance

oHoodie
  • 209
  • 4
  • 13
  • post your complete html – Frebin Francis Feb 12 '15 at 09:49
  • You can do this easily with javascript/jquery. Is that acceptable? –  Feb 12 '15 at 09:52
  • 1
    @oHoodie Use Ajax.BeginForm instead of Html.BeginForm. – Frebin Francis Feb 12 '15 at 09:55
  • @FrebinFrancis What will that do for me and what do I need to include/install to use it? – oHoodie Feb 12 '15 at 10:00
  • @oHoodie it will post the data without a postback – Frebin Francis Feb 12 '15 at 10:01
  • @oHoodie you can check this link for more information http://www.c-sharpcorner.com/UploadFile/3d39b4/working-with-html-beginform-and-ajax-beginform-in-mvc-3/ – Frebin Francis Feb 12 '15 at 10:02
  • @FrebinFrancis thanks the link looks promising. I'll go through it and post again later – oHoodie Feb 12 '15 at 10:05
  • ok try it, that's the best method to solve your problem – Frebin Francis Feb 12 '15 at 10:06
  • is that possible for you to mark my answer right, if i post it ? – Frebin Francis Feb 12 '15 at 10:06
  • Let me try if it works for me. If it does then sure! – oHoodie Feb 12 '15 at 10:09
  • Better to just use jquery to add new items and avoid all the extra overhead of repeatly posting with `Ajax.BeginForm()` –  Feb 12 '15 at 10:17
  • Well Ajax.BeginForm() works just like Html.BeginForm - It goes through my HttpPost Create method and the list is emptied again. @StephenMuecke I've never worked with jquery before. If it is rather easy to implement I'd give it a shot! Do you have good examples I could study? – oHoodie Feb 12 '15 at 10:36
  • @oHoodie, Answer added, but note that its for adding new items only. It gets a little more complex if you also want to delete items in the middle of the collection. But the reason `Ajax.BeginForm()` is not working for you is that you do not have the correct scripts loaded (otherwise it would post to the controller but stay on the same page) –  Feb 12 '15 at 10:40

1 Answers1

3

You can dynamically add new elements with jquery that will post back to your collection. The html your generating for the textboxes will be similar to

<input type="text" name="allStrings[0]" .../>
<input type="text" name="allStrings[1]" .../>

The name attribute includes an indexer which allows the DefaultModelBinder to bind a collection.

Wrap you textboxes in a container, include a button to add a new item, an input that gets copies and added to the DOM.

<div id="strings">
  @for (int i = 0; i < Model.allStrings.Count; i++)
  {
    <div class="editor-label">
        @Html.TextBoxFor(m => m.allStrings[i])
    </div>
  }
</div>
<div id="newstring" style="display:none;">
  <input type="text" name="allStrings[#]" />
</div>
<button type="button" id="addstring">Add</button>

Script

var container = $('#strings');
$('#addstring').click(function() {
  var index = container.children('input').length;
  var clone = $('#newstring').clone();
  clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
  container .append(clone.html());
});

Refer this fiddle for a working example

Note your model no longer required the public string newString { get; set; } property, and when you post back your collection will contain all the values of the textboxes.

  • sweet the fiddle shows exactly what I need! Only thing I'd have to add is the functionality to remove added strings again. Can you show me how you would do that? I'm sorry but my js/jquery skills are very limited. I'll have lunch now but I'll definitely try to work with this after lunch. – oHoodie Feb 12 '15 at 10:46
  • By default, collection indexers must start at zero and be consecutive, so if you remove an item in the middle (i.e. each textbox has an associated 'delete' button) then you need to renumber the control names (so the indexers start at zero and are consecutive) or you need to add a special hidden input for an Index property. Have a look at [this answer](http://stackoverflow.com/questions/24026374/adding-another-pet-to-a-model-form/24027152#24027152) and the associated comments to help your understanding. –  Feb 12 '15 at 10:53
  • I tried for so long now I just don't get it to work :/ The js function is executed but no clone is created and no error either. I also had to include . Did I forget to include anything else that is required? – oHoodie Feb 12 '15 at 14:54
  • Look at the fiddle - it works. You need jquery of course (but don't use the min versions - you can't debug it). All I can assume is you have not copied the code correctly. –  Feb 13 '15 at 04:12
  • Is it possible that I need to add something in order to cover the "onLoad" part that is set on jsfiddle? sorry for being a js noob :/ – oHoodie Feb 13 '15 at 11:48
  • No. Are you getting any errors in the browser console? Have you placed the script at the bottom of the page (immediately before the closing

    tag)? Either that or put inside a `$( document ).ready() { }` block

    –  Feb 13 '15 at 11:56
  • I put my script in a seperate .js file and referenced it in the header of the .cshtml file. Is that wrong? If possible I'd like to keep javascript and html apart... – oHoodie Feb 13 '15 at 11:58
  • 1
    Should not be a problem, but to start with put the script in the main view between ` –  Feb 13 '15 at 12:04
  • oh man and just like this it's working! The only thing I did wrong was wrapping the – oHoodie Feb 13 '15 at 12:08