All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mmm.ui.api.binding.data.UiDataBinding Maven / Gradle / Ivy

There is a newer version: 0.9.8
Show newest version
/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0 */
package io.github.mmm.ui.api.binding.data;

import java.util.Collection;

import io.github.mmm.bean.BeanFactory;
import io.github.mmm.bean.ReadableBean;
import io.github.mmm.bean.WritableBean;
import io.github.mmm.bean.property.ReadableBeanProperty;
import io.github.mmm.entity.id.Id;
import io.github.mmm.entity.link.Link;
import io.github.mmm.property.ReadableProperty;
import io.github.mmm.property.WritableProperty;
import io.github.mmm.property.container.ReadableContainerProperty;
import io.github.mmm.ui.api.UiLocalizer;
import io.github.mmm.ui.api.event.UiValueChangeEvent;
import io.github.mmm.ui.api.factory.UiWidgetFactoryProperty;
import io.github.mmm.ui.api.widget.form.UiFormGroup;
import io.github.mmm.ui.api.widget.form.UiFormPanel;
import io.github.mmm.ui.api.widget.input.UiAbstractInput;
import io.github.mmm.ui.api.widget.input.UiInput;
import io.github.mmm.ui.api.widget.value.UiValidatableWidget;
import io.github.mmm.validation.Validator;

/**
 * Binding that allows to bind a {@link ReadableProperty property} or an entire {@link ReadableBean bean} to the UI. It
 * can create entire dialogs forms for beans dynamically.
 */
public class UiDataBinding {

  private final PropertyProvider defaultPropertyFilter;

  /**
   * The constructor.
   */
  public UiDataBinding() {

    this(null);
  }

  /**
   * The constructor.
   *
   * @param defaultPropertyFilter the {@link PropertyProvider} to use as default.
   */
  public UiDataBinding(PropertyProvider defaultPropertyFilter) {

    super();
    if (defaultPropertyFilter == null) {
      this.defaultPropertyFilter = PropertyProviderDefault.INSTANCE;
    } else {
      this.defaultPropertyFilter = defaultPropertyFilter;
    }
  }

  public  UiValidatableWidget createEditor(B bean) {

    return createEditor(bean, 2);
  }

  public  UiValidatableWidget createEditor(B bean, int columns) {

    // UiResponsiveColumnPanel panel = context.create(UiResponsiveColumnPanel.class);
    // bindBean(bean, panel, columns);
    // return new UiCustomForm<>(this.context, getValue, setValue);
    return null;
  }

  /**
   * @param bean the {@link ReadableBean} to bind.
   * @param receiver the {@link UiBindingReceiver}.
   * @param createGroup - {@code true} to create {@link UiFormGroup}s for nested beans, {@code false} otherwise.
   */
  public void bindBean(ReadableBean bean, UiBindingReceiver receiver, boolean createGroup) {

    bindBean(bean, receiver, createGroup, this.defaultPropertyFilter);
  }

  /**
   * @param bean the {@link ReadableBean} to bind.
   * @param receiver the {@link UiBindingReceiver}.
   * @param createGroup - {@code true} to create {@link UiFormGroup}s for nested beans, {@code false} otherwise.
   * @param propertyProvider the {@link PropertyProvider}.
   */
  public void bindBean(ReadableBean bean, UiBindingReceiver receiver, boolean createGroup,
      PropertyProvider propertyProvider) {

    if (bean == null) {
      return;
    }
    propertyProvider.get(bean).forEach(p -> {
      bindProperty(p, bean, receiver, propertyProvider, createGroup);
    });
  }

  private  void bindProperty(ReadableProperty property, ReadableBean bean, UiBindingReceiver receiver,
      PropertyProvider propertyProvider, boolean createGroup) {

    if (property instanceof ReadableBeanProperty) {
      bindBean((ReadableBeanProperty) property, bean, receiver, propertyProvider, createGroup);
    } else {
      createInput(property, bean, receiver, false);
    }
  }

  private  void bindBean(ReadableBeanProperty beanProperty, ReadableBean parentBean,
      UiBindingReceiver receiver, PropertyProvider propertyFilter, boolean createGroup) {

    B childBean = beanProperty.get();
    if (childBean == null) {
      childBean = BeanFactory.get().create(beanProperty.getValueClass());
    }
    if (createGroup) {
      UiFormGroup formGroup = createFormGroup(childBean, beanProperty, parentBean, propertyFilter);
      receiver.add(beanProperty, formGroup);
    } else {
      bindBean(childBean, receiver, false, propertyFilter);
    }
  }

  /**
   * @param  type of {@link WritableBean}.
   * @param bean the {@link WritableBean}.
   * @return the {@link UiFormGroup}.
   */
  public  UiFormPanel createFormPanel(B bean) {

    return createFormPanel(bean, this.defaultPropertyFilter);
  }

