in my portlet view, I have a chart control. I don’t know the width of the view when the portlet will be displayed, but would like to set the width of the chart so that it occupies the whole portlet view (width-wise).
How is it possible? In the designer, I can only set absolute values in pixels. The input “100%” produces and error when the chart is displayed (For input string: “100%”).
It would likely be difficult to do with the relative positioning of the decoration around the main chart and the other calculations that must be done that are relative to the actual width and height. Plus, any time the html5 tag changes it’s size I believe it would need to re-draw everything from scratch again.
The best you could probably do would be to do a two pass rendering for that part of the portlet. In other words, in the initial rendering, the chart is not displayed, but you have some javascript that calculates how wide to make the chart. Then the value calculated by the javascript is posted back to the to the server and it renders the chart with that width on the second pass.
I’ll try the two pass approach, thank you for the hint. Setting the width on the server side is done via calling setValueExpression(“width”, …) on the UIComponent for the chart, right?
In my experience, posting requests to brainstorm is like sending data to /dev/null (I’ve done it a couple of times and never heard back). Hence I appreciate your proposals here very much.
I generally find it most convenient to create a field in the page bean.
For example:
private String chartWidth;
public String getChartWidth() {
return chartWidth;
}
public void setChartWidth(String chartWidth) {
this.chartWidth = chartWidth;
}
And then use a binding expression for the chart width property to reference that field.
#{TestDefaultviewView.chartWidth}
Then your action can just change the value of the field in the page bean and the next time the control renders it would get the new value automatically.
Yes, that’s indeed simpler. But still I somehow get lost how to organize the chain. My understanding is as follows:
In the view definition, bind the chart width property to the property “chartWidth” in the page bean. Place the chart into a hideable panel and bind the “visible” property of the panel to a (another) page bean property, say “chartVisible”. The chart should be not visible when the portlet is initially rendered.
Create a hidden async command control. Bind it to the action in the page bean which would set the value of “chartWidth” to WHAT? And then set the “chartVisible” to true. Set the “refresh” property to the hideable panel containing the chart.
Write a JavaScript to find out the desired width in pixels. The script must be placed after (below) the async command control so that it can reference the (already rendered) command. But how can I put the value from the JavaScript to the action’s param?
Fire the async command from the script. How should I do it? I assume, I should do something like
CAF.model("#{caf:cid('asyncCommand')}").raise()
. But how can I pass the value I found out in step 3 to the server side?
You may have seen in the documentation for CAF.Command.Model that there is a “go” method that you can use to invoke the action and pass additional parameters. In the server-side action handler you can get the addtional request parameter by name and use it.
For example:
//calculate the inner with of the block element
var chart1Width = $("#{caf:cid('chart1Block')}").clientWidth;
//subtract the dimensions of any padding or other decorations
chart1Width -= 150;
//raise the command with additional parameters
var commandModel = CAF.model("#{caf:cid('showChart1')}");
commandModel.go({ "chart1Width" : chart1Width });
Alternatively, you may use a hidden input that is bound to the same field and update the value of the hidden input before submitting the form.
For example:
//calculate the inner with of the block element
var chart2Width = $("#{caf:cid('chart2Block')}").clientWidth;
//subtract the dimensions of any padding
chart2Width -= 30;
//store as the value of the hidden input control
var chart2widthModel = CAF.model("#{caf:cid('chart2WidthInput')}");
chart2widthModel.setValue(chart2Width);
//raise the command
var commandModel = CAF.model("#{caf:cid('showChart2')}");
commandModel.raise();
I’ve attached a sample project that demonstrates both of those techniques for your reference. twopasschartdemo.zip (16.1 KB)
BTW, ‘clientWidth’ didn’t work for me in my previous attempts (I put the chart into a block panel). I had to use Element.getBoundingClientRect(). But that’s not CAF/JSF related.
And you also showed how to easily extract a request parameter. I was about to use the JSF API (getExternalContext etc.)
Very cool, thank you!
Edit: I have another question: IIUC, in your implementation the chart will remain visible once it has been rendered. How about the situation that the portlet is maximized? What event should one listen to to redo the two passes, i.e. to make the chart invisible, let recalculate its size and the make it visible again?
Well, in my sample project the two pass sequence would re-run every time the “Script Block” that does that work is rendered by the client. I believe that maximizing a portlet re-renders the whole page, so it should already be doing the two pass sequence for that use case.
The same would be true for an async action that refreshes the whole form (assuming the “Script Block” is within the form being refreshed). When the script block is replaced in the page with the new content, the script would run again which would start the second part of the two pass sequence.
If you are trying to only render the chart during the second pass, then you may do some checks before each render response with something like this:
@Override
public void preRenderResponse(PreRenderViewEvent e) {
super.preRenderResponse(e);
// if we are about to render the response for a partial request that is rending
// the chart block then make the chart control rendered, otherwise make it not rendered
boolean renderChart1Block = false;
boolean renderChart2Block = false;
FacesContext context = getFacesContext();
PartialViewContext pvc = context.getPartialViewContext();
if (pvc.isPartialRequest()) {
//find the component that the 'showChart1' action will refresh
UIComponent showChart1Block = findComponentInRoot("chart1Block");
//check if the renderId list contains the block
renderChart1Block = pvc.getRenderIds().contains(showChart1Block.getClientId());
//find the component that the 'showChart2' action will refresh
UIComponent showChart2Block = findComponentInRoot("chart2Block");
//check if the renderId list contains the block
renderChart2Block = pvc.getRenderIds().contains(showChart2Block.getClientId());
}
setChart1Rendered(renderChart1Block);
setChart2Rendered(renderChart2Block);
}
Wow, that’s a bit like the rocket science to me. But still it’s nice to know it’s possible.
I think I’ll stick with the first approach. The portlet will not be resizes that often, hence I can live with the fact that the chart is rendered twice.