Dynamic resource dependency loading

Community Driven Extensions Project
User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

25 Jan 2016, 19:05

Hi all,

I've been writing my first primefaces extension and it's been going well. I have a new component which is entirely functional when using the taglibs I have defined. This is great! However, I am having a lot of trouble when modifying the view through Java code as the resource dependencies are not dynamically being loaded. I have obviously done something wrong, but I'm unclear what.

I have the following structure.

Library name otbofaces
Component name dateentry
  • META-INF/resources/otbofaces/dateentry
    • dateentry.css
      dateentry.js
      jquery.autotab.js
      jquery.caret.js
      jquery.date-entry.js
My component is hooked up using annotations:

Code: Select all

@FacesRenderer( componentFamily = DateEntry.COMPONENT_FAMILY, rendererType = DateEntryRenderer.RENDERER_TYPE )
public class DateEntryRenderer extends CoreRenderer {
	public static final String RENDERER_TYPE = "com.tracegroup.isys.faces.DateEntryRenderer";

Code: Select all

@FacesComponent( value = DateEntry.COMPONENT_TYPE )
@ResourceDependencies( { 
	@ResourceDependency( library = "primefaces", name = "jquery/jquery.js" ), // JQuery !
	@ResourceDependency( library = "primefaces", name = "primefaces.js" ), // PrimeFaces !
	@ResourceDependency( library = "otbofaces",  name = "dateentry/jquery.autotab.js" ), // Auto Tab for automatic cursor transition between fields.
	@ResourceDependency( library = "otbofaces",  name = "dateentry/jquery.caret.js" ), // JQuery caret for cursor movement in fields.
	@ResourceDependency( library = "otbofaces",  name = "dateentry/jquery.date-entry.js" ), // Main body of the DateEntry definition.
	@ResourceDependency( library = "otbofaces",  name = "dateentry/dateentry.js" ), // Widget definition.
	@ResourceDependency( library = "otbofaces",  name = "dateentry/dateentry.css" ), // Main body of the DateEntry definition.
} )
public class DateEntry extends HtmlInputText implements Widget, ClientBehaviorHolder {
The dateentry.js is the widget definition which is invoking the javascript that is intended to build and render the widget client side. This code gets invoked absolutely fine when I use a tag notation as follows. This is working great!

Code: Select all

<otbofaces:date-entry id="datetime-enabled" date="29" month="1" year="1980" hour="9" minute="52" datepicker="true" format="datetime" binding="#{buttonView.datetime}" />
I'm struggling with the following scenario: If I add a button action that adds a DateEntry component to an arbitrary panel like so:

Code: Select all

		UIComponent component = actionEvent.getComponent();
		DateEntry dateEntry = new DateEntry();
		dateEntry.setFormat( "datetime" );
		dateEntry.setDate( 15 );
		dateEntry.setMonth( 8 );
		dateEntry.setYear( 2011 );
		dateEntry.setHour( 20 );
		dateEntry.setMinute( 12 );
		dateEntry.setDisabled( false );
		dateEntry.setDatepicker( true );
		
		datecontainer.getChildren().add( dateEntry );
The renderer is using the standard PrimeFaces org.primefaces.util.WidgetBuilder class within the overridden DateEntryRenderer.encodeEnd(FacesContext, UIComponent) method to create the javascript call to initiate the widget like so:

Code: Select all

		String clientId = component.getClientId();
		String widgetVar = component.resolveWidgetVar();

		WidgetBuilder wb = getWidgetBuilder( context );
		wb.initWithDomReady( "DateEntry", widgetVar, clientId, "dateentry" );
		
		encodeClientBehaviors( context, component );

