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

org.fujion.model.ModelAndView Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * #%L
 * fujion
 * %%
 * Copyright (C) 2008 - 2017 Regenstrief Institute, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * #L%
 */
package org.fujion.model;

import java.util.HashMap;
import java.util.Map;

import org.fujion.client.Synchronizer;
import org.fujion.component.BaseComponent;
import org.fujion.component.Page;
import org.fujion.model.IListModel.IListModelListener;
import org.fujion.model.IListModel.ListEventType;
import org.fujion.model.IPaginator.IPagingListener;
import org.fujion.model.IPaginator.PagingEventType;

/**
 * Associates a model with a view renderer.
 *
 * @param  The component type to be rendered.
 * @param  The model type.
 */
public class ModelAndView implements IListModelListener, IPagingListener, IModelAndView {
    
    private BaseComponent parent;
    
    private IComponentRenderer renderer;
    
    private IListModel model;
    
    private Map> linkedViews;
    
    private boolean deferredRendering;

    private final Paginator paginator;
    
    public ModelAndView(BaseComponent parent) {
        this.parent = parent;
        paginator = new Paginator();
        paginator.addEventListener(this);
    }
    
    public ModelAndView(BaseComponent parent, IListModel model, IComponentRenderer renderer) {
        this(parent);
        setModel(model);
        setRenderer(renderer);
    }
    
    @Override
    public IComponentRenderer getRenderer() {
        return renderer;
    }
    
    @Override
    public void setRenderer(IComponentRenderer renderer) {
        if (renderer != this.renderer) {
            this.renderer = renderer;
            rerender();
        }
    }
    
    @Override
    public IListModel getModel() {
        return model;
    }
    
    @Override
    public void setModel(IListModel model) {
        if (this.model != null) {
            this.model.removeEventListener(this);
        }
        
        this.model = model;
        
        if (this.model != null) {
            this.model.addEventListener(this);
        }
        
        paginator.setModelSize(model == null ? 0 : model.size());
        rerender();
    }
    
    private Map> getLinkedViews() {
        if (linkedViews == null) {
            linkedViews = new HashMap<>();
        }
        
        return linkedViews;
    }
    
    private int getChildIndex(int modelIndex) {
        if (paginator.isDisabled()) {
            return modelIndex;
        } else {
            return modelIndex - paginator.getModelOffset(paginator.getCurrentPage());
        }
    }
    
    @Override
    public void rerender() {
        removeLinkedViews();
        
        if (parent != null) {
            parent.destroyChildren();
        }
        
        if (model != null && parent != null && renderer != null) {
            try {
                onRenderStart();
                int start = adjustIndex(0);
                int end = adjustIndex(model.size() - 1);

                for (int i = start; i <= end; i++) {
                    renderChild(i);
                }
            } finally {
                onRenderStop();
            }
        }
    }
    
    /**
     * If deferred rendering is active, sets the synchronizer to queueing mode.
     */
    protected void onRenderStart() {
        if (deferredRendering) {
            synchronizer(true);
        }
    }
    
    /**
     * If deferred rendering is active, sets the synchronizer to non-queueing mode and flushes its
     * queue.
     */
    protected void onRenderStop() {
        if (deferredRendering) {
            synchronizer(false);
        }
    }
    
    private void synchronizer(boolean pause) {
        Page page = parent == null ? null : parent.getPage();
        Synchronizer synchronizer = page == null ? null : page.getSynchronizer();
        
        if (synchronizer != null) {
            if (pause) {
                synchronizer.startQueueing();
            } else {
                synchronizer.stopQueueing();
            }
        }
    }
    
    /**
     * Renders a child.
     *
     * @param modelIndex The index of the model object to be rendered.
     * @return The rendered child component, or null if no renderer has been registered or
     *         pagination is active and the child falls outside the current page.
     */
    protected T renderChild(int modelIndex) {
        if (renderer != null && paginator.inRange(modelIndex)) {
            M mdl = model.get(modelIndex);
            T child = renderer.render(mdl);
            parent.addChild(child, getChildIndex(modelIndex));
            
            if (model instanceof INestedModel) {
                getLinkedViews().put(child, new ModelAndView<>(child, ((INestedModel) model).getChildren(mdl), renderer));
            }
            
            return child;
        }
        
        return null;
    }
    
