I am developing a JSF Custom Component, using the information I found on the following book Pro JSF and HTML5 by Apress.
So far, I successfully developed:
the java class to obtain the data to be rendered in the component
the java component class
the java renderer class
the taglib file
an example page to render the taglib
Everything is working fine, the component is successfully rendered.
Now I would like to add javascript events and behaviour to the rendered elements, more specifically, the purpose of my custom component is to render a menu on a web page, and I would like to ad a dropdown effects to the menu entry. I know how to code the whole thing, in JavaScript, what I don't know is:
What is the best practice to add javascript events and behaviour to the element rendered within a custom component?
Where should the JS files be placed? How do I bind the events to the elements? Is it done in the render class, or after, on the web pages?
Thanks, I'm willing to provide more specific information about my code, if required.
Java Component Class
Note: The CosmoMenu class is just a bean. It basically stores a menu tree (a label, an id and a set of children, if any).
package components;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import domain.CosmoMenu;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponentBase;
#FacesComponent(CosmoMenuComponent.COMPONENT_TYPE)
public class CosmoMenuComponent extends UIComponentBase{
/** Component family of {#link CosmoMenuComponent}. */
public static final String COMPONENT_FAMILY = "CosmoMenu";
/** Component type of {#link CosmoMenuComponent}. */
public static final String COMPONENT_TYPE = "CosmoMenu";
#Override
public String getFamily(){
return CosmoMenuComponent.COMPONENT_FAMILY;
}
private CosmoMenu theMenu;
public CosmoMenu getMenu(){
Gson gson = new Gson();
JsonParser jsonParser = new JsonParser();
CosmoMenuAPI myApi = new CosmoMenuAPI();
String strMenu = myApi.getMenu();
JsonElement jEl = jsonParser.parse(strMenu);
theMenu = gson.fromJson(jEl, CosmoMenu.class);
return theMenu;
}
}
If you want your components to be reusable, I encourage you to pack everything in an independent jar. If using Servlet 3.0, you'll be able to easily access the web resources putting them in META-INF/resources. Provide the jar a faces-config.xml and you'll make it JSF annotation scannable:
components
\-(Your cource code)
META-INF
\-faces-config.xml
\-resources (This ends up in docroot)
\-resources
\-js (Here they go your js files)
\-comp (Here your composite components)
\-css (Here your css)
Later on, you'll have to take care of avoiding the specific ids in your composites, as JSF modifies them while rendering. Your best is to pass the current component reference to your JS functions:
<h:inputText styleClass="myInputStyle" onclick="showInputText(this)" />
Just refer to included CSS styles and JS functions.
Last but not least, be careful when including the jar as a web resource, if the file paths remain in conflict with the ones in your web app, they won't be included.
See also:
Exposing resources from jar files in web applications (Tomcat7)
How to reference JSF managed beans which are provided in a JAR file?
How can I know the id of a JSF component so I can use in Javascript
You can include into the facelets wich uses your component an external javascript file by adding the following code:
<script src="#{request.contextPath}/jspath/yourjs.js"></script>
Within the component when you generate the XHTML output give an Id to your menu entries e.g.
<h:outputText id="myid" value="#{bean.value}"/>
and in yourjs.js
$(document).ready(function() {
$("#myid").click(function(){
// dostuff
});
});
Related
i currently have the problem, that i want to use a reference of an existing java class in javascript to be able to execute methods from Javascript of the Java Class to use Shepherd (Javascript Tool for guiding users through the web app). Im using Vaadin for Web deployment with java but Shepherd only works through Javascript.
I dont want to execute the JS from the Class with the methods, moreover i want to execute it from an other class, so it looks like this:
User clicks on a Button
Button button = new Button(); button.addClickListener (c -> tourPageController.executeShepherd(UI.getCurrent().getInternals().getTitle());
Java Code to find active Page
public static void executeShepherd(String appTitle) {switch(appTitle) {case "example": UI.getCurrent().getPage().executeJs("window.startTour($0)", <here i want to reference the existing class with the methods);
execute the Shepherd Tour four the explicit page, the user is on (For that, i need to hand over
a reference of the class holding the methods to execute via javascript), its looks something like this in the header:
window.startTour = () => { ... }
Javascript is getting the parameter (the class)
From Javascript im going to use Shepherd, but there are references to Java Methods (these Methods are for the Vaadin Components, to open and close the respectively tabs i want to use in the tour). I use it like this:
beforeShowPromise: function () {
return new Promise(function(resolve) {
classParameter.$server.<method()>;
resolve();
});
},
How can i do this?
What you can do is to set id to the component in Java
component.setId("component-x");
This will mean that the DOM element will be have the id attribute set.
So if you have some bookkeeping of the components in your Java code, you can pass the id as a parameter in that #ClientCallable call, and then search for the component matching that id in your Java code.
If this is about only one view specific component, you do not need anything else than storing reference to the component in a class field (not even the id). But if you indeed have more than one Accordion in your view, the idea is like this.
public class MyView extends Div {
List<Accordion> accordions;
public MyView() {
Accordion accordion = new Accordion();
accordion.setId("accordion-1");
accordions.add(accordion);
...
}
#ClientCallable
public openAccordion(String id, int index) {
accordions.stream().filter(acc ->
acc.getId().equals(id)).findFirst().ifPresent(acc -> acc.open(null));
accordion.open(index);
}
}
For context, see: How can I change Vaadin Components in Java through Javascript
I am trying to dynamically create the component to register in Golden Layout. Eg,
#ViewChild('placeholder', { read: ViewContainerRef }) viewContainerRef;
ngAfterViewInit(){
this.myLayout.registerComponent('testComponent', (container, componentState) => {
//Create an empty div on the container
container.getElement().html("<div #placeholder></div>");
this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(TestComponent);
this.cmpRef = this.viewContainerRef.createComponent(this.componentFactory);
this.cmpRef.changeDetectorRef.detectChanges();
GlDirective.componentCount++;
});
}
However , because viewContainerRef is referring to ViewChild that only gets created now , it is always undefined. How can we create a component in RC5 that uses dynamically added div like the one above. I used #Günter Zöchbauer 's answer on Angular 2 dynamic tabs with user-click chosen components to derive to this. However unsure on how this can be achieved with Golden Layout which needs the DOM to be generated on the fly. Please help.
This is not supported.
Something like #placeholder in
container.getElement().html("<div #placeholder></div>");
will just be added to the browser and not recognized or processed in any way by Angular2 (except sanitization for security purposes).
A template variable can only be created by adding it statically to a components template.
I wonder is there a way to load a react component template from server?
When I worked with Vuejs or any other js-library I actually have to add code to a view and after it rendered by server javascript starts running and do the rest.
For instance, I can type something like this in a Symfony app to get localized string: {{ 'header.article_title'|trans }} and it were translated.
However, since templates are hardcoded into a reactjs-component I can not use php/symfony/twig functions anymore. So, I'm wondering if there a way to fetch template from a server like an AngularJS templateURL-option.
Finally get how to do what I need.
I've realized that it is possible to not have actual file by URL. So, if I need a /react/component/Article.js it could be a URL to an action.
So, I've created a new bundle, add a controller and use one action with view for a single react-component.
The skeleton code looks like this:
/**
* Class ComponentController
* #package ReactjsBundle\Controller
*
* #Route("/react/component")
*/
class ComponentController extends Controller
{
/**
* #Route("/Article.js")
*/
public function articleAction()
{
$resp = new Response();
$resp->headers->set('Content-Type', 'text/javascript');
return $this->render('ReactjsBundle:Component:article.html.twig', [], $resp);
}
}
I'm getting used to view components in MVC 6, and I asked a similar question a few years ago about partial views. If I build a view component encapsulating a common use-case that requires its own Javascript, where do I put that Javascript? I know that it is dangerous at best to have Javascript in partial views, but it would be a lot simpler to include it in the view component, rather than in the containing view or a separate file that has to be referenced by the containing view.
For example, say I have a view component that has two drop-downs. The selection in the first drop-down determines what items appear in the second drop-down. This is easily handled in Javascript, of course, but where do I put it?
From my experience with ASP.NET 5 View Components, I would say that the best thing to do with them is to keep them isolated and in one place, so they will be easily to manage in long-term projects.
In one of my ASP.NET projects, I've developed View Components structure like this one:
View, Backend code and Model are all in one place, so when you move around the folder, you are sure that you move whole component. Moreover, when you are modyfying them, you have quick access to all of their parts.
It will be convinient to put JavaScript which is highly coupled with a component also in such structure. You can do this by simply creating the file under the component's folder, and then writing a GULP TASK that will copy JS file to wwwroot. From that point, you will be able to link that JavaScript code on component's .cshtml using standard syntax:
<script src="~/Components/yourcomponent.js"></script>
To obtain such a structure in my project, I've extended Razor, to be able to search for my component's CSHTML's in proper place. To do this, I've added this code in Startup.cs:
public partial class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//non relevant code skipped
services.AddMvc().AddRazorOptions(ConfigureRazor);
}
public void ConfigureRazor(RazorViewEngineOptions razor)
{
razor.ViewLocationExpanders.Add(new ViewLocationExpander());
}
}
and the ViewLocationExpander class is:
public class ViewLocationExpander : IViewLocationExpander
{
protected static IEnumerable<string> ExtendedLocations = new[]
{
"/{0}.cshtml"
};
public void PopulateValues(ViewLocationExpanderContext context)
{
//nothing here
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
//extend current view locations
return viewLocations.Concat(ExtendedLocations);
}
}
Then, you invoke component like this (from any .cshtml view):
#await Component.InvokeAsync("NavigationComponent",new NavigationComponentModel())
I have a web page with a couple of view components, when I click on these components I open a simple editor for it, see image below. If I edit the text and hit enter, I would like to rerender the view component and not the hole page. Is it possible to invoke a view component using javascript to get this behaviour?
With updates, you should now be able to do this with asp.net core. You can return a ViewComponent from an IActionResult, then just call it with jQuery:
[Route("text-editor")]
public IActionResult TextEditor()
{
return ViewComponent("TextEditorViewComponent");
}
Then this can be called from your View with something as simple as this:
function showTextEditor() {
// Find the div to put it in
var container = $("#text-editor-container");
$.get("text-editor", function (data) { container.html(data); });
}
If you need to pass parameters to the ViewComponent you can do so in the IActionResult (maybe they trickle through from the View to there, to the view component, etc).
This is not possible today but you could build a standard proxy for viewcomponents if you'd like though (similarly you could build a proxy to partial views). e.g. you a catchall attribute route on an action.