SignalR and Knockout viewmodel binding - asp.net-mvc

I'am trying to display product list from database using signalr and knockout.js. But without any result. Could anyone tell me what I'am doing wrong or where I have mistake? I will be very grateful for helping me.
This is the view:
<div class="products">
<div class="row" data-bind="template: { name: 'productTemplate', foreach: products }">
</div>
<span class="messageClass" style="color: red;"></span>
</div>
<script type="text/html" id="productTemplate">
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<div class="caption">
<h3 data-bind="text: name"></h3>
</div>
</div>
</div>
</script>
This is the script:
<script>
$(function () {
function productViewModel(id, name) {
this.productId = id;
this.name = ko.observable(name);
var self = this;
}
function productListViewModel() {
this.hub = $.connection.voteHub;
this.products = ko.observableArray([]);
var products = this.products;
this.init = function () {
this.hub.server.getAllProducts();
}
this.hub.client.getAllProducts = function (allProducts) {
var mappedProducts = $.map(allProducts, function (item) {
return new productViewModel(item.productId, item.name)
});
products(mappedProducts);
}
}
var vm = new productListViewModel();
ko.applyBindings(vm);
$.connection.hub.start(function () {
vm.init();
});
});
</script>
Here is the hub method to get all products:
public void GetAllProducts()
{
VoteViewModel viewModel = new VoteViewModel();
viewModel.Products = ProductService.GetProducts(new GetProductsRequest()).Products.ToList();
if (viewModel.Products != null)
{
// TODO: pomyslec nad wysylaniem listy a nie tablicy!
Clients.All.getAllProducts(viewModel.Products.ToArray());
}
}
If I put on my hub code like this, it will work well (taken from demo app):
VoteViewModel vm = new VoteViewModel();
vm.Products = new List<Product>() { new Product() { Name = "Sample", Id = 1 } };
Clients.All.getAllProducts(vm.Products.ToArray());
My code which look like this don't work though. (i don't know why, maybe because my Product object from db, has more variables?):
VoteViewModel viewModel = new VoteViewModel();
viewModel.Products = ProductService.GetProducts(new GetProductsRequest()).Products.ToList();
Clients.All.getAllProducts(viewModel.Products.ToArray());
Here is code from my Product class: (dbcontext class)
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string ImagePath { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
I made new ProductViewModel class which looks like this:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public int VotesAmount { get; set; }
}
And then bind all data from db to that model, and then return List and IT WORKS NOW!, but I really don't understand why I can't just take list of products from database and send it to signalr client side (like I tried to do it before).
List<ProductViewModel> prods = new List<ProductViewModel>();
List<Product> products = ProductService.GetProducts(new GetProductsRequest()).Products.ToList();
foreach (var item in products)
{
ProductViewModel prod = new ProductViewModel()
{
Id = item.Id,
Name = item.Name,
Description = item.Description,
ImagePath = item.ImagePath,
VotesAmount = item.Votes.Count()
};
prods.Add(prod);
}
Clients.All.getAllProducts(prods.ToArray());

Your code is absolutely working fine. Please check if you have the referrence to the following files.
<script src="~/scripts/jquery.signalr-2.0.2.js" type="text/javascript"></script>
<script src="~/signalr/hubs"></script>
Signalr.hubs is the main reference which gives the javascripts proxy files. If you are facing any errors please let me know.
Next thing is the property name referred in the model object creation.
this.hub.client.getAllProducts = function (allProducts) {
var mappedProducts = $.map(allProducts, function (item) {
return new productViewModel(item.productId, item.name)
});
products(mappedProducts);
}
I have updated the property names to pascal case and worked for me.
return new productViewModel(item.ProductId, item.Name)

Related

model does not contain a public instance definition for 'OnlineUserModel'

