12

Google フォームの作成ページに非常によく似たページを作成しようとしています。

ここに画像の説明を入力

これは、GWT MVP フレームワーク (Places and Activities) と Editors を使用してモデル化しようとしている方法です。

CreateFormActivity (アクティビティとプレゼンター)

CreateFormView (ビュー用のインターフェイス、ネストされた Presenter インターフェイスを使用)

CreateFormViewImpl (CreateFormView および Editor< FormProxy > を実装します)

CreateFormViewImpl には、次のサブエディターがあります。

  • テキストボックスのタイトル
  • テキストボックスの説明
  • QuestionListEditor 質問リスト

QuestionListEditorは IsEditor< ListEditor< QuestionProxy, QuestionEditor>> を実装します

QuestionEditorは Editor < QuestionProxy> を実装します QuestionEditor には次のサブエディタがあります。

  • テキストボックスの質問のタイトル
  • テキストボックスのヘルプテキスト
  • ValueListBox questionType
  • 以下の各質問タイプのオプションの副編集者。

各質問タイプのエディター:

TextQuestionEditor

ParagraphTextQuestionEditor

複数選択問題エディタ

チェックボックスQuestionEditor

ListQuestionEditor

ScaleQuestionEditor

GridQuestionEditor


具体的な質問:

  1. フォームから質問を追加/削除する正しい方法は何ですか。 (フォローアップの質問を参照)
  2. 質問の種類ごとにエディターを作成するにはどうすればよいですか? questionType 値の変更を聞いてみましたが、その後どうすればよいかわかりません。(BobVが回答)
  3. 各質問タイプ固有のエディターは、optionalFieldEditor を使用してラッパーにする必要がありますか? 一度に使用できるのは の 1 つだけです。(BobVが回答)
  4. オブジェクト階層の奥深くにあるオブジェクトの作成/削除を最適に管理する方法。例) 質問番号 3 の選択式問題の解答を指定する。(フォローアップの質問を参照)
  5. OptionalFieldEditor エディターを使用して ListEditor をラップできますか? (BobVが回答)

回答に基づく実装

質問エディター

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);
    }
}

}

質問ベース データ エディタ

public interface QuestionBaseDataEditor extends HasRequestContext<QuestionDataProxy>,                         IsWidget {


}

サブタイプの例

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) {

}
}

残っている唯一の問題は、QuestionData サブタイプ固有のデータが表示またはフラッシュされていないことです。私が使用しているエディターのセットアップに関係していると思います。

たとえば、プロンプトの値BooleanQuestionDataEditorは設定もフラッシュもされず、rpc ペイロードでは null です。

私の推測では、QuestionDataEditor は LeafValueEditor を実装しているため、サブエディターがアタッチされていても、ドライバーはサブエディターにアクセスしません。

助けてくれる人に感謝します!!!

4

4 に答える 4

9

基本的に、CompositeEditorオブジェクトがエディタ階層に動的に追加または削除されるケースを処理する必要があります。ListEditorおよびOptionalFieldEditorアダプタは を実装しますCompositeEditor

さまざまな種類の質問に必要な情報が基本的に直交している場合は、OptionalFieldEditor質問の種類ごとに 1 つずつ、さまざまなフィールドで複数を使用できます。これは、質問の種類が少ない場合は機能しますが、将来的にはうまく拡張できません。

拡張性が向上する別のアプローチは、ポリモーフィック型階層CompositeEditor + LeafValueEditorを処理するのカスタム実装を使用することです。QuestionDataタイプ ドロップダウン UI 要素は、 の実装の詳細になりCompositeEditorます。質問タイプが選択されると、エディターはサブタイプEditorChain.attach()のインスタンスとQuestionDataタイプ固有のサブエディターを呼び出します。新しく作成されたQuestionDataインスタンスは、実装のために保持する必要がありますLeafValueEditor.getValue()。の実装はCompositeEditor.setValue()、タイプ固有のサブエディターを作成し、 を呼び出すだけEditorChain.attach()です。

FWIW は、またはその他のエディター タイプOptionalFieldEditorで使用できます。ListEditor

于 2011-08-15T13:15:18.203 に答える
2

同様のアプローチを実装し(受け入れられた回答を参照)、このように機能します。

ドライバーは最初、サブエディターによって使用される可能性のある単純なエディター パスを認識しないため、すべてのサブエディターには独自のドライバーがあります。

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

public interface RequestFactoryEditor<T> extends CreatesEditorDriver<T>, Editor<T> {
}

次に、RequestFactoryEditor を実装するサブエディターを使用できるようにする次のエディター アダプターを使用します。これは、エディターでポリモーフィズムをサポートするための回避策です。

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;
    }
}

サブエディターの例:

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;
    }
...
}

私たちの使用例:

        @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());
            }
        }

コメントをいただければ幸いです。

于 2012-03-26T16:34:58.520 に答える
1

Regarding your question why subtype specific data isn't displayed or flushed:

My scenario is a little bit different but I made the following observation:

GWT editor databinding does not work as one would expect with abstract editors in the editor hierarchy. The subEditor declared in your QuestionDataEditor is of type QuestionBaseDataEditor and this is fully abstract type (an interface). When looking for fields/sub editors to populate with data/flush GWT takes all the fields declared in this type. Since QuestionBaseDataEditor has no sub editors declared nothing is displayed/flushed. From debugging I found out that is happens due to GWT using a generated EditorDelegate for that abstract type rather than the EditorDelegate for the concrete subtype present at that moment.

In my case all the concrete sub editors had the same types of leaf value editors (I had two different concrete editors one to display and one to edit the same bean type) so I could do something like this to work around this limitation:

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 :)
}

Now GWT finds the subeditors in the abstract base class and in both cases I get the corresponding fields name and description populated and flushed.

Unfortunately this approach is not suitable when the concrete subeditors have different values in your bean structure to edit :(

I think this is a bug of the editors framework GWT code generation, that can only be solved by the GWT development team.

于 2011-09-23T09:50:49.033 に答える
0

バインディングがコンパイル時に発生するという根本的な問題は、QuestionDataProxyにのみバインドされるため、サブタイプ固有のバインディングがないのではないでしょうか。CompositeEditor javadocは、「特定のエディターがすべて同じタイプの未知の数のサブエディターで構成されていることを示すインターフェース」と言っているので、この使用法は除外されますか?

私の現在の仕事では、RDBMSもポリモーフィズムをサポートしていないため、ポリモーフィズムを完全に回避することを推進しています。残念ながら、現時点ではいくつかあるので、特定のゲッターですべてのサブタイプを公開するダミーラッパークラスを試しているので、コンパイラーは何か作業を行うことができます。でもきれいではありません。

この投稿を見たことがありますか:http://markmail.org/message/u2cff3mfbiboeejrこれは正しい方向に沿っているようです。

コードの膨張が少し心配です。

それが何らかの意味をなすことを願っています!

于 2012-05-19T10:43:10.733 に答える