flex and joda-time

15 posts / 0 new
Last post
atao
Offline
Joined: 10/15/2008
flex and joda-time

Vincent,

 

I finally wrote some codes about Flex and joda-time.

I have some requests and I need some help.

 

Requests:

- add a getter for DefaultRemoteViewFactory.guidGenerator

- set DefaultRemoteViewFactory.createRDateField as protected

- set RemoteConnectorFactory.attachListeners as protected

 

Help:

At the moment I get the joda date fields, but:

- there is no synchronisation between detail and list pages

- if I edit in the list page, I need to reenter in the field to get the date displaying

It must be related with some listeners but I'm stuck here. 

Regards

Pierre

PS: new classes

===============================================================================

package org.jspresso.framework.binding.remote;

[...]

public class PopsuiteRemoteConnectorFactory extends RemoteConnectorFactory{

    @Override
    public IValueConnector createValueConnector(String id) {
        RemoteValueConnector connector = new PopsuiteValueConnector(id, this);
        attachListeners(connector);
        return connector;
    }

    @Override
    public IFormattedValueConnector createFormattedValueConnector(String id, IFormatter formatter) {
        RemoteFormattedValueConnector connector = new PopsuiteFormattedValueConnector(id, this, formatter);
        attachListeners(connector);
        return connector;
    }

}

===============================================================================

package org.jspresso.framework.binding.remote;

[...]
public class PopsuiteValueConnector extends RemoteValueConnector{

    public PopsuiteValueConnector(String id,
            RemoteConnectorFactory connectorFactory) {
        super(id, connectorFactory);
    }

    @Override
    protected Object getConnecteeValue() {
        Object result =  super.getConnecteeValue();
        if (null == result) return null;
        Class<?> expectedType = ((IModelValueConnector) getModelConnector()).getModelDescriptor().getModelType();
        if (LocalDateTime.class.equals(expectedType)) {
            return LocalDateTime.fromDateFields((Date) result);
        } else if (LocalDate.class.equals(expectedType)) {
            return LocalDate.fromDateFields((Date) result);
        }
        return result;
    }

    @Override
    protected void setConnecteeValue(Object connecteeValue) {
        Object result = connecteeValue;
        if (connecteeValue instanceof LocalDate) {
            result = isUnknown(connecteeValue) ? null : ((LocalDate) connecteeValue).toDateMidnight().toDate();
        }
        else if(connecteeValue instanceof LocalDateTime) {
            result = isUnknown(connecteeValue) ? null : ((LocalDateTime) connecteeValue).toDateTime().toDate();
        }
        super.setConnecteeValue(result);
    }

}

===============================================================================

package org.popsuite.framework.view.remote;

[...]

public class PopsuiteRemoteViewFactory
extends DefaultRemoteViewFactory {

    private IGUIDGenerator        guidGenerator;

    @Override
    protected IView<RComponent> createPropertyView(
            IPropertyDescriptor propertyDescriptor,
            List<String> renderedChildProperties,
            IActionHandler actionHandler,
            Locale locale) {

        IView<RComponent> view = null;

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) {  // must come before ILocalDateTimePropertyDescriptor
            view = createDatePropertyView(
                    (ILocalDatePropertyDescriptor) propertyDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            view = createDatePropertyView(
                    (ILocalDateTimePropertyDescriptor) propertyDescriptor,
                    actionHandler,
                    locale);
        }
        if (view != null) {
            decorateWithDescription(propertyDescriptor, locale, view);
        } else {
            view = super.createPropertyView(
                    propertyDescriptor,
                    renderedChildProperties,
                    actionHandler,
                    locale);
        }
        if ((view != null) && (propertyDescriptor.getName() != null)) {
            view.getPeer().setLabel(
                    propertyDescriptor.getI18nName(getTranslationProvider(), locale));
        }
        return view;
    }

    protected IView<RComponent> createDatePropertyView(
            ILocalDatePropertyDescriptor propertyDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, null, connector);
        return view;
    }

    protected IView<RComponent> createDatePropertyView(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, null, connector);
        return view;
    }

    private RDateField createRDateField(IValueConnector connector) {
        RDateField component = new RDateField(guidGenerator.generateGUID());
        if (connector instanceof IRemoteStateOwner) {
            component.setState(((IRemoteStateOwner) connector).getState());
        }
        return component;
    }

    @Override
    public void setGuidGenerator(IGUIDGenerator guidGenerator) {
        this.guidGenerator = guidGenerator;
        super.setGuidGenerator(guidGenerator);
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

}

 

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Hi Pierre,

I've just published the 3.0.1-SNAPSHOT current trunk in the Jspresso maven repository. I've relaxed the method protections you asked for to protected.

