How to dynamically add an item to a list within a list in a ViewModel using Razor and .NET Core 2.2? - asp.net-core

I've been following this tutorial (i did it in asp.net core 2.2):
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ on how to edit a variable lenght list in asp.net. In this tutorial, we're just handling a list of gifts. I'm trying to apply this method to a more complex model.
I have an object called Workout.cs who contains a List of Section.cs. The Section.cs object contains a List of Movement.cs
public class Workout
{
public string Name { get; set; }
public IEnumerable<Section> Sections { get; set; } = new List<Section>();
}
public class Section
{
public int NbRounds { get; set; }
public string Name { get; set; }
public IEnumerable<Movement> Movements { get; set; } = new List<Movement>();
}
public class Movement
{
public string Name { get; set; }
}
WorkoutController
public IActionResult BlankSection()
{
return PartialView("_SectionEditor", new Section());
}
public IActionResult BlankMovement()
{
return PartialView("_MovementEditor", new Movement());
}
Index.cshtml
#model Workout
<h2>Workout</h2>
<form asp-action="Index" method="post" asp-controller="Workout">
<div id="editorRows">
#foreach (var item in Model.Sections)
{
<partial name="_SectionEditor" model="item" />
}
</div>
<a id="addItem" asp-action="BlankSection" asp-controller="Workout">Add Section...</a> <br />
<input type="submit" value="Finished" />
</form>
#section scripts {
<script>
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorRows").append(html); }
});
return false;
});
</script>
}
_SectionEditor.cshtml
#model Section
#{
Layout = "~/Views/Shared/_LayoutEditor.cshtml";
}
<div class="editorRow">
#using (Html.BeginCollectionItem("sections"))
{
<span>Name: </span> #Html.EditorFor(m => m.Name);
<span>Rounds: </span>#Html.EditorFor(m => m.NbRounds, new { size = 4 });
}
delete
<div id="editorMovement">
#foreach (var item in Model.Movements)
{
<partial name="_MovementEditor" model="item" />
}
</div>
<a id="addMovement" asp-action="BlankMovement" asp-controller="Workout">Add Movement...</a> <br />
</div>
#section Scripts {
<script>
$(document).ready(function () {
$("a.deleteRow").on("click", function () {
$(this).parents("div.editorRow:first").remove();
return false;
});
});
$("#addMovement").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorMovement").append(html); }
});
return false;
});
</script>
}
MovementEditor.cshtml
#model Movement
#{
Layout = "~/Views/Shared/_LayoutEditor.cshtml";
}
<div class="editorMovement">
#using (Html.BeginCollectionItem("movements"))
{
<span>Name: </span> #Html.TextBoxFor(m => m.Name);
}
delete
</div>
#section Scripts {
<script>
$(document).ready(function () {
$("a.deleteMovement").on("click", function () {
$(this).parents("div.editorMovement:first").remove();
return false;
});
});
</script>
}
With the tutorial, it's working fine when I'm adding sections to my workout, but when I'm trying the same to add movements to my sections, it's not working anymore. I would like to be able to add as many sections as I want to my workout, and for each section, as many movements as I want, and send it to the controller. How could I do that ?
thanks a lot

First: MovementEditor appears to be a partial in the controller, but not in the view file (despite the '_').
Second: since _SectionEditor is a partial view, you can't define #section scripts in it, because it's already defined in the main view Index. To solve this issue, you need to put all the scripts in the main view Index.
P.S: don't forget to change jquery selectors for items in the partial views, i.e: $("#addMovement") will not point to the anchor tag because it was not there when the DOM tree was created. Instead write $("body #addMovement") and it will get the anchor tag.

Related

dropzone image passing with a model

I have a form that includes several inputs(text) and below them, there is a dropzone area. I need to pass these data and image together in a model.
I have a model like this:
public BannerItem bannerItem { get; set; }
public IFormFile imagefile { get; set; }
I am using tags like asp-for and i dont know how to bind dropzone image to the imagefile. I tried js but it didn't work. Can you help me with this?
Here is a whole working demo you could follow:
Model
public class TestVM
{
public BannerItem bannerItem { get; set; }
public IFormFile imagefile { get; set; }
}
public class BannerItem
{
public int Id { get; set; }
public string Name { get; set; }
}
View (Index.cshtml)
#model TestVM
<form asp-action="Test" asp-controller="Home" method="post" enctype="multipart/form-data" class="dropzone dz-clickable form-horizontal form-bordered" id="dropzoneForm">
<div class="form-group form-actions">
<input asp-for="bannerItem.Name" />
<div class="col-md-9 col-md-offset-4">
<button type="submit" id="submit" class="btn btn-sm btn-primary"><i class="fa fa-floppy-o"></i> Upload</button>
</div>
</div>
</form>
#section Scripts
{
<link rel="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/dropzone.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/dropzone.js"> </script>
<script>
function myParamName() {
return "imagefile";
}
Dropzone.options.dropzoneForm = {
autoProcessQueue: false,
paramName: myParamName, // The name that will be used to transfer the file
uploadMultiple: true,
parallelUploads: 100,
init: function () {
console.log("active");
var wrapperThis = this;
$("#submit").click(function (e) {
e.preventDefault();
wrapperThis.processQueue();
});
},
accept: function (file, done) {
done();
}
};
</script>
}
Controller
public class HomeController : Controller
{
public async Task<IActionResult> Index()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Test(TestVM model)
{
//do your sufff...
}
}

