KendoUI SignalR grid not update if data is posed via ajax in my ASP.NET Core 5 web application - asp.net-core

I have a KendoUI grid which uses SignalR. Whilst the grid itself works fine if you update the data inline or incell, it doesn't work if I update the data in a form which uses ajax to post it to my controller.
It was my understanding that, if I injected my hub into my controller and then called (whichever I needed, create, update or destroy) :
await _fixtureHub.Clients.All.SendAsync("update", model);
or
await _fixtureHub.Clients.All.SendAsync("update", model);
That it would tell the clients that the data had been changed/created and the grid would update to reflect that change. This isn't happening however and I'm wondering what I've done wrong or missing.
To start, here is my signalR bound grid.
$('#fixture_grid').kendoGrid({
dataSource: {
schema: {
model: {
id: "Id",
fields: {
Created_Date: {
type: "date"
},
Commencement_Date: {
type: "date"
}
}
}
},
type: "signalr",
autoSync: true,
transport: {
signalr: {
promise: fixture_hub_start,
hub: fixture_hub,
server: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
},
client: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
}
}
}
},
autoBind: true,
sortable: true,
editable: false,
scrollable: true,
columns: [
{
field: "Created_Date",
title: "Created",
format: "{0:dd/MM/yyyy}"
},
{
field: "Commencement_Date",
title: "Commencement",
format: "{0:dd/MM/yyyy}"
},
{
field: "Charterer",
template: "#if(Charterer !=null){# #=Charterer_Name# #} else { #--# }#"
},
{
field: "Region",
template: "#if(Region !=null){# #=Region_Name# #} else { #--# }#"
}
]
});
Here is the relative hub for that grid:
var fixture_url = "/fixtureHub";
var fixture_hub = new signalR.HubConnectionBuilder().withUrl(fixture_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var fixture_hub_start = fixture_hub.start({
json: true
});
Here is the KendoUI wizard with form integration which I update the grid with, this form can process either a creation or an update data, this is achieved by checking the Id that is passed in. Whereby 0 equals new and >0 is existing.
function wizard_fixture() {
let wizard_name = "#wizard-fixture";
//Load Wizard
$(wizard_name).kendoWizard({
pager: true,
loadOnDemand: true,
reloadOnSelect: false,
contentPosition: "right",
validateForms: true,
deferred: true,
actionBar: true,
stepper: {
indicator: true,
label: true,
linear: true
},
steps: [
{
title: "Step 01",
buttons: [
{
name: "custom",
text: "Save & Continue",
click: function () {
let wizard = $(wizard_name).data("kendoWizard");
var validatable = $(wizard_name).kendoValidator().data("kendoValidator");
if (validatable.validate()) {
$.ajax({
type: "POST",
traditional: true,
url: "/Home/Process_Fixture",
data: $(wizard_name).serialize(),
success: function (result) {
...do stuff
},
error: function (xhr, status, error) {
console.log("error")
}
});
}
}
}
],
form: {
formData: {
Id: fixtureviewmodel.Id,
Created_User: fixtureviewmodel.Created_User,
Created_Date: fixtureviewmodel.Created_Date,
Connected: fixtureviewmodel.Connected
},
items: [
{
field: "Fixture_Id",
label: "Id",
editor: "<input type='text' name='Id' id='Fixture_Id' /> "
},
{
field: "Created_User",
label: "Created user",
editor: "<input type='text' name='Created_User' id='Created_User_Fixture' />"
},
{
field: "Created_Date",
id: 'Created_Date_Fixture',
label: "Created date",
editor: "DatePicker",
}
]
}
},
],
});
I've shortened this to demonstrate the custom button and the ajax posting that happens to Process_Fixture. Here is my controller which handles that:
public async Task<JsonResult> Process_Fixture(Fixture model)
{
if (model.Id == 0)
{
if (ModelState.IsValid)
{
var fixture = await _fixture.CreateAsync(model);
Update connected clients
await _fixtureHub.Clients.All.SendAsync("create", model);
return Json(new { success = true, data = fixture.Id, operation = "create" });
}
return Json(new { success = false });
}
else
{
var fixture = await _fixture.UpdateAsync(model);
await _fixtureHub.Clients.All.SendAsync("update", model);
return Json(new { success = true, data = fixture.Id, operation = "update" });
}
}
As you can see, I have injected my hub and I have called the "create" message to it which I believed would force the grid to update with whatever had changed or been created.
Here is the hub itself:
public class FixtureHub : DynamicHub
{
private readonly IRepository<Fixture> _fixtures;
private readonly IRepository<ViewGridFixtures> _viewFixtures;
public FixtureHub(IRepository<Fixture> fixtures, IRepository<ViewGridFixtures> viewFixtures)
{
_fixtures = fixtures;
_viewFixtures = viewFixtures;
}
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception e)
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnDisconnectedAsync(e);
}
public class ReadRequestData
{
public int ViewId { get; set; }
}
public IQueryable<ViewGridFixtures> Read()
{
IQueryable<ViewGridFixtures> data = _viewFixtures.GetAll();
return data;
}
public string GetGroupName()
{
return GetRemoteIpAddress();
}
public string GetRemoteIpAddress()
{
return Context.GetHttpContext()?.Connection.RemoteIpAddress.ToString();
}
}
I need some help here in understanding how I can tell the hub that the update/create/destroy has been called and it needs to do something. At the moment, I feel like injecting the hub and then calling the clients.all.async isn't the right way. With ajax it seems to ignore it and I wonder if the two technologies are working against each other.

