I'm trying to put together an application which uses YUI's DataTable component but I get the "Data error" message. The datasource is configured to get the records from an ASP.NET web method. The records are returned to the client side successfully (I checked it with IE's debugger). My code looks like the following:
YAHOO.example.Basic = function() {
var dsWS_Restaurants = new YAHOO.util.DataSource("/DemoWebSite/RestaurantsWebService.asmx/GetList", { connMethodPost: true });
dsWS_Restaurants.connMgr = YAHOO.util.Connect;
dsWS_Restaurants.connMgr.initHeader('Content-Type', 'application/json; charset=utf-8', true);
dsWS_Restaurants.responseType = YAHOO.util.DataSource.TYPE_JSON;
dsWS_Restaurants.doBeforeParseData =
function(oRequest, oFullResponse, oCallback) {
// checked here if oFullResponse contains the desired results and it does.
}
dsWS_Restaurants.responseSchema =
{
resultsList: 'd.records',
fields: ["id", "name"]
};
var dsWS_Restaurants_ColumnDefs = [
{ key: "id", sortable: true, resizeable: true },
{ key: "name", sortable: true, resizeable: true }
];
var dsWS_Restaurants_DataTable =
new YAHOO.widget.DataTable("basic4", dsWS_Restaurants_ColumnDefs, dsWS_Restaurants, { caption: "dsWS_Restaurants" });
return {
oDS: dsWS_Restaurants,
oDT: dsWS_Restaurants_DataTable
};
} ();
...
Web method look like this:
public Object GetList() {
var restaurants =
new []{
new
{
id="1",
name="Popeyes spinach"
},
new
{
id="2",
name="Big pappas cottage"
}
};
return restaurants.Select (x => new { id = x.id, name = x.name });
}
Any help is welcome and appreciated. Thanks in advance.
I believe that the overridable doBeforeParseData method should return the oFullResponse object...
dsWS_Restaurants.doBeforeParseData =
function(oRequest, oFullResponse, oCallback) {
// checked here if oFullResponse contains the desired results and it does.
return oFullResponse;
}
.. but there may be more to it than just that.
I found out what caused the error. In the responseSchema of the datasource the resultList was defined as 'd.records' but I had no "records" field returned by the web method. I replaced 'd.records' with 'd' and the sample worked. My mistake was that I borrowed the code from a sample application from http://mattberseth.com/blog/2008/09/dynamic_data_experimenting_wit.html which used the "records" field.
Happy coding.
Related
I've been trying to rerender a table when I update an Account record with a lightning-record-form.
I tried looking up a solution to this with similar questions I found here but I'm still not able to achieve this.
In this case I hardcoded the recordId with the Account with the name 'Gonzalo' shown in the preview below all the code. So the wanted result is to update the account name or any field and see the instant outcome in the table.
Here's my code:
Apex method (just in case):
#AuraEnabled(cacheable=true)
public static List<Account> getCuentas() {
return [SELECT id, Name, Phone FROM Account LIMIT 5];
}
Form (HTML):
<lightning-record-form
object-api-name="Account"
record-id="0015e00000F2JoWAAV"
fields={fields}
onsubmit={handleSubmit}
>
</lightning-record-form>
Table (HTML):
<lightning-datatable
key-field="pk"
data={cuentas}
columns={columnas}
onrowselection={action}
hide-checkbox-column
onrowaction={handleRow}
default-sort-direction={defaultSortDirection}
sorted-direction={sortDirection}
sorted-by={sortedBy}
onsort={onHandleSort}
>
</lightning-datatable>
Related code (JS):
***Imports***
import { refreshApex } from '#salesforce/apex';
import NAME from '#salesforce/schema/Account.Name';
import PHONE from '#salesforce/schema/Account.Phone';
import getCuentas from '#salesforce/apex/ProbandoJSON.getCuentas';
import { LightningElement, api, wire, track } from 'lwc';
***Vars for the form fields***
fields = [NAME, PHONE];
***Columns***
columnas = [
{
label: 'View',
type: 'button',
initialWidth: 75,
typeAttributes: {
label: {
fieldName: 'Boton'
},
title: 'Preview',
alternativeText: 'View',
variant: 'border-filled'
}
},
{
label: 'Name',
fieldName: 'Name',
sortable: true
},
{
label: 'Phone',
fieldName: 'Phone'
}
];
***Accounts***
#track cuentas = [];
_wiredResult;
#wire(getCuentas)
wireCuentas(result) {
this._wiredResult = result;
if(result.data) {
console.log('cuentas');
console.log(result.data);
for(var i in result.data) {
let obj = {};
obj.Id = result.data[i].Id;
obj.Name = result.data[i].Name;
obj.Phone = result.data[i].Phone;
obj.Boton = parseInt(i) + 1;
this.cuentas = [...this.cuentas, obj];
}
console.log('cuentas de nuevo');
console.log(this.cuentas);
} else if(result.error) {
console.log('error cuentas');
console.log(result.error);
}
}
***Submit handler for the Save button in the form***
handleSubmit(event) {
console.log('saving...')
return refreshApex(this._wiredResult);
}
Preview of the component:
Table
Form
Looks like a cache issue.
We can solve this in a couple of ways as follow:
Removing cacheable
You need to remove (cacheable=true) and then have to call the apex method imperatively on each form updates or while loading initial data.
Creating an unnecessary parameter in apex method which will be having new value on each call
You need to receive an additional parameter as a integer in the apex method, and then in lwc just create a var initializing it with 0, and on each update increment it by 1.
Then use same var to call the apex method via wire or in imperative calls.
I have autoGroupColumnDef and I want to setup text filter. But values of the column come from getDataPath method. But I need another value in the filter.
autoGroupColumnDef: {
headerName: "Systems",
filter: 'text',
valueGetter: function(params) {
var result = params.data.hospName || params.data.hospitalSystem;
return result;
},
cellRendererParams: {
suppressCount: true,
innerRenderer: function(params) {
var result = params.data.hospName || params.data.hospitalSystem;
return result;
}
}
},
After trying on couple of things,
Option 1: You can make use of [filterParams][1]. This only helps to play around with options/choices in the filterMenu..
function filterCellRenderer(params) {
//other than params.value nothing else will be there..
// params.data won't be there when its called from filter popup
return params.value+" Custom";
}
var gridOptions= {
...,
treeData: true,
components: {
...,
filterCellRenderer: filterCellRenderer
},
autoGroupColumnDef: {
...,
filterParams: {
cellRenderer: 'filterCellRenderer',
//values: ["A", "XYZ"] //you can feed directly specific values. These need to be part of filePath. Else filtering won't work.
}
}
}
Option 2: If you are looking for custom filter (tweak with GUI), or you want to post processing after it has been configured by ag-grid you can define the following:
var gridOptions = {
...
getMainMenuItems: getMainMenuItems,// function to build your own menu
postProcessPopup: function(params){
// edit the popup..
//params.type gives whether its column menu or not.
//params.ePopup gives handler to popup which you can modifiy.
},
...
}
OR you can build your own custom filter as described here
My problem is that I am just starting out with Backbone.js and are having trouble wrapping my head around a complex problem. I want to save a form that have infinite fields, and some of the fields also needs to have infinite options. I'm just worried I might have started at the wrong end with a JSON response, instead of building the models/collections first. Here is a short pseudocode of what I try to achieve.
id:
parent: <blockid>
fields: array(
id:
title:
helpertext
options: array(
id:
type:
value:
)
)
Currently I am working with a faked JSON response from the server, which I built from scratch, and now I want to divide it into models and collections on the client side.
//Fake a server response
var JSONresponse = {
"formid":"1",
"fields":[
{
"fieldid":"1",
"title":"Empty title",
"helper":"Helper text",
"type":"radio",
"options":[
{
"optionid":"1",
"value":"Empty option.."
},
{
"optionid":"2",
"value":"Empty option.."
}
]
},
{
// fieldid2
}
]
};
The idea is to add fields as I see fit, and then if the field type is radio/checkbox/ul/ol there must also be an "options" array within the field.
My work so far:
var app = {};
app.Models = {};
app.Collections = {};
app.View = {};
app.Models.Option = Backbone.Model.extend({
});
app.Collections.Options = Backbone.Collection.extend({
model: app.Models.Option
});
app.Models.Field = Backbone.Model.extend({
options: new app.Collections.Options()
});
app.Collections.Fields = Backbone.Collection.extend({
model: app.Models.Field
});
app.Models.Form = Backbone.Model.extend({
formid : "1",
fields: new app.Collections.Fields(),
initialize: function() {
}
});
How do I split up my JSON response into all these models and collections?
(Perhaps I should re-evaluate my approach, and go for something like form.fieldList and form.optionList[fieldListId] instead. If so, how would that look like?)
Edit: Here is a little jsfiddle after many fixes, but I still don't really know how to make the inner options list work.
The easiest solution would be using Backbone Relational or Backbone Associations.
The documentation should be enough to help you get started.
If you don't want to use a library you could override the parse function on the Form model.
app.Models.Form = Backbone.Model.extend({
defaults: {
fields: new app.Collections.Fields()
},
parse: function(response, options) {
return {
formid: response.formid,
fields: new app.Collections.Fields(_.map(response.fields, function(field) {
if (field.options) {
field.options = new app.Collections.Options(field.options);
}
return field;
}))
};
}
});
Now if you fetch a form from the server, the response will be parsed into an object graph of models and collections.
form.get('fields') will return an app.Collections.Fields collection. form.get('fields').first().get('options') will return an app.Collections.Options collection, if any options exist.
Also, you could create the form model like this:
var form = new app.Models.Form(JSONresponse, {
parse: true
});
This would result in the same object structure.
It's quite hard to handle the case of nested models and collections right in plain Backbone.
Easiest way of handling this will be something like this:
var Option = Nested.Model.extend({
idAttribute : 'optionid',
defaults : {
optionid : Integer
value : ""
}
});
var Field = Nested.Model.extend({
idAttribute : 'fieldid',
defaults : {
fieldid : Integer,
title : "",
helper : "",
type : "radio",
options : Option.Collection
}
});
var Form = Nested.Model.extend({
idAttribute : 'formid',
defaults : {
formid: Integer,
fields: Field.Collection
});
https://github.com/Volicon/backbone.nestedTypes
And that's it. Yep, you'll get direct access to the attributes as free bonus, just form.fields.first().options.first().value, without that get and set garbage.
I have an OnDemandGrid with one column that I want to populate with a custom Dojo widget I built. The data used to populate each of these widgets comes from a Solr query. Since I am expecting possibly thousands of search results, I need to use a JsonRest object to make the queries and handle pagination. Here's what I have so far:
The store:
var store = new JsonRest ({
target: "/solr/json/response",
});
Creating the grid:
var grid = new (declare([OnDemandGrid, Pagination])) ({
store: store,
getBeforePut: false,
columns: [
{
label: "Test",
field: "first",
renderCell: myRenderFunction //To render the custom widget
}
]
}, "grid");
grid.startup();
myRenderFunction:
var myRenderFunction = function(object, data, cell) {
var widget = new MyCustomWidget({
doc: object,
foo: bar
}, cell.appendChild(document.createElement("div"));
widget.startup();
return widget;
}
Sample Solr response, in JSON form:
{
"response":{
"docs":[
{
"foo": "Hello",
"bar": "World"
},
{
"foo": "Easy as",
"bar": "ABC"
},
{
"foo": "Simple as",
"bar": "Do re mi"
}
]
},
"highlighting": { ... },
"numFound": "74",
"start": 0
}
I have followed a few examples online demonstrating how to do this using JsonRest and any of the dgrid flavors (and they all worked), but when I try to render the widget to the grid nothing shows up and I get a TypeError: transform(...) is null.
Is there any reason why I can't render my widget to the grid?
I ran into the same problem trying to use Solr results with dgrid and JsonRest.
JsonRest uses QueryResults as a wrapper for what it returns.
Your problem is that QueryResults accepts only arrays or promises and you're currently giving it an object.
In order to give QueryResults the docs array, write a custom JsonRest store similar to:
define([
"dojo/Deferred", "dojo/io-query", "dojo/_base/declare", "dojo/request/xhr",
"dojo/store/JsonRest", "dojo/store/util/QueryResults"
], function (Deferred, ioQuery, declare, xhr, JsonRest, QueryResults) {
return declare([JsonRest], {
target: '/solr/json/response',
idProperty: 'foo',
query: function (query, options) {
var results, total, count = options.count, start = options.start;
if (start > 0 || count >= 0) {
query.start = start;
query.rows = ((options.hasOwnProperty('count') &&
count !== Infinity) ? count : 25);
} else {
console.error('Missing start and count arguments');
return;
}
results = new Deferred();
results.total = new Deferred();
xhr(this.target, {
query: ioQuery.objectToQuery(query),
handleAs: 'json',
headers: {
Accept: this.accepts
}
}).then(function (data) {
total = data.response.numFound;
results.total.resolve(total);
results.resolve(data.response.docs);
}, function (e) {
console.error(e.response.status + '. ' + e.message);
});
return new QueryResults(results);
}
});
});
I also recommend waiting to use a custom renderCell function until after you get dgrid properly populated.
edit: OnDemandGrid is not going to work with the Pagination extension.
So decide whether you want discreet paging controls or infinite scroll (paging handled by dgrid).
See Pagination and OnDemand docs.
In ExtJS, I have a JsonStore configured like this:
var store = new Ext.data.JsonStore({
// basic properties
restful: true,
autoSave: true,
// json writer
writer: new Ext.data.JsonWriter({
encode: false,
writeAllFields: true
}),
// field config
fields: [
'id',
'name',
{ name: 'timestamp', type: 'date', dateFormat: 'c' }
]
});
The timestamp property should be set by the server, and the client should only read it. However, if I try to add or update a record using the JsonStore:
// add a record
var record = new store.recordType({ name: 'New record' });
store.insert(0, record);
// update a record
var record = store.getAt(0);
record.set('name', 'Modified record');
The JSON that's being sent over to the server looks like this:
{
"id": 5,
"name": "Modified record",
"timestamp": "01-06-2001T15:23:14"
}
I'd like it to stop sending over the timestamp property, since it's supposed to be read-only. Is there any way of configuring either the store or the record in order to get this behavior?
Since Ext4 you may use
persist: false
in the Model field description
Ext.namespace('Ext.ux');
/**
* Extension of the JsonWriter that doesn't send fields having the config option
* write set to false
* #author Friedrich Röhrs
* #verison 1.0
*
*/
Ext.ux.AdvJsonWriter = function (config) {
Ext.ux.AdvJsonWriter.superclass.constructor.call(this, config);
};
Ext.extend(Ext.ux.AdvJsonWriter, Ext.data.JsonWriter, /** #lends Ext.ux.AdvJsonWriter */{
toHash: function(rec, options) {
var map = rec.fields.map,
data = {},
raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
m;
Ext.iterate(raw, function(prop, value){
if((m = map[prop])){
if (m.write !== false)
data[m.mapping ? m.mapping : m.name] = value;
}
});
// we don't want to write Ext auto-generated id to hash. Careful not to remove it on Models not having auto-increment pk though.
// We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
// we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
if (rec.phantom) {
if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
delete data[this.meta.idProperty];
}
} else {
data[this.meta.idProperty] = rec.id;
}
return data;
}
});
then
var store = new Ext.data.JsonStore({
// basic properties
restful: true,
autoSave: true,
// json writer
writer: new Ext.ux.AdvJsonWriter({
encode: false,
writeAllFields: true
}),
// field config
fields: [
'id',
'name',
{ name: 'timestamp', type: 'date', dateFormat: 'c', write: false }
]
});
the timestamp field will never be send to server.
If you set {disabled: true} on the timestamp field, it shouldn't be submitted to server
You can also set the default value to whatever you want on the timestamp field. You have configured your JsonWriter to go ahead and write to all fields. If you set the default value to '' or NULL you might be able to get the desired behavior you want.
You could try intercepting the call to the writer and just remove the timestamp at that time:
var writer = new Ext.data.JsonWriter({
encode: false,
writeAllFields: true
});
Ext.intercept(writer, 'render', function(params, baseParams, data) {
delete data.timestamp;
});
var store = new Ext.data.JsonStore({
...
writer: writer,
...
Does writeAllFields have to be true? It defaults to false which wouldn't send the timestamp property if you didn't update it.
Alternatively, you could override the toHash function on the JsonWriter. At the moment it has this comment:
TODO Implement excludes/only configuration with 2nd param
You could add your own implementation of that.