paulpitchford paulpitchford - 1 month ago 20
ASP.NET (C#) Question

Add items to select list on the client side in MVC 5 ASP

I'm trying to learn more about MVC 5 so I'm writing a bloging site for myself to learn more as I go.

I have set up a select list for tags and would like to be able to add new tags from the create blog entry page rather than having to remember to set the tags up before creating a new post. I'm thinking down the lines of a "Add Tag" button which displays a bootstrap modal window where the user can add a new tag.

Here is my controller action:

public ViewResult CreateBlogPost()
{
CreateEditBlogViewModel viewModel = new CreateEditBlogViewModel();
viewModel.BlogPost = new Core.BlogPost();

viewModel.BlogPost.ShortBody = "<p>Something short and sweet to describe the post</p>";
viewModel.BlogPost.Body = "<p>Enter something blog worthy here...</p>";

viewModel.Tags = new SelectList(_blogRepo.BlogTags(), "Id", "Name");
viewModel.Categories = new SelectList(_blogRepo.BlogCategories(), "Id", "Name");

return View(viewModel);
}


And here is the HTML in the view:

<div class="row">
<div class="form-group">
@Html.LabelFor(m => m.BlogPost.Tags, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.ListBoxFor(m => m.SelectedTags, Model.Tags, new { @class = "form-control chosen-select", @data_placeholder = "Start typing to see a list of tags" })
</div>
</div>
</div>

<div class="row">
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#tagModal">
Add Tag
</button>
</div>


Here is my partial view for the modal window:

@using (Html.BeginForm("SaveTag", "Home", FormMethod.Post, new { id = "tag-form" }))
{
@Html.AntiForgeryToken()

<!-- Modal -->
<div class="modal fade" id="tagModal" tabindex="-1" role="dialog" aria-labelledby="tagModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="tagModalLabel">Enter a name for a new tag</h4>
</div>
<div class="modal-body">
<input type="text" id="Name" placeholder="Enter a new tag name" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>

}


Is it possible to add a tag on the client side, persist it into the db and then add it to my tags select list without refreshing the page?

PS: FYI I'm using the Chosen multi-select from here.

@section scripts {
<script type="text/javascript" src="~/Scripts/chosen.jquery.min.js"></script>
<script type="text/javascript">
$(".chosen-select").chosen()
</script>
}


EDIT: I have updated the question with all the code that makes the view give the user the modal window to enter a new tag name. I'm just not sure how to post without navigating away from the page so I'm guessing some sort of Ajax post is required. And then what to do with the data that is returned from that post. How do I then add that new persisted record to the select list?

I know the tag isn't passing to the controller method as it's not bound to any sort of model but being as I'm using a view model on the parent view, I'm not sure how I would handle that here either.

Answer

In order to dynamically add a new BlogTag in the view you will need to post the new tag Name using ajax, to a controller method that saves the BlogTag and returns its new ID value. Your controller method would be something like

[HttpPost]
public JsonResult CreateTag(string name)
{
  BlogTag tag = new BlogTag(){ Name = name };
  db.BlogTags.Add(tag);
  db.SaveChanges();
  return Json(tag.ID);
  // If the above code could result in an error/exception, catch it and return
  // return Json(null);
}

Then in the view, handle the dialogs submit button to post the value and update the tag list

var url = '@Url.Action("CreateTag")';
var tagList = $('#SelectedTags');
$('#tag-form').submit(function() {
  var tagName = $('#Name').val();
  $.post(url, { name: tagName }, function(id) {
    if (id) {
      // add the new tag to the list box
      tagList.append($('<option></option>').val(id).text($('#Name').val()));
      // trigger the chosen update
      tagList.trigger("chosen:updated");  
    } else {
      // Oops - display an error message?
    }
  }).fail(function (result) {
    // Oops - display an error message?
  });
  return false; // cancel the default submit
});

Side note: I would recommend that you create a view model for BlogTagVM (containing a property for the Name with validation attributes) and an associated partial view (say _AddBlogTag.cshtml) that generates the dialog html, so that in the main view you can use @Html.Partial("_AddBlogTag", new BlogTagVM()) which will allow you to use the strongly typed html helpers, and to include client side validation.

Note also that nested <form> elements are invalid html so ensure that html for the dialog is outside the main <form> tag for the view.

Comments