Related

How to create location select box in cshtml page without model

I want to create a select box for the location field, in which if one types any letter should call API and fetch location details in the dropdown
I tried the below code but didn't work
<select class="js-data-example-ajax form-control" id="FilterLocation"></select>
#Html.Hidden("FilterLocation", new { id = "locationId" })
In the script written below code
function setLocation() {
$('.js-data-example-ajax').select2({
ajax: {
type: 'PUT',
url: function (params) {
return '/api/GoogleCustomSearch/getLocation?matchingName=' + params.term
},
delay: 250,
data: function (params) {
var query = {
}
// Query paramters will be ?search=[term]&page=[page]
return query;
},
processResults: function (data) {
data = JSON.parse(data);
let results = []
if (data.location !== null) {
data.location.forEach((e) => {
results.push({
id: e,
text: e
})
})
}
return {
results: results
};
}
},
placeholder: "Search"
})
$('.js-data-example-ajax').on('change',function(e){
var selVal = $('#FilterLocation').val()
$('#locationId').val(selVal)
//getZipCodeForDynamic(selVal)
})
var $newOption = $("<option selected='selected'></option>")
$("#FilterLocation").append($newOption).trigger('change');
}
Dropdown options are not getting with above code.
Here is a working demo for select2:
View:
<select id="mySelect2" class="js-data-example-ajax" style="width:200px"></select>
js:
$('#mySelect2').select2({
ajax: {
type: 'PUT',
url: "GetSelect2Data",
delay: 250,
data: function(params) {
var query = {
search: params.term,
}
// Query parameters will be ?search=[term]
return query;
},
processResults: function(data) {
//data = JSON.parse(data);
let results = []
if (data.location !== null) {
data.location.forEach((e) => {
results.push({
id: e,
text: e
})
})
}
return {
results: results
};
}
},
placeholder: "Search"
});
model:
public class Select2Model {
public List<string> Location { get; set; }
}
action:
[HttpPut]
public ActionResult GetSelect2Data(string Search)
{
return Json(new Select2Model() { Location = new List<string> { "a"+Search,"b" + Search, "c" + Search } });
}
result:

Bind validation results from ajax request to form model in mithril

Hi I would like to bind html inputs with validation response model returned from API like that:
{"userName":[{"memberNames":["UserName"],"errorMessage":"Field User Name is required."}],"acceptTerms":[{"memberNames":["AcceptTerms"],"errorMessage":"Accepting terms is requried"}]}
And my component in mithril
var RegisterPage = {
vm: {
userName: m.prop(),
password: m.prop(),
confirmPassword: m.prop(),
acceptTerms: m.prop(false)
},
controller: function (args) {
this.title = 'Register new user account';
this.vm = RegisterPage.vm;
this.register = function (e) {
e.preventDefault();
apiRequest({ method: "POST", url: "http://localhost:12116/auth/register", data: RegisterPage.vm }).then(RegisterPage.vm.registerResult)
}
},
view: function (ctrl, args) {
return m('form.input-group',
[
m('.input-row', [m('label', 'Email'), m('input[type=email][placeholder=Your email address like myemail#email.com]', { onchange: m.withAttr("value", ctrl.vm.email) })]),
m('.input-row', [m('label', 'Password'), m('input[type=password][placeholder=your password]', { onchange: m.withAttr("value", ctrl.vm.password) })]),
m('.input-row', [m('label', 'Confirm password'), m('input[type=password][placeholder=your password]', { onchange: m.withAttr("value", ctrl.vm.confirmPassword) })]),
m('.input-row', [m('label', 'Accept terms and conditions'), m('input[type=checkbox]', { onchange: m.withAttr("checked", ctrl.vm.acceptTerms) })]),
m('button[type=submit].btn btn-positive btn-block', { onclick: ctrl.register }, 'Register account')
]);
}
}
I am looking for some generic solution. I would like to mark invalid fields with css class and add field validation message.
UPDATE
In my project I use some wrapper around m.request to get more details when 400 is thrown
function apiRequest(args) {
NProgress.start();
if (!args.unwrapError) {
args.unwrapError = function (data, xhr) {
if (xhr.status === 401)
{
layout.badRequestMsg(xhr.statusText);
}
NProgress.done();
return data;
}
}
if (!args.unwrapSuccess) {
args.unwrapSuccess = function (data, xhr) {
NProgress.done();
return data;
}
}
return m.request(args);
}

