I'm trying to implement dropboxes that can appear prefilled, and can grow dynamically when you press a button. I started with a basic dropdown box implementation that doesn't grow dynamically. This is my controller+DTO code snippet:
public class TaskDTO
{
public string TaskTemplateName { get; set;}
}
public IActionResult Create()
{
ViewData["TaskTemplateId"] = new SelectList(_context.TaskTemplates, "Id", "Name");
return View();
}
public async Task<IActionResult> Create([Bind("Id,TaskTemplateName")] TaskDTO task)
{
if (ModelState.IsValid)
{
//Do some stuff
}
ViewData["TaskTemplateId"] = new SelectList(_context.TaskTemplates, "Id", "Name", task.TaskTemplateName);
return View(task);
}
This is my create.cshtml razor code:
<h2>Create</h2>
<h4>TemplateTask</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="TaskTemplateName" class="control-label"></label>
` `<select asp-for="TaskTemplateName" class="form-control" asp-items="ViewBag.TaskTemplateId"></select>
</div>
Then I looked around and found this link that shows how to create dynamic forms that can grow. I tried to combine that idea with dropdown boxes. Based on that tutorial, here's my new DTO+controller code:
public class TaskTemplateDTO
{
public string TaskTemplateName { get; set; }
}
public class TaskDTO
{
public List<TaskTemplateDTO> TaskTemplateNames { get; set; }
}
public IActionResult Create()
{
var vm = new TaskDTO() { };
ViewData["TaskTemplateId"] = new SelectList(_context.TaskTemplates, "Id", "Name");
return View(vm);
}
public async Task<IActionResult> Create([Bind("Id,TaskName,TaskTemplateNames,ParentTasks,IsBasicTask,EstimatedTime,RequiredResources")] TaskDTO task)
{
if (ModelState.IsValid)
{
//Do some stuff
}
ViewData["TaskTemplateId"] = new SelectList(_context.TaskTemplates, "Id", "Name", task.TaskTemplateNames);
return View(task);
}
here's my extra EditorTemplates razor, TaskTemplateDTO.cshtml:
#model Namespace.TaskTemplateDTO
<select asp-for="TaskTemplateName" class="TaskTemplate" asp-items="ViewBag.TaskTemplateId"></select>
This is my create.cshtml razor code:
<h2>Create</h2>
<h4>TemplateTask</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<table class="table table-bordered" id="TaskTemplateTable">
<tr>
<th>Task Templates</th>
<th><button type="button" name="add" id="btn_AddTaskTemplate" class="btn btn-success btn-sm add"><span class="glyphicon glyphicon-plus"></span></button></th>
#Html.EditorFor(f => f.TaskTemplateNames)
</tr>
</table>
</div>
</form>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
$("#btn_AddTaskTemplate").click(function () {
var i = $(".TaskTemplate").length;
var html = '';
html += '<tr>';
html += '<td><select type="text" name="TaskTemplateNames[' + i + '].TaskTemplateName" class="TaskTemplate" /></td>';
html += '<td></td></tr>';
});
$('#TaskTemplateTable').append(html);
</script>
}
The code above adds the new dropdowns on click, but the dropdowns aren't prefilled with data, what have I done wrong?
You will need to have the select options that you want in the new controls to be available to your javascript method. There are a few ways to do it, the most straightforward would be to hide it in your html page in a hidden select control.
Your CSHTML:
<h2>Create</h2>
//Added the hidden HTML control here
#Html.DropDownList("hidden-select",
new SelectList((IEnumerable) ViewData["TaskTemplateId"]),
null, new Dictionary<string, object>
{
{ "id", "task-template-names" },
{ "style", "display: none" }
})
<h4>TemplateTask</h4>
<hr />
<div class="row">
<div class="col-md-4">
////other things you were doing
This will render in your html as a prepopulated hidden template from which you can build new dropdowns with options on button click.
$("#btn_AddTaskTemplate").click(function () {
var i = $(".TaskTemplate").length;
var html = '';
html += '<tr>';
html += '<td><select type="text" name="TaskTemplateNames[' + i + '].TaskTemplateName" class="TaskTemplate">';
html += document.querySelector("#task-template-names").innerHTML;
html += '</select></td>';
html += '<td></td></tr>';
});
I wasn't able to test this code because the setup is beyond my scope here, so you might need to tweak it a little bit. If you encounter any difficult errors, let me know.
Related
I have a model class like following
public class UserViewModel
{
public UserViewModel()
{
UsersDet = new Dictionary<long, string>();
usrdat = new List<User>();
}
public Dictionary<long , string> UsersDet { get; set; }
public IEnumerable<User> usrdata { get; set; }
}
Then I'm trying to get those model values in partial view like following
this partial view is a popup actually.
_UserView partial view
#model Project.ViewModels.UserViewModel
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body" style="width: 530px;">
<div id="slider_view" style="display: block;">
<div class="flexslider">
<ul class="slides">
#foreach (var item in Model.UsersDet )
{
<li data-thumb="~/Upload_Images/A.jpg">
<img src="~/Upload_Images/A.jpg" data-imagedata="image001" />
<p class="flex-caption">Title</p>
</li>
}
</ul>
</div>
</div>
</div>
</div>
</div>
Here I cannot see the A.jpg images in Popup, but when I use like following I can see images
_UserView partial view
#model Project.ViewModels.UserViewModel
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body" style="width: 530px;">
<div id="slider_view" style="display: block;">
<div class="flexslider">
<ul class="slides">
#for (int i =0; i < 3 ; i ++)
{
<li data-thumb="~/Upload_Images/A.jpg">
<img src="~/Upload_Images/A.jpg" data-imagedata="image001" />
<p class="flex-caption">Title</p>
</li>
}
</ul>
</div>
</div>
</div>
</div>
</div>
this is controller method to bind values to above popup
[HttpGet]
public PartialViewResult MethodName(int id)
{
try
{
IEnumerable<User> listofData = ..
UserViewModel listofimage = new UserViewModel();
if (listofData != null)
{
listofimage.usrdata = listofData;
foreach(var item in listofimage.usrdata)
{
var path = RenderImage(item.ImageValue, "UploadImagePath");
var fileResult = path as FilePathResult;
if (fileResult != null)
{
string imageurl = fileResult.FileName;
imageurl = imageurl.Replace(#"\\", #"\");
listofimage.UsersDet.Add(item.CaseID, imageurl);
}
}
}
return PartialView("_UserView", listofimages);
}
catch (Exception)
{
throw;
}
}
public ActionResult RenderImage(string imageid, string pathvalue)
{
try
{
var URL = System.Configuration.ConfigurationManager.AppSettings[pathvalue].ToString();
var path = Path.Combine(URL, imageid + ".jpg");
return base.File(path, "image/jpeg");
}
catch (Exception)
{
throw;
}
}
Whats is wrong in my foreach approach how to populate elements as loop
If Model.UsersDet is populated with items then < li > elements will be rendered. If they are rendered but you don't see the images then you open the devtools and in the Network tab you look for the requests made for each image. If there requests have bad response then you fixed it according to the returned http status.
But not having the project to debug and if you are saying that #for is working but #foreach is not then the problem should be that the Model.UsersDet is empty.
Been searching around but couldn't find a direct solution to what I'm trying to achieve.
I've tried to include as much as needed but it's a very large project so hopefully you'll get the gist.
Overview:
I have a view model that has several lists of objects within it. I am using two partial views for control over each of the list of objects, one for gathering the list of objects (which is held in a session), and the other for adding a list of said object into the list.
Update:
As per comment - what I am looking to do is as follows - in the Index, fill out the existingIp model info, which is displayed through the addToListPartialView, then it will post to the ListPartialView to update the list through the session, handled backend in the controller, which will in turn display on the Index - the question ultimately is, how do I achieve this?
Problem:
The issue I'm having is once I've added an object, through a partial view, to the object list, another partial view, how do I then pass this back to the main view?
Code:
Controller
public ActionResult AddExistingIp([Bind(Include = "Subnet, Cidr, Mask")]ExistingIp existingIp)
{
if(Session["pa_ipv4Session"] != null)
{
pa_ipv4 pa_ipv4 = (pa_ipv4)Session["pa_ipv4Session"];
if(pa_ipv4.ExistingIps == null)
{
pa_ipv4.ExistingIps = new List<ExistingIp>();
}
pa_ipv4.ExistingIps.Add(existingIp);
ViewBag.pa_ipv4 = pa_ipv4.ExistingIps;
return View("ExistingIpView", ViewBag.pa_ipv4);
}
else
{
pa_ipv4 pa_ipv4 = new pa_ipv4();
Session["pa_ipv4Session"] = pa_ipv4;
pa_ipv4.ExistingIps = new List<ExistingIp>();
pa_ipv4.ExistingIps.Add(existingIp);
ViewBag.pa_ipv4 = pa_ipv4.ExistingIps;
return View("ExistingIpView", ViewBag.pa_ipv4);
}
Index:
#model ViewModel
<div id="ExistingIpList">
#{Html.RenderPartial("ExistingIpView");}
</div>
<div id="addExisting">
#{Html.RenderPartial("AddExistingIp");}
</div>
List Partial
#model IEnumerable<ExistingIp>
#if (Model != null)
{
foreach (var ei in Model)
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>#ei.Subnet</span>
</div>
<div class="ui-block-b">
<span>#ei.Cidr</span>
</div>
<div class="ui-block-c">
<span>#ei.Mask</span>
</div>
<div class="ui-block-d">
#ei.Id
Delete
</div>
</div>
}
}
Add to list partial:
#using (Html.BeginForm("AddExistingIp", "PA_IPV4"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
#Html.EditorFor(m => m.Subnet)
#Html.ValidationMessageFor(m => m.Subnet)
</span>
</div>
<div class="ui-block-b">
<span>
#Html.EditorFor(m => m.Cidr)
#Html.ValidationMessageFor(m => m.Cidr)
</span>
</div>
<div class="ui-block-c">
<span>
#Html.EditorFor(m => m.Mask)
#Html.ValidationMessageFor(m => m.Mask)
</span>
</div>
<div class="ui-block-d">
<span>
#Html.EditorFor(m => m.Id)
#Html.ValidationMessageFor(m => m.Id)
</span>
</div>
</div>
<div data-role="main" class="ui-content">
<div data-role="controlgroup" data-type="horizontal">
<input type="submit" id="addExistingIp" cssclass="ui-btn ui-corner-all ui-shadow" value="Add" />
</div>
</div>
}
ViewModel:
public Contact ContactDetails { get; set; }
[Required]
public bool ExistingAddress { get; set; }
public List<ExistingIp> ExistingIps { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string ExistingNotes { get; set; }
You can modify the AddExistingIp to just store the data. And to make a RedirectToAction Index. There you will take the data from Session and pass it to the Model.
[HttpPost]
public ActionResult AddExistingIp([Bind(Include = "Subnet, Cidr, Mask")]ExistingIp existingIp)
{
if(Session["pa_ipv4Session"] != null)
{
pa_ipv4 pa_ipv4 = (pa_ipv4)Session["pa_ipv4Session"];
if(pa_ipv4.ExistingIps == null)
{
pa_ipv4.ExistingIps = new List<ExistingIp>();
}
pa_ipv4.ExistingIps.Add(existingIp);
}
else
{
pa_ipv4 pa_ipv4 = new pa_ipv4();
Session["pa_ipv4Session"] = pa_ipv4;
pa_ipv4.ExistingIps = new List<ExistingIp>();
pa_ipv4.ExistingIps.Add(existingIp);
}
return RedirectToAction("Index");
}
The Index Action will look similar with this, where you take data from Session and use it in your Model
public ActionResult Index()
{
var viewModel = new ViewModel();
// take data from Session
pa_ipv4 pa_ipv4 = Session["pa_ipv4Session"] as (pa_ipv4);
// some verification
// add the list from Session to model
viewModel.ExistingIps = pa_ipv4.ExistingIps;
return View(viewModel);
}
Also, I think your Index View you should at ExistingIpView you should pass the Model to display.
#model ViewModel
<div id="ExistingIpList">
#{Html.RenderPartial("ExistingIpView", Model.ExistingIps);}
</div>
<div id="addExisting">
#{Html.RenderPartial("AddExistingIp");}
</div>
I'm having a problem in my MVC project. When trying to create an object to add it to the db, it always returns null.
public class ListsModel
{
public EntitiesList EntityList { get; set; }
public List<string> AllGroups { get; set; }
}
public ActionResult Create()
{
ListsModel model = new ListsModel();
model.EntityList = new EntitiesList();
model.AllGroups = managerLists.GetAllListsKeys(); //For droplist
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ListsModel model)
{
if (ModelState.IsValid)
{
model.EntityList.List_CreatedTime = DateTime.Now;
managerLists.AddNewObject(model.EntityList);
return RedirectToAction("Index");
}
return View(model);
}
And a simple cshtml:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>EntitiesList</legend>
<div class="form-group">
#Html.LabelFor(model => model.EntityList.List_EntitityName)
#Html.DropDownListFor(model => model.AllGroups, new SelectList(Model.AllGroups),
new { #class = "form-control" })
<p class="help-block">#Html.ValidationMessageFor(model => model.EntityList.List_EntitityName)</p>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EntityList.List_EntityValue)
<input class="form-control" value="#Model.EntityList.List_EntityValue"/>
<p class="help-block">#Html.ValidationMessageFor(model => model.EntityList.List_EntityValue)</p>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EntityList.List_OrderByNumber)
<input class="form-control" value="#Model.EntityList.List_OrderByNumber"/>
<p class="help-block">#Html.ValidationMessageFor(model => model.EntityList.List_OrderByNumber)</p>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EntityList.List_Comments)
<textarea class="form-control" rows="3">#Model.EntityList.List_Comments</textarea>
<p class="help-block">#Html.ValidationMessageFor(model => model.EntityList.List_Comments)</p>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When it's getting to the "model.EntityList.List_CreatedTime = DateTime.Now;" a null reference exception is thrown.
I tried to change the signature to "public ActionResult Create(ListsModel ListsModel)", as suggested here: Create view is posting null objects
But I got the same results.
Hope you can help me.
I think the problem is the way you define inputs like this:
<input class="form-control" value="#Model.EntityList.List_EntityValue"/>
For ASP MVC can collect form data, inputs should have an Name attribute corresponding with model fields.
Try to generate inputs using the standard:
#Html.TextBoxFor(model => model.EntityList.List_EntityValue)
I suggest you inspect the differences in the html generated (to see how is asp mvc generating inputs).
In a View i have the next structure ( control of Subject*s for each *Group):
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#for (int i = 0; i < ViewBag.AllGroups.Count; i++)
{
<h4>#ViewBag.AllGroups[i].Code</h4>
<select id="e-#i" multiple="multiple">
#foreach (Subject subject in ViewBag.AllSubjects)
{
<option value="#subject.Name">#subject.Name</option>
}
</select>
}
<input type="submit" value="Generate" class="btn btn-default" />
</div>
}
The question is that how can I retreive this data (I want to receive (1)list of Groups and and I want to get a (2)list of all selected Subjects for each group in my list(1)) in my Controller?
Thank you in advance.
Recommended way is to use strongly typed View Model Object
public class GroupViewModel
{
public string Code { get;set; }
public List<Subject> AllSubjects { get; set; }
}
Pass List as the Model to the Razor view in the controller.
return new View(new List<GroupViewModel>()); // populated one.
Use this list in the View.
#model IList<GroupViewModel>
#for (int i = 0; i < Model.Count; i++)
{
<h4>Model[i].Code</h4>
<select id="e-#i" multiple="multiple">
#foreach (Subject subject in Model[i].AllSubjects)
{
<option value="#subject.Name">#subject.Name</option>
}
</select>
}
There is nothing especial to deal with this situation, except that you have missed the tag name of the select element.
To be exact, all html elements such as select you have used here, should have a name not id (id="e-#i") and all elements are serialized based their names and sent to server. On the other side, at server-side, you should get the posted values which are in a csv formatted (due to multiple ability added you have added to select element)
Solved my problem by simplifying the task. What i had to to: I created new ViewModel for this thing. I replaced tag <select></select> with #Html.ListBoxFor(m => m.Subjects, Model.SubjectItems). I had to create a SubjectItems list in my ViewModel.
Here's the code (Sorry for tons of code: I just want to make everything clear):
My View:
#using System
#using System.Linq
#using TimeTable.GenericRepository
#model TimeTable.Models.GroupViewModel
#{
//it's better to move the next line to a Controller later
ViewBag.GroupId = new SelectList(new GroupRepository().Get().ToList(), "Id", "Code", Model.GroupId);
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_LayoutBootstrap.cshtml";
}
<h2>Index</h2>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.GroupId, "Group is: ", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("GroupId", String.Empty)
#Html.ValidationMessageFor(model => model.GroupId)
</div>
</div>
#Html.ListBoxFor(m => m.Subjects, Model.SubjectItems, new { #id = "e", #style = "width:80%; " })
<br /><br />
<input type="submit" value="Generate" class="btn btn-default" />
</div>
}
#* ReSharper disable once Razor.SectionNotResolved *#
#section Scripts {
#Styles.Render("~/Content/select2")
#Scripts.Render("~/bundles/select2")
<script type="text/javascript">
$(function () { $("#e").select2(); });
</script>
}
My Controller:
public ActionResult Generate()
{
return View(new GroupViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Generate(GroupViewModel gvm)
{
var subjects= gvm.Subjects; // <== selected subjects are here
return View();
}
My Model:
public class GroupViewModel
{
public int GroupId{ get; set; }
public Group Group {
get { return new GroupRepository().GetById(GroupId); }
}
public object Subjects { get; set; }
public IEnumerable<SelectListItem> SubjectItems
{
get
{
var items = new SelectList(new SubjectRepository().Get().ToList(), "Id", "Name");
return items;
}
}
}
P.S. Select2 is a custom replacement for selectBoxes, it's not necessary, but I like it: http://ivaynberg.github.io/select2/
So here, I need to add comment for video in one view.
I have the main view code like this that displaying video and comment for that video.
<!-- language: C# -->
#model AzureMediaPortal.ViewModels.ViewModelWatch
#{
ViewBag.Title = "Watch";
}
<div id="videoPlayer">
</div>
<h2>#Html.DisplayFor(model => model.media.Title)</h2>
<h3> By #Html.DisplayFor(model => model.media.UserId) at #Html.DisplayFor(model => model.media.UploadDate) </h3>
#Html.HiddenFor(model => model.media.Id)
#Html.HiddenFor(model => model.media.AssetId)
#Html.HiddenFor(model => model.media.FileUrl, new { id = "fileUrl" })
<div class="display-label" style="font-weight:bold">
#Html.DisplayNameFor(model => model.media.Description)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.media.Description)
</div>
<br />
<div class="display-label" style="font-weight:bold">
#Html.DisplayName("Category")
</div>
<div class="display-field">
#Html.DisplayFor(model => model.media.Category.CategoryName)
</div>
<h3>Comments</h3>
#foreach (var item in Model.comment)
{
<div class="display-label" style="font-weight:bold">
#item.UserId
</div>
<div class="display-field">
#item.Content
</div>
}
#Html.Partial("Post",new AzureMediaPortal.ViewModels.ViewModelWatch())
#section Scripts {
<script src="~/Scripts/playerframework.min.js"></script>
<script src="~/Scripts/media-player.js"></script>
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
mediaPlayer.initFunction("videoPlayer", $("#fileUrl").val());
</script>
}
and this the partial view
#model AzureMediaPortal.ViewModels.ViewModelWatch
#{
ViewBag.Title = "Post";
}
<h2>Add Comment</h2>
#Html.HiddenFor(model => model.cmnt.MediaElement.Id)
#using (Html.BeginForm("Post","Home",FormMethod.Post)) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Add Comment</legend>
<div class="editor-label" style="font-weight:bold">
#Context.User.Identity.Name
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.cmnt.Content)
#Html.ValidationMessageFor(model => model.cmnt.Content)
</div>
<p>
<input type="submit" value="Post" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
this is my ViewModel
public class ViewModelWatch
{
public MediaElement media { get; set; }
public List<Comment> comment { get; set; }
public Comment cmnt { get; set; }
}
and this is my controller
public ActionResult Watch(int id)
{
ViewModelWatch vm = new ViewModelWatch();
vm.media = _repository.GetMedia(id);
vm.comment = _repository.GetMediaComment(id);
return View(vm);
}
public ActionResult Post()
{
return View();
}
[HttpPost]
public ActionResult Post(Comment comment, int id)
{
if (ModelState.IsValid)
{
comment.UserId = User.Identity.Name;
comment.MediaElement.Id = id;
db.Comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Watch");
}
return View();
}
I need to pass data from partial view and save it to database include the media.Id to know that comment inserted for the video.
Thanks so muchhh
putting scripts on a partial view is generally bad practice. Script gets inserted into the middle of the view. For saving information from a partial view I use ajax calls. To do that add a class to your post button
<input type="button" class="btnSubmit" value="Post" />
then in your script on the main page
$(document).on('click', '.btnSubmit', function(){
$.ajax({
url: '#Url.Action("Action", "Controller")',
cache: false,
async: true,
data: {
//put the data that you want to save from the partial here
id: $('#hiddenID').val(),
content: $('#Content').val()
},
success: function (_result) {
//can add something here like an alert, close a popup something along those lines
}
});
});
just make sure the inputs on your controller match exactly the names that you have defined here
[HttpPost]
public ActionResult Action(int id, string content){
//database call to save the fields
//see here for an example of returning json http://stackoverflow.com/questions/1482034/extjs-how-to-return-json-success-w-data-using-asp-net-mvc
return Json(json);
}
I would suggest using #Html.Action and get the create form from there. Also, once the comments are stored you can redirect to the parent action. That should automatically update the comments list.
But that would be a bad idea.
Rather you can put get your comments by calling action from ajax ($.ajax) and use pure.js to replace the older ones with the new comment list.