I'm developing a Wicket application for college and I ran into (a somewhat tricky) problem.
To put some context: I'm making an application that uses a HTML5 player, my solution to this was to give the constructor of the page that contains the player some PageParamameters to make a query and retrieve the sources for the media.
Now my problem is that I'm using a template, so I made a page that puts the Header and the footer automatically, but my player needs the PageParameters to work and I'm trying to make it so it can extend from BasePage, but the constructor from base page also has some parameters so it can dynamically change the text on the header based on the page it is currently on. This makes putting the super() method tricky:
protected BasePage(String tabHeader, String header) {
add(new Label("tab_title", tabHeader));
add(new Header("header", header));
add(new UserPanel("user_panel"));
add(new Footer("footer"));
}
And my player page:
public PlayerPage(PageParameters params) {
String name = params.get("name").toString();
MediaItem item = getItem(name.trim());
add(new Label("tab_title", "MaeGûl - " + name)); //trying to get rid of this
add(new Header("header", item.getName())); //and this
add(new UserPanel("user_panel")); //and this
if (item.getType().equals(ItemTypes.MUSIC)) {
add(new AudioPlayer("player", item.getMediaSources()));
} else if (item.getType().equals(ItemTypes.SERIES)
|| item.getType().equals(ItemTypes.MOVIES))
add(new VideoPlayer("player", item.getMediaSources()));
add(new Footer("footer")); //and this
}
As you may see, if I put the super(tabHeader, header) in my PlayerPage constructor it needs the "name" parameter to update the header and tabHeader of the BasePage, so I'm stupmed...
Any solutions to this?
You may change the BasePage to accept models instead of strings:
protected BasePage(IModel<String> tabHeader, IModel<String> header)
and delegate the retrieval of the values to dedicated models, e.g. derived from AbstractReadOnlyModel:
public PlayerPage(PageParameters params) {
super(new TabHeaderModel(params), new HeaderModel(params));
...
}
There is, of course, a method that can be used by any component to get the PageParameters. It is..
getPage().getPageParameters();
For getPageParameters() to return a non-null value, however, you must have called the Wicket Page constructor with super(params), therefore insisting that your BasePage have a constructor BasePage(PageParameters params).
In fact, after playing around with this for awhile, if you use BookmarkablePageLink and PageParameters for most of your pages, then anything extending Page should only implement the constructor Page(PageParameters params). This doesn't apply if you use session relative pages and a lot of new PlayerPage() type of code.
Related
I have a code (swing):
javax.swing.JButton loginbutton = new javax.swing.JButton("Login");
loginbutton.setName("LoginButton126");
and test for it:
ComponentFinder finder = BasicComponentFinder.finderWithCurrentAwtHierarchy();
javax.swing.JButton loginbutton = (javax.swing.JButton) finder.findByName("LoginButton126");
but unfortunatelly I have:
Exception in thread "main" java.lang.NoClassDefFoundError: org/fest/util/Strings
What should I change ?
Best regards
I think this should work fine
By creating a new ComponentFinder that only has access to the GUI components created after it. In the following example, finder has access to MainFrame but not to LoginFrame.
// new LoginFrame();
ComponentFinder finder = BasicComponentFinder.finderWithNewAwtHierarchy();
finder.findByName("login", true); // will fail finding component of login frame
// new MainFrame();
finder.findByName("pw", true); // will work finding label of main frame
The easiest solution is to make all of the component variables be class variables so that you can access them anywhere. However, not everyone wants to do that, and some (like myself) are using GUI Editors that don't generate the components as class variables.
My solution is simple, I'd like to think, and doesn't really violate any programming standards, as far as I know (referencing what fortran was getting at). It allows for an easy and straightforward way to access components by name.
Create a Map class variable. You'll need to import HashMap at the very least. I named mine componentMap for simplicity.
private HashMap componentMap;
Add all of your components to the frame as normal.
initialize() {
//add your components and be sure
//to name them.
...
//after adding all the components,
//call this method we're about to create.
createComponentMap();
}
Define the following two methods in your class. You'll need to import
Component if you haven't already:
private void createComponentMap() {
componentMap = new HashMap<String,Component>();
Component[] components = yourForm.getContentPane().getComponents();
for (int i=0; i < components.length; i++) {
componentMap.put(components[i].getName(), components[i]);
}
}
public Component getComponentByName(String name) {
if (componentMap.containsKey(name)) {
return (Component) componentMap.get(name);
}
else return null;
}
Now you've got a HashMap that maps all the currently existing components in your frame/content pane/panel/etc to their respective names.
To now access these components, it is as simple as a call to getComponentByName(String name). If a component with that name exists, it will return that component. If not, it returns null. It is your responsibility to cast the component to the proper type. I suggest using instanceof to be sure.
If you plan on adding, removing, or renaming components at any point during runtime, I would consider adding methods that modify the HashMap according to your changes.
I'm having trouble figuring out why my render method isn't being called. Here is my custom cell that extends AbstractCell, broken down to its simplest form.
public class FormHistoryCell<T> extends AbstractCell<T> {
#Override
public void render(com.google.gwt.cell.client.Cell.Context context, T value, SafeHtmlBuilder sb) {
System.out.println("Rendering customer cell...");
if (value == null) {
return;
}
}
}
Here is the snipet in my code which creates an instance of "FormHistoryCell" and attempts to add it to a CellList.
#UiFactory
CellList<FormHistoryCell> initList() {
FormHistoryCell formHistoryCell = new FormHistoryCell();
CellList historyList = new CellList<FormHistoryCell>(formHistoryCell);
return historyList;
}
I have tried different things like adding a constructor that takes a String argument, etc. The constructor is called, but the render method is not. Looking at that Abstract class it extends, it seems the render method is called within the "setValue" method, but didn't see where that is called in other custom cell extensions whose render methods seem to be getting called just fine. I'm sure I'm missing something obvious here but can't figure out what. Please help.
Based on the code you provided, there is no reason for a browser to call the render method of your cell. You simply passed a reference to an object FormHistoryCell to your CellList. The render method is only needed when a browser has to display a cell and its content. This happens when you add data to your CellList, as #outellou suggested.
I have a question concerning MVC Swing java application.
Lets say, Entity e is simple class without any logic – only attributes and getters, setters, equals, hashCode, toString (or maybe compareTo). It will represent Model in MVC.
Than we have MainWindow (as the View in MVC).
Is it okay to use e.getSomething();, e.setSomething(someValue); or even sort/iterate some collection of Elements in MainWindow? And therefore do some GUI rendering and actions in component listeners anonymous classes (I guess listener implementation cannot be in Controller, because it's "view dependent" - HTML doesn't have listeners)?
I did something like this in MainWindow:
...
final Element el = Controller.getInstance().getSomeElement();
JButton save = new JButton();
JTextField field = new JTextField(el.getSomething());
save.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
el.setSomething(field.getText());
Controller.getInstance().persist(); //let controller know some Element has changed and needs to be saved
}
});
...
How to change this piece of code for it to comply with MVC? Thanks.
There's not a hard and fast rule; often the view and controller are combined in Swing apps.
However, in strict MVC, the view should not depend on the controller. The view simply listens to the model and draws itself, then exposes its components and events to the controller. The controller reacts to those events and alters the model as appropriate, which changes the view.
So, in your example, I would the following methods to MainWindow:
public void addSaveListener(ActionListener l) {
save.addActionListener(l);
}
public void removeSaveListener(ActionListener l) {
save.removeActionListener(l);
}
Furthermore, I would pass the instance of Element into the MainWindow constructor, so that it does not have to get it from the Controller. The Controller would be creating the MainWindow, passing its own reference in.
Then, in the controller:
myMainWindow.addSaveListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
el.setSomething(field.getText());
persist(); // Element has changed and needs to be saved
}
});
In larger apps, I would consider an event bus architecture instead of what I wrote above, but that is probably a different question.
I have a :
Client Class
ListView
TextField
I need to populate my ListView in order to form a table:
WORKING CODE:
clientModel = new LoadableDetachableModel() {
#Override
protected Object load() {
return Client.getClientListByCompanyName(searchClientInput.getValue());
}
};
searchClientInput.setModel(new Model<String>());
searchClientInput.add(new AjaxFormComponentUpdatingBehavior("onkeyup") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(clientListViewContainer);
}
});
clientListView = new ListView<Client>(CLIENT_ROW_LIST_ID, clientModel) {
#Override
protected void populateItem(ListItem<Client> item) {
Client client = item.getModelObject();
item.add(new Label(CLIENT_ROW_COMPANY_CNPJ_ID, client.getCompanyName()));
item.add(new Label(CLIENT_ROW_COMPANY_NAME_ID, client.getCompanyCnpj()));
}
};
clientListViewContainer.setOutputMarkupId(true);
clientListViewContainer.add(clientListView);
add(clientListViewContainer);
Now, in my HTML, I have a TextField. Whenever an user types something in this TextField, a select will be made in the database with whatever he typed. So for each word, a select is made, and the table needs to be updated. I am guessing I will need to use AJAX and possibly a Model. I'm kind of lost about how I can do this, if someone can provide me examples I would be very grateful.
EDIT: New code that is throwing exception: Last cause: Attempt to set model object on null model of component: searchClientForm:searchClientInput
EDIT 2: Ok so the exception was that my TextField didn't had a model to bind data to. So what I did was: searchClientInput.setModel(new Model<String>());
I also had a problem with the event. Using onkeydown was working, but not as intended. I had Company Name 1-4. If I typed Company Name 1, I would need to press one key again so the table would get updated. With onkeyup this don't happens. Thanks for the help.
You could give the ListView a LoadableDetachableModel which provides the selected clients matching your TextField's value.
Use an AjaxFormComponentUpdatingBehavior on your TextField which add a parent of the ListView to the request target (don't forget #setOutputMarkupId().
I believe the best way to perform what you want (which is repainting a table/list at each input change --> DB access) is with a DataView and a DataProvider.
A DataView is just like the ListView component except it uses an IDataProvider to get the data you want to present. You are able to implement the DataProvider so it accesses your DB, and you can add restrictions (where clauses) to the DataProvider.
[this is more like pseudo-code]
public final class MyDataProvider<T> extends SortableDataProvider<T> {
// ...
Set filters;
// filters is the set where the restrictions you want to apply are stored
...
#Override
public Iterator<T> iterator(int first, int count) {
// DAO (Data Access Object) access to DB
// ...
return dao.findByRestrictions(filters).iterator();
}
...
}
Now on the ajax event on your input component you are able to update the filter being used in the DataProvider, and in the the next repaint of the DataView, the provider will "pull" the data matching the restrictions defined in the filter.
Hope it helps. Best regards.
I'm trying to figure out how to make a dynamically generated csv available to a dygraphs JavaScript.
I'm using a wicket behavior to add the dygraph (JavaScript graph) to my markup like shown in the codesample bellow. Right now I've hardcoded it to use a csv file named "dygraph.csv". I want to change this, and instead make dygraph use the values from String csv, how do I achieve this?
Any help help is greatly appreciated.
public class DygraphBehavior extends AbstractBehavior {
private static final long serialVersionUID = -516501274090062937L;
private static final CompressedResourceReference DYGRAPH_JS = new CompressedResourceReference(DygraphBehavior.class, "dygraph-combined.js");
#Override
public void renderHead(IHeaderResponse response) {
response.renderJavascriptReference(DYGRAPH_JS);
}
#Override
public void onRendered(Component component) {
final String id = component.getId();
Response response = component.getResponse();
response.write(JavascriptUtils.SCRIPT_OPEN_TAG);
response.write("new Dygraph(document.getElementById(\""+id+"\"), \"dygraph.csv\", {rollPeriod: 7, showRoller: true, errorBars: true});");
response.write(JavascriptUtils.SCRIPT_CLOSE_TAG);
}
}
public class Dygraph extends WebPage {
public Dygraph() {
String csv = "Date,ms\n20070101,62\n20070102,62";
add(new ResourceLink<File>("csv", new ByteArrayResource("text/csv", csv.getBytes())));
add(new Label("graphdiv").add(new DygraphBehavior()));
}
}
<div>
<h1>Dygraph:</h1>
<div wicket:id="graphdiv" id="graphdiv" style="width:500px; height:300px;"></div>
<a wicket:id="csv" href="#">dl generated csv</a>
</div>
public class Dygraph extends WebPage {
public Dygraph() {
String csv = "Date,ms\n20070101,62\n20070102,62";
ResourceLink<File> link = new ResourceLink<File>("csv", new ByteArrayResource("text/csv", csv.getBytes()));
add( link );
//this is the url that should be passed to the javascript code
CharSequence url = link.urlFor( IResourceListener.INTERFACE );
add(new Label("graphdiv").add(new DygraphBehavior()));
}
}
There are other solutions based on the scope of your resource, maybe a dynamic shared resource would work better (if your graph parameters can simply be passed as url parameters), but this will work.
The JavaScript needs to see the data in some way after the page has been rendered. So you have two options:
Embed the data in the page (say in a hidden div) and then let JavaScript read the data from there as text.
Create a servlet where the JavaScript can download the data from.
The second option means that your page rendering code has to pass the data somehow to the servlet. You can try to put it into the session but then, it will sit there, occupying RAM. Probably not a problem if it's just a little bit of data and you have only a few users. But if that's not true, option #1 is probably better.