Skip to content
Snippets Groups Projects
Commit 09869e9b authored by lmihalkovic's avatar lmihalkovic
Browse files

OO-2007: new light weight form fragments mechanism to reuse groups of fields...

OO-2007: new light weight form fragments mechanism to reuse groups of fields without embedding then inside a separate Form/FormController logic.
parent 222490a7
No related branches found
No related tags found
No related merge requests found
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.FormUIFactory;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.modules.IModuleConfiguration;
/**
* Base class for implementing {@link IFormFragment}
*
* <p>Initial date: May 6, 2016
* @author lmihalkovic, http://www.frentix.com
*/
public abstract class BasicFormFragment implements IFormFragment {
protected IFormFragmentHost host;
protected IFormFragmentContainer container;
public BasicFormFragment(WindowControl wControl) {
CoreSpringFactory.autowireObject(this);
}
@Override
public final void initFormFragment(UserRequest ureq, IFormFragmentContainer container, Controller listener, IModuleConfiguration config) {
FormItemContainer formLayout = container.formItemsContainer();
this.container = container;
this.host = container.getFragmentHostInterface();
initFormFragment(formLayout, listener, ureq, config);
}
protected abstract void initFormFragment(FormItemContainer formLayout, Controller listener, UserRequest ureq, IModuleConfiguration config);
protected FormUIFactory uifactory() {
return container.getFragmentHostInterface().getUIFactory();
}
@Override
public void readConfiguration(UserRequest ureq, IModuleConfiguration moduleConfiguration) {
// do nothing by default
}
}
......@@ -25,6 +25,11 @@
*/
package org.olat.core.gui.components.form.flexible.impl;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
......@@ -67,7 +72,7 @@ import org.olat.core.logging.activity.ThreadLocalUserActivityLoggerInstaller;
*
* @author patrickb
*/
public abstract class FormBasicController extends BasicController {
public abstract class FormBasicController extends BasicController implements IFormFragmentContainer {
public static final int LAYOUT_DEFAULT = 0;
......@@ -345,11 +350,16 @@ public abstract class FormBasicController extends BasicController {
* @param source
* @param event
*/
@SuppressWarnings("unused")
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
// overwrite if you want to listen to inner form elements events
}
@Override
protected void event(UserRequest ureq,Controller source, Event event) {
routeEventToFragments(ureq, source, event);
super.event(ureq, source, event);
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.components.Component,
......@@ -357,6 +367,7 @@ public abstract class FormBasicController extends BasicController {
*/
@Override
public void event(UserRequest ureq, Component source, Event event) {
routeEventToFragments(ureq, source, event);
if (source == mainForm.getInitialComponent()) {
// general form events
if (event == org.olat.core.gui.components.form.Form.EVNT_VALIDATION_OK) {
......@@ -611,7 +622,117 @@ public abstract class FormBasicController extends BasicController {
disposableFormItem.dispose();
}
}
forEachFragment(fragment -> {
// do nothing for now
});
}
}, getUserActivityLogger());
}
// ------------------------------------------------------------------------
// IFormFragmentContainer implementation
/**
* A lifecycle method used to signal the controller that it should read the contents
* of its configuration and apply it to its view. The idea is that the structure of a
* form can be created separately from assigning contents to its fields.
* <p>This is particularly useful when dealing with fragments which know how to
* load/store data from a given configuration themselves.
* @param ureq
*/
public void readFormData(UserRequest ureq) {
// do nothing by default
}
/**
* A lifecycle used to signal the form controller that it should visit the view's
* content and save all relevant data into its current configuration.
* <p>This is particularly useful when dealing with fragments which know how to
* load/store data from a given configuration themselves.
* @param ureq
*/
public void storeFormData(UserRequest ureq) {
// do nothing by default
}
@Override
public IFormFragmentHost getFragmentHostInterface() {
// this is to document the missing method implementation in a subclass
throw new IllegalStateException("In order to host from fragments the controller must override getFragmentHostInterface(): " + this.getClass().getSimpleName() );
}
private static class FragmentRef extends WeakReference<IFormFragment>{
public FragmentRef(IFormFragment referent) {
super(referent);
}
}
private List<FragmentRef> fragments = new ArrayList<>();
@Override
public void setNeedsLayout() {
flc.setDirty(true);
}
@Override
public void registerFormFragment(IFormFragment fragment) {
fragments.add(new FragmentRef(fragment));
}
@Override
public FormItemContainer formItemsContainer() {
return flc;
}
// Helpers
@Override
protected void fireEvent(UserRequest ureq, Event event) {
super.fireEvent(ureq, event);
}
// Redefinition of a the super method to provide access with the same
// package (this is required for the Form Fragments)
@Override
protected Controller listenTo(Controller controller) {
return super.listenTo(controller);
}
@Override
public void forEachFragment(Consumer<IFormFragment> handler) {
fragments.stream()
.filter(ref -> ref.get() != null)
.forEach(ref -> {
IFormFragment frag = ref.get();
if (frag != null) {
handler.accept(frag);
}
});
}
protected boolean routeEventToFragments(UserRequest ureq, Component source, Event event) {
// implement shortcut?!
Boolean processed =
fragments.stream()
.reduce(Boolean.FALSE
, (t, u) -> {
IFormFragment frag = u.get();
return frag == null ? Boolean.FALSE : frag.processEvent(ureq, source, event);
}, (t, u) -> t & u)
;
return processed;
}
protected boolean routeEventToFragments(UserRequest ureq, Controller source, Event event) {
// implement shortcut?!
Boolean processed =
fragments.stream()
.reduce(Boolean.FALSE
, (t, u) -> {
IFormFragment frag = u.get();
return frag == null ? Boolean.FALSE : frag.processEvent(ureq, source, event);
}, (t, u) -> t & u)
;
return processed;
}
}
\ No newline at end of file
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Disposable;
import org.olat.core.gui.control.Event;
import org.olat.modules.IModuleConfiguration;
/**
* A form fragment is a simple abstraction that allows composing {@code Form} instances from
* reusable groups of fields. This is a lighter weight level of reuse than to embed entire
* forms inside containing forms. In the case of fragments, there is a single controller
* managing multiple fragments.
*
* <p>Fragments are hosted by a {@link IFormFragmentContainer}.
*
* <p>Initial date: May 6, 2016<br>
* @author lmihalkovic, http://www.frentix.com
*/
public interface IFormFragment extends Disposable {
// this is here for compatibility to help migrate chunks of existing code into fragments
default void initForm(IFormFragmentContainer container, Controller _listener, UserRequest ureq, IModuleConfiguration config) {
this.initFormFragment(ureq, container, _listener, config);
this.validateFormLogic(ureq);
}
/**
* This method must be implemented by fragments however it is not recommended that form
* fragment hosts call it directly. Instead fragment hosts should directly call {@code initForm}
* which will ensure that all initialization is performed in a single step
*
* @param ureq
* @param formLayout
* @param listener
*/
void initFormFragment(UserRequest ureq, IFormFragmentContainer container, Controller listener, IModuleConfiguration config);
/**
* @see FormBasicController#validateFormLogic(UserRequest)
* @return
*/
boolean validateFormLogic(UserRequest ureq);
void readConfiguration(UserRequest ureq, IModuleConfiguration moduleConfiguration);
void storeConfiguration(UserRequest ureq, IModuleConfiguration moduleConfiguration);
default void refreshContents() {
// do nothing by default
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.Controller,
* org.olat.core.gui.control.Event)
* @return true if the event was processed, false otherwise
*/
default boolean processEvent(UserRequest ureq, Controller source, Event event) {
return false;
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.components.Component,
* org.olat.core.gui.control.Event)
* @return true if the event was processed, false otherwise
*/
default boolean processEvent(UserRequest ureq, Component source, Event event) {
return false;
}
/**
* called if an element inside of form triggered an event
*
* @param source
* @param event
* @return true if the event was processed, false otherwise
*/
default boolean processFormEvent(UserRequest ureq, FormItem source, FormEvent event) {
return false;
}
}
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl;
import java.util.function.Consumer;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
/**
* Interface implemented by a visual element (usually a Form controller) that can contain
* one or more {@link IFormFragment} instances
*
* <p>Initial date: May 6, 2016<br>
* @author lmihalkovic, http://www.frentix.com
*/
public interface IFormFragmentContainer {
/**
* Return an instance of the interface used
* @return
*/
default IFormFragmentHost getFragmentHostInterface() {
return null;
}
/**
* Called by a hosted fragment to tell its container that the current presentation
* no longer accurately reflects the current internal state of the fragment. The
* container would generally use this information to trigger a re-evaluation of its
* layout
*/
void setNeedsLayout();
/**
* Used by the fragment in order to add contents to the visual presentation of the
* container
*
* @return
*/
FormItemContainer formItemsContainer();
/**
* Used for registering the existance of a fragment with the underlying form controller
*
* @param fragment
*/
void registerFormFragment(IFormFragment fragment);
/**
* This method can be used to perform an operation on all the fragments currently hosted
* by this container
*
* @param handler
*/
void forEachFragment(Consumer<IFormFragment> handler);
}
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl;
import java.util.function.Consumer;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
/**
* A {@link IFormFragment} requires access to a controller. This interface represents the very
* minimal set of features it expects from its controller. This subset corresponds strictly with
* the features offered by a {@link FormBasicController} and in most cases the fragment controller
* will be the form controller itself. Nonetheless this interface is here to guaranty that a
* fragment will be truly reusable by isolating it from the internal details of more powerful
* form controller.
*
* <p>Initial date: May 6, 2016<br>
* @author lmihalkovic, http://www.frentix.com
*/
public interface IFormFragmentController {
void removeAsListenerAndDispose(Controller controller);
WindowControl getWindowControl();
void listenTo(Controller controller);
void setFormCanSubmit(boolean canSubmit);
void fireEvent(UserRequest ureq, Event event);
/**
* A helper method to help adapt an existing {@link FormBasicController} instance to
* the subset of features exposed by a {@link IFormFragmentController}.
*
* @param delegate the form controller to be adapted
* @param canSubmitHandler a handler to be invoked when the fragment wants to change the overall readiness state of the form
* @return
*/
public static IFormFragmentController fragmentControllerAdapter(final FormBasicController delegate, final Consumer<Boolean> canSubmitHandler) {
return new IFormFragmentController() {
@Override
public void setFormCanSubmit(boolean canSubmit) {
canSubmitHandler.accept(canSubmit);
}
@Override
public void removeAsListenerAndDispose(Controller controller) {
delegate.removeAsListenerAndDispose(controller);
}
@Override
public void listenTo(Controller controller) {
delegate.listenTo(controller);
}
@Override
public WindowControl getWindowControl() {
return delegate.getWindowControlForDebug();
}
@Override
public void fireEvent(UserRequest ureq, Event event) {
delegate.fireEvent(ureq, event);
}
};
}
}
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl;
import org.olat.core.gui.components.form.flexible.FormUIFactory;
import org.olat.core.gui.translator.Translator;
/**
* The interface implemented by a {@link IFormFragmentContainer} to provide the {@link IFormFragment}
* with controller over its runtime behavior.
*
* <p>Initial date: May 6, 2016<br>
* @author lmihalkovic, http://www.frentix.com
*/
public interface IFormFragmentHost {
/**
* Return a {@link Translator} instance that can provide a translation specific to where the
* fragment is used, or a default translation associated with the fragment itself.
*
* @return
*/
Translator getFragmentTranslator();
/**
* Return a {@link FormUIFactory} instance that may have been tweaked to enforce certain
* behavior specific to where the fragment is being embedded
* @return
*/
default FormUIFactory getUIFactory() {
return FormUIFactory.getInstance();
}
/**
* Return the controller instance to be used by the fragment for event handling
*
* @return
*/
IFormFragmentController getFragmentController();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment