Populating OnDemandGrid with custom Dojo widget using Solr and JsonRest - javascript

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.

Related

How setup valuegetter for auto Group Column in Ag-Grid

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

Populating kendo-tree using from Json

I am in Angular environment using Kendo. All I want to do is following:
Take Json
Produce Kendo tree using it
I have tried it with simple data and it seems to work fine. But this time I have somewhat complex data and it seems like it does not work well with complex Json. I have been trying to have it render Json but it seems like it keeps on thinking and never comes back. I have created a sample Dojo for reference:
http://dojo.telerik.com/EdOqE
I am not sure what am I doing wrong but it just does not seem to work. Can anyone help me with this please?
I presume you have controll over the resultant json, because you'll have to change it a little to fit the TreeView's expected format. Check this out:
{
"items": [{ // Projects
"Id": 0,
"Name": "Your Example Project",
"CreatedOn": "",
"hasChildren": true,
"items": [{ // Analyses
"Id": 0,
"Name": "1.0 - Your Example Run",
"CreatedOn": "",
"hasChildren": true,
"items": [{ // Samples
"Id": 0,
"Name": "Sample 1",
"hasChildren": false,
"Description": "ample frample sample"
}, {
"Id": 0,
"Name": "Sample 2",
"hasChildren": false,
"Description": null
}]
}]
}]
};
The above json is what I did to work in the widget. First of all, the collection properties were renamed to items. All of them, in all levels. With that, kendo will know how property it should deal with. A hasChildren property was added to let it know when it has to show the expand icon. Otherwise it will show the expand option even if the item doesn't haves any children. So user clicks it and get an empty result.
This is the widget initialization options:
{
dataSource: new kendo.data.HierarchicalDataSource({
data: things,
schema: {
data: "items"
}
}),
dataTextField: "Name"
};
With schema.data I tell which property kendo will deal as the collection item. The dataSource expects an array, but if you give him an object, you have to set this property. If it was an array, then kendo would look for item property of each child for default. dataTextField is the name of the property it will use as the label.
Demo
Here is another demo with the data as an array. No need to set schema.data.
Update:
I was afraid you would say that. Yes, there is a way to deal with the data if you can't change it in the server-side. You have to intercept the data at the schema.parse() method and change the resultant data object property to items, so then the widget will understand:
schema: {
data: "items",
parse: function(data) {
if (data.hasOwnProperty("Projects")) {
return { items: data.Projects };
}
else if (data.hasOwnProperty("Analyses")) {
return { items: data.Analyses };
}
else if (data.hasOwnProperty("Samples")) {
return { items: data.Samples };
}
}
}
Demo
Every node when opened will call parse with items collection as data parameter. You have to return a new object with the property name as items instead of Projects, Analysis or Samples.
I forgot you can't touch the data, so can't add hasChildren property as well. Then you have to add a tiny logic into parse to set those properties in each level, otherwise the expand icon would not appear:
schema: {
data: "items",
parse: function(data) {
if (data.hasOwnProperty("Projects")) {
data.Projects.forEach(p => {
p.hasChildren = false;
if (p.hasOwnProperty("Analyses")) {
p.hasChildren = true;
}
});
return { items: data.Projects };
}
else if (data.hasOwnProperty("Analyses")) {
data.Analyses.forEach(a => {
a.hasChildren = false;
if (a.hasOwnProperty("Samples")) {
a.hasChildren = true;
}
});
return { items: data.Analyses };
}
else if (data.hasOwnProperty("Samples")) {
return { items: data.Samples };
}
}
}
Demo
It is ugly, I know. But get used to Kendo, it is the it goes with it.

Create object from Backbone collection that maps model values to one another

We are using Backgrid which allows you to define grid columns with an array of Javascript objects which it converts to a collection. We are trying to take advantage of this to have configurable validation on a column by column basis, so we might have the following where we've added a "validator" function to a couple of the columns:
[
{
label: "Delete",
name: "delete",
cell: "boolean"
},
{
label: "Alias",
name: "alias",
cell: "string",
sortType: "toggle",
validator: function (value) {
return true;
}
},
{
label: "Assigned Server",
name: "assignedServer",
cell: "string",
sortType: "toggle",
validator: function (value) {
return new Error("Cannot Assign Server")
}
}
]
We are listening to edits to the grid in the following prescribed manner and for the purposes of this question we can ignore the model argument to the function but concentrate on the column (delete, alias or assignedServer from above) which is itself a model in a collection. So far I have a snippet of code leveraging underscore.js's _.filter that returns the validatableColumns but I want to take this further and end up with an object of the format {name: validator, etc...}. Bearing in mind my specific use case, what is a succinct way to create an object from a Backbone collection that maps model values to one another?
certificateGrid.listenTo(certificateCollection, "backgrid:edited", function (model, column) {
var validatableColumns = _.filter(column.collection.models, function (c) {
return c.get('validator');
});
//etc.
Using _.reduce seems to do the trick:
var validatorFns = _.reduce(column.collection.models, function (fns, model) {
var validator = model.get('validator');
if (model.get('validator')) {
fns[model.get('name')] = validator;
}
return fns;
}, {});

select2: load asynchronous data through promise

What is the best place to load the options for select2 asynchronously. I want the same facility as ajax, but instead of select2 sending an ajax request, it needs to load the values asynchronously from a promise object. Below code works, in which I load the data in query, but which means every keystroke, invocation of select dropdown, it will query the data. so, what is the correct configuration?
code:
var items2 = [
{
"Id": 1,
"Name": "First"
},
{
"Id": 2,
"Name": "Second"
},
{
"Id": 3,
"Name": "Third"
}
];
var names = function () {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve(items2);
}, 200);
return deferred.promise;
};
var query: function (query) {
var results = [];
names().then(function(d){
$.each(d, function(index, item){
results.push({
id: item.Id,
text: item.Name
});
});
query.callback({ results: results });
})
};
Edit
looking at the source, it looks like it only allows either ajax or local for querying data. It would have been ideal if local takes a function which returns the data. Am I on the right track? is there an easy way to patch it?
thanks
// exports
window.Select2 = {
query: {
ajax: ajax,
local: local,
tags: tags
}, util: {
debounce: debounce,
markMatch: markMatch,
escapeMarkup: defaultEscapeMarkup,
stripDiacritics: stripDiacritics
}, "class": {
"abstract": AbstractSelect2,
"single": SingleSelect2,
"multi": MultiSelect2
}
};
Edit2:
'local` indeed accepts a function. but it doesn't play nicely with remote data, as the data is received with a delay (async), drop down is not populated with the new data. I have to close and open the drop-down again. This is not intuitive for the user.
As far I see, select2 invokes an ajax call for each key stroke, and opening the select box. I can get the same behavior using query (with promise) as in the original question. I was expecting select2 loads the data once, then do rest locally (search, and further invocation till any data change). looks like this is not an option. I may just cache my results locally. Better answer welcome.

YUI datatable error

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.

Categories