not able to display records as per page size in kendo grid

i want to perform filtering,sorting and no of records in kendo grid but it is not working.
this is my view page:
<script>
$(document).ready(function () {
$("#categories-grid").kendoGrid({
dataSource: {
type: "json",
transport: {
read: {
url: "#Html.Raw(Url.Action("categoriesList", "Admin"))",
type: "POST",
dataType: "json",
data: '',
}
},
schema: {
data: "Data",
total: "Total",
errors: "Errors"
},
error: function(e) {
display_kendoui_grid_error(e);
// Cancel the changes
this.cancelChanges();
},
pageSize: 2,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
pageable: {
refresh: true,
pageSizes: [10,20,30]
},
editable: {
confirmation: false,
mode: "inline"
},
scrollable: false,
columns: [{
field: "CategoryName",
title: "CategoryName",
width: 100
}, {
field: "CategoryId",
title: "Edit",
width: 100,
template: 'Edit'
}]
});
});
</script>
This is my controller side http post action:
[HttpPost]
public ActionResult categoriesList(DataSourceRequest command)
{
Categories categoriesBal = new Categories();
List<CategoryModel> categoriesList = new List<CategoryModel>();
var category = GetCategory();
ViewBag.Category = GetCategory();
List<Category> categoryDetails = categoriesBal.fetchCategory();//here i am fetching categoryid,name
var gridModel = new DataSourceResult
{
Data = categoryDetails.Select(x =>
{
var categoryModel = new CategoryModel();
categoryModel.CategoryId = x.CategoryId;
categoryModel.CategoryName = x.Name;
return categoryModel;
}),
Total = categoryDetails.Count
};
return Json(gridModel);
}
This is my DataSourceRequest class
public class DataSourceRequest
{
public int Page { get; set; }
public int PageSize { get; set; }
public DataSourceRequest()
{
this.Page = 1;
this.PageSize = 10;
}
}
This is my Category model:
public class CategoryModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public int frequency { get; set; }
public virtual ICollection<SubcategoryModel> subCategory { get; set; }
}
i am having 12 category.when i am setting item per page dat is 2 it display all records.
can any body tell me what is the problem in my code and how to perform sorting and filtering??
Change your schema to act as a function
schema: {
data: function(response) {
return response.Data ;
}
total: function(response) {
return response.Total;
}
},
and pageable to
pageable: {
refresh: true,
pageSizes: [2,10,20,30]
},
and do not see the use of DataSourceRequest in your code since you don't send the pageSize from the read . send a object of DataSourceRequest and return only what is specify in your DataSourceRequest . in this case only two records
transport: {
read: function (options) {
var commandOBJ=[{
Page: 1, // Once the first two item is loaded and you click for the next page you will have the page in "options" (should be like options.page)
PageSize:2
}];
$.ajax({
url:"#Html.Raw(Url.Action("categoriesList", "Admin"))",
data: { command: bookmarkID },
dataType: "json",
cache: false,
success: function (result) {
options.success(result);
},
error: function (result) {
options.error(result);
}
});
}
}
Now from your action categoriesList check the command and send data accordingly . and your read action can be a GET instead of POST . hope this Helps
Instead of using your custom DataSourceRequest class, use the KendoGridMvcRequest class from this project, this class correctly maps the paging, sorting and filtering.
Also available as nuget.
See this link for demo.

In Rally, when copying a test set, how do you copy the test case results with the test set?

