我使用JSF 2.2、Primefaces 6.0、CDI和Highcharts 4.2.3。我的一个页面包括一个图表(Highcharts)和一个干扰我的图表的简单表单。我已经剪切了我的代码(看下面),向您展示了我代码中最重要的部分。
我想实现这样的目标:
- 当我在表单中按下
commandButton
时,图表应该使用新数据重新创建
到目前为止,我已经:
- 声明了几个js变量,我在脚本中的函数中使用这些变量
- 创建了一个
createChart
函数,该函数使用#{bean.getDataForChart1a(typeOfData)}
从CDIBean(实际上是从数据库)下载数据(对于我的图表),并将这些数据放入js变量中。除了对数据进行下行编码外,此函数还可以创建图表 - 创建了一个
createNewChart
函数,它破坏了我的当前图表,并调用createChart
函数来创建应该包括新数据的新图表
问题是最后阶段。您可以看到以下内容:我已将oncomplete="createNewChart();"
属性添加到commandButton
中,目前我的页面工作方式如下:
- 当我打开我的页面时,一切都正常。下载数据并创建图表
- 当我按下
commandButton
时,图表会重新创建,但它使用旧数据。我注意到createChart
函数中没有再次下载数据,js也没有在我的#{bean.getDataForChart1a(typeOfData)}
中执行。因此,#{bean.getDataForChart1a(typeOfData)}
在开始时仅被执行一次。我不明白为什么
如何解决此问题
我的xhtml页面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:head>
<!-- loading css -->
</h:head>
<h:body>
<div id="container" style="height: 750px; width: 100%; margin: 0 auto;"/>
<script type="text/javascript">
//<![CDATA[
var chart;
var protos;
var dataChart;
var color;
var seriesChart;
var i, len;
function createChart() {
//Downloading data for the chart
protos = #{visualizationController.getDataForChart1a("protos")};
dataChart = #{visualizationController.getDataForChart1a("data")};
color = #{visualizationController.getDataForChart1a("color")};
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');
}
function createNewChart(){
chart.destroy();
createChart();
}
createChart();
//]]>
</script>
<h:form id="filterForm">
<p:selectManyCheckbox id="filter" value="#{visualizationController.selectedKindOfNetwork}" layout="responsive" columns="4">
<p:ajax update="filterButton" />
<f:selectItems value="#{visualizationController.kindOfNetwork}" var="kindOfNetwork" itemLabel="#{kindOfNetwork}" itemValue="#{kindOfNetwork}" />
</p:selectManyCheckbox>
<br/>
<p:commandButton id="filterButton" value="Filtruj" action="#{visualizationController.actionFilterButtonForChart1a()}"
disabled="#{!visualizationController.visibilityFilterButtonForChart1a}"
update="filterForm"
oncomplete="createNewChart();"/>
</h:form>
</h:body>
</html>
我刚刚达到了预期的结果。根据@SiMag的评论,我发布了答案,其中包括几个可能的解决方案。这篇文章分为几个部分,让你了解我做错了什么,以及我做了什么来解决这个问题。
我做错了什么
正如@SiMag所写:
脚本标记在第一次加载后不会更新,因此不再计算#{visualizationController.getData…}表达式,它们引用的是旧数据。
我到底有什么问题
目前,当我知道自己做错了什么时,正确的问题是:
- 如何将几个值从CDIBean传递到javascript
- 如何在服务器或客户端调用javascript函数(将准备好的和需要的数据传递给该函数)
我的页面应该如何工作
这是最后一个问题,你必须知道答案,才能理解我在下一节中要做什么。我的页面应该是这样工作的:
- 加载/打开页面时,应执行
createChart
javascript函数(无需任何按钮或链接即可自动执行),并且该函数应使用我在CDIBean中设置的几个值。此操作的结果应该是出现图表 - 每次按下按钮时,都应该执行
changeData
javascript函数,并且该函数应该使用我在CDIBean中设置的几个值。此操作的结果是,图表应更新其显示的数据
如果你想取得不同的成就,那不是问题所在。根据你在这里可以找到的解决方案,你将能够实现你想要的。
第一个解决方案
第一种解决方案基于:
- 通过
RequestContext
将一些值从CDIBean传递给javascript。为了实现这一点,我基于素数面文档(第11.1章RequestContext) - 调用服务器侧的CCD_ 15函数
- 调用客户端的CCD_ 16函数
首先,我为我的javascript函数添加了三个参数(每个值一个,我想从CDIBean传递给javascript代码)。目前,脚本应该是这样的:
<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>
让我们转到服务器。在这里,我要向您展示我的数据是什么样子的。在服务器端,我创建了三个String
变量。这个变量在服务器端的输出看起来像:
- 质子:
["tcp","udp",...]
- 数据:
[[[date,23],[date,1234]],[[date,1234]]]
- 颜色:
["black","white",...]
如您所见,我使用JSON.parse()
和eval()
方法来保存传递的数据。为什么?因为浏览器将传递的数据解释为字符串,所以我们必须将数据转换为数组。
在服务器端,我还使用RequestContext#addCallbackParam()
方法创建了几个回调参数。getProtos()
、getData()
和getColors()
方法返回带有我准备的数据的字符串。
requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("protosArg", getProtos());
requestContext.addCallbackParam("dataArg", getData());
requestContext.addCallbackParam("colorsArg", getColors());
最后,我调用RequestContext#execute()
方法,该方法调用createChart
javascript函数并传递三个必需值:
@PostConstruct
public void init() {
//Creating the callback parameters.
initData();
requestContext.execute(String.format("createChart('%s', '%s', '%s')", getProtos(),getData(),getColors()));
}
请注意,我已经使用''
字符在javascript端实现了正确的表示。此时,浏览器会将数据解释为字符串。我在CDIBean的init()
方法(有@PostConstruct
注释)中调用execute()
方法,所以我确信:
createChart
javascript函数在开始时只执行一次(CDIBean有@ViewScoped
注释)- 将在脚本中下载的数据已准备好
我在客户端做的最后一件事是:更改<p:commandButton>
组件的oncomplete
属性的值。oncomplete
属性调用changeData
javascript函数。args
参数是指我在服务器端设置的回调参数。
<p:commandButton id="filterButton" value="Filter" action="#{chart2Controller.actionFilterButton()}"
disabled="#{!chart2Controller.visibilityFilterButton}"
update="filterForm"
oncomplete="changeData(args.protosArg, args.dataArg, args.colorsArg);"/>
我的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;
}
第二个解决方案
第二种解决方案基于:
- 通过
RequestContext
将一些值从CDIBean传递给javascript - 调用客户端的CCD_ 41函数
- 调用客户端的CCD_ 42函数
您可以看到,此解决方案与以前的解决方案相似,但与createChart
函数调用的位置不同。在本例中,我在客户端调用了createChart
,并使用EL提供CDIBean的值。
我不会重复代码,所以我只告诉你我做了什么来达到预期的结果。在服务器端,除了调用RequestContext#execute()
方法之外,我做了与前面的解决方案相同的事情。相反,我在脚本末尾调用了客户端的createChart
函数。脚本应该是这样的:
<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>
请注意,我还从createChart
函数中删除了JSON.parse()
和eval()
方法,因为EL直接写入javascript代码,并且浏览器将数据正确解释为数组。
第三种解决方案
第三种解决方案基于:
- 通过
<h:inputHidden>
组件将一些值从CDIBean传递给javascript - 调用客户端的CCD_ 51函数
- 调用客户端的CCD_ 52函数
这个解决方案完全不同。一开始,我在表单中添加了三个<h:inputHidden>
组件(每个值一个,我想传递给javascript)。表单应该是这样的:
<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>
如上所述,我添加了<h:inputHidden>
的id
和value
属性,并在value
属性中保存了所需的数据。
请注意,当您按下按钮时,表单会更新(查看<p:commandButton>
组件的update
属性),因此我确信每次按下按钮时都会下载输入的value
属性中的数据。
最后,我在javascript代码中通过document.getElementById("filterForm:idOfInput").value
引用了这些值。记住使用JSON.parse()
或eval()
,因为数据被解释为字符串。脚本应该是这样的:
<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 API不支持这种情况。您可以在的循环中组合使用Chart.addSeries()、Series.setData()和Series.remove()方法。如果我还有一点时间,我会这么做,我会更新这个答案。在下面,您可以找到一些重新创建/更新图表的方法。
如果您不知道数据序列的编号
第一个解决方案
若你们不知道你们会得到多少系列的数据(就像在我的例子中),你们可以使用series.remove()删除当前的系列数据,然后通过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();
}
请注意,由于有效,我为redraw
参数设置了false
。Highcharts API表示:
如果在图表上执行更多操作,最好将redraw设置为false,然后调用chart.redraw()。
第二个解决方案
您也可以销毁图表并重新创建它。在这种情况下,您还必须更改changeData
javascript函数,如下所示:
function changeData(...){
chart.destroy();
createChart(...);
}
如果您知道数据序列的编号
这个案子比上一个容易。看看下面的代码,它不需要任何解释:
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();
}
作为一种替代方案,您还可以使用highfaces JSF库来处理这些情况,包括将整个流集成到JSF流中。
因此,解决方案将变得像一样简单
<p:outputPanel id="something">
<hf:chart type="line" value="# {lineChartBean.mappedList}" var="birth" subTitle="Including sub title"
point="# {birth.amount}" tickLabel="# {birth.year}" title="Map with Lists of Pojos"/>
</p:outputPanel>
....
<p:commandButton id="filterButton" value="Filtruj" action="#{visualizationController.actionFilterButtonForChart1a()}"
disabled="#{!visualizationController.visibilityFilterButtonForChart1a}"
update="filterForm something"/>
HighFaces也适用于动态序列数,您可以查看例如Simple List、Map with POJO List示例,当然它与PrimeFaces很好地集成在一起。