		wb.finish();
Without the "dateentry" parameter at the end of wb.initWithDomReady( "DateEntry", widgetVar, clientId, "dateentry" ); the client side was searching for /javax.faces.resource/undefined/undefined.css.xhtml?ln=primefaces&v=5.3 and /javax.faces.resource/undefined/undefined.js.xhtml?ln=primefaces&v=5.3 .. so I added the "dateentry" as the resource name, which edged me closer. It's still not enough...

It's executing the javascript with this javascript:

Code: Select all

$(function(){PrimeFaces.cw("DateEntry","widget_j_idt8_j_id5",{id:"j_idt8:j_id5"},"dateentry");});
This is where the problems begin for me. As the requests for the scripts are being done on the client-side, I wonder how it could possibly know to load all the resource dependencies that are assigned to the component via the @ResourceDependency annotations as these are server-side and haven't as far as I can tell been transmitted to the client.

A small summary:
  1. The URL generated by the primefaces.js appears to think that these resources are in the primefaces library. i.e. from /javax.faces.resource/dateentry/dateentry.css.xhtml?ln=primefaces&v=5.3 instead of /javax.faces.resource/dateentry/dateentry.css.xhtml?ln=otbofaces&v=20160125162258 (I have a server-side OtboResourceHandler to add the version, I can include the sources for these later if necessary).
  2. As you can see, the component maps 5 client side resources to the component. I have only seen that it attempts to get dateentry.js and dateentry.css, so it is ignoring the other resource dependencies that are wired onto the Widget implementation using the @ResourceDependency annotations.
I suppose I could alter the javascript before the PrimeFaces.cw call is made to load the resource dependencies, something like that below, but I would have thought that is a little hacky. What is the correct way?

Code: Select all

ResourceDependencies resourceDependencies = DateEntry.class.getAnnotation( ResourceDependencies.class );
for ( ResourceDependency dependency : resourceDependencies.value() ) {
	// insert javascript code that loads the javascript or stylesheet if it isn't present in the header.
}
Any help appreciated.

Thanks,
Stuart
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

tandraschko
PrimeFaces Core Developer
Posts: 3979
Joined: 03 Dec 2010, 14:11
Location: Bavaria, DE
Contact:

25 Jan 2016, 20:52

Dynamic resource loading is just a PF feature.
If you use dynamic ui:includes or if you add components in ajax requests dynamically like in your example, the @ResourceDependency will be ignored.
wb.initWithDomReady( "DateEntry", widgetVar, clientId, "dateentry" );
"dateentry" means that if PrimeFaces.widget.DateEntry is not available (as the script isn't loaded now), PF loads /dateenty/dateentry.css and /dateentry/dateentry.js.

This means that PF is currently not designed to dynamically load multiple scripts or styles.
The best way is to merge all your scripts into dateentry.js and all your styles into dateentry.css.
Thomas Andraschko

PrimeFaces | PrimeFaces Extensions

Apache Member | OpenWebBeans, DeltaSpike, MyFaces, BVal, TomEE

Sponsor me: https://github.com/sponsors/tandraschko
Blog: http://tandraschko.blogspot.de/
Twitter: https://twitter.com/TAndraschko

User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

25 Jan 2016, 22:53

Thanks for the reply tandraschko.
This means that PF is currently not designed to dynamically load multiple scripts or styles.
The best way is to merge all your scripts into dateentry.js and all your styles into dateentry.css.
I wondered about doing this as a workaround, but the issue still remains because the library name the primefaces uses is still 'primefaces' and my library is named 'otbofaces' so it wouldn't land in the correct directory. I could if things get desperate rename 'otbofaces' to 'primefaces', but that seems wrong and it should be kept separate strictly speaking.

e.g.
WRONG: /javax.faces.resource/dateentry/dateentry.css.xhtml?ln=primefaces&v=5.3
RIGHT: /javax.faces.resource/dateentry/dateentry.css.xhtml?ln=otbofaces&v=20160125162258

By the sounds of it I have a few options.
  1. Extend the javascript PrimeFaces object and use a custom one which resolves to 'otbofaces' instead of 'primefaces'. The downside is that this would tie the javascript quite heavily to the primefaces javascript API and may in future cause issues that are potentially difficult to debug. This would require the merge of resources into a single js and css file.
  2. Using JSF server-side code send down the resources based on the @ResourceDependency annotation values to the client and determine if they require loading using the javascript runtime.
  3. Using JSF server-side code send down the resources based on the @ResourceDependency annotation values to the client and determine if they require loading using the JSF runtime.
I think a useful feature for the future might be to be able to pass the library name into the WidgetBuilder.initWithDomReady() method so that the javascript can use an alternative library. It shouldn't be too hard to do, what do you think? :)

Thanks,
Stuart
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

25 Jan 2016, 23:00

mrswadge wrote: I think a useful feature for the future might be to be able to pass the library name into the WidgetBuilder.initWithDomReady() method so that the javascript can use an alternative library. It shouldn't be too hard to do, what do you think? :)
Or even better pass in a Resource object created from a ResourceHandler. This would then detail the entire URL required with the resource name, library and target location. The URL would also contain the version information keeping it consistent.
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

tandraschko
PrimeFaces Core Developer
Posts: 3979
Joined: 03 Dec 2010, 14:11
Location: Bavaria, DE
Contact:

25 Jan 2016, 23:19

Rendering every resource url of every component to the script would really blow up our generated scripts.

Not sure about the resource library. As you also use the PrimeFaces.widget namespace, whats the problem with the resource library?
Otherwise, i think we have to allow to modify both or providing a static resource library and namespace for "extensions/custom" components.
Thomas Andraschko

PrimeFaces | PrimeFaces Extensions

Apache Member | OpenWebBeans, DeltaSpike, MyFaces, BVal, TomEE

Sponsor me: https://github.com/sponsors/tandraschko
Blog: http://tandraschko.blogspot.de/
Twitter: https://twitter.com/TAndraschko

User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

25 Jan 2016, 23:37

tandraschko wrote:Rendering every resource url of every component to the script would really blow up our generated scripts.
True, it doesn't sound ideal.
tandraschko wrote:Not sure about the resource library. As you also use the PrimeFaces.widget namespace, whats the problem with the resource library?
You make a good point. To keep the extension as clean as possible I suppose I ought to create a custom WidgetBuilder class and change it to use OtboFaces.widget... or give in and utilise the primefaces namespace. The downside of sharing the namespace with PrimeFaces is that the caching will operate based on the version of PrimeFaces and not on the version of the extension. This is why I'm trying to keep some separation here as the versions are unlikely to change at the same moment.
tandraschko wrote:Otherwise, i think we have to allow to modify both or providing a static resource library and namespace for "extensions/custom" components.
I think I would prefer the modify both approach, but that's me :)
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

tandraschko
PrimeFaces Core Developer
Posts: 3979
Joined: 03 Dec 2010, 14:11
Location: Bavaria, DE
Contact:

26 Jan 2016, 00:04

Please create a issue - i think we can do something for 6.0 ;)
Thomas Andraschko

PrimeFaces | PrimeFaces Extensions

Apache Member | OpenWebBeans, DeltaSpike, MyFaces, BVal, TomEE

Sponsor me: https://github.com/sponsors/tandraschko
Blog: http://tandraschko.blogspot.de/
Twitter: https://twitter.com/TAndraschko

User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

26 Jan 2016, 11:45

tandraschko wrote:Please create a issue - i think we can do something for 6.0 ;)
Done, the issue is https://github.com/primefaces/primefaces/issues/1066.

Incidentally, I've placed it in the community issues list, but actually we are currently agreeing terms for a PRO subscription. You sounded fairly confident of a solution, so maybe this doesn't matter too much :)

