I have a grid and a store. The store gets populated with some data - country - while the rest is empty. Once I receive more inputs I fill up the store with the inputs.
Assume that I have only 1 country in the store after the initial load.
Store:
feedTheGrid = new Ext.data.JsonStore({
autoDestroy : true,
autoSave : false,
autoLoad : true,
idProperty: 'country',
root: 'root',
totalProperty : 'totalcount',
fields : ['country', 'towns'],
proxy : new Ext.data.HttpProxy(new Ext.data.Connection({
url : "country/searchCountries.action",
method : 'POST',
timeout : 300000
}))
});
So the user inputs towns. I save a town in the array towns using push():
listeners: {
'change': function() {
if (feedTheGrid.getAt(0).towns === undefined) {
feedTheGrid.getAt(0).towns = [];
}
feedTheGrid.getAt(0).towns.push(this.getValue());
gridMonster.getView().refresh();
}
}
I want the grid to update every time a new town is put in.
This is part of the column model I use for gridMonster:
{ header: "Town 1", width: 150, dataIndex: 'towns',
renderer: function(val) {
if (val[0] != undefined) {
return val[0];
}
}, sortable: true},
{ header: "Town 2", width: 150, dataIndex: 'towns',
renderer: function(val) {
if (val[1] != undefined) {
return val[1];
}
}, sortable: true},
{ header: "Town 3", width: 150, dataIndex: 'towns',
renderer: function(val) {
if (val[2] != undefined) {
return val[2];
}
}, sortable: true}
The issue is that after a town is put in the grid never refreshes.
Some things I noticed so far:
The store gets updated with the new value when I check through Firebug. Also it seems the val that is given in renderer is always empty String, even after I push the new value into the array and refresh the grid view.
I am using ExtJS 3.3.1
I know its old so I am open to solutions that would work for 4.0+ and I can try to apply it to 3.3.1.
EDIT: Found the issue
Very subtle problem, spent hours trying to find what is the problem only to find out I was looking in the wrong place. The issue was in this line:
feedTheGrid.getAt(0).towns.push(this.getValue());
This line actually creates a new field towns as part of the record that you get using getAt(0). Using firebug to see if getAt(0).towns[0] is populated actually returns the value you pushed which is misleading.
ExtJS is not aware of this new field and it shouldn't be.
This line should be:
feedTheGrid.getAt(0).data.towns.push(this.getValue());
The trick is to point at the right place: .data. Javascript/ExtJS allows you to add new field without any warning because technically you are not doing anything wrong. Logically it creates a new field that does nothing.
#Lorenz Meyer I am able to display elements of the array using renderer while pointing at towns array in the column model.
There is a major misunderstanding about what a store and a grid are in ExtJs.
Both are just a representation of tabular data. A store is the representation in memory of a table, in order to get fast access to records filtering and more features. A grid is the visual representation of a table in the browser window.
Such a table could look like :
Id | Country | City
-------------------
1 | US | NYC
2 | France | Macon
3 | US | Macon
First of all on one table row, you can only have one city. In tables, you cannot have an array in a single field. If you have more than one city, this is what rows are for.
Then, your idProperty cannot be the country. If it were, this would mean that countries are unique. Obviously, they aren't. There can be more than one city in a country. (cities cannot be the idProperty neither, because there exists more than one city with the same).
Now you certainly begin to understand, that feedTheGrid.getAt(0).towns.push(this.getValue()); makes no sense whatever. The towns column cannot be an array. You insert a new row with feedTheGrid.add({country: 'US', towns: 'LA'}).
At best the towns column can be a comma separated value, in which case, you update the store with
var record = feedTheGrid.getAt(0),
currentTowns = record.get('towns');
record.set('towns', currentTowns + ', ' + value)
The method set of the record does all the magic: It passes the update event to the grid in order to rerender it. Your push in addition to be wrong will not trigger the events necessary to update the view.
About the grid: The columns of your grid should be:
{ header: "Id", width: 150, dataIndex: 'id', sortable: true},
{ header: "Country", width: 150, dataIndex: 'country', sortable: true},
{ header: "Town(s)", width: 150, dataIndex: 'towns', sortable: true}
Notes:
That listeners: looks really strange to me, but maybe it's just because the bigger picture is missing.
feedTheGrid is a strange name for a store. The store does not feed the data. It is tha data.
A renderer alway must return a value, else the cell will remain blank. Therefore your renderers cannot work.
In your renderers the val can not be an array. It's a simple variable. That's why your grid is not refreshing.
Very subtle problem, spent hours trying to find what is the problem only to find out I was looking in the wrong place. The issue was in this line:
feedTheGrid.getAt(0).towns.push(this.getValue());
This line actually creates a new field towns as part of the record that you get using getAt(0). Using firebug to see if getAt(0).towns[0] is populated actually returns the value you pushed which is misleading.
ExtJS is not aware of this new field and it shouldn't be.
This line should be:
feedTheGrid.getAt(0).data.towns.push(this.getValue());
The trick is to point at the right place: .data. Javascript/ExtJS allows you to add new field without any warning because technically you are not doing anything wrong. Logically it creates a new field that does nothing.
#Lorenz Meyer I am able to display elements of the array using renderer while pointing at towns array in the column model.
Related
I'm using handsontable as more or less a gui for a separate backend datastore that uses lokijs. My lokijs datastore has the row, column, the datum, and some metadata.
The user can take a few actions that adjust the db and reloads the page. I have a function that converts the lokijs store to a 2d array and calls loadData on it, which refreshes the page the users see.
The problem: users can mark a cell as 'invalid', which turns the cell red and updates the lokijs db with an 'invalid = true' boolean. Users can also hide columns. On the backend this refreshes handsontable by creating a new 2d array that excludes the selected column and calls loaddata.
However, hiding cells won't play nice with column colors. The way I color cells is like so:
cells: function (row, col, prop) {
var cellProperties = {};
var cell = cells.findOne({ "col": {"$eq": col}, "row": {"$eq": row}});
if (cell.invalid === true) {
cellProperties.renderer = highlightRowRenderer; //colors red
}
return cellProperties;
You may be able to see the problem here -- if a column is hidden, there's an off by one error on highlighting, so a different cell gets hidden on the highlight.
Now a way to fix this is on load data, pass highlighting information in as metadata. Unfortunately I can't figure out how to do this. I'd imagine it looks something like this:
var data =
[
[ { data: 5, color: red }, { data: 7, color: blue} ],
[ { data: 3, color: green}, { data: 2 } ]
];
hot.loadData(data);
but I'm not sure what it is exactly. There appears to be a similar concern addressed here but it only seems to handle non-dynamically sized tables.
Any suggestions would be very much appreciated!
As of 1/2/18, this is not possible :(
$('#grid').w2grid({
name: 'grid',
columns: [
{
field: 'code',
caption: 'Code',
size: '120px',
sortable: true,
resizable: true,
editable: {
type: 'text'
}
}
I want to make it edit with if condition like if other fields are empty I don't want to make it editable. How I do this?
You have at least two options:
Either set record.w2ui.editable = false for your specific record (this requires your data source to be dynamic and contain some sort of logic).
- or -
In w2ui 1.5, grid.columns[i].editable can also be a function instead of an object, so you can write your own check function that will determine if the cell will be editable.
Quote from the source files:
col.editable can be a function which will be called with the same args as col.render()
Since the last one isn't well documented, I suggest you take a look at the implementation of getCellEditable() in the w2grid.js sources to get an idea how the two options I mentioned will interact.
I am working with KO-grid and it seems to load all the data fine. Now, I am working on the pagination part and it does not seem to work properly. Yes, I do the pagination control on the bottom but when it comes to be able to decide the page size, it does not seem to work. The page size is driven by two options according to confguration details given on https://github.com/ericmbarnard/KoGrid/wiki/Configuration
1.pageSizes:[5,10, 25] -- seems to show options but when I change my selection from 5 to 10 then it does nto seem to work upon the choices.
2.pagesize ://somenumber -- breaks the code.
I have working model of it on jsfiddle: http://jsfiddle.net/sf4p3/46/
Any suggestions?
Well, it appears that the pagination in KoGrid doesn't work the magic that you were hoping for.
Here's a link to the example from the KoGrid wiki on GitHub:
http://ericmbarnard.github.com/KoGrid/examples/Complex-Server-Side-Paging.html
In viewing source for the HTML page, one will likely see the beginning of the view model declaration without having to scroll (depending on screen resolution, of course). Regardless, this starts around line 30.
Notice that there is an observable named pageSize in the view model, and it is set to 50.
Scrolling down a bit, one should see functions named filter, sort, and getPagedDataAsync for filtering the data, sorting the data, and creating the data set for the current page.
Here's the code for the getPagedDataAsync function:
this.getPagedDataAsync = function (pageSize, page, filterInfo, sortInfo) {
setTimeout(function () {
var data = window.getExampleData();
var filteredData = filter(data(), filterInfo);
var sortedData = sort(filteredData, sortInfo);
var pagedData = sortedData.slice((page - 1) * pageSize, page * pageSize);
self.myObsArray(pagedData);
}, 0);
};
Without seeing the details of the rest of the view model, one should be able to tell from reading the above code that this function starts by retrieving all data to be displayed for this example page, then filters the data and sorts the data.
After that, the data array is sliced to extract the data to be viewed for the current page, and that slice is passed to the myObsArray observable array that is used to display the data in the grid.
Here's the declaration of the grid in this example:
<div id="sandBox" class="example" style="height: 600px; max-width: 700px;"
data-bind="koGrid: {
data: myObsArray,
columnDefs: [
{ field: 'Sku', width: 140 },
{ field: 'Vendor', width: 100 },
{ field: 'SeasonCode', displayName: 'Season Code', width: 150 },
{ field: 'Mfg_Id', displayName: 'Mfg ID', width: 180 },
{ field: 'UPC', width: 170 }
],
autogenerateColumns: false,
isMultiSelect: false,
enablePaging: true,
useExternalFiltering: true,
useExternalSorting: true,
filterInfo: filterInfo,
sortInfo: sortInfo,
pageSize: pageSize,
pageSizes: [25, 50, 75],
currentPage: currentPage,
totalServerItems: totalServerItems,
selectedItem: selectedItem }">
</div>
Hopefully, this helps, and you'll be able to fix your paging issues.
Regardless, please let me know if you have any questions.
UPDATE
#Californicated I'm on vacation, but I had some downtime to take a peek at your latest fiddle.
I forked what you had in your latest fiddle and made the following changes to get the paging to work:
In the declaration of observables, I changed the value of pageSize to 2 and the value of totalServerItems to 7.
In the JS, I changed the declaration of the data var in the
getPagedDataAsync function so it's retrieving the
Prizefillfilmentstatuses observable array.
On the last line of the JS code, I changed the first parameter from 50 to 2.
In the jsFiddle toolbar, I changed the first dropdown from "onLoad" to "no wrap (body)"
In the declaration of the koGrid in the HTML, I added the following options/parameters:
pageSize: pageSize,
currentPage: currentPage,
totalServerItems: totalServerItems,
selectedItem: selectedItem
The page setup was working with the JS changes, alone, but the paging tool (previous, next, etc.) was not active until I added the totalServerItems option in the koGrid declaration.
Again, let me know if you have any questions.
I've been digging around for this one quite a bit. I'm using dojox.grid.datagrid and I have an ajax call that brings back 200-300 rows.
The grid renders and scrolls just fine in Chrome but scrolling is excruciatingly slow in IE 7 and 8. I'd like to use virtual scrolling to try and remedy the issue but can't find any sample code.
Here's what my code looks like at present.
function setupAvailableScenes(location) {
var avaiableScenesGridPane = dijit.byId("AvaiableScenesGridPane");
var availableScenesGrid = dijit.byId("AvailableScenesGrid");
if (_isFirstLoad) {
availableScenesGrid = new dojox.grid.DataGrid({
id: 'AvailableScenesGrid',
store: availableScenesStore,
query: { Id: "*" },
sortInfo: "1",
rowsPerPage: 20,
autoHeight:20,
style: "width:315px",
structure: [
{ name: "Available Scenes", field: "Name", width: "85%" },
{ name: " ",
field: "_item",
rowsPerPage: "25",
type: dojox.grid.cells._Widget,
editable: false,
width: "15%",
formatter: function (scene) {
return new dijit.form.Button(
{
label: "+",
onClick: function () {
AddSceneToSelectedScenes(scene);
}
})
}
}
]
});
avaiableScenesGridPane.set('content', availableScenesGrid);
}
var availableScenesStore = new dojo.data.ItemFileWriteStore({
url: _applicationPath + "/Location/" + location.Id + "/Scene.json",
preventUrlCache: true
});
availableScenesGrid.setStore(availableScenesStore);
}
Often one of the biggest things you can do to improve DataGrid performance is to throw away the ItemFileReadStore/WriteStore and use an optimized data store (personally I like QueryReadStore). It would mean needing a server-side servlet of some kind (PHP/JSP/etc) to handle the virtual scrolling/pagination, but I've seen major perf boosts over just using a store backed by a JSON file.
Some other things to consider, which may or may not help:
give your anonymous formatter function a name and try scrolling the table with the Chrome or Firebug profiles turned on to see if it's hogging a lot of cycles (or, like Vijay Agrawal said, you could try replacing the dijit.form.Button with a vanilla html <button> tag)
you shouldn't actually need to specify the dojox.grid.cells._Widget type for that cell; having a custom formatter returning a valid Dijit should be sufficient to make the Grid do the right thing.
Since you specified rowsPerPage=25, it is already doing virtual scrolling (it pulls the new set of rows only when user scrolls down)
Since you mention scrolling is very slow, the performance issue seems to be around rendering the new rows - you may try a couple things to improve performance:
1) remove autoHeight attribute. Instead, specify a fixed height in the style attribute
2) in the formatter function, instead of returning a dijit, try returning a simple html button/anchor styled as button
so remove the type:dojox.grid.cells._Widget attribute and in the format function return the html you want to use
I may have come up with an incredibly crude way to get this sort of thing accomplished, but I figured that I'd ask the many experts present here at SO. Basically, I have an array that looks something like the following:
var bugs = [
{
id: "197526",
title: "Updating Lighthouse",
summary: "Enhancing the UI of Lighthouse",
status: "Active",
project: "Lighthouse",
area: "Internal Web Applications",
hours: 19
},
{
id: "190328",
title: "Adding Login Authentication to Lighthouse",
summary: "Create a login authentication process for Lighthouse",
status: "Active",
project: "Lighthouse",
area: "Administration",
hours: 12
},
...
{
id: "187562",
title: "Create a Maintenance Page",
summary: "Create a maintenance page to be displayed with the company site is down",
status: "Resolved",
project: "Other",
area: "Internal Web Projects",
hours: 4
},
];
Basically, the array holds several "bugs," each with an id, title, summary, status, project, area, and hours property. Each of these bugs are going to be displayed on my web application, but I allow the user to select how they will be grouped; either by status, project, or area. Depending upon which of the three they select from a select box above, I want to be able to sort through all of the bugs and group them by whichever category they chose. Then, when it comes to displaying them, have a simple header for each present option for that category. For example, if they were to sort by status, it would be something like:
Group By: Status
Active
------
Bug with status: "active"
Bug with status: "active"
Bug with status: "active"
Resolved
--------
Bug with status: "resolved"
Bug with status: "resolved"
Should I iterate through the entire array of bugs and, based on the category to sort by, simply create a new array for each possible option of that category and add the appropriate bugs to them? So in the case above, create new arrays var activeBugs = [] and var resolvedBugs = []? If so, my problem would then be knowing what possible options there are. Should I then first iterate through the entire bugs array to see what possible options are present for the desire group category before creating these new arrays?
What's the best way to do this without resorting to other jQuery plugins?
To elaborate on my comment questioning the need to sort/filter at all, can you do something (very roughly) like this in your display logic?
$('#SortOrder').live('change', function() {
var groupby = $(this).val();
$('#Bugs').empty();
for (var i = 0; i < bugs.length; i++) {
if (!document.getElementById(bugs[i][groupby])) {
$('<h3>', { text: bugs[i][groupby] }).appendTo('#Bugs');
$('<ul>', {
id: bugs[i][groupby]
}).appendTo('#Bugs');
}
var $li = $('<li>', {
text: bugs[i].title
});
$('#Bugs').find('#' + bugs[i][groupby]).append($li);
}
});
Working demo: http://jsfiddle.net/TTApQ/1/
That way there's no sorting or filtering at all, and you only need to make one pass over the array.
Will the user have a copy off all the bugs? or do you just send, lets say, the first 100?
If you send the first 100, the I suggest you to make 3 arrays on the server, which hold pointers to the objects, and every new bug is inserted according to its position. and then just send from the list that was requested. that should be fast because you just pull information. and the sorting is at insertion time. (all 'sorting' is done when the bug is added - this assumes that you view info more often that you edit it!)
If the client holds a copy of all the data, then just re-sort it on the client with javascript. shouldn't take too much time. this saves you bandwidth, and assumes you don't have that much bugs so you can show all of them at once.
Sort the array by status using array.sort. Iterate through the array remembering what the previous iteration's .status property was. When it changes, create a new visual representation.
bugs.sort(mySortFunc);
var lastStatus = '';
for (var i=0, b; b = bugs[i]; i++) {
if (b.status != lastStatus) {
//output a header for the new status b.status
document.write(b.status + '<br>----<br>');
lastStatus = b.status;
}
// output the bug
document.write(b.title + '<br>');
}
Warning: Eventually you'll wish you had let the database do all the sorting/grouping/paging/etc.
Here's an example using .filter() as I described in my comment:
var active_bugs = bugs.filter(function(bug) {
return bug.status === "active";
});