Communicating with parent component - java

I have the MyPage.tml page and MyComponent.tml component.
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<body>
<t:mycomponent />
</body>
</html>
I need to display some data on MyPage based on what has happened in MyComponent. How can I make some data from MyComponent available to MyPage? Is there something like "reverse" parameters (child passing parameter to parent)?

Your component is available to you within your page as a variable where you can access the variables required from within your page like so:
#Component(id = "myComponent")
private MyComponent myComponent;
#SetupRender //or any other render event method
private void setup() {
Object compVariable = myComponent.getYourVariable();
}
More elegant if you ask me is to use event bubbling as it makes it easer to refactor some logic out to a deeper component if needed.
Component:
#Inject
private ComponentResources resources;
#SetupRender //or any other lifecycle event method
private void triggerEvent() {
Object yourVariable = new Object();
resources.triggerEvent("YOUR_EVENT_NAME", new Object[]{yourVariable}, null);
//add an event callback if needed where I use null here
}
Page:
#OnEvent(value = "YOUR_EVENT_NAME")
private void handleComponentEvent(Object yourVariable) {
//do something with yourVariable
//even return something which would then can be handled by your component callback handler
}

You can use usual tapestry parameter.
<t:mycomponent value="myValue"/>
If this value will be changed on the component side, it will be available on the container side and vice versa.

I've used all three of these approaches, depending on context. I generally prefer event bubbling, where that makes sense.

Related

ZK Framework - How to access the onSelect method of a Combobox that is inside a Listbox generated with a render?

Good evening, I tell you my problem:
In the ZK Framework I need to use the onSelect method of a dynamically rendered Combobox within a Listbox that is also rendered.
When I select one of the Combobox options, its content should be saved in the observaciones variable of the DocumentoVinculado class. But the onSelect don't work! I appreciate any help. Attached code:
.zul
<zk>
<window id="myWindow" apply="com.curso.controller.NewFileComposer" title="Help">
<listbox id="myListbox">
<listhead>
<listheader label="NroGEBI"></listheader>
<listheader label="Observaciones"></listheader>
</listhead>
</listbox>
<label id="myLabel"></label>
</window>
</zk>
Composer / Controller
public class NewFileComposer extends BaseController {
private Window myWindow;
private Listbox myListbox;
private Combobox myCombobox0;
private Combobox myCombobox1;
private Label myLabel;
public void onSelect$myCombobox0() { myLabel.setValue(myCombobox0.getValue()); }
public void onSelect$myCombobox1() { myLabel.setValue(myCombobox1.getValue()); }
public void onCreate$myWindow() {
ListModelList<DocumentoVinculado> modelo = new ListModelList<>(crearLista());
myListbox.setModel(modelo);
myListbox.setItemRenderer(new NewFileRender());
}
private List<DocumentoVinculado> crearLista() {
List<DocumentoVinculado> docVinculados = new ArrayList<>();
docVinculados.add(new DocumentoVinculado("123GEBI1", " "));
docVinculados.add(new DocumentoVinculado("123GEBI2", " "));
return docVinculados;
}
}
Render
public class NewFileRender implements ListitemRenderer {
#Override
public void render(Listitem item, Object data, int i) throws Exception {
DocumentoVinculado docVinculado = (DocumentoVinculado) data;
Listcell nroGebiCell = new Listcell(docVinculado.getNroGEBI());
nroGebiCell.setParent(item);
Listcell opcionesCell = new Listcell();
opcionesCell.appendChild(comboboxObservaciones(i));
item.appendChild(opcionesCell);
}
private Combobox comboboxObservaciones(int i) {
Combobox combobox = new Combobox();
List<String> listaDeOpciones = listaDeOpciones();
for(String opcion : listaDeOpciones) {
Comboitem myComboitem = new Comboitem();
myComboitem.setLabel(opcion);
myComboitem.setParent(combobox);
}
combobox.setId("myCombobox" + i);
return combobox;
}
private List<String> listaDeOpciones() {
List<String> opciones = new ArrayList<>();
opciones.add(" ");
opciones.add("Opcion1");
opciones.add("Opcion2");
return opciones;
}
}
Thank you for reading. Cheers!
From your syntax, it looks like you are using GenericForwardComposer as the super class for your BaseController.
Is that correct?
Depending on how much you have already done, I'd recommend checking if you could switch to SelectorComposer instead.
They work the same way (wire components, and listen to events), but the SelectorComposer is much more explicit in how things are wired. GenericForwardComposer can be somewhat error-prone unless you are very clear about its lifecycle.
Regarding why the onSelect would not be firing in this case:
ZK Composers works in phases. One important phase is the "compose" phase, during which the components are created, events listeners are put in places, etc.
At the end of that phase, the "afterCompose" phase takes place. During afterCompose, "magic" stuff (like the wiring of the private components in your current composer, or the auto-forwarding of the onSelect$myCombobox0 in the same class) takes place. It will also attempt to rewire stuff that was missing again just before triggering onCreate on its root anchor component.
Now, if your comboboxes are created dynamically after that step (for example, during an "onCreate$myWindow" event which happens after all of the above is finished), the composer will have already done all of the wiring and forwarding, and will not know to re-check the components for additional wiring.
With all of that explained, what can you do about it?
First, you can consider moving the onCreate code to a doAfterCompose method.
Instead of waiting for onCreate to generate the Listbox content, if you do it during doAfterCompose (override from genericFowardComposer), you should be early enough that auto-wiring will trigger on these components.
That should look like this:
#Override
public void doAfterCompose(Component comp) {
ListModelList<DocumentoVinculado> modelo = new ListModelList<>(crearLista());
myListbox.setModel(modelo);
myListbox.setItemRenderer(new NewFileRender());
}
2nd, if you move from genericForwardComposer to SelectorComposer, you can actually tell the composer to rewire itself "on demand" by using the Selectors.wireComponents method. Of course, that's only valid if your application can be refactored for this change.