On my View:
#model IEnumerable<Project.Models.OnlineUserModel>
<div class="form-group online">
<ul>
#foreach (var user in Model)
{
<li>
#Html.DisplayFor(modelItem => user.UserName) <br />
</li>
}
</ul>
</div>
AJAX:
$(function () {
$.ajax({
type: "POST",
url: "/Home/OnlineUsers",
MODEL :
public class OnlineUserModel
{
public string UserName { get; set; }
public string UserID { get; set; }
public OnlineUserModel(DataRow rs)
{
if (!DBNull.Value.Equals(rs["UserName"])) this.UserName = (string)rs["UserName"];
if (!DBNull.Value.Equals(rs["UserID"])) this.UserID = (string)rs["UserID"];
}
public static List<OnlineUserModel> OnlineUsersList()
{
List <OnlineUserModel> user = new List<OnlineUserModel>();
DataTable resultTable = Globals.CommonData.RetrieveOnlineUsers();
if (resultTable != null)
{
foreach (DataRow rs in resultTable.Rows)
{
user.Add(new OnlineUserModel(rs));
}
}
return user;
}
}
CONTROLLER:
[HttpPost]
public JsonResult OnlineUsers()
{
return Json(Project.Models.OnlineUserModel.OnlineUsersList());
}
I did the same exact way on my other project and it is working fine but this one keeps giving me error and I need help in figuring it out. Probably an obvious one for the experienced contributor here but not for me. Thank you, Pls point me to the right direction.

Dropdown list population from ViewModel

First of all, I know this question has been asked many, many times. I've read countless articles and Stack Overflow answers. I've tried to figure this problem out for four days and I think I need help if someone doesn't mind.
I have two databases. The employee database has a field called "DisplayName" -- the second database has a relationship with the first and they work together great. I'm able to call the two databases perfectly in another application.
You can see the in the picture Index Page
that I have a list of people. I want a dropdown below it that lists all display names in the database so employees can add themselves to the list. You'll see a dropdown in the image but it's not populated.
Seems simple. But geez. Part of a problem I'm having is my home controller already has a function to populate the list in the picture so I can't do another on that page. I've tried a lot of suggestions on a lot of sites. I get IEnumerable errors or display reference errors....
Here's my controller (again - it has nothing in it that helps the dropdown):
namespace SeatingChart.Controllers
{
public class HomeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Employee
public ActionResult Index()
{
var lists = db.BreakModels
.Include("Employee")
.Include("TimeEntered")
.Include("TimeCleared")
.Include("DisplayName")
.Select(a => new HomeIndexViewModels
{
Employee = a.Employee,
DisplayName = a.EmployeeModels.DisplayName,
TimeEntered = a.TimeEntered,
TimeCleared = a.TimeCleared.Value,
Id = a.EmployeeModels.Id,
});
return View(lists);
}
View:
#model IEnumerable<SeatingChart.Models.HomeIndexViewModels>
#{
Layout = null;
}
#Html.Partial("_Header")
<div class="container_lists">
<div class="container_break col-md-8">
<h5 style="text-align:center">Break List</h5>
<table class="table-bordered col-lg-12">
#if (Model != null)
{
foreach (var item in Model)
{
if (item.TimeCleared == null)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
 BV
</td>
<td>
 #item.TimeEntered.ToString("HH:mm")
</td>
</tr>
}
}
}
</table>
#using (Html.BeginForm())
{
<div class="row site-spaced">
<div class="col-3">
#Html.DropDownList("DisplayName", new SelectList(new List<string>() { "---Dispatcher---" }), new { #class = "required " })
</div>
</div>
<div class="col-3">
<input type="submit" value="Submit" class="site-control" />
</div>
}
</div>
</div>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace SeatingChart.Models
{
public class HomeIndexViewModels
{
//Break Model
public int BreakId { get; set; }
public int Employee { get; set; }
public DateTime TimeEntered { get; set; }
public DateTime? TimeCleared { get; set; }
//Employee Model
public int Id { get; set; }
public string DisplayName { get; set; }
public string DisplayNames { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool NotActive { get; set; }
public int Force { get; set; }
public string EmployeeList { get; set; }
}
}
I hope this is clear enough. I've tried so many different ways with so much code - the errors are different with everything I've tried.
Thanks in advance for your patience and help!
You can add to your viewmodel
public List<SelectListItem> Employees { get; set; }
Then you can populate this list with controller then in view just call it with:
#Html.DropDownListFor(m => m.Id, Model.Employees, new { #class = "form-control", required = "required" })
Update - how to populate list. Should work (but not tested code).
public List<SelectListItem> GetEmployeeForDropdown(List<HomeIndexViewModels> list)
{
List<SelectListItem> empList = new List<SelectListItem>();
try
{
if (list != null && list.Count > 0)
{
foreach (var item in list)
{
empList.Add(new SelectListItem { Text = item.DisplayName, Value = item.Id.ToString() });
}
}
else
{
empList.Add(new SelectListItem { Text = "No items", Value = string.Empty });
}
}
catch (Exception ex)
{
//handle exceptions here
}
return empList;
}
Edit: Remember to use your model in view!

How do I change a partial view with a radio button click in .Net MVC and have some of the model state passed along to the partial view?

I'm trying to display a partial view in a div block named 'productButtonForm' based on a radio button click. The partial view appears to be called, but the passed model has null member variables. Can someone help me figure out where I've gone wrong? Below is what I've got:
Model
public class RetrieveAllModel
{
public Guid ConversationId { get; set; }
public List<RetrieveProductsModel> Products { get; set; }
public RetrieveOffersModel Offers { get; set; }
public int ProductType { get; set; }
}
View
#{
ViewBag.Title = "Easy Order";
int productCount = 0;
}
<legend>Offers/Products</legend>
#using (Html.BeginForm("ShowProductItems", "BrowseShopping"))
{
foreach (var type in Model.Products)
{
if (productCount > 0 && productCount % 5 == 0)
{
<br/>//break after every 5 products
}
#type.Name
#Html.RadioButtonFor(model => model.ProductType, type.ID, Model);
<label> </label>
productCount = productCount + 1;
}
<div class="col-lg-5 col-md-6 col-sm-12" id="productButtonForm">
</div>
}
Controller
public PartialViewResult ShowProductItems()
{
return PartialView("RetrieveProducts", new RetrieveAllModel() {Products = new List<RetrieveProductsModel>()});
}
[HttpPost]
public PartialViewResult ShowProductItems(RetrieveAllModel model)
{
//The passed model only has the ProductType set, every other
//member variable is null
return PartialView("RetrieveProducts", model);
}
Script file
$(function() {
$("[name=ProductType]").on('change',
function() {
var $radio = $(this);
var myurl = "ShowProductItems?ProductType=" + $radio.val();
console.log("We hit the script");
$.ajax({
url: myurl,
type: 'POST',
success: function(data) {
$("#productButtonForm").append(data);
}
});
});
});
I had a few issues going. Aside from what Stephen mentioned above, I had two data models that needed to be represented in the same button group. To address that, I had to use Html.RadioButton instead of RadionButtonFor. Also, I needed to access the controller's established conversation with the client to access the model state of the current view. Once I got those in place, the partial view changes as desired. Below are the changes I made to fix my triggering problem.
Model
public class RetrieveAllModel
{
public Guid ConversationId { get; set; }
public List<RetrieveProductsModel> Products { get; set; }
public RetrieveOffersModel Offers { get; set; }
public string ProductType { get; set; }
}
public class RetrieveCatalogModel
{
public List<BrowseDataItemModel> AvailableBrowseItems { get; set; }
}
public class RetrieveOffersModel : RetrieveCatalogModel
{
public List<int> SelectedServiceIds { get; set; }
}
public class RetrieveProductsModel : RetrieveCatalogModel
{
public int ID { get; set; }
public string Name { get; set; }
public int Count { get; set; }
}
View
#model OrderServiceClient.Models.RetrieveAllModel
#{
ViewBag.Title = "Easy Order";
int productCount = 1;
string offers = "Offers";
}
#using (Html.BeginForm("ShowCatalog", "BrowseShopping"))
{
//since offers are not part of the dynamic product list, they need to be specifically identified
#offers<label> </label>
#Html.RadioButton("catalogName", "Offers", true, new { catalogName = "Offers", conversationid = Model.ConversationId })
<label> </label>
foreach (var type in Model.Products)
{
if (productCount > 0 && productCount % 5 == 0)
{
<br/>//break after every 5 products
}
#type.Name<label> </label>
#Html.RadioButton("catalogName", type.Name, new { catalogName = type.Name, conversationid = Model.ConversationId })
<label> </label>
productCount = productCount + 1;
}
}
...
<div class="row">
#{Html.RenderPartial("RetrieveCatalogs", Model.Offers.AvailableBrowseItems);}
</div>
Partial View
#model List<OrderServiceClient.Models.BrowseDataItemModel>
#if (Model != null)
{
<div class="col-lg-7 col-md-6 col-sm-12 offers-container" id="shoppingcatalog">
<table class="table table-striped">
<tr>
<th>Data Type</th>
<th>Name</th>
<th>Price</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr class="offerList">
<td>#item.DataType</td>
<td>#item.Name</td>
<td>#string.Format($"{item.Amount,0:C2}")</td>
<td><a class="addService" dataType="#item.DataType" serviceId="#item.ServiceId" serviceName="#item.Name" amount="#item.Amount">Add</a></td>
</tr>
}
</table>
</div>
}
Controller
public PartialViewResult ShowCatalog()
{
RetrieveCatalogModel rcm = new RetrieveCatalogModel();
rcm.AvailableBrowseItems = new List<BrowseDataItemModel>();
return PartialView("RetrieveCatalogs", rcm.AvailableBrowseItems);
}
[HttpPost]
public PartialViewResult ShowCatalog(string catalogName, Guid conversationid)
{
if (catalogName.Equals("Offers"))
{
RetrieveOffersModel offers = new RetrieveOffersModel();
var response = BrowseShoppingHelper.RetrieveOffers(conversationid, _client);
offers.AvailableBrowseItems = BuildOffersBrowseDataItemsModel(response).ToList();
return PartialView("RetrieveCatalogs", offers.AvailableBrowseItems);
}
else
{
var prodctFolderResponse = BrowseShoppingHelper.RetrieveProductFolders(conversationid, _client);
var output = (RetrieveProductFoldersCommandOutput) prodctFolderResponse.Body.Output;
RetrieveProductsModel rpm = new RetrieveProductsModel{Name = catalogName, AvailableBrowseItems = new List<BrowseDataItemModel>()};
foreach (var folder in output.Folders)
{
if (!catalogName.Equals(folder.Name)) continue;
var items = BuildProductBrowseItemsModel(
(RetrieveProductsInGroupCommandOutput) BrowseShoppingHelper
.RetrieveProductItems(conversationid, _client, folder).Body.Output);
rpm.AvailableBrowseItems.AddRange(items);
break;
}
return PartialView("RetrieveCatalogs", rpm.AvailableBrowseItems);
}
}
Script file
$(function() {
$("[name=catalogName]").on('change',
function () {
var $radio = $(this);
var myurl = "ShowCatalog?catalogName=" + $radio.val() + "&conversationid=" + $(this).attr('conversationid');
console.log("Catalog item is: " + $radio.val() + " and id is: " + $(this).attr('conversationid'));
$.ajax({
url: myurl,
type: 'POST',
success: function (data) {
$("#shoppingcatalog").html(data);
}
});
});
});

View Not Returning Multiple Lists in Asp.net MVC [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 5 years ago.
i am new to web from desktop.
i have two models.
first
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public double Stock { get; set; }
public Category Category { get; set; }
public string ImagePath { get; set; }
}
and second is
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public bool isSelected { get; set; }
}
and a ViewModel for passing it to my view
public class ProductsVM
{
public IList<Item> items { get; set; }
public IList<Category> categories { get; set; }
}
and my controller action method looks like this
[HttpGet]
public ActionResult Products()
{
ViewBag.Message = "...Products...";
ProductsVM productsVm = new ProductsVM();
productsVm.items = db.Items.ToList();
productsVm.categories = db.Categories.ToList();
return View(productsVm);
}
[HttpPost]
public ActionResult Products(ProductsVM model)
{
ViewBag.Message = "...Categories...";
return View(model);
}
i have used it in my view like this
#using (Html.BeginForm())
{
<div class="row">
<div class="col-md-2">
#foreach (var it in Model.categories.ToList())
{
<div class="input-group">
#Html.CheckBoxFor(i => it.isSelected, new { Name = "ChkCategory", id = "ChkCategory"+it.Id, #class = "Categories" }) #it.Name
#Html.HiddenFor(i => it.Name)
</div>
}
</div>
#*Loading Items...*#
<div class="col-md-10">
#for (int i = 0; i < Model.items.Count() / 3; i++)
{
<div class="row">
#foreach (var item in Model.items.Skip(i * 3).Take(3))
{
<div class="col-md-4 col-sm-6 col-xs-12">
<img src="#Url.Content(item.ImagePath)" alt="#item.Description" class="thumbnail" />
</div>
}
</div>
}
</div>
</div>
<input type="submit" value="submit" />
}
#section scripts
{
<script src="~/Scripts/Products.js"></script>
}
i have tried all the ways to get my model back when i post the from to controller
i am posting the from on checkbox click event using ajax from my products.js file.
but in my controller action method it always show the ViewModel as null.
what should i do? am i doing something wrong.
Products.js
$(function () {
console.log('Inside js......');
$('.Categories').click(function (e) {
console.log(this.id, $("#" + this.id).is(":checked"));
$.ajax({
type: "POST",
url: "/Home/Products",
success: function () {
console.log("ajax successfull....");
},
error: function () {
console.log("ajax error....");
}
});
});
});
This is your ProductsVM:
public class ProductsVM
{
public IList<Item> items { get; set; }
public IList<Category> categories { get; set; }
}
This is your action method to which you are posting:
[HttpPost]
public ActionResult Products(ProductsVM model)
When you submit your form, it will take the values from your form's controls and using the names of the controls, it will post them to the Products action. In your view (form), you have a checkbox with the name chkCategory and a hidden input with the name Name. When you post your form, it will send chkCategory and its value, and the hidden item with the name Name. When it arrives on the server side, the MVC will look for an action method named Products in your controller. Then the default binder will try to look for chkCategory and Name properties to see if the action accepts them. It will not find it. Then it will try to see if it can create a ProductsVM and it cannot because ProductsVM has 2 properties: items and categories and they do not match what you are posting so it will just choose that action and pass it null.
You have many issues in your code and it is not playing nicely with the whole MVC framework. I suggest you read Understanding MVC Model Binding and try some simple examples to get a hang of it and then try what you are doing.

