extjs store error handling - json

I am trying to handle an exception in an Ext.data.Store instance when creating a new Ext.data.Record. When the server responds with the following json:
{"success": false, "message": "some text"}
I get an exception of type 'request', even though the server returns an HTTP 200 Response!
To get a 'remote' error I have to create an object with the root property
({
"success": false,
"message": "some text",
"data": {
"PositionId": "00000000-0000-0000-0000-000000000000",
"Name": "123"
}
})
...but I don't want this. Is there any way to change this behaviour?
Also, when I insert a record in the store, it is automatically added to the associated grid, but if an error occurs it remains there, so I need to reload store on every error. Is there any better way to do this?

You should catch one of the two Store events:
loadexception (deprecated)
exception
For example you could:
// make the store
var myStore = new Ext.data.Store({...});
// catch loading exceptions
myStore.on('exception',function( store, records, options ){
// do something about the record exception
},this);
// load store
myStore.load();
You could also just use the success and failure events from the store to take action based on the success flag.

Finally, I've found out that if I send back empty data it works as expected. So I don't need to send back any fictional data, my server response is:
({
"success": false,
"message": "some text",
"data": {}
})

when success is false operation doesn't have a response property. This thread explains it very clairly!
http://www.sencha.com/forum/showthread.php?196013-access-operation.response-when-success-false
Example:
Ext.define("SC.store.SegurosCancelacionStore", {
extend: "Ext.data.Store",
model: "SC.model.PersonaSeguro",
proxy: {
timeout: 90000,
actionMethods: {
read : 'POST'
},
type: "ajax",
url: "../SegurosFinsolCancelacionServlet",
reader: {
type: "json",
root: "seguros",
messageProperty : 'msjError' //without this, it doesn't work
}
},
autoLoad: false
});
Implementation:
storeSegurosCancelacion.load({
params: {
'sucursal':sucursal,
'persona': persona
},
callback:function(records, operation, success){
msg.hide();
if(success == true){
if(records.length == 0){
Ext.Msg.alert('Resultado', 'No se ha encontrado informaciĆ³n');
}
}
if(success == false){
try{
Ext.Msg.alert('Error', operation.getError()); // way more elegant than ussing rawData etc ...
}catch(e){
Ext.Msg.alert('Error', 'Error inesperado en el servidor.');
}
}
}
});
Best regards
#code4jhon

Related

tabulator Unable to process data due to invalid data type

I'm trying to learn how to load data into tabulator from JSON data. I'm running into the error
tabulator.min.js:3 Data Loading Error - Unable to process data due to invalid data type
Expecting: array
Received: object
I think I need to use the ajaxResponse:function but not entirely sure how. Here's a snippet of code I'm using.
//define data
var data = [ ]
var table = new Tabulator("#tabulator-example", {
height:800,
data:data,
layout:"fitDataStretch",
//ajaxURL:"omdata.json",
// ajaxProgressiveLoad:"scroll",
// paginationSize:20,
placeholder:"No Data Set",
autoResize:true,
ajaxContentType : "application/json; charset=utf-8",
ajaxContentType:"json",
tooltips:true,
addRowPos:"top",
resizableRows:true,
initialSort:[
{column:"feature", dir:"asc"},,
],
columns:[
<REMOVED>
],
});
//trigger AJAX load on "Load Data via AJAX" button click
document.getElementById("ajax-trigger").addEventListener("click", function(){
table.setData("omdata.json");
});
$("#tabulator-controls input[name=feature]").on("keyup", function(){
table.setFilter( "feature", "like", $(this).val())
});
Ok I figured it out. Here's what I did
var table = new Tabulator("#tabulator-example", {
height:800,
data:data,
layout:"fitDataStretch",
//ajaxURL:"omdata.json",
// ajaxProgressiveLoad:"scroll",
// paginationSize:20,
placeholder:"No Data Set",
autoResize:true,
ajaxContentType : "application/json; charset=utf-8",
ajaxContentType:"json",
tooltips:true,
addRowPos:"top",
resizableRows:true,
initialSort:[
{column:"feature", dir:"asc"},,
],
columns:[
<REMOVED>
],
ajaxResponse:function(url, params, response){
//url - the URL of the request
//params - the parameters passed with the request
//response - the JSON object returned in the body of the response.
return response.data; //pass the data array into Tabulator
},
}),

adding JSON as property

