Handling events in Tapestry layout - java

I have a problem in my tapestry project.
Every time I load one page, it triggers onActivate method if is defined in the page in question. But I don't know how to catch that event in my layout template.
If I define a variable in the layout.java, for example:
#Property
String a = "foo";
And I pick that variable value at the template (layout.tml):
<p>${a}</p>
Ok, that will print "foo" in the HTML of all pages that use that layout, but If I want to change that value every time that the page reloads, for example defining onActivate in the layout.java.
void onActivate(){
a="bar";
}
This method doesn't trigger in the layout.java, only in the child pages when it's defined.
(In the child pages I include the layout like Nathan Q says) How can I refresh the variable value?
Any ideas?

I guess layout is a component in this case. Only pages have an activation context, that's why the onActivate() is not fired.
I don't know your exact use case, but:
If it's a page specific variable then you can just pass a parameter to your Layout component.
Layout.java
#Parameter
#Property
private String a;
SomePage.tml
<html t:a="someString" t:type="Layout" ... />
SomePage.java
#Property
private String someString;
...
void onActivate() {
someString = "something specific for this page";
}
OR
If this variable needs to be set every render and it's not a page specific value, you can just use the SetupRender event of your Layout component.
void setupRender() {
a = ...;
}

Ok, I find a way to refresh my property value.
And It was very simple:
Instead of declare a property and update that value in the onActivate method, I declare a public method in the layout.java to get that value and make the updating changes there.
private String a = "foo";
public String getA(){
a = "bar";
return a;
}
This way, I can make any changes to the a variable every time the page loads.

Related

Selenium page object model, how to pass variable from one page class to another?