Vaadin change value from nested Layout

I use Vaadin 14 and would know whether it is possible to report changes in the nested list to objects in the main view.
A rough example is shown in the picture. Above you can see the sum as size (here 2), if I press Delete it should change to 1.
Is that possible and how?
concept
I don't have any code yet, it's a thought where I would like to have a hint about what would be possible, e.g. Observer Pattern or something, but code could look something like this
code:
#Rout("")
public class MainView extends VerticalLayout {
private List<CustomDetails> customDetails = new ArrayList<>();
public MainView(){
final var form = new FormLayout();
customDetails.forEach(form::add);
add(H1("Header"), form)
}
}
public class CustomDetails extends Details{
private CustomForm customForm;
private final Service service;
public CustomDetails(){
customForms = new CustomForm(service.getListOfObjects());
this.setContent(customForms)
}
}
public class CustomForm extend FormLayout{
private FormLayout formLayout = new FormLayout();
private List<Object> objects = new LinkedList<>();
public CustomForm(List<Object> list){
this.objects = list;
setUp();
add(new Paragraph("SUM: "+ list.size()), layout);
}
private void setUp(){
objects.forEarch(o->{
....
layout.add(...)
})
}
}
In Vaadin there is an utility class Binder which is used to bind data to forms. If your use case is related to this, i.e. your so called nested layout is in fact a form and objects you refer to are data beans you want bind into that form. I recommend to study that first.
If you have list editor, I would also investigate if it fits your application to implement it with Grid or IronList/VirtualList, which is backed by DataProvider. Say you edit one item, and after saving the item, you can call dataProvider.refreshItem(item) to update the view.
Observer Pattern or something...
Yes, that is a solution. It is a lot of work and has been done before.
One such library is Beanbag.
Note: I wrote this (or rather, I started writing it a day ago).
EDIT:
As of this edit, we have the ObservableCollection interface. You can use it like so:
// You have a collection called "strings".
// Wrap it in an ObservableCollection.
final ObservableCollection<String, Collection<String>, BasicObservableCollection.Default<String, Collection<String>>> observableStrings = ObservableCollections.observableCollection(strings);
// Add a removed observer.
observableStrings.addElementRemovedObserver(observation -> System.out.println("\"" + observation.getValue() + "\" was removed.");
// Remove an element.
observableStrings.remove("hello");
If you need the wrapper to have List methods, just wait until tomorrow evening EST. I'll have the code up by then and will update this post accordingly.

How can I switch perspective programmatically after starting an E4 application?

Scenario
I have a pure E4 application in which I want to select the initial perspective for the user depending on the user's roles. I therefore have a perspective to start with which contains one part only. In that part, I use the #PostConstruct-Method to check the user's roles and afterwards trigger the command for switching perspective:
Initial View
#Inject
private IEclipseContext eclipseContext;
#PostConstruct
public void initialize() {
// checking credentials and retrieving roles come here which is pretty long
// that's why switching perspective is a seperate method
// and EclipseContext is injected to instance instead of method
this.switchPerspective(_usersInitialPerspectiveId)
}
private void switchPerspective(String pTargetPerspectiveId) {
final ECommandService _commandService = this.eclipseContext.get(ECommandService.class);
final EHandlerService _handlerService = this.eclipseContext.get(EHandlerService.class);
final Map<String, Object> _commandParameter = new HashMap<>();
_commandParameter.put(PluginIdConstants.ID_OF_PARAMETER_FOR_SWITCH_PERSPEKTIVE,
pZielPerspektiveId);
final ParameterizedCommand _switchPerspectiveCommand =
_commandService.createCommand(COMMAND_ID_FOR_SWITCH_PERSPECTIVE,
_commandParameter);
_handlerService.executeHandler(_switchPerspectiveCommand);
}
For switching perspective from here, I use the exact same handler as from menu items configured in Application.e4xmi, which looks like this:
Perspective Switch Handler
#Execute
public void execute(final MWindow pWindow,
final EPartService pPartService,
final EModelService pModelService,
#Named(PluginIdConstants.ID_OF_PARAMETER_FOR_SWITCH_PERSPEKTIVE)
final String pPerspectiveId) {
final List<MPerspective> _perspectives =
pModelService.findElements(pWindow, pPerspectiveId, MPerspective.class, null);
if (!(_perspectives.isEmpty())) {
// Show perspective for looked up id
pPartService.switchPerspective(_perspectives.get(0));
}
}
The Problem
The problem is pretty simple: When using the above handler triggered by a menu item, it works as intended and switches perspective. But using the same handler by my initial view (triggering it programmatically) does not switch perspective. I debugged the code to check if the handler gets identical information in both cases and it does.
Maybe my application has not finished starting and that's why the handler has no effect, but if this is the problem, how can I check this?
Any ideas on what I maybe missed are welcome!
Based on Christoph Keimel's hint I could create a working solution (thank you very much!). Here's the code that solves the problem:
#ProcessAdditions
private void switchPerspective(final MApplication pApplication,
final IApplicationContext pApplicationContext,
final EModelService pModelService) {
final MWindow _window =
(MWindow) pModelService.find(PluginIdConstants.WINDOW_ID_FOR_MAIN, pApplication);
final String _appName = pApplicationContext.getBrandingName();
initializeWindowTitle(_window, _appName);
final MPerspectiveStack pPerspectiveStack =
(MPerspectiveStack) pModelService.find(PluginIdConstants.PERSPECTIVE_STACK_ID_FOR_MAIN,
pAnwendung);
for (final MPerspective _perspective : pPerspectiveStack.getChildren()) {
if (_perspektive.getElementId().equalsIgnoreCase(this.startingPerspectiveId)) {
pPerspectiveStack.setSelectedElement(_perspective);
break;
}
}
}
On how to register a LifeCycleHandler you can take a look at Lars Vogel's Tutorial.
My main problem finding this solution was how to access the perspective stack. As the UI is not up while the method annotated with ProcessAdditions is running, I have to access the application model via the MApplication type - which is the root element of my application model. Combining the EModelService I can access all UI elements I want and manipulate them accordingly.
Injecting any UI element like the MPerspectiveStack or the MWindow leads to a skipped method as these result in null values due to not being initalized yet.

GXT - send forms on enter, project-wide

I have an application which uses GXT and contains ±30 forms. I would like to make these forms so that when the user hits enter in a text field, the form gets submitted, like a regular browser form would.
I know I can add a key press listener to every text field, which would invoke the submit after enter is pressed, but since I want to apply this to every field in every form I am not sure if this is ideal.
Is there a simpler way to implement this in the entire application?
If not, which pattern should I use to add this functionality to every field? I can extend the TextField class, add the functionality in the child class and use the child class in the application. Or I can create a factory for the text field class which would also add the listener to the field. Or is there some other way, Decorator perhaps? I was wondering which of these approaches, if any, is generally preferred.
I would try something like this:
Event.addNativePreviewHandler(new NativePreviewHandler() {
#Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
if (event.getNativeEvent().getEventTarget() != null) {
Element as = Element.as(event.getNativeEvent().getEventTarget());
if (as.getTagName().toLowerCase().equals("input") ||
as.getTagName().toLowerCase().equals("textarea")) {
// TODO submit data;
}
}
}
}
});
Every time someone hits the Enter Key and the cursor is placed on a input- or textarea-tag, you will get the control and can submit your data.
I don't think there is a way to do what you're asking directly in the GXT library. I do want to stress that extending the TextField class just to add an event handler to it is not the correct way to handle this. Event handlers are based on the composition of a class. It would be like extending a class with a List field just to add another element into the list.
A singleton factory class that created and initialises the Textfield for you would be the cleanest solution, in my opinion. It would allow you to effectively change defaults and add other handlers as required at a later time in a single place if requirements change.
You can try it with GWT JSNI also.
Steps to follow:
define a function in JavaScript that is called on Enter key press
call GWT JSNI from above JavaScript function that is exported at the time of onModuleLoad using GWT JSNI
get the Element from where this event is triggered and finally submit the form based on its tag name or Id
Sample code:
HTML/JSP:
<script>
window.onkeydown = keydown;
function keydown(event) {
if (event.which == 13) {
formSubmit(event.target);
}
}
</script>
JAVA(Entry Point):
import com.google.gwt.dom.client.Element;
public void onModuleLoad() {
exportFormSubmit();
...
}
public static void formSubmit(Element element) {
Window.alert("element tag name:" + element.getTagName() + "form ID:"
+ element.getParentElement().getId());
}
public static native void exportFormSubmit() /*-{
$wnd.formSubmit = $entry(#com.x.y.z.client.GWTTestProject::formSubmit(Lcom/google/gwt/dom/client/Element;));
}-*/;

