Handsontable change a column source - handsontable

Is it possible to change the source in a Handsontable instance when inside an event?
Below is my code:
var container2 = $('#example2');
var hot2 = new Handsontable(container2, {
data: {},
minRows: 5,
colHeaders: ['Car', 'Year', 'Car Color'],
columns: [
{
type: 'autocomplete',
source: ['BMW', 'Chrysler', 'Nissan', 'Suzuki', 'Toyota', 'Volvo'],
strict: true,
allowInvalid: false
}, ,
{},
{
type: 'autocomplete',
source: ['yellow', 'red', 'orange', 'green', 'blue', 'gray', 'black', 'white', 'purple', 'lime', 'olive', 'cyan'],
strict: true,
allowInvalid: false
}]
});
Handsontable.hooks.add('afterChange', afterChangedCallback, hot2);
function afterChangedCallback(p) {
console.log(p);
if (p[0][1] == 0) {
alert('This means the first column has changed, I now want to update the colors here');
}
}
When a user selects a different car brand, I only want to populate the dropdown of "Car Color" with certain colors. Thus, not all car brands has the same colors.
EDIT
I updated the callback function to this based on the accepted answer:
function afterChanged(p) {
console.log(p);
if (p[0][1] == 0) {
hot2.updateSettings({
cells: function (row, col, prop) {
if (row == p[0][0] && col == 2) {
var cellProperties = {};
cellProperties.source = ['red', 'yellow', 'blue'];
return cellProperties;
}
}
});
}
}

Yup, you can use the updateSettings method to change the source of an entire column or particular cell. You probably want per-cell so I would do:
hot.updateSettings({
cells: newCellDefinitionFunction()
})
Of course this new definition is up to you to figure out. It could just be returning the same cellProperties each time but check on some global array for what sources to use for which cells.

Related

C3.js combination chart with time series - tooltip not functional