I'm trying to see if there's a way to pass a variable from one page object to another? I'm using Serenity BDD framework for my java project. On one page I'm creating and naming an item, and then I go to another page where I'm trying to locate that item, in a table for example. I'm running into an issue where the variable I'm calling on the second page is null.
Each page has its own page class and this is a sample code:
public class Page1 extends PageObject{
String itemName;
private String savedItemName;
public String getSavedItemName(){
return savedItemName;
}
public String createItemAndNameIt(){
//some action to create an item
//some action to save the item with a given name
//assigned from itemName which is fed from environment variable
savedItemName = itemName;
return itemName;
}
}
Next step is my code is going into next page and calling savedItemName variable.
public class Page2 extends PageObject{
Page1 page1Steps = New Page1();
String recoveredItemName;
public void searchForItem{
//perform some action to get a list of items
//calling a function that searches page for item
//which accepts element to search and string to search for
searchForElement(webElement,page1Steps.getSavedItemName());
}
}
When I'm calling page1Steps.getSavedItemName() that returns null. I suspect that the culprit could be webdriver when switching between pages the value which was saved in the previous page is not accessible to the new page? Looking for any advice on how to do this sucessfully.
public **String** createItemAndNameIt(){
//some action to create an item
//some action to save the item with a given name
//assigned from itemName which is fed from environment variable
savedItemName = itemName;
return itemName;
}
Your setter method does not have a return type.
Also in Page2 class your method does not have correct signature and return type. Not Sure how you did not get the compilation error.
When using the screenplay pattern you can remeber/recall these things:
actor.remember("itemName", "someItem")
actor.recall("itemName"
)
With help of John Ferguson, this is easily solved by using the following:
Serenity.setSessionVariable("customerName").to("Jim”);
String customerName = sessionVariableCalled("customerName”);

check if a form field has changed using a play Form object

Is there any way to check directly, if the content of a form field in play framework has changed?
for example my Device.java is something like this:
class Device{
String name;
String type;}
and then somewhere in my controller, I have a form of type Device. is there any way to check using boundForm if the value of the name property has changed?
public class Devices extends Controller {
private static final Form<Device> deviceForm = Form.form(Device.class);
public static Result details(Device device) {
if (device == null) {
return notFound(String.format("Device does not exist. "));
}
Form<Device> filledForm = deviceForm.fill(device);
return ok(views.html.devices.details.render(filledForm));
}
public static Result save() {
Form<Device> boundForm = deviceForm.bindFromRequest();
...
[here]
...
}
}
note: details method will show the user the filled form, user may or may not change the values, and then by pressing a Save button , the save() method will be called.
In shortest words Form<T> isn't able to check if fields are changed it's just stateless between request and to check it you just need to get record from DB and compare field, by field.
Also you shouldn't rely on client-side validation as it's mainly for cosmetic, NOT for safety. Remember that it can be manipulated or omitted quite easy with common webdev tools.
Finally you shouldn't resign from Form validation possibilities,as it's very handy tool, instead you can cooperate with it, i.e. it can be something like:
public static Result save() {
Form<Device> boundForm = deviceForm.bindFromRequest();
if (boundForm.hasErrors()){
return badRequest(devices.details.render(boundForm));
}
Device boundDevice = boundForm.get();
Device existingDevice = Device.find.byId(boundDevice.id);
if (boundDevice.name.equals(existingDevice.name)){
boundForm.reject("Contents are identical");
return badRequest(devices.details.render(boundForm));
}
// else... form hasn't errors, name changed - should be updated...
boundDevice.update(boundDevice.id);
}
So you can display it in your view i.e.:
#if(yourForm.error("identicalContent")!=null) {
<div class="alert alert-danger">#yourForm.error("identicalContent").message</div>
}
As you can see from this sample - if you want just to skip UPDATE query if no changes - to save resources - it does not make sense, as you need to make SELECT query anyway to compare. In other cases (like i.e. additional logging ONLY if changed) above snippet is correct solution.

enuma label from message bundle

I have a enum with some entries for a selectOneMenu, that means the enum stucture looks like this: display, pdfLabel.
I want to load the entries label from my message bundle, that means depending on the locale.
It works fine, but only the first time after I depoly the project. That means, if the locale is "en" first time I load the entries, even after logout - session invalidate; if I change the locale to "de" the entries are still from the "en" - message. It works only if I redeploy.
Anyone has an idea about this behavior?
My enum:
public enum Transportmittel {
TRUCK(I18n.get("tv.moc.truck"), "TRUCK"),
AIRFREIGHT(I18n.get("tv.moc.airfreight"), "AIRFREIGHT"),
TRAIN(I18n.get("tv.moc.train"), "TRAIN"),
SEAFREIGHT(I18n.get("tv.moc.seafreight"), "SEAFREIGHT"),
BARGE(I18n.get("tv.moc.barge"), "BARGE");
String ausgabe;
String pdfLabel;
private Transportmittel(String ausgabe, String pdfLabel) {
this.ausgabe = ausgabe;
this.pdfLabel = pdfLabel;
}
public String toString() {
return ausgabe;
}
public String getLabelForPdf() {
return pdfLabel;
}
}
The controller where I load the entries:
#PostConstruct
public void init() {
transportMittelSelectList.add(new SelectItem(Transportmittel.TRUCK.pdfLabel, Transportmittel.TRUCK.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.TRAIN.pdfLabel, Transportmittel.TRAIN.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.AIRFREIGHT.pdfLabel, Transportmittel.AIRFREIGHT.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.SEAFREIGHT.pdfLabel, Transportmittel.SEAFREIGHT.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.BARGE.pdfLabel, Transportmittel.BARGE.ausgabe));
}
And this is where I load the message bundle:
public class I18n {
public static String get(String msg) {
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle bundle = context.getApplication().getResourceBundle(
context, "messages");
return bundle.getString(msg);
}
}
The enum-values are static - so their constructor is called only once when loading the class by the classloader (=the first use). So at consecutive uses you still use the same instance containing the same string ausgabe set at construction-time during the first use.
So you have to set the values for ausgabe and pdfLabel when it is used. But maybe it is even better to have some "external" class which knows how to get the different labels for your enum-value instead of having these values somehow hard-coded inside the enum.
This is indeed not going to work. Enum properties are initialized only once, applicationwide, while i18n is essentially supposed to be resolved on a per-request basis.
You need to redesign your enum as such that only the label keys are hold instead of the resolved localized values.
TRUCK("tv.moc.truck", "TRUCK"),
AIRFREIGHT("tv.moc.airfreight", "AIRFREIGHT"),
TRAIN("tv.moc.train", "TRAIN"),
SEAFREIGHT("tv.moc.seafreight", "SEAFREIGHT"),
BARGE("tv.moc.barge", "BARGE");
And then provide the enum values as follows in an application scoped bean:
#ManagedBean
#ApplicationScoped
public class Data {
public Transportmittel[] getTransportmittels() {
return Transportmittel.values();
}
}
And then reference it in <f:selectItems> as follows (look, no need for SelectItem boilerplate):
<f:selectItems value="#{data.transportmittels}" var="transportmittel"
itemValue="#{transportmittel}" itemLabel="#{bundle[transportmittel.ausgabe]}" />
Or, if you happen to use JSF utility library OmniFaces already, as currently indicated in your user profile, then you could also bypass the whole application scoped Data bean and import it straight in the EL scope as follows:
<o:importConstants type="com.example.Transportmittels" /> <!-- can be declared in a master template -->
...
<f:selectItems value="#{Transportmittels}" var="transportmittel"
itemValue="#{transportmittel}" itemLabel="#{bundle[transportmittel.ausgabe]}" />
See also:
Localizing enum values in resource bundle
I had the same problem, but with ZK, I did need to fetch some properties to my enum, but it was blank String everytime.
To solve this you need to pass as the arguments the key of your property file in your enum constructor, like this:
After that in the get method of your enum propertie you must get the values in resource bundle and return them, like this:

How to use optionalBlock in build step's config.jelly

I have problem with creating constructor, which Jenkins can call for some JSON data originating from a Jelly form,. For testing, I created a minimal Jenkins plugin with mvn hpi:create and following two custom files:
src/main/resources/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder/config.jelly
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:block>
<table>
<f:optionalBlock name="enableText" title="Enable optional text" checked="${instance.enableText}">
<f:entry title="Optional text" field="text">
<f:textbox />
</f:entry>
</f:optionalBlock>
</table>
</f:block>
src/main/java/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder.java
package foo.hyde.jenkins.plugins;
public class OptionalBlockSampleBuilder extends hudson.tasks.Builder {
public final String text;
public final boolean enableText;
#org.kohsuke.stapler.DataBoundConstructor
public OptionalBlockSampleBuilder(String text, Boolean enableText) {
this.text = text;
this.enableText = (enableText != null) && enableText;
}
#Override
public boolean perform(hudson.model.AbstractBuild build, hudson.Launcher launcher, hudson.model.BuildListener listener) {
listener.getLogger().println("OptionalBlockSampleBuilder " + enableText + "/" + text);
return true;
}
#hudson.Extension
public static final class DescriptorImpl extends hudson.tasks.BuildStepDescriptor<hudson.tasks.Builder> {
public boolean isApplicable(Class<? extends hudson.model.AbstractProject> aClass) {
return true;
}
public String getDisplayName() {
return "Optional Block Sample";
}
}
}
I'm building against pom.xml parent <groupId>org.jenkins-ci.plugins</groupId><artifactId>plugin</artifactId><version>1.454</version>, and everything builds, Netbeans 6.9.1 launches Debug Jenkins and I get to create a job with this build step. Everything works if I don't check that checkbox, and I get expected OptionalBlockSampleBuilder false/null to job's console output.
But if I do check the checkbox and add text, then saving/applying the job config gives this exception from the depths of Jenkins code, when it tries to call my constructor:
java.lang.RuntimeException:
Failed to instantiate class
foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder
from {
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
There has to be a simple fix. I have tried many different changes, and also tried to see how other plugins use it, and finally created this minimal test plugin. How to fix it to make optionalBlock work?
The hint comes from the JSON data:
{
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
You can see here that enableText contains a child property, text. That means that the f:optionalBlock is actually expecting an encapsulation of all the fields contained within the block -- when the block is checked, you will receive an instance of the encapsulation field class; when it is unchecked, that field will be null. To use the optionalBlock properly, you would need the #DataBoundConstructor to take in a single nullable class instance that encapsulates the entire optionalBlock. For example:
private String text;
#DataBoundConstructor
public MyClass(EnableTextBlock enableText)
{
if (enableText != null)
{
this.text = enableText.text;
}
}
public static class EnableTextBlock
{
private String text;
#DataBoundConstructor
public EnableTextBlock(String text)
{
this.text = text;
}
}
Notice that the enableText field in this case is actually an instance of EnableTextBlock class, which contains a child property, text. That will satisfy the JSON object that is being sent in the form.
Instead, if all you need is a single field that has a checkbox to enable entry of that field, you might want to consider instead using the f:optionalProperty tag, which will take care of that single-field encapsulation for you. However, in many cases, the optionalBlock is actually needed to configure multiple fields, in which case the encapsulation class--as exampled above--is usually the correct way to go.
The encapsulation class does not have to be a static inner class; it could be a separate class within your package, but the important part is that the DataBoundConstructor should take in an argument that matches the JSON structure being passed from the form.
Or you can add inline tag to optionalBlock like this:
<f:optionalBlock inline="true">
if inline is present, the foldable section will not be grouped into a separate JSON object upon submission.

Accessing arbitrary number of fields in a Wicket form validator

I need to validate something about several Wicket input fields of type TextField<BigDecimal> (namely that the sum of percentages is 100). There are one to many such input fields; thing is, I don't know in advance how many.
(simplified example)
private class PercentageValidator extends AbstractFormValidator {
#Override
public FormComponent<?>[] getDependentFormComponents() {
// ...
}
#Override
public void validate(Form<?> form) {
List<TextField<BigDecimal>> fields = // TODO
// the actual validation where the value of every field is needed
}
}
Java code for the ListView:
ListView<?> listView = new ListView<PropertyShare>("shares", shares) {
#Override
protected void populateItem(ListItem<PropertyShare> item) {
// ...
item.add(new TextField<BigDecimal>("share", ... model ...));
}
};
HTML:
<tr wicket:id="shares">
<td> ... </td>
<td>
<input wicket:id="share" type="text" size="4"> %
</td>
</tr>
I tried keeping every TextField in a collection on the Page, but this approach fails as the populateItem() method of the enclosing ListView gets called not only the the Page is first created, so duplicate fields get added to the collection. (I couldn't figure out an easy way to keep it duplicate-free.)
The fact that ListView is used also seems to somewhat complicate finding the fields from the form object in the validate() method. I suppose I need to get the ListView with form.get("shares") and iterate through its children?
What's the "right way" to access any number of fields enclosed by a repeater such as ListView?
An alternative approach would be to subclass TextField and then use a Visitor to pick out all the descendant components of your subclass.
This way you can avoid unchecked casting and you don't have to rely on the ids, which isn't a very robust approach.
Edit: in practice, it would look something like this:
The subclass:
private static class ShareField extends TextField<BigDecimal> {
// ...
}
Helper method that finds all ShareFields from the form:
private List<ShareField> findShareFields(Form form) {
final List<ShareField> fields = Lists.newArrayList();
form.visitChildren(ShareField.class, new IVisitor<ShareField>() {
#Override
public Object component(ShareField component) {
fields.add(component);
return CONTINUE_TRAVERSAL;
}
});
return fields;
}
Right, while writing the question, it dawned on me that simply looping through the children of form.get("shares") and getting the field with id "share" would probably work.
It indeed does. Here's a helper method that finds the "share" fields:
#SuppressWarnings("unchecked")
private List<TextField<BigDecimal>> findFields(Form form) {
List<TextField<BigDecimal>> fields = Lists.newArrayList();
MarkupContainer container = (MarkupContainer) form.get("shares");
for (Iterator<? extends Component> it = container.iterator(); it.hasNext();) {
MarkupContainer c = (MarkupContainer) it.next();
fields.add((TextField<BigDecimal>) c.get("share"));
}
return fields;
}
However, there are three somewhat ugly casts in the above method, and one of those (Component -> TextField<BigDecimal>) produces an "unchecked cast" warning.
If you can clean up this solution, or know of better approaches, feel free to comment or post other answers!
As far I see you did not set the reuse items property on the list view; from the java doc:
If true re-rendering the list view is more efficient if the windows doesn't get changed at all or if it gets scrolled (compared to paging). But if you modify the listView model object, than you must manually call listView.removeAll() in order to rebuild the ListItems. If you nest a ListView in a Form, ALLWAYS set this property to true, as otherwise validation will not work properly.
However you also can iterate over the children of the listview with a Visitor. Wicket always keeps track of the components you added of the view.

Categories