    /**
     * Destroys the child component corresponding to the model object at the specified index.
     *
     * @param modelIndex The index of the model object.
     */
    protected void destroyChild(int modelIndex) {
        if (paginator.inRange(modelIndex)) {
            BaseComponent child = parent.getChildAt(getChildIndex(modelIndex));
            ModelAndView linkedView = linkedViews == null ? null : linkedViews.get(child);

            if (linkedView != null) {
                linkedViews.remove(child);
                linkedView.destroy();
            }
            child.destroy();
        }
    }
    
    private void removeLinkedViews() {
        if (linkedViews != null) {
            for (ModelAndView linkedView : linkedViews.values()) {
                linkedView.destroy();
            }
            
            linkedViews.clear();
        }
    }
    
    /**
     * Clean up all resources.
     */
    public void destroy() {
        if (model != null) {
            model.removeEventListener(this);
        }

        paginator.removeAllListeners();
        removeLinkedViews();
        linkedViews = null;
        model = null;
        renderer = null;
        parent = null;
    }
    
    /**
     * Dynamically updates the rendering when the model changes.
     *
     * @see org.fujion.model.IListModel.IListModelListener#onListChange(org.fujion.model.IListModel.ListEventType,
     *      int, int)
     */
    @Override
    public void onListChange(ListEventType type, int startIndex, int endIndex) {
        paginator.setModelSize(model.size());
        
        switch (type) {
            case ADD:
                startIndex = adjustIndex(startIndex);
                endIndex = adjustIndex(endIndex);

                for (int i = startIndex; i <= endIndex; i++) {
                    renderChild(i);
                }

                break;

            case DELETE:
                startIndex = adjustIndex(startIndex);
                endIndex = adjustIndex(endIndex);

                for (int i = endIndex; i >= startIndex; i--) {
                    destroyChild(i);
                }

                break;

            case CHANGE:
                rerender();
                break;

            case REPLACE:
                onListChange(ListEventType.DELETE, startIndex, endIndex);
                onListChange(ListEventType.ADD, startIndex, endIndex);
                break;

            case SWAP:
                if (paginator.isDisabled()) {
                    parent.swapChildren(startIndex, endIndex);
                }

                break;
            
            case SORT:
                if (!paginator.isDisabled()) {
                    rerender();
                }

                break;
        }
    }
    
    /**
     * Force the model index to be within the current page range.
     *
     * @param modelIndex Model index.
     * @return Adjusted index.
     */
    private int adjustIndex(int modelIndex) {
        if (modelIndex < 0 || paginator.isDisabled()) {
            return modelIndex;
        }
        
        int page = paginator.getCurrentPage();
        int min = paginator.getModelOffset(page);
        int max = paginator.getModelOffset(page + 1) - 1;
        return modelIndex < min ? min : modelIndex > max ? max : modelIndex;
    }
    
    @Override
    public T rerender(M object) {
        return rerender(model.indexOf(object));
    }
    
    @Override
    public T rerender(int modelIndex) {
        if (paginator.inRange(modelIndex)) {
            destroyChild(modelIndex);
            return renderChild(modelIndex);
        } else {
            return null;
        }
    }
    
    @Override
    public boolean getDeferredRendering() {
        return deferredRendering;
    }
    
    @Override
    public void setDeferredRendering(boolean value) {
        deferredRendering = value;
    }
    
    @Override
    public IPaginator getPaginator() {
        return paginator;
    }

    @Override
    public void onPagingChange(PagingEventType type, int oldValue, int newValue) {
        if (type != PagingEventType.MAX_PAGE) {
            rerender();
        }
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy