I'm trying to make multiple notification with for loops with different ID's.
I don't know why when I test the codes, only one notification play? The other notification cancelled (not play).
this is my code
addNotifications(){
this.localNotifications.cancelAll();
this.TempExamsList.forEach(e=>{
let notificationTime = new Date();
notificationTime.setFullYear(
parseInt(moment(e.date,"YYYY-MM-DD").format("YYYY")),
parseInt(moment(e.date,"YYYY-MM-DD").format("MM"))-1,
parseInt(moment(e.date,"YYYY-MM-DD").format("DD")));
notificationTime.setHours(parseInt(moment(e.time,"HH:mm").format("HH")));
notificationTime.setMinutes(parseInt(moment(e.time,"HH:mm").format("mm")));
this.localNotifications.schedule({
id: new Date().getUTCMilliseconds(),
title: e.name,
text: "Location: " + e.location + " - Time: " + e.time,
at: notificationTime,
//sound: null,
});
})
this.TempTasksList.forEach(t=>{
let notificationTime = new Date();
notificationTime.setFullYear(
parseInt(moment(t.date,"YYYY-MM-DD").format("YYYY")),
parseInt(moment(t.date,"YYYY-MM-DD").format("MM"))-1,
parseInt(moment(t.date,"YYYY-MM-DD").format("DD")));
notificationTime.setHours(parseInt(moment(t.time,"HH:mm").format("HH")));
notificationTime.setMinutes(parseInt(moment(t.time,"HH:mm").format("mm")));
this.localNotifications.schedule({
id: new Date().getUTCMilliseconds(),
title: t.name,
text: "Time: " + t.time,
at: notificationTime,
//sound: null,
});
})
}
Based on ionic documentation :
this.localNotifications.schedule([{
id: 1,
text: 'Multi ILocalNotification 1',
sound: isAndroid ? 'file://sound.mp3': 'file://beep.caf',
data: { secret:key }
},{
id: 2,
title: 'Local ILocalNotification Example',
text: 'Multi ILocalNotification 2',
icon: 'http://example.com/icon.png'
}]);
Looping condition
this.localNotificationsArray = [];
this.tempTaskList.forEach( data =>{
this.localNotificationsArray.push({
id: 1,
text: data.yourTask,
title: data.title,
icon: 'http://example.com/icon.png'
data: { secret:key }
}
});
});
this.localNotifications.schedule(this.localNotificationsArray);
Just try , maybe works .
If i were you, i will not load so many item on notification . Just use once , and describe detail in list view.
Your miss is on multiple notification explained using [] ex:this.localNotifications.schedule([])
Related
I am currently creating a podcast player feature for one of the apps I am developing. However I've hit a wall with this error which is to update the currentIndex of the array to the next one. This is the array i've created to test out the podcast plater
const [ episodes, setEpisodes ] = useState([
{
id: 1,
date: 'Today',
title: 'Episode 10: Postcast 1',
description: 'This is the podcast description',
duration: '25 mins',
image: require('../../../assets/images/podcast_image.jpg'),
audio_url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3',
},
{
id: 2,
date: '10/11/2020',
title: 'Episode 11: Postcast 2',
description: 'This is the podcast description',
duration: '25 mins',
image: require('../../../assets/images/podcast_image.jpg'),
audio_url: 'https://audio-previews.elements.envatousercontent.com/files/103682271/preview.mp3',
},
{
id: 3,
date: '10/11/2020',
title: 'Episode 12: Postcast 3',
description: 'This is the podcast description',
duration: '25 mins',
image: require('../../../assets/images/podcast_image.jpg'),
audio_url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-16.mp3',
}
]);
I have then passed the currentIndex to the next screen so it renders the correct podcast along with the episodes and podcast object.
<TouchableOpacity onPress={() => navigation.navigate("PodcastPlayer", {
currentIndex: index,
podcast: podcast,
episodes: episodes,
})}>
var { currentIndex } = route.params;
This is how I've used it for the TrackPlayer
const trackPlayerInit = async () => {
await TrackPlayer.setupPlayer();
TrackPlayer.updateOptions({
stopWithApp: true,
notificationCapabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
TrackPlayer.CAPABILITY_STOP
],
capabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
TrackPlayer.CAPABILITY_STOP
],
compactCapabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE
]
});
// After the track player has been set up, we can add to our playlist
await TrackPlayer.add({
id: episodes[currentIndex].id,
url: episodes[currentIndex].audio_url,
type: 'default',
title: episodes[currentIndex].title,
album: 'My Album',
artist: 'Track Artist',
artwork: 'https://picsum.photos/100'
});
return true;
};
When I go to skip the track it does log the next track in the console, but it does not update on the actual screen so I am not sure, i have tried to put the currentIndex inside of an new state and update it through that so the screen would re-render once the next button has been clicked. But I've had no luck.
This is the function that is called when I click on the next track icon
<TouchableOpacity style={styles.control} onPress={() => handleNextTrack()}>
<Ionicons name='ios-skip-forward' size={30} color={Colours.type.dark_blue} />
</TouchableOpacity>
handleNextTrack = async() => {
currentIndex + 1;
console.log(currentIndex);
// setNextEpisode(currentIndex);
// get the id of the current track
let trackId = await TrackPlayer.getCurrentTrack();
TrackPlayer.skip(trackId);
console.log(trackId);
}
Any help would be much appreciated!
Try this in your handleNextTrack function:
currentIndex = currentIndex + 1; // instead of just currentIndex + 1;
I trying to do my own Google Science Journal, but web version with MQTT protocol, but my code have so many bugs.
Some clarification about my code:
google.charts.load is calling in the parent component, then callback make a call for the child component
time props is an interval, change between true and false every second for collect data.
I need to watch changes of a data with multiple dependencies.
How my code works?
When a new instance is created, appear an empty google chart
The chart start to show data every second.
The tittle for the chart change when a new tab is selected and the chart data is erased.
This code has a problem, is the order in which the functions are called (generate some problems with google chart), but I fixed it, moving functions from computed to methods and calling in order.
I just want to know why this computed functions don't work, I mean, they aren't never call.
<template>
<v-card>
<v-tabs
v-model="active_tab"
background-color="red lighten-2"
dark
show-arrows
>
<v-tab
v-for="(n) in tabs"
:key="n.id"
>
{{n.text}}
<v-icon>{{n.icon}}</v-icon>
</v-tab>
</v-tabs>
<div ref="char_div"></div>
</v-card>
</template>
<script>
import debounce from 'debounce'
export default {
name: 'charts',
props:{
time: {
type: Boolean,
default: false
}
},
data(){
return {
chart: null,
options: null,
value: 0,
previus: null,
data: null,
tabs: [
{id: 0, text: "Aceleración lineal", unit: "(m/s²)", icon: ""},
{id: 1, text: "Aceleración en x", unit: "(m/s²)", icon: ""},
{id: 2, text: "Aceleración en y", unit: "(m/s²)", icon: ""},
{id: 3, text: "Aceleración en z", unit: "(m/s²)", icon: ""},
{id: 4, text: "Velocidad lineal", unit: "(m/s)", icon: ""},
{id: 5, text: "Luz ambiental", unit: "(lux)", icon: ""},
{id: 6, text: "Intensidad sonora", unit: "(dB)", icon: ""},
{id: 7, text: "Tono", unit: "(Hz)", icon: ""},
{id: 8, text: "Barómetro", unit: "(hPa)", icon: ""},
{id: 9, text: "Brujula", unit: "grados", icon: ""},
{id: 10, text: "Magnetómetro", unit: "µT", icon: ""},
{id: 11, text: "Humedad", unit: "%", icon: ""},
{id: 12, text: "Temperatura ambiente", unit: "°C", icon: ""}
],
active_tab: 0
}
},
computed: {
newData: function(){
switch (this.active_tab) {
case 0:
this.value = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
break
case 1:
this.value = this.$state.lin_acel.x
break
case 2:
this.value = this.$state.lin_acel.y
break
case 3:
this.value = this.$state.lin_acel.z
break
case 4:
if (this.previus != null){
//var cons = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
var ax = this.$state.lin_acel.x,
ay = this.$state.lin_acel.y,
az = this.$state.lin_acel.z
var nuevo = Date.now()
var vx = ax * ((nuevo - this.previus)/1000),
vy = ay * ((nuevo - this.previus)/1000),
vz = az * ((nuevo - this.previus)/1000)
//this.value += (cons)*((nuevo - this.previus)/1000)
this.value += Math.sqrt(vx^2 + vy^2 + vz^2)
this.previus = nuevo
}else{
this.value = Math.sqrt(this.$state.lin_acel.x^2 + this.$state.lin_acel.y^2 + this.$state.lin_acel.z^2)
this.previus = Date.now()
}
case 5:
this.value = this.$state.lux
break
case 6:
this.value = this.$state.noise
break
case 7:
this.value = this.$state.noise
break
case 8:
this.value = this.$state.presion
break
case 9:
var vector = Math.sqrt(this.$state.magneto.x^2 + this.$state.magneto.y^2 + this.$state.magneto.z^2)
break
case 10:
this.value = Math.sqrt(this.$state.magneto.x^2 + this.$state.magneto.y^2 + this.$state.magneto.z^2)
break
default:
this.value = 0
break
}
},
newOptions(){
console.log("new options")
this.options = {
tittle: this.tabs[this.active_tab].text,
crosshair: {orientation: 'vertical', trigger: 'both'},
legend: 'none',
hAxis: {
format:'mm:ss'
}
}
},
drawchart(){
console.log("chart is drawing")
this.chart.draw(this.data, google.charts.Line.convertOptions(this.options));
},
},
watch: {
time: {
immediate: false,
handler(){
this.addData()
}
},
active_tab: {
inmediate: false,
handler(){
this.updatetable()
}
}
},
methods: {
addData(){
this.data.addRows([[new Date(Date.now()),0]])
},
updatetable(){
this.data = null
this.data = new google.visualization.DataTable(
{cols:
[{label: 'tiempo', id: 'x', type: 'date'},
{label: String(this.tabs[this.active_tab].text), id: 'y', type: 'number'}]})
}
},
mounted() {
this.chart = new google.charts.Line(this.$refs.char_div)
this.newOptions
this.updatetable()
this.drawchart
}
}
</script>
<style>
</style>
As Phil says, "Computed property functions are not meant to alter any data properties and are supposed to return a value". Is because computed watchs changes only in return, for example:
//Computed watch this
return this.a + this.b
//{...}
//Not this
this.c = this.a + this.b
I found here and in Vue forums ways to improve this code:
If you need to watch a data with multiple dependencies, you can do the next:
computed: {
multi(){
return [this.a, this.b].join()
}
},
watch: {
multi: {
handler(): {
this.c = this.a + this.b
}
}
}
I thing the best I can find solution was:
created(){
this.$watch(
(this) => (vm.x, vm.y, vm.z, Date.now()),
function () {
// Executes if `x`, `y`, or `z` have changed.
}
)
}
This last also return the function unwatch(), it stop the watcher property, if I do this.watcher = this.$watch(/*data and callback here*/), then I can do this.watcher() stop watcher and make new one, perfect for improve performance of this code.
I have a rally poker app which is custom html where there is a screen for Team member and a screen for Scrum Master. What I need is how to control concurrent updates from team members when they use this custom html app. As of today, the app only takes the latest updates from the user who submitted last in concurrent session. It overrides everyone else data. It would be great help if someone can post the sample code of how to check for concurrent session and stop overriding of user inputs in concurrent sessions. Thanks for your help.
Click here for Scrum Master View Screenshot
#kyle - I tried to add Version ID as mentioned by you
Thanks Kyle. I did tried a cumbersome way of checking the versionID by repeating the fetch code however in my testing, it proved that versionID wasnt setting properly for concurrent sessions. I have pasted the code snippet which has the versionID check. Can you please review and let me know if anything needs to be added. Appreciate your help
Ext.create('Ext.window.Window', {
title: 'Planning Poker-Team Member view UserStoryNumber : '+userstorynumber, height: 200, width: 400, layout: 'fit',
items: [
//*************** START PANEL FORM 1 *******************
Ext.create('Ext.form.Panel', {
bodyPadding: 5, width: 350, url: 'save-form.php',
layout: { type: 'vbox', align: 'stretch', pack: 'start' },
defaults: { anchor: '100%' },
//Fields
defaultType: 'textfield',
items: [
{ fieldLabel: 'Estimate', name: 'Estimate', allowBlank: false, id: 'estimate' },
{ fieldLabel: 'Justification', name: 'Justification', allowBlank: false, id: 'justification' }
],
// Reset and Submit buttons
buttons: [
//{ text: 'Reset', handler: function () { this.up('form').getForm().reset(); } },
{
text: 'Submit', formBind: true, disabled: true,
handler: function () {
//alert(f_newEstimate('name','points','role','justification'));
var EstimateObject = getExistingEstimates(result);
if (EstimateObject)
console.log('Notes ************ ' + result.get('Notes'));
console.log('Stringfy ************ ' + JSON.stringify(EstimateObject));
//rec.set('PlanEstimate', estFinal);
var jsonobj = f_newEstimate(name, Ext.getCmp('estimate').getValue(), role, Ext.getCmp('justification').getValue());
console.log("json raw is", jsonobj);
//console.log("json justi is", jsonobj.justification);
console.log("PO note is", result.get('c_PONote'));
//var existingPONote = result.get('c_PONote');
//var newponote = existingPONote + ',' + jsonobj;
var CurrentVersionID;
var newVersionID;
//Exp Karthik Starts
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
CurrentVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
var existingPONote = result.get('c_PONote');
var newponote = '';
if (existingPONote.length == 0) {
newponote = '[' + jsonobj + ']';
}
else {
replacestr = ',' + jsonobj + ']';
newponote = existingPONote.replace(']', replacestr);
}
rec.set('c_PONote', newponote);
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
newVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
if (CurrentVersionID == newVersionID) {
rec.save();
}
else
{
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
newVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
var existingPONote = myStore.data.items["0"].data.c_PONote;
var newponote = '';
if (existingPONote.length == 0) {
newponote = '[' + jsonobj + ']';
}
else {
replacestr = ',' + jsonobj + ']';
newponote = existingPONote.replace(']', replacestr);
}
rec.set('c_PONote', newponote);
console.log("Concurrent versions saved", newponote);
rec.save();
}
console.log('Submit Success1');
this.up('window').close();
}
}]
All objects in WSAPI include a VersionId field which is automatically incremented on the server every time the object is updated. The way the product deals with concurrency issues is to always fetch this VersionId field. Then users can make updates locally. When users save, first the story is read again. If the VersionId is the same as it was when it was first read, the update is safe to send. If that VersionId is higher though, that means another user has updated the story in the meantime.
In the case of a conflict you can just merge your local changes into that newly read object and then try the save again. You can repeat this as needed until the user is able to perform a clean save.
I'm using Dojo GridX with many modules, including filter:
grid = new Grid({
cacheClass : Cache,
structure: structure,
store: store,
modules : [ Sort, ColumnResizer, Pagination, PaginationBar, CellWidget, GridEdit,
Filter, FilterBar, QuickFilter, HiddenColumns, HScroller ],
autoHeight : true, autoWidth: false,
paginationBarSizes: [25, 50, 100],
paginationBarPosition: 'top,bottom',
}, gridNode);
grid.filterBar.applyFilter({type: 'all', conditions: [
{colId: 'type', condition: 'equal', type: 'Text', value: 'car'}
]})
I've wanted to access the items, that are matching the filter that was set. I've travelled through grid property in DOM explorer, I've found many store references in many modules, but all of them contained all items.
Is it possible to find out what items are visible in grid because they are matching filter, or at least those that are visible on current page? If so, how to do that?
My solution is:
try {
var filterData = [];
var ids = grid.model._exts.clientFilter._ids;
for ( var i = 0; i < ids.length; ++i) {
var id = ids[i];
var item = grid.model.store.get(id);
filterData.push(item);
}
var store = new MemoryStore({
data : filterData
});
} catch (error) {
console.log("Filter is not set.");
}
I was able to obtain filtered gridX data rows using gridX Exporter. Add this Exporter module to your grid. This module does exports the filtered data. Then, convert CSV to Json. There are many CSV to Json conversion javasripts out there.
this.navResult.grid.exporter.toCSV(args).then(this.showResult, this.onError, null)
Based on AirG answer I have designed the following solution. Take into account that there are two cases, with or without filter and that you must be aware of the order of rows if you have applied some sort. At least this works for me.
var store = new Store({
idProperty: "idPeople", data: [
{ idPeople: 1, name: 'John', score: 130, city: 'New York', birthday: '31/02/1980' },
{ idPeople: 2, name: 'Alice', score: 123, city: 'Wáshington', birthday: '07/12/1984' },
{ idPeople: 3, name: 'Lee', score: 149, city: 'Shanghai', birthday: '8/10/1986' },
...
]
});
gridx = new GridX({
id: 'mygridx',
cacheClass: Cache,
store: store,
...
modules: [
...
{
moduleClass: Dod,
defaultShow: false,
useAnimation: true,
showExpando: true,
detailProvider: gridXDetailProvider
},
...
],
...
}, 'gridNode');
function gridXDetailProvider (grid, rowId, detailNode, rendered) {
gridXGetDetailContent(grid, rowId, detailNode);
rendered.callback();
return rendered;
}
function gridXGetDetailContent(grid, rowId, detailNode) {
if (grid.model._exts.clientFilter._ids === undefined || grid.model._exts.clientFilter._ids === 0) {
// No filter, with or without sort
detailNode.innerHTML = 'Hello ' + grid.row(grid.model._cache._priority.indexOf(rowId)).item().name + " with id " +
grid.row(grid.model._cache._priority.indexOf(rowId)).item().idPeople;
} else {
// With filter, with or without sort
detailNode.innerHTML = 'Hello ' + grid.row(grid.model._exts.clientFilter._ids.indexOf(rowId)).item().name + " with id " +
grid.row(grid.model._exts.clientFilter._ids.indexOf(rowId)).item().idPeople;
}
}
Hope that helps,
Santiago Horcajo
function getFilteredData() {
var filteredIds = grid.model._exts.clientFilter._ids;
return grid.store.data.filter(function(item) {
return filteredIds.indexOf(item.id) > -1;
});
}
I'm looking to try and do a cumulative flow diagram by story points in rally with their newer API/SDK and found some sample code on their GitHub page RallyAnalytics GitHub
So after some work I have it working to some degree but don't understand or can find any documentation for how to configure this more. It looks like the report being generated is doing count and not the PlanEstimate which I tried to add in fieldsToSum. How can I get it to sum the PlanEstimate field by c_KanbanState and not just give me a count of stories that matched the c_KanbanState for that week? Sample code below minus the minified code from GitHub.
var userConfig = {
title: 'Cumulative Flow Diagram',
debug: false,
trace: false,
// asOf: "2012-11-01", // Optional. Only supply if want a specific time frame. Do not send in new Date().toISOString().
granularity: 'week',
fieldsToSum: ['PlanEstimate'],
scopeField: "Project", // Supports Iteration, Release, Tags, Project, _ProjectHierarchy, _ItemHierarchy
scopeValue: 'scope',
scopeData: {
StartDate: new Date("2012-12-01T07:00:00.000Z"),
EndDate: new Date(new Date()),
Name: ""
},
//fieldNames: ['count', 'PlanEstimate']
kanbanStateField: 'c_KanbanState',
chartSeries: [
{name: 'To Do'},
{name: 'Dev Ready'},
{name: 'In Dev'},
{name: 'Peer Review'},
{name: 'QA Ready'},
{name: 'QA Done'},
{name: 'Accepted'}
]
}
(function() {
var charts = {};
var visualizer;
var nameToDisplayNameMap;
createVisualization = function(visualizationData) {
if (typeof visualizationData !== "undefined" && visualizationData !== null) {
categories = visualizationData.categories;
series = visualizationData.series;
charts.lowestValueInLastState = visualizationData.lowestValueInLastState;
charts.chart = new Highcharts.Chart({
chart: {
renderTo: 'chart-container',
defaultSeriesType: 'column',
zoomType: 'x'
},
legend: {
enabled: true
},
credits: {
enabled: false
},
title: {
text: userConfig.title
},
subtitle: {
text: userConfig.scopeData.Name
},
xAxis: {
categories: categories,
tickmarkPlacement: 'on',
tickInterval: Math.floor(categories.length / 12) + 1,
title: {
text: userConfig.granularity.slice(0, 1).toUpperCase() + userConfig.granularity.slice(1) + 's'
}
},
yAxis: [
{
title: {
text: 'Total Points',
},
min: charts.lowestValueInLastState
}
],
tooltip: {
formatter: function() {
point = this.point
s = point.series.name + ': <b>' + point.y + '</b><br \>';
if (point.x == point.series.data.length - 1) {
s += point.category.slice(0, point.category.length - 1) + ' to-date';
} else {
s += point.category;
}
return s;
}
},
plotOptions: {
series: {
events: {
legendItemClick: function(event) {
if (this.chart.series.length == this.index + 1) {
if (!this.visible) {
this.chart.yAxis[0].setExtremes(charts.lowestValueInLastState);
} else {
this.chart.yAxis[0].setExtremes(0);
};
};
return true;
}
}
}
},
series: series
}); // end of chart
} else {
// Put a spinner in the chart containers until first fetch returns
$('#chart-container')
.html('<img height="20px" src="https://rally1.rallydev.com/slm/js-lib/ext/2.2/resources/images/default/grid/loading.gif"></img>')
.attr("style", "text-align:center");
};
};
$(document).ready(function() {
visualizer = new CFDVisualizer(charts, userConfig, createVisualization);
});
})();
You may be using a slightly older version because the latest doesn't have the fieldsToSum parameter in the config, but you can upgrade the chart to sum PlanEstimate by chaging a few lines in the CFDVisualizer.coffee to this:
#config.lumenizeCalculatorConfig.metrics = [
{f: 'groupBySum', field: 'PlanEstimate', groupByField: #config.kanbanStateField, allowedValues: allowedValues}
]
from:
#config.lumenizeCalculatorConfig.metrics = [
{f: 'groupByCount', groupByField: #config.kanbanStateField, allowedValues: allowedValues}
]
You should probably also change the axis label in the cfd.html.
If this proves too difficult to accomplish (CoffeeScript may be unfamiliar), let me know and I'll post a new version to GitHub.