  /**
   * @param  type of {@link WritableBean}.
   * @param bean the {@link WritableBean}.
   * @param propertyFilter the {@link PropertyProvider}.
   * @return the {@link UiFormGroup}.
   */
  public  UiFormPanel createFormPanel(B bean, PropertyProvider propertyFilter) {

    UiBindingReceiverImpl binding = new UiBindingReceiverImpl<>(bean);
    bindBean(bean, binding, true, propertyFilter);
    Collection> inputs = binding.getInputs();
    if (inputs.isEmpty()) {
      return null;
    }
    UiFormPanel formPanel = UiFormPanel.of(binding);
    for (UiAbstractInput input : inputs) {
      formPanel.addChild(input);
    }
    return formPanel;
  }

  /**
   * @param  type of {@link WritableBean}.
   * @param bean the {@link WritableBean}.
   * @param beanProperty the {@link ReadableBeanProperty}.
   * @param parentBean the parent {@link ReadableBean bean}.
   * @param propertyFilter the {@link PropertyProvider}.
   * @return the {@link UiFormGroup}.
   */
  public  UiFormGroup createFormGroup(B bean, ReadableBeanProperty beanProperty,
      ReadableBean parentBean, PropertyProvider propertyFilter) {

    String groupName = localizeLabel(beanProperty, parentBean);
    UiBindingReceiverImpl binding = new UiBindingReceiverImpl<>(bean);
    bindBean(bean, binding, false, propertyFilter);
    Collection> inputs = binding.getInputs();
    if (inputs.isEmpty()) {
      return null;
    }
    UiFormGroup formGroup = UiFormGroup.of(binding, groupName);
    for (UiAbstractInput input : inputs) {
      formGroup.addChild((UiInput) input);
    }
    return formGroup;
  }

  /**
   * @param property the {@link ReadableProperty} to test.
   * @return {@code true} if the given {@code property} should be bound to the UI, {@code false} otherwise.
   */
  protected boolean isBindableProperty(ReadableProperty property) {

    if (property instanceof ReadableContainerProperty) {
      return isBindableProperty(((ReadableContainerProperty) property).getValueProperty());
    }
    Class valueClass = property.getValueClass();
    if (Id.class.isAssignableFrom(valueClass)) {
      return false;
    } else if (Link.class.isAssignableFrom(valueClass)) {
      return false;
    }
    return true;
  }

  /**
   * @param  type of the {@link ReadableProperty#get() property value}.
   * @param property the {@link ReadableProperty}.
   * @param source the optional {@link Object} (e.g. {@code Bean}) owning the property. May be {@code null} but is
   *        required for advanced localization (if you want more specific labels in case the
   *        {@link WritableProperty#getName() property name} is not specific enough).
   * @param receiver the {@link UiBindingReceiver}. May be {@code null}.
   * @param bindValue {@code true} to bind the value of the {@link ReadableProperty} bidirectional with the
   *        {@link UiInput}.
   * @return the {@link UiInput} widget for the given {@link ReadableProperty property}.
   */
  public  UiInput createInput(ReadableProperty property, Object source, UiBindingReceiver receiver,
      boolean bindValue) {

    UiInput input = UiWidgetFactoryProperty.get().create(property);
    input.setId(createId(property, source));
    bindProperty(property, input, source, bindValue);
    if (receiver != null) {
      receiver.add(property, input);
    }
    return input;
  }

  /**
   * @param property the {@link ReadableProperty} to bind.
   * @param input the {@link UiInput} to bind.
   * @param source the optional {@link Object} (e.g. {@code Bean}) owning the property. May be {@code null} but is
   *        required for advanced localization (if you want more specific labels in case the
   *        {@link WritableProperty#getName() property name} is not specific enough).
   * @param bindValue {@code true} to bind the value of the {@link ReadableProperty} bidirectional with the
   *        {@link UiInput}.
   * @param  type of the value.
   */
  public  void bindProperty(ReadableProperty property, UiInput input, Object source, boolean bindValue) {

    Validator validator = property.getMetadata().getValidator();
    input.setValidator(validator);
    String label = localizeLabel(property, source);
    input.setName(label);
    boolean readOnly = property.isReadOnly();
    if (readOnly) {
      input.setReadOnlyFixed(Boolean.TRUE);
    } else {
      if (bindValue) {
        final WritableProperty writableProperty = (WritableProperty) property;
        input.addListener((e) -> {
          if (e.getType() == UiValueChangeEvent.TYPE) {
            V value = input.getValue();
            writableProperty.set(value);
          }
        });
      }
    }
    if (bindValue) {
      property.addWeakListener((e) -> {
        if (!e.isChange()) {
          V value = property.get();
          input.setValue(value);
        }
      });
    }
  }

  /**
   * @param property the {@link ReadableProperty}.
   * @param source the optional {@link Object} (e.g. {@code Bean}) owning the property. May be {@code null}.
   * @return the widget ID.
   */
  protected String createId(ReadableProperty property, Object source) {

    String propertyName = property.getName();
    if (source == null) {
      return propertyName;
    }
    return source.getClass().getSimpleName() + "_" + propertyName;
  }

  /**
   * @param property the {@link ReadableProperty}.
   * @param source the optional {@link Object} (e.g. {@code Bean}) owning the property. May be {@code null}.
   * @return the localized label {@link String}.
   */
  protected String localizeLabel(ReadableProperty property, Object source) {

    String name = property.getName();
    String result = UiLocalizer.get().localizeOrNull(name, source);
    if (result == null) {
      result = name;
    }
    return result;
  }

}