I'm fairly new to Rally and so far have only used the web interface (I haven't used the Rally APIs from a programming languages yet). Occasionally we have a test set that we don't finish in an iteration, so we'd like to be able to copy the test set to the next iteration but retain the test case results entered so far in the new iteration so that we don't have to look in 2 different places for the complete test set results. Perhaps one solution is better iteration planning, but I'm still curious if there's a way to copy test case results along with a test set when copying a test set.
Test Case Result cannot be copied. It can be created in UI or Web Services API with the identical data, but it does not support a copy functionality.
For a general example of how to create a TCR object using a browser REST client see this SO post here.
Here is a poof of concept app that creates a copy of an existing TCR. The app filters TestSets by Iteration, and loads those TestSets into a combobox. Based on the TestSet selection in the combobox another combobox is created, populated with TestCases. When a Test Case is selected from that combobox a grid is built with TestCaseResults. Doubleclicking on a TCR in the grid will invoke a copy function. See AppSDK2 topic on how to copy records here. You may extend the code per your specifications. The source is in this github repo.
Minimally you need to change ObjectIDs of destination test case and destination test set in _copyRecordOnDoubleClick method below:
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select an Iteration:',
labelWidth: 100,
width: 300
},
onScopeChange: function() {
if (this.down('#testSetComboxBox')) {
this.down('#testSetComboxBox').destroy();
}
if (this.down('#testCaseComboxBox')) {
this.down('#testCaseComboxBox').destroy();
}
if (this.down('#resultsGrid')) {
this.down('#resultsGrid').destroy();
}
var testSetComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'testSetComboxBox',
storeConfig: {
model: 'TestSet',
pageSize: 100,
autoLoad: true,
filters: [this.getContext().getTimeboxScope().getQueryFilter()]
},
fieldLabel: 'select TestSet',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
else{
console.log('selected iteration has no testsets');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestSetSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(testSetComboxBox);
},
_onTestSetSelected:function(selectedTestset){
var testCases = selectedTestset.getCollection('TestCases', {fetch: ['FormattedID','ObjectID', 'Results']});
var ts = {
FormattedID: selectedTestset.get('FormattedID'),
TestCaseCount: selectedTestset.get('TestCases').Count,
TestCases: [],
ResultCount: 0
};
testCases.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(testcase){
console.log("testcase.get('FormattedID')", testcase.get('FormattedID'));
console.log("testcase.get('Results').Count", testcase.get('Results').Count);
ts.ResultCount = testcase.get('Results').Count;
ts.TestCases.push({_ref: testcase.get('_ref'),
FormattedID: testcase.get('FormattedID'),
ObjectID: testcase.get('ObjectID')
});
}, this);
this._makeTestCaseCombobox(ts.TestCases);
},
scope: this
});
},
_makeTestCaseCombobox:function(testcases){
if (this.down('#testCaseComboxBox')) {
this.down('#testCaseComboxBox').destroy();
}
if (this.down('#resultsGrid')) {
this.down('#resultsGrid').destroy();
}
if (testcases.length>0) {
var idArray = [];
_.each(testcases, function(testcase){
console.log(testcase);
console.log('OID', testcase['ObjectID']);
idArray.push(testcase['ObjectID']);
});
console.log('idArray',idArray);
var filterArray = [];
_.each(idArray, function(id){
filterArray.push(
{
property: 'ObjectID',
value:id
}
)
});
var filters = Ext.create('Rally.data.QueryFilter', filterArray[0]);
filterArray = _.rest(filterArray,1);
_.each(filterArray, function(filter){
filters = filters.or(filter)
},1);
var testCaseComboxBox = Ext.create('Rally.ui.combobox.ComboBox',{
id: 'testCaseComboxBox',
storeConfig: {
model: 'TestCase',
pageSize: 100,
autoLoad: true,
filters:filters,
fetch: true
},
fieldLabel: 'select TestCase',
listeners:{
ready: function(combobox){
if (combobox.getRecord()) {
this._onTestCaseSelected(combobox.getRecord());
}
else{
console.log('selected testset has no testcases');
}
},
select: function(combobox){
if (combobox.getRecord()) {
this._onTestCaseSelected(combobox.getRecord());
}
},
scope: this
}
});
this.add(testCaseComboxBox);
}
else{
console.log('selected testset has no testcases');
}
},
_onTestCaseSelected:function(selectedTestcase){
var results = selectedTestcase.getCollection('Results', {fetch: ['ObjectID','Date', 'TestSet', 'TestCase', 'Build', 'Verdict']});
var tc = {
ObjectID: selectedTestcase.get('ObjectID'),
FormattedID: selectedTestcase.get('FormattedID'),
Results: []
};
results.load({
callback: function(records, operation, success){
Ext.Array.each(records, function(result){
console.log("result.get('ObjectID')", result.get('ObjectID'));
console.log("result.get('Verdict')", result.get('Verdict'));
tc.Results.push({_ref: result.get('_ref'),
ObjectID: result.get('ObjectID'),
Date: result.get('Date'),
Build: result.get('Build'),
Verdict: result.get('Verdict')
});
}, this);
this._updateGrid(tc.Results);
},
scope: this
});
},
_updateGrid: function(results){
var store = Ext.create('Rally.data.custom.Store', {
data: results,
pageSize: 100
});
if (!this.down('#resultsGrid')) {
this._createGrid(store);
}
else{
this.down('#resultsGrid').reconfigure(store);
}
},
_createGrid: function(store){
var that = this;
var that = this;
var resultsGrid = Ext.create('Rally.ui.grid.Grid', {
id: 'resultsGrid',
store: store,
columnCfgs: [
{
text: 'ObjectID ID', dataIndex: 'ObjectID',
},
{
text: 'Date', dataIndex: 'Date',
},
{
text: 'Build', dataIndex: 'Build',
},
{
text: 'Verdict', dataIndex: 'Verdict',
},
],
listeners: {
celldblclick: function( grid, td, cellIndex, record, tr, rowIndex){
that._copyRecordOnDoubleClick(record);
}
}
});
this.add(resultsGrid);
},
_copyRecordOnDoubleClick: function(record){
var that = this;
console.log('record', record);
Rally.data.ModelFactory.getModel({
type: 'TestCaseResult',
success: function(model) {
that._model = model;
var copy = Ext.create(model, {
Date: record.get('Date'),
Build: record.get('Build'),
Verdict: record.get('Verdict'),
TestCase: '/testcase/17237838118',
TestSet: '/testset/17234968911'
});
copy.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log('result',result);
}
else{
console.log("problem");
}
}
});
}
});
}
});

