Related
I am working on Kendo UI jQuery grid CRUD. I can get data display in the grid, but not adding new records.
When I click the update button to add a record after filling up columns in the pop-up window, nothing is posted to the server side as every property has a null value.
The picture shows what I got when the button is pressed.
Controller:
[HttpPost]
public JsonResult AddLostProperty(LostPropertyViewModel lostProperty)
{
try
{
using (var dbContext = new DBEntities())
{
if (lostProperty != null)
{
var newLostProperty = new sz_LostProperty()
{
Name = lostProperty.PropertyName,
CategoryId = dbContext.sz_PropertyCategory.Where(x => x.Name == lostProperty.CategoryName).Select(c => c.Id).FirstOrDefault(),
Description = lostProperty.PropertyDescription,
FoundDate = lostProperty.FoundDate,
FoundLocation = lostProperty.FoundLocation,
CratedDate = DateTime.UtcNow.Date,
CratedBy = ""
};
dbContext.sz_LostProperty.Add(newLostProperty);
dbContext.SaveChanges();
return Json(new { Success = true, Message = "The Property has been added." });
}
else
{
return Json(new { Success = false, Message = "No lost property added." });
}
}
}
catch (Exception e)
{
return Json(new { Success = false, Message = "Error: " + e });
}
}
JavaScript:
<script>
$(document).ready(function () {
var serviceBaseUrl = "#Request.Url.ToString()",
lostPropertyDataSource = new kendo.data.DataSource({
transport: {
create: {
url: serviceBaseUrl + "/AddLostProperty",
type: "POST",
dataType: "json",
complete: function (e) {
$('#manageLostPropertiesGrid').data('kendoGrid').dataSource.read();
}
},
read: {
url: serviceBaseUrl + "/GetLostProperties",
type: "GET",
dataType: "json"
},
update: {
url: serviceBaseUrl + "/UpdateLostProperty",
type: "PUT",
dataType: "json"
},
destroy: {
url: serviceBaseUrl + "/DeleteLostProperty",
type: "DELETE",
dataType: "json"
},
parameterMap: function (options, operation) {
if (operation !== "read" && options.models) {
return { models: kendo.stringify(options.models) };
}
}
},
batch: true,
pageSize: 20,
schema: {
model: {
id: "PropertyId",
fields: {
PropertyId: { editable: false, nullable: true, type: "number" },
PropertyName: { type: "string", editable: true, validation: { required: true } },
CategoryId: { type: "number", editable: true, validation: { required: true } },
PropertyDescription: { validation: { required: false } },
Image: { validation: { required: false } },
FoundDate: { type: "Date" },
FoundLocation: { editable: true, validation: { required: false } }
}
}
}
});
$("#manageLostPropertiesGrid").kendoGrid({
dataSource: lostPropertyDataSource,
pageable: true,
height: 550,
toolbar: ["create"],
columns: [
{ field: "PropertyName", title: "Property Name", width: "150px" },
{ field: "CategoryName", title: "Category", editor: propertyCategoryList,/* template: "#=CategoryName#", */width: "150px"},
{ field: "PropertyDescription", title: "Description", width: "200px" },
{ field: "FoundDate", title: "Found Date", template: "#= kendo.toString(kendo.parseDate(FoundDate, 'dd-MM-yyyy'), 'dd-MM-yyyy') #", width: "130px" },
{ field: "FoundLocation", title: "Found Location", width: "120px" },
{ command: ["edit", "destroy"], title: " ", width: "250px" }],
editable: "popup"
}).data("kendoGrid");
});
From the browser, I can see the object sent to the server below:
What am I doing wrong?
I believe that in this case is problem in your parameter type at server side.
You have enabled batch: true editing which is useful if you want make many changes in your grid but send only one request with changed models in the end. It is very useful for example in case of inCell edit mode, when you would see many many requests and so decrease them is something you want, but in case of popup edit, I personally do not see any reason to use batch editing, but yes, I know Telerik has this in their demo.
So, because batch editing is enabled, there is called parameterMap before request is executed. (note: parameterMap is called only if you have batch edit enabled, otherwise it's ignored). That parameterMap wraps all your models into json string array and send that array with request to the server. In your case, there is always one record edited, but it doesn't matter - it will be sent as an array (in json string format).
Because it is sent as serialized string, you can
1) Change parameter of your AddLostProperty method to string models and then deserialize into array which allows you to work with it as you are used to
public ActionResult AddLostProperty(string models)
{
...
var data = JsonConvert.DeserializeObject<IEnumerable<LostPropertyViewModel>>(models);
...
}
2) If we will follow Telerik demo, you can use such implementation
public ActionResult AddLostProperty()
{
var products = this.DeserializeObject<IEnumerable<LostPropertyViewModel>>("models");
if (products != null)
{
//logic
}
return this.Jsonp(products);
}
3) And this is solution I would prefer
Just remove batch: true and parameterMap (since without batch it's useless) - and it should start send single object to your server method.
In order to make a real-time .NET Web application, I am using SignalR for Kendo grid, which works with read, update, destroy method on the grid.
However, my situation is creating and updating records from other pages, the Kendo Grid just for reading data. I would like to implement SignalR to notify user whenever a new record is added to the database.
Here is my code.
SignalRHub class:
public class SignalRHub : Hub
{
private DbEntities db;
public SignalRHub()
{
db = new DbEntities();
}
public IEnumerable<ViewTicketModel> Read()
{
return db.Post_User.AsEnumerable()
.Select(ticket => new ViewTicketModel
{
Id = ticket.Id,
BuyerName = ticket.Name,
DateCreated = ticket.CreatedOn.ToString("dd/MM/yyyy"),
BuyerPhoneNumber = ticket.Mobile,
Details = ticket.Details,
Location = ticket.Location,
})
.OrderByDescending(x => DateTime.ParseExact(x.DateCreated, "dd/MM/yyyy", null))
.ToList();
}
}
Index.cshtml:
var connection = $.connection;
var hub = connection.signalRHub;
var hubStart = connection.hub.start();
console.log("here");
var signalRDataSource = new kendo.data.DataSource({
type: "signalr",
autoSync: true,
push: function(e) {
var notification = $("#notification").data("kendoNotification");
notification.success(e.type);
},
schema: {
model: {
id: "Id",
fields: {
"Id": { editable: false, type: "Number" },
"BuyerName": { type: "string" },
"DateCreated": { type: "string" },
"BuyerPhone": { type: "string" },
"Details": { type: "string" },
"Location": { type: "string" }
}
}
},
transport: {
signalr: {
promise: hubStart,
hub: hub,
server: {
read: "read",
},
client: {
read: "read",
}
}
},
pageSize: 10,
});
$("#grid").kendoGrid({
height: 700,
filterable: {
extra: false,
},
pageable: true,
sortable: {
mode: "multiple",
allowUnsort: true
},
columns: [
{ field: "Id", title: "Notification Id", width: 100, hidden: true },
{
field: "DateCreated",
title: "Date Created",
width: 150,
filterable: {
ui: "datetimepicker"
}
},
{ field: "Location", title: "Location", width: 150 },
{ field: "BuyerName", title: "Buyer Name", width: 120, hidden: true },
{ field: "BuyerPhoneNumber", title: "Buyer Phone", width: 120, hidden: true },
],
dataSource: signalRDataSource
});
by other pages, you meant different application? If that is the case, this will complicate the issue.
remember kendo grid only comes with 4 default signal actions (create, update, read, destroy). Any other will have to be implemented by you.
here's an example how you can make your "connected" clients do a refresh read.
in your hub:
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<Dashboard>();
context.Clients.All.reload();
in your client html page:
<script type="text/javascript">
var hub = $.connection.dashboard;
hub.client.reload = function () {
updategrid();
};
var hubStart = $.connection.hub.start();
function updategrid()
{
$('#GridName').data('kendoGrid').dataSource.read();
$('#GridName').data('kendoGrid').refresh();
}</script>
This will force all your connected clients to do a refresh.
The perfect scenario would be to the appropriate changes pushed out to the client. This implementation can get tricky, because you don't know what filter/sort/grouping the user has on their side. However, it's achievable. You can have each connected client to send it's filter/grouping/sort back to the server and pull only the appropriate change.
I am using Qtip tooltip and my content is from JSON.
When i click the tooltip to display the content, i get an error that 'data is not defined' when inspecting the element in chrome.
This is my javascript code.
$('#accordion_functions ul li a span:nth-child(2)').each(function () {
$(this).qtip({
content: {
text: function(event,api) {
$.ajax({
url: 'http://localhost:51783/Help/GetHelpText.ashx',
type: 'GET',
dataType: 'json',
data: {
id: $(this).attr('id') // Pass through the ID span
},
})
.then(function(content) {
var content = 'Help' + data.Text;
api.set('content.text', content);
}, function(xhr, status, error) {
api.set('content.text', status + ': ' + error);
});
return 'Loading' // Set some initial loading text
}
},
hide: {
event: 'click',
fixed: true,
delay: 50
},
show: {
event: 'click',
solo: true
},
events: {
show: function (event, api) {
$(api.elements.target).addClass('highlight');
},
hide: function (event, api) {
$(api.elements.target).removeClass('highlight');
}
},
style: {
width: 1200,
padding: 5,
tip: 'bottomRight',
},
position: {
my: 'bottom right',
at: 'bottom middle'
}
});
The data posted by the url looks like this:
[{"id":"PCG01","Form":"Party Company","Tab":"General","Text":"Help needed here"},{"id":"PCG02","Form":"Party Company","Tab":"Contact","Text":"This is a second help"},{"id":"PCG03","Form":"Party Company","Tab":"Settlement","Text":"Third help"},{"id":"PCG04","Form":"Party Company","Tab":"Client","Text":"Fourth help"},{"id":"PCG05","Form":"Party Company","Tab":"Trade Constraints","Text":"Fifth help"},{"id":"PCG06","Form":"Party Company","Tab":"Attachments","Text":"Sixth help"}]
What could i be doing wrong?
isn't it in the .then
.then(function(content) {
var **contentText** = 'Help' + **content**.Text;
api.set('content.text', **contentText**);
The problem was in the .then. I were basically accessing the json object in a wrong way.
I changed .then from:
.then(function(content) {
var content = 'Help' + data.Text;
api.set('content.text', content);
},
To:
.then(function(content) {
var content = 'Help' + content[0].Text +'';
api.set('content.text', content);
},
I have 4 grid tables. They use the same modal. I cannot use the same store as each grid has to send a parameter value to get the data from db. So, what's happening is I ended up making 4 different stores and then when I load my application, it lags because it waits to load all the 4 stores. this is my grid.. so I have 3 more grids like this
this.grid1 =Ext.create('Ext.grid.Panel',{
title:'GridView App', store: store, loadMask:true,
columns:[
{ header:'Q1', sortable:true, dataIndex:'Q1', flex:1,},
{ header:'Q2', sortable:true, dataIndex:'Q2', flex:1,},
{ header:'Q3', sortable:true, dataIndex:'Q3', flex:1,},
{ header:'Q4', sortable:true, dataIndex:'Q4', flex:1,}
and this is my store1... and similarly I have 3 more stores like this each with parameter Q2, Q3, Q4 respectively
var store1 =Ext.create('Ext.data.JsonStore',{
storeId:'myData', scope:this,
fields:[
{ name:'Q1', type:'int'},
{ name:'Q2', type:'int'},
{ name:'Q3', type:'int'},
{ name:'Q4', type:'int'}
],
sorters:[{ property:'Q1', direct:'ASC'}],
proxy:{
type:'ajax',
url:'GridView/writeRecord',
extraParams: { ID: Q1 },
reader: newExt.data.JsonReader({
root:'myTable',
totalProperty:'count'
})
}
});
Is there a faster/better way to implement it than the way that I have?
UPDATE -
SECOND UPDATE -
Here is my layout for the whole application... my rightcontainer is disabled at first and contains the actual grids and forms, and click on the item on tab enables the rightcontainer and loads all the grid.
Ext.define('ExtjsApp.app1.appPanel', {
extend: 'Ext.panel.Panel',
alias: 'widget.mypanel',
layout: {
type: 'vbox',
align: 'stretch'
},
scope: this,
titleAlign: 'center',
minWidth: 900,
bodyPadding: 10,
requires: [],
items: [],
constructor: function () {
this.callParent(arguments);
this.regForm = Ext.create('ExtjsApp.app1.RegForm', {});
leftTreeStore = Ext.create('Ext.data.TreeStore', {
scope: this,
storeId: 'leftTreeStore',
fields: [
{ name: 'text', type: 'string' },
{ name: 'dataId', type: 'string' },
{ name: 'listName', type: 'string' },
{ name: 'leaf', type: 'bool' }
],
root: { expanded: true },
proxy: {
type: 'ajax',
url: 'app1/getRecords',
extraParams: { organization: 'GOOGLE' },
reader: { type: 'json' }
},
autoLoad: true,
expanded: true,
autoSync: true,
listeners: {
}
});
allRecordsStore = Ext.create('Ext.data.TreeStore', {
id: 'allRecordsStore',
autoLoad: false,
autoSync: false,
scope: this,
fields: [
{ name: 'text', type: 'string' },
{ name: 'dataId', type: 'string' },
{ name: 'listName', type: 'string' },
{ name: 'leaf', type: 'bool' }
],
root: { expanded: true },
proxy: {
type: 'ajax',
url: 'app1/getRecords',
extraParams: { organization: 'GOOGLE' },
reader: { type: 'json' }
}
});
this.currentMonthsTree = Ext.create('Ext.tree.TreePanel', {
scope: this,
title: 'Current 12 Months',
titleAlign: 'center',
tabIndex: 0,
height: 500,
flex: 1,
rootVisible: false,
store: leftTreeStore,
id: 'currentMonthsTree',
useArrows: true,
hideHeaders: true,
columns: [
{
xtype: 'treecolumn',
id: 'ID',
dataIndex: 'text',
flex: 1
}
],
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop',
enableDrop: false,
appendOnly: false,
enableDrag: false
},
listeners: {
itemclick: function (view, rec, item) {
if (rec.isLeaf()) {
alert('isLeaf');
}
else if (!rec.isLeaf()) {
alert('isNotLeaf');
}
}
},
allowCopy: true,
copy: true
}
});
this.currentMonthsTree.on('selectionchange', function (selected) {
FnDisplayRecord(selected.selected.items[0]);
});
this.allRecordsTree = Ext.create('Ext.tree.TreePanel', {
scope: this,
title: 'All Records',
titleAlign: 'center',
flex: 1,
tabIndex: 1,
rootVisible: false,
store: allRecordsStore,
id: 'allRecordsTree',
useArrows: true,
hideHeaders: true,
columns: [
{
xtype: 'treecolumn',
id: 'ID',
dataIndex: 'text',
flex: 1
}
],
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop',
enableDrop: false,
enableDrag: false,
appendOnly: false
},
listeners: {
itemclick: function (view, rec, item) {
if (rec.isLeaf()) {
alert('isLeaf');
}
else if (!rec.isLeaf()) {
alert('isNotLeaf');
}
}
},
allowCopy: true,
copy: true
}
});
this.allRecordsTree.on('selectionchange', function (selected) {
FnDisplayRecord(selected.selected.items[0]);
//alert('Hello');
});
function FnClearValues() {
//Clear All Values
alert('ClearALLValues');
}
function FnSetValues(myObj) {
//I set all my form values using Ext.getCmp
Ext.getCmp('Textl').setValue(myObj.Text1);
}
function FnDisplayRecord(row) {
if (row.get('leaf') == true) {
console.log(row.data.dataId);
var tempID = row.data.dataId;
Ext.getCmp('rightContainer').setLoading(true, true);
Ext.getCmp('requisitionPOGridPanel').store.loadData([], false);
Ext.Ajax.request({
method: 'GET',
url: 'app1/getRecord',
headers: { 'Content-Type': 'application/json' },
dataType: 'json',
params: {
ID: tempID
},
success: function (response) {
Ext.getCmp('rightContainer').setLoading(false, false);
myObj = Ext.JSON.decode(response.responseText);
if (AsbestosObj.DateIssued != '') {
FnSetValues(AsbestosObj);
Ext.getCmp('GridPanel').store.load({ params: { ID: tempID} });
Ext.getCmp('Grid1').store.load({ params: { ID: tempID, qID: 'Q01'} });
Ext.getCmp('Grid2').store.load({ params: { ID: tempID, qID: 'Q02'} });
Ext.getCmp('Grid3').store.load({ params: { ID: tempID, qID: 'Q03'} });
Ext.getCmp('Grid4').store.load({ params: { ID: tempID, qID: 'Q04'} });
}
else { FnClearValues(); }
},
failure: function () {
Ext.Msg.alert('Message', 'Error');
}
});
}
else if (row.get('leaf') == false) {
FnClearValues();
}
}
this.rightContainer = Ext.create('Ext.form.Panel', {
scope: this,
id: 'rightContainer',
layout: {
type: 'vbox',
align: 'stretch',
pack: 'start'
},
autoScroll: true,
disabled: true,
border: 1,
flex: 1,
items: [
this.regForm
]
});
this.tabContainer = Ext.create('Ext.tab.Panel', {
scope: this,
activeTab: 0,
flex: 0.5,
id: 'tabContainer',
layout: { type: 'vbox', align: 'stretch' },
plain: true,
listeners: {
tabchange: function (panel, newTab, oldTab) {
Ext.getCmp('rightContainer').disable();
FnClearValues();
var getTabStat = this.getActiveTab();
if (getTabStat.tabIndex == 0) {
Ext.getCmp('currentMonthsTree').store.load();
}
else if (getTabStat.tabIndex == 1) {
Ext.getCmp('allRecordsTree').store.load();
}
}
},
items: [
this.currentMonthsTree, this.allRecordsTree
]
});
this.mainContainer = Ext.create('Ext.container.Container', {
scope: this,
bodyPadding: 10,
title: 'MAIN',
layout: {
type: 'hbox',
align: 'stretch'
},
flex: 1,
items: [
this.tabContainer,
{ xtype: 'splitter', width: 5, animate: true },
this.rightContainer
]
});
this.add(this.mainContainer);
},
loadingOn: function () {
setTimeout(function () { Ext.getCmp('currentMonthsTree').setLoading(true, true); }, 100);
},
loadingOff: function () {
setTimeout(function () { Ext.getCmp('currentMonthsTree').setLoading(false, false); }, 100);
}
});
Please reference my SO question that is very similar: Combo box loads too slow
Basically, you will want to define all your models as you do normally.
Then you will want to define all the stores for your page as array stores without proxies, like this:
var myStore1 = Ext.create("Ext.data.ArrayStore", {
model: "MyModel1",
data: []
});
var myStore2 = Ext.create("Ext.data.ArrayStore", {
model: "MyModel1",
data: []
});
Then you will want to create a single call to wherever you are getting your data from, you will need to change the server to output all the arrays into an single JSON object, something like this, and for super optimization, make them array arrays, this would be the output I would expect from the server:
{
grid_data_1: [...],
grid_data_2: [...]
}
Then on your webpage after you create all the stores, make a single ajax call to get the data for all four grids:
Ext.Ajax.request({
url: 'url',
method: 'GET',
params: {
...whatever you want
},
success: function (response, opts) {
var result = Ext.decode(response.responseText);
myStore1.loadData(result.grid_data_1);
myStore2.loadData(result.grid_data_2);
...
},
});
This will make it much more efficient, you probably don't need to use array arrays in your case because there is only 5 rows per grid, but optimizing 4 ajax calls into one should have a large impact.
You cannot do much more here. I think you can do some micro tunes but I doubt they are worth time they took to identify. If your app do the following you've done it all the right way;
Time till your is loaded
Init only the required controller and the stores (check each request)
Show the main view
As soon as your grids get rendered they will fire the load operation, at least when they have a paging toolbar. You could begin the load operation earlier, meaning before you create the view that may give you some milliseconds but I doubt that you can save more time.
This is of course based on the available information's.
Can`t really understand where is a mistake..
I have a form with fileuploadfield. Request is sended good, action on my controller gets all params, works with them, and sends response to browser. But in html page, after submiting the form, always fires FAILURE event, and never SUCCESS.
Client Side Code
Ext.create('Ext.form.Panel', {
renderTo: 'Container',
bodyPadding: '10 10 0',
items: [
{
xtype: 'form',
width: 300,
border: false,
fileUpload: true,
items: [
{
xtype: 'combobox',
id: 'PayTypes',
width: 215,
store: PayTypesStore,
valueField: 'id',
displayField: 'Name',
editable: false,
name: 'id'
}
,
{
xtype: 'filefield',
id: 'form-file',
name: 'file',
buttonText: '',
buttonConfig: {
iconCls: 'upload-icon'
}
}
],
buttons: [
{
text: 'Send',
handler: function () {
var form = this.up('form').getForm();
if (form.isValid()) {
form.submit({
url: 'UploadFile',
waitMsg: 'Uploading your photo...',
success: function (fp, o) {
console.log("success");
msg('Success', 'Processed file on the server');
},
failure: function (form, action) {
console.log(action);
Ext.Msg.alert('Failed', action.result ? action.result.message : 'No response');
}
});
}
}
}
]
}
]
});
Server Side Code:
public JsonResult UploadFile(HttpPostedFileWrapper file, int id)
{
var response = Json(new { success = true });
response.ContentType = "text/html";
return Json(response);
}
Response, recieved on the client side:
{"ContentEncoding":null,"ContentType":"text/html","Data":"success":true},"JsonRequestBehavior":1,"MaxJsonLength":null,"RecursionLimit":null}
What i need to fix in my code to get SUCCESS event after sumniting form?
You called Json method twice. The only thing you need is
public JsonResult UploadFile(HttpPostedFileWrapper file, int id)
{
return Json(new { success = true });
}