I'd like a way to easily tie a widget back to the business object it is rendering. So when the user interacts with a widget I can easily determine the business object holding the data for that widget.
For example, if we imagine a calendar widget that we're going to implement with an AbsolutePanel. For each appt object we'll add a label to the calendar. Then when a user clicks on a label he can update the appt. So I need to know which appt object that label refers to.
For instance, if we look at the following code; if the label for an appointment receives a click, how can I find out to which appt it represented ? The only solution I can see is to create a ApptLabel sub-class for Label which would hold a reference to its appt. This is fine, but the example illustrates a more general need which is to associate widgets with data objects; however this would mean that every object that has a presence in a view needs to subclass a widget. that seems heavy - I expected to find something in the framework e.g. a string property in a widget that I can set to an object key
other approaches I tried; maintaining a map of Map -- this didnt work as the label object I create doesnt appear to be the same (in terms of the Object.equals which I guess is what HashMap uses)
class WidgetCalendar extends Composite {
AbsolutePanel m_panel = new AbsolutePanel();
m_panel.setStylePrimaryName("calendar");
m_panel.setPixelSize(width, height);
public WidgetCalendar(ArrayList<BomAppt> appts) {
initWidget(m_panel);
for (BomAppt a : appts) {
Label l = new Label();
l.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// how do I know my BomAppt in here ?
}
m_panel.add(l, someX, someY);
}
}
}
Ideally I can do something like this
class WidgetCalendar extends Composite {
AbsolutePanel m_panel = new AbsolutePanel();
m_panel.setStylePrimaryName("calendar");
m_panel.setPixelSize(width, height);
public WidgetCalendar(ArrayList<BomAppt> appts) {
initWidget(m_panel);
for (BomAppt a : appts) {
Label l = new Label();
l.setItemData(a.getUniqueId());
l.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
BomAppt a = BomAppt.getApptWithId(e.getItemData())
}
}
m_panel.add(l, someX, someY);
}
}
}
This is the solution where I create a subclass, this seems heavy to me and I'd prefer something simpler
class ApptLabel extends Label {
public ApptLabel(BomAppt a) {
m_a = a;
this.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
m_a.doSomething();
});
}
BomAppt m_a;
}
class WidgetCalendar extends Composite {
AbsolutePanel m_panel = new AbsolutePanel();
m_panel.setStylePrimaryName("calendar");
m_panel.setPixelSize(width, height);
public WidgetCalendar(ArrayList<BomAppt> appts) {
initWidget(m_panel);
for (BomAppt a : appts) {
BomLabel l = new BomLabel();
l.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// how do I know my BomAppt in here ?
}
m_panel.add(l, someX, someY);
}
}
}
For instance, if we look at the following code; if the label for an
appointment receives a click, how can I find out to which appt it
represented ?
By using Composite pattern you can find out which widget was clicked, initially you should create your own custom Appointment widget which is responsible for drawing one appointment. And in you Appointment widget you can have a set of other widgets, in your case, for Label add click handler. Once user clicks that label, you can execute business logic with its data and you can represent data.
public class Appointment extends Composite {
private AppointmentDetails data;
public Appointment(AppointmentDetails data){
draw(data);
}
private void draw(AppointmentDetails data){
Label label = new Label();
label.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// do your business logic with this AppointmentDetails
}
});
}
}
After that you should have one Calendar widget which contains several Appointments.
Keep in your mind: your classes each serve a single, very clearly defined purpose, separated from other classes with other clearly defined purposes.
Related
I have a method called creatcomponents() where I am creating few text fields and buttons in a composite. Now I want to write listeners to the button which calls a method and in this method I have get the values of the text fields. The problem I am facing is I am unable to access textfields from the method called in the listeners. Can someone help me on how to acheive this?
One way is to save the controls are fields in your class:
public class MyClass
{
private Text text1;
private Text text2;
public void createComponents(Composite parent)
{
Composite composite = new Composite(parent, SWT.None);
text1 = new Text(composite, SWT.SINGLE);
text2 = new Text(composite, SWT.SINGLE);
text1.addModifyListener(new ModifyListener()
{
#Override
public void modifyText(ModifyEvent event)
{
// Access field
String text = text1.getText();
}
});
}
}
Also note that many of the event classes passed to listeners have a widget field which refers to the current control which you can also use:
text1.addModifyListener(new ModifyListener()
{
#Override
public void modifyText(ModifyEvent event)
{
Text control = (Text)event.widget;
String text = control.getText();
}
});
I have an ObservableList of model items. The model item is enabled for property binding (the setter fires a property changed event). The list is the content provider to a TableViewer which allows cell editing. I also intend to add a way of adding new rows (model items) via the TableViewer so the number of items in the list may vary with time.
So far, so good.
As this is all within an eclipse editor, I would like to know when the model gets changed. I just need one changed event from any changed model item in order to set the editor 'dirty'. I guess I could attach some kind of listener to each individual list item object but I wonder if there is a clever way to do it.
I think that I might have a solution. The following class is an inline Text editor. Changes to the model bean (all instances) are picked up using the listener added in doCreateElementObservable. My eclipse editor just needs to add its' own change listener to be kept informed.
public class InlineEditingSupport extends ObservableValueEditingSupport
{
private CellEditor cellEditor;
private String property;
private DataBindingContext dbc;
IChangeListener changeListener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
for (ITableEditorChangeListener listener : listenersChange)
{
listener.changed();
}
}
};
public InlineEditingSupport(ColumnViewer viewer, DataBindingContext dbc, String property)
{
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.property = property;
this.dbc = dbc;
}
protected CellEditor getCellEditor(Object element)
{
return cellEditor;
}
#Override
protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor)
{
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(Object element, ViewerCell cell)
{
IObservableValue value = BeansObservables.observeValue(element, property);
value.addChangeListener(changeListener); // ADD THIS LINE TO GET CHANGE EVENTS
return value;
}
private List<ITableEditorChangeListener> listenersChange = new ArrayList<ITableEditorChangeListener>();
public void addChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
listenersChange.add(listener);
}
public void removeChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
}
}
I want to add an image to my celltable , for that i use imageResouce as below
interface Resources extends ClientBundle {
#Source("close.png")
ImageResource getImageResource();
}
Resources resources = GWT.create(Resources.class);
deleteJobColumn = new Column<EmployerJobs, ImageResource>(new ImageResourceCell()) {
#Override
public ImageResource getValue(EmployerJobs object) {
return resources.getImageResource();
}
};
Its working perfectly fine , i am getting image in my celltable but Now i want to add clickhandler to that image ,For that i am using field Updater like below
display.getListJobsWidget().getDeleteJobColumn().setFieldUpdater(
new FieldUpdater<EmployerJobs, ImageResource>() {
public void update(int index, EmployerJobs employerJobs,
ImageResource value) {
Window.alert("Hello");
}
});
so now when i click on that above image cell it should say "Hello", but its not doing any thing .. Any solution ..
Thanks
ImageResourceCell doesn't have any wiring to call the ValueUpdater, it is just designed to draw the image and be done with it.
You have a few ways you can change this:
Subclass ImageResourceCell and override onBrowserEvent - take a look at ButtonCell to see how this can work
Use a different cell class instead, like ActionCell - this doesn't take any data from the client, but will let you pass in a delegate isntead, which will be run when the thing is clicked. Use the ActionCell(SafeHtml,Delegate<C>) constructor to pass in your image, in the form of html
Build a new cell, subclassing AbstractCell, borrowing the render code from ImageResourceCell and the event code from ButtonCell or ActionCell to keep your code as generic and reusable as possible.
An example for the first solution provided by Colin. I found it a bit tricky to override getConsumedEvents but seems to work well.
public class ClickableImageResourceCell extends ImageResourceCell{
public ClickableImageResourceCell(){
super();
}
#Override
public Set<String> getConsumedEvents() {
Set<String> set = new HashSet<String>();
set.add("click");
return set;
}
#Override
public void onBrowserEvent(com.google.gwt.cell.client.Cell.Context context, Element parent, ImageResource value, NativeEvent event, ValueUpdater<ImageResource> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("click".equals(event.getType())) {
EventTarget eventTarget = event.getEventTarget();
if (!Element.is(eventTarget)) {
return;
}
if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
// Ignore clicks that occur outside of the main element.
keyDown(context, parent, value, event, valueUpdater);
}
}
}
protected void keyDown(Context context, Element parent, ImageResource value,
NativeEvent event, ValueUpdater<ImageResource> valueUpdater) {
if (valueUpdater != null) {
valueUpdater.update(value);
}
}
}
I have a gwt form which has about 70-100 widgets (textboxes,listboxes,custom widgets etc)
I am trying to implement the features of CUT ,COPY in this form .For this i have 2 buttons right on top of the form.
Now the problem i have is that when i click on the copy button , the widget that was focused in the form looses focus and i dont know which text to copy(or which widget was last focused before the focus getting to the copy button)
I was planning to implement blur handlers on all the widgets but i feel is a very laborious and not a good solution.
How can i get around this issue?
Thanks
Perhaps someone with a deeper insight might provide a better approach but I beleive adding blur handlers is perfectly valid. I do not quite see why you think it would be laborious, after all you don't need a different handler for each of your widgets, you can get away with only one(at most a couple for a variety of controls..), here is a very simple example,
public class CustomBlurHandler implements BlurHandler{
Object lastSource;
String text;
#Override
public void onBlur(BlurEvent event) {
if (event.getSource() instanceof TextBox) {
lastSource = event.getSource();
text = textBox.getSelectedText();
}
}
public Object getLastSource() {
return lastSource;
}
public String getText() {
return text;
}
}
and onModuleLoad :
public class Test implements EntryPoint {
CustomBlurHandler handler = new CustomBlurHandler();
public void onModuleLoad() {
TextBox text1 = new TextBox();
TextBox text2 = new TextBox();
text1.addBlurHandler(handler);
text2.addBlurHandler(handler);
Button b = new Button("Get last selected text");
b.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
Window.alert(handler.getLastSource()+ " " + handler.getText());
}
});
RootPanel.get().add(text1);
RootPanel.get().add(text2);
RootPanel.get().add(b);
}
}
I am having problems while adding my widget into the RootPanel of another container class. I think that it may be related to the Asynchronous call that I make during the creation of the widget. I have a main class named ImageView which implements EntryPoint. In this class, I am creating an instance of my widget named NewWidget by clicking on a button. However, I cannot display it with the traditional methods:
Here is the EntryPoint class (ImageView):
import com.mycompany.project.client.widgets.NewWidget;
public class ImageViewer implements EntryPoint {
public void onModuleLoad() {
Button b = new Button("Button");
RootPanel.get().add(b);
b.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
NewWidget w = new NewWidget();
RootPanel.get().add(w);
}
});
}
And here is my widget (NewWidget):
public class NewWidget extends Composite {
final static DataServiceAsync service = (DataServiceAsync) GWT.create(DataService.class);
final FlowPanel mainPanel = new FlowPanel();
public NewWidget() {
fillImagePath();
}
public void fillImagePath(){
ServiceDefTarget endpoint = (ServiceDefTarget) service;
endpoint.setServiceEntryPoint(GWT.getModuleBaseURL() + "data");
service.getAllDocuments(new AsyncCallback<ArrayList<String>>() {
#Override
public void onFailure(Throwable e) {
Window.alert("Server call failed.");
}
#Override
public void onSuccess(ArrayList<String> paths) {
process(paths);
}
});
}
public void process(ArrayList<String> paths){
ArrayList<String> imagePath = paths;
Image img = new Image(imagePath.get(4));
mainPanel.add(img);
initWidget(mainPanel);
}
}
In this NewWidget, I am making an asynchronous call to my server in order to receive a String ArrayList, which contains 10 Strings that refer to the file path of 10 different images (i.e, "images/01.jpg", "images/02.jpg", and so on). I am pretty sure that I successfully and correctly receive these image paths. I have arbitrarily chosen index number 4 to display in my NewWidget.
The problem is that I cannot display this NewWidget in my ImageView main panel. I can easily display other widgets with this method. With many attempts, I have realized that I can display the image if I add the line RootPanel.get().add(mainPanel) at the end of NewWidget. However, I do not want to make a call that refers to the parent container (RootPanel in ImageView). Why can't I display this image with only instantiating it in my container panel, like I can display any other widget? I am pretty sure that it is related to the Asynchronous call not getting completed before I attempt to add the widget. But I don't know how to fix this.
I would be very glad if people would share their ideas. Thanks.
Move the initWidget(mainPanel) call to the first line the constructor.