There are some API change for you to take in consideration (some refactoring to make the whole code more consistent) :

public API (straightforward changes in your spring context files)

- nesting view descriptors (BasicNestingViewDescriptor) have been removed.
You can safely change their reference to border view descriptors (BasicBorderViewDescriptor)
setting the "centerViewDescriptor" to the old "nestedViewDescriptor".

- Due to a API refactoring in component view descriptors, all references to
BasicSubviewDescriptor must be changed to BasicPropertyViewDescriptor.

Protected API

I've changed the API to create form related property views (all view factories). All property view factory methods have their signature changed to gain flexibility in component view description. The change is quite straightforward. Basically, property view factory methods now rely on IPropertyViewDescriptor (from which you can get the IPropertyDescriptor that was used previously) to allow fine grain customization that was impossible before. You should get it instantly but if you have any problem regarding this refactoring, just post back and I will help.

I've also fixed a bug that made the use of the flex date field popup impossible (pushing the months navigation button didn't have any effect...).

Regarding your last problem, I didn't understand it clearly. Is it for sure related to your extensions (i.e. does it work with plain date fields) ? If yes, when you change a date do you see it arrive on the server-side ? It should arrive as a RemoteValueCommand in the handleCommand method of the DefaultRemoteController. Basically, the flow is the following :

gui component -> FlexController -> RemoteValueCommand -> RemoteController -> remote (view) connector -> model connector -> model -> other remote (view) connectors backed by the same model -> RemoteValueCommand(s) -> RemoteController -> FlexController -> other gui component(s).

If you can give me more details, I can investigate further (might very well be a bug in the fw...)

Regards,

Vincent

atao
Offline
Joined: 10/15/2008
flex and joda-time

Vincent,

 

[quote]

does it work with plain date fields

[/quote]

Yes, it does.

 

[quote]

do you see it arrive on the server-side

[/quote]

Yes, a RemoteValueCommand arrives with the expected value.

 

One scenario is e.g.:

- I create a new item on the list page

- for this new item, I click on its date field and I select a date with the datepicker

- the date shows in the date field

- I put the focus outside the date field, the date disappears

- I put again the focus inside the date field, the date comes back and now stays

but if now I open the detail view, the date field is empty.

By the way, I'm working under Eclipse with Tomcat 5.5: I get a bunch of exceptions

     java.net.MalformedURLException: no protocol: null

Is it normal?

Regards

Pierre

 

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Do you use the latest 3.0.1 snapshot I've published or at least the 3.0.0 release ? I remember a bug like this I've fixed at the very beginning. I will check... but it's quite difficult without reproducing it here. BTW, you've not changed anything on the Flex side, right ?

By the way, I'm working under Eclipse with Tomcat 5.5: I get a bunch of exceptions

     java.net.MalformedURLException: no protocol: null

Is it normal?

No it's not. The problem comes from the custom Flex renderers that try to load an image icon (tree, comboboxes) even if you've not declared one. It's a bug. Thanks.

atao
Offline
Joined: 10/15/2008
flex and joda-time

I'm still using 3.0-SNAPSHOT

But I replaced the joda-time library by the version 1.6 (with joda-time-hibernate version 1.1)

 

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Try to update to 3.0.1-SNAPSHOT. It's really worth... But it also implies some refactoring to your code due to the API changes I've mentioned.

Regarding your problem, one idea would be that for a reason or another, the server recieves the correct RemoteCommandValue, dispatches it, then gets notified by the connector listeners of a change, in the form of another RemoteCommandValue with the same GUID as the recieved one, but with a null value. It then dispatches it to the Flex client, thus resetting the value you've entered.

You can debug that using a breakpoint in the registerCommand method of the DefaultRemoteController. This is the place where remote commands are registered for dispath to the client-side. If this is the case, once the beakpoint is reached, you can walk the call stack to find the offending code.

BTW, what is the isUnknown(connecteeValue) method doing ? Would it be possible that it returns false, thus sending back a null value to the client ?

atao
Offline
Joined: 10/15/2008
flex and joda-time

[quote]

what is the isUnknown(connecteeValue) method doing ? Would it be possible that it returns false, thus sending back a null value to the client ?

[/quote]

The model defines an "unknow" instance of LocalDate, in fact a date in a very far future. When this unknow date is found, then a null value is send to the client, to display an empty date field.

And when the client returns a null value, the model replaces it by an "unknow" instance.

But during the tests, I removed this behavior: no "unknow" instance, all the value are kept unchanged even the null value.

 

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Pierre,

There is actually a fw design problem that prevents your code from working properly.

