About three months ago I was evaluating JavaScript Library Raphaël. This is a library for simplifying the work with vector graphics on the web. It uses SVG and VML (Internet Explorer), is highly compatible with every browser environment and doesn't depend on Flash or Canvas. I'm developing a collaborative online whiteboard just now by means of Raphaël and several frameworks for bidirectional communication in the web. Presentation is coming soon. I will also post examples how to use Raphaël library to draw shapes and more in the web. In this post I just wanted to tell that my first example (at the beginning of my evaluation) has been included to the known Raphaël Source Examples. You find it there under "Other related sites" as "Oleg's online collaborative whiteboard is evolving". The direct link to the test application is here.
Have much fun with Raphaël ;-)
Thursday, July 21, 2011
Raphaël Library evaluated
Unified JSF Connection (Ajax) Status Indicator
Real JSF web applications consist of various ajaxifed components. The good practise is to tell users that they should be waiting for something to finish. A global Ajax Status Indicator seems to be very handy in ajaxifed applications. Such components exist e.g. in RichFaces, ICEFaces and PrimeFaces. If you only use one of these component libraries, you can skip this post :-) But what is about if you use a mix composed of standard f:ajax with any standard and not standard components + ajaxified components from any libraries? I have counted f:ajax and PrimeFaces' p:ajax appearances in my applications. Approximately 50% to 50%. Probably you used f:ajax too because p:ajax had issues in early PrimeFaces versions. The question is now how to adjust p:ajaxStatus in order to consider standard Ajax requests in addition to PrimeFaces Ajax requests based on jQuery Ajax? We can add global callbacks for event handling from standard JSF
I took connection icons from ICEFaces showcase.
jsf.ajax.addOnEvent(callback) jsf.ajax.addOnError(callback)Let me extend PrimeFaces AjaxStatus, register new global callbacks and show an example in action. At first we have to add the standard JS library for Ajax and our extended script ajaxstatus.js.
import javax.faces.application.ResourceDependencies; import javax.faces.application.ResourceDependency; @ResourceDependencies({ @ResourceDependency(library="primefaces", name="jquery/jquery.js"), @ResourceDependency(library="primefaces", name="core/core.js"), @ResourceDependency(library="primefaces", name="ajaxstatus/ajaxstatus.js"), @ResourceDependency(library="javax.faces", name="jsf.js"), @ResourceDependency(library="js", name="ajaxstatus.js") }) public class AjaxStatus extends org.primefaces.component.ajaxstatus.AjaxStatus { }Renderer class is simple
import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import java.io.IOException; public class AjaxStatusRenderer extends org.primefaces.component.ajaxstatus.AjaxStatusRenderer { protected void encodeScript(FacesContext context, org.primefaces.component.ajaxstatus.AjaxStatus st) throws IOException { AjaxStatus status = (AjaxStatus)st; ResponseWriter writer = context.getResponseWriter(); String clientId = status.getClientId(context); String widgetVar = status.resolveWidgetVar(); writer.startElement("script", null); writer.writeAttribute("type", "text/javascript", null); writer.write(widgetVar + " = new PrimeFaces.widget.ExtendedAjaxStatus('" + clientId + "');"); encodeCallback(context, status, widgetVar, "ajaxSend", "onprestart", AjaxStatus.PRESTART_FACET); encodeCallback(context, status, widgetVar, "ajaxStart", "onstart", AjaxStatus.START_FACET); encodeCallback(context, status, widgetVar, "ajaxError", "onerror", AjaxStatus.ERROR_FACET); encodeCallback(context, status, widgetVar, "ajaxSuccess", "onsuccess", AjaxStatus.SUCCESS_FACET); encodeCallback(context, status, widgetVar, "ajaxComplete", "oncomplete", AjaxStatus.COMPLETE_FACET); writer.endElement("script"); } }Note: Prestart event "ajaxSend" is not supported in the standard Ajax, but it's rarely used anyway. "ajaxStart" is normally enough. JavaScript ajaxstatus.js is a little bit complicate. I register there global handlers for Ajax events by jsf.ajax.addOnError / jsf.ajax.addOnEvent.
PrimeFaces.widget.ExtendedAjaxStatus = function(id) { this.id = id; this.jqId = PrimeFaces.escapeClientId(this.id); } PrimeFaces.widget.ExtendedAjaxStatus.prototype.bindFacet = function(eventName, facetToShow) { var _self = this; // jQuery jQuery(document).bind(eventName, function() { _self.showFacet(facetToShow); }); // Standard if (eventName == "ajaxError") { jsf.ajax.addOnError(this.processAjaxOnErrorFacet(facetToShow)); } else { jsf.ajax.addOnEvent(this.processAjaxOnEventFacet(eventName, facetToShow)); } } PrimeFaces.widget.ExtendedAjaxStatus.prototype.bindCallback = function(eventName, fn) { // jQuery jQuery(document).bind(eventName, fn); // Standard if (eventName == "ajaxError") { jsf.ajax.addOnError(this.processAjaxOnErrorCallback(fn)); } else { jsf.ajax.addOnEvent(this.processAjaxOnEventCallback(eventName, fn)); } } PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnEventFacet = function(eventName, facetToShow) { var _self = this; function processEvent(data) { if (eventName == "ajaxStart" && data.status == "begin") { _self.showFacet(facetToShow); } else if (eventName == "ajaxComplete" && data.status == "complete") { _self.showFacet(facetToShow); } else if (eventName == "ajaxSuccess" && data.status == "success") { _self.showFacet(facetToShow); } } return processEvent; } PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnErrorFacet = function(facetToShow) { var _self = this; function processEvent() { _self.showFacet(facetToShow); } return processEvent; } PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnEventCallback = function(eventName, fn) { function processEvent(data) { if (eventName == "ajaxStart" && data.status == "begin") { fn(); } else if (eventName == "ajaxComplete" && data.status == "complete") { fn(); } else if (eventName == "ajaxSuccess" && data.status == "success") { fn(); } } return processEvent; } PrimeFaces.widget.ExtendedAjaxStatus.prototype.processAjaxOnErrorCallback = function(fn) { function processEvent() { fn(); } return processEvent; } PrimeFaces.widget.ExtendedAjaxStatus.prototype.showFacet = function(facetToShow) { jQuery(this.jqId).children().hide(); jQuery(this.jqId + '_' + facetToShow).show(); }Using on the page along with JS handling could be
<p:ajaxStatus onstart="ajaxOnStartIndicator()" onerror="ajaxOnErrorIndicator()" onsuccess="ajaxOnSuccessIndicator()"/> <h:graphicImage id="ajaxIndicatorActive" library="img" name="connect_active.gif" style="display: none;" styleClass="ajaxIndicator"/> <h:graphicImage id="ajaxIndicatorCaution" library="img" name="connect_caution.gif" style="display: none;" styleClass="ajaxIndicator" title="Connection problem" alt="Connection problem"/>
function ajaxOnStartIndicator() { document.body.style.cursor = 'wait'; jQuery("#ajaxIndicatorCaution").css("display", "none"); jQuery("#ajaxIndicatorActive").css("display", "block"); // get the current counter var jqDoc = jQuery(document); var requestCount = jqDoc.data("ajaxStatus.requestCount"); if (typeof requestCount === 'undefined') { requestCount = 0; } // increase the counter jqDoc.data("ajaxStatus.requestCount", requestCount+1); } function ajaxOnSuccessIndicator() { // get the current counter var jqDoc = jQuery(document); var requestCount = jqDoc.data("ajaxStatus.requestCount"); // check the counter if (typeof requestCount !== 'undefined') { if (requestCount == 1) { // hide indicators document.body.style.cursor = 'auto'; jQuery("#ajaxIndicatorActive").css("display", "none"); jQuery("#ajaxIndicatorCaution").css("display", "none"); jqDoc.data("ajaxStatus.requestCount", 0); } else if (requestCount > 1) { // only decrease the counter jqDoc.data("ajaxStatus.requestCount", requestCount-1); } } } function ajaxOnErrorIndicator() { document.body.style.cursor = 'auto'; jQuery("#ajaxIndicatorActive").css("display", "none"); jQuery("#ajaxIndicatorCaution").css("display", "block"); // reset counter jQuery(document).data("ajaxStatus.requestCount", 0); }The implementation is smart enough and avoid collisions with parallel running Ajax requests (s. counters). Furthermore, the cursor is set to 'wait' if an Ajax request is running. It's how desktop applications behaves.
I took connection icons from ICEFaces showcase.
Wednesday, July 13, 2011
Filter keyboard input of JSF input components
jQuery keyfilter plugin is a good choice if you want to filter input by specified regular expression. It allows you to input only specified characters via regular expression. Assume, your input field has an ID "myinput" and you want to restrict any user input to hexadecimal characters. This task can be done as follows
$('#myinput').keyfilter(/[0-9a-f]/i);You can also pass a test function instead of regular expression. It expects two arguments: event target (DOM element) and incoming character. If the return value is true, the input character is valid and allowed, otherwise - not.
$('#myinput').keyfilter(function(c) { return c != 'a'; });Easy? There are a lot of predefined masks too - for numeric, alphanumeric, etc. signs. Do we have something like in JSF? No. Well, there is a component MaskedInput in PrimeFaces, but it's restricted to a fix length. Length of the input value can not be variable and there is a very small set of signs to configure input masks. Conclusion - poor component :-). Keyfilter covers MaskedInput and offers more flexibility. Can we implement the keyfilter functionality in JSF? Yes, ideally as client behavior. The aim of this post is not this implementation. The aim of this topic is to show how to attach this functionality to an existing JSF component (hard-coded). I would like to take PrimeFaces AutoComplete. I would also like to show a tooltip if user input some disallowed characters. Let's start. We extend the component class by adding new resources - CSS and JS files for jQuery tooltip (qTip2), keyfilter plugins and an own extension for the AutoComplete widget.
@ResourceDependencies({ @ResourceDependency(library="primefaces", name="forms/forms.css"), @ResourceDependency(library="primefaces", name="jquery/ui/jquery-ui.css"), @ResourceDependency(library="primefaces", name="jquery/jquery.js"), @ResourceDependency(library="primefaces", name="jquery/ui/jquery-ui.js"), @ResourceDependency(library="primefaces", name="core/core.js"), @ResourceDependency(library="primefaces", name="autocomplete/autocomplete.js"), @ResourceDependency(library = "css", name = "jquery.qtip.css"), @ResourceDependency(library = "js", name = "tooltip/jquery.qtip.js"), @ResourceDependency(library = "js", name = "autocomplete/jquery.keyfilter.js"), @ResourceDependency(library = "js", name = "autocomplete/autocomplete.js") }) public class AutoComplete extends org.primefaces.component.autocomplete.AutoComplete { }In the AutoCompleteRenderer.java we add these lines to encodeScript()
// get tooltip text for invalid characters String tooltipText = MessageUtils.getMessageText(facesContext, facesContext.getApplication().getMessageBundle(), "autocomplete.invalidChars"); if (tooltipText != null) { tooltipText = tooltipText.replaceAll("'", "\\\\'"); } else { // fallback tooltipText = "Input character is not allowed"; } writer.write(",tooltipText:'" + tooltipText + "'");I used here an own utility class MessageUtils to get a message from message bundle. The most complicated step is the writing of an extended widget.
PrimeFaces.widget.ExtendedAutoComplete = function(id, cfg) { this.superWidget = PrimeFaces.widget.AutoComplete; this.superWidget(id, cfg); var _self = this; // initialize flag whether the current input character is valid this.jq.data("allowedChar", true); // define tooltip this.jq.qtip({ content: { text: cfg.tooltipText }, style: { widget: true, classes: 'ui-tooltip-rounded ui-tooltip-shadow' }, position: { my: 'bottom center', at: 'top center' }, events: { show: function(event, api) { try { if (_self.jq.data("allowedChar")) { event.preventDefault(); } } catch(e) { // IE can throw an error } }, hide: function(event, api) { _self.jq.data("allowedChar", true); } }, show: { event: false }, hide: { event: 'blur' } }); // filter input this.filterKeys(cfg); } jQuery.extend(PrimeFaces.widget.ExtendedAutoComplete.prototype, PrimeFaces.widget.AutoComplete.prototype); PrimeFaces.widget.ExtendedAutoComplete.prototype.filterKeys = function(cfg) { var _self = this; _self.jq.keyfilter(function(c) { var isAllow = _self.allowChar(c); _self.jq.data("allowedChar", isAllow); if (cfg.pojo) { _self.jq.unbind('keyup'); } if (isAllow) { // hide tooltip _self.jq.qtip('hide'); if (cfg.pojo) { _self.jq.keyup(function(e) { if (e.keyCode != 13) { jQuery(_self.jqh).val(jQuery(this).val()); } }); } } else { // show tooltip _self.jq.qtip('show'); } return isAllow; }); } PrimeFaces.widget.ExtendedAutoComplete.prototype.allowChar = function(c) { return c != '%' && c != '?' && c != '&'; }I wrote here a test function instead of regular expression to prevent % ? & signs to be input. The result looks pretty fine
Wednesday, July 6, 2011
Cross-browser fonts (aka @font-face)
Recently I have experimented with web fonts to figure out the best one and take it as standard for my company. There are not much predefined web fonts at all. "Verdana" was too wide for me, "Arial" was too narrow, "Tahoma" and "Trebuchet MS" were different displayed dependent on browser, ... I ended up with a cross browser font face implementation. There are many wonderful fonts which can be added to web applications by using of @font-face. @font-face is a css rule which allows you to download a particular font from your server to render a webpage if the user hasn't got that font installed. @font-face is supported by all browsers, even old Internet Explorer. There are dozens of Internet ressources with quality and amazing fonts. For instance, go to the Font Squirrel. That's the best resource for free, high-quality, commercial-use fonts. Choose a font and download a @font-face kit for it (a zip file). You need four types of font files. Each @font-face kit come with:
- EOT fonts for Internet Explorer 4+
- TrueType fonts for Firefox 3.5+ , Opera 10+, Safari 3.1+, Chrome 4.0.249.4+
- WOFF fonts for Firefox 3.6+, Internet Explorer 9+, Chrome 5+
- SVG fonts for iPad and iPhone
@font-face { font-family: 'BPreplayRegular'; src: url('BPreplay-webfont.eot'); src: url('BPreplay-webfont.eot?#iefix') format('embedded-opentype'), url('BPreplay-webfont.woff') format('woff'), url('BPreplay-webfont.ttf') format('truetype'), url('BPreplay-webfont.svg#BPreplayRegular') format('svg'); font-weight: normal; font-style: normal; }To match your directory structure you should set right paths to your four files in "url", of course. After that you are able to apply the font to any HTML element, e.g.
body { font-family: 'BPreplayRegular', Verdana, sans-serif; padding: 0px; margin: 0px; }Such web fonts work fine and look identical in IE6-IE9, Firefox, Chrome, Safari, Opera. The good news - there are hundreds of free fonts which leave nothing to be desired.
Friday, July 1, 2011
Auto updatable JSF components are easy done
Do you want to update automatically any JSF components with each ajax response? Are you looking for a dynamic way without needs to touch and change components? Nothing is easier than that. Auto updatable components make sense. They could be messages, tooltips, or everything else what is avalaible on every page and have to be updated. An own PartialViewContext and JSF system events on the component level will help us to archieve this aim. We start with a listener which must be fired on the PreRenderComponentEvent. Components to be auto updated are added there to the view map of the view root. More precisely - client IDs of such components will be added shortly before components get rendered.
package com.xyz.webapp.jsf import ... public class AutoUpdatableComponentListener implements SystemEventListener { public static final String AUTO_UPDATED_IDS = "com.xyz.webapp.jsf.AutoUpdatedIds"; @SuppressWarnings("unchecked") public void processEvent(SystemEvent event) throws AbortProcessingException { FacesContext fc = FacesContext.getCurrentInstance(); if (fc.getPartialViewContext().isAjaxRequest()) { // auto updatable component was already added to the view map return; } UIComponent component = (UIComponent) event.getSource(); Map<String, Object> viewMap = fc.getViewRoot().getViewMap(); Collection<String> autoUpdatedIds = (Collection<String>) fc.getViewRoot().getViewMap().get(AUTO_UPDATED_IDS); if (autoUpdatedIds == null) { autoUpdatedIds = new HashSet<String>(); } autoUpdatedIds.add(component.getClientId()); viewMap.put(AUTO_UPDATED_IDS, autoUpdatedIds); } public boolean isListenerForSource(Object source) { return true; } }The view map is accessed in a special implementaion of the PartialViewContext where all client IDs of auto updatable components have to be added to IDs getting from getRenderIds().
package com.xyz.webapp.jsf.context; import com.xyz.webapp.jsf.AutoUpdatableComponentListener; import javax.faces.context.FacesContext; import javax.faces.context.PartialViewContext; import javax.faces.context.PartialViewContextWrapper; import javax.faces.event.PhaseId; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class MyPartialViewContext extends PartialViewContextWrapper { private PartialViewContext wrapped; public MyPartialViewContext(PartialViewContext wrapped) { this.wrapped = wrapped; } public PartialViewContext getWrapped() { return this.wrapped; } public void setPartialRequest(boolean isPartialRequest) { getWrapped().setPartialRequest(isPartialRequest); } @SuppressWarnings("unchecked") public Collection<String> getRenderIds() { FacesContext fc = FacesContext.getCurrentInstance(); if (PhaseId.RENDER_RESPONSE.compareTo(fc.getCurrentPhaseId()) != 0) { return getWrapped().getRenderIds(); } else { List<String> ids = new ArrayList<String>(getWrapped().getRenderIds()); Collection<String> autoUpdatedIds = (Collection<String>) FacesContext.getCurrentInstance(). getViewRoot().getViewMap().get(AutoUpdatableComponentListener.AUTO_UPDATED_IDS); if (autoUpdatedIds != null) { ids.addAll(autoUpdatedIds); } return ids; } } } package com.xyz.webapp.jsf.context; import javax.faces.context.FacesContext; import javax.faces.context.PartialViewContext; import javax.faces.context.PartialViewContextFactory; public class MyPartialViewContextFactory extends PartialViewContextFactory { private PartialViewContextFactory parent; public MyPartialViewContextFactory(PartialViewContextFactory parent) { this.parent = parent; } @Override public PartialViewContextFactory getWrapped() { return this.parent; } @Override public PartialViewContext getPartialViewContext(FacesContext fc) { return new MyPartialViewContext(getWrapped().getPartialViewContext(fc)); } }For MyPartialViewContext is important to know that getRenderIds() is called in four JSF lifecycle phases. We are only interesting in the "render response" phase. That is why I made this check
if (PhaseId.RENDER_RESPONSE.compareTo(fc.getCurrentPhaseId()) != 0) { return getWrapped().getRenderIds(); }The last step - register all stuff in the faces-config.xml.
<factory> <partial-view-context-factory> com.xyz.webapp.jsf.context.MyPartialViewContextFactory </partial-view-context-factory> </factory> <system-event-listener> <system-event-listener-class>com.xyz.webapp.jsf.AutoUpdatableComponentListener</system-event-listener-class> <system-event-class>javax.faces.event.PreRenderComponentEvent</system-event-class> <source-class>com.xyz.webapp.jsf.component.MyComponent</source-class> </system-event-listener>I have registered the listener for com.xyz.webapp.jsf.component.MyComponent, but we can made the configuration a little bit more flexible via a config parameter in web.xml.
<context-param> <param-name>AUTO_UPDATABLE_COMPONENTS</param-name> <param-value> com.xyz.webapp.jsf.component.MyComponent, javax.faces.component.html.HtmlMessage, org.primefaces.component.tooltip.Tooltip </param-value> </context-param>We have no needs then to specify source-class and the isListenerForSource() method looks like
// This is just an example. Evaluation of init parameters should be done outside of the isListenerForSource() of course. public boolean isListenerForSource(Object source) { String components = FacesContext.getCurrentInstance().getExternalContext().getInitParameter("AUTO_UPDATABLE_COMPONENTS"); if (components == null) { return false; } String[] arr = components.split("[,\\s]+"); for (String component : arr) { if (component.equals(source.getClass().getName())) { return true; } } return false; }We can go on and configure components client IDs instead of their fully qualified names (better in my opinion). Check against IDs happens in the isListenerForSource() too. More flexible and precise configurations are possible - e.g. client IDs + pages (= view root IDs) where auto updatable components are placed exactly.
Subscribe to:
Posts (Atom)