My Bootstap modal form is covering the entire screen

I have a modal form in my ASP.NET Core 3.1 project. The problem is my modal form unexpectedly is covering entire of screen and I don't know why.
My _Modal.cshtml:
#model MyProject.CommonLayer.PublicClass.BootstrapModel
<div aria-hidden="true" aria-labelledby="#Model.AreaLabeledId" role="dialog" tabindex="-1" id="#Model.ID" class="modal fade">
<div class="modal-dialog #Model.ModalSizeClass">
<div class="modal-content" style="border-radius:6px;">
</div>
</div>
</div>
My modal.js file:
(function ($) {
function Modal() {
var $this = this;
function initilizeModel() {
$("#modal-action").on('loaded.bs.modal', function (e) {
}).on('hidden.bs.modal', function (e) {
$(this).removeData('bs.modal');
});
}
$this.init = function () {
initilizeModel();
}
}
$(function () {
var self = new Modal();
self.init();
})
}(jQuery))
and my BooststrapModal.cs file:
public class BootstrapModel
{
public string ID { get; set; }
public string AreaLabeledId { get; set; }
public ModalSize Size { get; set; }
public string Message { get; set; }
public string ModalSizeClass
{
get
{
switch (this.Size)
{
case ModalSize.Small:
return "modal-sm";
case ModalSize.Large:
return "modal-lg";
case ModalSize.Medium:
default:
return "";
}
}
}
public enum ModalSize
{
Small,
Large,
Medium
}
}
In the end of my index.cs file, for modal I put:
#Html.Partial("_Modal", new BootstrapModel { ID = "modal-action", Size = BootstrapModel.ModalSize.Medium })
#section AdminScripts {
<script src="~/js/modal/modal.js"></script>
}
Finally when button is clicked, the modal form covers my entire screen.
Please help. Thanks in advance.

How to return a partial view and invoke the model's constructor with DI system

I have a parent view, and want to call a handler with ajax that will return a partial view. The problem I'm having is that my partial view needs it's model also which has all its own OnGet, OnPost etc methods.
When calling:
public PartialViewResult OnGetPartialView(Guid Id)
{
return Partial("MyPartialView");
}
I don't know how to add the model for this view, as its only constructor takes several services that usually the DI systems takes care of for me. I also need to pass the Id to the partial view as its used in the OnGet method (which I'm assuming will be invoked when this works properly).
Thanks!
To pass the model to the partial view in razor pages, you need to add the second parameter as the model you need to pass when returning to the Partial:
return Partial("MyPartialView", model);
It should be noted that in MyPartialView, you need to delete the #Page in the first line of the page and add the model reference you passed.
This will ensure that MyPartialView receives the model data, otherwise there will be an error that model is null.
Regarding the OnGet and OnPost methods of the MyPartialView page you mentioned, if you delete #Page, they will lose their actual contact meaning.
My suggestion is that if you have some post or get methods that need to be used in MyPartialView, you can write these methods to other pages.
Here is a complete example:
TestModel.cshtml.cs:
public class TestModel : PageModel
{
public void OnGet()
{
}
public PartialViewResult OnGetPartialView(Guid Id)
{
List<Person> persons = new List<Person>()
{
new Person(){ Age = 12,
FirstName = "dd",
LastName = "aa" },
new Person(){ Age = 13,
FirstName = "bb",
LastName = "ff" },
new Person(){ Age = 14,
FirstName = "ggr",
LastName = "rwe" },
};
return Partial("MyPartialView", persons);
}
public IActionResult OnPostTest()
{
return Content("aa");
}
}
TestModel.cshtml:
#page
#model WebApplication_razorpage_new.Pages.TestModel
#{
ViewData["Title"] = "Test";
Layout = "~/Pages/Shared/_Layout.cshtml";
}
<h1>Test</h1>
<input id="Button1" type="button" value="Get partial view" /><br /><br /><br />
<div id="partial" class="border"></div>
#section Scripts{
<script>
$(function () {
$("#Button1").click(function () {
$.ajax({
type: "get",
url: "/Test?handler=PartialView",
data: { Id: "780cd7ce-91b2-40fd-b4c8-7efa6b7c84a5" },
success: function (data) {
$("#partial").html(data);
}
});
});
})
</script>
}
MyPartialView.cshtml:
#model List<Person>
<form method="post">
<input id="Button1" type="button" value="button" onclick="Click()" />
<input id="Text1" type="text" />
<table class="table table-bordered">
#foreach (var item in Model)
{
<tr>
<td>#item.Age</td>
<td>#item.FirstName</td>
<td>#item.LastName</td>
</tr>
}
</table>
</form>
<script>
function Click() {
$.ajax({
type: "POST",
url: "/Test?handler=test",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
contentType: "application/json; charset=utf-8",
success: function (data) {
$("#Text1").val(data);
}
});
}
</script>
Here is the test result:

