Использование редакторов GWT со сложным сценарием использования - PullRequest
12 голосов
/ 12 августа 2011

Я пытаюсь создать страницу, которая очень похожа на страницу создания формы Google.

enter image description here

Вот как я пытаюсь смоделировать ее с использованием GWT MVPframework (места и действия) и редакторы.

CreateFormActivity (Activity и Presenter)

CreateFormView (интерфейс для просмотра с вложенным интерфейсом Presenter)

CreateFormViewImpl (реализует CreateFormView и редактор

CreateFormViewImpl имеет следующие подредакторы:

  • заголовок TextBox
  • TextBox description
  • QuestionListEditor questionList

QuestionListEditor реализует IsEditor >

QuestionEditor реализует редактор QuestionEditor имеет следующие подредакторы:

  • TextBox questionTitle
  • TextBox helpText
  • ValueListBox questionType
  • Необязательный подредактор для каждого типа вопроса ниже.

Редактор для каждого типа вопроса:

TextQuestionEditor

ParagraphTextQuestionEditor

MultipleChoiceQuestionEditor

ФлажкиQuestionEditor

ListQuestionEditor

ScaleQuestionEditor

GridQuestionEditor


Отдельные вопросы:

  1. Что такоеправильный способ добавить / удалить вопросы из формы. (см. следующий вопрос )
  2. Как мне создать редактор для каждого типа вопроса?Я попытался прослушать изменения значения questionType, я не уверен, что делать после. (отвечает BobV)
  3. Должен ли каждый редактор, относящийся к конкретному типу вопроса, быть оберткой с необязательным элементом FieldEditor?Поскольку только один из может быть использован одновременно. (ответил 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.

Я предполагаю: поскольку QuestionDataEditor реализует LeafValueEditor, драйвер не будет посещать подредактор, даже если он был прикреплен.

Большое спасибо всем, кто может помочь!!!

Ответы [ 4 ]

9 голосов
/ 15 августа 2011

По сути, вы хотите, чтобы CompositeEditor обрабатывал случаи, когда объекты динамически добавляются или удаляются из иерархии редактора.Адаптеры ListEditor и OptionalFieldEditor реализуют CompositeEditor.

Если информация, необходимая для различных типов вопросов, является в основном ортогональной, то можно использовать несколько OptionalFieldEditor с разными полями, по одному для каждого вопроса.тип.Это будет работать, когда у вас есть только несколько типов вопросов, но в будущем не очень хорошо масштабируется.

Другой подход, который будет масштабироваться лучше, будет использовать пользовательскую реализацию CompositeEditor + LeafValueEditor, котораяобрабатывает полиморфную иерархию типов QuestionData.Элемент пользовательского интерфейса раскрывающегося типа станет подробностью реализации CompositeEditor.Когда выбран тип вопроса, редактор будет вызывать EditorChain.attach() с экземпляром подтипа QuestionData и подредактором для конкретного типа.Вновь созданный экземпляр QuestionData должен быть сохранен для реализации LeafValueEditor.getValue().Реализация CompositeEditor.setValue() просто создает специфичный для типа подчиненный редактор и вызывает EditorChain.attach().

FWIW, OptionalFieldEditor может использоваться с ListEditor или любым другим типом редактора.

2 голосов
/ 26 марта 2012

Мы реализовали аналогичный подход (см. Принятый ответ), и он работает для нас так:

Поскольку драйвер изначально не знает простых путей редактора, которые могут использоваться подредакторами, каждый подредактор имеет свой собственныйдрайвер:

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

Было бы здоровоуслышать ваши комментарии.

1 голос
/ 23 сентября 2011

Относительно вашего вопроса, почему данные подтипа не отображаются или сбрасываются:

Мой сценарий немного отличается, но я сделал следующее наблюдение:

Привязка данных редактора GWT не работает какможно было бы ожидать с абстрактными редакторами в иерархии редакторов.SubEditor, объявленный в вашем QuestionDataEditor, имеет тип QuestionBaseDataEditor, и это полностью абстрактный тип (интерфейс).При поиске полей / подредакторов для заполнения данными / сброса GWT берет все поля, объявленные в этом типе.Поскольку у QuestionBaseDataEditor нет подредакторов, объявлено, что ничего не отображается / сбрасывается.Из отладки я выяснил, что это происходит из-за того, что GWT использует сгенерированный EditorDelegate для этого абстрактного типа, а не EditorDelegate для конкретного подтипа, присутствующего в тот момент.

В моем случае все конкретные субредакторы имели одинаковые типыредакторов значений листа (у меня было два разных конкретных редактора, один для отображения и один для редактирования одного и того же типа компонента), поэтому я мог сделать что-то подобное, чтобы обойти это ограничение:

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

Теперь GWT находит подредакторыв абстрактном базовом классе, и в обоих случаях я получаю имя и описание соответствующих полей, которые заполняются и сбрасываются.

К сожалению, этот подход не подходит, когда конкретные подредакторы имеют различные значения в структуре вашего компонента для редактирования: (*

Я думаю, что это ошибка генерации кода GWT в среде редакторов, которая может быть решена только командой разработчиков GWT.

0 голосов
/ 19 мая 2012

Не является ли фундаментальная проблема, что связывание происходит во время компиляции, поэтому будет связываться только с QuestionDataProxy, поэтому не будет привязок для подтипа?Javadoc CompositeEditor говорит: «Интерфейс, который указывает, что данный Редактор состоит из неизвестного числа подчиненных Редакторов одного и того же типа», так что это исключает использование?чтобы вообще избежать полиморфизма, так как СУБД его тоже не поддерживает.К сожалению, у нас есть некоторые в данный момент, поэтому я экспериментирую с фиктивным классом-оберткой, который предоставляет все подтипы с определенными геттерами, так что компилятору есть над чем работать.Хотя не очень.

Вы видели этот пост: http://markmail.org/message/u2cff3mfbiboeejr это похоже на правильные строки.

Я немного беспокоюсь по поводу раздувания кода.

Надеюсь, чтоимеет какой-то смысл!

...