Hot Towel SPA Modal Dialog knockout bindings and validation - durandal

I'm building a SPA using the Hot Towel SPA template.
Here's my problem:
I have a view where I put the information related to a Venue (Name, Description, Address, etc.)
Associated with the Venue there are different Rooms with their own fields (Name, Description, Type, etc.)
I have a list of Rooms and a button "Add New Room".When I hit the button, a modal dialog pops up, I fill the form with the requested information then I save. After the dialog is closed the list gets updated.
I am able to retrieve the information from the dialog, but I'm not able to trigger the validation rules if the fields are left blank. Also the datacontext.HasChanges() returns always true.
Is it possible to use the modal dialog like any other view?
Here's part of the code I am using:
From Model.cs:
public class Venue
{
[Key]
public int VenueId { get; set; }
[Required(ErrorMessage = "Venue Name is required.")]
[Display(Name = "Venue Name")]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Room> Fields { get; set; }
...
}
public class Room
{
[Key]
public int RoomId { get; set; }
[Required(ErrorMessage = "Name is required.")]
[Display(Name = "Name")]
public string Name { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public int VenueId { get; set; }
public virtual Venue Venue { get; set; }
...
}
From venuedetail.js:
define(['services/datacontext',
'services/model',
'durandal/plugins/router',
'durandal/system',
'durandal/app',
'services/logger',
'viewmodels/roommodal'],
function (datacontext, model, router, system, app, logger, roommodal) {
...
var addRoom = function () {
var newRoom= datacontext.manager.createEntity("Room");
roommodal.room = newRoom;
app.showModal(roommodal).then(function (response) {
if (response) {
}
return true;
});
};
...
From roommodal.js:
define(['durandal/app', 'services/datacontext', 'durandal/system', 'durandal/plugins/router', 'services/logger'],
function (app, datacontext, system, router, logger) {
var isSaving = ko.observable(false);
var room= ko.observable();
activate = function(routeData) {
return true;
};
hasChanges = ko.computed(function() {
return datacontext.hasChanges(); // Always true ?
});
canSave = ko.computed(function() {
return hasChanges() && !isSaving();
});
canDeactivate = function () {
return true;
};
var save = function(dialogResult) {
this.modal.close(dialogResult);
};
var cancel = function() {
this.modal.close(false);
};
var vm = {
activate: activate,
save: save,
canSave: canSave,
cancel: cancel,
canDeactivate: canDeactivate,
room: room,
hasChanges: hasChanges,
title: 'Add room'
};
return vm;
From roommodal.html:
<div class="messageBox">
<div class="modal-header">
<h3 data-bind="text: title"></h3>
<i class="icon-asterisk" data-bind="visible: hasChanges"></i>
</div>
<div class="modal-body">
<div data-bind="with: room">
<label for="name">Name</label>
<input id="name" data-bind="value: name, valueUpdate: 'afterkeydown'"
placeholder="Enter name" />
<div>
<label>Description</label>
<textarea data-bind="value: description"
placeholder="Enter description"></textarea>
</div>
<div>
<label>Notes</label>
<textarea data-bind="value: notes"
placeholder="Enter notes"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-info"
data-bind="click: cancel, enable: canSave">
<i class="icon-undo"></i>Cancel</button>
<button class="btn btn-info"
data-bind="click: save, enable: canSave">
<i class="icon-save"></i>Save</button>
</div>
Any help will be greatly appreciated.
Thanks in advance.

Validation is triggered when you try to save using datacontext.saveChanges() which you don't do in this piece of code, instead, you are just closing the modal window.
Easiest way to see if saving was successful is to check HasChanges afterwards - it should be false if everything went well, otherwise it will be true.
datacontext.HasChanges() returns true when you enter modal window because you create an entity and place it in your context before opening the modal window. You can only ignore HasChanges and silently CancelChanges before closing the modal window.

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.

Cannot update user Identity Role from list in razor page

I have a razor page which shows checkbox of Roles. The Roles owned by the selected user will be checked on page load. What I'm trying to do is, I want to be able to edit the roles for the selected user. But when I click update, it doesn't update.
Here is the razor page:
<EditForm Model="#RoleDto" OnValidSubmit="#EditRole">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="card">
<div class="card-header">
<h2>Manage User Roles</h2>
Add/Remove Roles for User / #UserFullname
</div>
<div class="card-body">
#for (int i = 0; i < numOfRoles; i++)
{
<div class="form-check m-1">
<input type="hidden" value="#RoleListModel[i].Id" />
<input type="hidden" value="#RoleListModel[i].Name" />
<input type="checkbox" checked="#RoleListModel[i].Selected" /> #RoleListModel[i].Name
</div>
}
</div>
</div>
<button type="submit" class="btn btn-success btn-block">
Confirm
</button>
#code {
ApplicationRoleDto RoleDto = new ApplicationRoleDto();
private List<ApplicationRoleDto> RoleListModel;
[Parameter] public string Id { get; set; }
[Parameter] public ApplicationUserDto UserDto { get; set; }
[Parameter] public string UserFullname { get; set; }
[Parameter] public int numOfRoles { get; set; }
protected async override Task OnParametersSetAsync()
{
UserDto = await _client.GetFromJsonAsync<ApplicationUserDto>($"api/userroles/{Id}");
UserFullname = UserDto.FullName;
RoleListModel = await _client.GetFromJsonAsync<List<ApplicationRoleDto>>($"api/rolemanager/{Id}");
numOfRoles = RoleListModel.Count();
}
async Task EditRole()
{
await _client.PostAsJsonAsync($"api/rolemanager/{Id}", RoleListModel);
_navManager.NavigateTo($"/userroles/");
}
}
and here is the controller:
[HttpPost]
public async Task<IActionResult> Manage(List<ApplicationRoleDto> model, string Id)
{
var user = await _userManager.FindByIdAsync(Id);
if (user == null)
{
NotFound();
}
var roles = await _userManager.GetRolesAsync(user);
var result = await _userManager.RemoveFromRolesAsync(user, roles);
if (!result.Succeeded)
{
Console.WriteLine("Cannot remove user existing roles");
return NotFound();
}
result = await _userManager.AddToRolesAsync(user, model.Where(x => x.Selected).Select(y => y.Name));
if (!result.Succeeded)
{
Console.WriteLine("Cannot add selected roles to user");
return NotFound();
}
return NoContent();
}
Did I miss anything here?

How to solve empty model trouble using both Ajax.BeginForm and Html.BeginForm in MVC

I am new to MVC and stuck in passing modal to controller.
I have read many similar threads in SO, to no avail.
Here, I have a view for entering order details.
User will enter order item details (using ajax.BeginForm) and when he clicks save, whole order will be saved in backend (using Html.BeginForm). Ajax.BeginForm is working properly and passing + displaying records properly. But Html.BeginForm is passing model as nothing.
Here is my code ...
My Models
public class OrderItemsModel
{
public string SrNo { get; set; }
public int? ItemCode { get; set; }
public string ItemName { get; set; }
public Decimal? Qty { get; set; }
public Decimal? Rate { get; set; }
public Decimal? Amount { get; set; }
}
public class OrderModel
{
public string OrderNumber { get; set; }
public string OrderDate { get; set; }
public int? CustomerCode { get; set; }
public string CustomerName { get; set; }
public string Note { get; set; }
//List of items selected in Order
public List<OrderItemsModel> ItemsSelected { get; set; }
}
Extract from My View
#model OrderApplication.Models.OrderModel
#{
ViewBag.Title = "Index";
Model.ItemsSelected = ViewBag.getlist;
}
#using (Ajax.BeginForm("UpdateItemList", "Order", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "selectedtable" }))
{
<h2 style="margin-left:5%;">Order Entry</h2>
//Order No & Date
<div class="row">
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderNumber, "OrderNo"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderNumber, new { #class = "form-control", #readonly = "readonly" })
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderDate, "Date"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderDate, new { #class = "form-control" })
</div>
</div>
</div>
<br />
//Table of entries
<div id="selectedtable">
#Html.Partial("_selectTable", Model);
</div>
<br />
}
#*Main Save*#
<div class="row">
<div class="col-sm-12">
<div class="col-sm-3">
#using (Html.BeginForm("SaveData", "Order", new { order = Model, id = "loginform", #class = "justify-content-center" }))
{
<input type="submit" value="Save Order" class="btn btn-success" />
}
</div>
<div class="col-sm-3">
<input type="button" class="btn btn-success" value="Clear Form" onclick="location.href='#Url.Action("Clear", "Order")'" />
</div>
</div>
</div>
My Controller
public class OrderController : Controller
{
public List<OrderItemsModel> dd = new List<OrderItemsModel>() ;
[HttpPost]
public ActionResult SaveData(OrderModel order, string id)
{
if (order == null) //order is always Nothing
{
return View(order);
}
if (order.CustomerCode == 0)
{
return View(order);
}
return View(order);
}
}
}
You shouldn't use both Ajax.BeginForm and Html.BeginForm, as it won't work. Please, check this post as might be of help to decide which one you wish to choose:
https://forums.asp.net/t/1757936.aspx?When+to+use+Html+BeginForm+vs+ajax+BeginForm
If you still want to use Html.BeginForm, just move the using sentence to replace your Ajax.BeginForm at the top of the page, so the form covers all fields, and the model won't be empty.

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({});
}
}