Set focus on a component with Apache Wicket?

How do you set focus on a component with Apache Wicket? Searching leads to very little information, mostly on setting the default field. I do not want to set a default field, rather I am looking to set focus when, for example, a specific radio button is selected.
I suggest using the native org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(). For example:
/**
* Sets the focus in the browser to the given component. The markup id must be set. If
* the component is null the focus will not be set to any component.
*
* #param component
* The component to get the focus or null.
*/
org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(Component component)
Once you create your behavior to set the focus, you should be able to add it to the component on any event, just make sure that component is part of the AjaxRequestTarget. I don't see why this wouldn't work...
myRadioButton.add(new AjaxEventBehavior("onchange") {
#Override
protected void onEvent(AjaxRequestTarget target) {
myOtherComponent.add(new DefaultFocusBehavior());
target.addComponent(myForm);
}
});
Here's a link that shows how to create the default focus behavior if you do not have one already:
http://javathoughts.capesugarbird.com/2009/01/wicket-and-default-focus-behavior.html
If you only want to setFocus through javascript and don't want to reload a form or a component, you can use the following code:
import org.apache.wicket.Component;
public class JavascriptUtils {
private JavascriptUtils() {
}
public static String getFocusScript(Component component) {
return "document.getElementById('" + component.getMarkupId() + "').focus();";
}
}
And then in any Ajax Method you can use:
target.appendJavascript(JavascriptUtils.getFocusScript(componentToFocus));
For a pop-up like modalWindow my workaround solution was to use the attribute "autofocus" on the first input tag.
An easy solution is to add it to the html directly.
<input ..... autofocus>
Another solution is to add it to the modalWindow itself:
#Override
public void show(AjaxRequestTarget target) {
super.show(target);
setUpFocus();
}
protected void setUpFocus() {
DeepChildFirstVisitor visitor = new DeepChildFirstVisitor() {
#Override
public void component(Component component, IVisit<Void> iVisit) {
if (isAutofocusable(component)) {
component.add(new AttributeAppender("autofocus", ""));
iVisit.stop();
}
}
#Override
public boolean preCheck(Component component) {
return false;
}
};
this.visitChildren(FormComponent.class, visitor);
}
protected boolean isAutofocusable(Component component) {
if (component instanceof TextArea ||
component instanceof DropDownChoice ||
// component instanceof RadioChoice ||
component instanceof AjaxCheckBox ||
component instanceof AjaxButton ||
component instanceof TextField) {
return true;
}
return false;
}
RadioChoice is commented out because this solution is not working on that. For RadioChoice i would recommend to implement a FocusedRadioChoice:
public class FocusedRadioChoice<T> extends RadioChoice<T> {
//constructors...
#Override
protected IValueMap getAdditionalAttributes(int index, T choice) {
super.getAdditionalAttributes(0, choice);
AttributeMap am = new AttributeMap();
am.put("autofocus", "");
return am;
}
}
Is there a way to achieve the same without JavaScript?
(I am implementing a form with a feedback-Panel that only comes up when Javascript is turned off, so it would not make sense to depend on JavaScript there...,-)
I could only find answers which use JS .focs()... maybe Wicket 1.5 will provide a method Component.setFocus()...
If you happen to be using an Ajax button, you can simply call target.focusComponent(myComponent); in the button's onSubmit method.
#martin-g 's solution was the only solution that got it working for my scenario - a modal/pop up.
Note:
I think autofocus embedded explicitly in HTML only works on page load, not modal load so any efforts to skillfully set the autofocus attribute in the HTML of a modal just fail miserably - always.
Here I lay out the steps for setting the focus on an input field called 'myInput' using the full power of Wicket (no JS!):
In onInitialize:
// Make sure the field has an ID in markup
myInput.setOutoutMarkupId(true);
Provide an overridden show method where you call the focusComponent method:
public void show(AjaxRequestTarget target)
{
// Make sure you call the super method first!
super.show(target);
target.focusComponent(myInput);
}
This does require that your component is an attribute of your modal content class so that you can access it in the show method. To avoid creating a class attribute for your input component you could blend this solution with the solution from BlondCode by replacing that solution's
component.add(new AttributeAppender("autofocus", ""));
with
target.focusComponent(component);
This also works!

Categories