gwt requestfactory gwt-editors

Uso de Editores GWT con un uso complejo



requestfactory gwt-editors (4)

Fundamentalmente, desea que CompositeEditor maneje los casos en los que los objetos se agregan o quitan dinámicamente de la jerarquía del Editor. Los adaptadores ListEditor y OptionalFieldEditor implementan CompositeEditor .

Si la información requerida para los diferentes tipos de preguntas es fundamentalmente ortogonal, entonces se podría utilizar el OptionalFieldEditor con diferentes campos, uno para cada tipo de pregunta. Esto funcionará cuando solo tenga unos pocos tipos de preguntas, pero en realidad no se escalará bien en el futuro.

Un enfoque diferente, que se escalará mejor, sería el uso de una implementación personalizada de un CompositeEditor + LeafValueEditor que maneje una jerarquía de tipos de QuestionData polimórfica. El elemento de interfaz de usuario desplegable de tipo se convertiría en un detalle de implementación del CompositeEditor . Cuando se selecciona un tipo de pregunta, el editor llamará a EditorChain.attach() con una instancia de un subtipo QuestionData y el sub-Editor específico del tipo. La instancia de QuestionData recién creada debe conservarse para implementar LeafValueEditor.getValue() . La implementación de CompositeEditor.setValue() solo crea el sub-Editor específico de tipo y llama EditorChain.attach() .

FWIW, OptionalFieldEditor se puede usar con ListEditor o cualquier otro tipo de editor.

Intento crear una página que sea muy similar a la página de creación de Google Form.

Así es como estoy intentando modelarlo usando el marco GWT MVP (Lugares y Actividades) y Editores.

CreateFormActivity (actividad y presentador)

CreateFormView (interfaz para ver, con la interfaz del presentador anidada)

