问题描述
我试图创建一个与Google表单创建页面非常相似的页面。
CreateFormActivity (活动和演示者)
CreateFormView (用于视图的接口,带有嵌套Presenter接口)
$ b
CreateFormViewImpl (实现CreateFormView和Editor< FormProxy>
CreateFormViewImpl包含以下子编辑器:
$ b QuestionListEditor 实现IsEditor< ListEditor< QuestionProxy,QuestionEditor>
QuestionEditor implements implements Editor < QuestionProxy>
QuestionEditor有以下子编辑器:
- TextBox questionTitle
- TextBox helpText
- ValueListBox questionType
- 以下每个问题类型的可选子编辑器。 / ul>
- 从表单中添加/删除问题的正确方法是什么? (请参阅跟进问题 a>)
- 我应该如何为每个问题类型创建编辑器?我试图听取questionType值的变化,我不知道该怎么办。 (由BobV回答)
- 每个问题类型专用编辑器是否应该包含一个optionalFieldEditor?因为一次只能使用其中一个。 (由BobV回答)
- 如何最好地管理在对象层次结构中深入创建/删除对象。例如)指定问题编号为3的类型选择题的答案。 (请参阅跟进问题 a>)
- 可以使用OptionalFieldEditor编辑器来包装ListEditor吗? (由BobV回答)
每种问题类型的编辑器:
TextQuestion编辑器
ParagraphTextQuestionEditor
MultipleChoiceQuestionEditor
CheckboxesQuestionEditor
$ b
ListQuestionEditor
ScaleQuestionEditor
GridQuestion编辑器
具体问题:
问题编辑器
public class QuestionDataEditor extends Composite implements
CompositeEditor< QuestionDataProxy,QuestionDataProxy,Editor< QuestionDataProxy>> ;,
LeafValueEditor< QuestionDataProxy>,HasRequestContext< QuestionDataProxy> {
interface Binder扩展了UiBinder< Widget,QuestionDataEditor> {}
private CompositeEditor.EditorChain< QuestionDataProxy,Editor< QuestionDataProxy>>链;
private QuestionBaseDataEditor subEditor = null;
private QuestionDataProxy currentValue = null;
@UiField
SimplePanel容器;
@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());
}
}
});
私人RequestContext ctx;
$ b $ public Q QuestionDataEditor(){
initWidget(GWT。< Binder> create(Binder.class).createAndBindUi(this));
dataType.setValue(QuestionType.BooleanQuestionType,true);
dataType.setAcceptableValues(Arrays.asList(QuestionType.values()));
/ *
*类型下拉UI元素是
* CompositeEditor的实现细节。当选择问题类型时,编辑器将
*调用EditorChain.attach(),其中包含QuestionData子类型
*的实例和特定于类型的子编辑器。
* /
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不出现);
setValue(value2);
break;
}
}
});
}
/ *
*调用createEditorForTraversal()的唯一方法是RequestFactoryEditorDriver.getPaths()使用的PathCollector
*。
*
*我的建议是总是返回您的问题
*类型编辑器的一个简单实例,并知道您可能需要修改
返回的值* getPaths()
* /
@Override
public Editor< QuestionDataProxy> createEditorForTraversal(){
return new QuestionNumberDataEditor();
}
@Override
public void flush(){
// XXX这不起作用,没有数据返回
currentValue = chain。的getValue(副主编);
}
/ **
*返回一个空字符串,因为只有一个子编辑器被使用。
* /
@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(最终EditorChain< QuestionDataProxy,编辑< QuestionDataProxy>>链){
this.chain =链;
}
@Override
public void setRequestContext(final RequestContext ctx){
this.ctx = ctx;
$ b $ * b $ b * CompositeEditor.setValue()的实现只是创建
*类型特定的子编辑器并调用EditorChain.attach()。
* /
@Override
public void setValue(final QuestionDataProxy value){
// if(currentValue!= null&& value == null) {
chain.detach(subEditor);
//}
QuestionType type = null; ((QuestionMultiChoiceDataProxy)value).getCustomList()== null){
((QuestionMultiChoiceDataProxy)value).setCustomList(new ArrayList< CustomListItemProxy>
if(value instanceof QuestionMultiChoiceDataProxy){
if ());
}
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);
$ b $ / code>
问题库数据编辑器
public interface QuestionBaseDataEditor扩展HasRequestContext< QuestionDataProxy>,IsWidget {
}
示例子类型
public class BooleanQuestionDataEditor扩展Composite实现QuestionBaseDataEditor {
接口Binder扩展UiBinder< Widget,BooleanQuestionDataEditor> {}
@Path(prompt)
@UiField
TextBox提示符= new TextBox();
$ b $ public QuestionNumberDataEditor(){
initWidget(GWT。< Binder> create(Binder.class).createAndBindUi(this));
}
@Override
public void setRequestContext(final RequestContext ctx){
}
}
剩下的唯一问题是QuestionData子类型特定的数据未被显示或刷新。我认为它与我使用的编辑器设置有关。例如, BooleanQuestionDataEditor
中提示的值既未设置也未刷新,并且在rpc中为null有效载荷。
我的猜测是:由于QuestionDataEditor实现了LeafValueEditor,即使它已被连接,驱动程序也不会访问子编辑器。
非常感谢给任何可以提供帮助的人!!! 从根本上来说,您需要一个 CompositeEditor
来处理动态添加或从编辑器层次结构中删除对象的情况。 ListEditor
和 OptionalFieldEditor
适配器实现 CompositeEditor
。
如果不同类型的问题所需的信息基本上是正交的,那么可以将多个 OptionalFieldEditor
用于不同的字段,为每个问题类型。这将适用于只有几种问题类型的情况,但未来不会很好地扩展。
另一种可以更好地扩展的方法是使用一个处理多态 QuestionData
类型层次结构的 CompositeEditor + LeafValueEditor
的自定义实现。类型下拉UI元素将成为 CompositeEditor
的实现细节。当选择一个问题类型时,编辑器将调用 EditorChain.attach()
和一个 QuestionData
子类型的实例,类型特定的子编辑器。应该保留新创建的 QuestionData
实例来实现 LeafValueEditor.getValue()
。 CompositeEditor.setValue()
的实现只是创建类型特定的子编辑器,并调用 EditorChain.attach()
。
$ b FWIW, OptionalFieldEditor
可以与 ListEditor一起使用
或任何其他编辑器类型。
I'm trying to create a page which is very similar to the Google Form creation page.
This is how I am attempting to model it using the GWT MVP framework (Places and Activities), and Editors.
CreateFormActivity (Activity and presenter)
CreateFormView (interface for view, with nested Presenter interface)
CreateFormViewImpl (implements CreateFormView and Editor< FormProxy >
CreateFormViewImpl has the following sub-editors:
- TextBox title
- TextBox description
- QuestionListEditor questionList
QuestionListEditor implements IsEditor< ListEditor< QuestionProxy, QuestionEditor>>
QuestionEditor implements Editor < QuestionProxy>QuestionEditor has the following sub-editors:
- TextBox questionTitle
- TextBox helpText
- ValueListBox questionType
- An optional subeditor for each question type below.
An editor for each question type:
TextQuestionEditor
ParagraphTextQuestionEditor
MultipleChoiceQuestionEditor
CheckboxesQuestionEditor
ListQuestionEditor
ScaleQuestionEditor
GridQuestionEditor
Specific Questions:
- What is the correct way to add / remove questions from the form. (see follow up question)
- How should I go about creating the Editor for each question type? I attempted to listen to the questionType value changes, I'm not sure what to do after. (answered by BobV)
- Should each question-type-specific editor be wrapper with an optionalFieldEditor? Since only one of can be used at a time. (answered by BobV)
- How to best manage creating/removing objects deep in the object hierarchy. Ex) Specifying answers for a question number 3 which is of type multiple choice question. (see follow up question)
- Can OptionalFieldEditor editor be used to wrap a ListEditor? (answered by BobV)
Implementation based on Answer
The Question Editor
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);
}
}
}
Question Base Data Editor
public interface QuestionBaseDataEditor extends HasRequestContext<QuestionDataProxy>, IsWidget {
}
Example Subtype
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) {
}
}
The only issue left is that QuestionData subtype specific data isn't being displayed, or flushed. I think it has to do with the Editor setup I'm using.
For example, The value for prompt in the BooleanQuestionDataEditor
is neither set nor flushed, and is null in the rpc payload.
My guess is: Since the QuestionDataEditor implements LeafValueEditor, the driver will not visit the subeditor, even though it has been attached.
Big thanks to anyone who can help!!!
Fundamentally, you want a CompositeEditor
to handle cases where objects are dynamically added or removed from the Editor hierarchy. The ListEditor
and OptionalFieldEditor
adaptors implement CompositeEditor
.
If the information required for the different types of questions is fundamentally orthogonal, then multiple OptionalFieldEditor
could be used with different fields, one for each question type. This will work when you have only a few question types, but won't really scale well in the future.
A different approach, that will scale better would be to use a custom implementation of a CompositeEditor + LeafValueEditor
that handles a polymorphic QuestionData
type hierarchy. The type drop-down UI element would become 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. The newly-created QuestionData
instance should be retained to implement LeafValueEditor.getValue()
. The implementation of CompositeEditor.setValue()
just creates the type-specific sub-Editor and calls EditorChain.attach()
.
FWIW, OptionalFieldEditor
can be used with ListEditor
or any other editor type.
这篇关于使用GWT编辑器和复杂的用例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!