Thanks for your assistance!
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

User avatar
mrswadge
Posts: 36
Joined: 10 Sep 2012, 11:11

26 Jan 2016, 18:30

I've found a very agrreable work around that requires no javascript. It's actually very simple and clean too.

Previously I was instantiating the DateEntry component with a simple constructor as seen below.
mrswadge wrote:

Code: Select all

UIComponent component = actionEvent.getComponent();
DateEntry dateEntry = new DateEntry();
datecontainer.getChildren().add( dateEntry );
I changed this to use the following notation. This invokes all the annotation handlers, including most vitally for my case the ResourceDependencyHandler (Mojarra implementation).

Code: Select all

UIComponent component = actionEvent.getComponent();
DateEntry dateEntry = (DateEntry) context.getApplication().createComponent( DateEntry.COMPONENT_TYPE );
datecontainer.getChildren().add( dateEntry );
As a result, I do not need any JavaScript in order to load the dependencies and it's a much, much cleaner result. The scripts are loaded directly through plain HTML. There is already code in Mojarra that is checking for duplicate resource dependencies in the framework, and so I need not worry about duplicates being placed in the header.

Do you still plan on doing something with the issue I created?

Thanks,
Stuart
PrimeFaces 6.0 | Mojarra 2.0.11 | WebLogic 10.3.6

tandraschko
PrimeFaces Core Developer
Posts: 3979
Joined: 03 Dec 2010, 14:11
Location: Bavaria, DE
Contact:

26 Jan 2016, 18:47

Does it also work if your code, to add the component, is only executed on a ajax request?
So that the resources are not available on the html in the initial GET request.
Thomas Andraschko

PrimeFaces | PrimeFaces Extensions

Apache Member | OpenWebBeans, DeltaSpike, MyFaces, BVal, TomEE

Sponsor me: https://github.com/sponsors/tandraschko
Blog: http://tandraschko.blogspot.de/
Twitter: https://twitter.com/TAndraschko

Post Reply

Return to “Extensions”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 23 guests