In your case, what you want to do is :

  • exchange java.util.Date with the flex frontend. This means getting and setting java.util.Date from and to its remote value state (that is the server widget state), so that it gets dispached to the client.
  • echange LocalDate and LocalDateTime with the rest of the architecture (to and from the model).

So, java.util.Date manipulation should absolutely remain limited to the first case. Schematically :

    -- java.util.Date ->                   -- LocalDate[Time] ->
Flex RemoteValueConnector [Rest of the architecture]
<- java.util.Date -- <- LocalDate[Time] --

But it can't be done with the current codebase since the remote value state are updated out of the connector by a listener that reads the connector value (that must remain a joda structure). This forces you to override the get/setConnecteeValue the way you did... The problem is that get/setConnecteeValue is called by get/setConnectorValue from both sides.

Basically, what needs to be introduced is an indirection on the RemoteValueConnector to allow changing the actual value that will be set to its remote value state and thus exchanged with the remote frontend :

  /**
* Gets the value that has to be set to the remote state when updating it. It
* defaults to the connector value but the developper is given a chance here
* to mutate the actual object returned. This allows for changing the type of
* objects actually exchanged with the remote frontend peer.
*
* @return the value that has to be set to the remote state when updating it.
*/
protected Object getValueForState() {
return getConnectorValue();
}

Then, in your subclass :

  @Override
  protected Object getValueForState() {
Object valueForState = super.getValueForState();
if (valueForState instanceof LocalDate[Time]) {
valueForState = transformToDate((Localxxx) connecteeValue);
}
    return valueForState;
  }

  @Override
  protected void setConnecteeValue(Object connecteeValue) {
Object actualConnecteeValue = connecteeValue;
if(connecteeValue instanceof Date) {
// This comes from the Flex front-end and needs to be transformed
// before getting to the connector
actualConnecteeValue = transformToJoda((Date)connecteeValue);
}
    super.setConnecteeValue(actualValueToPushUp);
  }

This way, you  are respecting the layers isolation and only the remote value state knows about java.util.Date.

Now the framework has to read the state you've updated (java.util.Date) and not take the value from the value change event (joda date) to build the command to be dispached to the client. This is what needs to be fixed.

I've published an updated 3.0.1 snapshot to the repo including the whole refactoring. Don't hesitate to post back if it doesn't fix your issue.

Thanks again.

Regards,

Vincent

atao
Offline
Joined: 10/15/2008
flex and joda-time

Vincent,

 

[quote]

The problem is that get/setConnecteeValue is called by get/setConnectorValue from both sides.

[/quote]

I was just trying to understand this part of the code...

 

So, no choice, I have to upgrade!

 

Thanks

Pierre

atao
Offline
Joined: 10/15/2008
flex and joda-time

When I created a new project with

       mvn archetype:generate -DarchetypeCatalog=http://repository.jspresso.org/maven2-snapshots/

the only option is still 3.0-SNAPSHOT

 

 

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Right. I forgot to update the archetype catalog. It should be fine now.

BTW, I also fixed the erronous stacktraces you got in Tomcat logs.

atao
Offline
Joined: 10/15/2008
flex and joda-time

Vincent,

 

RemoteFormattedValueConnector doesn't need the "getValueForState" method?

 

Pierre

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Pierre,

There is no need to do so, since the RemoteFormattedValueConnector exchanges strings with the front-end. I've prepared them in the case when the remote client (and the matching network communication layer) is unable to handle typed values (like dates, numbers, ...) and will forward and recieve strings. The parsing is then done on the server to transform the received string into a correctly typed connector value that can be used with the rest of the architecture.

The method used to set and extract the string value based on the connector value is get/setConnectorValueAsString(). These methods are used when updating the remote value state and handling a recieved string value from the frontend (as getValueForState() is used for RemoteValueConnector)...

All is fine but ;-) there is a bug in the DefaultRemoteController. When it receives a RemoteValueCommand, it should test if the receiver is an instance of IFormattedValueConnector and use the setConnectorValueAsString() method instead of the setConnectorValue() one. I've fixed it (thanks!).

The idea is then to adapt the instance of DefaultRemoteViewFactory to allow for server parsing of values returned by the remote client, depending on the capacity of the GUI technology used. But for Flex, the blazeds network layer and the Flex components are rich enough to avoid this "feature", that is somehow a degraded mode of operations... And it explains the bug also since it isn't actually used.

Hope this helps,

Vincent

atao
Offline
Joined: 10/15/2008
flex and joda-time

Vincent,

Flex version runs fine.

Thanks

Pierre

vvandens
Offline
Joined: 05/29/2008
flex and joda-time

Great ! That's good news.