I've been trying for 3 days to get this chart to display the way I want it to. Everything was working 100% until I realized the grouped bar chart numbers were off.
Example: When the bottom bar value equals 10 and the top bar value equals 20, the top of the grouped bar read 30. This is the default behavior, but not how I want to represent my data. I want the top of the grouped bar to read whatever the highest number is, which lead me to this fiddle representing the data exactly how I wanted to.
After refactoring my logic, this is what I have so far. As you can see the timeseries line is broken up and the tooltip is not rendering the group of data being hovered over.
My questions:
1) How to get the tooltip to render all three data points (qty, price, searches)
2) How to solidify the timeseries line so it's not disconnected
Any help would be greatly appreciated so I can move on from this 3 day headache!
Below is most of my code - excluding the JSON array for brevity, which is obtainable at my jsfiddle link above. Thank you in advance for your time.
var chart = c3.generate({
bindto: '#chart',
data: {
x: 'x-axis',
type: 'bar',
json: json,
xFormat: '%Y-%m-%d',
keys: {
x: 'x-axis',
y: 'searches',
value: ['qty', 'searches', 'price']
},
types: {
searches: 'line'
},
groups: [
['qty', 'price']
],
axes: {
qty: 'y',
searches: 'y2'
},
names: {
qty: 'Quantity',
searches: 'Searches',
price: 'Price ($)'
},
colors: {
price: 'rgb(153, 153, 153)',
qty: 'rgb(217, 217, 217)',
searches: 'rgb(255, 127, 14)'
}
},
bar: {
width: {
ratio: 0.60
}
},
axis: {
x: {
type: 'timeseries',
label: { text: 'Timeline', position: 'outer-right' },
tick: {
format: '%Y-%m-%d'
}
},
y: {
type: 'bar',
label: {
text: 'Quantity / Price',
position: 'outer-middle'
}
},
y2: {
show: true,
label: {
text: 'Searches',
position: 'outer-middle'
}
}
},
tooltip: {
grouped: true,
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map(function(series) {
var matchArr = series.values.filter(function(datum) {
return datum.value != undefined && datum.x === d[0].x;
});
if (matchArr.length > 0) {
matchArr[0].name = series.id;
return matchArr[0];
}
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
1) If I got it right, you want tooltip to show all values, even if some of them are null.
Null values are hidden by default. You can replace them with zero (if it is suitable for your task) and thus make them visible.
Also, it seems to me that there is a shorter way to get grouped values:
var data = chart.internal.api.data().map(function(item) {
var row = item.values[d[0].index]; // get data for selected index
if (row.value === null) row.value = 0; // make null visible
return row;
});
2) I think you are talking about line.connectNull option:
line: {
connectNull: true
}
UPDATE
Looks like having duplicate keys breaks work of api.data() method.
You need to change json structure to make keys unique:
Before:
var json = [
{"x-axis":"2017-07-17","qty":100},
{"x-axis":"2017-07-17","price":111},
{"x-axis":"2017-07-17","searches":1},
{"x-axis":"2017-07-18","qty":200},
{"x-axis":"2017-07-18","price":222},
{"x-axis":"2017-07-18","searches":2}
];
After:
var json = [
{"x-axis":"2017-07-17","qty":100,"price":111,"searches":1},
{"x-axis":"2017-07-18","qty":200,"price":222,"searches":2}
];
See fiddle.

How to filter/sort OpenUI5 Grid by column, which is nested in the json?

I'm now testing the capabilities of this grid and I'm having a look at this example at the moment.
Last week, I tried some basic loading of data, returned from the controller of the MVC app that I'm working on. It returns json, which I then give to the grid to be displayed.
The data, that I want to show, is stored in multiple tables. For now, I load data from only two of them for simplicity, because I'm only testing the capabilities of the grid - will it suit our needs.
The data, which arrives at the grid (in js), looks something like this:
{
Cars: [
{
car_Number: '123',
car_Color: 'red',
car_Owner: Owner: {
owner_ID: '234',
owner_Name: 'John'
},
car_DateBought: '/Date(1450648800000)/'
},
{
car_Number: '456',
car_Color: 'yellow',
car_Owner: Owner: {
owner_ID: '345',
owner_Name: 'Peter'
},
car_DateBought: '/Date(1450648800000)/'
},
{
car_Number: '789',
car_Color: 'green',
car_Owner: Owner: {
owner_ID: '567',
owner_Name: 'Michael'
},
car_DateBought: '/Date(1450648800000)/'
}
]
}
Here is some sample code of what I have done so far:
$.ajax({
type: 'post',
url: BASE_HREF + 'OpenUI5/GetAllCars',
success: function (result) {
var dataForGrid = result['rows'];
debugger;
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(dataForGrid);
var oTable = new sap.ui.table.Table({
selectionMode: sap.ui.table.SelectionMode.Multi,
selectionBehavior: sap.ui.table.SelectionBehavior.Row,
visibleRowCountMode: sap.ui.table.VisibleRowCountMode.Auto,
minAutoRowCount: 10,
//visibleRowCount: 10,
showNoData: false
});
// define the Table columns and the binding values
oTable.addColumn(new sap.ui.table.Column({
label: new sap.ui.commons.Label({
text: "ID of car"
}),
template: new sap.ui.commons.TextView({ text: "{car_Number}" }),
sortProperty: "car_Number", // https://sapui5.netweaver.ondemand.com/sdk/test-resources/sap/ui/table/demokit/Table.html#__2
filterProperty: "car_Number"
}));
oTable.addColumn(new sap.ui.table.Column({
label: new sap.ui.commons.Label({ text: "Color of car" }),
template: new sap.ui.commons.TextView({ text: "{car_Color}" }),
sortProperty: "car_Color",
filterProperty: "car_Color"
}));
oTable.addColumn(new sap.ui.table.Column({
label: new sap.ui.commons.Label({ text: "Car Owner ID" }),
template: new sap.ui.commons.TextView({
// does not work like this -> text: "{Owner.owner_ID}"
text: {
path: 'Owner',
formatter: function (owner) {
return owner !== null ? owner['owner_ID'] : '';
}
}
}),
sortProperty: "Owner.owner_ID", // these two don't work
filterProperty: "Owner.owner_ID"
}));
oTable.addColumn(new sap.ui.table.Column({
label: new sap.ui.commons.Label({ text: "Car Owner Name" }),
template: new sap.ui.commons.TextView({
// does not work like this -> text: "{Owner.owner_Name}"
text: {
path: 'Owner',
formatter: function (owner) {
return owner !== null ? owner['Name'] : '';
}
}
}),
sortProperty: "Owner.owner_Name", // these two don't work
filterProperty: "Owner.owner_Name"
}));
var dateType = new sap.ui.model.type.Date({ // http://stackoverflow.com/questions/22765286/how-to-use-a-table-column-filter-with-formatted-columns
pattern: "dd-MM-yyyy"
});
oTable.addColumn(new sap.ui.table.Column({
label: new sap.ui.commons.Label({ text: "Date bought" }),
template: new sap.ui.commons.TextView({
text: {
path: 'car_DateBought',
formatter: dateFormatterBG
}
}),
sortProperty: "car_DateBought",
filterProperty: "car_DateBought",
filterType: dateType
}));
oTable.setModel(oModel);
oTable.bindRows("/");
oTable.placeAt("testTable", "only");
},
error: function (xhr, status, errorThrown) {
console.log("XHR:");
console.log(xhr);
console.log("Status:");
console.log(status);
console.log("ErrorThrown:");
console.log(errorThrown);
}
});
My problems:
I cannot sort or filter the list of cars by owner_ID or owner_Name. How should I do the filtering and sorting? Should it be done with the help of a formatter function in some way, or...?
I can sort by car_DateBought, but I cannot filter the cars by this field. First, I tried setting filterType: dateType, then I tried setting it to filterType: dateFormatterBG(it turns out, that dateType does exactly the same thing as my own dateFormatterBG does, btw).
function dateFormatterBG(cellvalue, options, rowObject) {
var formatedDate = '';
if ((cellvalue != undefined)) {
var date = new Date(parseInt(cellvalue.substr(6)));
var month = '' + (date.getMonth() + 1);
var day = '' + date.getDate();
var year = date.getFullYear();
if (month.length < 2) {
month = '0' + month;
}
if (day.length < 2) {
day = '0' + day;
}
formatedDate = [day, month, year].join('-');
}
return formatedDate;
}
Anyway, as I said, I tried both, but it doesn't work. When I click on the header of a column like in the example in the first link, I don't get any sort of a datepicker. How can I tell OpenUI5, that this column needs to be filtered by date and it should provide the user with a datepicker, when he/she clicks on the 'Filter' input field at the bottom of the dropdown menu? When I try to write the date in the filter field like '07-11-2016' (the way it is formatted), I get an empty table/grid. If I try to enter the huge number from field car_DateBought in the json object, all available rows in the table stay the same and when I reclick on the header, the filter field at the bottom of the dropdown menu appears with error-state.
Thank you in advance for your help and pieces of advice!
Edit:
This is just sample, dummy data. I try to load the real data and I see, that in the table I've got a couple of rows with date, which is today (07-11-2016, or 11/7/2016 if you prefer). That's why getting an empty table after trying to filter means it's not working correctly.
Sorting: in a sample I am looking at the following appears in the controller:
onInit : function () {
this._IDSorter = new sap.ui.model.Sorter("my_id", false);
},
....
Then in the view there is a button defined in the header column as
<Column>
<Label text="Project"/>
<Button icon="sap-icon://sort" press="onSortID" />
</Column>
And back in the controller there is a further function:
onSortID: function(){
this._IDSorter.bDescending = !this._IDSorter.bDescending;
this.byId("table").getBinding("items").sort(this._IDSorter);
},
I read this collectively as defining a sorter in the onInit(), then toggle/reversing it in the click event of the sort button in the column header via the onSortId() function. The OpenUI5 API doc re sorters indicates there are more parameters in the sorter constructor for initial sort direction and sorting function.
Following this pattern, for your needs to sort on the owner_ID or owner_Name, I assume you could set up a sorter as
this._OwnerIDSorter = new sap.ui.model.Sorter("car_Owner/owner_ID", false);
this._OwnerNameSorter = new sap.ui.model.Sorter("car_Owner/owner_Name", false);

ZoomRange Highstock works not correct?

I made a Highstock diagramm and got aproblem with zooming on the yAxis.
I have a Button and 2 textfield to get the wanted min/max values for the axis. With min:0, max: 100 it works well. With min:0, max:80 it doesn't (max will still be 100 in the Diagramm).
If I use the mouse for zooming it works well (even a min of: 3.7 and a max of 3.894 is possible). But using the mouse is not an Option, because in the later Diagramm there will be 3 yAxes with individual zoom.
$(function () {
var seriesOptions = [],
seriesCounter = 0,
names = ['MSFT', 'AAPL', 'GOOG'];
/**
* Create the chart when all data is loaded
* #returns {undefined}
*/
function createChart() {
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 4
},
chart:{
zoomType: 'xy'
},
yAxis: [
{
labels: {
format: '{value}',
},
height: '100%',
opposite: false,
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
],
plotOptions: {
series: {
compare: 'percent'
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2
},
series: seriesOptions
},
function(chart){
$('#btn').click(function(){
var min = temp_min.value,
max = temp_max.value;
chart.yAxis[0].setExtremes((min),(max));
});
});
}
$.each(names, function (i, name) {
$.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=' + name.toLowerCase() + '-c.json&callback=?', function (data) {
if(seriesCounter==0){
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
} else {
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
}
// As we're loading the data asynchronously, we don't know what order it will arrive. So
// we keep a counter and create the chart when all the data is loaded.
seriesCounter += 1;
if (seriesCounter === names.length) {
createChart();
}
});
});
});
JSFiddle
Another Question: Is it possible to set up a scrollbar for the yAxis as well?
Thanks for your help, Patrick
This is related with fact that tickInterval is not regular, so is rounded to value (like 100). The solution is using tickPositioner which calculates ticks, based on extremes which you define.
tickPositioner: function (min,max) {
var positions = [],
tick = Math.floor(min),
increment = Math.ceil((max - min) / 5);
for (tick; tick - increment <= max; tick += increment) {
positions.push(tick);
}
return positions;
},
http://jsfiddle.net/6s11kcwd/
The scrollbar is supported only for xAxis.