.net-core Site with Partial Vue.js Frontend Form Fields

I am trying to create a site that has partial implementation of Vue.js, I am looking at using Vue.js as from what I understand it does not require a SPA site like other JS frameworks and I believe this framework ticks the boxes required.
I have a basic form that I want to be used to Create, Update and Delete objects.
The data is received via a SAL which calls an API, all Create, Update and Delete calls will go through the same API.
I have been able to do a HttpGet and HttpPost to Get and Update the data and show it on a simple form.
However when I try to display just a blank form I get the following errors:
Error Received
The code I have is as followed:
.cshtml page
#model bms.accessbookings.com.Types.ViewModels.ShowVenueViewModel
#{
ViewData["Title"] = "Venue";
}
<div class="m-grid__item m-grid__item--fluid m-wrapper">
<div class="m-content">
<div class="row">
<div id="venueForm">
Venue ID: <input type="text" v-model="venue.venueId" />
<br/>
Venue Name: <input type="text" v-model="venue.venueName" />
<br/>
Address: <input type="text" v-
model="venue.address.addressLine1"/>
<br/>
Line 2: <input type="text" v-
model="venue.address.addressLine2"/>
<br/>
City: <input type="text" v-model="venue.address.city"/>
<button type="button" v-on:click="sendToServer"
style="padding: 0; border: none; background: none;
cursor: pointer;">
<i class="la la-save"></i>
</button>
</div>
</div>
</div>
</div>
#section Scripts {
<script src="/js/venueform.js" type="text/javascript"></script>
}
.cs ViewModel:
public class ShowVenueViewModel
{
public int VenueId { get; set; }
public string VenueName { get; set; }
public Address Address { get; set; }
}
Address element contains Line1, Line2, City etc
VenueController Get and Post:
[HttpGet]
[Route("GetVenue")]
public ShowVenueViewModel GetVenue(int venueId = 0)
{
ShowVenueViewModel viewModel = new ShowVenueViewModel
{
Address = new Address()
};
if (venueId > 0)
{
viewModel = _venueImplementation.GetShowVenueViewModel(venueId);
}
return viewModel;
}
[HttpPost]
[Route("SaveVenue")]
public ShowVenueViewModel SaveVenue([FromBody]ShowVenueViewModel venueViewModel)
{
return venueViewModel;
}
.js page:
$(document).ready(function() {
var venueId = window.location.pathname.substring(7);
const vm = new Vue({
el: '#venueForm',
data () {
return {
venue: {}
}
},
props: {
currentevent: Object
},
created() {
Object.assign(this.venue, this.currentevent || {});
},
mounted: function() {
axios.get('/venue/GetVenue', { params: { venueId: venueId } }).then(response => {
this.venue = response.data;
});
},
methods: {
sendToServer: function () {
var self = this;
console.log("Venue getting updated");
axios.post('/venue/SaveVenue', self.venue)
.then(response => {
this.venue = response.data;
console.log("Venue Updated");
});
}
}
});
});
At the moment if the venue has items it returns these items without a problem and displays them into the form, I can edit the inputs and "save" them which then returns the newly saved information (save functionality not yet connected to my BLL / SAL).
However when no Venue object is returned (empty) the form does not display at all, and so there is no way to enter details onto a blank form to "save" and create a new venue.
Still really new to vue.js and I find it hard to find guides that are not pointing to CLI or SPA style sites.
I may have a lot of things wrong here, but if there are any pointers to help me I would be very grateful.
Ok well the error you're getting comes from your template (.cshtml page). You need either to make sure venue.address always has a value, or, safer, test for the presence of venue.address.addressLine1 before displaying it.
When you get an error in a render function, vue can't tell you the line number. But you know it's in the template somewhere and it's generally not hard to find. Keep your templates short :-) (the one shown is fine).

