I've just achieved the result which I expected. Based on the @SiMag's comments, I publish the answer which includes several possiable solutions. The post is divided into several sections which allow you to understand what I did wrong and what I did to fix that.
What did I do wrong?
As @SiMag wrote:
The script tag isn't updated after the first load so the #{visualizationController.getData...} expressions aren't evaluated anymore and they are referring to the old data.
What exactly was my problem?
At the moment, when I know what I did wrong, the correct questions are:
- How to pass several values from the CDI bean to the javascript?
- How to call the javascript function (passing the ready and required data to this function) on the server or client side?
How should my page work?
This is the last question to which you've to know answer to understand what I'll do in the next sections. My page should work like this:
- When the page is loaded/opened, the
createChart
javascript function should be executed (automatically without any pressing the button or the link) and the function should use several values which I set in the CDI bean. As the result of this action, the chart should appear.
- Each time when I press the button, the
changeData
javascript function should be executed and the function should use several values which I set in the CDI bean. As the result of this action, the chart should update its data which it displays.
If you would like to achieve something different, it isn't the problem. Based on the solutions which you can find here, you'll be able to achieve what you want.
First solution
The first solution is based on:
- passing some values from the CDI bean to the javascript by means of the
RequestContext
. To achieve that I based on the primefaces documentation (chapter 11.1 RequestContext);
- calling the
createChart
function on the server side;
- calling the
changeChart
function on the client side.
First of all I've added three parameters to my javascript functions (one for each value which I want to pass to the javascript code from the CDI bean). At the moment, the script should look like this:
<script type="text/javascript">
//<![CDATA[
var chart;
var protos;
var dataChart;
var color;
var seriesChart;
var i, len;
//Creating the chart.
function createChart(protosArg, dataArg, colorsArg) {
$(function() {
//The data are parsed
protos = JSON.parse(protosArg);
dataChart = eval(dataArg);
colors = JSON.parse(colorsArg);
seriesChart = [];
//Creating several series of the data
for (i = 0, len = protos.length; i < len; i++) {
seriesChart.push({
color: color[i],
name: protos[i],
data: dataChart[i]
//other options
});
}
console.time('scatter');
console.time('asyncRender');
Highcharts.setOptions({
lang: {
//translate
}
});
// Create the chart
chart = new Highcharts.Chart({
//other options
chart: {
//other options
renderTo: "container"
},
series : seriesChart
});
console.timeEnd('scatter');
});
}
//Updating the chart using the new supplied data.
function changeData(protosArg, dataArg, colorsArg){
$(function() {
protos = JSON.parse(protosArg);
dataChart = eval(dataArg);
colors = JSON.parse(colorsArg);
seriesChart = [];
//Creating several series of the data using the new supplied data.
for (i = 0, len = protos.length; i < len; i++) {
seriesChart.push({
color: color[i],
name: protos[i],
data: dataChart[i]
//other options
});
}
//Removing the old data from the chart.
for (i = 0, len = chart.series.length; i < len; i++) {
chart.series[0].remove(false);
}
//Inserting the new data to the chart.
for (i = 0, len = protos.length; i < len; i++) {
chart.addSeries(seriesChart[i],false);
}
chart.redraw();
});
}
//]]>
</script>
Let's move on to the server. Here I've to show you, how my data look like. On the server side I've created three String
variables. Output of this variables on the server side look like:
- protos:
["tcp","udp",...]
- data:
[[[date,23],[date,1234]],[[date,1234]]]
- color:
["black","white",...]
How you see above, I use to JSON.parse()
and eval()
methods to save the passed data. Why? Because the passed data are interpreted as the string by the browser, so we've to convert the data to the array.
On the server side I've also created several callback parameters using RequestContext#addCallbackParam()
method. The getProtos()
, getData()
and getColors()
methods return the string with my prepared data.
requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("protosArg", getProtos());
requestContext.addCallbackParam("dataArg", getData());
requestContext.addCallbackParam("colorsArg", getColors());
Finally, I call RequestContext#execute()
method which calls the createChart
javascript function and passes three required values:
@PostConstruct
public void init() {
//Creating the callback parameters.
initData();
requestContext.execute(String.format("createChart('%s', '%s', '%s')", getProtos(),getData(),getColors()));
}
Note that, I've used ''
characters to achieve the correct representation on the javascript side. At the moment, the data will be interpreted as the string by the browser. I call execute()
method in the init()
method (which has the @PostConstruct
annotation) of the CDI bean, so I'm sure that:
- the
createChart
javascript function will be executed only once at the beginning (the CDI bean has @ViewScoped
annotation);
- the data which will be downloaded in the script, are ready.
The last thing which I've done on the client side is: change the value of the oncomplete
attribute of the <p:commandButton>
component. The oncomplete
attribute calls changeData
javascript function. The args
parameter refers to the callback parameters which I've set on the server side.
<p:commandButton id="filterButton" value="Filter" action="#{chart2Controller.actionFilterButton()}"
disabled="#{!chart2Controller.visibilityFilterButton}"
update="filterForm"
oncomplete="changeData(args.protosArg, args.dataArg, args.colorsArg);"/>
Part of my CDI bean:
@Named
@ViewScoped
public class Chart2Controller implements Serializable {
/**
* Start method.
*/
@PostConstruct
public void init() {
//Creating the callback parameters.
initData();
requestContext.execute(String.format("createChart('%s', '%s', '%s')", getProtos(),getData(),getColors()));
}
/**
* Downloading the data from the database.
*/
private void initData(){
/*
* Sending the query to the database including the filter options of the form,
* saving the result and preparing the data basing on the result.
*/
//Preparing the callback parameters.
requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("protos", protos);
requestContext.addCallbackParam("data", data);
requestContext.addCallbackParam("colors", colors);
}
/**
* Action for the filter button.
*/
public void actionFilterButton(){
initData();
}
/**
* Visibility for filter button.
*/
public boolean getVisibilityFilterButton(){
//return true or false.
}
//Getter and Setter
private static final long serialVersionUID = -8128862377479499815L;
@Inject
private VisualizationService visualizationService;
private RequestContext requestContext;
private List<String> kindOfNetwork;
private List<String> selectedKindOfNetwork;
private String protos;
private String data;
private String colors;
}
Second solution
The second solution is based on:
- passing some values from the CDI bean to the javascript by means of the
RequestContext
;
- calling the
createChart
function on the client side;
- calling the
changeChart
function on the client side.
How you see, this solution is similar to the previous solution but it's different place where the createChart
function is calling. In this case, I've called the createChart
on the client side and I've used the EL to supply the values from the CDI bean.
I'll not repeat the code, so I'll tell you only what I did to achieve the expected result. On the server side I've done the same things like in the previous solution except for calling the RequestContext#execute()
method. Instead of that, I've called the createChart
function on the client side at the end of the script. The script should like this:
<script type="text/javascript">
//<![CDATA[
var protos;
var dataChart;
var color;
//other code
//Creating the chart.
function createChart(protosArg, dataArg, colorsArg) {
$(function() {
protos = protosArg;
dataChart = dataArg;
colors = colorsArg;
//other code
});
}
//Updating the chart using the new supplied data.
function changeData(protosArg, dataArg, colorsArg){
$(function() {
//The data are parsed
protos = JSON.parse(protosArg);
dataChart = eval(dataArg);
colors = JSON.parse(colorsArg);
//other code
});
}
createChart(#{chart2Controller.protos}, #{chart2Controller.data}, #{chart2Controller.colors});
//]]>
</script>
Note that, I've also deleted JSON.parse()
and eval()
methods from the createChart
function because the ELs have written directly into the javascript code and the browser correctly interprets the data as the array.
Third solution
The third solution is based on:
- passing some values from the CDI bean to the javascript by means of the
<h:inputHidden>
componenets;
- calling the
createChart
function on the client side;
- calling the
changeChart
function on the client side.
This solution is quite different. On the beginning I've added three <h:inputHidden>
componenets to my form (one for each value which I want to pass to the javascript). The form should look like this:
<h:form id="filterForm">
<h:inputHidden id="protos" value="#{chart2Controller.protos}" />
<h:inputHidden id="data" value="#{chart2Controller.data}" />
<h:inputHidden id="colors" value="#{chart2Controller.colors}" />
<p:selectManyCheckbox id="filter" value="#{chart2Controller.selectedKindOfNetwork}" layout="responsive" columns="4">
<p:ajax update="filterButton" />
<f:selectItems value="#{chart2Controller.kindOfNetwork}" var="kindOfNetwork" itemLabel="#{kindOfNetwork}" itemValue="#{kindOfNetwork}" />
</p:selectManyCheckbox>
<br/>
<p:commandButton id="filterButton" value="Filtruj" action="#{chart2Controller.actionFilterButton()}"
disabled="#{!chart2Controller.visibilityFilterButton}"
update="filterForm"
oncomplete="changeData();"/>
</h:form>
How you see above, I've added the id
and value
attributes of the <h:inputHidden>
and I've saved the required data in the value
attributes.
Note that, when you press the button, the form is updated (look at the update
attribute of the <p:commandButton>
component), so I'm sure that the data in the value
attributes of the inputs will be downloaded every time when I press the button.
Finally, I've referred to these values in the javascript code by means of document.getElementById("filterForm:idOfInput").value
. Remember to use JSON.parse()
or eval()
because the data are interpreted as the string. The script should look like this:
<script type="text/javascript">
//<![CDATA[
var chart;
var protos;
var dataChart;
var colors;
function createChart() {
$(function() {
protos = JSON.parse(document.getElementById("filterForm:protos").value);
dataChart = eval(document.getElementById("filterForm:data").value);
colors = JSON.parse(document.getElementById("filterForm:colors").value);
//other code
});
}
function changeData(){
$(function() {
protos = JSON.parse(document.getElementById("filterForm:protos").value);
dataChart = eval(document.getElementById("filterForm:data").value);
colors = JSON.parse(document.getElementById("filterForm:colors").value);
//other code
});
}
//]]>
</script>
Highcharts and change the chart's data
In my case I don't know how many series of the data I'll get after each pressed the button, so it's possiable that I'll get different number of series each time. Highcharts API doesn't support this case. You can use combination of Chart.addSeries(), Series.setData() and Series.remove() methods in loop for. If I've a little more time, I'll do that and I'll update this answer. The below you can find some ways to recreate/update the chart.
If you don't know the number of the series of the data.
First solution
If you don't know how many series of the data you'll get (like in my case) you can remove current series of the data using Series.remove() and then add new series of the data by means of Chart.addSeries():
function changeData(...){
//Preparation of the received data.
//Removing the old data from the chart.
for (i = 0, len = chart.series.length; i < len; i++) {
chart.series[0].remove(false);
}
//Inserting the new data to the chart.
for (i = 0, len = protos.length; i < len; i++) {
chart.addSeries(seriesChart[i],false);
}
chart.redraw();
}
Note that, I've set false
for the redraw
parameters due to efficient. The Highcharts API says:
If doing more operations on the chart, it is a good idea to set redraw to false and call chart.redraw() after.
Second solution
You can also destroy the chart and recreate it. In this case you've to also change the changeData
javascript function like this:
function changeData(...){
chart.destroy();
createChart(...);
}
If you know the number of the series of the data.
This case is easier than the previous one. Look at the code below, it doesn't need any explanation:
function changeData(...){
//Preparation of the received data.
for(var i = 0; i < chart.series.length; i++){
chart.series[i].setData(seriesData[i],false);
}
chart.redraw();
}