I have a JSON object that looks like this:
{
"field": "value",
"field2": "value",
"field3": "value"
}
How can I add this to a keen event as a property similar to the "keen" object so I can reference individual fields, ie. my_property.field1
The properties on your events in Keen are based on whatever JSON you send in when you first post your event. You can post historical events, but you can't add new properties to events you've already posted. Here's an example of sending an event in JavaScript. Say your event is a tweet.
var client = new Keen({
projectId: 'PROJECT_ID',
writeKey: 'WRITE_KEY'
});
var tweet_event = {
keen: {
timestamp: new Date().toISOString(), // time the tweet happened
},
field: "value", // values you mentioned
field2: "value",
field3: "value,
tweet: { // other properties you might have
author: "#michellewetzler",
text: "Dwell on the beauty of life. Watch the stars, and see yourself running with them. (Marcus Aurelius)"
}
}
// Record the event (send it to Keen IO)
client.recordEvent('tweets', tweet_event, function(err, res){ // name your collection here
if (err) {
document.getElementById('yeah').innerHTML = "Something is amiss. Check console for errors. Did you forget a comma perhaps? Or a curly brace?"
}
else {
document.getElementById('yeah').innerHTML = "You just sent an event to Keen IO!"
}
});
then you can reference these properties in queries like:
var client = new Keen({
projectId: "PROJECT_ID",
readKey: "READ_KEY"
});
// count the number of tweets where field = value
var count = new Keen.Query("count", {
event_collection: "tweets",
timeframe: "this_14_days",
filters: [
{
property_name: "field",
operator: "eq",
property_value: value
}
]
});
// Send query
client.run(count, function(err, response){
// if (err) handle the error
console.log('result is: ', response.result);
});

ExtJs5: How to read server response in Model.save

I use Model.save to save data from the ExtJs form. Sometimes server returns operation status in following format:
{"success": false, "error": {"name": "Invalid Name"}}
The following code sends data from form to server:
var model = formPanel.getRecord();
model.save({
callback: function(record, operation, success) {
// operation.response is null,
// and success === true
// how to read server response here?
}
})
Server response is treated as successful because HTTP status is 200. So I I have to read server response to check operation status. But operation.response is null in callback function.
Here is my Model:
Ext.define('My.Model', {
extend: 'Ext.data.Model',
idProperty: 'id',
fields: [
{name: 'id', type: 'auto'},
{name: 'name', type: 'auto'}
],
proxy: {
type: 'rest',
url: 'api/v1/models',
appendId: true,
reader: {
type: 'json',
},
writer: {
type: 'json'
}
}
});
Question: how can I access server response after Model.save's call?
More generic question: is it semantically correct to use Model.load or Model.save to populate/save the ExtJs form?
I'm using ExJs 5.0.1.1255.
I created some simple test code for this:
var Clazz = Ext.define('MyModel', {
extend: 'Ext.data.Model',
proxy: {
type: 'rest',
url: 'api/v1/models'
}
});
var instance = Ext.create('MyModel', {
name: 'MyName'
});
instance.save({
callback: function(record, operation) {
}
});
The server responds with:
{
success: true,
something: 'else'
}
You can see this in a fiddle here: https://fiddle.sencha.com/#fiddle/fhi
With this code, the callback has a record argument, and record.data contains the the original record merged with the server response. In addition, you can do operation.getResponse() (rather than just operation.response) to get full access to the server's response.
In regard to your question on load vs save, if you use view models and bind the model that way, it kind of becomes moot as your form should always reflect the state of the model.
Using model.save() and Model.load() is definitely the correct thing to do.
In addition to providing a custom callback, you should investigate configuring a custom proxy. In a custom proxy, you can provide your own implementation of the extractResponseData method. This would let you centralise your need to examine the server response.

Kendo UI Grid Inserts/Updates create Duplicate Records (again)

