Getting the co-ordinates of a component - jsf

How to get the co-ordinates of a JSF component after onclick event ? I need to place an overlay just below an icon that was clicked.
Is there any such in-built facility provided by JSF rather than manually writing a javascript function for that ?

No, there isn't. The position is browser (client) dependent. In the client side there is also totally no means of JSF, it's all just plain HTML/CSS/JS. The location can only be extracted from the HTML DOM element. You'd have to pass it from JS to JSF or do the business job fully in JS instead of JSF.
As PrimeFaces ships with jQuery, your best way to retrieve the element position relative to the document is using jQuery.offset().
var $element = jQuery('#someid');
var offset = $element.offset();
var x = offset.left;
var y = offset.top;
// ...

In addition to BalusC's answer, here is an example, where a click on a component with id=placeholder (maybe a link or button) will open another component (id=menu) directly below the triggering component. If the mouse is moved away from the triggering component, the menu will disappear:
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery("#placeholder").click(function(event) {
//get the position of the placeholder element
var pos = jQuery(this).offset();
var height = jQuery(this).height();
//show the menu directly below the placeholder
jQuery("#menu").css( { "left": pos.left + "px", "top": (pos.top + height) + "px" } );
jQuery("#menu").show();
});
// hide the menu on mouseout
jQuery("#placeholder").mouseout(function(event) {
jQuery("#menu").hide();
});
});
</script>
The component with id=menu can be a div or a jsf h:panelGroup or any other component. The style attribute with display: none will initially hide the component:
<h:panelGroup style="position: absolute; display: none;" id="menu">
<!-- content here -->
</h:panelGroup>
Make sure that the jQuery id selector gets the correct id of the component. E.g. if your elements are inside a form with id=form1, the jQuery call has to look something like this:
jQuery("#form1\\:placeholder").click(function(event)
Notice the double backslash.

I'd recommend using a JSF component library like RichFaces or IceFaces. Many of them have AJAX capabilities and JavaScript library integration, and therefore have components for doing that kind of thing.

Related

Primefaces NotificationBar close icon not visible

In the documentation of primefaces, it is said that "Note that notificationBar has a default built-in close icon to hide the content.". But so far I could not get it displayed ? Is there a special property or facet required to show the close icon ?
pf version I am using is 6.2
If you see the notification.js resource inside the Primefaces library, you can see that they took into account to give to the close icon the "hide functionality":
primefaces-6_2\src\main\resources\META-INF\resources\primefaces\notificationbar\notificationbar.js =>
/**
* PrimeFaces NotificationBar Widget
*/
PrimeFaces.widget.NotificationBar = PrimeFaces.widget.BaseWidget.extend({
init: function(cfg) {
this._super(cfg);
var _self = this;
//relocate
this.jq.css(this.cfg.position, '0').appendTo($('body'));
//display initially
if(this.cfg.autoDisplay) {
$(this.jq).css('display','block')
}
//bind events
this.jq.children('.ui-notificationbar-close').click(function() {
_self.hide();
});
},
So, considering the previous code, if a children component has the ui-notificationbar-close class and you click on it, the NotificationBar component will be hided calling to hide function automatically (without having to use the PF(widgetVar).hide().
I have tested with the following code and in effect, the notificationbar disappears after clicking on the close icon:
<p:notificationBar id="notificationBar" position="top" effect="slide" styleClass="top" widgetVar="myNotificationBarWV" autoDisplay="false">
<i class="ui-icon ui-icon-closethick ui-notificationbar-close"></i>
<h:outputText value="You Rock!" style="font-size:1.5 rem;"/>
</p:notificationBar>

Primefaces: Input Text / Overlay panel / Retain focus

We have a p:inputText element that should display an overlay panel for various options. (Its a global search, so you can tick categories to search in)
Users usually click in the textbox, start typing and THEN look at the screen again
The Problem is: As soon as the overlay panel is shown, the textbox looses its focus.
<p:inputText id="searchItem"></p:inputText>
<p:overlayPanel id="gsOverlay" for="searchItem" my="left top"
at="left bottom" dynamic="true"
onShow="resizeGSOverlay();">
So i tried to fix this, by immediately focusing back on the "search" inputtext using
<p:overlayPanel id="gsOverlay" for="searchItem" my="left top"
at="left bottom" dynamic="true"
onShow="PrimeFaces.focus('globalSearchForm:searchItem'); resizeGSOverlay();">
However, there is a split second, where the inputfield lost focus, leading to searches missing the first charater.
Can i display the overlay panel, without having the inputtext loosing its focus? (Each component inside the overlay panel will focus back after clicking, that's fast enough - just the initial focus-back is to slow)
Just found the "holy grail":
Default for the overlayPanel is:
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function(){
this.jq.find(':not(:submit):not(:button):input:visible:enabled:first').focus();
}
so, I just put the following javascript AFTER including the primefaces resources, which will then override the default implementation:
<script type="text/javascript">
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function() {
if (this.id == "globalSearchForm:gsOverlay")
return;
else
this.jq.find(':not(:submit):not(:button):input:visible:enabled:first').focus();
}
</script>
So - No focus for any element within the overlay panel in question once it becomes visible. Works like a charm.
Update:
using the Proxy Pattern (http://api.jquery.com/Types/#Proxy_Pattern) seems a more reliable solution, as it avoids the need to duplicate the content of the original implementation, which might be different in one of the next Primefaces releases:
<script type="text/javascript">
(function() {
var proxied = PrimeFaces.widget.OverlayPanel.prototype.applyFocus;
PrimeFaces.widget.OverlayPanel.prototype.applyFocus = function(){
if (this.id == "globalSearchForm:gsOverlay")
return;
return proxied.apply(this, arguments);
};
})();
</script>

jsf render components with js

Let's say I have
<p:outputPanel/>
What I want to do is to specify rendered attr using js method not serverside.
This is for improving performance.
So I need something like :
<p:outputPanel rendered = "someJsFunction()"/>
What is the solution?
rendered propery is processed at server side and if it resolves to false, the element is not added into the html document. So javascript can't even find the element to display or hide because it is not created.
The only thing you can do is to remove the rendered property and change the display property of the element with javascript.
<div id="myDiv">My Content</div>
<button onclick="myFunction()">Click Me</button>
<script>
function myFunction() {
document.getElementById("myDIV").style.display = "none";
}
</script>
Well, you can have the same effect at page load cause rendered attribute is resolved at Server Side only , So using jQuery you can do it like
$(document).ready(function() {
document.getElementById("YourPanelIdHere").style.display = "none";
});
and it will be not displayed.

Render JSF h:message with p element instead of span

I would like to create a custom message renderer to renders h:message as a 'p' html element instead of as a 'span' element. It concerns the following message tag:
<h:message id="firstNameErrorMsg" for="firstname" class="error-msg" />
I've written to code underneath, but that's only rendering an empty 'p' element. I suppose I have to copy all attributes and text from the original component and write it to the writer. However, I don't know where to find everything and it seems to be a lot of work for just a replacement of a tag.
Is there a better way to get an h:message tag rendered as a 'p' element?
Code:
#FacesRenderer(componentFamily = "javax.faces.Message", rendererType = "javax.faces.Message")
public class FoutmeldingRenderer extends Renderer {
#Override
public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("p", component);
writer.endElement("p");
}
}
It isn't exactly "a lot of work". It's basically a matter of extending from the standard JSF messages renderer, copypasting its encodeEnd() method consisting about 200 lines then editing only 2 lines to replace "span" by "p". It's doable in less than a minute.
But yes, I agree that this is a plain ugly approach.
You can consider the following alternatives which are not necessarily more easy, but at least more clean:
First of all, what's the semantic value of using a <p> instead of a <span> in this specific case? To be honest, I'm not seeing any semantic value for this. So, I suggest to just keep it a <span>. If the sole functional requirement is to let it appear like a <p>, then just throw in some CSS. E.g.
.error-msg {
display: block;
margin: 1em 0;
}
You can obtain all messages for a particular client ID directly in EL as follows, assuming that the parent form has the ID formId:
#{facesContext.getMessageList('formId:firstName')}
So, to print the summary and detail of the first message, just do:
<c:set var="message" value="#{facesContext.getMessageList('formId:firstName')[0]}" />
<p title="#{message.detail}">#{message.summary}</p>
You can always hide it away into a custom tag file like so:
<my:message id="firstNameErrorMsg" for="firstname" class="error-msg" />
Use OmniFaces <o:messages>. When the var attribute is specified, then you can use it like an <ui:repeat>.
<o:messages for="firstNameErrorMsg" var="message">
<p title="#{message.detail}">#{message.summary}</p>
</o:messages>

Reinitialize SVGweb for ajax

I have no problem using SVGweb when page is simply loaded (opened).
How is it possible to reinitialize SVGweb in order to redraw all SVG on the page?
Anotherwords I need SVGweb to rescan and rerender everything on the page.
source (from this):
<script type="image/svg+xml">
<svg>
...
</svg>
</script>
to this (like SVGweb si doing that when simply open the page):
<svg>
...
</svg>
I need this because I change the SVG graphics using ajax and need to rerender it on the page.
I needed the same capability and figured out how to do this properly without modifying the svgweb source or calling the _onDOMContentLoaded() handler manually. In fact, it is supported natively.
The trick is to (re)attach your SVG elements to the DOM using window.svgweb.appendChild() which causes the node to be processed by svgweb, as is documented within the svgweb manual.
Example, using jQuery:
// Iterate over all script elements whose type attribute has a value of "image/svg+xml".
jQuery('body').find('script[type="image/svg+xml"]').each(function () {
// Wrap "this" (script DOM node) in a jQuery object.
var $this = jQuery(this);
// Now we use svgweb's appendChild method. The first argument is our new SVG element
// we create with jQuery from the inner text of the script element. The second
// argument is the parent node we are attaching to -- in this case we want to attach
// to the script element's parent, making it a sibling.
window.svgweb.appendChild(jQuery($this.text())[0], $this.parent()[0]);
// Now we can remove the script element from the DOM and destroy it.
$this.remove();
});
For this to work properly I suggest wrapping all SVG script tags with a dedicated div, so that when attaching the SVG element it is attached to a parent element containing no other nodes. This removes the possibility of inadvertently reordering nodes during the process.
After the DOM is changed with a new SVGweb code (through Ajax)
<script type="image/svg+xml">
<svg>
...
</svg>
</script>
need to execute this:
svgweb._onDOMContentLoaded();
But before need to comment a line in the core source of SVGweb svg-uncompressed.js or svg.js
svg-uncompressed.js
from
if (arguments.callee.done) {
return;
}
to
if (arguments.callee.done) {
//return;
}
svg.js: find and delete this:
arguments.callee.done=true;
or replace with
arguments.callee.done=false;
EDIT:
One more fix to work for IE9:
for svg.js
from
var a=document.getElementById("__ie__svg__onload");if(a){a.parentNode.removeChild(a);a.onreadystatechange=null}
to
var IEv=parseFloat(navigator.appVersion.split("MSIE")[1]);if(IEv<9){var a=document.getElementById("__ie__svg__onload");if(a){a.parentNode.removeChild(a);a.onreadystatechange=null;a=null;}}
for svg-uncompressed.js
from
// cleanup onDOMContentLoaded handler to prevent memory leaks on IE
var listener = document.getElementById('__ie__svg__onload');
if (listener) {
listener.parentNode.removeChild(listener);
listener.onreadystatechange = null;
listener = null;
}
to
// cleanup onDOMContentLoaded handler to prevent memory leaks on IE
var IEv=parseFloat(navigator.appVersion.split("MSIE")[1]);
if (IEv<9) {
var listener = document.getElementById('__ie__svg__onload');
if (listener) {
listener.parentNode.removeChild(listener);
listener.onreadystatechange = null;
listener = null;
}
}

Resources