jqgrid select only one row per group

How can we allow the users to only select one row per group?
I have the following code.
var data = [
{ ActionItemId: "AAZ08702-0001104", StrarTime: "2007-10-01", Category: "General", CategoryDetails: "dummy text of industry. a galley of type ", TargetCategory: "200.00",
TargetDateCategory: "10.00", ActualCategory: "210.00"}
];
$("#jqGrid").jqGrid({
data: data,
datatype: "local",
colModel: [
{ label: 'Action Item ID', name: 'ActionItemId', key: true },
{ label: 'Start Time', name: 'StrarTime'},
{ label: 'Category', name: 'Category'},
{ label: 'Details', name: 'CategoryDetails', cellattr: function (rowId, tv, rawObject, cm, rdata) { return 'style="white-space: normal;"' }},
{ label: 'Target <Category>', name: 'TargetCategory' },
{ label: 'Target Date <Category>', name: 'TargetDateCategory'}
],
loadonce: true,
viewrecords: true,
//width: 1000,
height: 400,
rowNum: 20,
rowList: [20, 30, 50],
rownumbers: true,
rownumWidth: 25,
multiselect: true,
shrinkToFit: false,
pager: "#jqGridPager",
grouping: true,
groupingView: {
groupField: ["Category"],
groupColumnShow: [true],
groupText: ["Category: <b>{0}</b>"],
groupOrder: ["asc"],
groupSummary: [false],
groupCollapse: false
}
});
I need to disable the ability to select multiple rows per column. is it possible?
Is there a setting in the grouping function to enable which will work as mu requirement? or should it be custom development?
Note: I have only added one column to avoid a very long code in the question
One of the possible implementation could be adding add callback which return false if another row from the same group is already selected. An example of the implementation is the following:
beforeSelectRow: function (rowid, e) {
var selarrrow = $(this).jqGrid("getGridParam", "selarrrow"),
$tr = $(e.target).closest("tr.jqgrow"),
otherIdsOfTheGroup;
if ($tr.length > 0) {
otherIdsOfTheGroup =
// get all rows of the group before the current
$tr.prevUntil("tr.jqgroup")
// add all rows of the group after the current
.add($tr.nextUntil("tr.jqgroup"))
// enum all the rows of the group without the current
.map(function () {
// test whether the rowid is already selected
if ($.inArray(this.id, selarrrow) >= 0) {
// add the rowid to the array of returned values
return this.id;
}
});
// otherIdsOfTheGroup contains the array of rowids of the rows
// from the same group, which are already selected
if (otherIdsOfTheGroup.length > 0) {
return false; // prevent selection
}
}
return true; // allow selection
}
See the demo
UPDATED: One can easy modify the aboce vode to unselect the previously selected rows from the same group. One need just call resetSelection for every rowid from otherIdsOfTheGroup array and return true from otherIdsOfTheGroup to allow selection:
beforeSelectRow: function (rowid, e) {
var $this = $(this),
selarrrow = $this.jqGrid("getGridParam", "selarrrow"),
$tr = $(e.target).closest("tr.jqgrow"),
otherIdsOfTheGroup;
if ($tr.length > 0) {
otherIdsOfTheGroup =
// get all rows of the group before the current
$tr.prevUntil("tr.jqgroup")
// add all rows of the group after the current
.add($tr.nextUntil("tr.jqgroup"))
// enum all the rows of the group without the current
.map(function () {
// test whether the rowid is already selected
if ($.inArray(this.id, selarrrow) >= 0) {
// add the rowid to the array of returned values
return this.id;
}
});
// otherIdsOfTheGroup contains the array of rowids of the rows
// from the same group, which are already selected
if (otherIdsOfTheGroup.length > 0) {
$.each(otherIdsOfTheGroup, function () {
$this.jqGrid("resetSelection", this);
});
}
}
return true; // allow selection
}
See the next demo. I included hiding of the column header of "Select All" button just to write less code. You can implement onSelectAll callback and allow to select only one (for example the first) row from every group.
I managed to solve this issue using the following code.
beforeSelectRow: function (id, e) {
var rowdata = $("#jqGrid").getRowData(id);
var category = rowdata.Category;
var selectedRowTR = $("#jqGrid").find("tr[id='" + id + "']");
var groupTRs = $("#jqGrid").find("tbody> tr.jqgrow > td[title='" + category + "']").parents("tr");
var ids = groupTRs.map(function () {
return this.id;
}).get();
var selectedIDs = $("#jqGrid").getGridParam("selarrrow");
var commonValues = [];
var i, j;
var arr1Length = ids.length;
var arr2Length = selectedIDs.length;
for (i = 0; i < arr1Length; i++) {
for (j = 0; j < arr2Length; j++) {
if (ids[i] === selectedIDs[j]) {
commonValues.push(ids[i]);
}
}
}
for (var i = 0; i < commonValues.length; i++) {
$("#jqGrid").jqGrid('setSelection', commonValues[i], false);
}
return true;
},
rowdata.Category; is the variable which the table is grouped. The only issue is that the user cannot untick what he/she has already selected in a group. so it works like a radio button set. But it works for my requirement.
hopefully we can improve this and introduce radio kind of behavior for grouping in jqgrid.
thanks everyone.