I have same problem as Daniel had in this topic, but his solution doesn't work for me:
http://www.kendoui.com/forums/ui/grid/kendo-ui-grid-inserts-updates-create-duplicate-records.aspx#-jhxqRrNAUGsTFJaC-Ojwg
So use-case. Users adds 2 new records one after another:
Presses "Add new record" button of a grid
Fills the fields (name="Alex", amount=10, comment="first").
Record one is ready. Press 'Save'. (data goes to controller and than to Database)
User see one record in a grid
Press "Add new record" button again
Fills fields (name="Bob", amount=20, comment = "second").
Record one is ready. Press 'Save'. Data goes to controller and than to Database.
In this moment something happens and grid send Ajax request again with record one data to controller.
User updates grid and see three records
"Alex | 10 | first" (duplicated record) ID = 1
"Bob | 20 | second" ID = 2
"Alex | 10 | first" ID = 1
They recommend to return an ID for correct binding\update of data source with new record.
And I return it (new ID from database comes in response with bouns entity)! and this doesn't help.
Only if I add first record and refresh page with F5 and after that add second record everything is ok. But if add another one, the third records - problems appears again
Code in controller:
[HttpPost]
public JsonResult Create(BonusDto bonusDto)
{
BonusAggregate bonus;
if (bonusDto.Amount <= 0)
throw new ArgumentOutOfRangeException("Amount should be more than 0");
if (bonusDto.EmployeeId <= 0)
throw new ArgumentNullException("You should specify an existing employee");
using (var dbContext = new DatabaseContext())
{
BonusesRepository = new BonusesRepository(dbContext);
var employeeRepository = new EmployeesRepository(dbContext);
bonus = new BonusFactory(employeeRepository).Create(bonusDto);
BonusesRepository.Save(bonus);
}
HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
return Json(bonus); // try to return ID after bonus was saved
}
UI Code
// creates bonuses grid control
$("#bonusesGrid").kendoGrid({
dataSource: bonusesDataSource,
toolbar: ["create"],
editable: "inline",
columns: [
"BonusId",
"EmployeeId",
{
field: "EmployeeLastName",
editor: employeeAutocompletingEditor,
template: "#=EmployeeLastName#"
},
"Amount",
{
field: "Comment",
titel: "Comment",
editor: textareaEditor,
filterable: {
operators: {
number: {
contains: "Contains"
}
}
}
},
{
command: ["edit"],
title: " "
}
],
save: function(e) {
if (newValueEmployeeId !== undefined &&
newValueEmployeeLastName !== undefined &&
newValueEmployeeLastName !== "") {
setNewValueEmployeeIdAndLastName(newValueEmployeeId, newValueEmployeeLastName);
gridDataSource.model.EmployeeId = newValueEmployeeId; // it's a hack to bind model and autocomplete control
gridDataSource.model.EmployeeLastName = newValueEmployeeLastName;
} else {
gridDataSource.model.EmployeeId = currentValueEmployeeId;
gridDataSource.model.EmployeeLastName = currentValueEmployeeLastName;
}
},
edit: function(e) {
setCurrentValueEmployeeIdAndLastName(e.model.EmployeeId, e.model.EmployeeLastName);
},
cancel: function(e) {
setCurrentValueEmployeeIdAndLastName(e.model.EmployeeId, e.model.EmployeeLastName);
}
});
Bonus data source:
// bind json result from /Bonuses/GetPagedJsonBonuses
var bonusesDataSource = new kendo.data.DataSource({
transport: {
read: {
url: "#Url.Action("GetPagedJsonBonuses", "Bonuses")",
type : "GET",
contentType: "application/json",
dataType: "json",
cache: false
},
create: {
url: "#Url.Action("Create", "Bonuses")",
dataType: "json",
type: "POST"
},
parameterMap: function(options, operation) {
if (operation === "update" || operation === "create") {
// correct format for conversion
var d = new Date(options.Date);
options.Date = kendo.toString(d, dateFormat);
// updates the BonusDTO.EmployeeId with selected value
if (newValueEmployeeId !== undefined)
options.EmployeeId = newValueEmployeeId;
}
if(operation === "read") {
options.filter = setFormattedFilterDate(options.filter);
}
return options;
}
},
pageSize: 15,
serverPaging: true,
serverSorting: true,
serverFiltering: true,
error: showErrorMessage,
schema: {
data: "Data", // PagedResponse.Data
total: "TotalCount", // PagedResponse.TotalCount
model: {
id: "BonusId", // Data
fields: {
EmployeeId: { type: "number" },
EmployeeLastName: {
type: "string",
editable: true,
nulable: false,
validation: { required: {message: "Employee's last name is required"}}
},
Date: {
type: "date",
editable: true,
nullable: false,
validation: {
required: { message: "Date is required to be set" }
}
},
Amount: {
type: "number",
editable: true,
nullable: false,
defaultValue: 1,
validation: {
required: { message: "Amount is required to be set" }
}
},
Comment: { type: "string", editable: true }
} // fields
} // model
}// schema
});
I haven't seen this problem in my code. I do however have a "complete" event handler on my create and update events that refreshed the grid - it may help you:
dataSource: {
type: "jsonp",
transport: {
read: UrlBase + "getAll",
update: {
url: UrlBase + "Update",
dataType: "jsonp",
complete: function (e) {
$("#grid").data("kendoGrid").dataSource.read();
}
},
create: {
url: UrlBase + "create",
dataType: "jsonp",
complete: function (e) {
$("#grid").data("kendoGrid").dataSource.read();
}
},
destroy: {
url: UrlBase + "destroy",
dataType: "jsonp",
complete: function (e) {
$("#grid").data("kendoGrid").dataSource.read();
}
}
},
...
Yes, hamed is correct. Your "create" action result passes in the object from your model that is to be saved to your database. Have the INSERT in your data access layer return the newly created key ("ID") in the database. Use this key to now set the "ID" field on your model that's passed in to the action result and then passed back to the view as JSON. Now the grid should know it's just created this record and does not need to do anything more with it. Otherwise, the model object returns with the "ID" field set to 0 so the grid thinks it still needs to add this record.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Grid_Create([DataSourceRequest] DataSourceRequest request, MyObject obj)
{
if (obj != null && ModelState.IsValid)
{
obj.Id = _myService.Create(obj);
}
return Json(new[] { obj }.ToDataSourceResult(request, ModelState));
}
This error occur when you did not pass Primary key to view in read action.
Regards
An alternative to Quinton Bernhardt's complete event: bind the dataSource.read() to the kendo sync event.
I'm using kendo's C# MVC's html helpers which don't expose the sync event, so I had to modify it after setting up the grid.
On window load:
var grid = $("#GridName").data("kendoGrid");
grid.dataSource.bind("sync", function () {
$("#GridName").data("kendoGrid").dataSource.read();
});
The sync event fires after the save request has been completed. The dataSource.read() gets the latest from the server, including the id that was set server side.
I had a similar issue, did various trials but fixed with the following trial
Jquery
create: {
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
url: "../../ajax/ajaxGv.aspx/addstaff"
},
parameterMap: function (options, operation) {
if (operation == "create" && options.models) {
return JSON.stringify({ "oStaff": options.models });
}
VB.Net
'Adding Staff
<System.Web.Services.WebMethod()> _
Public Shared Sub addStaff(ByVal oStaff As Object)
Dim strJson As String = JsonConvert.SerializeObject(oStaff)
Dim lstStaff As List(Of Staff) = JsonConvert.DeserializeObject(Of List(Of Staff))(strJson)
Dim db As New LiveB2cDataContext
Try
db.Staff.InsertAllOnSubmit(lstStaff)
Next
db.SubmitChanges()
'Fix is that you need to return the objects you have added in the database back as json to kendo
strJson = JsonConvert.SerializeObject(lstStaff, Formatting.None)
WriteJson(strJson) ' Returning the objects added as json back to Kendo
Catch ex As Exception
ErrorLog(ex)
End Try
End Sub
Public Shared Sub WriteJson(strJson As String)
Try
HttpContext.Current.Response.Write(strJson)
HttpContext.Current.Response.Flush()
HttpContext.Current.ApplicationInstance.CompleteRequest()
HttpContext.Current.Response.SuppressContent = True
Catch ex As Exception
ErrorLog(ex)
End Try
End Sub
Fix is that you need to return the objects you have added in the database back as json to kendo
I'm not sure if this is part of your issue, but in your DataSource's schema model you specify that the ID is the field named "BonusId", but that field isn't specified in the array of fields.
I was having a simular issue.
I fixed it but making sure the id in the model is referring to a field:-
model: {
id: "Id",
fields: {
Id: { editable: false, type: "number" },
AnotherName: { editable: true, type: "string" },
AnotherId: { editable: true, type: "number" }
}
}
This may not solve the asker's problem, but hopefully might help someone with the same issue.
For us, this problem was being caused by errors being returned in JSON. Some required properties didn't have a value, but weren't related to the data we were displaying in the grid. Giving those properties a default value in the grid solved the issue.
You can view the JSON that's being returned by using Fiddler Web Debugging Tools.

How to make a store with jsonreader using metadata in Extjs 4?

Is it possible to create a store that will read json, and use fields specified in the metadata in the json as a model?
I want to say something like:
var store = new Ext.data.Store({
autoLoad: {
params: {
metaNeeded: true
}
},
reader: new Ext.data.JsonReader({fields:[]}),
proxy: new Ext.data.HttpProxy({
api: {
url: 'php/chart-data.php'
}
})
});
I've tried a number of combinations however I cannot seem to get it to work.
I currently get the error "Cannot call method 'indexOf' of undefined". I've had others including "object has no read method".
The json I am sending is:
{
metadata:{
root:"rows",
sortInfo:{
field:"date",
direction:"ASC"
},
fields:[ {
name:"date"
}, {
name:"flow"
},{
name:"limit"
}
],
idProperty:"date"
},
success:true,
rows: << snip >>
}
Is it possible to have the store's model configured by the data that it receives, so I could use the same store later with different fields (e.g. date, flow, limit and temperature)?
I have gotten it to work with the following:
var store = new Ext.data.Store({
proxy: {
type: 'ajax',
url: 'php/chart-data2.php',
reader: new Ext.data.JsonReader({
fields:[]
})
}
});
And the php that sends the json:
'{"metaData":{
"root":"rows",
"fields": [
{"name":"date",
"type":"number",
"convert": function(val, rec) {
return val*1000
} },
{"name":"flow"},
{"name":"limit"}
]
},
"totalCount":'.count($chart).',
"success":true,
"rows":' . json_encode($chart) . '
}'
This now allows the server to specify the data (that's getting displayed in a chart), and can add in series dynamically. I don't know if it is good, but it works. I am kind of disappointed in the lack of documentation about this.