Update kendo grid columns without page reload in ASP MVC

I have a kendo grid where user can select list of columns from grid and save the selection by giving name (i.e view name). Each saved selection (view name) will show up as drop-down above grid so that user can change grid columns whenever they want. In current implementation, whenever user selects view name from one drop-down value to other, i call action method to select that view name as current view name. Then page reloads to invoke Index' action method to retrieve current view names columns values. I am using visible attribute in grid to show and hide columns in grid.
Now i was wondering if i can update the grid columns without reloading the page when user change the view name from the dropdown.
Thanks
Sanjeev
Screenshot:
Here is my setup:
ViewModel:
namespace MvcApplicatioin.Models
{
public class EmployeeViewModel
{
public EmployeeColumns EmployeeColumns { get; set; }
public IEnumerable<SelectListItem> EmployeeViewNames { get; set; }
public long EmployeeSelectedViewId { get; set; }
}
public class EmployeeResponse
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class EmployeeColumns
{
public bool Id { get; set; }
public bool FirstName { get; set; }
public bool LastName { get; set; }
}
}
Controller:
public class EmployeeController : Controller
{
// GET: Employee
public ActionResult Index()
{
var service = new EmployeeService();
EmployeeViewModel model = new EmployeeViewModel();
long currentViewId;
//setup views and column preferences
EmployeeColumns employeeColumns = service.GetCurrentEmployeeColumnsPreferences();
model.EmployeeColumns = employeeColumns;
model.EmployeeViewNames = service.GetAllEmployeeViewNames(out currentViewId);
model.EmployeeSelectedViewId = currentViewId;
return View(model);
}
}
Razor:
#using Kendo.Mvc.UI
#{
ViewBag.Title = "Employee Info:";
}
<h3 style="margin-bottom: 10px;">Employee Info</h3>
<input id="btnSearch" type="button" value="Search" class="btn_Search" />
<div class="row">
<div class="col-sm-12">
#(Html.Kendo().Grid<MvcApplicatioin.Models.EmployeeResponse>()
.Name("GridEmployee")
.Columns(columns =>
{
columns.Bound(e => e.Id).Width("170px").Visible(Model.EmployeeColumns.Id);
columns.Bound(e => e.FirstName).Width("190px").Visible(Model.EmployeeColumns.FirstName);
columns.Bound(e => e.LastName).Width("170px").Visible(Model.EmployeeColumns.LastName);
})
.ToolBar(tools =>
{
tools.Template(#<text>
<div class="col-lg-4 col-md-5 col-sm-5 col-xs-7 pull-right" style="padding-right: 0;">
<div class="form-group" style="margin-bottom: 0;">
#Html.Label("Grid View:", new { #class = "col-sm-3 col-xs-4 control-label view" })
<div class="col-sm-7 col-xs-6" style="padding-left: 0;">
#Html.DropDownList("lstEmployeeViewNames", new SelectList(Model.EmployeeViewNames, "Value", "Text", Model.EmployeeSelectedViewId), "- Select View Name -", new { #class = "form-control", #style = "height: auto;" })
</div>
</div>
</div>
</text>);
})
.Pageable(x => x.PageSizes(new int[] { 10, 20, 50, 100 }).ButtonCount(4))
.AutoBind(false)
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(10)
.ServerOperation(false)
.Read(read => read.Action("SearchEmployee", "Employee")))
)
</div>
</div><!--//row-->
<script type="text/javascript">
$('#btnSearch').click(function (e) {
e.preventDefault(); //This prevent the submit button onclick from submitting by itself
$('#GridEmployee').data('kendoGrid').dataSource.read();
});
//Change event for Dropdown placed inside the Grid's Toolbar - To Change the view
$("#lstEmployeeViewNames").change(function (e) {
var selectedViewId = $('select#lstEmployeeViewNames option:selected').val();
if (selectedViewId == null || selectedViewId == '') {
alert("Please select the view name from the dropdown first !!");
return;
}
$.post("/Employee/SetEmployeeColumnsCurrentPreferences", { viewId: selectedViewId }, function (data) {
window.top.location.reload();
});
});
</script>
This is how I would do a search in client. I know it doesn't tell how to show and hide columns, but at least it could put you on the right track.
The trick is to manipulate $("#GridEmployee") instead of posting.
function search() {
var searchCriteria = $("#searchField").val();
var gridData = $("#GridEmployee").data("kendoGrid");
if searchCriteria != "") {
gridData.dataSource.filter({ field: "FirstName", operator: "contains", value: searchCriteria });
} else {
gridData.dataSource.filter({});
}
}