CreateFormViewImpl (implementa CreateFormView y Editor <FormProxy>

CreateFormViewImpl tiene los siguientes subeditores:

  • Título de TextBox
  • Descripción de TextBox
  • QuestionListEditor questionList

QuestionListEditor implementa IsEditor <ListEditor <QuestionProxy, QuestionEditor >>

QuestionEditor implementa Editor <QuestionProxy> QuestionEditor tiene los siguientes subeditores:

  • TextBox questionTitle
  • TextBox helpText
  • ValueListBox questionType
  • Un subeditor opcional para cada tipo de pregunta a continuación.

Un editor para cada tipo de pregunta:

TextQuestionEditor

ParagraphTextQuestionEditor

MultipleChoiceQuestionEditor

CheckboxesQuestionEditor

ListQuestionEditor

ScaleQuestionEditor

GridQuestionEditor

Preguntás especificas:

  1. ¿Cuál es la forma correcta de agregar / eliminar preguntas del formulario? (ver pregunta de seguimiento )
  2. ¿Cómo debo hacer para crear el Editor para cada tipo de pregunta? Intenté escuchar los cambios en el valor de QuestionType, no estoy seguro de qué hacer después. (respondido por BobV)
  3. ¿Debería cada editor específico de tipo de pregunta ser contenedor con un editor de campo opcional? Como solo se puede usar uno de ellos a la vez. (respondido por BobV)
  4. Cómo administrar mejor la creación / eliminación de objetos en las profundidades de la jerarquía de objetos. Ej. Especificar respuestas para una pregunta número 3 que es de tipo pregunta de opción múltiple. (ver pregunta de seguimiento )
  5. ¿Se puede usar el editor OpcionalFieldEditor para envolver un ListEditor? (respondido por BobV)

Implementación basada en la respuesta

El editor de preguntas

public class QuestionDataEditor extends Composite implements CompositeEditor<QuestionDataProxy, QuestionDataProxy, Editor<QuestionDataProxy>>, LeafValueEditor<QuestionDataProxy>, HasRequestContext<QuestionDataProxy> { interface Binder extends UiBinder<Widget, QuestionDataEditor> {} private CompositeEditor.EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain; private QuestionBaseDataEditor subEditor = null; private QuestionDataProxy currentValue = null; @UiField SimplePanel container; @UiField(provided = true) @Path("dataType") ValueListBox<QuestionType> dataType = new ValueListBox<QuestionType>(new Renderer<QuestionType>() { @Override public String render(final QuestionType object) { return object == null ? "" : object.toString(); } @Override public void render(final QuestionType object, final Appendable appendable) throws IOException { if (object != null) { appendable.append(object.toString()); } } }); private RequestContext ctx; public QuestionDataEditor() { initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this)); dataType.setValue(QuestionType.BooleanQuestionType, true); dataType.setAcceptableValues(Arrays.asList(QuestionType.values())); /* * The type drop-down UI element is an implementation detail of the * CompositeEditor. When a question type is selected, the editor will * call EditorChain.attach() with an instance of a QuestionData subtype * and the type-specific sub-Editor. */ dataType.addValueChangeHandler(new ValueChangeHandler<QuestionType>() { @Override public void onValueChange(final ValueChangeEvent<QuestionType> event) { QuestionDataProxy value; switch (event.getValue()) { case MultiChoiceQuestionData: value = ctx.create(QuestionMultiChoiceDataProxy.class); setValue(value); break; case BooleanQuestionData: default: final QuestionNumberDataProxy value2 = ctx.create(BooleanQuestionDataProxy.class); value2.setPrompt("this value doesn''t show up"); setValue(value2); break; } } }); } /* * The only thing that calls createEditorForTraversal() is the PathCollector * which is used by RequestFactoryEditorDriver.getPaths(). * * My recommendation is to always return a trivial instance of your question * type editor and know that you may have to amend the value returned by * getPaths() */ @Override public Editor<QuestionDataProxy> createEditorForTraversal() { return new QuestionNumberDataEditor(); } @Override public void flush() { //XXX this doesn''t work, no data is returned currentValue = chain.getValue(subEditor); } /** * Returns an empty string because there is only ever one sub-editor used. */ @Override public String getPathElement(final Editor<QuestionDataProxy> subEditor) { return ""; } @Override public QuestionDataProxy getValue() { return currentValue; } @Override public void onPropertyChange(final String... paths) { } @Override public void setDelegate(final EditorDelegate<QuestionDataProxy> delegate) { } @Override public void setEditorChain(final EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain) { this.chain = chain; } @Override public void setRequestContext(final RequestContext ctx) { this.ctx = ctx; } /* * The implementation of CompositeEditor.setValue() just creates the * type-specific sub-Editor and calls EditorChain.attach(). */ @Override public void setValue(final QuestionDataProxy value) { // if (currentValue != null && value == null) { chain.detach(subEditor); // } QuestionType type = null; if (value instanceof QuestionMultiChoiceDataProxy) { if (((QuestionMultiChoiceDataProxy) value).getCustomList() == null) { ((QuestionMultiChoiceDataProxy) value).setCustomList(new ArrayList<CustomListItemProxy>()); } type = QuestionType.CustomList; subEditor = new QuestionMultipleChoiceDataEditor(); } else { type = QuestionType.BooleanQuestionType; subEditor = new BooleanQuestionDataEditor(); } subEditor.setRequestContext(ctx); currentValue = value; container.clear(); if (value != null) { dataType.setValue(type, false); container.add(subEditor); chain.attach(value, subEditor); } } }

Editor de datos base de preguntas

public interface QuestionBaseDataEditor extends HasRequestContext<QuestionDataProxy>, IsWidget { }

Ejemplo de subtipo

public class BooleanQuestionDataEditor extends Composite implements QuestionBaseDataEditor { interface Binder extends UiBinder<Widget, BooleanQuestionDataEditor> {} @Path("prompt") @UiField TextBox prompt = new TextBox(); public QuestionNumberDataEditor() { initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this)); } @Override public void setRequestContext(final RequestContext ctx) { } }

El único problema que queda es que los datos específicos del subtipo QuestionData no se muestran o se vacían. Creo que tiene que ver con la configuración del editor que estoy usando.

Por ejemplo, El valor de solicitud en el BooleanQuestionDataEditor no está configurado ni enrojecido, y es nulo en la carga útil de rpc.

Mi suposición es: dado que QuestionDataEditor implementa LeafValueEditor, el controlador no visitará el subeditor, aunque haya sido adjuntado.

Muchas gracias a todos los que pueden ayudar!


En relación con su pregunta, ¿por qué los datos específicos del subtipo no se muestran o se descargan?

Mi escenario es un poco diferente, pero hice la siguiente observación:

El enlace de datos del editor GWT no funciona como uno esperaría con los editores abstractos en la jerarquía del editor. El subEditor declarado en su QuestionDataEditor es del tipo QuestionBaseDataEditor y este es un tipo totalmente abstracto (una interfaz). Al buscar campos / subeditores para completar con datos / flush, GWT toma todos los campos declarados en este tipo. Dado que QuestionBaseDataEditor no tiene subeditores declarados, no se muestra / descarga nada. Desde la depuración descubrí que esto sucede debido a que GWT usa un EditorDelegate generado para ese tipo abstracto en lugar de EditorDelegate para el subtipo concreto presente en ese momento.

En mi caso, todos los subeditores concretos tenían los mismos tipos de editores de valor de hoja (tenía dos editores concretos diferentes para mostrar y uno para editar el mismo tipo de bean), así que podía hacer algo como esto para evitar esta limitación:

interface MyAbstractEditor1 extends Editor<MyBean> { LeafValueEditor<String> description(); } // or as an alternative abstract class MyAbstractEditor2 implements Editor<MyBean> { @UiField protected LeafValueEditor<String> name; } class MyConcreteEditor extends MyAbstractEditor2 implements MyAbstractEditor1 { @UiField TextBox description; public LeafValueEditor<String> description() { return description; } // super.name is bound to a TextBox using UiBinder :) }

Ahora GWT encuentra los subeditores en la clase base abstracta y en ambos casos obtengo el nombre y la descripción de los campos correspondientes rellenos y enjuagados.

Lamentablemente, este enfoque no es adecuado cuando los subeditores concretos tienen diferentes valores en su estructura de frijol para editar :(

Creo que esto es un error de la generación de código GWT del marco de los editores, que solo puede ser resuelto por el equipo de desarrollo de GWT.


Implementamos un enfoque similar (ver respuesta aceptada) y funciona así para nosotros.

Dado que el controlador inicialmente no está al tanto de las rutas de edición simples que pueden usar los subeditores, cada subeditor tiene su propio controlador:

public interface CreatesEditorDriver<T> { RequestFactoryEditorDriver<T, ? extends Editor<T>> createDriver(); } public interface RequestFactoryEditor<T> extends CreatesEditorDriver<T>, Editor<T> { }

Luego usamos el siguiente adaptador de editor que permitiría cualquier sub editor que implemente RequestFactoryEditor para ser utilizado. Esta es nuestra solución para apoyar el polimorfismo en los editores:

public static class DynamicEditor<T> implements LeafValueEditor<T>, CompositeEditor<T, T, RequestFactoryEditor<T>>, HasRequestContext<T> { private RequestFactoryEditorDriver<T, ? extends Editor<T>> subdriver; private RequestFactoryEditor<T> subeditor; private T value; private EditorDelegate<T> delegate; private RequestContext ctx; public static <T> DynamicEditor<T> of(RequestFactoryEditor<T> subeditor) { return new DynamicEditor<T>(subeditor); } protected DynamicEditor(RequestFactoryEditor<T> subeditor) { this.subeditor = subeditor; } @Override public void setValue(T value) { this.value = value; subdriver = null; if (null != value) { RequestFactoryEditorDriver<T, ? extends Editor<T>> newSubdriver = subeditor.createDriver(); if (null != ctx) { newSubdriver.edit(value, ctx); } else { newSubdriver.display(value); } subdriver = newSubdriver; } } @Override public T getValue() { return value; } @Override public void flush() { if (null != subdriver) { subdriver.flush(); } } @Override public void onPropertyChange(String... paths) { } @Override public void setDelegate(EditorDelegate<T> delegate) { this.delegate = delegate; } @Override public RequestFactoryEditor<T> createEditorForTraversal() { return subeditor; } @Override public String getPathElement(RequestFactoryEditor<T> subEditor) { return delegate.getPath(); } @Override public void setEditorChain(EditorChain<T, RequestFactoryEditor<T>> chain) { } @Override public void setRequestContext(RequestContext ctx) { this.ctx = ctx; } }

Nuestro ejemplo de editor secundario:

public static class VirtualProductEditor implements RequestFactoryEditor<ProductProxy> { interface Driver extends RequestFactoryEditorDriver<ProductProxy, VirtualProductEditor> {} private static final Driver driver = GWT.create(Driver.class); public Driver createDriver() { driver.initialize(this); return driver; } ... }

Nuestro ejemplo de uso:

@Path("") DynamicEditor<ProductProxy> productDetailsEditor; ... public void setProductType(ProductType type){ if (ProductType.VIRTUAL==type){ productDetailsEditor = DynamicEditor.of(new VirtualProductEditor()); } else if (ProductType.PHYSICAL==type){ productDetailsEditor = DynamicEditor.of(new PhysicalProductEditor()); } }

Sería genial escuchar tus comentarios.


¿No es el problema fundamental que la vinculación se produce en el momento de la compilación, por lo que solo se vinculará con QuestionDataProxy, por lo que no tendrá enlaces específicos del subtipo? El CompositeEditor javadoc dice "Una interfaz que indica que un Editor dado se compone de un número desconocido de subeditores del mismo tipo", ¿así que se descarta este uso?

En mi trabajo actual, estoy presionando para evitar el polimorfismo ya que el RDBMS tampoco lo admite. Lamentablemente tenemos algunos en este momento, así que estoy experimentando con una clase de envoltura ficticia que expone todos los subtipos con getters específicos para que el compilador tenga algo en qué trabajar. No es lindo sin embargo.

¿Has visto esta publicación ?: http://markmail.org/message/u2cff3mfbiboeejr esto parece estar en la línea derecha.

Aunque estoy un poco preocupado por el código hinchado.

Espero que tenga algún tipo de sentido!