jqGrid: is it possible to selectively make cells editable?

how can I make one single cell editable out of a not editable column?
my javaScript looks like this:
$( '#grid' ).jqGrid({
// ...
cellEdit : true,
colModel : [
{ name : "id", index : "id", editable : false },
{ name : "wbs", index : "wbs", editable : false },
{ name : "value", index : "value", editable : false }
],
loadComplete : function(data) {
// ... foreach ( cell in data.rows.columns ) ...
if ( cell.shouldBeEditable ) {
jQuery('#grid').setCell(cell.row, cell.col, '', 'green', { editable : true });
}
}
// ...
}
so, after globally setting columns as not editable, I try to set them as editable locally, based on some criteria (to identify them more easily I also paint them green).
Alas, it's not working: cells become green, but when I try to click them they do not become editable.
Inspecting the selected cell with firebug reveals the edit-cell class to be correctly applied.
As a last note, it's working if I set columns as editable in the first instance.
I suggest you do it in reverse. Make the column editable, but disable the cells that you do not want to be editable. This is a function I wrote to disable cells:
// cellName is the name defined in your colModel
function disableGridCell(cellName) {
var cell = $('[name="' + cellName + '"]');
cell.css('display', 'none');
var div = $("<div>")
.css('width', '100%')
.css('height', '100%')
.css('border', '1px solid #000')
.css('background-color', '#CCC')
.text('xxxxxxxxxxxx');
cell.parent().append(div);
}
I call disableGridCell inside of my onEditFunc of my grid's editRow function:
$('#grid').jqGrid('editRow', id, keys, onEditFunc);
function onEditFunc(id) {
if (condition to disable cell) {
disableGridCell('CellName');
}
}
I found a nice workaround to the problem (thanks to Walter for getting me on the right track).
Instead of locking all the cells and selectively unlocking them in a declarative manner (which may lead to long load times), I'm declaring all the cells as editable.
Then I'm attaching a callback to afterEditCell option:
var $grid = $('#grid');
$grid.jqGrid({
// ...
afterEditCell : function(rowid, cellname, value, iRow, iCol) {
if ( shouldNotBeEditable(iRow, iCol) ) {
$grid.setCell(rowid, cellname, '', 'not-editable-cell');
$grid.restoreCell(iRow, iCol);
}
},
// ...
});
This way the cell is immediately reset to its previous value and I ensure that the callback gets called only once per not-to-be-edited cell, since the cell is made into a not-editable-cell after the first call.
colModel:[
{name:'description',index:'description', width:4, sortable: false, editable: true},
{name:'qty',index:'qty', width:1,sortable: false, editable: true},
{name:'cost',index:'cost', width:1, editable: true, sortable: false},
{name:'totalCost',index:'totalCost', width:1, sortable: false}
onCellSelect: function(rowid, iCol, cellcontent, e) {
//here it will allow editing of either qty or cost col for that rowid
//only if both cols have a numeric value already
if(iCol===1 || iCol===2) {
var rowdata = $("#projectTable").getRowData(rowid);
var itemCost = parseInt(rowdata['cost']);
var qty = parseInt(rowdata['qty']);
if(!$.isNumeric(itemCost) || !$.isNumeric(qty)) {
$("#projectTable").jqGrid('setColProp','qty',{editable: false});
$("#projectTable").jqGrid('setColProp','cost',{editable: false});
} else {
$("#projectTable").jqGrid('setColProp','qty',{editable: true});
$("#projectTable").jqGrid('setColProp','cost',{editable: true});
}
}
},

Resources