implementing jTable MVC 4

I have following
****** controller ***********
namespace DLM.Controllers{
public class BooksController : Controller
{
private IRepositoryContainer _repository;
//
// GET: /Books/
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult ListBooks(int jtStartIndex = 0, int jtPageSize = 0, string jtSorting = null)
{
try
{
//Get data from database
int bookCount = _repository.BookRepository.GetBooksCount();
List<Books> book = _repository.BookRepository.GetBooks(jtStartIndex, jtPageSize, jtSorting);
//Return result to jTable
return Json(new { Result = "OK", Records = book, TotalRecordCount = bookCount });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}
***** ListBooks View ************
{
#Styles.Render("~/Scripts/jtable/jtable.2.3.0/themes/metro/darkgray/jtable.min.css")
<script src="/Scripts/jtable/jtable.2.3.0/jquery.jtable.min.js" type="text/javascript"></script>
<div id="BookTableContainer"></div>
<script type="text/javascript">
$(document).ready(function () {
$('#BookTableContainer').jtable({
title: 'The Student List',
paging: true, //Enable paging
pageSize: 10, //Set page size (default: 10)
sorting: true, //Enable sorting
defaultSorting: 'Book Title ASC', //Set default sorting
actions: {
listAction: '/Books/Index',
deleteAction: '/Books/DeleteBook',
updateAction: '/Books/UpdateBook',
createAction: '/Books/AddBook'
},
fields: {
BooksID: {
key: true,
create: false,
edit: false,
list: false
},
Code_No: {
title: 'Book Code',
width: '23%'
},
Title: {
title: 'Book Title',
list: false
},
Author: {
title: 'Author',
list: false
},
Page_No: {
title: 'Number of Pages',
width: '13%'
},
Y_of_P: {
title: 'Year of Publication',
width: '12%'
},
Language: {
title: 'Language',
width: '15%'
},
Subject: {
title: 'Subject',
list: false
},
Publisher: {
title: 'Publisher',
list: false
},
Keyword: {
title: 'Keywords',
type: 'textarea',
width: '12%',
sorting: false
}
}
});
//Load student list from server
$('#BookTableContainer').jtable('load');
});
</script>
}
ISSUE *****************
When I am trying to access /Books/ListBooks
There is an error The resource cannot be found.
Please help me out i am new to jTable and it's implementation.
Use ListBooks action instead of Index action in jtable listAction. Index action will be used to render the view and after the jquery will load the data from ListBooks
I think you are requesting /Books/Listbooks through browser URL. Browser by default uses get method to get data from server and you putted HttpPost DataAnnotation over the method thats why you are getting error so if you want output makes following two changes.
1)Remove HttpPost Data Annotation of ListBooks Method.
2)Add JsonRequestBehavior.AllowGet in return Json method as follows
return Json(new { Result = "OK", Records = book, TotalRecordCount = bookCount }, JsonRequestBehavior.AllowGet )
Now Your method will work Perfectly.