I have placed a textbox widget inside grid cell by using formatter. However, I cannot move my cursor around nor select text inside the textbox.
E.g.
http://jsfiddle.net/g33m9/69/
Does anyone know how to fix this?
Thanks
You need to set the column as 'editable' so that the Grid component will know how to handle keypressed events. So a modification to the layout is in order
from
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func}
]];
to
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func, editable: true}
]];
Edit state activates by doubleclick.
Now, OP wants it to be a fully bloated widget, popping up in the editable state. For this to be scaleable up with any number of rows/columns i will restrict this to the edit state, so that the value simply shows text but once double-clicked it will pop a FilteringSelect. Same principle goes with the dijit widget ValidationTextBox.
Currently (1.7.2) the possible celltypes are:
dojox.grid.cells.Bool
dojox.grid.cells.ComboBox
dojox.grid.cells.DateTextBox
dojox.grid.cells.Select
Catch me SEO:
example of custom dojox.grid cellType widget - semi-programmatic
First step - create some data
var i = 0,
data = {
identifier: 'id',
items: [
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++}
]
},
// The item label which holds visible value and which holds the value to represent
searchAttr = 'value',
valueAttr = data.identifier,
// The store to use for select widget
store = new dojo.data.ItemFileReadStore({ data: data }),
// And the options, reassembling the valid options we will present in dropdown
// Used when cellType is dojox.grid.cells.Select to name the allowable options
options = [];
dojo.forEach(data.items, function(it) { options.push(it[searchAttr])});
Tricky part - Define a cellType
Lets extend the existing dojox.grid.cells.Cell, it has two key features - an edit-state-formatter and the default-formatter. The default would work just fine. Last but not least, we'll override the '_finish' function allthough allow the Cell to process its own definition too.
var whenIdle = function( /*inContext, inMethod, args ...*/ ) {
setTimeout(dojo.hitch.apply(dojo, arguments), 0);
};
var FilteringSelectCell = declare("dojox.grid.cells.FilteringSelect", [dojox.grid.cells.Cell], {
options: null,
values: null,
_destroyOnRemove: true,
constructor: function(inCell){
this.values = this.values || this.options;
},
selectMarkupFactory: function(cellData, rowIndex) {
var h = ['<select data-dojo-type="dijit.form.FilteringSelect" id="deleteme' + rowIndex + '" name="foo">'];
for (var i = 0, o, v;
((o = this.options[i]) !== undefined) && ((v = this.values[i]) !== undefined); i++) {
v = v.replace ? v.replace(/&/g, '&').replace(/</g, '<') : v;
o = o.replace ? o.replace(/&/g, '&').replace(/</g, '<') : o;
h.push("<option", (cellData == v ? ' selected' : ''), ' value="' + v + '"', ">", o, "</option>");
}
h.push('</select>');
return h;
},
textMarkupFactory: function(cellData, rowIndex) {
return ['<input class="dojoxGridInput" id="deleteme' + rowIndex + '" data-dojo-type="dijit.form.ValidationTextBox" type="text" value="' + cellData + '">']
},
// #override
formatEditing: function(cellData, rowIndex) {
this.needFormatNode(cellData, rowIndex);
var h = (cellData == "W1")
? this.textMarkupFactory(cellData, rowIndex)
: this.selectMarkupFactory(cellData, rowIndex);
// a slight hack here, i had no time to figure out when the html would actually be inserted to the '<td>' so.. Use 'debugger' statement and track function to hook into
whenIdle(function() {
dojo.parser.parse(dojo.byId('deleteme' + rowIndex).parentNode);
var w = dijit.byId('deleteme' + rowIndex);
w.focus()
});
return h.join('');
},
// clean up avoiding multiple widget definitions 'hanging'
_finish: function(inRowIndex) {
this.inherited(arguments)
dijit.byId('deleteme' + inRowIndex).destroy();
},
// needed to read the value properly, will work with either variant
getValue: function(rowIndex) {
var n = this.getEditNode(rowIndex);
n = dijit.getEnclosingWidget(n);
return n.get("value");
}
});
Last bit, a new layout
var layout = [[
{ name: 'Column 1', field: 'col1' },
{ name: 'Column 2', field: 'col2',
cellType: FilteringSelectCell, options: options, editable: true
}
]];
Running sample here http://jsfiddle.net/dgbxw/1/
Related
So my javascript is bad, but I have a datatables need that I cannot figure out. Relevant code here:
$('#lesson-table').DataTable( {
'data': cleanData,
'paging': false,
'order': [[ 8, 'asc' ], [ 3, 'desc' ], [ 1, 'desc' ]],
initComplete: function () {
// this is where we populate the filter dropdowns
this.api().columns([0,1,2,6,7]).every( function () {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo( $(column.header()) )
.on( 'change', function () {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
column
.search( val ? '^'+val+'$' : '', true, false )
.draw();
} );
column.data().unique().sort().each( function ( d, j ) {
var val = $('<div/>').html(d).text();
select.append( '<option value="' + val + '">' + val + '</option>' );
} );
} );
}
} );
// this removes duplicate dropdown values
var usedNames = {};
$("#lesson-table select > option").each(function () {
if (usedNames[this.value]) {
$(this).remove();
} else {
usedNames[this.value] = this.text;
}
});
So the change that was made in my datasource is that columns 1, the second column in the set, is now a comma separated list of items. Most of the time it is one item say: "Entertainment", but sometimes it can be "Entertainment, Sports, Healthcare" At the moment, it will show that list as an option in my dropdown, but what I need it to do is split them up and then filter by contains...not by exact.
Hope that makes sense. Can explain more if needed.
has data
items = {
0: {id:1,name:'foo'},
1: {id:2,name:'bar'},
2: {id:1,name:'foo'}
};
I wont get counted elements like this
result = {
0: {id:1,name:'foo', count:2},
1: {id:2,name:'bar', count:1}
};
lodash has function _.countBy(items, 'name') it's got {'foo': 2, 'bar':1}, i need id too.
If pure JS approach is acceptable, you can try something like this:
Logiic:
Loop over array and copy the object and add a property count and set it to 0.
Now on every iteration update this count variable.
Using above 2 steps, create a hashMap.
Now loop over hashMap again and convert it back to array.
var items = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
var temp = items.reduce(function(p,c){
var defaultValue = {
name: c.name,
id: c.id,
count: 0
};
p[c.name] = p[c.name] || defaultValue
p[c.name].count++;
return p;
}, {});
var result = [];
for( var k in temp ){
result.push(temp[k]);
}
console.log(result)
I have a question about the pie chart in c3.js.
How can I add the total count of a pie chart in the title??
var title = new Array('data1.sql','data2.sql')
var dtitle = new Array('title1','title2')
var chart = new Array('chart0', 'chart1')
for(var i = 0; i < title.length; i++){
chart[i] = c3.generate({
bindto : '#chart' + i,
size: {
height: 550,
width: 800
},
data : {
url : '/json/sql/data/test/' + title[i],
mimeType : 'json',
type : 'donut'
},
donut: {
title: dtitle[i] + ' - Total:' ,
label: {
format: function(value, ratio, id) {
return value;
}
}
}
});
}
The annoying thing here is that the title option can take a function, but the chart variable is not initialised within it so using the c3 api methods can't be done at this point.
So the best (maybe only) way is to add an onrendered callback that adds up the data as you'd need to anyways and then replace the text in the chart's title text using a spot of d3:
onrendered: function () {
var data = this.api.data();
var total = data.reduce (function (subtotal, t) {
return subtotal + t.values.reduce (function (subsubtotal,b) { return subsubtotal + b.value; }, 0);
}, 0);
d3.select(this.config.bindto + " .c3-chart-arcs-title").text("Total: "+total);
}
Edit: If you want it to keep track of a total as you hide/show series use this
var data = this.api.data.shown.call (this.api);
instead of
var data = this.api.data();
I want to display "population" of various countries through the years in the same line chart. The data displayed is based on selections from a multi-select dropdown "Countries". Underlying Data Table has 3 columns:
Year, Country, Population
2012,countryA,33
2013,countryA,35
2014,countryA,40
2012,countryB,65
2013,countryB,70
2014,countryB,75
2012,countryC,15
2013,countryC,20
2014,countryC,25
I am trying to create a pivoted Data View from the underlying Data Table
The code I am using is:
function drawLineChart() {
var arr = $('#country').val();
var lineChartJson = $.ajax({
url: "../json/lineChart.json",
dataType: "json",
async: false
}).responseText;
var lineChartData = new google.visualization.DataTable(lineChartJson);
var view = new google.visualization.DataView(lineChartData);
var viewCols = [0];
for(var i = 0; i < arr.length; i++) {
var viewCols1 = [{
type: 'number',
label: arr[i],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[i]) ? dt.getValue(row, 2) : null;
}
}];
viewCols = viewCols.concat(viewCols1);
}
view.setColumns(viewCols);
var aggCols = [{
column: 1,
type: 'number',
label: view.getColumnLabel(1),
aggregation: google.visualization.data.sum
}];
for(var i = 2; i < 4; i++) {
var aggCols1 = [{
column: i,
type: 'number',
label: view.getColumnLabel(i),
aggregation: google.visualization.data.sum
}];
aggCols = aggCols.concat(aggCols1);
}
var pivotedData = google.visualization.data.group(view, [0], aggCols);
But this does not seem to work as expected and I just get 1 Line in the chart with values for all countries added up (although I can see the legend for 3 countries)
On the other hand if I set my View columns as below, it works as expected.
view.setColumns([0, {
type: 'number',
label: arr[0],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[0]) ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: arr[1],
calc: function (dt, row) {
// return values of C only for the rows where B = "bar"
return (dt.getValue(row, 1) == arr[1]) ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: arr[2],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[2]) ? dt.getValue(row, 2) : null;
}
}]);
What is going wrong in the loop? Is something wrong with "concat" in the loop where I am creating View Columns? I also saw the viewCols array by using console.log and it seems to have the right elements
I was trying to follow the below post:
Creating pivoted DataView from existing google charts DataTable object
the problem has to do with scope
arr[i] is undefined within calc: function (dt, row)
here is another way to pivot the data...
google.charts.load('current', {
callback: function () {
var arr = [
'countryA',
'countryB',
'countryC'
];
var lineChartData = google.visualization.arrayToDataTable([
['Year', 'Country', 'Population'],
[2012,'countryA',33],
[2013,'countryA',35],
[2014,'countryA',40],
[2012,'countryB',65],
[2013,'countryB',70],
[2014,'countryB',75],
[2012,'countryC',15],
[2013,'countryC',20],
[2014,'countryC',25]
]);
// sort by year
lineChartData.sort([{column: 0}]);
// get unique countries
var countryGroup = google.visualization.data.group(
lineChartData,
[1]
);
// build country data table
var countryData = new google.visualization.DataTable({
cols: [
{label: 'Year', type: 'number'},
]
});
// add column for each country
for (var i = 0; i < countryGroup.getNumberOfRows(); i++) {
countryData.addColumn(
{label: countryGroup.getValue(i, 0), type: 'number'}
);
}
// add row for each year / country
var rowYear;
var rowIndex;
for (var i = 0; i < lineChartData.getNumberOfRows(); i++) {
if (rowYear !== lineChartData.getValue(i, 0)) {
rowYear = lineChartData.getValue(i, 0);
rowIndex = countryData.addRow();
countryData.setValue(rowIndex, 0, rowYear);
}
for (var x = 1; x < countryData.getNumberOfColumns(); x++) {
if (countryData.getColumnLabel(x) === lineChartData.getValue(i, 1)) {
countryData.setValue(rowIndex, x, lineChartData.getValue(i, 2));
}
}
}
// draw agg table
new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table-div',
dataTable: countryData
}).draw();
// draw line chart
new google.visualization.ChartWrapper({
chartType: 'LineChart',
containerId: 'chart-div',
dataTable: countryData
}).draw();
},
packages: ['corechart', 'table']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="table-div"></div>
<div id="chart-div"></div>
I could figure out the problem with my code above.
"calc" is the callback function in loop. So only last value of loop variable "i" is visible within the loop.
Putting a wrapper function fixes it:
for(var i = 0; i <= arr.length; i++)(function(i) {
var viewCols1 = [{
type: 'number',
label: arr[i],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[i]) ? dt.getValue(row, 2) : null;
}
}];
viewCols = viewCols.concat(viewCols1);
})(i);
I am currently implementing a custom data source in a Windows8 application. However, I got some trouble with it: no data is displayed.
First, here is the code:
var dataArray = [
{ title: "Basic banana", text: "Low-fat frozen yogurt", picture: "images/60banana.png" },
// Other data taken from Windows8 ListView quick start
{ title: "Succulent strawberry", text: "Sorbet", picture: "images/60strawberry.png" }
];
var searchAdDataAdapter = WinJS.Class.define(
function () {}, // Constructor
{
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
var that = this;
if (requestIndex >= that._maxCount) {
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
}
var fetchSize, fetchIndex;
// See which side of the requestIndex is the overlap.
if (countBefore > countAfter) {
// Limit the overlap
countAfter = Math.min(countAfter, 10);
// Bound the request size based on the minimum and maximum sizes.
var fetchBefore = Math.max(
Math.min(countBefore, that._maxPageSize - (countAfter + 1)),
that._minPageSize - (countAfter + 1)
);
fetchSize = fetchBefore + countAfter + 1;
fetchIndex = requestIndex - fetchBefore;
} else {
countBefore = Math.min(countBefore, 10);
var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
fetchSize = countBefore + fetchAfter + 1;
fetchIndex = requestIndex - countBefore;
}
// Create an array of IItem objects:
// results =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
for (var i = 0, itemsLength = dataArray.length ; i < itemsLength ; i++) {
var dataItem = dataArray[i];
results.push({
key: (fetchIndex + i).toString(),
data: dataArray[i]
});
}
// Get the count.
count = dataArray.length;
return {
items: results, // The array of items.
offset: requestIndex - fetchIndex, // The index of the requested item in the items array.
totalCount: count
};
},
getCount: function () {
return dataArray.length;
}
}
);
var searchAdDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function () {
this._baseDataSourceConstructor(new searchAdDataAdapter());
});
// Create a namespace to make the data publicly
// accessible.
var publicMembers = {
itemList: new searchAdDataSource()
};
WinJS.Namespace.define("DataExample", publicMembers);
I know the code is a little bit long, but the major part of it is taken from official Microsoft custom data source quick start.
I tried to debug it, but it seems the code contained in itemFromIndex is never used (my breakpoint is never reached).
The HTML code is:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource}">
</div>
I do not use any template for the moment, to simplify the code as more as I can. Data are normally displayed in text this way (but nothing appears).
Have one of this great community any idea?
Furthermore, I do not understand the countBefore and countAfter parameters, even with the documentation. Can somebody explain it to me with other words?
Thanks a lot! :)
Try modifying your HTML code to the following:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList}">
</div>
No need to call the .datasource member, as you are talking to the datasource directly.