Using jqAutocomplete in an array

I am using the jqAutocomplete plugin which I want to use inside a row of a table.
I cannot get it to work. The autocomplete selection labels do not appear. It only allows me to enter 1 letter.
I am using knockout mapping to map server side viewmodels to client side viewmodels.
The page renders fine. For a new form - as in this case - the code produces 10 empty Lines (not show). I want to use autocomplete to select a contract from a list for the JobName column.
I have copied the viewmodals here, reduced to make it easier to follow;
Parent viewmodel:
public class WholeViewModel : BaseViewModel
{
public WholeViewModel(int employeeId, string name;)
: base()
{
this.Lines = new List<LineViewModel>();
this.Contracts = SessionObjectsSTAS.GetContracts().Select(x => new ContractViewModel { ContractId = x.ContractId, JobName = x.JobName, Label = x.ToString() }).ToList();
this.EmployeeId = employeeId;
this.Name = name;
}
public int EmployeeId { get; set; }
public string Name { get; set; }
public List<ContractViewModel> Contracts { get; set; }
}
The Lines Collection is made up of this viewmodal:
public class LineViewModel
{
public LineViewModel()
{
}
public LineViewModel(int key)
: this()
{
this.Id = key;
this.JobName = string.Empty;
this.Description = string.Empty;
}
public int Id { get; set; }
public int? ContractId { get; set; }
public string JobName { get; set; }
public string Description { get; set; }
}
The ContractViewModel:
public class ContractViewModel
{
public int ContractId { get; set; }
public string JobName { get; set; }
public string Label { get; set; }
}
So to my javascript:
var lineMapping = {
'Lines': {
key: function (line) {
return ko.unwrap(line.Id);
},
create: function (options) {
return new LineViewModel(options.data);
}
}
};
LineViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, lineMapping, self);
};
WholeViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, lineMapping, self);
};
and my ASP.Net Razor page:
#using Newtonsoft.Json
#model ViewModel.WholeViewModel
#{
var data = JsonConvert.SerializeObject(Model);
}
<table class="table">
<tbody data-bind="foreach: Lines">
<tr>
<td>
<input type="text"
data-bind="jqAuto: { source: $parent.Contracts, value: JobName, labelProp: 'Label', inputProp: 'Label', valueProp: 'ContractId' }" />
</td>
<td>
<input type="text" data-bind="value: Description" />
</td>
</tr>
</tbody>
</table>
#section scripts
{
#Scripts.Render("~/bundles/BootstrapJs")
#Scripts.Render("~/bundles/jqueryui")
#Scripts.Render("~/bundles/inputmask")
#Scripts.Render("~/bundles/Knockout")
<script type="text/javascript">
var wholeViewModel = new WholeViewModel(#Html.Raw(data));
ko.applyBindings(wholeViewModel);
</script>
}
When I set a breakpoint in Visual Studio, the LineViewModel looks like this;
Use a global variable data to bind the source directly.
<script type="text/javascript">
var data = #Html.Raw(data);
var wholeViewModel = new WholeViewModel(data);
ko.applyBindings(wholeViewModel);
</script>
There is a small error in mapping the children, lines. Attributing it to the Knockout documentation itself.
var LineViewModel = function(data) {
ko.mapping.fromJS(data, {}, this);
}
Note that the children are mapped to an empty object and not to lineMapping object. These objects are part of LineViewModel which itself gets attached to the Lines array in lineMapping.
The value should be ContractId. source should be data.Contracts.
<input type="text"
data-bind="jqAuto: {
source: data.Contracts,
value: ContractId,
labelProp: 'Label',
inputProp: 'Label',
valueProp: 'ContractId'
}"
/>

Resources